@webex/contact-center 3.10.0-next.2 → 3.10.0-next.21

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 (103) hide show
  1. package/dist/cc.js +13 -1
  2. package/dist/cc.js.map +1 -1
  3. package/dist/config.js.map +1 -1
  4. package/dist/constants.js.map +1 -1
  5. package/dist/index.js +17 -1
  6. package/dist/index.js.map +1 -1
  7. package/dist/logger-proxy.js.map +1 -1
  8. package/dist/metrics/MetricsManager.js +2 -1
  9. package/dist/metrics/MetricsManager.js.map +1 -1
  10. package/dist/metrics/behavioral-events.js +12 -0
  11. package/dist/metrics/behavioral-events.js.map +1 -1
  12. package/dist/metrics/constants.js +4 -0
  13. package/dist/metrics/constants.js.map +1 -1
  14. package/dist/services/AddressBook.js +2 -3
  15. package/dist/services/AddressBook.js.map +1 -1
  16. package/dist/services/EntryPoint.js +2 -3
  17. package/dist/services/EntryPoint.js.map +1 -1
  18. package/dist/services/Queue.js +2 -3
  19. package/dist/services/Queue.js.map +1 -1
  20. package/dist/services/WebCallingService.js +1 -1
  21. package/dist/services/WebCallingService.js.map +1 -1
  22. package/dist/services/agent/index.js +1 -2
  23. package/dist/services/agent/index.js.map +1 -1
  24. package/dist/services/agent/types.js +10 -0
  25. package/dist/services/agent/types.js.map +1 -1
  26. package/dist/services/config/Util.js.map +1 -1
  27. package/dist/services/config/constants.js.map +1 -1
  28. package/dist/services/config/index.js +1 -1
  29. package/dist/services/config/index.js.map +1 -1
  30. package/dist/services/config/types.js +2 -2
  31. package/dist/services/config/types.js.map +1 -1
  32. package/dist/services/constants.js.map +1 -1
  33. package/dist/services/core/Err.js.map +1 -1
  34. package/dist/services/core/GlobalTypes.js.map +1 -1
  35. package/dist/services/core/Utils.js +92 -74
  36. package/dist/services/core/Utils.js.map +1 -1
  37. package/dist/services/core/WebexRequest.js +1 -2
  38. package/dist/services/core/WebexRequest.js.map +1 -1
  39. package/dist/services/core/aqm-reqs.js +2 -3
  40. package/dist/services/core/aqm-reqs.js.map +1 -1
  41. package/dist/services/core/constants.js +17 -1
  42. package/dist/services/core/constants.js.map +1 -1
  43. package/dist/services/core/types.js.map +1 -1
  44. package/dist/services/core/websocket/WebSocketManager.js +1 -2
  45. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  46. package/dist/services/core/websocket/connection-service.js +1 -1
  47. package/dist/services/core/websocket/connection-service.js.map +1 -1
  48. package/dist/services/core/websocket/keepalive.worker.js.map +1 -1
  49. package/dist/services/core/websocket/types.js.map +1 -1
  50. package/dist/services/index.js +1 -1
  51. package/dist/services/index.js.map +1 -1
  52. package/dist/services/task/AutoWrapup.js +1 -1
  53. package/dist/services/task/AutoWrapup.js.map +1 -1
  54. package/dist/services/task/TaskManager.js +177 -56
  55. package/dist/services/task/TaskManager.js.map +1 -1
  56. package/dist/services/task/TaskUtils.js +122 -5
  57. package/dist/services/task/TaskUtils.js.map +1 -1
  58. package/dist/services/task/constants.js +3 -1
  59. package/dist/services/task/constants.js.map +1 -1
  60. package/dist/services/task/contact.js +0 -2
  61. package/dist/services/task/contact.js.map +1 -1
  62. package/dist/services/task/dialer.js.map +1 -1
  63. package/dist/services/task/index.js +46 -40
  64. package/dist/services/task/index.js.map +1 -1
  65. package/dist/services/task/types.js +377 -4
  66. package/dist/services/task/types.js.map +1 -1
  67. package/dist/types/cc.d.ts +6 -0
  68. package/dist/types/index.d.ts +1 -1
  69. package/dist/types/metrics/constants.d.ts +4 -0
  70. package/dist/types/services/config/types.d.ts +4 -4
  71. package/dist/types/services/core/Utils.d.ts +32 -17
  72. package/dist/types/services/core/constants.d.ts +14 -0
  73. package/dist/types/services/task/TaskUtils.d.ts +59 -3
  74. package/dist/types/services/task/constants.d.ts +2 -0
  75. package/dist/types/services/task/types.d.ts +57 -13
  76. package/dist/types.js +5 -0
  77. package/dist/types.js.map +1 -1
  78. package/dist/utils/PageCache.js +1 -1
  79. package/dist/utils/PageCache.js.map +1 -1
  80. package/dist/webex-config.js.map +1 -1
  81. package/dist/webex.js +2 -2
  82. package/dist/webex.js.map +1 -1
  83. package/package.json +8 -8
  84. package/src/cc.ts +12 -0
  85. package/src/index.ts +1 -0
  86. package/src/metrics/behavioral-events.ts +12 -0
  87. package/src/metrics/constants.ts +4 -0
  88. package/src/services/config/types.ts +2 -2
  89. package/src/services/core/Utils.ts +101 -85
  90. package/src/services/core/constants.ts +16 -0
  91. package/src/services/task/TaskManager.ts +204 -36
  92. package/src/services/task/TaskUtils.ts +145 -5
  93. package/src/services/task/constants.ts +2 -0
  94. package/src/services/task/index.ts +50 -63
  95. package/src/services/task/types.ts +60 -13
  96. package/test/unit/spec/cc.ts +1 -0
  97. package/test/unit/spec/metrics/behavioral-events.ts +14 -0
  98. package/test/unit/spec/services/core/Utils.ts +262 -31
  99. package/test/unit/spec/services/task/TaskManager.ts +748 -5
  100. package/test/unit/spec/services/task/TaskUtils.ts +311 -9
  101. package/test/unit/spec/services/task/index.ts +323 -68
  102. package/umd/contact-center.min.js +2 -2
  103. package/umd/contact-center.min.js.map +1 -1
@@ -17,6 +17,8 @@ import {
17
17
  getIsConferenceInProgress,
18
18
  isParticipantInMainInteraction,
19
19
  isPrimary,
20
+ isSecondaryEpDnAgent,
21
+ shouldAutoAnswerTask,
20
22
  } from './TaskUtils';
21
23
 
22
24
  /** @internal */
@@ -35,6 +37,7 @@ export default class TaskManager extends EventEmitter {
35
37
  private static taskManager;
36
38
  private wrapupData: WrapupData;
37
39
  private agentId: string;
40
+ private webRtcEnabled: boolean;
38
41
  /**
39
42
  * @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
40
43
  * @param webCallingService - Webrtc Service Layer
@@ -72,6 +75,10 @@ export default class TaskManager extends EventEmitter {
72
75
  return this.agentId;
73
76
  }
74
77
 
78
+ public setWebRtcEnabled(webRtcEnabled: boolean) {
79
+ this.webRtcEnabled = webRtcEnabled;
80
+ }
81
+
75
82
  private handleIncomingWebCall = (call: ICall) => {
76
83
  const currentTask = Object.values(this.taskCollection).find(
77
84
  (task) => task.data.interaction.mediaType === 'telephony'
@@ -128,6 +135,15 @@ export default class TaskManager extends EventEmitter {
128
135
  method: METHODS.REGISTER_TASK_LISTENERS,
129
136
  interactionId: payload.data.interactionId,
130
137
  });
138
+
139
+ // Check if auto-answer should happen for this task
140
+ const shouldAutoAnswer = shouldAutoAnswerTask(
141
+ payload.data,
142
+ this.agentId,
143
+ this.webCallingService.loginOption,
144
+ this.webRtcEnabled
145
+ );
146
+
131
147
  task = new Task(
132
148
  this.contact,
133
149
  this.webCallingService,
@@ -135,6 +151,8 @@ export default class TaskManager extends EventEmitter {
135
151
  ...payload.data,
136
152
  wrapUpRequired:
137
153
  payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
154
+ isConferenceInProgress: getIsConferenceInProgress(payload.data),
155
+ isAutoAnswering: shouldAutoAnswer, // Set flag before emitting
138
156
  },
139
157
  this.wrapupData,
140
158
  this.agentId
@@ -165,13 +183,23 @@ export default class TaskManager extends EventEmitter {
165
183
  }
166
184
  }
167
185
  break;
168
- case CC_EVENTS.AGENT_CONTACT_RESERVED:
186
+
187
+ case CC_EVENTS.AGENT_CONTACT_RESERVED: {
188
+ // Check if auto-answer should happen for this task
189
+ const shouldAutoAnswerReserved = shouldAutoAnswerTask(
190
+ payload.data,
191
+ this.agentId,
192
+ this.webCallingService.loginOption,
193
+ this.webRtcEnabled
194
+ );
195
+
169
196
  task = new Task(
170
197
  this.contact,
171
198
  this.webCallingService,
172
199
  {
173
200
  ...payload.data,
174
201
  isConsulted: false,
202
+ isAutoAnswering: shouldAutoAnswerReserved, // Set flag before emitting
175
203
  },
176
204
  this.wrapupData,
177
205
  this.agentId
@@ -186,6 +214,7 @@ export default class TaskManager extends EventEmitter {
186
214
  this.emit(TASK_EVENTS.TASK_INCOMING, task);
187
215
  }
188
216
  break;
217
+ }
189
218
  case CC_EVENTS.AGENT_OFFER_CONTACT:
190
219
  // We don't have to emit any event here since this will be result of promise.
191
220
  task = this.updateTaskData(task, payload.data);
@@ -195,17 +224,29 @@ export default class TaskManager extends EventEmitter {
195
224
  interactionId: payload.data?.interactionId,
196
225
  });
197
226
  this.emit(TASK_EVENTS.TASK_OFFER_CONTACT, task);
227
+
228
+ // Handle auto-answer for offer contact
229
+ this.handleAutoAnswer(task);
198
230
  break;
199
231
  case CC_EVENTS.AGENT_OUTBOUND_FAILED:
200
- // We don't have to emit any event here since this will be result of promise.
201
- if (task.data) {
202
- this.removeTaskFromCollection(task);
232
+ if (task) {
233
+ task = this.updateTaskData(task, payload.data);
234
+ this.metricsManager.trackEvent(
235
+ METRIC_EVENT_NAMES.TASK_OUTDIAL_FAILED,
236
+ {
237
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(payload.data),
238
+ taskId: payload.data.interactionId,
239
+ reason: payload.data.reasonCode || payload.data.reason,
240
+ },
241
+ ['behavioral', 'operational']
242
+ );
243
+ LoggerProxy.log(`Agent outbound failed for task`, {
244
+ module: TASK_MANAGER_FILE,
245
+ method: METHODS.REGISTER_TASK_LISTENERS,
246
+ interactionId: payload.data.interactionId,
247
+ });
248
+ task.emit(TASK_EVENTS.TASK_OUTDIAL_FAILED, payload.data.reason ?? 'UNKNOWN_REASON');
203
249
  }
204
- LoggerProxy.log(`Agent outbound failed for task`, {
205
- module: TASK_MANAGER_FILE,
206
- method: METHODS.REGISTER_TASK_LISTENERS,
207
- interactionId: payload.data?.interactionId,
208
- });
209
250
  break;
210
251
  case CC_EVENTS.AGENT_CONTACT_ASSIGNED:
211
252
  task = this.updateTaskData(task, payload.data);
@@ -244,13 +285,23 @@ export default class TaskManager extends EventEmitter {
244
285
  break;
245
286
  }
246
287
  case CC_EVENTS.CONTACT_ENDED:
247
- task = this.updateTaskData(task, {
248
- ...payload.data,
249
- wrapUpRequired: payload.data.interaction.state !== 'new',
250
- });
251
- this.handleTaskCleanup(task);
252
- task.emit(TASK_EVENTS.TASK_END, task);
288
+ // Update task data
289
+ if (task) {
290
+ task = this.updateTaskData(task, {
291
+ ...payload.data,
292
+ wrapUpRequired:
293
+ payload.data.interaction.state !== 'new' &&
294
+ !isSecondaryEpDnAgent(payload.data.interaction),
295
+ });
296
+
297
+ // Handle cleanup based on whether task should be deleted
298
+ this.handleTaskCleanup(task);
253
299
 
300
+ task?.emit(TASK_EVENTS.TASK_END, task);
301
+ }
302
+ break;
303
+ case CC_EVENTS.CONTACT_MERGED:
304
+ task = this.handleContactMerged(task, payload.data);
254
305
  break;
255
306
  case CC_EVENTS.AGENT_CONTACT_HELD:
256
307
  // As soon as the main interaction is held, we need to emit TASK_HOLD
@@ -288,6 +339,9 @@ export default class TaskManager extends EventEmitter {
288
339
  isConsulted: true, // This ensures that the task is marked as us being requested for a consult
289
340
  });
290
341
  task.emit(TASK_EVENTS.TASK_OFFER_CONSULT, task);
342
+
343
+ // Handle auto-answer for consult offer
344
+ this.handleAutoAnswer(task);
291
345
  break;
292
346
  case CC_EVENTS.AGENT_CONSULTING:
293
347
  // Received when agent is in an active consult state
@@ -372,32 +426,22 @@ export default class TaskManager extends EventEmitter {
372
426
  } else {
373
427
  this.removeTaskFromCollection(task);
374
428
  }
375
- task?.emit(TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
429
+ task.emit(TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
376
430
  break;
377
431
  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
432
  task = this.updateTaskData(task, {
385
433
  ...payload.data,
386
- isConferenceInProgress: getIsConferenceInProgress(simulatedTaskForJoin),
434
+ isConferenceInProgress: getIsConferenceInProgress(payload.data),
387
435
  });
388
436
  task.emit(TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
389
437
  break;
390
438
  }
391
439
  case CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE: {
392
440
  // 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
- };
441
+
398
442
  task = this.updateTaskData(task, {
399
443
  ...payload.data,
400
- isConferenceInProgress: getIsConferenceInProgress(simulatedTaskForLeft),
444
+ isConferenceInProgress: getIsConferenceInProgress(payload.data),
401
445
  });
402
446
  if (checkParticipantNotInInteraction(task, this.agentId)) {
403
447
  if (
@@ -433,13 +477,10 @@ export default class TaskManager extends EventEmitter {
433
477
  task = this.updateTaskData(task, payload.data);
434
478
  task.emit(TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
435
479
  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
480
  case CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
441
481
  // Post-call activity for participant - update task state with activity details
442
482
  task = this.updateTaskData(task, payload.data);
483
+ task.emit(TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
443
484
  break;
444
485
  default:
445
486
  break;
@@ -479,6 +520,54 @@ export default class TaskManager extends EventEmitter {
479
520
  }
480
521
  }
481
522
 
523
+ /**
524
+ * Handles CONTACT_MERGED event logic
525
+ * @param task - The task to process
526
+ * @param taskData - The task data from the event payload
527
+ * @returns Updated or newly created task
528
+ * @private
529
+ */
530
+ private handleContactMerged(task: ITask, taskData: TaskData): ITask {
531
+ if (taskData.childInteractionId) {
532
+ // remove the child task from collection
533
+ this.removeTaskFromCollection(this.taskCollection[taskData.childInteractionId]);
534
+ }
535
+
536
+ if (this.taskCollection[taskData.interactionId]) {
537
+ LoggerProxy.log(`Got CONTACT_MERGED: Task already exists in collection`, {
538
+ module: TASK_MANAGER_FILE,
539
+ method: METHODS.REGISTER_TASK_LISTENERS,
540
+ interactionId: taskData.interactionId,
541
+ });
542
+ // update the task data
543
+ task = this.updateTaskData(task, taskData);
544
+ } else {
545
+ // Case2 : Task is not present in taskCollection
546
+ LoggerProxy.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
547
+ module: TASK_MANAGER_FILE,
548
+ method: METHODS.REGISTER_TASK_LISTENERS,
549
+ interactionId: taskData.interactionId,
550
+ });
551
+
552
+ task = new Task(
553
+ this.contact,
554
+ this.webCallingService,
555
+ {
556
+ ...taskData,
557
+ wrapUpRequired: taskData.interaction?.participants?.[this.agentId]?.isWrapUp || false,
558
+ isConferenceInProgress: getIsConferenceInProgress(taskData),
559
+ },
560
+ this.wrapupData,
561
+ this.agentId
562
+ );
563
+ this.taskCollection[taskData.interactionId] = task;
564
+ }
565
+
566
+ this.emit(TASK_EVENTS.TASK_MERGED, task);
567
+
568
+ return task;
569
+ }
570
+
482
571
  private removeTaskFromCollection(task: ITask) {
483
572
  if (task?.data?.interactionId) {
484
573
  delete this.taskCollection[task.data.interactionId];
@@ -490,7 +579,79 @@ export default class TaskManager extends EventEmitter {
490
579
  }
491
580
  }
492
581
 
582
+ /**
583
+ * Handles auto-answer logic for incoming tasks
584
+ * Automatically accepts tasks when isAutoAnswering flag is set
585
+ * The flag is set during task creation based on:
586
+ * 1. WebRTC calls with auto-answer enabled in agent profile
587
+ * 2. Agent-initiated WebRTC outdial calls
588
+ * 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
589
+ *
590
+ * @param task - The task to auto-answer
591
+ * @private
592
+ */
593
+ private async handleAutoAnswer(task: ITask): Promise<void> {
594
+ if (!task || !task.data || !task.data.isAutoAnswering) {
595
+ return;
596
+ }
597
+
598
+ LoggerProxy.info(`Auto-answering task`, {
599
+ module: TASK_MANAGER_FILE,
600
+ method: 'handleAutoAnswer',
601
+ interactionId: task.data.interactionId,
602
+ });
603
+
604
+ try {
605
+ await task.accept();
606
+ LoggerProxy.info(`Task auto-answered successfully`, {
607
+ module: TASK_MANAGER_FILE,
608
+ method: 'handleAutoAnswer',
609
+ interactionId: task.data.interactionId,
610
+ });
611
+
612
+ // Track successful auto-answer
613
+ this.metricsManager.trackEvent(
614
+ METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS,
615
+ {
616
+ taskId: task.data.interactionId,
617
+ mediaType: task.data.interaction.mediaType,
618
+ isAutoAnswered: true,
619
+ },
620
+ ['behavioral', 'operational']
621
+ );
622
+ // Emit task:autoAnswered event for widgets/UI to react
623
+ task.emit(TASK_EVENTS.TASK_AUTO_ANSWERED, task);
624
+ } catch (error) {
625
+ // Reset isAutoAnswering flag on failure
626
+ task.updateTaskData({...task.data, isAutoAnswering: false});
627
+ LoggerProxy.error(`Failed to auto-answer task`, {
628
+ module: TASK_MANAGER_FILE,
629
+ method: 'handleAutoAnswer',
630
+ interactionId: task.data.interactionId,
631
+ error,
632
+ });
633
+
634
+ // Track auto-answer failure
635
+ this.metricsManager.trackEvent(
636
+ METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_FAILED,
637
+ {
638
+ taskId: task.data.interactionId,
639
+ mediaType: task.data.interaction.mediaType,
640
+ error: error?.message || 'Unknown error',
641
+ isAutoAnswered: false,
642
+ },
643
+ ['behavioral', 'operational']
644
+ );
645
+ }
646
+ }
647
+
648
+ /**
649
+ * Handles cleanup of task resources including Desktop/WebRTC call cleanup and task removal
650
+ * @param task - The task to clean up
651
+ * @private
652
+ */
493
653
  private handleTaskCleanup(task: ITask) {
654
+ // Clean up Desktop/WebRTC calling resources for browser-based telephony tasks
494
655
  if (
495
656
  this.webCallingService.loginOption === LoginOption.BROWSER &&
496
657
  task.data.interaction.mediaType === 'telephony'
@@ -498,9 +659,16 @@ export default class TaskManager extends EventEmitter {
498
659
  task.unregisterWebCallListeners();
499
660
  this.webCallingService.cleanUpCall();
500
661
  }
501
- if (task.data.interaction.state === 'new') {
502
- // Only remove tasks in 'new' state immediately. For other states,
503
- // retain tasks until they complete wrap-up, unless the task disconnected before being answered.
662
+
663
+ const isOutdial = task.data.interaction.outboundType === 'OUTDIAL';
664
+ const isNew = task.data.interaction.state === 'new';
665
+ const needsWrapUp = task.data.agentsPendingWrapUp?.length > 0;
666
+
667
+ // For OUTDIAL: only remove if NOT terminated (user-declined, no wrap-up follows)
668
+ // If terminated, keep task for wrap-up flow (CONTACT_ENDED → AGENT_WRAPUP)
669
+ // For non-OUTDIAL: remove if state is 'new'
670
+ // Always remove if secondary EpDn agent
671
+ if ((isNew && !(isOutdial && needsWrapUp)) || isSecondaryEpDnAgent(task.data.interaction)) {
504
672
  this.removeTaskFromCollection(task);
505
673
  }
506
674
  }
@@ -1,5 +1,7 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
- import {ITask} from './types';
2
+ import {Interaction, ITask, TaskData, MEDIA_CHANNEL} from './types';
3
+ import {OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE, OUTBOUND_TYPE} from '../../constants';
4
+ import {LoginOption} from '../../types';
3
5
 
4
6
  /**
5
7
  * Determines if the given agent is the primary agent (owner) of the task
@@ -53,13 +55,13 @@ export const checkParticipantNotInInteraction = (task: ITask, agentId: string):
53
55
 
54
56
  /**
55
57
  * 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
58
+ * @param TaskData - The payLoad data to check for conference status
57
59
  * @returns true if there are 2 or more active agent participants in the main call, false otherwise
58
60
  */
59
- export const getIsConferenceInProgress = (task: ITask): boolean => {
60
- const mediaMainCall = task?.data?.interaction?.media?.[task?.data?.interactionId];
61
+ export const getIsConferenceInProgress = (data: TaskData): boolean => {
62
+ const mediaMainCall = data.interaction.media?.[data?.interactionId];
61
63
  const participantsInMainCall = new Set(mediaMainCall?.participants);
62
- const participants = task?.data?.interaction?.participants;
64
+ const participants = data.interaction.participants;
63
65
 
64
66
  const agentParticipants = new Set();
65
67
  if (participantsInMainCall.size > 0) {
@@ -79,3 +81,141 @@ export const getIsConferenceInProgress = (task: ITask): boolean => {
79
81
 
80
82
  return agentParticipants.size >= 2;
81
83
  };
84
+
85
+ /**
86
+ * Checks if the current agent is a secondary agent in a consultation scenario.
87
+ * Secondary agents are those who were consulted (not the original call owner).
88
+ * @param task - The task object containing interaction details
89
+ * @returns true if this is a secondary agent (consulted party), false otherwise
90
+ */
91
+ export const isSecondaryAgent = (interaction: Interaction): boolean => {
92
+ if (!interaction.callProcessingDetails) {
93
+ return false;
94
+ }
95
+
96
+ return (
97
+ interaction.callProcessingDetails.relationshipType === 'consult' &&
98
+ !!interaction.callProcessingDetails.parentInteractionId &&
99
+ interaction.callProcessingDetails.parentInteractionId !== interaction.interactionId
100
+ );
101
+ };
102
+
103
+ /**
104
+ * Checks if the current agent is a secondary EP-DN (Entry Point Dial Number) agent.
105
+ * This is specifically for telephony consultations to external numbers/entry points.
106
+ * @param task - The task object containing interaction details
107
+ * @returns true if this is a secondary EP-DN agent in telephony consultation, false otherwise
108
+ */
109
+ export const isSecondaryEpDnAgent = (interaction: Interaction): boolean => {
110
+ if (!interaction) {
111
+ return false;
112
+ }
113
+
114
+ return interaction.mediaType === 'telephony' && isSecondaryAgent(interaction);
115
+ };
116
+
117
+ /**
118
+ * Checks if auto-answer is enabled for the agent participant
119
+ * @param interaction - The interaction object
120
+ * @param agentId - Current agent ID
121
+ * @returns true if auto-answer is enabled, false otherwise
122
+ */
123
+ export const isAutoAnswerEnabled = (interaction: Interaction, agentId: string): boolean => {
124
+ return interaction.participants?.[agentId]?.autoAnswerEnabled === true;
125
+ };
126
+
127
+ /**
128
+ * Checks if the interaction is a WebRTC call eligible for auto-answer
129
+ * @param interaction - The interaction object
130
+ * @param loginOption - The agent's login option (BROWSER, AGENT_DN, etc.)
131
+ * @param webRtcEnabled - Whether WebRTC is enabled for the agent
132
+ * @returns true if this is a WebRTC call, false otherwise
133
+ */
134
+ export const isWebRTCCall = (
135
+ interaction: Interaction,
136
+ loginOption: string,
137
+ webRtcEnabled: boolean
138
+ ): boolean => {
139
+ return (
140
+ webRtcEnabled &&
141
+ loginOption === LoginOption.BROWSER &&
142
+ interaction.mediaType === OUTDIAL_MEDIA_TYPE
143
+ );
144
+ };
145
+
146
+ /**
147
+ * Checks if the interaction is a digital outbound (Email/SMS)
148
+ * @param interaction - The interaction object
149
+ * @returns true if this is a digital outbound, false otherwise
150
+ */
151
+ export const isDigitalOutbound = (interaction: Interaction): boolean => {
152
+ return (
153
+ interaction.contactDirection?.type === OUTDIAL_DIRECTION &&
154
+ interaction.outboundType === OUTBOUND_TYPE &&
155
+ (interaction.mediaChannel === MEDIA_CHANNEL.EMAIL ||
156
+ interaction.mediaChannel === MEDIA_CHANNEL.SMS)
157
+ );
158
+ };
159
+
160
+ /**
161
+ * Checks if the outdial was initiated by the current agent
162
+ * @param interaction - The interaction object
163
+ * @param agentId - Current agent ID
164
+ * @returns true if agent initiated the outdial, false otherwise
165
+ */
166
+ export const hasAgentInitiatedOutdial = (interaction: Interaction, agentId: string): boolean => {
167
+ return (
168
+ interaction.contactDirection?.type === OUTDIAL_DIRECTION &&
169
+ interaction.outboundType === OUTBOUND_TYPE &&
170
+ interaction.callProcessingDetails?.outdialAgentId === agentId &&
171
+ interaction.owner === agentId &&
172
+ !interaction.callProcessingDetails?.BLIND_TRANSFER_IN_PROGRESS
173
+ );
174
+ };
175
+
176
+ /**
177
+ * Determines if a task should be auto-answered based on interaction data
178
+ * Auto-answer logic handles:
179
+ * 1. WebRTC calls with auto-answer enabled in agent profile
180
+ * 2. Agent-initiated WebRTC outdial calls
181
+ * 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
182
+ *
183
+ * @param taskData - The task data
184
+ * @param agentId - Current agent ID
185
+ * @param loginOption - Agent's login option
186
+ * @param webRtcEnabled - Whether WebRTC is enabled for the agent
187
+ * @returns true if task should be auto-answered, false otherwise
188
+ */
189
+ export const shouldAutoAnswerTask = (
190
+ taskData: TaskData,
191
+ agentId: string,
192
+ loginOption: string,
193
+ webRtcEnabled: boolean
194
+ ): boolean => {
195
+ const {interaction} = taskData;
196
+
197
+ if (!interaction || !agentId) {
198
+ return false;
199
+ }
200
+
201
+ // Check if auto-answer is enabled for this agent
202
+ const autoAnswerEnabled = isAutoAnswerEnabled(interaction, agentId);
203
+
204
+ // Check if this is an agent-initiated outdial
205
+ const agentInitiatedOutdial = hasAgentInitiatedOutdial(interaction, agentId);
206
+
207
+ // WebRTC telephony calls
208
+ if (isWebRTCCall(interaction, loginOption, webRtcEnabled)) {
209
+ return autoAnswerEnabled || agentInitiatedOutdial;
210
+ }
211
+
212
+ // Digital outbound (Email/SMS)
213
+ if (isDigitalOutbound(interaction) && agentInitiatedOutdial) {
214
+ // Don't auto-answer if task has been transferred (has previous vteams)
215
+ const hasPreviousVteams = interaction.previousVTeams && interaction.previousVTeams.length > 0;
216
+
217
+ return !hasPreviousVteams;
218
+ }
219
+
220
+ return false;
221
+ };
@@ -34,6 +34,8 @@ export const PRESERVED_TASK_DATA_FIELDS = {
34
34
  WRAP_UP_REQUIRED: 'wrapUpRequired',
35
35
  /** Indicates if a conference is currently in progress (2+ active agents) */
36
36
  IS_CONFERENCE_IN_PROGRESS: 'isConferenceInProgress',
37
+ /** Indicates if auto-answer is in progress for this task */
38
+ IS_AUTO_ANSWERING: 'isAutoAnswering',
37
39
  };
38
40
 
39
41
  /**