@webex/contact-center 3.10.0-next.7 → 3.10.0-next.8

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 (41) hide show
  1. package/dist/cc.js +11 -0
  2. package/dist/cc.js.map +1 -1
  3. package/dist/index.js.map +1 -1
  4. package/dist/services/config/types.js +2 -2
  5. package/dist/services/config/types.js.map +1 -1
  6. package/dist/services/core/Utils.js +90 -71
  7. package/dist/services/core/Utils.js.map +1 -1
  8. package/dist/services/core/constants.js +17 -1
  9. package/dist/services/core/constants.js.map +1 -1
  10. package/dist/services/task/TaskManager.js +61 -36
  11. package/dist/services/task/TaskManager.js.map +1 -1
  12. package/dist/services/task/TaskUtils.js +33 -5
  13. package/dist/services/task/TaskUtils.js.map +1 -1
  14. package/dist/services/task/index.js +49 -58
  15. package/dist/services/task/index.js.map +1 -1
  16. package/dist/services/task/types.js +2 -4
  17. package/dist/services/task/types.js.map +1 -1
  18. package/dist/types/cc.d.ts +6 -0
  19. package/dist/types/index.d.ts +1 -1
  20. package/dist/types/services/config/types.d.ts +4 -4
  21. package/dist/types/services/core/Utils.d.ts +32 -17
  22. package/dist/types/services/core/constants.d.ts +14 -0
  23. package/dist/types/services/task/TaskUtils.d.ts +17 -3
  24. package/dist/types/services/task/types.d.ts +25 -13
  25. package/dist/webex.js +1 -1
  26. package/package.json +1 -1
  27. package/src/cc.ts +11 -0
  28. package/src/index.ts +1 -0
  29. package/src/services/config/types.ts +2 -2
  30. package/src/services/core/Utils.ts +101 -85
  31. package/src/services/core/constants.ts +16 -0
  32. package/src/services/task/TaskManager.ts +75 -28
  33. package/src/services/task/TaskUtils.ts +37 -5
  34. package/src/services/task/index.ts +54 -89
  35. package/src/services/task/types.ts +26 -13
  36. package/test/unit/spec/services/core/Utils.ts +262 -31
  37. package/test/unit/spec/services/task/TaskManager.ts +224 -1
  38. package/test/unit/spec/services/task/TaskUtils.ts +6 -6
  39. package/test/unit/spec/services/task/index.ts +283 -86
  40. package/umd/contact-center.min.js +2 -2
  41. package/umd/contact-center.min.js.map +1 -1
@@ -1,12 +1,7 @@
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
- buildConsultConferenceParamData,
9
- } from '../core/Utils';
4
+ import {generateTaskErrorObject, calculateDestAgentId, calculateDestType} from '../core/Utils';
10
5
  import {Failure} from '../core/GlobalTypes';
11
6
  import {LoginOption} from '../../types';
12
7
  import {TASK_FILE} from '../../constants';
@@ -1447,35 +1442,31 @@ export default class Task extends EventEmitter implements ITask {
1447
1442
  public async consultTransfer(
1448
1443
  consultTransferPayload?: ConsultTransferPayLoad
1449
1444
  ): Promise<TaskResponse> {
1450
- try {
1451
- // Get the destination agent ID using custom logic from participants data
1452
- const destAgentId = getDestinationAgentId(
1453
- this.data.interaction?.participants,
1454
- this.data.agentId
1455
- );
1456
-
1457
- // Resolve the target id (queue consult transfers go to the accepted agent)
1458
- if (!destAgentId) {
1459
- throw new Error('No agent has accepted this queue consult yet');
1460
- }
1445
+ // Get the destination agent ID using custom logic from participants data
1446
+ const destAgentId = calculateDestAgentId(this.data.interaction, this.agentId);
1461
1447
 
1462
- LoggerProxy.info(
1463
- `Initiating consult transfer to ${consultTransferPayload?.to || destAgentId}`,
1464
- {
1465
- module: TASK_FILE,
1466
- method: METHODS.CONSULT_TRANSFER,
1467
- interactionId: this.data.interactionId,
1468
- }
1469
- );
1470
- // Obtain payload based on desktop logic using TaskData
1471
- const finalDestinationType = deriveConsultTransferDestinationType(this.data);
1472
-
1473
- // By default we always use the computed destAgentId as the target id
1474
- const consultTransferRequest: ConsultTransferPayLoad = {
1475
- to: destAgentId,
1476
- destinationType: finalDestinationType,
1477
- };
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
+ }
1478
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 {
1479
1470
  const result = await this.contact.consultTransfer({
1480
1471
  interactionId: this.data.interactionId,
1481
1472
  data: consultTransferRequest,
@@ -1513,17 +1504,12 @@ export default class Task extends EventEmitter implements ITask {
1513
1504
  errorData: err.data?.errorData,
1514
1505
  reasonCode: err.data?.reasonCode,
1515
1506
  };
1516
- const failedDestinationType = deriveConsultTransferDestinationType(this.data);
1517
- const failedDestAgentId = getDestinationAgentId(
1518
- this.data.interaction?.participants,
1519
- this.data.agentId
1520
- );
1521
1507
  this.metricsManager.trackEvent(
1522
1508
  METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1523
1509
  {
1524
1510
  taskId: this.data.interactionId,
1525
- destination: failedDestAgentId || '',
1526
- destinationType: failedDestinationType,
1511
+ destination: destAgentId || '',
1512
+ destinationType: destType,
1527
1513
  isConsultTransfer: true,
1528
1514
  error: error.toString(),
1529
1515
  ...taskErrorProps,
@@ -1556,38 +1542,36 @@ export default class Task extends EventEmitter implements ITask {
1556
1542
  * ```
1557
1543
  */
1558
1544
  public async consultConference(): Promise<TaskResponse> {
1559
- try {
1560
- // Get the destination agent ID using custom logic from participants data (same as consultTransfer)
1561
- const destAgentId = getDestinationAgentId(
1562
- this.data.interaction?.participants,
1563
- this.data.agentId
1564
- );
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);
1565
1548
 
1566
- // Validate that we have a destination agent (for queue consult scenarios)
1567
- if (!destAgentId) {
1568
- throw new Error('No agent has accepted this queue consult yet');
1569
- }
1570
- // Extract consultation conference data from task data (used in both try and catch)
1571
- const consultationData = {
1572
- agentId: this.agentId,
1573
- destAgentId,
1574
- destinationType: this.data.destinationType || 'agent',
1575
- };
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
+ }
1576
1553
 
1577
- LoggerProxy.info(`Initiating consult conference to ${consultationData.destAgentId}`, {
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}`, {
1578
1567
  module: TASK_FILE,
1579
1568
  method: METHODS.CONSULT_CONFERENCE,
1580
1569
  interactionId: this.data.interactionId,
1581
1570
  });
1582
1571
 
1583
- const paramsDataForConferenceV2 = buildConsultConferenceParamData(
1584
- consultationData,
1585
- this.data.interactionId
1586
- );
1587
-
1588
1572
  const response = await this.contact.consultConference({
1589
- interactionId: paramsDataForConferenceV2.interactionId,
1590
- data: paramsDataForConferenceV2.data,
1573
+ interactionId: this.data.interactionId,
1574
+ data: consultationData,
1591
1575
  });
1592
1576
 
1593
1577
  // Track success metrics (following consultTransfer pattern)
@@ -1595,9 +1579,9 @@ export default class Task extends EventEmitter implements ITask {
1595
1579
  METRIC_EVENT_NAMES.TASK_CONFERENCE_START_SUCCESS,
1596
1580
  {
1597
1581
  taskId: this.data.interactionId,
1598
- destination: paramsDataForConferenceV2.data.to,
1599
- destinationType: paramsDataForConferenceV2.data.destinationType,
1600
- agentId: paramsDataForConferenceV2.data.agentId,
1582
+ destination: consultationData.to,
1583
+ destinationType: consultationData.destinationType,
1584
+ agentId: consultationData.agentId,
1601
1585
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
1602
1586
  },
1603
1587
  ['operational', 'behavioral', 'business']
@@ -1620,32 +1604,13 @@ export default class Task extends EventEmitter implements ITask {
1620
1604
  reasonCode: err.data?.reasonCode,
1621
1605
  };
1622
1606
 
1623
- // Track failure metrics (following consultTransfer pattern)
1624
- // Recalculate destination info for error tracking
1625
- const failedDestAgentId = getDestinationAgentId(
1626
- this.data.interaction?.participants,
1627
- this.data.agentId
1628
- );
1629
-
1630
- // Build conference data for error tracking using recalculated data
1631
- const failedConsultationData = {
1632
- agentId: this.agentId,
1633
- destAgentId: failedDestAgentId,
1634
- destinationType: this.data.destinationType || 'agent',
1635
- };
1636
-
1637
- const failedParamsData = buildConsultConferenceParamData(
1638
- failedConsultationData,
1639
- this.data.interactionId
1640
- );
1641
-
1642
1607
  this.metricsManager.trackEvent(
1643
1608
  METRIC_EVENT_NAMES.TASK_CONFERENCE_START_FAILED,
1644
1609
  {
1645
1610
  taskId: this.data.interactionId,
1646
- destination: failedParamsData.data.to,
1647
- destinationType: failedParamsData.data.destinationType,
1648
- agentId: failedParamsData.data.agentId,
1611
+ destination: consultationData.to,
1612
+ destinationType: consultationData.destinationType,
1613
+ agentId: consultationData.agentId,
1649
1614
  error: error.toString(),
1650
1615
  ...taskErrorProps,
1651
1616
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
@@ -490,6 +490,30 @@ export enum TASK_EVENTS {
490
490
  * ```
491
491
  */
492
492
  TASK_PARTICIPANT_LEFT_FAILED = 'task:participantLeftFailed',
493
+
494
+ /**
495
+ * Triggered when a contact is merged
496
+ * @example
497
+ * ```typescript
498
+ * task.on(TASK_EVENTS.TASK_MERGED, (task: ITask) => {
499
+ * console.log('Contact merged:', task.data.interactionId);
500
+ * // Handle contact merge
501
+ * });
502
+ * ```
503
+ */
504
+ TASK_MERGED = 'task:merged',
505
+
506
+ /**
507
+ * Triggered when a participant enters post-call activity state
508
+ * @example
509
+ * ```typescript
510
+ * task.on(TASK_EVENTS.TASK_POST_CALL_ACTIVITY, (task: ITask) => {
511
+ * console.log('Participant in post-call activity:', task.data.interactionId);
512
+ * // Handle post-call activity
513
+ * });
514
+ * ```
515
+ */
516
+ TASK_POST_CALL_ACTIVITY = 'task:postCallActivity',
493
517
  }
494
518
 
495
519
  /**
@@ -757,6 +781,8 @@ export type TaskData = {
757
781
  isWebCallMute?: boolean;
758
782
  /** Identifier for reservation interaction */
759
783
  reservationInteractionId?: string;
784
+ /** Identifier for the reserved agent channel (used for campaign tasks) */
785
+ reservedAgentChannelId?: string;
760
786
  /** Indicates if wrap-up is required for this task */
761
787
  wrapUpRequired?: boolean;
762
788
  };
@@ -1005,19 +1031,6 @@ export type ConsultConferenceData = {
1005
1031
  destinationType: string;
1006
1032
  };
1007
1033
 
1008
- /**
1009
- * Legacy consultation conference data type matching Agent Desktop
1010
- * @public
1011
- */
1012
- export type consultConferencePayloadData = {
1013
- /** Identifier of the agent initiating consult/conference */
1014
- agentId: string;
1015
- /** Type of destination (e.g., 'agent', 'queue') */
1016
- destinationType: string;
1017
- /** Identifier of the destination agent */
1018
- destAgentId: string;
1019
- };
1020
-
1021
1034
  /**
1022
1035
  * Parameters required for cancelling a consult to queue operation
1023
1036
  * @public
@@ -229,7 +229,7 @@ describe('Utils', () => {
229
229
  });
230
230
  });
231
231
 
232
- it('should return DUPLICATE_LOCATION message and fieldName for DN number', () => {
232
+ it('should return DUPLICATE_LOCATION message and fieldName for dial number', () => {
233
233
  const failure = {data: {reason: 'DUPLICATE_LOCATION'}} as Failure;
234
234
  const result = Utils.getStationLoginErrorData(failure, LoginOption.AGENT_DN);
235
235
  expect(result).toEqual({
@@ -277,53 +277,284 @@ describe('Utils', () => {
277
277
  });
278
278
  });
279
279
 
280
- describe('getDestinationAgentId', () => {
281
- const currentAgentId = 'agent-current-123';
280
+ describe('getConsultedAgentId', () => {
281
+ const currentAgentId = 'agent-123';
282
282
 
283
- it('returns another Agent id when present and not in wrap-up', () => {
284
- const participants: any = {
285
- [currentAgentId]: {type: 'Agent', id: currentAgentId, isWrapUp: false},
286
- agent1: {type: 'Agent', id: 'agent-1', isWrapUp: false},
287
- customer1: {type: 'Customer', id: 'cust-1', isWrapUp: false},
283
+ it('should return consulted agent ID from consult media', () => {
284
+ const media: any = {
285
+ mainCall: {
286
+ mType: 'mainCall',
287
+ participants: [currentAgentId, 'customer-1'],
288
+ },
289
+ consultCall: {
290
+ mType: 'consult',
291
+ participants: [currentAgentId, 'agent-456'],
292
+ },
293
+ };
294
+
295
+ const result = Utils.getConsultedAgentId(media, currentAgentId);
296
+ expect(result).toBe('agent-456');
297
+ });
298
+
299
+ it('should return empty string when no consult media exists', () => {
300
+ const media: any = {
301
+ mainCall: {
302
+ mType: 'mainCall',
303
+ participants: [currentAgentId, 'customer-1'],
304
+ },
288
305
  };
289
306
 
290
- const result = Utils.getDestinationAgentId(participants, currentAgentId);
291
- expect(result).toBe('agent-1');
307
+ const result = Utils.getConsultedAgentId(media, currentAgentId);
308
+ expect(result).toBe('');
292
309
  });
293
310
 
294
- it('ignores self and wrap-up participants', () => {
295
- const participants: any = {
296
- [currentAgentId]: {type: 'Agent', id: currentAgentId, isWrapUp: false},
297
- agentWrap: {type: 'Agent', id: 'agent-wrap', isWrapUp: true},
311
+ it('should return empty string when current agent is not in consult participants', () => {
312
+ const media: any = {
313
+ consultCall: {
314
+ mType: 'consult',
315
+ participants: ['other-agent-1', 'other-agent-2'],
316
+ },
298
317
  };
299
318
 
300
- const result = Utils.getDestinationAgentId(participants, currentAgentId);
319
+ const result = Utils.getConsultedAgentId(media, currentAgentId);
320
+ expect(result).toBe('');
321
+ });
322
+
323
+ it('should handle empty media object', () => {
324
+ const result = Utils.getConsultedAgentId({}, currentAgentId);
301
325
  expect(result).toBe('');
302
326
  });
303
327
 
304
- it('supports DN, EpDn and entryPoint types', () => {
305
- const participantsDN: any = {
306
- [currentAgentId]: {type: 'Agent', id: currentAgentId, isWrapUp: false},
307
- dn1: {type: 'DN', id: 'dn-1', isWrapUp: false},
328
+ it('should handle multiple media entries and find consult', () => {
329
+ const media: any = {
330
+ media1: {mType: 'mainCall', participants: [currentAgentId]},
331
+ media2: {mType: 'hold', participants: []},
332
+ media3: {mType: 'consult', participants: [currentAgentId, 'consulted-agent']},
333
+ };
334
+
335
+ const result = Utils.getConsultedAgentId(media, currentAgentId);
336
+ expect(result).toBe('consulted-agent');
337
+ });
338
+ });
339
+
340
+ describe('getDestAgentIdForCBT', () => {
341
+ it('should return destination agent ID for CBT scenario', () => {
342
+ const interaction: any = {
343
+ participants: {
344
+ 'agent-uuid-123': {
345
+ type: 'Agent',
346
+ pType: 'dn',
347
+ dn: '5551234567',
348
+ id: 'agent-uuid-123',
349
+ },
350
+ 'customer-1': {
351
+ type: 'Customer',
352
+ pType: 'Customer',
353
+ id: 'customer-1',
354
+ },
355
+ },
356
+ };
357
+ const consultingAgent = '5551234567'; // Phone number, not in participants as key
358
+
359
+ const result = Utils.getDestAgentIdForCBT(interaction, consultingAgent);
360
+ expect(result).toBe('agent-uuid-123');
361
+ });
362
+
363
+ it('should return empty string when consultingAgent is in participants (non-CBT)', () => {
364
+ const interaction: any = {
365
+ participants: {
366
+ 'agent-123': {
367
+ type: 'Agent',
368
+ pType: 'Agent',
369
+ id: 'agent-123',
370
+ },
371
+ },
308
372
  };
309
- expect(Utils.getDestinationAgentId(participantsDN, currentAgentId)).toBe('dn-1');
373
+ const consultingAgent = 'agent-123'; // Exists as key in participants
374
+
375
+ const result = Utils.getDestAgentIdForCBT(interaction, consultingAgent);
376
+ expect(result).toBe('');
377
+ });
310
378
 
311
- const participantsEpDn: any = {
312
- [currentAgentId]: {type: 'Agent', id: currentAgentId, isWrapUp: false},
313
- epdn1: {type: 'EpDn', id: 'epdn-1', isWrapUp: false},
379
+ it('should return empty string when no matching dial number found', () => {
380
+ const interaction: any = {
381
+ participants: {
382
+ 'agent-uuid-123': {
383
+ type: 'Agent',
384
+ pType: 'dn',
385
+ dn: '5559999999',
386
+ id: 'agent-uuid-123',
387
+ },
388
+ },
314
389
  };
315
- expect(Utils.getDestinationAgentId(participantsEpDn, currentAgentId)).toBe('epdn-1');
390
+ const consultingAgent = '5551234567'; // Different number
391
+
392
+ const result = Utils.getDestAgentIdForCBT(interaction, consultingAgent);
393
+ expect(result).toBe('');
394
+ });
316
395
 
317
- const participantsEntry: any = {
318
- [currentAgentId]: {type: 'Agent', id: currentAgentId, isWrapUp: false},
319
- entry1: {type: 'entryPoint', id: 'entry-1', isWrapUp: false},
396
+ it('should return empty string when consultingAgent is empty', () => {
397
+ const interaction: any = {
398
+ participants: {
399
+ 'agent-uuid-123': {
400
+ type: 'Agent',
401
+ pType: 'dn',
402
+ dn: '5551234567',
403
+ },
404
+ },
320
405
  };
321
- expect(Utils.getDestinationAgentId(participantsEntry, currentAgentId)).toBe('entry-1');
406
+
407
+ const result = Utils.getDestAgentIdForCBT(interaction, '');
408
+ expect(result).toBe('');
322
409
  });
323
410
 
324
- it('returns empty string when participants is missing or empty', () => {
325
- expect(Utils.getDestinationAgentId(undefined as any, currentAgentId)).toBe('');
326
- expect(Utils.getDestinationAgentId({} as any, currentAgentId)).toBe('');
411
+ it('should match only when participant type is dial number and type is Agent', () => {
412
+ const interaction: any = {
413
+ participants: {
414
+ 'participant-1': {
415
+ type: 'Customer',
416
+ pType: 'dn',
417
+ dn: '5551234567',
418
+ },
419
+ 'participant-2': {
420
+ type: 'Agent',
421
+ pType: 'Agent',
422
+ dn: '5551234567',
423
+ },
424
+ 'participant-3': {
425
+ type: 'Agent',
426
+ pType: 'dn',
427
+ dn: '5551234567',
428
+ id: 'correct-agent',
429
+ },
430
+ },
431
+ };
432
+
433
+ const result = Utils.getDestAgentIdForCBT(interaction, '5551234567');
434
+ expect(result).toBe('participant-3');
435
+ });
436
+
437
+ it('should handle case-insensitive participant type comparison', () => {
438
+ const interaction: any = {
439
+ participants: {
440
+ 'agent-uuid': {
441
+ type: 'Agent',
442
+ pType: 'DN', // Uppercase (dial number)
443
+ dn: '5551234567',
444
+ },
445
+ },
446
+ };
447
+
448
+ const result = Utils.getDestAgentIdForCBT(interaction, '5551234567');
449
+ expect(result).toBe('agent-uuid');
327
450
  });
328
451
  });
452
+
453
+ describe('calculateDestAgentId', () => {
454
+ const currentAgentId = 'agent-123';
455
+
456
+ it('should return destAgentIdCBT when found', () => {
457
+ const interaction: any = {
458
+ media: {
459
+ consult: {
460
+ mType: 'consult',
461
+ participants: [currentAgentId, '5551234567'],
462
+ },
463
+ },
464
+ participants: {
465
+ 'agent-uuid-456': {
466
+ type: 'Agent',
467
+ pType: 'dn',
468
+ dn: '5551234567',
469
+ id: 'agent-uuid-456',
470
+ },
471
+ },
472
+ };
473
+
474
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
475
+ expect(result).toBe('agent-uuid-456');
476
+ });
477
+
478
+ it('should return participant id for regular agent when not CBT', () => {
479
+ const consultedAgentId = 'agent-456';
480
+ const interaction: any = {
481
+ media: {
482
+ consult: {
483
+ mType: 'consult',
484
+ participants: [currentAgentId, consultedAgentId],
485
+ },
486
+ },
487
+ participants: {
488
+ [consultedAgentId]: {
489
+ type: 'Agent',
490
+ id: consultedAgentId,
491
+ },
492
+ },
493
+ };
494
+
495
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
496
+ expect(result).toBe(consultedAgentId);
497
+ });
498
+
499
+ it('should return epId for EpDn type participants', () => {
500
+ const consultedAgentId = 'epdn-456';
501
+ const interaction: any = {
502
+ media: {
503
+ consult: {
504
+ mType: 'consult',
505
+ participants: [currentAgentId, consultedAgentId],
506
+ },
507
+ },
508
+ participants: {
509
+ [consultedAgentId]: {
510
+ type: 'EpDn',
511
+ id: consultedAgentId,
512
+ epId: 'entry-point-id-789',
513
+ },
514
+ },
515
+ };
516
+
517
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
518
+ expect(result).toBe('entry-point-id-789');
519
+ });
520
+
521
+ it('should return undefined when no consulting agent found', () => {
522
+ const interaction: any = {
523
+ media: {
524
+ mainCall: {
525
+ mType: 'mainCall',
526
+ participants: [currentAgentId],
527
+ },
528
+ },
529
+ participants: {},
530
+ };
531
+
532
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
533
+ expect(result).toBeUndefined();
534
+ });
535
+
536
+ it('should handle CBT scenario when phone number is not a direct participant key', () => {
537
+ const interaction: any = {
538
+ media: {
539
+ consult: {
540
+ mType: 'consult',
541
+ participants: [currentAgentId, '5551234567'], // Phone number in media
542
+ },
543
+ },
544
+ participants: {
545
+ // Note: '5551234567' is NOT a key - this is CBT
546
+ 'agent-uuid-cbt': {
547
+ type: 'Agent',
548
+ pType: 'dn',
549
+ dn: '5551234567', // Found by matching DN
550
+ id: 'agent-uuid-cbt',
551
+ },
552
+ },
553
+ };
554
+
555
+ const result = Utils.calculateDestAgentId(interaction, currentAgentId);
556
+ expect(result).toBe('agent-uuid-cbt'); // Returns the CBT agent
557
+ });
558
+ });
559
+
329
560
  });