@webex/contact-center 3.10.0-next.1 → 3.10.0-next.11

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 +62 -29
  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 +45 -39
  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 +8 -8
  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 +77 -22
  33. package/src/services/task/TaskUtils.ts +37 -5
  34. package/src/services/task/index.ts +50 -63
  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 +370 -1
  38. package/test/unit/spec/services/task/TaskUtils.ts +6 -6
  39. package/test/unit/spec/services/task/index.ts +323 -68
  40. package/umd/contact-center.min.js +2 -2
  41. package/umd/contact-center.min.js.map +1 -1
@@ -6,11 +6,10 @@ import WebexRequest from './WebexRequest';
6
6
  import {
7
7
  TaskData,
8
8
  ConsultTransferPayLoad,
9
- ConsultConferenceData,
10
- consultConferencePayloadData,
11
9
  CONSULT_TRANSFER_DESTINATION_TYPE,
12
10
  Interaction,
13
11
  } from '../task/types';
12
+ import {PARTICIPANT_TYPES, STATE_CONSULT} from './constants';
14
13
 
15
14
  /**
16
15
  * Extracts common error details from a Webex request payload.
@@ -218,59 +217,118 @@ export const createErrDetailsObject = (errObj: WebexRequestPayload) => {
218
217
  };
219
218
 
220
219
  /**
221
- * Derives the consult transfer destination type based on the provided task data.
220
+ * Gets the consulted agent ID from the media object by finding the agent
221
+ * in the consult media participants (excluding the current agent).
222
222
  *
223
- * Logic parity with desktop behavior:
224
- * - If agent action is dialing a number (DN/EPDN/ENTRYPOINT):
225
- * - ENTRYPOINT/EPDN map to ENTRYPOINT
226
- * - DN maps to DIALNUMBER
227
- * - Otherwise defaults to AGENT
223
+ * @param media - The media object from the interaction
224
+ * @param agentId - The current agent's ID to exclude from the search
225
+ * @returns The consulted agent ID, or empty string if none found
226
+ */
227
+ export const getConsultedAgentId = (media: Interaction['media'], agentId: string): string => {
228
+ let consultParticipants: string[] = [];
229
+ let consultedParticipantId = '';
230
+
231
+ Object.keys(media).forEach((key) => {
232
+ if (media[key].mType === STATE_CONSULT) {
233
+ consultParticipants = media[key].participants;
234
+ }
235
+ });
236
+
237
+ if (consultParticipants.includes(agentId)) {
238
+ const id = consultParticipants.find((participant) => participant !== agentId);
239
+ consultedParticipantId = id || consultedParticipantId;
240
+ }
241
+
242
+ return consultedParticipantId;
243
+ };
244
+
245
+ /**
246
+ * Gets the destination agent ID for CBT (Capacity Based Team) scenarios.
247
+ * CBT refers to teams created in Control Hub with capacity-based routing
248
+ * (as opposed to agent-based routing). This handles cases where the consulted
249
+ * participant is not directly in participants but can be found by matching
250
+ * the dial number (dn).
228
251
  *
229
- * @param taskData - The task data used to infer the agent action and destination type
230
- * @returns The normalized destination type to be used for consult transfer
252
+ * @param interaction - The interaction object
253
+ * @param consultingAgent - The consulting agent identifier
254
+ * @returns The destination agent ID for CBT scenarios, or empty string if none found
231
255
  */
256
+ export const getDestAgentIdForCBT = (interaction: Interaction, consultingAgent: string): string => {
257
+ const participants = interaction.participants;
258
+ let destAgentIdForCBT = '';
259
+
260
+ // Check if this is a CBT scenario (consultingAgent exists but not directly in participants)
261
+ if (consultingAgent && !participants[consultingAgent]) {
262
+ const foundEntry = Object.entries(participants).find(
263
+ ([, participant]: [string, Interaction['participants'][string]]) => {
264
+ return (
265
+ participant.pType.toLowerCase() === PARTICIPANT_TYPES.DN &&
266
+ participant.type === PARTICIPANT_TYPES.AGENT &&
267
+ participant.dn === consultingAgent
268
+ );
269
+ }
270
+ );
271
+
272
+ if (foundEntry) {
273
+ destAgentIdForCBT = foundEntry[0];
274
+ }
275
+ }
276
+
277
+ return destAgentIdForCBT;
278
+ };
279
+
232
280
  /**
233
- * Checks if a participant type represents a non-customer participant.
234
- * Non-customer participants include agents, dial numbers, entry point dial numbers,
235
- * and entry points.
281
+ * Calculates the destination agent ID for consult operations.
282
+ *
283
+ * @param interaction - The interaction object
284
+ * @param agentId - The current agent's ID
285
+ * @returns The destination agent ID
236
286
  */
237
- const isNonCustomerParticipant = (participantType: string): boolean => {
238
- return (
239
- participantType === 'Agent' ||
240
- participantType === 'DN' ||
241
- participantType === 'EpDn' ||
242
- participantType === 'entryPoint'
243
- );
287
+ export const calculateDestAgentId = (interaction: Interaction, agentId: string): string => {
288
+ const consultingAgent = getConsultedAgentId(interaction.media, agentId);
289
+
290
+ // Check if this is a CBT (Capacity Based Team) scenario
291
+ // If not CBT, the function will return empty string and we'll use the normal flow
292
+ const destAgentIdCBT = getDestAgentIdForCBT(interaction, consultingAgent);
293
+ if (destAgentIdCBT) {
294
+ return destAgentIdCBT;
295
+ }
296
+
297
+ return interaction.participants[consultingAgent]?.type === PARTICIPANT_TYPES.EP_DN
298
+ ? interaction.participants[consultingAgent]?.epId
299
+ : interaction.participants[consultingAgent]?.id;
244
300
  };
245
301
 
246
302
  /**
247
- * Gets the destination agent ID from participants data by finding the first
248
- * non-customer participant that is not the current agent and is not in wrap-up state.
303
+ * Calculates the destination agent ID for fetching destination type.
249
304
  *
250
- * @param participants - The participants data from the interaction
251
- * @param agentId - The current agent's ID to exclude from the search
252
- * @returns The destination agent ID, or empty string if none found
305
+ * @param interaction - The interaction object
306
+ * @param agentId - The current agent's ID
307
+ * @returns The destination agent ID for determining destination type
253
308
  */
254
- export const getDestinationAgentId = (
255
- participants: Interaction['participants'],
256
- agentId: string
257
- ): string => {
258
- let id = '';
259
-
260
- if (participants) {
261
- Object.keys(participants).forEach((participant) => {
262
- const participantData = participants[participant];
263
- if (
264
- isNonCustomerParticipant(participantData.type) &&
265
- participantData.id !== agentId &&
266
- !participantData.isWrapUp
267
- ) {
268
- id = participantData.id;
269
- }
270
- });
309
+ export const calculateDestType = (interaction: Interaction, agentId: string): string => {
310
+ const consultingAgent = getConsultedAgentId(interaction.media, agentId);
311
+
312
+ // Check if this is a CBT (Capacity Based Team) scenario, otherwise use consultingAgent
313
+ const destAgentIdCBT = getDestAgentIdForCBT(interaction, consultingAgent);
314
+ const destinationaegntId = destAgentIdCBT || consultingAgent;
315
+ const destAgentType = destinationaegntId
316
+ ? interaction.participants[destinationaegntId]?.pType
317
+ : undefined;
318
+ if (destAgentType) {
319
+ if (destAgentType === 'DN') {
320
+ return CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER;
321
+ }
322
+ if (destAgentType === 'EP-DN') {
323
+ return CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT;
324
+ }
325
+ // Keep the existing destinationType if it's something else (like "agent" or "Agent")
326
+ // Convert "Agent" to lowercase for consistency
327
+
328
+ return destAgentType.toLowerCase();
271
329
  }
272
330
 
273
- return id;
331
+ return CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
274
332
  };
275
333
 
276
334
  export const deriveConsultTransferDestinationType = (
@@ -286,45 +344,3 @@ export const deriveConsultTransferDestinationType = (
286
344
 
287
345
  return CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
288
346
  };
289
-
290
- /**
291
- * Builds consult conference parameter data using EXACT Agent Desktop logic.
292
- * This matches the Agent Desktop's consultConference implementation exactly.
293
- *
294
- * @param dataPassed - Original consultation data from Agent Desktop format
295
- * @param interactionIdPassed - The interaction ID for the task
296
- * @returns Object with interactionId and ConsultConferenceData matching Agent Desktop format
297
- * @public
298
- */
299
- export const buildConsultConferenceParamData = (
300
- dataPassed: consultConferencePayloadData,
301
- interactionIdPassed: string
302
- ): {interactionId: string; data: ConsultConferenceData} => {
303
- const data: ConsultConferenceData = {
304
- // Include agentId if present in input data
305
- ...('agentId' in dataPassed && {agentId: dataPassed.agentId}),
306
- // Handle destAgentId from consultation data
307
- to: dataPassed.destAgentId,
308
- destinationType: '',
309
- };
310
-
311
- // Agent Desktop destination type logic
312
- if ('destinationType' in dataPassed) {
313
- if (dataPassed.destinationType === 'DN') {
314
- data.destinationType = CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER;
315
- } else if (dataPassed.destinationType === 'EP_DN') {
316
- data.destinationType = CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT;
317
- } else {
318
- // Keep the existing destinationType if it's something else (like "agent" or "Agent")
319
- // Convert "Agent" to lowercase for consistency
320
- data.destinationType = dataPassed.destinationType.toLowerCase();
321
- }
322
- } else {
323
- data.destinationType = CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
324
- }
325
-
326
- return {
327
- interactionId: interactionIdPassed,
328
- data,
329
- };
330
- };
@@ -64,6 +64,22 @@ export const CONNECTIVITY_CHECK_INTERVAL = 5000;
64
64
  */
65
65
  export const CLOSE_SOCKET_TIMEOUT = 16000;
66
66
 
67
+ /**
68
+ * Constants for participant types, destination types, and interaction states
69
+ * @ignore
70
+ */
71
+ export const PARTICIPANT_TYPES = {
72
+ /** Participant type for Entry Point Dial Number */
73
+ EP_DN: 'EpDn',
74
+ /** Participant type for dial number */
75
+ DN: 'dn',
76
+ /** Participant type for Agent */
77
+ AGENT: 'Agent',
78
+ };
79
+
80
+ /** Interaction state for consultation */
81
+ export const STATE_CONSULT = 'consult';
82
+
67
83
  // Method names for core services
68
84
  export const METHODS = {
69
85
  // WebexRequest methods
@@ -17,6 +17,7 @@ import {
17
17
  getIsConferenceInProgress,
18
18
  isParticipantInMainInteraction,
19
19
  isPrimary,
20
+ isSecondaryEpDnAgent,
20
21
  } from './TaskUtils';
21
22
 
22
23
  /** @internal */
@@ -128,6 +129,7 @@ export default class TaskManager extends EventEmitter {
128
129
  method: METHODS.REGISTER_TASK_LISTENERS,
129
130
  interactionId: payload.data.interactionId,
130
131
  });
132
+
131
133
  task = new Task(
132
134
  this.contact,
133
135
  this.webCallingService,
@@ -135,6 +137,7 @@ export default class TaskManager extends EventEmitter {
135
137
  ...payload.data,
136
138
  wrapUpRequired:
137
139
  payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
140
+ isConferenceInProgress: getIsConferenceInProgress(payload.data),
138
141
  },
139
142
  this.wrapupData,
140
143
  this.agentId
@@ -165,6 +168,7 @@ export default class TaskManager extends EventEmitter {
165
168
  }
166
169
  }
167
170
  break;
171
+
168
172
  case CC_EVENTS.AGENT_CONTACT_RESERVED:
169
173
  task = new Task(
170
174
  this.contact,
@@ -244,13 +248,22 @@ export default class TaskManager extends EventEmitter {
244
248
  break;
245
249
  }
246
250
  case CC_EVENTS.CONTACT_ENDED:
251
+ // Update task data
247
252
  task = this.updateTaskData(task, {
248
253
  ...payload.data,
249
- wrapUpRequired: payload.data.interaction.state !== 'new',
254
+ wrapUpRequired:
255
+ payload.data.interaction.state !== 'new' &&
256
+ !isSecondaryEpDnAgent(payload.data.interaction),
250
257
  });
258
+
259
+ // Handle cleanup based on whether task should be deleted
251
260
  this.handleTaskCleanup(task);
252
- task.emit(TASK_EVENTS.TASK_END, task);
253
261
 
262
+ task?.emit(TASK_EVENTS.TASK_END, task);
263
+
264
+ break;
265
+ case CC_EVENTS.CONTACT_MERGED:
266
+ task = this.handleContactMerged(task, payload.data);
254
267
  break;
255
268
  case CC_EVENTS.AGENT_CONTACT_HELD:
256
269
  // As soon as the main interaction is held, we need to emit TASK_HOLD
@@ -372,32 +385,22 @@ export default class TaskManager extends EventEmitter {
372
385
  } else {
373
386
  this.removeTaskFromCollection(task);
374
387
  }
375
- task?.emit(TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
388
+ task.emit(TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
376
389
  break;
377
390
  case CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE: {
378
- // Participant joined conference - update task state with participant information and emit event
379
- // Pre-calculate isConferenceInProgress with updated data to avoid double update
380
- const simulatedTaskForJoin = {
381
- ...task,
382
- data: {...task.data, ...payload.data},
383
- };
384
391
  task = this.updateTaskData(task, {
385
392
  ...payload.data,
386
- isConferenceInProgress: getIsConferenceInProgress(simulatedTaskForJoin),
393
+ isConferenceInProgress: getIsConferenceInProgress(payload.data),
387
394
  });
388
395
  task.emit(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
389
396
  break;
390
397
  }
391
398
  case CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE: {
392
399
  // Conference ended - update task state and emit event
393
- // Pre-calculate isConferenceInProgress with updated data to avoid double update
394
- const simulatedTaskForLeft = {
395
- ...task,
396
- data: {...task.data, ...payload.data},
397
- };
400
+
398
401
  task = this.updateTaskData(task, {
399
402
  ...payload.data,
400
- isConferenceInProgress: getIsConferenceInProgress(simulatedTaskForLeft),
403
+ isConferenceInProgress: getIsConferenceInProgress(payload.data),
401
404
  });
402
405
  if (checkParticipantNotInInteraction(task, this.agentId)) {
403
406
  if (
@@ -433,13 +436,10 @@ export default class TaskManager extends EventEmitter {
433
436
  task = this.updateTaskData(task, payload.data);
434
437
  task.emit(TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
435
438
  break;
436
- case CC_EVENTS.CONSULTED_PARTICIPANT_MOVING:
437
- // Participant is being moved/transferred - update task state with movement info
438
- task = this.updateTaskData(task, payload.data);
439
- break;
440
439
  case CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
441
440
  // Post-call activity for participant - update task state with activity details
442
441
  task = this.updateTaskData(task, payload.data);
442
+ task.emit(TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
443
443
  break;
444
444
  default:
445
445
  break;
@@ -479,6 +479,54 @@ export default class TaskManager extends EventEmitter {
479
479
  }
480
480
  }
481
481
 
482
+ /**
483
+ * Handles CONTACT_MERGED event logic
484
+ * @param task - The task to process
485
+ * @param taskData - The task data from the event payload
486
+ * @returns Updated or newly created task
487
+ * @private
488
+ */
489
+ private handleContactMerged(task: ITask, taskData: TaskData): ITask {
490
+ if (taskData.childInteractionId) {
491
+ // remove the child task from collection
492
+ this.removeTaskFromCollection(this.taskCollection[taskData.childInteractionId]);
493
+ }
494
+
495
+ if (this.taskCollection[taskData.interactionId]) {
496
+ LoggerProxy.log(`Got CONTACT_MERGED: Task already exists in collection`, {
497
+ module: TASK_MANAGER_FILE,
498
+ method: METHODS.REGISTER_TASK_LISTENERS,
499
+ interactionId: taskData.interactionId,
500
+ });
501
+ // update the task data
502
+ task = this.updateTaskData(task, taskData);
503
+ } else {
504
+ // Case2 : Task is not present in taskCollection
505
+ LoggerProxy.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
506
+ module: TASK_MANAGER_FILE,
507
+ method: METHODS.REGISTER_TASK_LISTENERS,
508
+ interactionId: taskData.interactionId,
509
+ });
510
+
511
+ task = new Task(
512
+ this.contact,
513
+ this.webCallingService,
514
+ {
515
+ ...taskData,
516
+ wrapUpRequired: taskData.interaction?.participants?.[this.agentId]?.isWrapUp || false,
517
+ isConferenceInProgress: getIsConferenceInProgress(taskData),
518
+ },
519
+ this.wrapupData,
520
+ this.agentId
521
+ );
522
+ this.taskCollection[taskData.interactionId] = task;
523
+ }
524
+
525
+ this.emit(TASK_EVENTS.TASK_MERGED, task);
526
+
527
+ return task;
528
+ }
529
+
482
530
  private removeTaskFromCollection(task: ITask) {
483
531
  if (task?.data?.interactionId) {
484
532
  delete this.taskCollection[task.data.interactionId];
@@ -490,7 +538,13 @@ export default class TaskManager extends EventEmitter {
490
538
  }
491
539
  }
492
540
 
541
+ /**
542
+ * Handles cleanup of task resources including Desktop/WebRTC call cleanup and task removal
543
+ * @param task - The task to clean up
544
+ * @private
545
+ */
493
546
  private handleTaskCleanup(task: ITask) {
547
+ // Clean up Desktop/WebRTC calling resources for browser-based telephony tasks
494
548
  if (
495
549
  this.webCallingService.loginOption === LoginOption.BROWSER &&
496
550
  task.data.interaction.mediaType === 'telephony'
@@ -498,8 +552,9 @@ export default class TaskManager extends EventEmitter {
498
552
  task.unregisterWebCallListeners();
499
553
  this.webCallingService.cleanUpCall();
500
554
  }
501
- if (task.data.interaction.state === 'new') {
502
- // Only remove tasks in 'new' state immediately. For other states,
555
+
556
+ if (task.data.interaction.state === 'new' || isSecondaryEpDnAgent(task.data.interaction)) {
557
+ // Only remove tasks in 'new' state or isSecondaryEpDnAgent immediately. For other states,
503
558
  // retain tasks until they complete wrap-up, unless the task disconnected before being answered.
504
559
  this.removeTaskFromCollection(task);
505
560
  }
@@ -1,5 +1,5 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import {ITask} from './types';
2
+ import {Interaction, ITask, TaskData} from './types';
3
3
 
4
4
  /**
5
5
  * Determines if the given agent is the primary agent (owner) of the task
@@ -53,13 +53,13 @@ export const checkParticipantNotInInteraction = (task: ITask, agentId: string):
53
53
 
54
54
  /**
55
55
  * Determines if a conference is currently in progress based on the number of active agent participants
56
- * @param task - The task to check for conference status
56
+ * @param TaskData - The payLoad data to check for conference status
57
57
  * @returns true if there are 2 or more active agent participants in the main call, false otherwise
58
58
  */
59
- export const getIsConferenceInProgress = (task: ITask): boolean => {
60
- const mediaMainCall = task?.data?.interaction?.media?.[task?.data?.interactionId];
59
+ export const getIsConferenceInProgress = (data: TaskData): boolean => {
60
+ const mediaMainCall = data.interaction.media?.[data?.interactionId];
61
61
  const participantsInMainCall = new Set(mediaMainCall?.participants);
62
- const participants = task?.data?.interaction?.participants;
62
+ const participants = data.interaction.participants;
63
63
 
64
64
  const agentParticipants = new Set();
65
65
  if (participantsInMainCall.size > 0) {
@@ -79,3 +79,35 @@ export const getIsConferenceInProgress = (task: ITask): boolean => {
79
79
 
80
80
  return agentParticipants.size >= 2;
81
81
  };
82
+
83
+ /**
84
+ * Checks if the current agent is a secondary agent in a consultation scenario.
85
+ * Secondary agents are those who were consulted (not the original call owner).
86
+ * @param task - The task object containing interaction details
87
+ * @returns true if this is a secondary agent (consulted party), false otherwise
88
+ */
89
+ export const isSecondaryAgent = (interaction: Interaction): boolean => {
90
+ if (!interaction.callProcessingDetails) {
91
+ return false;
92
+ }
93
+
94
+ return (
95
+ interaction.callProcessingDetails.relationshipType === 'consult' &&
96
+ !!interaction.callProcessingDetails.parentInteractionId &&
97
+ interaction.callProcessingDetails.parentInteractionId !== interaction.interactionId
98
+ );
99
+ };
100
+
101
+ /**
102
+ * Checks if the current agent is a secondary EP-DN (Entry Point Dial Number) agent.
103
+ * This is specifically for telephony consultations to external numbers/entry points.
104
+ * @param task - The task object containing interaction details
105
+ * @returns true if this is a secondary EP-DN agent in telephony consultation, false otherwise
106
+ */
107
+ export const isSecondaryEpDnAgent = (interaction: Interaction): boolean => {
108
+ if (!interaction) {
109
+ return false;
110
+ }
111
+
112
+ return interaction.mediaType === 'telephony' && isSecondaryAgent(interaction);
113
+ };
@@ -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,28 +1542,36 @@ export default class Task extends EventEmitter implements ITask {
1556
1542
  * ```
1557
1543
  */
1558
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
+
1559
1558
  // Extract consultation conference data from task data (used in both try and catch)
1560
1559
  const consultationData = {
1561
1560
  agentId: this.agentId,
1562
- destAgentId: this.data.destAgentId,
1563
- destinationType: this.data.destinationType || 'agent',
1561
+ to: destAgentId,
1562
+ destinationType: destAgentType || this.data.destinationType || 'agent',
1564
1563
  };
1565
1564
 
1566
1565
  try {
1567
- LoggerProxy.info(`Initiating consult conference to ${consultationData.destAgentId}`, {
1566
+ LoggerProxy.info(`Initiating consult conference to ${destAgentId}`, {
1568
1567
  module: TASK_FILE,
1569
1568
  method: METHODS.CONSULT_CONFERENCE,
1570
1569
  interactionId: this.data.interactionId,
1571
1570
  });
1572
1571
 
1573
- const paramsDataForConferenceV2 = buildConsultConferenceParamData(
1574
- consultationData,
1575
- this.data.interactionId
1576
- );
1577
-
1578
1572
  const response = await this.contact.consultConference({
1579
- interactionId: paramsDataForConferenceV2.interactionId,
1580
- data: paramsDataForConferenceV2.data,
1573
+ interactionId: this.data.interactionId,
1574
+ data: consultationData,
1581
1575
  });
1582
1576
 
1583
1577
  // Track success metrics (following consultTransfer pattern)
@@ -1585,9 +1579,9 @@ export default class Task extends EventEmitter implements ITask {
1585
1579
  METRIC_EVENT_NAMES.TASK_CONFERENCE_START_SUCCESS,
1586
1580
  {
1587
1581
  taskId: this.data.interactionId,
1588
- destination: paramsDataForConferenceV2.data.to,
1589
- destinationType: paramsDataForConferenceV2.data.destinationType,
1590
- agentId: paramsDataForConferenceV2.data.agentId,
1582
+ destination: consultationData.to,
1583
+ destinationType: consultationData.destinationType,
1584
+ agentId: consultationData.agentId,
1591
1585
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
1592
1586
  },
1593
1587
  ['operational', 'behavioral', 'business']
@@ -1610,20 +1604,13 @@ export default class Task extends EventEmitter implements ITask {
1610
1604
  reasonCode: err.data?.reasonCode,
1611
1605
  };
1612
1606
 
1613
- // Track failure metrics (following consultTransfer pattern)
1614
- // Build conference data for error tracking using extracted data
1615
- const failedParamsData = buildConsultConferenceParamData(
1616
- consultationData,
1617
- this.data.interactionId
1618
- );
1619
-
1620
1607
  this.metricsManager.trackEvent(
1621
1608
  METRIC_EVENT_NAMES.TASK_CONFERENCE_START_FAILED,
1622
1609
  {
1623
1610
  taskId: this.data.interactionId,
1624
- destination: failedParamsData.data.to,
1625
- destinationType: failedParamsData.data.destinationType,
1626
- agentId: failedParamsData.data.agentId,
1611
+ destination: consultationData.to,
1612
+ destinationType: consultationData.destinationType,
1613
+ agentId: consultationData.agentId,
1627
1614
  error: error.toString(),
1628
1615
  ...taskErrorProps,
1629
1616
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),