@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.
Files changed (100) hide show
  1. package/dist/cc.js +193 -47
  2. package/dist/cc.js.map +1 -1
  3. package/dist/constants.js +1 -0
  4. package/dist/constants.js.map +1 -1
  5. package/dist/index.js +9 -0
  6. package/dist/index.js.map +1 -1
  7. package/dist/logger-proxy.js +24 -1
  8. package/dist/logger-proxy.js.map +1 -1
  9. package/dist/metrics/behavioral-events.js +89 -0
  10. package/dist/metrics/behavioral-events.js.map +1 -1
  11. package/dist/metrics/constants.js +30 -2
  12. package/dist/metrics/constants.js.map +1 -1
  13. package/dist/services/AddressBook.js +271 -0
  14. package/dist/services/AddressBook.js.map +1 -0
  15. package/dist/services/EntryPoint.js +227 -0
  16. package/dist/services/EntryPoint.js.map +1 -0
  17. package/dist/services/Queue.js +261 -0
  18. package/dist/services/Queue.js.map +1 -0
  19. package/dist/services/config/constants.js +36 -2
  20. package/dist/services/config/constants.js.map +1 -1
  21. package/dist/services/config/index.js +29 -21
  22. package/dist/services/config/index.js.map +1 -1
  23. package/dist/services/config/types.js +33 -1
  24. package/dist/services/config/types.js.map +1 -1
  25. package/dist/services/core/Utils.js +91 -31
  26. package/dist/services/core/Utils.js.map +1 -1
  27. package/dist/services/core/constants.js +17 -1
  28. package/dist/services/core/constants.js.map +1 -1
  29. package/dist/services/task/TaskManager.js +150 -7
  30. package/dist/services/task/TaskManager.js.map +1 -1
  31. package/dist/services/task/TaskUtils.js +104 -0
  32. package/dist/services/task/TaskUtils.js.map +1 -0
  33. package/dist/services/task/constants.js +26 -1
  34. package/dist/services/task/constants.js.map +1 -1
  35. package/dist/services/task/contact.js +86 -0
  36. package/dist/services/task/contact.js.map +1 -1
  37. package/dist/services/task/index.js +302 -39
  38. package/dist/services/task/index.js.map +1 -1
  39. package/dist/services/task/types.js +12 -0
  40. package/dist/services/task/types.js.map +1 -1
  41. package/dist/types/cc.d.ts +121 -35
  42. package/dist/types/constants.d.ts +1 -0
  43. package/dist/types/index.d.ts +4 -3
  44. package/dist/types/metrics/constants.d.ts +24 -1
  45. package/dist/types/services/AddressBook.d.ts +74 -0
  46. package/dist/types/services/EntryPoint.d.ts +67 -0
  47. package/dist/types/services/Queue.d.ts +76 -0
  48. package/dist/types/services/config/constants.d.ts +35 -1
  49. package/dist/types/services/config/index.d.ts +6 -9
  50. package/dist/types/services/config/types.d.ts +79 -58
  51. package/dist/types/services/core/Utils.d.ts +33 -5
  52. package/dist/types/services/core/constants.d.ts +14 -0
  53. package/dist/types/services/task/TaskUtils.d.ts +42 -0
  54. package/dist/types/services/task/constants.d.ts +23 -0
  55. package/dist/types/services/task/contact.d.ts +10 -0
  56. package/dist/types/services/task/index.d.ts +84 -3
  57. package/dist/types/services/task/types.d.ts +245 -21
  58. package/dist/types/types.d.ts +162 -0
  59. package/dist/types/utils/PageCache.d.ts +173 -0
  60. package/dist/types.js +17 -0
  61. package/dist/types.js.map +1 -1
  62. package/dist/utils/PageCache.js +192 -0
  63. package/dist/utils/PageCache.js.map +1 -0
  64. package/dist/webex.js +1 -1
  65. package/package.json +10 -9
  66. package/src/cc.ts +217 -52
  67. package/src/constants.ts +1 -0
  68. package/src/index.ts +17 -2
  69. package/src/logger-proxy.ts +24 -1
  70. package/src/metrics/behavioral-events.ts +94 -0
  71. package/src/metrics/constants.ts +34 -1
  72. package/src/services/AddressBook.ts +291 -0
  73. package/src/services/EntryPoint.ts +241 -0
  74. package/src/services/Queue.ts +277 -0
  75. package/src/services/config/constants.ts +42 -2
  76. package/src/services/config/index.ts +30 -30
  77. package/src/services/config/types.ts +59 -58
  78. package/src/services/core/Utils.ts +101 -41
  79. package/src/services/core/constants.ts +16 -0
  80. package/src/services/task/TaskManager.ts +181 -9
  81. package/src/services/task/TaskUtils.ts +113 -0
  82. package/src/services/task/constants.ts +25 -0
  83. package/src/services/task/contact.ts +80 -0
  84. package/src/services/task/index.ts +364 -54
  85. package/src/services/task/types.ts +264 -20
  86. package/src/types.ts +180 -0
  87. package/src/utils/PageCache.ts +252 -0
  88. package/test/unit/spec/cc.ts +282 -85
  89. package/test/unit/spec/metrics/behavioral-events.ts +42 -0
  90. package/test/unit/spec/services/AddressBook.ts +332 -0
  91. package/test/unit/spec/services/EntryPoint.ts +259 -0
  92. package/test/unit/spec/services/Queue.ts +323 -0
  93. package/test/unit/spec/services/config/index.ts +279 -65
  94. package/test/unit/spec/services/core/Utils.ts +262 -31
  95. package/test/unit/spec/services/task/TaskManager.ts +752 -1
  96. package/test/unit/spec/services/task/TaskUtils.ts +131 -0
  97. package/test/unit/spec/services/task/contact.ts +31 -1
  98. package/test/unit/spec/services/task/index.ts +675 -69
  99. package/umd/contact-center.min.js +2 -2
  100. 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 (newData[key] && typeof newData[key] === 'object' && !Array.isArray(newData[key])) {
282
- oldData[key] = this.reconcileData({...oldData[key]}, newData[key]);
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: this.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: this.data.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: this.data.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 {mediaResourceId} = this.data.interaction.media[mainInteractionId];
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: mainInteractionId
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
- try {
1405
- // Get the destination agent ID using custom logic from participants data
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
- // By default we always use the computed destAgentId as the target id
1428
- const consultTransferRequest: ConsultTransferPayLoad = {
1429
- to: destAgentId,
1430
- destinationType: finalDestinationType,
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: failedDestAgentId || '',
1480
- destinationType: failedDestinationType,
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
  }