@webex/contact-center 3.12.0-next.95 → 3.12.0-next.97

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 (34) hide show
  1. package/dist/services/task/TaskUtils.js +51 -3
  2. package/dist/services/task/TaskUtils.js.map +1 -1
  3. package/dist/services/task/state-machine/TaskStateMachine.js +50 -14
  4. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  5. package/dist/services/task/state-machine/actions.js +57 -33
  6. package/dist/services/task/state-machine/actions.js.map +1 -1
  7. package/dist/services/task/state-machine/guards.js +42 -0
  8. package/dist/services/task/state-machine/guards.js.map +1 -1
  9. package/dist/services/task/state-machine/types.js.map +1 -1
  10. package/dist/services/task/state-machine/uiControlsComputer.js +78 -28
  11. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  12. package/dist/services/task/voice/Voice.js +5 -3
  13. package/dist/services/task/voice/Voice.js.map +1 -1
  14. package/dist/types/services/task/TaskUtils.d.ts +2 -0
  15. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +72 -22
  16. package/dist/types/services/task/state-machine/guards.d.ts +12 -0
  17. package/dist/types/services/task/state-machine/types.d.ts +2 -0
  18. package/dist/webex.js +1 -1
  19. package/package.json +2 -2
  20. package/src/services/task/TaskUtils.ts +100 -2
  21. package/src/services/task/state-machine/TaskStateMachine.ts +101 -12
  22. package/src/services/task/state-machine/actions.ts +88 -35
  23. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +73 -11
  24. package/src/services/task/state-machine/guards.ts +39 -0
  25. package/src/services/task/state-machine/types.ts +2 -0
  26. package/src/services/task/state-machine/uiControlsComputer.ts +135 -41
  27. package/src/services/task/voice/Voice.ts +6 -3
  28. package/test/unit/spec/services/task/TaskUtils.ts +29 -0
  29. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +826 -0
  30. package/test/unit/spec/services/task/state-machine/guards.ts +158 -0
  31. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +648 -5
  32. package/test/unit/spec/services/task/voice/Voice.ts +18 -0
  33. package/umd/contact-center.min.js +2 -2
  34. package/umd/contact-center.min.js.map +1 -1
@@ -539,6 +539,7 @@ function createVoiceContext(overrides: Partial<TaskContext> = {}): TaskContext {
539
539
  consultDestinationAgentId: null,
540
540
  consultDestinationAgentJoined: true,
541
541
  consultCallHeld: false,
542
+ hideBlindTransferForEpDnPendingMerge: false,
542
543
  recordingControlsAvailable: true,
543
544
  recordingInProgress: true,
544
545
  uiControlConfig: {
@@ -741,7 +742,13 @@ describe('uiControlsComputer consult initiator controls', () => {
741
742
  taskData: {
742
743
  ...consultedTaskData,
743
744
  isConsulted: true,
745
+ agentId: 'agent-2',
744
746
  } as any,
747
+ uiControlConfig: {
748
+ ...createVoiceContext().uiControlConfig,
749
+ agentId: 'agent-2',
750
+ isEndConsultEnabled: true,
751
+ },
745
752
  });
746
753
 
747
754
  const uiControls = computeUIControls(
@@ -751,6 +758,10 @@ describe('uiControlsComputer consult initiator controls', () => {
751
758
  );
752
759
 
753
760
  expect(uiControls.consult.transfer).toEqual({isVisible: false, isEnabled: false});
761
+ expect(uiControls.consult.conference).toEqual({isVisible: false, isEnabled: false});
762
+ expect(uiControls.consult.mergeToConference).toEqual({isVisible: false, isEnabled: false});
763
+ expect(uiControls.consult.switch).toEqual({isVisible: false, isEnabled: false});
764
+ expect(uiControls.consult.endConsult).toEqual({isVisible: true, isEnabled: true});
754
765
  });
755
766
 
756
767
  it('hides main-leg controls for consulted agent when payload isConsulted is stale false', () => {
@@ -853,10 +864,124 @@ describe('uiControlsComputer consult initiator controls', () => {
853
864
 
854
865
  const uiControls = computeUIControls(TaskState.CONFERENCING, context, taskData);
855
866
 
867
+ expect(uiControls.activeLeg).toBe('main');
856
868
  expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
857
869
  expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
858
870
  });
859
871
 
872
+ it('keeps main leg active for conference owner when another agent consults (CAI-8329)', () => {
873
+ const taskData = createTaskData({
874
+ agentId: 'agent-3',
875
+ type: 'AgentConsultCreated' as any,
876
+ consultMediaResourceId: 'consult-media-1',
877
+ consultingAgentId: 'agent-3',
878
+ isConsulted: false,
879
+ interaction: {
880
+ state: 'conference',
881
+ interactionId: 'interaction-1',
882
+ mainInteractionId: 'interaction-1',
883
+ owner: 'agent-1',
884
+ callProcessingDetails: {
885
+ conferenceHoldParticipant: 'true',
886
+ recordInProgress: 'true',
887
+ recordingStarted: 'true',
888
+ },
889
+ participants: {
890
+ 'agent-1': {
891
+ id: 'agent-1',
892
+ pType: 'Agent',
893
+ hasJoined: true,
894
+ hasLeft: false,
895
+ consultState: 'conferencing',
896
+ },
897
+ 'agent-2': {
898
+ id: 'agent-2',
899
+ pType: 'Agent',
900
+ hasJoined: false,
901
+ hasLeft: false,
902
+ isConsulted: true,
903
+ },
904
+ 'agent-3': {
905
+ id: 'agent-3',
906
+ pType: 'Agent',
907
+ hasJoined: true,
908
+ hasLeft: false,
909
+ consultState: 'consultInitiated',
910
+ },
911
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasJoined: true, hasLeft: false},
912
+ } as any,
913
+ media: {
914
+ 'interaction-1': {
915
+ mediaResourceId: 'interaction-1',
916
+ mType: 'mainCall',
917
+ isHold: true,
918
+ participants: ['customer-1', 'agent-1', 'agent-3'],
919
+ },
920
+ 'consult-media-1': {
921
+ mediaResourceId: 'consult-media-1',
922
+ mType: 'consult',
923
+ isHold: false,
924
+ participants: ['agent-2', 'agent-3'],
925
+ },
926
+ } as any,
927
+ } as any,
928
+ });
929
+ const baseContext = createVoiceContext();
930
+ const context = createVoiceContext({
931
+ consultInitiator: true,
932
+ consultFromConference: true,
933
+ consultDestinationAgentJoined: true,
934
+ recordingControlsAvailable: true,
935
+ recordingInProgress: true,
936
+ taskData,
937
+ uiControlConfig: {
938
+ ...baseContext.uiControlConfig,
939
+ agentId: 'agent-1',
940
+ },
941
+ });
942
+
943
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, taskData);
944
+
945
+ expect(uiControls.activeLeg).toBe('main');
946
+ expect(uiControls.main.hold).toEqual({isVisible: true, isEnabled: false});
947
+ expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: false});
948
+ expect(uiControls.main.recording).toEqual({isVisible: true, isEnabled: true});
949
+ expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
950
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
951
+ expect(uiControls.main.transfer).toEqual({isVisible: false, isEnabled: false});
952
+ expect(uiControls.main.transferConference).toEqual({isVisible: false, isEnabled: false});
953
+ });
954
+
955
+ it('detects other-agent consult in progress with mixed-case Agent pType (CAI-8329)', () => {
956
+ const taskData = createConferenceWithOtherAgentConsultPendingTaskData();
957
+ const mixedCaseTaskData = createTaskData({
958
+ ...taskData,
959
+ interaction: {
960
+ ...taskData.interaction,
961
+ participants: {
962
+ ...taskData.interaction?.participants,
963
+ 'agent-1': {
964
+ ...(taskData.interaction?.participants as any)['agent-1'],
965
+ pType: 'Agent',
966
+ },
967
+ },
968
+ } as any,
969
+ });
970
+ const baseContext = createVoiceContext();
971
+ const context = createVoiceContext({
972
+ consultInitiator: false,
973
+ taskData: mixedCaseTaskData,
974
+ uiControlConfig: {
975
+ ...baseContext.uiControlConfig,
976
+ agentId: 'agent-2',
977
+ },
978
+ });
979
+
980
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, mixedCaseTaskData);
981
+
982
+ expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: false});
983
+ });
984
+
860
985
  it('enables end and exitConference on unheld main leg for non-initiator during active conference consult', () => {
861
986
  const taskData = createUnheldConferenceWithActiveConsultTaskData();
862
987
  const baseContext = createVoiceContext();
@@ -1734,6 +1859,67 @@ describe('uiControlsComputer consult initiator controls', () => {
1734
1859
  expect(uiControls.consult.endConsult).toEqual({isVisible: false, isEnabled: false});
1735
1860
  });
1736
1861
 
1862
+ it('enables main.end after resume when stale consult media persists after RONA (AgentContactUnheld)', () => {
1863
+ const unheldTaskData = createTaskData({
1864
+ agentId: 'agent-1',
1865
+ mediaResourceId: 'interaction-1',
1866
+ consultMediaResourceId: 'consult-media',
1867
+ isConsulted: false,
1868
+ type: 'AgentContactUnheld' as any,
1869
+ interaction: {
1870
+ state: 'connected',
1871
+ interactionId: 'interaction-1',
1872
+ mainInteractionId: 'interaction-1',
1873
+ owner: 'agent-1',
1874
+ participants: {
1875
+ 'agent-1': {
1876
+ id: 'agent-1',
1877
+ pType: 'Agent',
1878
+ hasLeft: false,
1879
+ consultState: 'consultCompleted',
1880
+ isConsulted: false,
1881
+ },
1882
+ 'agent-2': {
1883
+ id: 'agent-2',
1884
+ pType: 'Agent',
1885
+ hasLeft: false,
1886
+ hasJoined: false,
1887
+ consultState: 'consultReserved',
1888
+ isConsulted: true,
1889
+ },
1890
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasLeft: false, hasJoined: true},
1891
+ } as any,
1892
+ media: {
1893
+ 'interaction-1': {
1894
+ mediaResourceId: 'interaction-1',
1895
+ mType: 'mainCall',
1896
+ isHold: false,
1897
+ participants: ['customer-1', 'agent-1'],
1898
+ },
1899
+ 'consult-media': {
1900
+ mediaResourceId: 'consult-media',
1901
+ mType: 'consult',
1902
+ isHold: false,
1903
+ participants: ['agent-1', 'agent-2'],
1904
+ },
1905
+ } as any,
1906
+ } as any,
1907
+ });
1908
+ const context = createVoiceContext({
1909
+ taskData: unheldTaskData as any,
1910
+ consultInitiator: false,
1911
+ consultDestinationAgentJoined: false,
1912
+ consultCallHeld: false,
1913
+ });
1914
+
1915
+ const uiControls = computeUIControls(TaskState.CONNECTED, context, unheldTaskData as any);
1916
+
1917
+ expect(uiControls.activeLeg).toBe('main');
1918
+ expect(uiControls.main.hold).toEqual({isVisible: true, isEnabled: true});
1919
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
1920
+ expect(uiControls.consult.endConsult).toEqual({isVisible: false, isEnabled: false});
1921
+ });
1922
+
1737
1923
  });
1738
1924
 
1739
1925
  describe('uiControlsComputer outdial accept/decline controls', () => {
@@ -1802,6 +1988,7 @@ describe('uiControlsComputer WebRTC call-control end button', () => {
1802
1988
  consultDestinationAgentId: null,
1803
1989
  consultDestinationAgentJoined: false,
1804
1990
  consultCallHeld: false,
1991
+ hideBlindTransferForEpDnPendingMerge: false,
1805
1992
  recordingControlsAvailable: true,
1806
1993
  recordingInProgress: true,
1807
1994
  uiControlConfig: {
@@ -1888,6 +2075,7 @@ describe('uiControlsComputer conference controls', () => {
1888
2075
  consultDestinationAgentId: null,
1889
2076
  consultDestinationAgentJoined: false,
1890
2077
  consultCallHeld: false,
2078
+ hideBlindTransferForEpDnPendingMerge: false,
1891
2079
  recordingControlsAvailable: true,
1892
2080
  recordingInProgress: true,
1893
2081
  uiControlConfig: {
@@ -1914,14 +2102,468 @@ describe('uiControlsComputer conference controls', () => {
1914
2102
  expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
1915
2103
  });
1916
2104
 
1917
- it('real conference (participantCount > 1): transfer/recording hidden, consult enabled, exitConference visible', () => {
1918
- const context = createConferenceContext(2);
2105
+ it('hides blind transfer on main after EP-DN consult merge before destination joins (CAI-8329)', () => {
2106
+ const taskData = createTaskData({
2107
+ agentId: 'agent-1',
2108
+ consultingAgentId: 'agent-1',
2109
+ consultMediaResourceId: 'consult-media-1',
2110
+ isConsulted: false,
2111
+ destinationType: 'EP-DN',
2112
+ type: 'AgentConsultCreated' as any,
2113
+ interaction: {
2114
+ state: 'conference',
2115
+ interactionId: 'interaction-1',
2116
+ mainInteractionId: 'interaction-1',
2117
+ owner: 'agent-1',
2118
+ participants: {
2119
+ 'agent-1': {
2120
+ id: 'agent-1',
2121
+ pType: 'Agent',
2122
+ hasJoined: true,
2123
+ hasLeft: false,
2124
+ consultState: 'consulting',
2125
+ },
2126
+ 'dn-dest': {
2127
+ id: 'dn-dest',
2128
+ pType: 'DN',
2129
+ hasLeft: false,
2130
+ hasJoined: false,
2131
+ },
2132
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasJoined: true, hasLeft: false},
2133
+ } as any,
2134
+ media: {
2135
+ 'interaction-1': {
2136
+ mediaResourceId: 'interaction-1',
2137
+ mType: 'mainCall',
2138
+ participants: ['agent-1', 'customer-1'],
2139
+ isHold: false,
2140
+ },
2141
+ 'consult-media-1': {
2142
+ mediaResourceId: 'consult-media-1',
2143
+ mType: 'consult',
2144
+ participants: ['agent-1', 'dn-dest'],
2145
+ isHold: false,
2146
+ },
2147
+ } as any,
2148
+ } as any,
2149
+ });
2150
+ const context = createConferenceContext(1, {
2151
+ consultInitiator: false,
2152
+ consultFromConference: false,
2153
+ consultDestinationType: null,
2154
+ hideBlindTransferForEpDnPendingMerge: true,
2155
+ consultCallHeld: false,
2156
+ taskData,
2157
+ uiControlConfig: {
2158
+ isEndTaskEnabled: true,
2159
+ isEndConsultEnabled: true,
2160
+ channelType: TASK_CHANNEL_TYPE.VOICE,
2161
+ isRecordingEnabled: true,
2162
+ voiceVariant: VOICE_VARIANT.WEBRTC,
2163
+ agentId: 'agent-1',
2164
+ },
2165
+ });
1919
2166
 
1920
- const uiControls = computeUIControls(TaskState.CONFERENCING, context, context.taskData);
2167
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, taskData);
1921
2168
 
1922
2169
  expect(uiControls.main.transfer).toEqual({isVisible: false, isEnabled: false});
1923
- expect(uiControls.main.recording).toEqual({isVisible: false, isEnabled: false});
1924
- expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
2170
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
2171
+ expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
2172
+ });
2173
+
2174
+ it('hides blind transfer on main after EP-DN consult merge when destination is hydrated as joined (CAI-8329)', () => {
2175
+ const taskData = createTaskData({
2176
+ agentId: 'agent-1',
2177
+ consultingAgentId: 'agent-1',
2178
+ consultMediaResourceId: 'consult-media-1',
2179
+ isConsulted: false,
2180
+ destinationType: 'EP-DN',
2181
+ type: 'AgentConsultCreated' as any,
2182
+ interaction: {
2183
+ state: 'conference',
2184
+ interactionId: 'interaction-1',
2185
+ mainInteractionId: 'interaction-1',
2186
+ owner: 'agent-1',
2187
+ callProcessingDetails: {
2188
+ consultDestinationAgentJoined: 'true',
2189
+ },
2190
+ participants: {
2191
+ 'agent-1': {
2192
+ id: 'agent-1',
2193
+ pType: 'Agent',
2194
+ hasJoined: true,
2195
+ hasLeft: false,
2196
+ consultState: 'consulting',
2197
+ },
2198
+ 'dn-dest': {
2199
+ id: 'dn-dest',
2200
+ pType: 'DN',
2201
+ hasLeft: false,
2202
+ hasJoined: false,
2203
+ },
2204
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasJoined: true, hasLeft: false},
2205
+ } as any,
2206
+ media: {
2207
+ 'interaction-1': {
2208
+ mediaResourceId: 'interaction-1',
2209
+ mType: 'mainCall',
2210
+ participants: ['agent-1', 'customer-1'],
2211
+ isHold: false,
2212
+ },
2213
+ 'consult-media-1': {
2214
+ mediaResourceId: 'consult-media-1',
2215
+ mType: 'consult',
2216
+ participants: ['agent-1', 'dn-dest'],
2217
+ isHold: false,
2218
+ },
2219
+ } as any,
2220
+ } as any,
2221
+ });
2222
+ const context = createConferenceContext(1, {
2223
+ consultInitiator: false,
2224
+ consultFromConference: false,
2225
+ consultDestinationType: null,
2226
+ hideBlindTransferForEpDnPendingMerge: true,
2227
+ consultCallHeld: false,
2228
+ taskData,
2229
+ uiControlConfig: {
2230
+ isEndTaskEnabled: true,
2231
+ isEndConsultEnabled: true,
2232
+ channelType: TASK_CHANNEL_TYPE.VOICE,
2233
+ isRecordingEnabled: true,
2234
+ voiceVariant: VOICE_VARIANT.WEBRTC,
2235
+ agentId: 'agent-1',
2236
+ },
2237
+ });
2238
+
2239
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, taskData);
2240
+
2241
+ expect(uiControls.main.transfer).toEqual({isVisible: false, isEnabled: false});
2242
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
2243
+ expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
2244
+ });
2245
+
2246
+ it('hides blind transfer on AgentConsultConferencing after clearConsultState sets flag (CAI-8329)', () => {
2247
+ const agentId = '4b496e73-55e4-4d74-a53c-469babdd8326';
2248
+ const mainInteractionId = '08f427ed-71d8-41df-8a44-43e1bb2b2074';
2249
+ const consultMediaId = 'e6592ab7-8f56-4132-87a9-48c4c262df3c';
2250
+ const taskData = createTaskData({
2251
+ agentId,
2252
+ consultMediaResourceId: consultMediaId,
2253
+ isConsulted: false,
2254
+ type: 'AgentConsultConferencing' as any,
2255
+ interaction: {
2256
+ state: 'conference',
2257
+ interactionId: mainInteractionId,
2258
+ mainInteractionId,
2259
+ owner: agentId,
2260
+ callProcessingDetails: {
2261
+ relationshipType: 'consult',
2262
+ childInteractionId: consultMediaId,
2263
+ },
2264
+ participants: {
2265
+ [agentId]: {
2266
+ id: agentId,
2267
+ pType: 'Agent',
2268
+ hasJoined: true,
2269
+ hasLeft: false,
2270
+ consultState: 'conferencing',
2271
+ isConsulted: false,
2272
+ },
2273
+ '+13159998087': {
2274
+ id: '+13159998087',
2275
+ pType: 'EP-DN',
2276
+ type: 'EpDn',
2277
+ hasLeft: false,
2278
+ },
2279
+ '+14696762938': {
2280
+ id: '+14696762938',
2281
+ pType: 'Customer',
2282
+ hasJoined: true,
2283
+ hasLeft: false,
2284
+ },
2285
+ } as any,
2286
+ media: {
2287
+ [mainInteractionId]: {
2288
+ mediaResourceId: mainInteractionId,
2289
+ mType: 'mainCall',
2290
+ participants: ['+14696762938', agentId],
2291
+ isHold: false,
2292
+ },
2293
+ [consultMediaId]: {
2294
+ mediaResourceId: consultMediaId,
2295
+ mType: 'consult',
2296
+ participants: ['+13159998087', agentId],
2297
+ isHold: false,
2298
+ },
2299
+ } as any,
2300
+ } as any,
2301
+ });
2302
+ const context = createConferenceContext(1, {
2303
+ consultInitiator: false,
2304
+ consultFromConference: false,
2305
+ consultDestinationType: null,
2306
+ hideBlindTransferForEpDnPendingMerge: true,
2307
+ consultCallHeld: false,
2308
+ taskData,
2309
+ uiControlConfig: {
2310
+ isEndTaskEnabled: true,
2311
+ isEndConsultEnabled: true,
2312
+ channelType: TASK_CHANNEL_TYPE.VOICE,
2313
+ isRecordingEnabled: true,
2314
+ voiceVariant: VOICE_VARIANT.WEBRTC,
2315
+ agentId,
2316
+ },
2317
+ });
2318
+
2319
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, taskData);
2320
+
2321
+ expect(uiControls.activeLeg).toBe('main');
2322
+ expect(uiControls.main.transfer).toEqual({isVisible: false, isEnabled: false});
2323
+ expect(uiControls.main.mute).toEqual({isVisible: true, isEnabled: true});
2324
+ expect(uiControls.main.recording).toEqual({isVisible: true, isEnabled: true});
2325
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
2326
+ expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
2327
+ });
2328
+
2329
+ it('shows blind transfer on pending conference when consult context is absent (CAI-8329)', () => {
2330
+ const context = createConferenceContext(1, {
2331
+ consultInitiator: false,
2332
+ consultFromConference: false,
2333
+ consultDestinationType: null,
2334
+ });
2335
+
2336
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, context.taskData);
2337
+
2338
+ expect(uiControls.main.transfer).toEqual({isVisible: true, isEnabled: true});
2339
+ });
2340
+
2341
+ it('real conference (participantCount > 1): transfer hidden, recording visible when controls available, consult enabled, exitConference visible', () => {
2342
+ const context = createConferenceContext(2);
2343
+
2344
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, context.taskData);
2345
+
2346
+ expect(uiControls.main.transfer).toEqual({isVisible: false, isEnabled: false});
2347
+ expect(uiControls.main.recording).toEqual({isVisible: true, isEnabled: true});
2348
+ expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: true});
2349
+ expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
2350
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
2351
+ });
2352
+
2353
+ it('real conference (participantCount > 1): recording hidden when recording controls unavailable', () => {
2354
+ const context = createConferenceContext(2, {
2355
+ recordingControlsAvailable: false,
2356
+ recordingInProgress: false,
2357
+ });
2358
+
2359
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, context.taskData);
2360
+
2361
+ expect(uiControls.main.recording).toEqual({isVisible: false, isEnabled: false});
2362
+ });
2363
+
2364
+ it('shows recording when second agent joins EP-DN merged conference (CAI-8329)', () => {
2365
+ const taskData = createTaskData({
2366
+ agentId: 'agent-1',
2367
+ type: 'ContactUpdated' as any,
2368
+ interaction: {
2369
+ state: 'conference',
2370
+ interactionId: 'interaction-1',
2371
+ mainInteractionId: 'interaction-1',
2372
+ owner: 'agent-1',
2373
+ callProcessingDetails: {
2374
+ recordInProgress: 'true',
2375
+ recordingStarted: 'true',
2376
+ epDnParticipantRemoved: '+13159998059',
2377
+ },
2378
+ participants: {
2379
+ 'agent-1': {
2380
+ id: 'agent-1',
2381
+ pType: 'Agent',
2382
+ hasJoined: true,
2383
+ hasLeft: false,
2384
+ consultState: 'conferencing',
2385
+ },
2386
+ 'agent-2': {
2387
+ id: 'agent-2',
2388
+ pType: 'Agent',
2389
+ hasJoined: true,
2390
+ hasLeft: false,
2391
+ consultState: 'conferencing',
2392
+ },
2393
+ 'customer-1': {id: 'customer-1', pType: 'Customer', hasJoined: true, hasLeft: false},
2394
+ } as any,
2395
+ media: {
2396
+ 'interaction-1': {
2397
+ mediaResourceId: 'interaction-1',
2398
+ mType: 'mainCall',
2399
+ participants: ['customer-1', 'agent-1', 'agent-2'],
2400
+ isHold: false,
2401
+ },
2402
+ } as any,
2403
+ } as any,
2404
+ });
2405
+ const context = createConferenceContext(2, {
2406
+ hideBlindTransferForEpDnPendingMerge: false,
2407
+ taskData,
2408
+ });
2409
+
2410
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, taskData);
2411
+
2412
+ expect(uiControls.main.recording).toEqual({isVisible: true, isEnabled: true});
2413
+ expect(uiControls.main.transfer).toEqual({isVisible: false, isEnabled: false});
2414
+ expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
2415
+ });
2416
+
2417
+
2418
+ it('post_call after customer exit: only exitConference and end enabled during EP-DN pending merge (CAI-8329)', () => {
2419
+ const taskData = createTaskData({
2420
+ agentId: 'agent-1',
2421
+ consultingAgentId: 'agent-1',
2422
+ consultMediaResourceId: 'consult-media-1',
2423
+ isConsulted: false,
2424
+ destinationType: 'EP-DN',
2425
+ type: 'ParticipantPostCallActivity' as any,
2426
+ interaction: {
2427
+ state: 'post_call',
2428
+ interactionId: 'interaction-1',
2429
+ mainInteractionId: 'interaction-1',
2430
+ owner: 'agent-1',
2431
+ callProcessingDetails: {
2432
+ hasCustomerLeft: 'true',
2433
+ },
2434
+ participants: {
2435
+ 'agent-1': {
2436
+ id: 'agent-1',
2437
+ pType: 'Agent',
2438
+ hasJoined: true,
2439
+ hasLeft: false,
2440
+ consultState: 'conferencing',
2441
+ },
2442
+ '+13159998059': {
2443
+ id: '+13159998059',
2444
+ pType: 'EP-DN',
2445
+ hasLeft: false,
2446
+ hasJoined: false,
2447
+ },
2448
+ '+14696762938': {
2449
+ id: '+14696762938',
2450
+ pType: 'Customer',
2451
+ hasJoined: true,
2452
+ hasLeft: true,
2453
+ },
2454
+ } as any,
2455
+ media: {
2456
+ 'interaction-1': {
2457
+ mediaResourceId: 'interaction-1',
2458
+ mType: 'mainCall',
2459
+ participants: ['agent-1'],
2460
+ isHold: false,
2461
+ },
2462
+ 'consult-media-1': {
2463
+ mediaResourceId: 'consult-media-1',
2464
+ mType: 'consult',
2465
+ participants: ['+13159998059', 'agent-1'],
2466
+ isHold: false,
2467
+ },
2468
+ } as any,
2469
+ } as any,
2470
+ });
2471
+ const context = createConferenceContext(1, {
2472
+ consultInitiator: true,
2473
+ consultFromConference: true,
2474
+ hideBlindTransferForEpDnPendingMerge: true,
2475
+ taskData,
2476
+ uiControlConfig: {
2477
+ isEndTaskEnabled: true,
2478
+ isEndConsultEnabled: true,
2479
+ channelType: TASK_CHANNEL_TYPE.VOICE,
2480
+ isRecordingEnabled: true,
2481
+ voiceVariant: VOICE_VARIANT.WEBRTC,
2482
+ agentId: 'agent-1',
2483
+ },
2484
+ });
2485
+
2486
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, taskData);
2487
+
2488
+ expect(uiControls.main.transfer).toEqual({isVisible: false, isEnabled: false});
2489
+ expect(uiControls.main.recording).toEqual({isVisible: true, isEnabled: false});
2490
+ expect(uiControls.main.hold).toEqual({isVisible: true, isEnabled: false});
2491
+ expect(uiControls.main.mute).toEqual({isVisible: true, isEnabled: false});
2492
+ expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: false});
2493
+ expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
2494
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
2495
+ });
2496
+
2497
+ it('hides blind transfer after AgentConsultFailed ANOTHER_CONSULT_IN_PROGRESS during EP-DN pending merge (CAI-8329)', () => {
2498
+ const taskData = createTaskData({
2499
+ agentId: 'agent-1',
2500
+ consultingAgentId: 'agent-1',
2501
+ consultMediaResourceId: 'consult-media-1',
2502
+ isConsulted: false,
2503
+ destinationType: 'Agent',
2504
+ reason: 'ANOTHER_CONSULT_IN_PROGRESS',
2505
+ type: 'AgentConsultFailed' as any,
2506
+ interaction: {
2507
+ state: 'conference',
2508
+ interactionId: 'interaction-1',
2509
+ mainInteractionId: 'interaction-1',
2510
+ owner: 'agent-1',
2511
+ participants: {
2512
+ 'agent-1': {
2513
+ id: 'agent-1',
2514
+ pType: 'Agent',
2515
+ hasJoined: true,
2516
+ hasLeft: false,
2517
+ consultState: 'conferencing',
2518
+ },
2519
+ '+13159998059': {
2520
+ id: '+13159998059',
2521
+ pType: 'EP-DN',
2522
+ hasLeft: false,
2523
+ hasJoined: false,
2524
+ },
2525
+ '+14696762938': {
2526
+ id: '+14696762938',
2527
+ pType: 'Customer',
2528
+ hasJoined: true,
2529
+ hasLeft: false,
2530
+ },
2531
+ } as any,
2532
+ media: {
2533
+ 'interaction-1': {
2534
+ mediaResourceId: 'interaction-1',
2535
+ mType: 'mainCall',
2536
+ participants: ['agent-1', '+14696762938'],
2537
+ isHold: false,
2538
+ },
2539
+ 'consult-media-1': {
2540
+ mediaResourceId: 'consult-media-1',
2541
+ mType: 'consult',
2542
+ participants: ['+13159998059', 'agent-1'],
2543
+ isHold: false,
2544
+ },
2545
+ } as any,
2546
+ } as any,
2547
+ });
2548
+ const context = createConferenceContext(1, {
2549
+ consultInitiator: true,
2550
+ consultFromConference: true,
2551
+ hideBlindTransferForEpDnPendingMerge: true,
2552
+ taskData,
2553
+ uiControlConfig: {
2554
+ isEndTaskEnabled: true,
2555
+ isEndConsultEnabled: true,
2556
+ channelType: TASK_CHANNEL_TYPE.VOICE,
2557
+ isRecordingEnabled: true,
2558
+ voiceVariant: VOICE_VARIANT.WEBRTC,
2559
+ agentId: 'agent-1',
2560
+ },
2561
+ });
2562
+
2563
+ const uiControls = computeUIControls(TaskState.CONFERENCING, context, taskData);
2564
+
2565
+ expect(uiControls.main.transfer).toEqual({isVisible: false, isEnabled: false});
2566
+ expect(uiControls.main.consult).toEqual({isVisible: true, isEnabled: false});
1925
2567
  expect(uiControls.main.exitConference).toEqual({isVisible: true, isEnabled: true});
1926
2568
  expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: true});
1927
2569
  });
@@ -1995,6 +2637,7 @@ describe('uiControlsComputer post-call consult controls (customer left)', () =>
1995
2637
  consultDestinationAgentId: null,
1996
2638
  consultDestinationAgentJoined: true,
1997
2639
  consultCallHeld: false,
2640
+ hideBlindTransferForEpDnPendingMerge: false,
1998
2641
  recordingControlsAvailable: true,
1999
2642
  recordingInProgress: true,
2000
2643
  uiControlConfig: {