@webex/contact-center 3.12.0-llmrefactor.2 → 3.12.0-llmrefactor.3

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 (187) hide show
  1. package/.sdd/manifest.json +5 -4
  2. package/ai-docs/ARCHITECTURE.md +1 -1
  3. package/ai-docs/CONTRACTS.md +5 -1
  4. package/ai-docs/SECURITY.md +1 -1
  5. package/ai-docs/contact-center-spec.md +22 -4
  6. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  7. package/dist/cc.js +440 -10
  8. package/dist/cc.js.map +1 -1
  9. package/dist/config.js +7 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +10 -2
  12. package/dist/constants.js.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/metrics/behavioral-events.js +13 -0
  15. package/dist/metrics/behavioral-events.js.map +1 -1
  16. package/dist/metrics/constants.js +2 -0
  17. package/dist/metrics/constants.js.map +1 -1
  18. package/dist/services/AddressBook.js +18 -15
  19. package/dist/services/AddressBook.js.map +1 -1
  20. package/dist/services/AnswerCallOnWebexService.js +174 -0
  21. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  22. package/dist/services/EntryPoint.js +46 -68
  23. package/dist/services/EntryPoint.js.map +1 -1
  24. package/dist/services/Queue.js +27 -22
  25. package/dist/services/Queue.js.map +1 -1
  26. package/dist/services/WebexCrossClientService.js +171 -0
  27. package/dist/services/WebexCrossClientService.js.map +1 -0
  28. package/dist/services/WxAppTelephonyMercurySync.js +93 -0
  29. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  30. package/dist/services/config/Util.js +3 -0
  31. package/dist/services/config/Util.js.map +1 -1
  32. package/dist/services/config/constants.js +10 -6
  33. package/dist/services/config/constants.js.map +1 -1
  34. package/dist/services/config/types.js +4 -0
  35. package/dist/services/config/types.js.map +1 -1
  36. package/dist/services/core/Err.js.map +1 -1
  37. package/dist/services/core/WebexRequest.js +6 -2
  38. package/dist/services/core/WebexRequest.js.map +1 -1
  39. package/dist/services/core/aqm-reqs.js +28 -14
  40. package/dist/services/core/aqm-reqs.js.map +1 -1
  41. package/dist/services/core/types.js.map +1 -1
  42. package/dist/services/task/Task.js +73 -7
  43. package/dist/services/task/Task.js.map +1 -1
  44. package/dist/services/task/TaskFactory.js +8 -4
  45. package/dist/services/task/TaskFactory.js.map +1 -1
  46. package/dist/services/task/TaskManager.js +176 -20
  47. package/dist/services/task/TaskManager.js.map +1 -1
  48. package/dist/services/task/WebexCallingUtils.js +70 -0
  49. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  50. package/dist/services/task/constants.js +4 -1
  51. package/dist/services/task/constants.js.map +1 -1
  52. package/dist/services/task/contact.js +29 -0
  53. package/dist/services/task/contact.js.map +1 -1
  54. package/dist/services/task/digital/Digital.js +3 -2
  55. package/dist/services/task/digital/Digital.js.map +1 -1
  56. package/dist/services/task/state-machine/TaskStateMachine.js +135 -37
  57. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  58. package/dist/services/task/state-machine/actions.js +6 -1
  59. package/dist/services/task/state-machine/actions.js.map +1 -1
  60. package/dist/services/task/state-machine/guards.js +38 -11
  61. package/dist/services/task/state-machine/guards.js.map +1 -1
  62. package/dist/services/task/state-machine/types.js.map +1 -1
  63. package/dist/services/task/state-machine/uiControlsComputer.js +118 -18
  64. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  65. package/dist/services/task/types.js +16 -1
  66. package/dist/services/task/types.js.map +1 -1
  67. package/dist/services/task/voice/Voice.js +350 -14
  68. package/dist/services/task/voice/Voice.js.map +1 -1
  69. package/dist/services/task/voice/WebRTC.js +4 -1
  70. package/dist/services/task/voice/WebRTC.js.map +1 -1
  71. package/dist/services/task/voice/wxAppVoiceMethods.js +201 -0
  72. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  73. package/dist/services/wxAppTelephonyUtils.js +19 -0
  74. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  75. package/dist/types/cc.d.ts +77 -4
  76. package/dist/types/config.d.ts +7 -0
  77. package/dist/types/constants.d.ts +8 -0
  78. package/dist/types/index.d.ts +3 -1
  79. package/dist/types/metrics/constants.d.ts +2 -0
  80. package/dist/types/services/AddressBook.d.ts +2 -1
  81. package/dist/types/services/AnswerCallOnWebexService.d.ts +37 -0
  82. package/dist/types/services/EntryPoint.d.ts +7 -5
  83. package/dist/types/services/Queue.d.ts +5 -3
  84. package/dist/types/services/WebexCrossClientService.d.ts +28 -0
  85. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  86. package/dist/types/services/config/constants.d.ts +8 -5
  87. package/dist/types/services/config/types.d.ts +17 -4
  88. package/dist/types/services/core/Err.d.ts +2 -0
  89. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  90. package/dist/types/services/core/types.d.ts +2 -0
  91. package/dist/types/services/task/Task.d.ts +20 -2
  92. package/dist/types/services/task/TaskFactory.d.ts +2 -1
  93. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  94. package/dist/types/services/task/constants.d.ts +3 -0
  95. package/dist/types/services/task/contact.d.ts +4 -0
  96. package/dist/types/services/task/digital/Digital.d.ts +2 -2
  97. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +260 -56
  98. package/dist/types/services/task/state-machine/guards.d.ts +6 -5
  99. package/dist/types/services/task/state-machine/types.d.ts +9 -1
  100. package/dist/types/services/task/types.d.ts +81 -4
  101. package/dist/types/services/task/voice/Voice.d.ts +48 -9
  102. package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
  103. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +52 -0
  104. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  105. package/dist/types/types.d.ts +14 -8
  106. package/dist/types/utils/PageCache.d.ts +20 -3
  107. package/dist/types.js +7 -2
  108. package/dist/types.js.map +1 -1
  109. package/dist/utils/PageCache.js +19 -5
  110. package/dist/utils/PageCache.js.map +1 -1
  111. package/dist/webex.js +1 -1
  112. package/package.json +1 -1
  113. package/src/cc.ts +506 -9
  114. package/src/config.ts +7 -0
  115. package/src/constants.ts +8 -0
  116. package/src/index.ts +2 -0
  117. package/src/metrics/ai-docs/metrics-spec.md +9 -3
  118. package/src/metrics/behavioral-events.ts +14 -0
  119. package/src/metrics/constants.ts +2 -0
  120. package/src/services/AddressBook.ts +17 -6
  121. package/src/services/AnswerCallOnWebexService.ts +206 -0
  122. package/src/services/EntryPoint.ts +59 -60
  123. package/src/services/Queue.ts +29 -12
  124. package/src/services/WebexCrossClientService.ts +212 -0
  125. package/src/services/WxAppTelephonyMercurySync.ts +115 -0
  126. package/src/services/ai-docs/AGENTS.md +10 -10
  127. package/src/services/ai-docs/services-spec.md +6 -1
  128. package/src/services/config/Util.ts +3 -0
  129. package/src/services/config/ai-docs/AGENTS.md +1 -1
  130. package/src/services/config/ai-docs/ARCHITECTURE.md +2 -2
  131. package/src/services/config/ai-docs/config-spec.md +6 -0
  132. package/src/services/config/constants.ts +10 -6
  133. package/src/services/config/types.ts +16 -4
  134. package/src/services/core/Err.ts +1 -0
  135. package/src/services/core/WebexRequest.ts +3 -1
  136. package/src/services/core/ai-docs/core-spec.md +5 -1
  137. package/src/services/core/aqm-reqs.ts +30 -15
  138. package/src/services/core/types.ts +2 -0
  139. package/src/services/task/Task.ts +79 -8
  140. package/src/services/task/TaskFactory.ts +8 -3
  141. package/src/services/task/TaskManager.ts +254 -15
  142. package/src/services/task/WebexCallingUtils.ts +136 -0
  143. package/src/services/task/ai-docs/ARCHITECTURE.md +1 -0
  144. package/src/services/task/ai-docs/task-spec.md +117 -2
  145. package/src/services/task/constants.ts +3 -0
  146. package/src/services/task/contact.ts +30 -0
  147. package/src/services/task/digital/Digital.ts +4 -2
  148. package/src/services/task/state-machine/TaskStateMachine.ts +210 -63
  149. package/src/services/task/state-machine/actions.ts +5 -2
  150. package/src/services/task/state-machine/ai-docs/AGENTS.md +8 -4
  151. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +17 -8
  152. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +46 -22
  153. package/src/services/task/state-machine/guards.ts +64 -11
  154. package/src/services/task/state-machine/types.ts +16 -1
  155. package/src/services/task/state-machine/uiControlsComputer.ts +206 -32
  156. package/src/services/task/types.ts +114 -4
  157. package/src/services/task/voice/Voice.ts +462 -12
  158. package/src/services/task/voice/WebRTC.ts +5 -1
  159. package/src/services/task/voice/wxAppVoiceMethods.ts +307 -0
  160. package/src/services/wxAppTelephonyUtils.ts +14 -0
  161. package/src/types.ts +33 -9
  162. package/src/utils/AGENTS.md +21 -10
  163. package/src/utils/PageCache.ts +38 -5
  164. package/src/utils/ai-docs/utils-spec.md +21 -11
  165. package/test/unit/spec/cc.ts +1274 -0
  166. package/test/unit/spec/metrics/behavioral-events.ts +18 -0
  167. package/test/unit/spec/services/AddressBook.ts +37 -6
  168. package/test/unit/spec/services/AnswerCallOnWebexService.ts +223 -0
  169. package/test/unit/spec/services/EntryPoint.ts +87 -40
  170. package/test/unit/spec/services/Queue.ts +123 -12
  171. package/test/unit/spec/services/WebexCrossClientService.ts +261 -0
  172. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +113 -0
  173. package/test/unit/spec/services/config/Util.ts +85 -0
  174. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  175. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  176. package/test/unit/spec/services/task/Task.ts +200 -0
  177. package/test/unit/spec/services/task/TaskFactory.ts +39 -2
  178. package/test/unit/spec/services/task/TaskManager.ts +653 -1
  179. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  180. package/test/unit/spec/services/task/contact.ts +33 -0
  181. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +854 -72
  182. package/test/unit/spec/services/task/state-machine/guards.ts +210 -8
  183. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +445 -7
  184. package/test/unit/spec/services/task/voice/Voice.ts +874 -0
  185. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +459 -0
  186. package/umd/contact-center.min.js +2 -2
  187. package/umd/contact-center.min.js.map +1 -1
@@ -1,8 +1,13 @@
1
- import {TASK_CHANNEL_TYPE, VOICE_VARIANT} from '../../../../../../src/services/task/types';
1
+ import {
2
+ CONSULT_TRANSFER_DESTINATION_TYPE,
3
+ TASK_CHANNEL_TYPE,
4
+ VOICE_VARIANT,
5
+ } from '../../../../../../src/services/task/types';
2
6
  import {TaskState} from '../../../../../../src/services/task/state-machine/constants';
3
7
  import {
4
8
  computeUIControls,
5
9
  getDefaultUIControls,
10
+ haveUIControlsChanged,
6
11
  } from '../../../../../../src/services/task/state-machine/uiControlsComputer';
7
12
  import {getTaskStateForUiControls} from '../../../../../../src/services/task/state-machine/actions';
8
13
  import {TaskContext} from '../../../../../../src/services/task/state-machine/types';
@@ -554,6 +559,27 @@ function createVoiceContext(overrides: Partial<TaskContext> = {}): TaskContext {
554
559
  };
555
560
  }
556
561
 
562
+ function createConsultTransferContext(
563
+ taskData = createTaskData(),
564
+ overrides: Partial<NonNullable<TaskContext['uiControlConfig']['consultTransferConfig']>> = {}
565
+ ): TaskContext {
566
+ const context = createVoiceContext({taskData});
567
+
568
+ return {
569
+ ...context,
570
+ uiControlConfig: {
571
+ ...context.uiControlConfig,
572
+ consultTransferConfig: {
573
+ allowConsultToQueue: true,
574
+ accessQueue: 'ALL',
575
+ accessEntryPoint: 'ALL',
576
+ accessBuddyTeam: 'ALL',
577
+ ...overrides,
578
+ },
579
+ },
580
+ };
581
+ }
582
+
557
583
  function createTaskData1LikeConferenceConsultTaskData() {
558
584
  return createTaskData({
559
585
  agentId: '058b3e7c-8fcf-45ee-b0c4-4ef546d360b9',
@@ -613,7 +639,10 @@ function createTaskData1LikeConferenceConsultTaskData() {
613
639
  mediaResourceId: '66cc5edd-8ac9-4f27-8f48-286edea460b2',
614
640
  mType: 'consult',
615
641
  isHold: false,
616
- participants: ['747a2138-0a24-48fc-8d69-3a336d9b7158', '058b3e7c-8fcf-45ee-b0c4-4ef546d360b9'],
642
+ participants: [
643
+ '747a2138-0a24-48fc-8d69-3a336d9b7158',
644
+ '058b3e7c-8fcf-45ee-b0c4-4ef546d360b9',
645
+ ],
617
646
  },
618
647
  } as any,
619
648
  owner: 'e271075a-077d-42d0-9ae4-2a43cb847664',
@@ -1258,7 +1287,11 @@ describe('uiControlsComputer consult initiator controls', () => {
1258
1287
  agentId: 'agent-1',
1259
1288
  },
1260
1289
  });
1261
- const createdControls = computeUIControls(TaskState.CONSULTING, createdContext, createdTaskData);
1290
+ const createdControls = computeUIControls(
1291
+ TaskState.CONSULTING,
1292
+ createdContext,
1293
+ createdTaskData
1294
+ );
1262
1295
 
1263
1296
  expect(createdControls.consult.transferConference).toEqual({isVisible: true, isEnabled: false});
1264
1297
 
@@ -1274,7 +1307,10 @@ describe('uiControlsComputer consult initiator controls', () => {
1274
1307
  consultingTaskData
1275
1308
  );
1276
1309
 
1277
- expect(consultingControls.consult.transferConference).toEqual({isVisible: true, isEnabled: true});
1310
+ expect(consultingControls.consult.transferConference).toEqual({
1311
+ isVisible: true,
1312
+ isEnabled: true,
1313
+ });
1278
1314
  });
1279
1315
 
1280
1316
  it('hides exitConference on main leg for consult initiator before destination joins', () => {
@@ -1919,7 +1955,6 @@ describe('uiControlsComputer consult initiator controls', () => {
1919
1955
  expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
1920
1956
  expect(uiControls.consult.endConsult).toEqual({isVisible: false, isEnabled: false});
1921
1957
  });
1922
-
1923
1958
  });
1924
1959
 
1925
1960
  describe('uiControlsComputer outdial accept/decline controls', () => {
@@ -2064,7 +2099,10 @@ describe('uiControlsComputer conference controls', () => {
2064
2099
  });
2065
2100
  }
2066
2101
 
2067
- function createConferenceContext(participantCount: number, overrides: Partial<TaskContext> = {}): TaskContext {
2102
+ function createConferenceContext(
2103
+ participantCount: number,
2104
+ overrides: Partial<TaskContext> = {}
2105
+ ): TaskContext {
2068
2106
  return {
2069
2107
  taskData: createConferenceTaskData(participantCount),
2070
2108
  consultInitiator: true,
@@ -2414,7 +2452,6 @@ describe('uiControlsComputer conference controls', () => {
2414
2452
  expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
2415
2453
  });
2416
2454
 
2417
-
2418
2455
  it('post_call after customer exit: only exitConference and end enabled during EP-DN pending merge (CAI-8329)', () => {
2419
2456
  const taskData = createTaskData({
2420
2457
  agentId: 'agent-1',
@@ -2577,6 +2614,115 @@ describe('uiControlsComputer conference controls', () => {
2577
2614
  });
2578
2615
  });
2579
2616
 
2617
+ describe('uiControlsComputer consult/transfer destinations', () => {
2618
+ const AGENT = CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
2619
+ const QUEUE = CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE;
2620
+ const DIAL_NUMBER = CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER;
2621
+ const ENTRY_POINT = CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT;
2622
+
2623
+ it('returns the default destination order and applies the action-specific inbound voice queue rule', () => {
2624
+ const taskData = createTaskData({
2625
+ interaction: {contactDirection: {type: 'INBOUND'}} as any,
2626
+ });
2627
+ const context = createConsultTransferContext(taskData, {allowConsultToQueue: false});
2628
+
2629
+ const controls = computeUIControls(TaskState.CONNECTED, context, taskData);
2630
+
2631
+ expect(controls.consultTransferDestinations.consult).toEqual([AGENT, DIAL_NUMBER, ENTRY_POINT]);
2632
+ expect(controls.consultTransferDestinations.transfer).toEqual([
2633
+ AGENT,
2634
+ QUEUE,
2635
+ DIAL_NUMBER,
2636
+ ENTRY_POINT,
2637
+ ]);
2638
+ });
2639
+
2640
+ it('reads the outbound queue-transfer flag from callProcessingDetails', () => {
2641
+ const disabledTaskData = createTaskData({
2642
+ interaction: {
2643
+ contactDirection: {type: 'OUTBOUND'},
2644
+ callProcessingDetails: {outdialTransferToQueueEnabled: false},
2645
+ } as any,
2646
+ });
2647
+ const disabledContext = createConsultTransferContext(disabledTaskData);
2648
+ const disabledControls = computeUIControls(
2649
+ TaskState.CONNECTED,
2650
+ disabledContext,
2651
+ disabledTaskData
2652
+ );
2653
+
2654
+ expect(disabledControls.consultTransferDestinations.transfer).toEqual([
2655
+ AGENT,
2656
+ DIAL_NUMBER,
2657
+ ENTRY_POINT,
2658
+ ]);
2659
+
2660
+ const enabledTaskData = createTaskData({
2661
+ interaction: {
2662
+ contactDirection: {type: 'OUTBOUND'},
2663
+ callProcessingDetails: {outdialTransferToQueueEnabled: true},
2664
+ } as any,
2665
+ });
2666
+ const enabledContext = createConsultTransferContext(enabledTaskData);
2667
+ const enabledControls = computeUIControls(TaskState.CONNECTED, enabledContext, enabledTaskData);
2668
+
2669
+ expect(enabledControls.consultTransferDestinations.transfer).toEqual([
2670
+ AGENT,
2671
+ QUEUE,
2672
+ DIAL_NUMBER,
2673
+ ENTRY_POINT,
2674
+ ]);
2675
+ expect(haveUIControlsChanged(disabledControls, enabledControls)).toBe(true);
2676
+ });
2677
+
2678
+ it('hides profile-disabled destinations and does not allow queue transfer for unknown voice direction', () => {
2679
+ const taskData = createTaskData({
2680
+ interaction: {contactDirection: {type: 'UNKNOWN'}} as any,
2681
+ });
2682
+ const context = createConsultTransferContext(taskData, {
2683
+ accessQueue: 'NONE',
2684
+ accessEntryPoint: 'NONE',
2685
+ accessBuddyTeam: 'NONE',
2686
+ });
2687
+
2688
+ const controls = computeUIControls(TaskState.CONNECTED, context, taskData);
2689
+
2690
+ expect(controls.consultTransferDestinations).toEqual({
2691
+ consult: [DIAL_NUMBER],
2692
+ transfer: [DIAL_NUMBER],
2693
+ });
2694
+ });
2695
+
2696
+ it('returns only agents and queues for digital tasks', () => {
2697
+ const taskData = createTaskData({
2698
+ interaction: {mediaType: 'chat'} as any,
2699
+ });
2700
+ const voiceContext = createConsultTransferContext(taskData, {allowConsultToQueue: false});
2701
+ const context: TaskContext = {
2702
+ ...voiceContext,
2703
+ uiControlConfig: {
2704
+ ...voiceContext.uiControlConfig,
2705
+ channelType: TASK_CHANNEL_TYPE.DIGITAL,
2706
+ },
2707
+ };
2708
+
2709
+ const controls = computeUIControls(TaskState.CONNECTED, context, taskData);
2710
+
2711
+ expect(controls.consultTransferDestinations).toEqual({
2712
+ consult: [AGENT, QUEUE],
2713
+ transfer: [AGENT, QUEUE],
2714
+ });
2715
+ });
2716
+
2717
+ it('returns no destinations when no agent-profile policy was configured', () => {
2718
+ const context = createVoiceContext();
2719
+
2720
+ const controls = computeUIControls(TaskState.CONNECTED, context, context.taskData);
2721
+
2722
+ expect(controls.consultTransferDestinations).toEqual({consult: [], transfer: []});
2723
+ });
2724
+ });
2725
+
2580
2726
  describe('uiControlsComputer post-call consult controls (customer left)', () => {
2581
2727
  function createPostCallConsultTaskData() {
2582
2728
  return createTaskData({
@@ -2661,3 +2807,295 @@ describe('uiControlsComputer post-call consult controls (customer left)', () =>
2661
2807
  expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: false});
2662
2808
  });
2663
2809
  });
2810
+
2811
+ // ---------------------------------------------------------------------------
2812
+ // WXCC-6026: wxApp thick-client answer UI controls
2813
+ // ---------------------------------------------------------------------------
2814
+
2815
+ const WX_APP_DEVICE = {
2816
+ deviceType: 'wxApp',
2817
+ deviceId: 'device-id-1',
2818
+ deviceCallId: 'call-id-1',
2819
+ };
2820
+
2821
+ function createWxAppContext(
2822
+ overrides: Partial<{
2823
+ enableWxBetterTogether: boolean;
2824
+ wxAppAnswerPending: boolean;
2825
+ wxAppAcceptInFlight: boolean;
2826
+ }> = {}
2827
+ ): TaskContext {
2828
+ return {
2829
+ taskData: createTaskData({
2830
+ agentId: 'agent-1',
2831
+ mediaResourceId: 'interaction-1',
2832
+ interaction: {
2833
+ interactionId: 'interaction-1',
2834
+ participants: {
2835
+ 'agent-1': {id: 'agent-1', pType: 'Agent', ...WX_APP_DEVICE},
2836
+ 'customer-1': {id: 'customer-1', pType: 'Customer'},
2837
+ } as any,
2838
+ media: {
2839
+ 'interaction-1': {mediaResourceId: 'interaction-1', isHold: false},
2840
+ } as any,
2841
+ } as any,
2842
+ }),
2843
+ consultInitiator: false,
2844
+ exitingConference: false,
2845
+ consultFromConference: false,
2846
+ transferConferenceRequested: false,
2847
+ consultDestinationType: null,
2848
+ consultDestinationAgentId: null,
2849
+ consultDestinationAgentJoined: false,
2850
+ consultCallHeld: false,
2851
+ hideBlindTransferForEpDnPendingMerge: false,
2852
+ recordingControlsAvailable: false,
2853
+ recordingInProgress: false,
2854
+ uiControlConfig: {
2855
+ isEndTaskEnabled: true,
2856
+ isEndConsultEnabled: false,
2857
+ channelType: TASK_CHANNEL_TYPE.VOICE,
2858
+ isRecordingEnabled: false,
2859
+ agentId: 'agent-1',
2860
+ enableWxBetterTogether: overrides.enableWxBetterTogether ?? true,
2861
+ wxAppAnswerPending: overrides.wxAppAnswerPending,
2862
+ wxAppAcceptInFlight: overrides.wxAppAcceptInFlight,
2863
+ },
2864
+ uiControls: getDefaultUIControls(),
2865
+ };
2866
+ }
2867
+
2868
+ describe('uiControlsComputer wxApp thick-client answer (WXCC-6026)', () => {
2869
+ describe('accept and decline when wxApp offer and enableWxBetterTogether=true', () => {
2870
+ it('accept is visible and enabled in OFFERED state with wxApp participant', () => {
2871
+ const ctx = createWxAppContext();
2872
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2873
+
2874
+ expect(controls.main.accept).toEqual({isVisible: true, isEnabled: true});
2875
+ });
2876
+
2877
+ it('decline is visible and enabled in OFFERED state with wxApp participant', () => {
2878
+ const ctx = createWxAppContext();
2879
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2880
+
2881
+ expect(controls.main.decline).toEqual({isVisible: true, isEnabled: true});
2882
+ });
2883
+
2884
+ it('accept is visible but disabled while wxAppAcceptInFlight on inbound offer', () => {
2885
+ const ctx = createWxAppContext({wxAppAcceptInFlight: true});
2886
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2887
+
2888
+ expect(controls.main.accept).toEqual({isVisible: true, isEnabled: false});
2889
+ });
2890
+
2891
+ it('accept is visible but disabled while wxAppAnswerPending on inbound offer', () => {
2892
+ const ctx = createWxAppContext({wxAppAnswerPending: true});
2893
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2894
+
2895
+ expect(controls.main.accept).toEqual({isVisible: true, isEnabled: false});
2896
+ });
2897
+
2898
+ it('decline is visible but disabled while wxAppAnswerPending on inbound offer', () => {
2899
+ const ctx = createWxAppContext({wxAppAnswerPending: true});
2900
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2901
+
2902
+ expect(controls.main.decline).toEqual({isVisible: true, isEnabled: false});
2903
+ });
2904
+
2905
+ it('decline is visible but disabled while wxAppAcceptInFlight on outdial offer', () => {
2906
+ const ctx = createWxAppContext({wxAppAcceptInFlight: true, wxAppAnswerPending: true});
2907
+ ctx.taskData.interaction.outboundType = 'OUTDIAL';
2908
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2909
+
2910
+ expect(controls.main.decline).toEqual({isVisible: true, isEnabled: false});
2911
+ });
2912
+
2913
+ it('decline is visible and enabled while wxAppAnswerPending on outdial offer (Calling phase)', () => {
2914
+ const ctx = createWxAppContext({wxAppAnswerPending: true});
2915
+ ctx.taskData.interaction.outboundType = 'OUTDIAL';
2916
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2917
+
2918
+ expect(controls.main.decline).toEqual({isVisible: true, isEnabled: true});
2919
+ });
2920
+
2921
+ it('keypad is disabled in OFFERED state (not yet engaged)', () => {
2922
+ const ctx = createWxAppContext();
2923
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2924
+
2925
+ expect(controls.main.keypad).toEqual({isVisible: false, isEnabled: false});
2926
+ });
2927
+ });
2928
+
2929
+ describe('mute and keypad when wxApp engaged (CONNECTED state)', () => {
2930
+ it('mute is visible and enabled in CONNECTED state', () => {
2931
+ const ctx = createWxAppContext();
2932
+ const controls = computeUIControls(TaskState.CONNECTED, ctx, ctx.taskData);
2933
+
2934
+ expect(controls.main.mute).toEqual({isVisible: true, isEnabled: true});
2935
+ });
2936
+
2937
+ it('keypad is visible and enabled in CONNECTED state', () => {
2938
+ const ctx = createWxAppContext();
2939
+ const controls = computeUIControls(TaskState.CONNECTED, ctx, ctx.taskData);
2940
+
2941
+ expect(controls.main.keypad).toEqual({isVisible: true, isEnabled: true});
2942
+ });
2943
+
2944
+ it('keypad is disabled in WRAPPING_UP state', () => {
2945
+ const ctx = createWxAppContext();
2946
+ const controls = computeUIControls(TaskState.WRAPPING_UP, ctx, ctx.taskData);
2947
+
2948
+ expect(controls.main.keypad).toEqual({isVisible: false, isEnabled: false});
2949
+ });
2950
+
2951
+ it('mute is disabled in WRAPPING_UP state', () => {
2952
+ const ctx = createWxAppContext();
2953
+ const controls = computeUIControls(TaskState.WRAPPING_UP, ctx, ctx.taskData);
2954
+
2955
+ expect(controls.main.mute).toEqual({isVisible: false, isEnabled: false});
2956
+ });
2957
+ });
2958
+
2959
+ describe('when enableWxBetterTogether=false', () => {
2960
+ it('accept falls back to standard WebRTC logic (disabled for non-WebRTC)', () => {
2961
+ const ctx = createWxAppContext({enableWxBetterTogether: false});
2962
+ const controls = computeUIControls(TaskState.OFFERED, ctx, ctx.taskData);
2963
+
2964
+ // Without WebRTC variant, accept should be visible but disabled per existing logic
2965
+ expect(controls.main.accept.isVisible).toBe(true);
2966
+ // isEnabled is false without webRTC variant
2967
+ expect(controls.main.accept.isEnabled).toBe(false);
2968
+ });
2969
+
2970
+ it('keypad is always disabled when enableWxBetterTogether=false', () => {
2971
+ const ctx = createWxAppContext({enableWxBetterTogether: false});
2972
+ const controls = computeUIControls(TaskState.CONNECTED, ctx, ctx.taskData);
2973
+
2974
+ expect(controls.main.keypad).toEqual({isVisible: false, isEnabled: false});
2975
+ });
2976
+
2977
+ it('mute is disabled for non-WebRTC when enableWxBetterTogether=false', () => {
2978
+ const ctx = createWxAppContext({enableWxBetterTogether: false});
2979
+ const controls = computeUIControls(TaskState.CONNECTED, ctx, ctx.taskData);
2980
+
2981
+ expect(controls.main.mute).toEqual({isVisible: false, isEnabled: false});
2982
+ });
2983
+ });
2984
+
2985
+ describe('default keypad state without wxApp', () => {
2986
+ it('keypad is disabled for standard non-wxApp tasks', () => {
2987
+ const ctx: TaskContext = {
2988
+ taskData: createTaskData(),
2989
+ consultInitiator: false,
2990
+ exitingConference: false,
2991
+ consultFromConference: false,
2992
+ transferConferenceRequested: false,
2993
+ consultDestinationType: null,
2994
+ consultDestinationAgentId: null,
2995
+ consultDestinationAgentJoined: false,
2996
+ consultCallHeld: false,
2997
+ hideBlindTransferForEpDnPendingMerge: false,
2998
+ recordingControlsAvailable: false,
2999
+ recordingInProgress: false,
3000
+ uiControlConfig: {
3001
+ isEndTaskEnabled: true,
3002
+ isEndConsultEnabled: false,
3003
+ channelType: TASK_CHANNEL_TYPE.VOICE,
3004
+ isRecordingEnabled: false,
3005
+ },
3006
+ uiControls: getDefaultUIControls(),
3007
+ };
3008
+ const controls = computeUIControls(TaskState.CONNECTED, ctx, ctx.taskData);
3009
+
3010
+ expect(controls.main.keypad).toEqual({isVisible: false, isEnabled: false});
3011
+ });
3012
+ });
3013
+
3014
+ describe('wxApp engaged through consult and hold phases', () => {
3015
+ it('enables main mute and keypad during consult-requested phase on main leg', () => {
3016
+ const ctx = createWxAppContext();
3017
+ ctx.taskData = createTaskData({
3018
+ agentId: 'agent-1',
3019
+ consultMediaResourceId: 'consult-media',
3020
+ interaction: {
3021
+ interactionId: 'interaction-1',
3022
+ mainInteractionId: 'interaction-1',
3023
+ participants: {
3024
+ 'agent-1': {
3025
+ id: 'agent-1',
3026
+ pType: 'Agent',
3027
+ consultState: 'consultInitiated',
3028
+ ...WX_APP_DEVICE,
3029
+ },
3030
+ 'customer-1': {id: 'customer-1', pType: 'Customer'},
3031
+ } as any,
3032
+ media: {
3033
+ 'interaction-1': {
3034
+ mediaResourceId: 'interaction-1',
3035
+ isHold: false,
3036
+ participants: ['agent-1', 'customer-1'],
3037
+ },
3038
+ 'consult-media': {
3039
+ mediaResourceId: 'consult-media',
3040
+ mType: 'consult',
3041
+ participants: ['agent-1'],
3042
+ },
3043
+ } as any,
3044
+ } as any,
3045
+ });
3046
+ ctx.consultInitiator = true;
3047
+ ctx.consultDestinationAgentJoined = false;
3048
+
3049
+ const controls = computeUIControls(TaskState.CONNECTED, ctx, ctx.taskData);
3050
+
3051
+ expect(controls.main.mute).toEqual({isVisible: true, isEnabled: true});
3052
+ expect(controls.main.keypad).toEqual({isVisible: true, isEnabled: true});
3053
+ });
3054
+
3055
+ it('enables main mute and keypad while consulting with main leg held', () => {
3056
+ const taskData = createConsultTaskData();
3057
+ taskData.interaction!.participants!['agent-1'] = {
3058
+ id: 'agent-1',
3059
+ pType: 'AGENT',
3060
+ hasLeft: false,
3061
+ consultState: 'consulting',
3062
+ ...WX_APP_DEVICE,
3063
+ };
3064
+ const ctx = createVoiceContext({
3065
+ taskData,
3066
+ uiControlConfig: {
3067
+ ...createVoiceContext().uiControlConfig,
3068
+ enableWxBetterTogether: true,
3069
+ },
3070
+ });
3071
+
3072
+ const controls = computeUIControls(TaskState.CONSULTING, ctx, taskData);
3073
+
3074
+ expect(controls.main.mute).toEqual({isVisible: true, isEnabled: true});
3075
+ expect(controls.main.keypad).toEqual({isVisible: true, isEnabled: true});
3076
+ expect(controls.consult.mute).toEqual({isVisible: false, isEnabled: false});
3077
+ });
3078
+
3079
+ it('keeps BROWSER consult mute enabled when enableWxBetterTogether is true and wxApp is not engaged', () => {
3080
+ const consultedTaskData = createConsultedAgentInconsistentTaskData();
3081
+ const consultedContext = createVoiceContext({
3082
+ consultInitiator: false,
3083
+ taskData: consultedTaskData,
3084
+ uiControlConfig: {
3085
+ ...createVoiceContext().uiControlConfig,
3086
+ agentId: 'agent-2',
3087
+ voiceVariant: VOICE_VARIANT.WEBRTC,
3088
+ enableWxBetterTogether: true,
3089
+ },
3090
+ });
3091
+
3092
+ const controls = computeUIControls(
3093
+ TaskState.CONNECTED,
3094
+ consultedContext,
3095
+ consultedTaskData
3096
+ );
3097
+
3098
+ expect(controls.main.mute).toEqual({isVisible: true, isEnabled: true});
3099
+ });
3100
+ });
3101
+ });