@webex/contact-center 3.9.0-next.9 → 3.10.0-multi-llms.1
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/dist/cc.js +193 -47
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +1 -0
- package/dist/constants.js.map +1 -1
- package/dist/index.js +9 -0
- package/dist/index.js.map +1 -1
- package/dist/logger-proxy.js +24 -1
- package/dist/logger-proxy.js.map +1 -1
- package/dist/metrics/behavioral-events.js +89 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +30 -2
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AddressBook.js +271 -0
- package/dist/services/AddressBook.js.map +1 -0
- package/dist/services/EntryPoint.js +227 -0
- package/dist/services/EntryPoint.js.map +1 -0
- package/dist/services/Queue.js +261 -0
- package/dist/services/Queue.js.map +1 -0
- package/dist/services/config/constants.js +36 -2
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +29 -21
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +33 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Utils.js +91 -31
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/constants.js +17 -1
- package/dist/services/core/constants.js.map +1 -1
- package/dist/services/task/TaskManager.js +150 -7
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +104 -0
- package/dist/services/task/TaskUtils.js.map +1 -0
- package/dist/services/task/constants.js +26 -1
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/contact.js +86 -0
- package/dist/services/task/contact.js.map +1 -1
- package/dist/services/task/index.js +302 -39
- package/dist/services/task/index.js.map +1 -1
- package/dist/services/task/types.js +12 -0
- package/dist/services/task/types.js.map +1 -1
- package/dist/types/cc.d.ts +121 -35
- package/dist/types/constants.d.ts +1 -0
- package/dist/types/index.d.ts +4 -3
- package/dist/types/metrics/constants.d.ts +24 -1
- package/dist/types/services/AddressBook.d.ts +74 -0
- package/dist/types/services/EntryPoint.d.ts +67 -0
- package/dist/types/services/Queue.d.ts +76 -0
- package/dist/types/services/config/constants.d.ts +35 -1
- package/dist/types/services/config/index.d.ts +6 -9
- package/dist/types/services/config/types.d.ts +79 -58
- package/dist/types/services/core/Utils.d.ts +33 -5
- package/dist/types/services/core/constants.d.ts +14 -0
- package/dist/types/services/task/TaskUtils.d.ts +42 -0
- package/dist/types/services/task/constants.d.ts +23 -0
- package/dist/types/services/task/contact.d.ts +10 -0
- package/dist/types/services/task/index.d.ts +84 -3
- package/dist/types/services/task/types.d.ts +245 -21
- package/dist/types/types.d.ts +162 -0
- package/dist/types/utils/PageCache.d.ts +173 -0
- package/dist/types.js +17 -0
- package/dist/types.js.map +1 -1
- package/dist/utils/PageCache.js +192 -0
- package/dist/utils/PageCache.js.map +1 -0
- package/dist/webex.js +1 -1
- package/package.json +10 -9
- package/src/cc.ts +217 -52
- package/src/constants.ts +1 -0
- package/src/index.ts +17 -2
- package/src/logger-proxy.ts +24 -1
- package/src/metrics/behavioral-events.ts +94 -0
- package/src/metrics/constants.ts +34 -1
- package/src/services/AddressBook.ts +291 -0
- package/src/services/EntryPoint.ts +241 -0
- package/src/services/Queue.ts +277 -0
- package/src/services/config/constants.ts +42 -2
- package/src/services/config/index.ts +30 -30
- package/src/services/config/types.ts +59 -58
- package/src/services/core/Utils.ts +101 -41
- package/src/services/core/constants.ts +16 -0
- package/src/services/task/TaskManager.ts +181 -9
- package/src/services/task/TaskUtils.ts +113 -0
- package/src/services/task/constants.ts +25 -0
- package/src/services/task/contact.ts +80 -0
- package/src/services/task/index.ts +364 -54
- package/src/services/task/types.ts +264 -20
- package/src/types.ts +180 -0
- package/src/utils/PageCache.ts +252 -0
- package/test/unit/spec/cc.ts +282 -85
- package/test/unit/spec/metrics/behavioral-events.ts +42 -0
- package/test/unit/spec/services/AddressBook.ts +332 -0
- package/test/unit/spec/services/EntryPoint.ts +259 -0
- package/test/unit/spec/services/Queue.ts +323 -0
- package/test/unit/spec/services/config/index.ts +279 -65
- package/test/unit/spec/services/core/Utils.ts +262 -31
- package/test/unit/spec/services/task/TaskManager.ts +752 -1
- package/test/unit/spec/services/task/TaskUtils.ts +131 -0
- package/test/unit/spec/services/task/contact.ts +31 -1
- package/test/unit/spec/services/task/index.ts +675 -69
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -17,6 +17,9 @@ import {
|
|
|
17
17
|
TRANSFER,
|
|
18
18
|
UNHOLD,
|
|
19
19
|
WRAPUP,
|
|
20
|
+
CONSULT_CONFERENCE,
|
|
21
|
+
CONFERENCE_EXIT,
|
|
22
|
+
CONFERENCE_TRANSFER,
|
|
20
23
|
} from './constants';
|
|
21
24
|
import * as Contact from './types';
|
|
22
25
|
import {DESTINATION_TYPE} from './types';
|
|
@@ -425,5 +428,82 @@ export default function routingContact(aqm: AqmReqs) {
|
|
|
425
428
|
errId: 'Service.aqm.task.cancelCtq',
|
|
426
429
|
},
|
|
427
430
|
})),
|
|
431
|
+
|
|
432
|
+
/*
|
|
433
|
+
* Start consult conference
|
|
434
|
+
*/
|
|
435
|
+
consultConference: aqm.req(
|
|
436
|
+
(p: {interactionId: string; data: Contact.ConsultConferenceData}) => ({
|
|
437
|
+
url: `${TASK_API}${p.interactionId}${CONSULT_CONFERENCE}`,
|
|
438
|
+
data: p.data,
|
|
439
|
+
host: WCC_API_GATEWAY,
|
|
440
|
+
err,
|
|
441
|
+
notifSuccess: {
|
|
442
|
+
bind: {
|
|
443
|
+
type: TASK_MESSAGE_TYPE,
|
|
444
|
+
data: {
|
|
445
|
+
type: [CC_EVENTS.AGENT_CONSULT_CONFERENCED, CC_EVENTS.AGENT_CONSULT_CONFERENCING],
|
|
446
|
+
interactionId: p.interactionId,
|
|
447
|
+
}, // any of the two events can be received for API success event
|
|
448
|
+
},
|
|
449
|
+
msg: {} as Contact.AgentContact,
|
|
450
|
+
},
|
|
451
|
+
notifFail: {
|
|
452
|
+
bind: {
|
|
453
|
+
type: TASK_MESSAGE_TYPE,
|
|
454
|
+
data: {type: CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED},
|
|
455
|
+
},
|
|
456
|
+
errId: 'Service.aqm.task.consultConference',
|
|
457
|
+
},
|
|
458
|
+
})
|
|
459
|
+
),
|
|
460
|
+
|
|
461
|
+
/*
|
|
462
|
+
* Exit conference
|
|
463
|
+
*/
|
|
464
|
+
exitConference: aqm.req((p: {interactionId: string}) => ({
|
|
465
|
+
url: `${TASK_API}${p.interactionId}${CONFERENCE_EXIT}`,
|
|
466
|
+
data: {},
|
|
467
|
+
host: WCC_API_GATEWAY,
|
|
468
|
+
err,
|
|
469
|
+
notifSuccess: {
|
|
470
|
+
bind: {
|
|
471
|
+
type: TASK_MESSAGE_TYPE,
|
|
472
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, interactionId: p.interactionId},
|
|
473
|
+
},
|
|
474
|
+
msg: {} as Contact.AgentContact,
|
|
475
|
+
},
|
|
476
|
+
notifFail: {
|
|
477
|
+
bind: {
|
|
478
|
+
type: TASK_MESSAGE_TYPE,
|
|
479
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED}, // to be finalized
|
|
480
|
+
},
|
|
481
|
+
errId: 'Service.aqm.task.consultConference',
|
|
482
|
+
},
|
|
483
|
+
})),
|
|
484
|
+
|
|
485
|
+
/*
|
|
486
|
+
* Transfer conference
|
|
487
|
+
*/
|
|
488
|
+
conferenceTransfer: aqm.req((p: {interactionId: string}) => ({
|
|
489
|
+
url: `${TASK_API}${p.interactionId}${CONFERENCE_TRANSFER}`,
|
|
490
|
+
data: {},
|
|
491
|
+
host: WCC_API_GATEWAY,
|
|
492
|
+
err,
|
|
493
|
+
notifSuccess: {
|
|
494
|
+
bind: {
|
|
495
|
+
type: TASK_MESSAGE_TYPE,
|
|
496
|
+
data: {type: CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, interactionId: p.interactionId},
|
|
497
|
+
},
|
|
498
|
+
msg: {} as Contact.AgentContact,
|
|
499
|
+
},
|
|
500
|
+
notifFail: {
|
|
501
|
+
bind: {
|
|
502
|
+
type: TASK_MESSAGE_TYPE,
|
|
503
|
+
data: {type: CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED},
|
|
504
|
+
},
|
|
505
|
+
errId: 'Service.aqm.task.consultConference',
|
|
506
|
+
},
|
|
507
|
+
})),
|
|
428
508
|
};
|
|
429
509
|
}
|
|
@@ -1,15 +1,11 @@
|
|
|
1
1
|
import EventEmitter from 'events';
|
|
2
2
|
import {CALL_EVENT_KEYS, LocalMicrophoneStream} from '@webex/calling';
|
|
3
3
|
import {CallId} from '@webex/calling/dist/types/common/types';
|
|
4
|
-
import {
|
|
5
|
-
generateTaskErrorObject,
|
|
6
|
-
deriveConsultTransferDestinationType,
|
|
7
|
-
getDestinationAgentId,
|
|
8
|
-
} from '../core/Utils';
|
|
4
|
+
import {generateTaskErrorObject, calculateDestAgentId, calculateDestType} from '../core/Utils';
|
|
9
5
|
import {Failure} from '../core/GlobalTypes';
|
|
10
6
|
import {LoginOption} from '../../types';
|
|
11
7
|
import {TASK_FILE} from '../../constants';
|
|
12
|
-
import {METHODS} from './constants';
|
|
8
|
+
import {METHODS, KEYS_TO_NOT_DELETE} from './constants';
|
|
13
9
|
import routingContact from './contact';
|
|
14
10
|
import LoggerProxy from '../../logger-proxy';
|
|
15
11
|
import {
|
|
@@ -138,6 +134,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
138
134
|
public webCallMap: Record<TaskId, CallId>;
|
|
139
135
|
private wrapupData: WrapupData;
|
|
140
136
|
public autoWrapup?: AutoWrapup;
|
|
137
|
+
private agentId: string;
|
|
141
138
|
|
|
142
139
|
/**
|
|
143
140
|
* Creates a new Task instance which provides the following features:
|
|
@@ -150,7 +147,8 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
150
147
|
contact: ReturnType<typeof routingContact>,
|
|
151
148
|
webCallingService: WebCallingService,
|
|
152
149
|
data: TaskData,
|
|
153
|
-
wrapupData: WrapupData
|
|
150
|
+
wrapupData: WrapupData,
|
|
151
|
+
agentId: string
|
|
154
152
|
) {
|
|
155
153
|
super();
|
|
156
154
|
this.contact = contact;
|
|
@@ -161,6 +159,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
161
159
|
this.metricsManager = MetricsManager.getInstance();
|
|
162
160
|
this.registerWebCallListeners();
|
|
163
161
|
this.setupAutoWrapupTimer();
|
|
162
|
+
this.agentId = agentId;
|
|
164
163
|
}
|
|
165
164
|
|
|
166
165
|
/**
|
|
@@ -277,9 +276,24 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
277
276
|
* @private
|
|
278
277
|
*/
|
|
279
278
|
private reconcileData(oldData: TaskData, newData: TaskData): TaskData {
|
|
279
|
+
// Remove keys from oldData that are not in newData
|
|
280
|
+
Object.keys(oldData).forEach((key) => {
|
|
281
|
+
if (!(key in newData) && !KEYS_TO_NOT_DELETE.includes(key as string)) {
|
|
282
|
+
delete oldData[key];
|
|
283
|
+
}
|
|
284
|
+
});
|
|
285
|
+
|
|
286
|
+
// Merge or update keys from newData
|
|
280
287
|
Object.keys(newData).forEach((key) => {
|
|
281
|
-
if (
|
|
282
|
-
|
|
288
|
+
if (
|
|
289
|
+
newData[key] &&
|
|
290
|
+
typeof newData[key] === 'object' &&
|
|
291
|
+
!Array.isArray(newData[key]) &&
|
|
292
|
+
oldData[key] &&
|
|
293
|
+
typeof oldData[key] === 'object' &&
|
|
294
|
+
!Array.isArray(oldData[key])
|
|
295
|
+
) {
|
|
296
|
+
this.reconcileData(oldData[key], newData[key]);
|
|
283
297
|
} else {
|
|
284
298
|
oldData[key] = newData[key];
|
|
285
299
|
}
|
|
@@ -507,6 +521,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
507
521
|
* Puts the current task/interaction on hold.
|
|
508
522
|
* Emits task:hold event when successful. For voice tasks, this mutes the audio.
|
|
509
523
|
*
|
|
524
|
+
* @param mediaResourceId - Optional media resource ID to use for the hold operation. If not provided, uses the task's current mediaResourceId
|
|
510
525
|
* @returns Promise<TaskResponse>
|
|
511
526
|
* @throws Error if hold operation fails
|
|
512
527
|
* @example
|
|
@@ -527,9 +542,17 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
527
542
|
* console.error('Failed to place task on hold:', error);
|
|
528
543
|
* // Handle error (e.g., show error message, reset UI state)
|
|
529
544
|
* }
|
|
545
|
+
*
|
|
546
|
+
* // Place task on hold with custom mediaResourceId
|
|
547
|
+
* try {
|
|
548
|
+
* await task.hold('custom-media-resource-id');
|
|
549
|
+
* console.log('Successfully placed task on hold with custom mediaResourceId');
|
|
550
|
+
* } catch (error) {
|
|
551
|
+
* console.error('Failed to place task on hold:', error);
|
|
552
|
+
* }
|
|
530
553
|
* ```
|
|
531
554
|
*/
|
|
532
|
-
public async hold(): Promise<TaskResponse> {
|
|
555
|
+
public async hold(mediaResourceId?: string): Promise<TaskResponse> {
|
|
533
556
|
try {
|
|
534
557
|
LoggerProxy.info(`Holding task`, {
|
|
535
558
|
module: TASK_FILE,
|
|
@@ -542,9 +565,11 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
542
565
|
METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
|
|
543
566
|
]);
|
|
544
567
|
|
|
568
|
+
const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
|
|
569
|
+
|
|
545
570
|
const response = await this.contact.hold({
|
|
546
571
|
interactionId: this.data.interactionId,
|
|
547
|
-
data: {mediaResourceId:
|
|
572
|
+
data: {mediaResourceId: effectiveMediaResourceId},
|
|
548
573
|
});
|
|
549
574
|
|
|
550
575
|
this.metricsManager.trackEvent(
|
|
@@ -552,7 +577,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
552
577
|
{
|
|
553
578
|
...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
|
|
554
579
|
taskId: this.data.interactionId,
|
|
555
|
-
mediaResourceId:
|
|
580
|
+
mediaResourceId: effectiveMediaResourceId,
|
|
556
581
|
},
|
|
557
582
|
['operational', 'behavioral']
|
|
558
583
|
);
|
|
@@ -574,11 +599,13 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
574
599
|
errorData: err.data?.errorData,
|
|
575
600
|
reasonCode: err.data?.reasonCode,
|
|
576
601
|
};
|
|
602
|
+
const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
|
|
603
|
+
|
|
577
604
|
this.metricsManager.trackEvent(
|
|
578
605
|
METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
|
|
579
606
|
{
|
|
580
607
|
taskId: this.data.interactionId,
|
|
581
|
-
mediaResourceId:
|
|
608
|
+
mediaResourceId: effectiveMediaResourceId,
|
|
582
609
|
error: error.toString(),
|
|
583
610
|
...taskErrorProps,
|
|
584
611
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
|
|
@@ -593,6 +620,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
593
620
|
* Resumes the task/interaction that was previously put on hold.
|
|
594
621
|
* Emits task:resume event when successful. For voice tasks, this restores the audio.
|
|
595
622
|
*
|
|
623
|
+
* @param mediaResourceId - Optional media resource ID to use for the resume operation. If not provided, uses the task's current mediaResourceId from interaction media
|
|
596
624
|
* @returns Promise<TaskResponse>
|
|
597
625
|
* @throws Error if resume operation fails
|
|
598
626
|
* @example
|
|
@@ -613,9 +641,17 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
613
641
|
* console.error('Failed to resume task:', error);
|
|
614
642
|
* // Handle error (e.g., show error message)
|
|
615
643
|
* }
|
|
644
|
+
*
|
|
645
|
+
* // Resume task from hold with custom mediaResourceId
|
|
646
|
+
* try {
|
|
647
|
+
* await task.resume('custom-media-resource-id');
|
|
648
|
+
* console.log('Successfully resumed task from hold with custom mediaResourceId');
|
|
649
|
+
* } catch (error) {
|
|
650
|
+
* console.error('Failed to resume task:', error);
|
|
651
|
+
* }
|
|
616
652
|
* ```
|
|
617
653
|
*/
|
|
618
|
-
public async resume(): Promise<TaskResponse> {
|
|
654
|
+
public async resume(mediaResourceId?: string): Promise<TaskResponse> {
|
|
619
655
|
try {
|
|
620
656
|
LoggerProxy.info(`Resuming task`, {
|
|
621
657
|
module: TASK_FILE,
|
|
@@ -623,7 +659,9 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
623
659
|
interactionId: this.data.interactionId,
|
|
624
660
|
});
|
|
625
661
|
const {mainInteractionId} = this.data.interaction;
|
|
626
|
-
const
|
|
662
|
+
const defaultMediaResourceId =
|
|
663
|
+
this.data.interaction.media[mainInteractionId]?.mediaResourceId;
|
|
664
|
+
const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
|
|
627
665
|
|
|
628
666
|
this.metricsManager.timeEvent([
|
|
629
667
|
METRIC_EVENT_NAMES.TASK_RESUME_SUCCESS,
|
|
@@ -632,7 +670,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
632
670
|
|
|
633
671
|
const response = await this.contact.unHold({
|
|
634
672
|
interactionId: this.data.interactionId,
|
|
635
|
-
data: {mediaResourceId},
|
|
673
|
+
data: {mediaResourceId: effectiveMediaResourceId},
|
|
636
674
|
});
|
|
637
675
|
|
|
638
676
|
this.metricsManager.trackEvent(
|
|
@@ -640,7 +678,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
640
678
|
{
|
|
641
679
|
taskId: this.data.interactionId,
|
|
642
680
|
mainInteractionId,
|
|
643
|
-
mediaResourceId,
|
|
681
|
+
mediaResourceId: effectiveMediaResourceId,
|
|
644
682
|
...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
|
|
645
683
|
},
|
|
646
684
|
['operational', 'behavioral']
|
|
@@ -657,6 +695,11 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
657
695
|
} catch (error) {
|
|
658
696
|
const err = generateTaskErrorObject(error, METHODS.RESUME, TASK_FILE);
|
|
659
697
|
const mainInteractionId = this.data.interaction?.mainInteractionId;
|
|
698
|
+
const defaultMediaResourceId = mainInteractionId
|
|
699
|
+
? this.data.interaction.media[mainInteractionId]?.mediaResourceId
|
|
700
|
+
: '';
|
|
701
|
+
const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
|
|
702
|
+
|
|
660
703
|
const taskErrorProps = {
|
|
661
704
|
trackingId: err.data?.trackingId,
|
|
662
705
|
errorMessage: err.data?.message,
|
|
@@ -669,9 +712,7 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
669
712
|
{
|
|
670
713
|
taskId: this.data.interactionId,
|
|
671
714
|
mainInteractionId,
|
|
672
|
-
mediaResourceId:
|
|
673
|
-
? this.data.interaction.media[mainInteractionId].mediaResourceId
|
|
674
|
-
: '',
|
|
715
|
+
mediaResourceId: effectiveMediaResourceId,
|
|
675
716
|
...taskErrorProps,
|
|
676
717
|
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
|
|
677
718
|
},
|
|
@@ -1401,35 +1442,31 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1401
1442
|
public async consultTransfer(
|
|
1402
1443
|
consultTransferPayload?: ConsultTransferPayLoad
|
|
1403
1444
|
): Promise<TaskResponse> {
|
|
1404
|
-
|
|
1405
|
-
|
|
1406
|
-
const destAgentId = getDestinationAgentId(
|
|
1407
|
-
this.data.interaction?.participants,
|
|
1408
|
-
this.data.agentId
|
|
1409
|
-
);
|
|
1410
|
-
|
|
1411
|
-
// Resolve the target id (queue consult transfers go to the accepted agent)
|
|
1412
|
-
if (!destAgentId) {
|
|
1413
|
-
throw new Error('No agent has accepted this queue consult yet');
|
|
1414
|
-
}
|
|
1415
|
-
|
|
1416
|
-
LoggerProxy.info(
|
|
1417
|
-
`Initiating consult transfer to ${consultTransferPayload?.to || destAgentId}`,
|
|
1418
|
-
{
|
|
1419
|
-
module: TASK_FILE,
|
|
1420
|
-
method: METHODS.CONSULT_TRANSFER,
|
|
1421
|
-
interactionId: this.data.interactionId,
|
|
1422
|
-
}
|
|
1423
|
-
);
|
|
1424
|
-
// Obtain payload based on desktop logic using TaskData
|
|
1425
|
-
const finalDestinationType = deriveConsultTransferDestinationType(this.data);
|
|
1445
|
+
// Get the destination agent ID using custom logic from participants data
|
|
1446
|
+
const destAgentId = calculateDestAgentId(this.data.interaction, this.agentId);
|
|
1426
1447
|
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1430
|
-
|
|
1431
|
-
};
|
|
1448
|
+
// Resolve the target id (queue consult transfers go to the accepted agent)
|
|
1449
|
+
if (!destAgentId) {
|
|
1450
|
+
throw new Error('No agent has accepted this queue consult yet');
|
|
1451
|
+
}
|
|
1432
1452
|
|
|
1453
|
+
LoggerProxy.info(
|
|
1454
|
+
`Initiating consult transfer to ${consultTransferPayload?.to || destAgentId}`,
|
|
1455
|
+
{
|
|
1456
|
+
module: TASK_FILE,
|
|
1457
|
+
method: METHODS.CONSULT_TRANSFER,
|
|
1458
|
+
interactionId: this.data.interactionId,
|
|
1459
|
+
}
|
|
1460
|
+
);
|
|
1461
|
+
|
|
1462
|
+
// Derive destination type from the participant's type property
|
|
1463
|
+
const destType = calculateDestType(this.data.interaction, this.agentId);
|
|
1464
|
+
// By default we always use the computed destAgentId as the target id
|
|
1465
|
+
const consultTransferRequest: ConsultTransferPayLoad = {
|
|
1466
|
+
to: destAgentId,
|
|
1467
|
+
destinationType: destType,
|
|
1468
|
+
};
|
|
1469
|
+
try {
|
|
1433
1470
|
const result = await this.contact.consultTransfer({
|
|
1434
1471
|
interactionId: this.data.interactionId,
|
|
1435
1472
|
data: consultTransferRequest,
|
|
@@ -1467,17 +1504,12 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1467
1504
|
errorData: err.data?.errorData,
|
|
1468
1505
|
reasonCode: err.data?.reasonCode,
|
|
1469
1506
|
};
|
|
1470
|
-
const failedDestinationType = deriveConsultTransferDestinationType(this.data);
|
|
1471
|
-
const failedDestAgentId = getDestinationAgentId(
|
|
1472
|
-
this.data.interaction?.participants,
|
|
1473
|
-
this.data.agentId
|
|
1474
|
-
);
|
|
1475
1507
|
this.metricsManager.trackEvent(
|
|
1476
1508
|
METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
|
|
1477
1509
|
{
|
|
1478
1510
|
taskId: this.data.interactionId,
|
|
1479
|
-
destination:
|
|
1480
|
-
destinationType:
|
|
1511
|
+
destination: destAgentId || '',
|
|
1512
|
+
destinationType: destType,
|
|
1481
1513
|
isConsultTransfer: true,
|
|
1482
1514
|
error: error.toString(),
|
|
1483
1515
|
...taskErrorProps,
|
|
@@ -1488,4 +1520,282 @@ export default class Task extends EventEmitter implements ITask {
|
|
|
1488
1520
|
throw err;
|
|
1489
1521
|
}
|
|
1490
1522
|
}
|
|
1523
|
+
|
|
1524
|
+
/**
|
|
1525
|
+
* Starts a consultation conference by merging the consultation call with the main call
|
|
1526
|
+
*
|
|
1527
|
+
* Creates a three-way conference between the agent, customer, and consulted party
|
|
1528
|
+
* Extracts required consultation data from the current task data
|
|
1529
|
+
* On success, emits a `task:conferenceStarted` event
|
|
1530
|
+
*
|
|
1531
|
+
* @returns Promise<TaskResponse> - Response from the consultation conference API
|
|
1532
|
+
* @throws Error if the operation fails or if consultation data is invalid
|
|
1533
|
+
*
|
|
1534
|
+
* @example
|
|
1535
|
+
* ```typescript
|
|
1536
|
+
* try {
|
|
1537
|
+
* await task.consultConference();
|
|
1538
|
+
* console.log('Conference started successfully');
|
|
1539
|
+
* } catch (error) {
|
|
1540
|
+
* console.error('Failed to start conference:', error);
|
|
1541
|
+
* }
|
|
1542
|
+
* ```
|
|
1543
|
+
*/
|
|
1544
|
+
public async consultConference(): Promise<TaskResponse> {
|
|
1545
|
+
// Get the destination agent ID dynamically from participants
|
|
1546
|
+
// This handles multi-party conference scenarios, CBT (Capacity Based Team), and EP-DN cases
|
|
1547
|
+
const destAgentId = calculateDestAgentId(this.data.interaction, this.agentId);
|
|
1548
|
+
|
|
1549
|
+
// Validate that we have a destination agent (for queue consult scenarios)
|
|
1550
|
+
if (!destAgentId) {
|
|
1551
|
+
throw new Error('No agent has accepted this queue consult yet');
|
|
1552
|
+
}
|
|
1553
|
+
|
|
1554
|
+
// Get the destination agent ID for fetching destination type
|
|
1555
|
+
// This helps determine the correct participant type for CBT (Capacity Based Team) and EP-DN scenarios
|
|
1556
|
+
const destAgentType = calculateDestType(this.data.interaction, this.agentId);
|
|
1557
|
+
|
|
1558
|
+
// Extract consultation conference data from task data (used in both try and catch)
|
|
1559
|
+
const consultationData = {
|
|
1560
|
+
agentId: this.agentId,
|
|
1561
|
+
to: destAgentId,
|
|
1562
|
+
destinationType: destAgentType || this.data.destinationType || 'agent',
|
|
1563
|
+
};
|
|
1564
|
+
|
|
1565
|
+
try {
|
|
1566
|
+
LoggerProxy.info(`Initiating consult conference to ${destAgentId}`, {
|
|
1567
|
+
module: TASK_FILE,
|
|
1568
|
+
method: METHODS.CONSULT_CONFERENCE,
|
|
1569
|
+
interactionId: this.data.interactionId,
|
|
1570
|
+
});
|
|
1571
|
+
|
|
1572
|
+
const response = await this.contact.consultConference({
|
|
1573
|
+
interactionId: this.data.interactionId,
|
|
1574
|
+
data: consultationData,
|
|
1575
|
+
});
|
|
1576
|
+
|
|
1577
|
+
// Track success metrics (following consultTransfer pattern)
|
|
1578
|
+
this.metricsManager.trackEvent(
|
|
1579
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_START_SUCCESS,
|
|
1580
|
+
{
|
|
1581
|
+
taskId: this.data.interactionId,
|
|
1582
|
+
destination: consultationData.to,
|
|
1583
|
+
destinationType: consultationData.destinationType,
|
|
1584
|
+
agentId: consultationData.agentId,
|
|
1585
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
|
|
1586
|
+
},
|
|
1587
|
+
['operational', 'behavioral', 'business']
|
|
1588
|
+
);
|
|
1589
|
+
|
|
1590
|
+
LoggerProxy.log(`Consult conference started successfully`, {
|
|
1591
|
+
module: TASK_FILE,
|
|
1592
|
+
method: METHODS.CONSULT_CONFERENCE,
|
|
1593
|
+
interactionId: this.data.interactionId,
|
|
1594
|
+
});
|
|
1595
|
+
|
|
1596
|
+
return response;
|
|
1597
|
+
} catch (error) {
|
|
1598
|
+
const err = generateTaskErrorObject(error, METHODS.CONSULT_CONFERENCE, TASK_FILE);
|
|
1599
|
+
const taskErrorProps = {
|
|
1600
|
+
trackingId: err.data?.trackingId,
|
|
1601
|
+
errorMessage: err.data?.message,
|
|
1602
|
+
errorType: err.data?.errorType,
|
|
1603
|
+
errorData: err.data?.errorData,
|
|
1604
|
+
reasonCode: err.data?.reasonCode,
|
|
1605
|
+
};
|
|
1606
|
+
|
|
1607
|
+
this.metricsManager.trackEvent(
|
|
1608
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_START_FAILED,
|
|
1609
|
+
{
|
|
1610
|
+
taskId: this.data.interactionId,
|
|
1611
|
+
destination: consultationData.to,
|
|
1612
|
+
destinationType: consultationData.destinationType,
|
|
1613
|
+
agentId: consultationData.agentId,
|
|
1614
|
+
error: error.toString(),
|
|
1615
|
+
...taskErrorProps,
|
|
1616
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
|
|
1617
|
+
},
|
|
1618
|
+
['operational', 'behavioral', 'business']
|
|
1619
|
+
);
|
|
1620
|
+
|
|
1621
|
+
LoggerProxy.error(`Failed to start consult conference`, {
|
|
1622
|
+
module: TASK_FILE,
|
|
1623
|
+
method: METHODS.CONSULT_CONFERENCE,
|
|
1624
|
+
interactionId: this.data.interactionId,
|
|
1625
|
+
});
|
|
1626
|
+
|
|
1627
|
+
throw err;
|
|
1628
|
+
}
|
|
1629
|
+
}
|
|
1630
|
+
|
|
1631
|
+
/**
|
|
1632
|
+
* Exits the current conference by removing the agent from the conference call
|
|
1633
|
+
*
|
|
1634
|
+
* Exits the agent from the conference, leaving the customer and consulted party connected
|
|
1635
|
+
* On success, emits a `task:conferenceEnded` event
|
|
1636
|
+
*
|
|
1637
|
+
* @returns Promise<TaskResponse> - Response from the conference exit API
|
|
1638
|
+
* @throws Error if the operation fails or if no active conference exists
|
|
1639
|
+
*
|
|
1640
|
+
* @example
|
|
1641
|
+
* ```typescript
|
|
1642
|
+
* try {
|
|
1643
|
+
* await task.exitConference();
|
|
1644
|
+
* console.log('Successfully exited conference');
|
|
1645
|
+
* } catch (error) {
|
|
1646
|
+
* console.error('Failed to exit conference:', error);
|
|
1647
|
+
* }
|
|
1648
|
+
* ```
|
|
1649
|
+
*/
|
|
1650
|
+
public async exitConference(): Promise<TaskResponse> {
|
|
1651
|
+
try {
|
|
1652
|
+
LoggerProxy.info(`Exiting consult conference`, {
|
|
1653
|
+
module: TASK_FILE,
|
|
1654
|
+
method: METHODS.EXIT_CONFERENCE,
|
|
1655
|
+
interactionId: this.data.interactionId,
|
|
1656
|
+
});
|
|
1657
|
+
|
|
1658
|
+
// Validate that interaction ID exists
|
|
1659
|
+
if (!this.data.interactionId) {
|
|
1660
|
+
throw new Error('Invalid interaction ID');
|
|
1661
|
+
}
|
|
1662
|
+
|
|
1663
|
+
const response = await this.contact.exitConference({
|
|
1664
|
+
interactionId: this.data.interactionId,
|
|
1665
|
+
});
|
|
1666
|
+
|
|
1667
|
+
// Track success metrics (following consultTransfer pattern)
|
|
1668
|
+
this.metricsManager.trackEvent(
|
|
1669
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_END_SUCCESS,
|
|
1670
|
+
{
|
|
1671
|
+
taskId: this.data.interactionId,
|
|
1672
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
|
|
1673
|
+
},
|
|
1674
|
+
['operational', 'behavioral', 'business']
|
|
1675
|
+
);
|
|
1676
|
+
|
|
1677
|
+
LoggerProxy.log(`Consult conference exited successfully`, {
|
|
1678
|
+
module: TASK_FILE,
|
|
1679
|
+
method: METHODS.EXIT_CONFERENCE,
|
|
1680
|
+
interactionId: this.data.interactionId,
|
|
1681
|
+
});
|
|
1682
|
+
|
|
1683
|
+
return response;
|
|
1684
|
+
} catch (error) {
|
|
1685
|
+
const err = generateTaskErrorObject(error, METHODS.EXIT_CONFERENCE, TASK_FILE);
|
|
1686
|
+
const taskErrorProps = {
|
|
1687
|
+
trackingId: err.data?.trackingId,
|
|
1688
|
+
errorMessage: err.data?.message,
|
|
1689
|
+
errorType: err.data?.errorType,
|
|
1690
|
+
errorData: err.data?.errorData,
|
|
1691
|
+
reasonCode: err.data?.reasonCode,
|
|
1692
|
+
};
|
|
1693
|
+
|
|
1694
|
+
// Track failure metrics (following consultTransfer pattern)
|
|
1695
|
+
this.metricsManager.trackEvent(
|
|
1696
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_END_FAILED,
|
|
1697
|
+
{
|
|
1698
|
+
taskId: this.data.interactionId,
|
|
1699
|
+
error: error.toString(),
|
|
1700
|
+
...taskErrorProps,
|
|
1701
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
|
|
1702
|
+
},
|
|
1703
|
+
['operational', 'behavioral', 'business']
|
|
1704
|
+
);
|
|
1705
|
+
|
|
1706
|
+
LoggerProxy.error(`Failed to exit consult conference`, {
|
|
1707
|
+
module: TASK_FILE,
|
|
1708
|
+
method: METHODS.EXIT_CONFERENCE,
|
|
1709
|
+
interactionId: this.data.interactionId,
|
|
1710
|
+
});
|
|
1711
|
+
|
|
1712
|
+
throw err;
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
|
|
1716
|
+
/**
|
|
1717
|
+
* Transfers the current conference to another agent
|
|
1718
|
+
*
|
|
1719
|
+
* Moves the entire conference (including all participants) to a new agent,
|
|
1720
|
+
* while the current agent exits and goes to wrapup
|
|
1721
|
+
* On success, the current agent receives `task:conferenceEnded` event
|
|
1722
|
+
*
|
|
1723
|
+
* @returns Promise<TaskResponse> - Response from the conference transfer API
|
|
1724
|
+
* @throws Error if the operation fails or if no active conference exists
|
|
1725
|
+
*
|
|
1726
|
+
* @example
|
|
1727
|
+
* ```typescript
|
|
1728
|
+
* try {
|
|
1729
|
+
* await task.transferConference();
|
|
1730
|
+
* console.log('Conference transferred successfully');
|
|
1731
|
+
* } catch (error) {
|
|
1732
|
+
* console.error('Failed to transfer conference:', error);
|
|
1733
|
+
* }
|
|
1734
|
+
* ```
|
|
1735
|
+
*/
|
|
1736
|
+
public async transferConference(): Promise<TaskResponse> {
|
|
1737
|
+
try {
|
|
1738
|
+
LoggerProxy.info(`Transferring conference`, {
|
|
1739
|
+
module: TASK_FILE,
|
|
1740
|
+
method: METHODS.TRANSFER_CONFERENCE,
|
|
1741
|
+
interactionId: this.data.interactionId,
|
|
1742
|
+
});
|
|
1743
|
+
|
|
1744
|
+
// Validate that interaction ID exists
|
|
1745
|
+
if (!this.data.interactionId) {
|
|
1746
|
+
throw new Error('Invalid interaction ID');
|
|
1747
|
+
}
|
|
1748
|
+
|
|
1749
|
+
const response = await this.contact.conferenceTransfer({
|
|
1750
|
+
interactionId: this.data.interactionId,
|
|
1751
|
+
});
|
|
1752
|
+
|
|
1753
|
+
// Track success metrics (following consultTransfer pattern)
|
|
1754
|
+
this.metricsManager.trackEvent(
|
|
1755
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_TRANSFER_SUCCESS,
|
|
1756
|
+
{
|
|
1757
|
+
taskId: this.data.interactionId,
|
|
1758
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
|
|
1759
|
+
},
|
|
1760
|
+
['operational', 'behavioral', 'business']
|
|
1761
|
+
);
|
|
1762
|
+
|
|
1763
|
+
LoggerProxy.log(`Conference transferred successfully`, {
|
|
1764
|
+
module: TASK_FILE,
|
|
1765
|
+
method: METHODS.TRANSFER_CONFERENCE,
|
|
1766
|
+
interactionId: this.data.interactionId,
|
|
1767
|
+
});
|
|
1768
|
+
|
|
1769
|
+
return response;
|
|
1770
|
+
} catch (error) {
|
|
1771
|
+
const err = generateTaskErrorObject(error, METHODS.TRANSFER_CONFERENCE, TASK_FILE);
|
|
1772
|
+
const taskErrorProps = {
|
|
1773
|
+
trackingId: err.data?.trackingId,
|
|
1774
|
+
errorMessage: err.data?.message,
|
|
1775
|
+
errorType: err.data?.errorType,
|
|
1776
|
+
errorData: err.data?.errorData,
|
|
1777
|
+
reasonCode: err.data?.reasonCode,
|
|
1778
|
+
};
|
|
1779
|
+
|
|
1780
|
+
// Track failure metrics (following consultTransfer pattern)
|
|
1781
|
+
this.metricsManager.trackEvent(
|
|
1782
|
+
METRIC_EVENT_NAMES.TASK_CONFERENCE_TRANSFER_FAILED,
|
|
1783
|
+
{
|
|
1784
|
+
taskId: this.data.interactionId,
|
|
1785
|
+
error: error.toString(),
|
|
1786
|
+
...taskErrorProps,
|
|
1787
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
|
|
1788
|
+
},
|
|
1789
|
+
['operational', 'behavioral', 'business']
|
|
1790
|
+
);
|
|
1791
|
+
|
|
1792
|
+
LoggerProxy.error(`Failed to transfer conference`, {
|
|
1793
|
+
module: TASK_FILE,
|
|
1794
|
+
method: METHODS.TRANSFER_CONFERENCE,
|
|
1795
|
+
interactionId: this.data.interactionId,
|
|
1796
|
+
});
|
|
1797
|
+
|
|
1798
|
+
throw err;
|
|
1799
|
+
}
|
|
1800
|
+
}
|
|
1491
1801
|
}
|