@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
package/dist/webex.js CHANGED
@@ -41,7 +41,7 @@ if (!global.Buffer) {
41
41
  */
42
42
  const Webex = _webexCore.default.extend({
43
43
  webex: true,
44
- version: `3.12.0-next.95`
44
+ version: `3.12.0-next.97`
45
45
  });
46
46
 
47
47
  /**
package/package.json CHANGED
@@ -45,7 +45,7 @@
45
45
  },
46
46
  "dependencies": {
47
47
  "@types/platform": "1.3.4",
48
- "@webex/calling": "3.12.0-next.81",
48
+ "@webex/calling": "3.12.0-next.82",
49
49
  "@webex/internal-plugin-mercury": "3.12.0-next.40",
50
50
  "@webex/internal-plugin-metrics": "3.12.0-next.37",
51
51
  "@webex/internal-plugin-support": "3.12.0-next.41",
@@ -83,5 +83,5 @@
83
83
  "typedoc": "^0.25.0",
84
84
  "typescript": "5.4.5"
85
85
  },
86
- "version": "3.12.0-next.95"
86
+ "version": "3.12.0-next.97"
87
87
  }
@@ -1,7 +1,13 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
2
  import {Interaction, ITask, TaskData, MEDIA_CHANNEL} from './types';
3
3
  import {LoginOption} from '../../types';
4
- import {PARTICIPANT_TYPE, MEDIA_TYPE_MAIN_CALL} from './state-machine/constants';
4
+ import {
5
+ PARTICIPANT_TYPE,
6
+ MEDIA_TYPE_MAIN_CALL,
7
+ CONSULT_STATE,
8
+ INTERACTION_STATE,
9
+ MEDIA_TYPE_CONSULT,
10
+ } from './state-machine/constants';
5
11
  import {TaskContext} from './state-machine/types';
6
12
  import {CC_EVENTS} from '../config/types';
7
13
  import {OUTBOUND_TYPE, OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE} from '../../constants';
@@ -92,6 +98,25 @@ export const getIsConsultInProgressForConferenceControls = (
92
98
  const p: any = interaction.participants?.[participantId];
93
99
  if (!p || p.hasLeft) return false;
94
100
  if (selfAgentId && participantId === selfAgentId) return false;
101
+ if (mainSet.has(participantId)) return false;
102
+
103
+ const participantType = String(p.pType ?? '').toUpperCase();
104
+ const isEpDnParticipant =
105
+ participantType === 'EP-DN' || participantType === 'EP_DN' || participantType === 'DN';
106
+ if (isEpDnParticipant) {
107
+ if (!selfAgentId) {
108
+ return true;
109
+ }
110
+ const selfConsultState = interaction.participants?.[selfAgentId]?.consultState as
111
+ | string
112
+ | undefined;
113
+
114
+ return (
115
+ selfConsultState === 'consulting' ||
116
+ selfConsultState === 'conferencing' ||
117
+ selfConsultState === 'consultInitiated'
118
+ );
119
+ }
95
120
 
96
121
  const consultState = p.consultState as string | undefined;
97
122
  const isRonaPendingConsultee = consultState === 'consultReserved' && p.hasJoined === false;
@@ -100,7 +125,7 @@ export const getIsConsultInProgressForConferenceControls = (
100
125
  p.currentState === 'consulting' ||
101
126
  (p.isConsulted === true && consultState !== 'consultCompleted' && !isRonaPendingConsultee);
102
127
 
103
- return consultLegActive && !mainSet.has(participantId);
128
+ return consultLegActive;
104
129
  });
105
130
  });
106
131
  };
@@ -216,6 +241,79 @@ export const getIsConferenceInProgress = (data: TaskData): boolean => {
216
241
  return agentParticipants.size >= 2;
217
242
  };
218
243
 
244
+ const EP_DN_MERGE_TERMINAL_CONSULT_EVENTS = new Set<string>([
245
+ CC_EVENTS.AGENT_CONSULT_ENDED,
246
+ CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED,
247
+ ]);
248
+
249
+ const isEpDnParticipantType = (pType: string | undefined): boolean => {
250
+ const normalized = String(pType ?? '').toUpperCase();
251
+
252
+ return normalized === 'EP-DN' || normalized === 'EP_DN' || normalized === 'DN';
253
+ };
254
+
255
+ /** EP-DN consult merged to conference before a second agent joins the main leg (CAI-8329). */
256
+ export const isEpDnConsultPendingConferenceMerge = (
257
+ taskData: TaskData | undefined,
258
+ selfAgentId: string | undefined
259
+ ): boolean => {
260
+ if (!taskData?.interaction || !selfAgentId) return false;
261
+
262
+ if (taskData.type && EP_DN_MERGE_TERMINAL_CONSULT_EVENTS.has(taskData.type)) {
263
+ return false;
264
+ }
265
+
266
+ const interaction = taskData.interaction;
267
+ const mainCallId = interaction.mainInteractionId || taskData.interactionId;
268
+ if (!mainCallId) return false;
269
+
270
+ if (getConferenceParticipantsCount(interaction, mainCallId) > 1) return false;
271
+
272
+ const selfParticipant = interaction.participants?.[selfAgentId] as
273
+ | {consultState?: string}
274
+ | undefined;
275
+ if (taskData.isConsulted === true || selfParticipant?.consultState === 'consultCompleted') {
276
+ return false;
277
+ }
278
+
279
+ const mainParticipantIds = new Set(interaction.media?.[mainCallId]?.participants ?? []);
280
+ const hasEpDnOnConsultLegNotMain = Object.values(interaction.media ?? {}).some((media: any) => {
281
+ if (media?.mType !== MEDIA_TYPE_CONSULT) return false;
282
+
283
+ return (media.participants ?? []).some((participantId: string) => {
284
+ if (participantId === selfAgentId || mainParticipantIds.has(participantId)) return false;
285
+ const participant = interaction.participants?.[participantId] as
286
+ | {hasLeft?: boolean; pType?: string}
287
+ | undefined;
288
+ if (!participant || participant.hasLeft) return false;
289
+
290
+ return isEpDnParticipantType(participant.pType);
291
+ });
292
+ });
293
+ if (!hasEpDnOnConsultLegNotMain) return false;
294
+
295
+ const callProcessingDetails = interaction.callProcessingDetails as
296
+ | {hasCustomerLeft?: string}
297
+ | undefined;
298
+ const customerLeft =
299
+ callProcessingDetails?.hasCustomerLeft === 'true' ||
300
+ !getIsCustomerInCall(interaction, mainCallId);
301
+
302
+ const conferenceFromPayload =
303
+ interaction.state === INTERACTION_STATE.CONFERENCE ||
304
+ getIsConferenceInProgress(taskData) ||
305
+ (interaction.state === INTERACTION_STATE.POST_CALL && customerLeft);
306
+ if (!conferenceFromPayload) return false;
307
+
308
+ return (
309
+ taskData.consultingAgentId === selfAgentId ||
310
+ interaction.owner === selfAgentId ||
311
+ selfParticipant?.consultState === CONSULT_STATE.CONSULTING ||
312
+ selfParticipant?.consultState === CONSULT_STATE.CONFERENCING ||
313
+ selfParticipant?.consultState === 'consultInitiated'
314
+ );
315
+ };
316
+
219
317
  /**
220
318
  * Checks if the current agent is a secondary agent in a consultation scenario.
221
319
  * Secondary agents are those who were consulted (not the original call owner).
@@ -101,6 +101,11 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
101
101
  target: TaskState.WRAPPING_UP,
102
102
  actions: ['updateTaskData', 'markEnded', 'emitTaskHydrate'],
103
103
  },
104
+ {
105
+ guard: guards.isEpDnPendingConferenceMergeHydrate,
106
+ target: TaskState.CONFERENCING,
107
+ actions: ['updateTaskData', 'emitTaskHydrate'],
108
+ },
104
109
  {
105
110
  guard: guards.isInteractionConsulting,
106
111
  target: TaskState.CONSULTING,
@@ -299,6 +304,14 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
299
304
  target: TaskState.HELD,
300
305
  actions: ['updateTaskData', 'setHoldState', 'emitTaskHold'],
301
306
  },
307
+ [TaskEvent.UNHOLD_INITIATED]: {
308
+ target: TaskState.RESUME_INITIATING,
309
+ },
310
+ // Resume while machine is CONNECTED but main media is still on hold (stale state after consult RONA/decline).
311
+ [TaskEvent.UNHOLD_SUCCESS]: {
312
+ target: TaskState.CONNECTED,
313
+ actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
314
+ },
302
315
  // Click of the consult button
303
316
  [TaskEvent.CONSULT]: {
304
317
  target: TaskState.CONSULT_INITIATING,
@@ -691,6 +704,48 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
691
704
  actions: ['updateTaskData', 'setHoldState', 'emitTaskResume'],
692
705
  },
693
706
 
707
+ [TaskEvent.PARTICIPANT_LEAVE]: [
708
+ {
709
+ guard: (params) =>
710
+ guards.didCurrentAgentLeaveConference(params) && guards.shouldWrapUp(params),
711
+ target: TaskState.WRAPPING_UP,
712
+ actions: [
713
+ 'updateTaskData',
714
+ 'handleParticipantLeft',
715
+ 'markEnded',
716
+ 'clearConsultState',
717
+ 'emitTaskParticipantLeft',
718
+ 'emitTaskWrapup',
719
+ ],
720
+ },
721
+ {
722
+ guard: guards.didCurrentAgentLeaveConference,
723
+ target: TaskState.TERMINATED,
724
+ actions: [
725
+ 'updateTaskData',
726
+ 'handleParticipantLeft',
727
+ 'markEnded',
728
+ 'clearConsultState',
729
+ 'emitTaskParticipantLeft',
730
+ 'emitTaskEnd',
731
+ ],
732
+ },
733
+ {
734
+ guard: (params) =>
735
+ !guards.didCurrentAgentLeaveConference(params) &&
736
+ guards.shouldDowngradeConferenceToConnected(params),
737
+ target: TaskState.CONNECTED,
738
+ actions: [
739
+ 'updateTaskData',
740
+ 'handleParticipantLeft',
741
+ 'clearConsultState',
742
+ 'emitTaskParticipantLeft',
743
+ 'emitTaskConferenceEnded',
744
+ ],
745
+ },
746
+ {actions: ['updateTaskData', 'handleParticipantLeft', 'emitTaskParticipantLeft']},
747
+ ],
748
+
694
749
  [TaskEvent.TRANSFER_SUCCESS]: [
695
750
  {
696
751
  guard: guards.shouldWrapUpOrIsInitiator,
@@ -711,14 +766,6 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
711
766
  actions: ['setTransferConferenceRequested', 'emitTaskTransferConference'],
712
767
  },
713
768
  [TaskEvent.TRANSFER_CONFERENCE_SUCCESS]: [
714
- {
715
- guard: ({context}) => context.transferConferenceRequested !== true,
716
- actions: [
717
- 'updateTaskData',
718
- 'handleTransferConferenceSuccess',
719
- 'clearTransferConferenceRequested',
720
- ],
721
- },
722
769
  {
723
770
  guard: guards.shouldWrapUp,
724
771
  target: TaskState.WRAPPING_UP,
@@ -732,8 +779,8 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
732
779
  ],
733
780
  },
734
781
  {
735
- // Non-initiator (consulted agent) stays in CONFERENCING
736
- guard: ({context}) => !context.consultInitiator,
782
+ // Non-initiator (e.g. consultee receiving transfer) moves to CONFERENCING
783
+ guard: (params) => !guards.isSelfConferenceTransferInitiator(params),
737
784
  target: TaskState.CONFERENCING,
738
785
  actions: [
739
786
  'updateTaskData',
@@ -977,14 +1024,46 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
977
1024
  },
978
1025
  [TaskEvent.TRANSFER_CONFERENCE_SUCCESS]: [
979
1026
  {
980
- // Not initiated by this agent → just refresh backend state.
981
- guard: ({context}) => context.transferConferenceRequested !== true,
1027
+ guard: guards.isPassiveConferenceTransferObserver,
1028
+ actions: [
1029
+ 'updateTaskData',
1030
+ 'handleTransferConferenceSuccess',
1031
+ 'clearTransferConferenceRequested',
1032
+ ],
1033
+ },
1034
+ {
1035
+ guard: guards.shouldWrapUp,
1036
+ target: TaskState.WRAPPING_UP,
1037
+ actions: [
1038
+ 'updateTaskData',
1039
+ 'markEnded',
1040
+ 'clearConsultState',
1041
+ 'handleTransferConferenceSuccess',
1042
+ 'clearTransferConferenceRequested',
1043
+ 'emitTaskWrapup',
1044
+ ],
1045
+ },
1046
+ {
1047
+ guard: (params) => !guards.isSelfConferenceTransferInitiator(params),
1048
+ target: TaskState.CONFERENCING,
982
1049
  actions: [
983
1050
  'updateTaskData',
1051
+ 'clearConsultState',
984
1052
  'handleTransferConferenceSuccess',
985
1053
  'clearTransferConferenceRequested',
986
1054
  ],
987
1055
  },
1056
+ {
1057
+ target: TaskState.TERMINATED,
1058
+ actions: [
1059
+ 'updateTaskData',
1060
+ 'markEnded',
1061
+ 'clearConsultState',
1062
+ 'handleTransferConferenceSuccess',
1063
+ 'clearTransferConferenceRequested',
1064
+ 'emitTaskEnd',
1065
+ ],
1066
+ },
988
1067
  ],
989
1068
  [TaskEvent.TRANSFER_CONFERENCE_FAILED]: {
990
1069
  actions: ['clearTransferConferenceRequested', 'emitTaskTransferConferenceFailed'],
@@ -1026,6 +1105,16 @@ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
1026
1105
  actions: ['updateTaskData', 'requestCleanup'],
1027
1106
  },
1028
1107
  ],
1108
+
1109
+ [TaskEvent.CONSULT_FAILED]: {
1110
+ actions: ['updateTaskData', 'handleConsultFailed'],
1111
+ },
1112
+
1113
+ [TaskEvent.TASK_WRAPUP]: {
1114
+ guard: guards.shouldWrapUp,
1115
+ target: TaskState.WRAPPING_UP,
1116
+ actions: ['updateTaskData', 'markEnded', 'clearConsultState', 'emitTaskWrapup'],
1117
+ },
1029
1118
  },
1030
1119
  },
1031
1120
 
@@ -19,10 +19,17 @@ import {
19
19
  MEDIA_TYPE_CONSULT,
20
20
  } from './constants';
21
21
  import {DestinationType, TaskData} from '../types';
22
+ import {CC_EVENTS} from '../../config/types';
22
23
  import {computeUIControls, getDefaultUIControls} from './uiControlsComputer';
23
- import {getIsConferenceInProgress} from '../TaskUtils';
24
+ import {getIsConferenceInProgress, isEpDnConsultPendingConferenceMerge} from '../TaskUtils';
24
25
  import {hasActiveConsultInPostCall} from './guards';
25
26
 
27
+ /** Terminal consult events that clear consult context in deriveTaskDataUpdates. */
28
+ const CONSULT_CONTEXT_CLEAR_EVENTS = new Set<string>([
29
+ CC_EVENTS.AGENT_CONSULT_ENDED,
30
+ CC_EVENTS.AGENT_CONSULT_FAILED,
31
+ ]);
32
+
26
33
  const determineConsultInitiator = (
27
34
  taskData: TaskData | undefined,
28
35
  selfAgentId: string | undefined
@@ -98,7 +105,8 @@ const isActiveConsultState = (taskData: TaskData | undefined, selfAgentId?: stri
98
105
  return false;
99
106
  }
100
107
  if (
101
- selfParticipant?.consultState === CONSULT_STATE.CONSULTING &&
108
+ (selfParticipant?.consultState === CONSULT_STATE.CONSULTING ||
109
+ selfParticipant?.consultState === CONSULT_STATE.CONFERENCING) &&
102
110
  hasConsultMedia &&
103
111
  taskData?.isConsulted === false
104
112
  ) {
@@ -125,6 +133,7 @@ const isSelfConsultingOrPending = (
125
133
 
126
134
  return (
127
135
  selfParticipant?.consultState === CONSULT_STATE.CONSULTING ||
136
+ selfParticipant?.consultState === CONSULT_STATE.CONFERENCING ||
128
137
  selfParticipant?.consultState === 'consultInitiated'
129
138
  );
130
139
  };
@@ -225,6 +234,11 @@ export const getTaskStateForUiControls = (
225
234
  return TaskState.WRAPPING_UP;
226
235
  }
227
236
 
237
+ // EP-DN consult merged to conference before Agent 2 joins main — UI is conferencing (CAI-8329).
238
+ if (isEpDnConsultPendingConferenceMerge(taskData, selfAgentId)) {
239
+ return TaskState.CONFERENCING;
240
+ }
241
+
228
242
  if (isActiveConsultState(taskData, selfAgentId)) {
229
243
  return TaskState.CONSULTING;
230
244
  }
@@ -250,34 +264,43 @@ export const getTaskStateForUiControls = (
250
264
  const deriveTaskDataUpdates = (context: TaskContext, taskData: TaskData | undefined) =>
251
265
  taskData
252
266
  ? (() => {
267
+ const enrichedTaskData: TaskData =
268
+ taskData.type === 'AgentConsultCreated' && taskData.agentId
269
+ ? {...taskData, consultingAgentId: taskData.agentId}
270
+ : taskData;
271
+
253
272
  const updates: Partial<TaskContext> = {
254
- taskData,
255
- ...deriveRecordingState(taskData),
273
+ taskData: enrichedTaskData,
274
+ ...deriveRecordingState(enrichedTaskData),
256
275
  };
257
276
 
258
- const selfAgentId = context.uiControlConfig.agentId ?? taskData?.agentId;
277
+ const selfAgentId = context.uiControlConfig.agentId ?? enrichedTaskData?.agentId;
259
278
  const consultingActive =
260
- isActiveConsultState(taskData, selfAgentId) ||
261
- hasActiveConsultInPostCall(taskData, selfAgentId);
279
+ isActiveConsultState(enrichedTaskData, selfAgentId) ||
280
+ hasActiveConsultInPostCall(enrichedTaskData, selfAgentId);
262
281
  const conferenceFromPayload =
263
- taskData?.interaction?.state === INTERACTION_STATE.CONFERENCE ||
264
- getIsConferenceInProgress(taskData);
265
- const selfConsultingOrPending = isSelfConsultingOrPending(taskData, selfAgentId);
282
+ enrichedTaskData?.interaction?.state === INTERACTION_STATE.CONFERENCE ||
283
+ getIsConferenceInProgress(enrichedTaskData);
284
+ const selfConsultingOrPending = isSelfConsultingOrPending(enrichedTaskData, selfAgentId);
266
285
  const inferredConsultingInitiator =
267
- selfConsultingOrPending && taskData?.isConsulted === false;
286
+ selfConsultingOrPending && enrichedTaskData?.isConsulted === false;
268
287
 
269
- if (taskData.destAgentId) {
288
+ if (enrichedTaskData.destAgentId) {
270
289
  const isEpDnWithStoredId =
271
290
  context.consultDestinationType === 'entryPoint' && context.consultDestinationAgentId;
272
291
  if (!isEpDnWithStoredId) {
273
- updates.consultDestinationAgentId = taskData.destAgentId;
292
+ updates.consultDestinationAgentId = enrichedTaskData.destAgentId;
274
293
  }
275
294
  }
276
295
 
277
296
  const isConsultTerminalEvent =
278
- taskData.type === 'AgentConsultEnded' || taskData.type === 'AgentConsultFailed';
297
+ enrichedTaskData.type != null && CONSULT_CONTEXT_CLEAR_EVENTS.has(enrichedTaskData.type);
298
+ const epDnPendingMergeActive = isEpDnConsultPendingConferenceMerge(
299
+ enrichedTaskData,
300
+ selfAgentId
301
+ );
279
302
 
280
- if (isConsultTerminalEvent) {
303
+ if (isConsultTerminalEvent && !epDnPendingMergeActive) {
281
304
  updates.consultInitiator = false;
282
305
  updates.consultFromConference = false;
283
306
  updates.consultDestinationAgentJoined = false;
@@ -285,21 +308,38 @@ const deriveTaskDataUpdates = (context: TaskContext, taskData: TaskData | undefi
285
308
  updates.consultDestinationType = null;
286
309
  updates.consultDestinationAgentId = null;
287
310
  updates.transferConferenceRequested = false;
311
+ updates.hideBlindTransferForEpDnPendingMerge = false;
288
312
  }
289
313
 
290
- if (consultingActive && taskData.destinationType) {
291
- updates.consultDestinationType = mapConsultDestinationType(taskData.destinationType);
314
+ updates.hideBlindTransferForEpDnPendingMerge = isEpDnConsultPendingConferenceMerge(
315
+ enrichedTaskData,
316
+ selfAgentId
317
+ );
318
+
319
+ if (consultingActive && enrichedTaskData.destinationType) {
320
+ updates.consultDestinationType = mapConsultDestinationType(
321
+ enrichedTaskData.destinationType
322
+ );
292
323
  }
293
324
 
294
- if (!isConsultTerminalEvent && !context.consultInitiator) {
295
- const consultInitiator = determineConsultInitiator(taskData, selfAgentId);
325
+ if (!isConsultTerminalEvent) {
326
+ const consultInitiator = determineConsultInitiator(enrichedTaskData, selfAgentId);
296
327
  if (consultInitiator !== undefined) {
297
328
  updates.consultInitiator = consultInitiator;
298
329
  } else if (
299
- inferredConsultingInitiator ||
300
- (consultingActive && taskData.isConsulted === false)
330
+ enrichedTaskData.type === 'AgentConsultCreated' &&
331
+ enrichedTaskData.agentId &&
332
+ enrichedTaskData.agentId !== selfAgentId &&
333
+ conferenceFromPayload
301
334
  ) {
302
- updates.consultInitiator = true;
335
+ updates.consultInitiator = false;
336
+ } else if (!context.consultInitiator) {
337
+ if (
338
+ inferredConsultingInitiator ||
339
+ (consultingActive && enrichedTaskData.isConsulted === false)
340
+ ) {
341
+ updates.consultInitiator = true;
342
+ }
303
343
  }
304
344
  }
305
345
 
@@ -308,7 +348,9 @@ const deriveTaskDataUpdates = (context: TaskContext, taskData: TaskData | undefi
308
348
  !isConsultTerminalEvent &&
309
349
  effectiveConsultInitiator &&
310
350
  conferenceFromPayload &&
311
- (consultingActive || selfConsultingOrPending || Boolean(taskData?.consultMediaResourceId))
351
+ (consultingActive ||
352
+ selfConsultingOrPending ||
353
+ Boolean(enrichedTaskData?.consultMediaResourceId))
312
354
  ) {
313
355
  updates.consultFromConference = true;
314
356
  }
@@ -316,16 +358,16 @@ const deriveTaskDataUpdates = (context: TaskContext, taskData: TaskData | undefi
316
358
  if (
317
359
  !isConsultTerminalEvent &&
318
360
  (consultingActive || selfConsultingOrPending) &&
319
- taskData.interaction
361
+ enrichedTaskData.interaction
320
362
  ) {
321
- const joinedConsultee = hasJoinedConsultDestination(taskData, selfAgentId);
363
+ const joinedConsultee = hasJoinedConsultDestination(enrichedTaskData, selfAgentId);
322
364
  const selfParticipant = selfAgentId
323
- ? (taskData.interaction.participants?.[selfAgentId] as
365
+ ? (enrichedTaskData.interaction.participants?.[selfAgentId] as
324
366
  | {consultState?: string}
325
367
  | undefined)
326
368
  : undefined;
327
369
  const agentConsultingSelfJoined =
328
- taskData.type === 'AgentConsulting' &&
370
+ enrichedTaskData.type === 'AgentConsulting' &&
329
371
  effectiveConsultInitiator &&
330
372
  selfParticipant?.consultState === CONSULT_STATE.CONSULTING;
331
373
 
@@ -337,8 +379,8 @@ const deriveTaskDataUpdates = (context: TaskContext, taskData: TaskData | undefi
337
379
 
338
380
  if (!context.consultDestinationType && !updates.consultDestinationType) {
339
381
  const hasEpDnParticipant = Boolean(
340
- taskData.interaction.participants &&
341
- Object.values(taskData.interaction.participants).some((p: any) => {
382
+ enrichedTaskData.interaction.participants &&
383
+ Object.values(enrichedTaskData.interaction.participants).some((p: any) => {
342
384
  if (p?.hasLeft) return false;
343
385
  const pType = String(p?.pType ?? '').toUpperCase();
344
386
 
@@ -349,7 +391,7 @@ const deriveTaskDataUpdates = (context: TaskContext, taskData: TaskData | undefi
349
391
  }
350
392
 
351
393
  if (effectiveConsultInitiator) {
352
- const consultCallHeld = deriveConsultCallHeldFromTaskData(taskData);
394
+ const consultCallHeld = deriveConsultCallHeldFromTaskData(enrichedTaskData);
353
395
  if (consultCallHeld !== undefined) {
354
396
  updates.consultCallHeld = consultCallHeld;
355
397
  }
@@ -360,9 +402,9 @@ const deriveTaskDataUpdates = (context: TaskContext, taskData: TaskData | undefi
360
402
  ...context,
361
403
  ...updates,
362
404
  } as TaskContext;
363
- const inferredState = getTaskStateForUiControls(taskData, selfAgentId);
405
+ const inferredState = getTaskStateForUiControls(enrichedTaskData, selfAgentId);
364
406
 
365
- updates.uiControls = computeUIControls(inferredState, nextContext, taskData);
407
+ updates.uiControls = computeUIControls(inferredState, nextContext, enrichedTaskData);
366
408
 
367
409
  return updates;
368
410
  })()
@@ -385,6 +427,7 @@ export function createInitialContext(
385
427
  consultDestinationAgentId: null,
386
428
  consultDestinationAgentJoined: false,
387
429
  consultCallHeld: false,
430
+ hideBlindTransferForEpDnPendingMerge: false,
388
431
  recordingControlsAvailable: false,
389
432
  recordingInProgress: false,
390
433
  uiControlConfig,
@@ -446,6 +489,11 @@ export const actions: TaskActionsMap = {
446
489
 
447
490
  handleConsultFailed: assign(({context, event}: TaskActionArgs) => {
448
491
  const taskData = getTaskDataFromEvent(event) ?? context.taskData;
492
+ const selfAgentId = context.uiControlConfig.agentId ?? taskData?.agentId;
493
+ const hideBlindTransferForEpDnPendingMerge = isEpDnConsultPendingConferenceMerge(
494
+ taskData,
495
+ selfAgentId
496
+ );
449
497
  const cleared = {
450
498
  consultDestinationType: null,
451
499
  consultDestinationAgentId: null,
@@ -455,8 +503,8 @@ export const actions: TaskActionsMap = {
455
503
  consultCallHeld: false,
456
504
  consultFromConference: false,
457
505
  transferConferenceRequested: false,
506
+ hideBlindTransferForEpDnPendingMerge,
458
507
  };
459
- const selfAgentId = context.uiControlConfig.agentId ?? taskData?.agentId;
460
508
  const nextContext = {...context, ...cleared, taskData} as TaskContext;
461
509
  const inferredState = getTaskStateForUiControls(taskData, selfAgentId);
462
510
 
@@ -539,6 +587,12 @@ export const actions: TaskActionsMap = {
539
587
  }),
540
588
 
541
589
  clearConsultState: assign(({context, event}: TaskActionArgs) => {
590
+ const taskData = context.taskData ?? getTaskDataFromEvent(event);
591
+ const selfAgentId = context.uiControlConfig.agentId ?? taskData?.agentId;
592
+ const hideBlindTransferForEpDnPendingMerge = isEpDnConsultPendingConferenceMerge(
593
+ taskData,
594
+ selfAgentId
595
+ );
542
596
  const cleared = {
543
597
  consultDestinationType: null,
544
598
  consultDestinationAgentId: null,
@@ -548,9 +602,8 @@ export const actions: TaskActionsMap = {
548
602
  consultCallHeld: false,
549
603
  consultFromConference: false,
550
604
  transferConferenceRequested: false,
605
+ hideBlindTransferForEpDnPendingMerge,
551
606
  };
552
- const taskData = context.taskData ?? getTaskDataFromEvent(event);
553
- const selfAgentId = context.uiControlConfig.agentId ?? taskData?.agentId;
554
607
  const nextContext = {...context, ...cleared, taskData} as TaskContext;
555
608
  const inferredState = getTaskStateForUiControls(taskData, selfAgentId);
556
609