@webex/contact-center 3.12.0-next.9 → 3.12.0-next.91

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 (229) hide show
  1. package/.sdd/manifest.json +876 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +46 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +138 -0
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +444 -0
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +48 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +341 -0
  17. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  18. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  19. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  20. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  21. package/ai-docs/patterns/testing-patterns.md +480 -0
  22. package/ai-docs/patterns/typescript-patterns.md +365 -0
  23. package/ai-docs/templates/README.md +102 -0
  24. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  25. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  26. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  27. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  28. package/ai-docs/templates/new-method/00-master.md +80 -0
  29. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  30. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  31. package/ai-docs/templates/new-method/03-tests.md +201 -0
  32. package/ai-docs/templates/new-method/04-validation.md +141 -0
  33. package/ai-docs/templates/new-service/00-master.md +109 -0
  34. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  35. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  36. package/ai-docs/templates/new-service/03-integration.md +178 -0
  37. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  38. package/ai-docs/templates/new-service/05-validation.md +145 -0
  39. package/dist/cc.js +265 -29
  40. package/dist/cc.js.map +1 -1
  41. package/dist/config.js +6 -0
  42. package/dist/config.js.map +1 -1
  43. package/dist/constants.js +17 -1
  44. package/dist/constants.js.map +1 -1
  45. package/dist/index.js +20 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/metrics/behavioral-events.js +101 -0
  48. package/dist/metrics/behavioral-events.js.map +1 -1
  49. package/dist/metrics/constants.js +25 -4
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/services/ApiAiAssistant.js +153 -8
  52. package/dist/services/ApiAiAssistant.js.map +1 -1
  53. package/dist/services/UserPreference.js +427 -0
  54. package/dist/services/UserPreference.js.map +1 -0
  55. package/dist/services/config/Util.js +3 -3
  56. package/dist/services/config/Util.js.map +1 -1
  57. package/dist/services/config/constants.js +23 -2
  58. package/dist/services/config/constants.js.map +1 -1
  59. package/dist/services/config/types.js +49 -9
  60. package/dist/services/config/types.js.map +1 -1
  61. package/dist/services/core/Err.js.map +1 -1
  62. package/dist/services/core/Utils.js +107 -32
  63. package/dist/services/core/Utils.js.map +1 -1
  64. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  65. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  66. package/dist/services/core/websocket/types.js.map +1 -1
  67. package/dist/services/index.js +1 -1
  68. package/dist/services/index.js.map +1 -1
  69. package/dist/services/task/Task.js +688 -0
  70. package/dist/services/task/Task.js.map +1 -0
  71. package/dist/services/task/TaskFactory.js +45 -0
  72. package/dist/services/task/TaskFactory.js.map +1 -0
  73. package/dist/services/task/TaskManager.js +728 -527
  74. package/dist/services/task/TaskManager.js.map +1 -1
  75. package/dist/services/task/TaskUtils.js +162 -26
  76. package/dist/services/task/TaskUtils.js.map +1 -1
  77. package/dist/services/task/constants.js +9 -2
  78. package/dist/services/task/constants.js.map +1 -1
  79. package/dist/services/task/dialer.js +78 -0
  80. package/dist/services/task/dialer.js.map +1 -1
  81. package/dist/services/task/digital/Digital.js +77 -0
  82. package/dist/services/task/digital/Digital.js.map +1 -0
  83. package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
  84. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  85. package/dist/services/task/state-machine/actions.js +543 -0
  86. package/dist/services/task/state-machine/actions.js.map +1 -0
  87. package/dist/services/task/state-machine/constants.js +161 -0
  88. package/dist/services/task/state-machine/constants.js.map +1 -0
  89. package/dist/services/task/state-machine/guards.js +340 -0
  90. package/dist/services/task/state-machine/guards.js.map +1 -0
  91. package/dist/services/task/state-machine/index.js +53 -0
  92. package/dist/services/task/state-machine/index.js.map +1 -0
  93. package/dist/services/task/state-machine/types.js +54 -0
  94. package/dist/services/task/state-machine/types.js.map +1 -0
  95. package/dist/services/task/state-machine/uiControlsComputer.js +553 -0
  96. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  97. package/dist/services/task/taskDataNormalizer.js +99 -0
  98. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  99. package/dist/services/task/types.js +212 -4
  100. package/dist/services/task/types.js.map +1 -1
  101. package/dist/services/task/voice/Voice.js +1042 -0
  102. package/dist/services/task/voice/Voice.js.map +1 -0
  103. package/dist/services/task/voice/WebRTC.js +149 -0
  104. package/dist/services/task/voice/WebRTC.js.map +1 -0
  105. package/dist/types/cc.d.ts +94 -1
  106. package/dist/types/config.d.ts +6 -0
  107. package/dist/types/constants.d.ts +17 -1
  108. package/dist/types/index.d.ts +21 -6
  109. package/dist/types/metrics/constants.d.ts +21 -1
  110. package/dist/types/services/ApiAiAssistant.d.ts +22 -4
  111. package/dist/types/services/UserPreference.d.ts +118 -0
  112. package/dist/types/services/config/constants.d.ts +21 -0
  113. package/dist/types/services/config/types.d.ts +171 -10
  114. package/dist/types/services/core/Err.d.ts +4 -0
  115. package/dist/types/services/core/Utils.d.ts +33 -13
  116. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  117. package/dist/types/services/core/websocket/types.d.ts +1 -1
  118. package/dist/types/services/index.d.ts +1 -1
  119. package/dist/types/services/task/Task.d.ts +157 -0
  120. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  121. package/dist/types/services/task/TaskUtils.d.ts +46 -2
  122. package/dist/types/services/task/constants.d.ts +7 -0
  123. package/dist/types/services/task/dialer.d.ts +30 -0
  124. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  125. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
  126. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  127. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  128. package/dist/types/services/task/state-machine/guards.d.ts +90 -0
  129. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  130. package/dist/types/services/task/state-machine/types.d.ts +267 -0
  131. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  132. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  133. package/dist/types/services/task/types.d.ts +603 -66
  134. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  135. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  136. package/dist/types/types.d.ts +135 -0
  137. package/dist/types/webex.d.ts +1 -0
  138. package/dist/types.js +118 -2
  139. package/dist/types.js.map +1 -1
  140. package/dist/webex.js +14 -2
  141. package/dist/webex.js.map +1 -1
  142. package/package.json +15 -12
  143. package/src/cc.ts +329 -30
  144. package/src/config.ts +6 -0
  145. package/src/constants.ts +17 -1
  146. package/src/index.ts +23 -5
  147. package/src/metrics/ai-docs/AGENTS.md +350 -0
  148. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  149. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  150. package/src/metrics/behavioral-events.ts +106 -0
  151. package/src/metrics/constants.ts +27 -4
  152. package/src/services/ApiAiAssistant.ts +203 -8
  153. package/src/services/UserPreference.ts +509 -0
  154. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  155. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  156. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  157. package/src/services/ai-docs/AGENTS.md +386 -0
  158. package/src/services/ai-docs/services-spec.md +492 -0
  159. package/src/services/config/Util.ts +3 -3
  160. package/src/services/config/ai-docs/AGENTS.md +255 -0
  161. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  162. package/src/services/config/ai-docs/config-spec.md +669 -0
  163. package/src/services/config/constants.ts +25 -1
  164. package/src/services/config/types.ts +174 -11
  165. package/src/services/core/Err.ts +2 -0
  166. package/src/services/core/Utils.ts +123 -37
  167. package/src/services/core/ai-docs/AGENTS.md +381 -0
  168. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  169. package/src/services/core/ai-docs/core-spec.md +783 -0
  170. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  171. package/src/services/core/websocket/types.ts +1 -1
  172. package/src/services/index.ts +1 -1
  173. package/src/services/task/Task.ts +837 -0
  174. package/src/services/task/TaskFactory.ts +55 -0
  175. package/src/services/task/TaskManager.ts +739 -614
  176. package/src/services/task/TaskUtils.ts +205 -25
  177. package/src/services/task/ai-docs/AGENTS.md +457 -0
  178. package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
  179. package/src/services/task/ai-docs/task-spec.md +1319 -0
  180. package/src/services/task/constants.ts +7 -0
  181. package/src/services/task/dialer.ts +80 -0
  182. package/src/services/task/digital/Digital.ts +95 -0
  183. package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
  184. package/src/services/task/state-machine/actions.ts +685 -0
  185. package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
  186. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
  187. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2115 -0
  188. package/src/services/task/state-machine/constants.ts +172 -0
  189. package/src/services/task/state-machine/guards.ts +406 -0
  190. package/src/services/task/state-machine/index.ts +28 -0
  191. package/src/services/task/state-machine/types.ts +241 -0
  192. package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
  193. package/src/services/task/taskDataNormalizer.ts +137 -0
  194. package/src/services/task/types.ts +710 -71
  195. package/src/services/task/voice/Voice.ts +1267 -0
  196. package/src/services/task/voice/WebRTC.ts +187 -0
  197. package/src/types.ts +166 -2
  198. package/src/utils/AGENTS.md +278 -0
  199. package/src/utils/ai-docs/utils-spec.md +381 -0
  200. package/src/webex.js +2 -0
  201. package/test/unit/spec/cc.ts +343 -23
  202. package/test/unit/spec/logger-proxy.ts +70 -0
  203. package/test/unit/spec/services/ApiAiAssistant.ts +178 -20
  204. package/test/unit/spec/services/UserPreference.ts +401 -0
  205. package/test/unit/spec/services/WebCallingService.ts +7 -1
  206. package/test/unit/spec/services/config/index.ts +30 -30
  207. package/test/unit/spec/services/core/Utils.ts +425 -8
  208. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  209. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  210. package/test/unit/spec/services/task/Task.ts +477 -0
  211. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  212. package/test/unit/spec/services/task/TaskManager.ts +834 -1704
  213. package/test/unit/spec/services/task/TaskUtils.ts +206 -0
  214. package/test/unit/spec/services/task/dialer.ts +190 -0
  215. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  216. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
  217. package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
  218. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  219. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
  220. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  221. package/test/unit/spec/services/task/voice/Voice.ts +631 -0
  222. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  223. package/umd/contact-center.min.js +2 -2
  224. package/umd/contact-center.min.js.map +1 -1
  225. package/dist/services/task/index.js +0 -1525
  226. package/dist/services/task/index.js.map +0 -1
  227. package/dist/types/services/task/index.d.ts +0 -650
  228. package/src/services/task/index.ts +0 -1801
  229. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -1,8 +1,135 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
2
  import {Interaction, ITask, TaskData, MEDIA_CHANNEL} from './types';
3
- import {CC_EVENTS} from '../config/types';
4
- import {OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE, OUTBOUND_TYPE} from '../../constants';
5
3
  import {LoginOption} from '../../types';
4
+ import {PARTICIPANT_TYPE, MEDIA_TYPE_MAIN_CALL} from './state-machine/constants';
5
+ import {TaskContext} from './state-machine/types';
6
+ import {CC_EVENTS} from '../config/types';
7
+ import {OUTBOUND_TYPE, OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE} from '../../constants';
8
+
9
+ const CAMPAIGN_PREVIEW_OUTBOUND_TYPES = ['STANDARD_PREVIEW_CAMPAIGN', 'DIRECT_PREVIEW_CAMPAIGN'];
10
+ const CAMPAIGN_PREVIEW_CAMPAIGN_TYPES = ['preview_standard', 'preview_direct'];
11
+
12
+ /**
13
+ * Checks if the customer is still in the call (not left)
14
+ *
15
+ * @param interaction - The interaction object
16
+ * @param interactionId - The main interaction ID
17
+ * @returns true if customer is in the call
18
+ */
19
+ export const getIsCustomerInCall = (interaction: Interaction, interactionId: string): boolean => {
20
+ const mainCallMedia = interaction.media?.[interactionId];
21
+ const participants = interaction.participants;
22
+ if (!mainCallMedia?.participants || !participants) {
23
+ return false;
24
+ }
25
+
26
+ return mainCallMedia.participants.some((participantId: string) => {
27
+ const participant = participants[participantId];
28
+
29
+ return participant?.pType === PARTICIPANT_TYPE.CUSTOMER && !participant.hasLeft;
30
+ });
31
+ };
32
+
33
+ /**
34
+ * Gets the count of active agent participants in the conference
35
+ * Excludes Customer, Supervisor, and VVA participant types
36
+ *
37
+ * @param interaction - The interaction object
38
+ * @param interactionId - The main interaction ID
39
+ * @returns Number of active agent participants
40
+ */
41
+ export const getConferenceParticipantsCount = (
42
+ interaction: Interaction,
43
+ interactionId: string
44
+ ): number => {
45
+ const mainCallMedia = interaction.media?.[interactionId];
46
+ const participants = interaction.participants;
47
+ if (!mainCallMedia?.participants || !participants) {
48
+ return 0;
49
+ }
50
+
51
+ let count = 0;
52
+ for (const participantId of mainCallMedia.participants) {
53
+ const participant = participants[participantId];
54
+ if (
55
+ participant &&
56
+ participant.pType !== PARTICIPANT_TYPE.CUSTOMER &&
57
+ participant.pType !== PARTICIPANT_TYPE.SUPERVISOR &&
58
+ participant.pType !== PARTICIPANT_TYPE.VVA &&
59
+ !participant.hasLeft
60
+ ) {
61
+ count += 1;
62
+ }
63
+ }
64
+
65
+ return count;
66
+ };
67
+
68
+ /**
69
+ * Determines if a consult is actively in-progress for conference control gating.
70
+ * This is used to disable conference controls (End/Consult) only when a consult leg
71
+ * still exists outside the main call participants.
72
+ */
73
+ export const getIsConsultInProgressForConferenceControls = (
74
+ interaction: Interaction | undefined,
75
+ mainCallId: string | undefined,
76
+ selfAgentId: string | undefined
77
+ ): boolean => {
78
+ if (!interaction || !mainCallId) return false;
79
+
80
+ const mainParticipants = interaction.media?.[mainCallId]?.participants;
81
+ if (!Array.isArray(mainParticipants) || mainParticipants.length === 0) return false;
82
+
83
+ const mainSet = new Set(mainParticipants);
84
+ const media = interaction.media;
85
+ if (!media) return false;
86
+
87
+ return Object.values(media).some((m: any) => {
88
+ if (!m || m.mType !== 'consult') return false;
89
+ if (!Array.isArray(m.participants) || m.participants.length === 0) return false;
90
+
91
+ return m.participants.some((participantId: string) => {
92
+ const p: any = interaction.participants?.[participantId];
93
+ if (!p || p.hasLeft) return false;
94
+ if (selfAgentId && participantId === selfAgentId) return false;
95
+
96
+ const consultState = p.consultState as string | undefined;
97
+ const isRonaPendingConsultee = consultState === 'consultReserved' && p.hasJoined === false;
98
+ const consultLegActive =
99
+ consultState === 'consulting' ||
100
+ p.currentState === 'consulting' ||
101
+ (p.isConsulted === true && consultState !== 'consultCompleted' && !isRonaPendingConsultee);
102
+
103
+ return consultLegActive && !mainSet.has(participantId);
104
+ });
105
+ });
106
+ };
107
+
108
+ export const getIsConsultedAgentForControls = (
109
+ taskData: TaskData | null,
110
+ context: TaskContext,
111
+ isConsultingState: boolean
112
+ ): boolean => {
113
+ return Boolean(taskData?.isConsulted) || (isConsultingState && !context.consultInitiator);
114
+ };
115
+
116
+ export const getServerHoldStateForControls = (
117
+ context: TaskContext,
118
+ mainCallId?: string,
119
+ fallbackTaskData?: TaskData | null
120
+ ): boolean | undefined => {
121
+ const media = context.taskData?.interaction?.media ?? fallbackTaskData?.interaction?.media;
122
+ if (!media) return undefined;
123
+
124
+ if (mainCallId && media[mainCallId]) {
125
+ return media[mainCallId].isHold ?? false;
126
+ }
127
+
128
+ const mediaId = context.taskData?.mediaResourceId ?? fallbackTaskData?.mediaResourceId;
129
+ if (!mediaId) return undefined;
130
+
131
+ return media[mediaId]?.isHold;
132
+ };
6
133
 
7
134
  /**
8
135
  * Determines if the given agent is the primary agent (owner) of the task
@@ -32,7 +159,9 @@ export const isParticipantInMainInteraction = (task: ITask, agentId: string): bo
32
159
 
33
160
  return Object.values(task.data.interaction.media).some(
34
161
  (mediaObj) =>
35
- mediaObj && mediaObj.mType === 'mainCall' && mediaObj.participants?.includes(agentId)
162
+ mediaObj &&
163
+ mediaObj.mType === MEDIA_TYPE_MAIN_CALL &&
164
+ mediaObj.participants?.includes(agentId)
36
165
  );
37
166
  };
38
167
 
@@ -56,29 +185,33 @@ export const checkParticipantNotInInteraction = (task: ITask, agentId: string):
56
185
 
57
186
  /**
58
187
  * Determines if a conference is currently in progress based on the number of active agent participants
59
- * @param TaskData - The payLoad data to check for conference status
188
+ * @param data - The task data to check for conference status
60
189
  * @returns true if there are 2 or more active agent participants in the main call, false otherwise
190
+ *
191
+ * For Agent B (consulted agent), their task's interactionId may be different from the main call.
192
+ * We use mainInteractionId from the interaction if available, otherwise fallback to interactionId.
61
193
  */
62
194
  export const getIsConferenceInProgress = (data: TaskData): boolean => {
63
- const mediaMainCall = data.interaction.media?.[data?.interactionId];
195
+ if (!data.interaction) return false;
196
+
197
+ const mainCallId = data.interaction.mainInteractionId || data.interactionId;
198
+ const mediaMainCall = data.interaction.media?.[mainCallId];
64
199
  const participantsInMainCall = new Set(mediaMainCall?.participants);
65
- const participants = data.interaction.participants;
200
+ const {participants} = data.interaction;
66
201
 
67
202
  const agentParticipants = new Set();
68
- if (participantsInMainCall.size > 0) {
69
- participantsInMainCall.forEach((participantId: string) => {
70
- const participant = participants?.[participantId];
71
- if (
72
- participant &&
73
- participant.pType !== 'Customer' &&
74
- participant.pType !== 'Supervisor' &&
75
- !participant.hasLeft &&
76
- participant.pType !== 'VVA'
77
- ) {
78
- agentParticipants.add(participantId);
79
- }
80
- });
81
- }
203
+ participantsInMainCall.forEach((participantId: string) => {
204
+ const participant = participants[participantId];
205
+ if (
206
+ participant &&
207
+ participant.pType !== PARTICIPANT_TYPE.CUSTOMER &&
208
+ participant.pType !== PARTICIPANT_TYPE.SUPERVISOR &&
209
+ participant.pType !== PARTICIPANT_TYPE.VVA &&
210
+ !participant.hasLeft
211
+ ) {
212
+ agentParticipants.add(participantId);
213
+ }
214
+ });
82
215
 
83
216
  return agentParticipants.size >= 2;
84
217
  };
@@ -104,17 +237,30 @@ export const isSecondaryAgent = (interaction: Interaction): boolean => {
104
237
  /**
105
238
  * Checks if the current agent is a secondary EP-DN (Entry Point Dial Number) agent.
106
239
  * This is specifically for telephony consultations to external numbers/entry points.
107
- * @param task - The task object containing interaction details
240
+ * @param interaction - The interaction object
108
241
  * @returns true if this is a secondary EP-DN agent in telephony consultation, false otherwise
109
242
  */
110
243
  export const isSecondaryEpDnAgent = (interaction: Interaction): boolean => {
111
- if (!interaction) {
112
- return false;
113
- }
114
-
115
244
  return interaction.mediaType === 'telephony' && isSecondaryAgent(interaction);
116
245
  };
117
246
 
247
+ /**
248
+ * Checks if the task belongs to a campaign preview interaction.
249
+ * Campaign preview ContactEnded events are terminal cleanup events and should not trigger wrapup.
250
+ */
251
+ export const isCampaignPreviewTask = (taskData?: TaskData | null): boolean => {
252
+ const outboundType = taskData?.interaction?.outboundType ?? '';
253
+ const cpd = taskData?.interaction?.callProcessingDetails as unknown as
254
+ | Record<string, string | undefined>
255
+ | undefined;
256
+ const campaignType = cpd?.campaignType ?? '';
257
+
258
+ return (
259
+ CAMPAIGN_PREVIEW_OUTBOUND_TYPES.includes(outboundType) ||
260
+ CAMPAIGN_PREVIEW_CAMPAIGN_TYPES.includes(campaignType)
261
+ );
262
+ };
263
+
118
264
  /**
119
265
  * Checks if auto-answer is enabled for the agent participant
120
266
  * @param interaction - The interaction object
@@ -221,6 +367,40 @@ export const shouldAutoAnswerTask = (
221
367
  return false;
222
368
  };
223
369
 
370
+ /**
371
+ * Gets the consult media resource ID for switch-call operations.
372
+ * Searches for the consult media leg in the interaction.
373
+ *
374
+ * @param interaction - The interaction object
375
+ * @param consultMediaResourceId - The consult media resource ID from task data
376
+ * @param agentId - Current agent ID
377
+ * @returns The consult media resource ID or undefined
378
+ */
379
+ export const getConsultMediaResourceId = (
380
+ interaction: Interaction | undefined,
381
+ consultMediaResourceId: string | undefined,
382
+ agentId: string | undefined
383
+ ): string | undefined => {
384
+ // First priority: use consultMediaResourceId from task data if available
385
+ if (consultMediaResourceId) {
386
+ return consultMediaResourceId;
387
+ }
388
+
389
+ // Second priority: search for consult media leg in interaction.media
390
+ if (!interaction?.media || !agentId) {
391
+ return undefined;
392
+ }
393
+
394
+ // Find the consult media leg where this agent is a participant
395
+ for (const [mediaId, media] of Object.entries(interaction.media)) {
396
+ if (media.mType === 'consult' && media.participants?.includes(agentId)) {
397
+ return mediaId;
398
+ }
399
+ }
400
+
401
+ return undefined;
402
+ };
403
+
224
404
  /**
225
405
  * Checks if a task is a campaign preview reservation that has not yet been accepted.
226
406
  * Campaign preview tasks should not trigger incoming call handling until the agent