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

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 (111) hide show
  1. package/dist/cc.js +196 -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/MetricsManager.js +1 -1
  10. package/dist/metrics/MetricsManager.js.map +1 -1
  11. package/dist/metrics/behavioral-events.js +89 -0
  12. package/dist/metrics/behavioral-events.js.map +1 -1
  13. package/dist/metrics/constants.js +32 -2
  14. package/dist/metrics/constants.js.map +1 -1
  15. package/dist/services/AddressBook.js +271 -0
  16. package/dist/services/AddressBook.js.map +1 -0
  17. package/dist/services/EntryPoint.js +227 -0
  18. package/dist/services/EntryPoint.js.map +1 -0
  19. package/dist/services/Queue.js +261 -0
  20. package/dist/services/Queue.js.map +1 -0
  21. package/dist/services/config/constants.js +36 -2
  22. package/dist/services/config/constants.js.map +1 -1
  23. package/dist/services/config/index.js +29 -21
  24. package/dist/services/config/index.js.map +1 -1
  25. package/dist/services/config/types.js +33 -1
  26. package/dist/services/config/types.js.map +1 -1
  27. package/dist/services/core/GlobalTypes.js.map +1 -1
  28. package/dist/services/core/Utils.js +162 -2
  29. package/dist/services/core/Utils.js.map +1 -1
  30. package/dist/services/core/aqm-reqs.js +0 -4
  31. package/dist/services/core/aqm-reqs.js.map +1 -1
  32. package/dist/services/core/websocket/WebSocketManager.js +0 -4
  33. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  34. package/dist/services/task/TaskManager.js +114 -3
  35. package/dist/services/task/TaskManager.js.map +1 -1
  36. package/dist/services/task/TaskUtils.js +76 -0
  37. package/dist/services/task/TaskUtils.js.map +1 -0
  38. package/dist/services/task/constants.js +26 -1
  39. package/dist/services/task/constants.js.map +1 -1
  40. package/dist/services/task/contact.js +86 -0
  41. package/dist/services/task/contact.js.map +1 -1
  42. package/dist/services/task/index.js +418 -87
  43. package/dist/services/task/index.js.map +1 -1
  44. package/dist/services/task/types.js +14 -0
  45. package/dist/services/task/types.js.map +1 -1
  46. package/dist/types/cc.d.ts +115 -35
  47. package/dist/types/constants.d.ts +1 -0
  48. package/dist/types/index.d.ts +3 -2
  49. package/dist/types/metrics/constants.d.ts +25 -1
  50. package/dist/types/services/AddressBook.d.ts +74 -0
  51. package/dist/types/services/EntryPoint.d.ts +67 -0
  52. package/dist/types/services/Queue.d.ts +76 -0
  53. package/dist/types/services/config/constants.d.ts +35 -1
  54. package/dist/types/services/config/index.d.ts +6 -9
  55. package/dist/types/services/config/types.d.ts +79 -58
  56. package/dist/types/services/core/GlobalTypes.d.ts +25 -0
  57. package/dist/types/services/core/Utils.d.ts +40 -1
  58. package/dist/types/services/task/TaskUtils.d.ts +28 -0
  59. package/dist/types/services/task/constants.d.ts +23 -0
  60. package/dist/types/services/task/contact.d.ts +10 -0
  61. package/dist/types/services/task/index.d.ts +85 -4
  62. package/dist/types/services/task/types.d.ts +233 -21
  63. package/dist/types/types.d.ts +162 -0
  64. package/dist/types/utils/PageCache.d.ts +173 -0
  65. package/dist/types.js +17 -0
  66. package/dist/types.js.map +1 -1
  67. package/dist/utils/PageCache.js +192 -0
  68. package/dist/utils/PageCache.js.map +1 -0
  69. package/dist/webex.js +1 -1
  70. package/package.json +10 -9
  71. package/src/cc.ts +221 -52
  72. package/src/constants.ts +1 -0
  73. package/src/index.ts +16 -2
  74. package/src/logger-proxy.ts +24 -1
  75. package/src/metrics/MetricsManager.ts +1 -1
  76. package/src/metrics/behavioral-events.ts +94 -0
  77. package/src/metrics/constants.ts +37 -1
  78. package/src/services/AddressBook.ts +291 -0
  79. package/src/services/EntryPoint.ts +241 -0
  80. package/src/services/Queue.ts +277 -0
  81. package/src/services/config/constants.ts +42 -2
  82. package/src/services/config/index.ts +30 -30
  83. package/src/services/config/types.ts +59 -58
  84. package/src/services/core/GlobalTypes.ts +27 -0
  85. package/src/services/core/Utils.ts +199 -1
  86. package/src/services/core/aqm-reqs.ts +0 -5
  87. package/src/services/core/websocket/WebSocketManager.ts +0 -4
  88. package/src/services/task/TaskManager.ts +123 -5
  89. package/src/services/task/TaskUtils.ts +81 -0
  90. package/src/services/task/constants.ts +25 -0
  91. package/src/services/task/contact.ts +80 -0
  92. package/src/services/task/index.ts +510 -71
  93. package/src/services/task/types.ts +251 -20
  94. package/src/types.ts +180 -0
  95. package/src/utils/PageCache.ts +252 -0
  96. package/test/unit/spec/cc.ts +282 -85
  97. package/test/unit/spec/metrics/MetricsManager.ts +0 -1
  98. package/test/unit/spec/metrics/behavioral-events.ts +42 -0
  99. package/test/unit/spec/services/AddressBook.ts +332 -0
  100. package/test/unit/spec/services/EntryPoint.ts +259 -0
  101. package/test/unit/spec/services/Queue.ts +323 -0
  102. package/test/unit/spec/services/config/index.ts +279 -65
  103. package/test/unit/spec/services/core/Utils.ts +50 -0
  104. package/test/unit/spec/services/core/aqm-reqs.ts +1 -3
  105. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +0 -4
  106. package/test/unit/spec/services/task/TaskManager.ts +390 -1
  107. package/test/unit/spec/services/task/TaskUtils.ts +131 -0
  108. package/test/unit/spec/services/task/contact.ts +31 -1
  109. package/test/unit/spec/services/task/index.ts +585 -130
  110. package/umd/contact-center.min.js +2 -2
  111. package/umd/contact-center.min.js.map +1 -1
@@ -1,10 +1,16 @@
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 {getErrorDetails} from '../core/Utils';
4
+ import {
5
+ generateTaskErrorObject,
6
+ deriveConsultTransferDestinationType,
7
+ getDestinationAgentId,
8
+ buildConsultConferenceParamData,
9
+ } from '../core/Utils';
10
+ import {Failure} from '../core/GlobalTypes';
5
11
  import {LoginOption} from '../../types';
6
12
  import {TASK_FILE} from '../../constants';
7
- import {METHODS} from './constants';
13
+ import {METHODS, KEYS_TO_NOT_DELETE} from './constants';
8
14
  import routingContact from './contact';
9
15
  import LoggerProxy from '../../logger-proxy';
10
16
  import {
@@ -19,14 +25,12 @@ import {
19
25
  ConsultEndPayload,
20
26
  TransferPayLoad,
21
27
  DESTINATION_TYPE,
22
- CONSULT_TRANSFER_DESTINATION_TYPE,
23
28
  ConsultTransferPayLoad,
24
29
  MEDIA_CHANNEL,
25
30
  } from './types';
26
31
  import WebCallingService from '../WebCallingService';
27
32
  import MetricsManager from '../../metrics/MetricsManager';
28
33
  import {METRIC_EVENT_NAMES} from '../../metrics/constants';
29
- import {Failure} from '../core/GlobalTypes';
30
34
  import AutoWrapup from './AutoWrapup';
31
35
  import {WrapupData} from '../config/types';
32
36
 
@@ -135,6 +139,7 @@ export default class Task extends EventEmitter implements ITask {
135
139
  public webCallMap: Record<TaskId, CallId>;
136
140
  private wrapupData: WrapupData;
137
141
  public autoWrapup?: AutoWrapup;
142
+ private agentId: string;
138
143
 
139
144
  /**
140
145
  * Creates a new Task instance which provides the following features:
@@ -147,7 +152,8 @@ export default class Task extends EventEmitter implements ITask {
147
152
  contact: ReturnType<typeof routingContact>,
148
153
  webCallingService: WebCallingService,
149
154
  data: TaskData,
150
- wrapupData: WrapupData
155
+ wrapupData: WrapupData,
156
+ agentId: string
151
157
  ) {
152
158
  super();
153
159
  this.contact = contact;
@@ -158,6 +164,7 @@ export default class Task extends EventEmitter implements ITask {
158
164
  this.metricsManager = MetricsManager.getInstance();
159
165
  this.registerWebCallListeners();
160
166
  this.setupAutoWrapupTimer();
167
+ this.agentId = agentId;
161
168
  }
162
169
 
163
170
  /**
@@ -274,9 +281,24 @@ export default class Task extends EventEmitter implements ITask {
274
281
  * @private
275
282
  */
276
283
  private reconcileData(oldData: TaskData, newData: TaskData): TaskData {
284
+ // Remove keys from oldData that are not in newData
285
+ Object.keys(oldData).forEach((key) => {
286
+ if (!(key in newData) && !KEYS_TO_NOT_DELETE.includes(key as string)) {
287
+ delete oldData[key];
288
+ }
289
+ });
290
+
291
+ // Merge or update keys from newData
277
292
  Object.keys(newData).forEach((key) => {
278
- if (newData[key] && typeof newData[key] === 'object' && !Array.isArray(newData[key])) {
279
- oldData[key] = this.reconcileData({...oldData[key]}, newData[key]);
293
+ if (
294
+ newData[key] &&
295
+ typeof newData[key] === 'object' &&
296
+ !Array.isArray(newData[key]) &&
297
+ oldData[key] &&
298
+ typeof oldData[key] === 'object' &&
299
+ !Array.isArray(oldData[key])
300
+ ) {
301
+ this.reconcileData(oldData[key], newData[key]);
280
302
  } else {
281
303
  oldData[key] = newData[key];
282
304
  }
@@ -373,17 +395,25 @@ export default class Task extends EventEmitter implements ITask {
373
395
 
374
396
  return Promise.resolve(); // TODO: reject for extension as part of refactor
375
397
  } catch (error) {
376
- const {error: detailedError} = getErrorDetails(error, METHODS.ACCEPT, TASK_FILE);
398
+ const err = generateTaskErrorObject(error, METHODS.ACCEPT, TASK_FILE);
399
+ const taskErrorProps = {
400
+ trackingId: err.data?.trackingId,
401
+ errorMessage: err.data?.message,
402
+ errorType: err.data?.errorType,
403
+ errorData: err.data?.errorData,
404
+ reasonCode: err.data?.reasonCode,
405
+ };
377
406
  this.metricsManager.trackEvent(
378
407
  METRIC_EVENT_NAMES.TASK_ACCEPT_FAILED,
379
408
  {
380
409
  taskId: this.data.interactionId,
381
410
  error: error.toString(),
411
+ ...taskErrorProps,
382
412
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details as Failure),
383
413
  },
384
414
  ['operational', 'behavioral', 'business']
385
415
  );
386
- throw detailedError;
416
+ throw err;
387
417
  }
388
418
  }
389
419
 
@@ -422,8 +452,8 @@ export default class Task extends EventEmitter implements ITask {
422
452
 
423
453
  return Promise.resolve();
424
454
  } catch (error) {
425
- const {error: detailedError} = getErrorDetails(error, METHODS.TOGGLE_MUTE, TASK_FILE);
426
- throw detailedError;
455
+ const err = generateTaskErrorObject(error, METHODS.TOGGLE_MUTE, TASK_FILE);
456
+ throw err;
427
457
  }
428
458
  }
429
459
 
@@ -470,17 +500,25 @@ export default class Task extends EventEmitter implements ITask {
470
500
 
471
501
  return Promise.resolve();
472
502
  } catch (error) {
473
- const {error: detailedError} = getErrorDetails(error, METHODS.DECLINE, TASK_FILE);
503
+ const err = generateTaskErrorObject(error, METHODS.DECLINE, TASK_FILE);
504
+ const taskErrorProps = {
505
+ trackingId: err.data?.trackingId,
506
+ errorMessage: err.data?.message,
507
+ errorType: err.data?.errorType,
508
+ errorData: err.data?.errorData,
509
+ reasonCode: err.data?.reasonCode,
510
+ };
474
511
  this.metricsManager.trackEvent(
475
512
  METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
476
513
  {
477
514
  taskId: this.data.interactionId,
478
515
  error: error.toString(),
516
+ ...taskErrorProps,
479
517
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
480
518
  },
481
519
  ['operational', 'behavioral']
482
520
  );
483
- throw detailedError;
521
+ throw err;
484
522
  }
485
523
  }
486
524
 
@@ -488,6 +526,7 @@ export default class Task extends EventEmitter implements ITask {
488
526
  * Puts the current task/interaction on hold.
489
527
  * Emits task:hold event when successful. For voice tasks, this mutes the audio.
490
528
  *
529
+ * @param mediaResourceId - Optional media resource ID to use for the hold operation. If not provided, uses the task's current mediaResourceId
491
530
  * @returns Promise<TaskResponse>
492
531
  * @throws Error if hold operation fails
493
532
  * @example
@@ -508,9 +547,17 @@ export default class Task extends EventEmitter implements ITask {
508
547
  * console.error('Failed to place task on hold:', error);
509
548
  * // Handle error (e.g., show error message, reset UI state)
510
549
  * }
550
+ *
551
+ * // Place task on hold with custom mediaResourceId
552
+ * try {
553
+ * await task.hold('custom-media-resource-id');
554
+ * console.log('Successfully placed task on hold with custom mediaResourceId');
555
+ * } catch (error) {
556
+ * console.error('Failed to place task on hold:', error);
557
+ * }
511
558
  * ```
512
559
  */
513
- public async hold(): Promise<TaskResponse> {
560
+ public async hold(mediaResourceId?: string): Promise<TaskResponse> {
514
561
  try {
515
562
  LoggerProxy.info(`Holding task`, {
516
563
  module: TASK_FILE,
@@ -523,9 +570,11 @@ export default class Task extends EventEmitter implements ITask {
523
570
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
524
571
  ]);
525
572
 
573
+ const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
574
+
526
575
  const response = await this.contact.hold({
527
576
  interactionId: this.data.interactionId,
528
- data: {mediaResourceId: this.data.mediaResourceId},
577
+ data: {mediaResourceId: effectiveMediaResourceId},
529
578
  });
530
579
 
531
580
  this.metricsManager.trackEvent(
@@ -533,7 +582,7 @@ export default class Task extends EventEmitter implements ITask {
533
582
  {
534
583
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
535
584
  taskId: this.data.interactionId,
536
- mediaResourceId: this.data.mediaResourceId,
585
+ mediaResourceId: effectiveMediaResourceId,
537
586
  },
538
587
  ['operational', 'behavioral']
539
588
  );
@@ -547,18 +596,28 @@ export default class Task extends EventEmitter implements ITask {
547
596
 
548
597
  return response;
549
598
  } catch (error) {
550
- const {error: detailedError} = getErrorDetails(error, METHODS.HOLD, TASK_FILE);
599
+ const err = generateTaskErrorObject(error, METHODS.HOLD, TASK_FILE);
600
+ const taskErrorProps = {
601
+ trackingId: err.data?.trackingId,
602
+ errorMessage: err.data?.message,
603
+ errorType: err.data?.errorType,
604
+ errorData: err.data?.errorData,
605
+ reasonCode: err.data?.reasonCode,
606
+ };
607
+ const effectiveMediaResourceId = mediaResourceId ?? this.data.mediaResourceId;
608
+
551
609
  this.metricsManager.trackEvent(
552
610
  METRIC_EVENT_NAMES.TASK_HOLD_FAILED,
553
611
  {
554
612
  taskId: this.data.interactionId,
555
- mediaResourceId: this.data.mediaResourceId,
613
+ mediaResourceId: effectiveMediaResourceId,
556
614
  error: error.toString(),
615
+ ...taskErrorProps,
557
616
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
558
617
  },
559
618
  ['operational', 'behavioral']
560
619
  );
561
- throw detailedError;
620
+ throw err;
562
621
  }
563
622
  }
564
623
 
@@ -566,6 +625,7 @@ export default class Task extends EventEmitter implements ITask {
566
625
  * Resumes the task/interaction that was previously put on hold.
567
626
  * Emits task:resume event when successful. For voice tasks, this restores the audio.
568
627
  *
628
+ * @param mediaResourceId - Optional media resource ID to use for the resume operation. If not provided, uses the task's current mediaResourceId from interaction media
569
629
  * @returns Promise<TaskResponse>
570
630
  * @throws Error if resume operation fails
571
631
  * @example
@@ -586,9 +646,17 @@ export default class Task extends EventEmitter implements ITask {
586
646
  * console.error('Failed to resume task:', error);
587
647
  * // Handle error (e.g., show error message)
588
648
  * }
649
+ *
650
+ * // Resume task from hold with custom mediaResourceId
651
+ * try {
652
+ * await task.resume('custom-media-resource-id');
653
+ * console.log('Successfully resumed task from hold with custom mediaResourceId');
654
+ * } catch (error) {
655
+ * console.error('Failed to resume task:', error);
656
+ * }
589
657
  * ```
590
658
  */
591
- public async resume(): Promise<TaskResponse> {
659
+ public async resume(mediaResourceId?: string): Promise<TaskResponse> {
592
660
  try {
593
661
  LoggerProxy.info(`Resuming task`, {
594
662
  module: TASK_FILE,
@@ -596,7 +664,9 @@ export default class Task extends EventEmitter implements ITask {
596
664
  interactionId: this.data.interactionId,
597
665
  });
598
666
  const {mainInteractionId} = this.data.interaction;
599
- const {mediaResourceId} = this.data.interaction.media[mainInteractionId];
667
+ const defaultMediaResourceId =
668
+ this.data.interaction.media[mainInteractionId]?.mediaResourceId;
669
+ const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
600
670
 
601
671
  this.metricsManager.timeEvent([
602
672
  METRIC_EVENT_NAMES.TASK_RESUME_SUCCESS,
@@ -605,7 +675,7 @@ export default class Task extends EventEmitter implements ITask {
605
675
 
606
676
  const response = await this.contact.unHold({
607
677
  interactionId: this.data.interactionId,
608
- data: {mediaResourceId},
678
+ data: {mediaResourceId: effectiveMediaResourceId},
609
679
  });
610
680
 
611
681
  this.metricsManager.trackEvent(
@@ -613,7 +683,7 @@ export default class Task extends EventEmitter implements ITask {
613
683
  {
614
684
  taskId: this.data.interactionId,
615
685
  mainInteractionId,
616
- mediaResourceId,
686
+ mediaResourceId: effectiveMediaResourceId,
617
687
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
618
688
  },
619
689
  ['operational', 'behavioral']
@@ -628,21 +698,32 @@ export default class Task extends EventEmitter implements ITask {
628
698
 
629
699
  return response;
630
700
  } catch (error) {
631
- const {error: detailedError} = getErrorDetails(error, METHODS.RESUME, TASK_FILE);
701
+ const err = generateTaskErrorObject(error, METHODS.RESUME, TASK_FILE);
632
702
  const mainInteractionId = this.data.interaction?.mainInteractionId;
703
+ const defaultMediaResourceId = mainInteractionId
704
+ ? this.data.interaction.media[mainInteractionId]?.mediaResourceId
705
+ : '';
706
+ const effectiveMediaResourceId = mediaResourceId ?? defaultMediaResourceId;
707
+
708
+ const taskErrorProps = {
709
+ trackingId: err.data?.trackingId,
710
+ errorMessage: err.data?.message,
711
+ errorType: err.data?.errorType,
712
+ errorData: err.data?.errorData,
713
+ reasonCode: err.data?.reasonCode,
714
+ };
633
715
  this.metricsManager.trackEvent(
634
716
  METRIC_EVENT_NAMES.TASK_RESUME_FAILED,
635
717
  {
636
718
  taskId: this.data.interactionId,
637
719
  mainInteractionId,
638
- mediaResourceId: mainInteractionId
639
- ? this.data.interaction.media[mainInteractionId].mediaResourceId
640
- : '',
720
+ mediaResourceId: effectiveMediaResourceId,
721
+ ...taskErrorProps,
641
722
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
642
723
  },
643
724
  ['operational', 'behavioral']
644
725
  );
645
- throw detailedError;
726
+ throw err;
646
727
  }
647
728
  }
648
729
 
@@ -722,16 +803,24 @@ export default class Task extends EventEmitter implements ITask {
722
803
 
723
804
  return response;
724
805
  } catch (error) {
725
- const {error: detailedError} = getErrorDetails(error, METHODS.END, TASK_FILE);
806
+ const err = generateTaskErrorObject(error, METHODS.END, TASK_FILE);
807
+ const taskErrorProps = {
808
+ trackingId: err.data?.trackingId,
809
+ errorMessage: err.data?.message,
810
+ errorType: err.data?.errorType,
811
+ errorData: err.data?.errorData,
812
+ reasonCode: err.data?.reasonCode,
813
+ };
726
814
  this.metricsManager.trackEvent(
727
815
  METRIC_EVENT_NAMES.TASK_END_FAILED,
728
816
  {
729
817
  taskId: this.data.interactionId,
818
+ ...taskErrorProps,
730
819
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
731
820
  },
732
821
  ['operational', 'behavioral', 'business']
733
822
  );
734
- throw detailedError;
823
+ throw err;
735
824
  }
736
825
  }
737
826
 
@@ -826,18 +915,26 @@ export default class Task extends EventEmitter implements ITask {
826
915
 
827
916
  return response;
828
917
  } catch (error) {
829
- const {error: detailedError} = getErrorDetails(error, METHODS.WRAPUP, TASK_FILE);
918
+ const err = generateTaskErrorObject(error, METHODS.WRAPUP, TASK_FILE);
919
+ const taskErrorProps = {
920
+ trackingId: err.data?.trackingId,
921
+ errorMessage: err.data?.message,
922
+ errorType: err.data?.errorType,
923
+ errorData: err.data?.errorData,
924
+ reasonCode: err.data?.reasonCode,
925
+ };
830
926
  this.metricsManager.trackEvent(
831
927
  METRIC_EVENT_NAMES.TASK_WRAPUP_FAILED,
832
928
  {
833
929
  taskId: this.data.interactionId,
834
930
  wrapUpCode: wrapupPayload.auxCodeId,
835
931
  wrapUpReason: wrapupPayload.wrapUpReason,
932
+ ...taskErrorProps,
836
933
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
837
934
  },
838
935
  ['operational', 'behavioral', 'business']
839
936
  );
840
- throw detailedError;
937
+ throw err;
841
938
  }
842
939
  }
843
940
 
@@ -906,17 +1003,25 @@ export default class Task extends EventEmitter implements ITask {
906
1003
 
907
1004
  return result;
908
1005
  } catch (error) {
909
- const {error: detailedError} = getErrorDetails(error, METHODS.PAUSE_RECORDING, TASK_FILE);
1006
+ const err = generateTaskErrorObject(error, METHODS.PAUSE_RECORDING, TASK_FILE);
1007
+ const taskErrorProps = {
1008
+ trackingId: err.data?.trackingId,
1009
+ errorMessage: err.data?.message,
1010
+ errorType: err.data?.errorType,
1011
+ errorData: err.data?.errorData,
1012
+ reasonCode: err.data?.reasonCode,
1013
+ };
910
1014
  this.metricsManager.trackEvent(
911
1015
  METRIC_EVENT_NAMES.TASK_PAUSE_RECORDING_FAILED,
912
1016
  {
913
1017
  taskId: this.data.interactionId,
914
1018
  error: error.toString(),
1019
+ ...taskErrorProps,
915
1020
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
916
1021
  },
917
1022
  ['operational', 'behavioral', 'business']
918
1023
  );
919
- throw detailedError;
1024
+ throw err;
920
1025
  }
921
1026
  }
922
1027
 
@@ -997,17 +1102,25 @@ export default class Task extends EventEmitter implements ITask {
997
1102
 
998
1103
  return result;
999
1104
  } catch (error) {
1000
- const {error: detailedError} = getErrorDetails(error, METHODS.RESUME_RECORDING, TASK_FILE);
1105
+ const err = generateTaskErrorObject(error, METHODS.RESUME_RECORDING, TASK_FILE);
1106
+ const taskErrorProps = {
1107
+ trackingId: err.data?.trackingId,
1108
+ errorMessage: err.data?.message,
1109
+ errorType: err.data?.errorType,
1110
+ errorData: err.data?.errorData,
1111
+ reasonCode: err.data?.reasonCode,
1112
+ };
1001
1113
  this.metricsManager.trackEvent(
1002
1114
  METRIC_EVENT_NAMES.TASK_RESUME_RECORDING_FAILED,
1003
1115
  {
1004
1116
  taskId: this.data.interactionId,
1005
1117
  error: error.toString(),
1118
+ ...taskErrorProps,
1006
1119
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1007
1120
  },
1008
1121
  ['operational', 'behavioral', 'business']
1009
1122
  );
1010
- throw detailedError;
1123
+ throw err;
1011
1124
  }
1012
1125
  }
1013
1126
 
@@ -1082,7 +1195,14 @@ export default class Task extends EventEmitter implements ITask {
1082
1195
 
1083
1196
  return result;
1084
1197
  } catch (error) {
1085
- const {error: detailedError} = getErrorDetails(error, METHODS.CONSULT, TASK_FILE);
1198
+ const err = generateTaskErrorObject(error, METHODS.CONSULT, TASK_FILE);
1199
+ const taskErrorProps = {
1200
+ trackingId: err.data?.trackingId,
1201
+ errorMessage: err.data?.message,
1202
+ errorType: err.data?.errorType,
1203
+ errorData: err.data?.errorData,
1204
+ reasonCode: err.data?.reasonCode,
1205
+ };
1086
1206
  this.metricsManager.trackEvent(
1087
1207
  METRIC_EVENT_NAMES.TASK_CONSULT_START_FAILED,
1088
1208
  {
@@ -1090,11 +1210,12 @@ export default class Task extends EventEmitter implements ITask {
1090
1210
  destination: consultPayload.to,
1091
1211
  destinationType: consultPayload.destinationType,
1092
1212
  error: error.toString(),
1213
+ ...taskErrorProps,
1093
1214
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1094
1215
  },
1095
1216
  ['operational', 'behavioral', 'business']
1096
1217
  );
1097
- throw detailedError;
1218
+ throw err;
1098
1219
  }
1099
1220
  }
1100
1221
 
@@ -1167,17 +1288,25 @@ export default class Task extends EventEmitter implements ITask {
1167
1288
 
1168
1289
  return result;
1169
1290
  } catch (error) {
1170
- const {error: detailedError} = getErrorDetails(error, METHODS.END_CONSULT, TASK_FILE);
1291
+ const err = generateTaskErrorObject(error, METHODS.END_CONSULT, TASK_FILE);
1292
+ const taskErrorProps = {
1293
+ trackingId: err.data?.trackingId,
1294
+ errorMessage: err.data?.message,
1295
+ errorType: err.data?.errorType,
1296
+ errorData: err.data?.errorData,
1297
+ reasonCode: err.data?.reasonCode,
1298
+ };
1171
1299
  this.metricsManager.trackEvent(
1172
1300
  METRIC_EVENT_NAMES.TASK_CONSULT_END_FAILED,
1173
1301
  {
1174
1302
  taskId: this.data.interactionId,
1175
1303
  error: error.toString(),
1304
+ ...taskErrorProps,
1176
1305
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1177
1306
  },
1178
1307
  ['operational', 'behavioral', 'business']
1179
1308
  );
1180
- throw detailedError;
1309
+ throw err;
1181
1310
  }
1182
1311
  }
1183
1312
 
@@ -1258,7 +1387,14 @@ export default class Task extends EventEmitter implements ITask {
1258
1387
 
1259
1388
  return result;
1260
1389
  } catch (error) {
1261
- const {error: detailedError} = getErrorDetails(error, METHODS.TRANSFER, TASK_FILE);
1390
+ const err = generateTaskErrorObject(error, METHODS.TRANSFER, TASK_FILE);
1391
+ const taskErrorProps = {
1392
+ trackingId: err.data?.trackingId,
1393
+ errorMessage: err.data?.message,
1394
+ errorType: err.data?.errorType,
1395
+ errorData: err.data?.errorData,
1396
+ reasonCode: err.data?.reasonCode,
1397
+ };
1262
1398
  this.metricsManager.trackEvent(
1263
1399
  METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1264
1400
  {
@@ -1267,11 +1403,12 @@ export default class Task extends EventEmitter implements ITask {
1267
1403
  destinationType: transferPayload.destinationType,
1268
1404
  isConsultTransfer: false,
1269
1405
  error: error.toString(),
1406
+ ...taskErrorProps,
1270
1407
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1271
1408
  },
1272
1409
  ['operational', 'behavioral', 'business']
1273
1410
  );
1274
- throw detailedError;
1411
+ throw err;
1275
1412
  }
1276
1413
  }
1277
1414
 
@@ -1308,68 +1445,370 @@ export default class Task extends EventEmitter implements ITask {
1308
1445
  * ```
1309
1446
  */
1310
1447
  public async consultTransfer(
1311
- consultTransferPayload: ConsultTransferPayLoad
1448
+ consultTransferPayload?: ConsultTransferPayLoad
1312
1449
  ): Promise<TaskResponse> {
1313
1450
  try {
1314
- LoggerProxy.info(`Initiating consult transfer to ${consultTransferPayload.to}`, {
1315
- module: TASK_FILE,
1316
- method: METHODS.CONSULT_TRANSFER,
1317
- interactionId: this.data.interactionId,
1318
- });
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
+ );
1319
1456
 
1320
- // For queue destinations, use the destAgentId from task data
1321
- if (consultTransferPayload.destinationType === CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE) {
1322
- if (!this.data.destAgentId) {
1323
- throw new Error('No agent has accepted this queue consult yet');
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
+ }
1461
+
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,
1324
1468
  }
1469
+ );
1470
+ // Obtain payload based on desktop logic using TaskData
1471
+ const finalDestinationType = deriveConsultTransferDestinationType(this.data);
1325
1472
 
1326
- // Override the destination with the agent who accepted the queue consult
1327
- consultTransferPayload = {
1328
- to: this.data.destAgentId,
1329
- destinationType: CONSULT_TRANSFER_DESTINATION_TYPE.AGENT,
1330
- };
1331
- }
1473
+ // By default we always use the computed destAgentId as the target id
1474
+ const consultTransferRequest: ConsultTransferPayLoad = {
1475
+ to: destAgentId,
1476
+ destinationType: finalDestinationType,
1477
+ };
1332
1478
 
1333
1479
  const result = await this.contact.consultTransfer({
1334
1480
  interactionId: this.data.interactionId,
1335
- data: consultTransferPayload,
1481
+ data: consultTransferRequest,
1336
1482
  });
1337
1483
 
1338
1484
  this.metricsManager.trackEvent(
1339
1485
  METRIC_EVENT_NAMES.TASK_TRANSFER_SUCCESS,
1340
1486
  {
1341
1487
  taskId: this.data.interactionId,
1342
- destination: consultTransferPayload.to,
1343
- destinationType: consultTransferPayload.destinationType,
1488
+ destination: consultTransferRequest.to,
1489
+ destinationType: consultTransferRequest.destinationType,
1344
1490
  isConsultTransfer: true,
1345
1491
  ...MetricsManager.getCommonTrackingFieldForAQMResponse(result),
1346
1492
  },
1347
1493
  ['operational', 'behavioral', 'business']
1348
1494
  );
1349
1495
 
1350
- LoggerProxy.log(`Consult transfer completed successfully to ${consultTransferPayload.to}`, {
1351
- module: TASK_FILE,
1352
- method: METHODS.CONSULT_TRANSFER,
1353
- trackingId: result.trackingId,
1354
- interactionId: this.data.interactionId,
1355
- });
1496
+ LoggerProxy.log(
1497
+ `Consult transfer completed successfully to ${consultTransferPayload?.to || destAgentId}`,
1498
+ {
1499
+ module: TASK_FILE,
1500
+ method: METHODS.CONSULT_TRANSFER,
1501
+ trackingId: result.trackingId,
1502
+ interactionId: this.data.interactionId,
1503
+ }
1504
+ );
1356
1505
 
1357
1506
  return result;
1358
1507
  } catch (error) {
1359
- const {error: detailedError} = getErrorDetails(error, METHODS.CONSULT_TRANSFER, TASK_FILE);
1508
+ const err = generateTaskErrorObject(error, METHODS.CONSULT_TRANSFER, TASK_FILE);
1509
+ const taskErrorProps = {
1510
+ trackingId: err.data?.trackingId,
1511
+ errorMessage: err.data?.message,
1512
+ errorType: err.data?.errorType,
1513
+ errorData: err.data?.errorData,
1514
+ reasonCode: err.data?.reasonCode,
1515
+ };
1516
+ const failedDestinationType = deriveConsultTransferDestinationType(this.data);
1517
+ const failedDestAgentId = getDestinationAgentId(
1518
+ this.data.interaction?.participants,
1519
+ this.data.agentId
1520
+ );
1360
1521
  this.metricsManager.trackEvent(
1361
1522
  METRIC_EVENT_NAMES.TASK_TRANSFER_FAILED,
1362
1523
  {
1363
1524
  taskId: this.data.interactionId,
1364
- destination: consultTransferPayload.to,
1365
- destinationType: consultTransferPayload.destinationType,
1525
+ destination: failedDestAgentId || '',
1526
+ destinationType: failedDestinationType,
1366
1527
  isConsultTransfer: true,
1367
1528
  error: error.toString(),
1529
+ ...taskErrorProps,
1530
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1531
+ },
1532
+ ['operational', 'behavioral', 'business']
1533
+ );
1534
+ throw err;
1535
+ }
1536
+ }
1537
+
1538
+ /**
1539
+ * Starts a consultation conference by merging the consultation call with the main call
1540
+ *
1541
+ * Creates a three-way conference between the agent, customer, and consulted party
1542
+ * Extracts required consultation data from the current task data
1543
+ * On success, emits a `task:conferenceStarted` event
1544
+ *
1545
+ * @returns Promise<TaskResponse> - Response from the consultation conference API
1546
+ * @throws Error if the operation fails or if consultation data is invalid
1547
+ *
1548
+ * @example
1549
+ * ```typescript
1550
+ * try {
1551
+ * await task.consultConference();
1552
+ * console.log('Conference started successfully');
1553
+ * } catch (error) {
1554
+ * console.error('Failed to start conference:', error);
1555
+ * }
1556
+ * ```
1557
+ */
1558
+ public async consultConference(): Promise<TaskResponse> {
1559
+ // Extract consultation conference data from task data (used in both try and catch)
1560
+ const consultationData = {
1561
+ agentId: this.agentId,
1562
+ destAgentId: this.data.destAgentId,
1563
+ destinationType: this.data.destinationType || 'agent',
1564
+ };
1565
+
1566
+ try {
1567
+ LoggerProxy.info(`Initiating consult conference to ${consultationData.destAgentId}`, {
1568
+ module: TASK_FILE,
1569
+ method: METHODS.CONSULT_CONFERENCE,
1570
+ interactionId: this.data.interactionId,
1571
+ });
1572
+
1573
+ const paramsDataForConferenceV2 = buildConsultConferenceParamData(
1574
+ consultationData,
1575
+ this.data.interactionId
1576
+ );
1577
+
1578
+ const response = await this.contact.consultConference({
1579
+ interactionId: paramsDataForConferenceV2.interactionId,
1580
+ data: paramsDataForConferenceV2.data,
1581
+ });
1582
+
1583
+ // Track success metrics (following consultTransfer pattern)
1584
+ this.metricsManager.trackEvent(
1585
+ METRIC_EVENT_NAMES.TASK_CONFERENCE_START_SUCCESS,
1586
+ {
1587
+ taskId: this.data.interactionId,
1588
+ destination: paramsDataForConferenceV2.data.to,
1589
+ destinationType: paramsDataForConferenceV2.data.destinationType,
1590
+ agentId: paramsDataForConferenceV2.data.agentId,
1591
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
1592
+ },
1593
+ ['operational', 'behavioral', 'business']
1594
+ );
1595
+
1596
+ LoggerProxy.log(`Consult conference started successfully`, {
1597
+ module: TASK_FILE,
1598
+ method: METHODS.CONSULT_CONFERENCE,
1599
+ interactionId: this.data.interactionId,
1600
+ });
1601
+
1602
+ return response;
1603
+ } catch (error) {
1604
+ const err = generateTaskErrorObject(error, METHODS.CONSULT_CONFERENCE, TASK_FILE);
1605
+ const taskErrorProps = {
1606
+ trackingId: err.data?.trackingId,
1607
+ errorMessage: err.data?.message,
1608
+ errorType: err.data?.errorType,
1609
+ errorData: err.data?.errorData,
1610
+ reasonCode: err.data?.reasonCode,
1611
+ };
1612
+
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
+ this.metricsManager.trackEvent(
1621
+ METRIC_EVENT_NAMES.TASK_CONFERENCE_START_FAILED,
1622
+ {
1623
+ taskId: this.data.interactionId,
1624
+ destination: failedParamsData.data.to,
1625
+ destinationType: failedParamsData.data.destinationType,
1626
+ agentId: failedParamsData.data.agentId,
1627
+ error: error.toString(),
1628
+ ...taskErrorProps,
1368
1629
  ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1369
1630
  },
1370
1631
  ['operational', 'behavioral', 'business']
1371
1632
  );
1372
- throw detailedError;
1633
+
1634
+ LoggerProxy.error(`Failed to start consult conference`, {
1635
+ module: TASK_FILE,
1636
+ method: METHODS.CONSULT_CONFERENCE,
1637
+ interactionId: this.data.interactionId,
1638
+ });
1639
+
1640
+ throw err;
1641
+ }
1642
+ }
1643
+
1644
+ /**
1645
+ * Exits the current conference by removing the agent from the conference call
1646
+ *
1647
+ * Exits the agent from the conference, leaving the customer and consulted party connected
1648
+ * On success, emits a `task:conferenceEnded` event
1649
+ *
1650
+ * @returns Promise<TaskResponse> - Response from the conference exit API
1651
+ * @throws Error if the operation fails or if no active conference exists
1652
+ *
1653
+ * @example
1654
+ * ```typescript
1655
+ * try {
1656
+ * await task.exitConference();
1657
+ * console.log('Successfully exited conference');
1658
+ * } catch (error) {
1659
+ * console.error('Failed to exit conference:', error);
1660
+ * }
1661
+ * ```
1662
+ */
1663
+ public async exitConference(): Promise<TaskResponse> {
1664
+ try {
1665
+ LoggerProxy.info(`Exiting consult conference`, {
1666
+ module: TASK_FILE,
1667
+ method: METHODS.EXIT_CONFERENCE,
1668
+ interactionId: this.data.interactionId,
1669
+ });
1670
+
1671
+ // Validate that interaction ID exists
1672
+ if (!this.data.interactionId) {
1673
+ throw new Error('Invalid interaction ID');
1674
+ }
1675
+
1676
+ const response = await this.contact.exitConference({
1677
+ interactionId: this.data.interactionId,
1678
+ });
1679
+
1680
+ // Track success metrics (following consultTransfer pattern)
1681
+ this.metricsManager.trackEvent(
1682
+ METRIC_EVENT_NAMES.TASK_CONFERENCE_END_SUCCESS,
1683
+ {
1684
+ taskId: this.data.interactionId,
1685
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
1686
+ },
1687
+ ['operational', 'behavioral', 'business']
1688
+ );
1689
+
1690
+ LoggerProxy.log(`Consult conference exited successfully`, {
1691
+ module: TASK_FILE,
1692
+ method: METHODS.EXIT_CONFERENCE,
1693
+ interactionId: this.data.interactionId,
1694
+ });
1695
+
1696
+ return response;
1697
+ } catch (error) {
1698
+ const err = generateTaskErrorObject(error, METHODS.EXIT_CONFERENCE, TASK_FILE);
1699
+ const taskErrorProps = {
1700
+ trackingId: err.data?.trackingId,
1701
+ errorMessage: err.data?.message,
1702
+ errorType: err.data?.errorType,
1703
+ errorData: err.data?.errorData,
1704
+ reasonCode: err.data?.reasonCode,
1705
+ };
1706
+
1707
+ // Track failure metrics (following consultTransfer pattern)
1708
+ this.metricsManager.trackEvent(
1709
+ METRIC_EVENT_NAMES.TASK_CONFERENCE_END_FAILED,
1710
+ {
1711
+ taskId: this.data.interactionId,
1712
+ error: error.toString(),
1713
+ ...taskErrorProps,
1714
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1715
+ },
1716
+ ['operational', 'behavioral', 'business']
1717
+ );
1718
+
1719
+ LoggerProxy.error(`Failed to exit consult conference`, {
1720
+ module: TASK_FILE,
1721
+ method: METHODS.EXIT_CONFERENCE,
1722
+ interactionId: this.data.interactionId,
1723
+ });
1724
+
1725
+ throw err;
1726
+ }
1727
+ }
1728
+
1729
+ /**
1730
+ * Transfers the current conference to another agent
1731
+ *
1732
+ * Moves the entire conference (including all participants) to a new agent,
1733
+ * while the current agent exits and goes to wrapup
1734
+ * On success, the current agent receives `task:conferenceEnded` event
1735
+ *
1736
+ * @returns Promise<TaskResponse> - Response from the conference transfer API
1737
+ * @throws Error if the operation fails or if no active conference exists
1738
+ *
1739
+ * @example
1740
+ * ```typescript
1741
+ * try {
1742
+ * await task.transferConference();
1743
+ * console.log('Conference transferred successfully');
1744
+ * } catch (error) {
1745
+ * console.error('Failed to transfer conference:', error);
1746
+ * }
1747
+ * ```
1748
+ */
1749
+ public async transferConference(): Promise<TaskResponse> {
1750
+ try {
1751
+ LoggerProxy.info(`Transferring conference`, {
1752
+ module: TASK_FILE,
1753
+ method: METHODS.TRANSFER_CONFERENCE,
1754
+ interactionId: this.data.interactionId,
1755
+ });
1756
+
1757
+ // Validate that interaction ID exists
1758
+ if (!this.data.interactionId) {
1759
+ throw new Error('Invalid interaction ID');
1760
+ }
1761
+
1762
+ const response = await this.contact.conferenceTransfer({
1763
+ interactionId: this.data.interactionId,
1764
+ });
1765
+
1766
+ // Track success metrics (following consultTransfer pattern)
1767
+ this.metricsManager.trackEvent(
1768
+ METRIC_EVENT_NAMES.TASK_CONFERENCE_TRANSFER_SUCCESS,
1769
+ {
1770
+ taskId: this.data.interactionId,
1771
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
1772
+ },
1773
+ ['operational', 'behavioral', 'business']
1774
+ );
1775
+
1776
+ LoggerProxy.log(`Conference transferred successfully`, {
1777
+ module: TASK_FILE,
1778
+ method: METHODS.TRANSFER_CONFERENCE,
1779
+ interactionId: this.data.interactionId,
1780
+ });
1781
+
1782
+ return response;
1783
+ } catch (error) {
1784
+ const err = generateTaskErrorObject(error, METHODS.TRANSFER_CONFERENCE, TASK_FILE);
1785
+ const taskErrorProps = {
1786
+ trackingId: err.data?.trackingId,
1787
+ errorMessage: err.data?.message,
1788
+ errorType: err.data?.errorType,
1789
+ errorData: err.data?.errorData,
1790
+ reasonCode: err.data?.reasonCode,
1791
+ };
1792
+
1793
+ // Track failure metrics (following consultTransfer pattern)
1794
+ this.metricsManager.trackEvent(
1795
+ METRIC_EVENT_NAMES.TASK_CONFERENCE_TRANSFER_FAILED,
1796
+ {
1797
+ taskId: this.data.interactionId,
1798
+ error: error.toString(),
1799
+ ...taskErrorProps,
1800
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error.details || {}),
1801
+ },
1802
+ ['operational', 'behavioral', 'business']
1803
+ );
1804
+
1805
+ LoggerProxy.error(`Failed to transfer conference`, {
1806
+ module: TASK_FILE,
1807
+ method: METHODS.TRANSFER_CONFERENCE,
1808
+ interactionId: this.data.interactionId,
1809
+ });
1810
+
1811
+ throw err;
1373
1812
  }
1374
1813
  }
1375
1814
  }