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

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/.sdd/manifest.json +882 -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 +379 -50
  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 +22 -2
  44. package/dist/constants.js.map +1 -1
  45. package/dist/index.js +27 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/metrics/behavioral-events.js +114 -0
  48. package/dist/metrics/behavioral-events.js.map +1 -1
  49. package/dist/metrics/constants.js +32 -3
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/services/ApiAiAssistant.js +318 -0
  52. package/dist/services/ApiAiAssistant.js.map +1 -0
  53. package/dist/services/UserPreference.js +427 -0
  54. package/dist/services/UserPreference.js.map +1 -0
  55. package/dist/services/agent/types.js.map +1 -1
  56. package/dist/services/config/Util.js +8 -4
  57. package/dist/services/config/Util.js.map +1 -1
  58. package/dist/services/config/constants.js +35 -2
  59. package/dist/services/config/constants.js.map +1 -1
  60. package/dist/services/config/index.js +41 -2
  61. package/dist/services/config/index.js.map +1 -1
  62. package/dist/services/config/types.js +66 -8
  63. package/dist/services/config/types.js.map +1 -1
  64. package/dist/services/constants.js +27 -1
  65. package/dist/services/constants.js.map +1 -1
  66. package/dist/services/core/Err.js.map +1 -1
  67. package/dist/services/core/Utils.js +122 -25
  68. package/dist/services/core/Utils.js.map +1 -1
  69. package/dist/services/core/aqm-reqs.js +92 -17
  70. package/dist/services/core/aqm-reqs.js.map +1 -1
  71. package/dist/services/core/websocket/WebSocketManager.js +22 -6
  72. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  73. package/dist/services/core/websocket/connection-service.js +3 -1
  74. package/dist/services/core/websocket/connection-service.js.map +1 -1
  75. package/dist/services/core/websocket/types.js.map +1 -1
  76. package/dist/services/index.js +6 -0
  77. package/dist/services/index.js.map +1 -1
  78. package/dist/services/task/Task.js +688 -0
  79. package/dist/services/task/Task.js.map +1 -0
  80. package/dist/services/task/TaskFactory.js +45 -0
  81. package/dist/services/task/TaskFactory.js.map +1 -0
  82. package/dist/services/task/TaskManager.js +751 -457
  83. package/dist/services/task/TaskManager.js.map +1 -1
  84. package/dist/services/task/TaskUtils.js +220 -23
  85. package/dist/services/task/TaskUtils.js.map +1 -1
  86. package/dist/services/task/constants.js +23 -2
  87. package/dist/services/task/constants.js.map +1 -1
  88. package/dist/services/task/dialer.js +129 -0
  89. package/dist/services/task/dialer.js.map +1 -1
  90. package/dist/services/task/digital/Digital.js +77 -0
  91. package/dist/services/task/digital/Digital.js.map +1 -0
  92. package/dist/services/task/state-machine/TaskStateMachine.js +873 -0
  93. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  94. package/dist/services/task/state-machine/actions.js +567 -0
  95. package/dist/services/task/state-machine/actions.js.map +1 -0
  96. package/dist/services/task/state-machine/constants.js +161 -0
  97. package/dist/services/task/state-machine/constants.js.map +1 -0
  98. package/dist/services/task/state-machine/guards.js +382 -0
  99. package/dist/services/task/state-machine/guards.js.map +1 -0
  100. package/dist/services/task/state-machine/index.js +53 -0
  101. package/dist/services/task/state-machine/index.js.map +1 -0
  102. package/dist/services/task/state-machine/types.js +54 -0
  103. package/dist/services/task/state-machine/types.js.map +1 -0
  104. package/dist/services/task/state-machine/uiControlsComputer.js +603 -0
  105. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  106. package/dist/services/task/taskDataNormalizer.js +99 -0
  107. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  108. package/dist/services/task/types.js +227 -4
  109. package/dist/services/task/types.js.map +1 -1
  110. package/dist/services/task/voice/Voice.js +1044 -0
  111. package/dist/services/task/voice/Voice.js.map +1 -0
  112. package/dist/services/task/voice/WebRTC.js +149 -0
  113. package/dist/services/task/voice/WebRTC.js.map +1 -0
  114. package/dist/types/cc.d.ts +894 -0
  115. package/dist/types/config.d.ts +72 -0
  116. package/dist/types/constants.d.ts +66 -0
  117. package/dist/types/index.d.ts +199 -0
  118. package/dist/types/logger-proxy.d.ts +71 -0
  119. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  120. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  121. package/dist/types/metrics/constants.d.ts +181 -0
  122. package/dist/types/services/AddressBook.d.ts +74 -0
  123. package/dist/types/services/ApiAiAssistant.d.ts +49 -0
  124. package/dist/types/services/EntryPoint.d.ts +67 -0
  125. package/dist/types/services/Queue.d.ts +76 -0
  126. package/dist/types/services/UserPreference.d.ts +118 -0
  127. package/dist/types/services/WebCallingService.d.ts +1 -0
  128. package/dist/types/services/agent/index.d.ts +46 -0
  129. package/dist/types/services/agent/types.d.ts +413 -0
  130. package/dist/types/services/config/Util.d.ts +20 -0
  131. package/dist/types/services/config/constants.d.ts +270 -0
  132. package/dist/types/services/config/index.d.ts +177 -0
  133. package/dist/types/services/config/types.d.ts +1368 -0
  134. package/dist/types/services/constants.d.ts +110 -0
  135. package/dist/types/services/core/Err.d.ts +125 -0
  136. package/dist/types/services/core/GlobalTypes.d.ts +58 -0
  137. package/dist/types/services/core/Utils.d.ts +121 -0
  138. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  139. package/dist/types/services/core/aqm-reqs.d.ts +65 -0
  140. package/dist/types/services/core/constants.d.ts +99 -0
  141. package/dist/types/services/core/types.d.ts +47 -0
  142. package/dist/types/services/core/websocket/WebSocketManager.d.ts +36 -0
  143. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  144. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  145. package/dist/types/services/core/websocket/types.d.ts +37 -0
  146. package/dist/types/services/index.d.ts +54 -0
  147. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  148. package/dist/types/services/task/Task.d.ts +157 -0
  149. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  150. package/dist/types/services/task/TaskManager.d.ts +1 -0
  151. package/dist/types/services/task/TaskUtils.d.ts +138 -0
  152. package/dist/types/services/task/constants.d.ts +91 -0
  153. package/dist/types/services/task/contact.d.ts +69 -0
  154. package/dist/types/services/task/dialer.d.ts +73 -0
  155. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  156. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1194 -0
  157. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  158. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  159. package/dist/types/services/task/state-machine/guards.d.ts +102 -0
  160. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  161. package/dist/types/services/task/state-machine/types.d.ts +269 -0
  162. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  163. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  164. package/dist/types/services/task/types.d.ts +1856 -0
  165. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  166. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  167. package/dist/types/types.d.ts +778 -0
  168. package/dist/types/utils/PageCache.d.ts +173 -0
  169. package/dist/types/webex-config.d.ts +53 -0
  170. package/dist/types/webex.d.ts +8 -0
  171. package/dist/types.js +130 -1
  172. package/dist/types.js.map +1 -1
  173. package/dist/webex.js +14 -2
  174. package/dist/webex.js.map +1 -1
  175. package/package.json +16 -12
  176. package/src/cc.ts +477 -51
  177. package/src/config.ts +6 -0
  178. package/src/constants.ts +21 -1
  179. package/src/index.ts +24 -5
  180. package/src/metrics/ai-docs/AGENTS.md +350 -0
  181. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  182. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  183. package/src/metrics/behavioral-events.ts +120 -0
  184. package/src/metrics/constants.ts +37 -3
  185. package/src/services/ApiAiAssistant.ts +412 -0
  186. package/src/services/UserPreference.ts +509 -0
  187. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  188. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  189. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  190. package/src/services/agent/types.ts +1 -1
  191. package/src/services/ai-docs/AGENTS.md +386 -0
  192. package/src/services/ai-docs/services-spec.md +492 -0
  193. package/src/services/config/Util.ts +10 -2
  194. package/src/services/config/ai-docs/AGENTS.md +255 -0
  195. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  196. package/src/services/config/ai-docs/config-spec.md +669 -0
  197. package/src/services/config/constants.ts +37 -1
  198. package/src/services/config/index.ts +45 -1
  199. package/src/services/config/types.ts +241 -11
  200. package/src/services/constants.ts +29 -0
  201. package/src/services/core/Err.ts +3 -0
  202. package/src/services/core/Utils.ts +143 -30
  203. package/src/services/core/ai-docs/AGENTS.md +381 -0
  204. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  205. package/src/services/core/ai-docs/core-spec.md +783 -0
  206. package/src/services/core/aqm-reqs.ts +100 -22
  207. package/src/services/core/websocket/WebSocketManager.ts +23 -6
  208. package/src/services/core/websocket/connection-service.ts +5 -1
  209. package/src/services/core/websocket/types.ts +1 -1
  210. package/src/services/index.ts +4 -0
  211. package/src/services/task/Task.ts +837 -0
  212. package/src/services/task/TaskFactory.ts +55 -0
  213. package/src/services/task/TaskManager.ts +793 -521
  214. package/src/services/task/TaskUtils.ts +314 -24
  215. package/src/services/task/ai-docs/AGENTS.md +457 -0
  216. package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
  217. package/src/services/task/ai-docs/task-spec.md +1319 -0
  218. package/src/services/task/constants.ts +23 -0
  219. package/src/services/task/dialer.ts +136 -1
  220. package/src/services/task/digital/Digital.ts +95 -0
  221. package/src/services/task/state-machine/TaskStateMachine.ts +1166 -0
  222. package/src/services/task/state-machine/actions.ts +738 -0
  223. package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
  224. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
  225. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2177 -0
  226. package/src/services/task/state-machine/constants.ts +172 -0
  227. package/src/services/task/state-machine/guards.ts +445 -0
  228. package/src/services/task/state-machine/index.ts +28 -0
  229. package/src/services/task/state-machine/types.ts +243 -0
  230. package/src/services/task/state-machine/uiControlsComputer.ts +961 -0
  231. package/src/services/task/taskDataNormalizer.ts +137 -0
  232. package/src/services/task/types.ts +734 -71
  233. package/src/services/task/voice/Voice.ts +1270 -0
  234. package/src/services/task/voice/WebRTC.ts +187 -0
  235. package/src/types.ts +205 -2
  236. package/src/utils/AGENTS.md +278 -0
  237. package/src/utils/ai-docs/utils-spec.md +381 -0
  238. package/src/webex.js +2 -0
  239. package/test/unit/spec/cc.ts +503 -43
  240. package/test/unit/spec/logger-proxy.ts +70 -0
  241. package/test/unit/spec/services/ApiAiAssistant.ts +273 -0
  242. package/test/unit/spec/services/UserPreference.ts +401 -0
  243. package/test/unit/spec/services/WebCallingService.ts +7 -1
  244. package/test/unit/spec/services/config/index.ts +85 -29
  245. package/test/unit/spec/services/core/Utils.ts +481 -2
  246. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +137 -41
  247. package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
  248. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  249. package/test/unit/spec/services/task/Task.ts +477 -0
  250. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  251. package/test/unit/spec/services/task/TaskManager.ts +1001 -1003
  252. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  253. package/test/unit/spec/services/task/dialer.ts +372 -96
  254. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  255. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +2651 -0
  256. package/test/unit/spec/services/task/state-machine/guards.ts +637 -0
  257. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  258. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2663 -0
  259. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  260. package/test/unit/spec/services/task/voice/Voice.ts +649 -0
  261. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  262. package/umd/contact-center.min.js +2 -2
  263. package/umd/contact-center.min.js.map +1 -1
  264. package/dist/services/task/index.js +0 -1525
  265. package/dist/services/task/index.js.map +0 -1
  266. package/src/services/task/index.ts +0 -1801
  267. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -1,7 +1,160 @@
1
1
  /* eslint-disable import/prefer-default-export */
2
2
  import {Interaction, ITask, TaskData, MEDIA_CHANNEL} from './types';
3
- import {OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE, OUTBOUND_TYPE} from '../../constants';
4
3
  import {LoginOption} from '../../types';
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';
11
+ import {TaskContext} from './state-machine/types';
12
+ import {CC_EVENTS} from '../config/types';
13
+ import {OUTBOUND_TYPE, OUTDIAL_DIRECTION, OUTDIAL_MEDIA_TYPE} from '../../constants';
14
+
15
+ const CAMPAIGN_PREVIEW_OUTBOUND_TYPES = ['STANDARD_PREVIEW_CAMPAIGN', 'DIRECT_PREVIEW_CAMPAIGN'];
16
+ const CAMPAIGN_PREVIEW_CAMPAIGN_TYPES = ['preview_standard', 'preview_direct'];
17
+
18
+ /**
19
+ * Checks if the customer is still in the call (not left)
20
+ *
21
+ * @param interaction - The interaction object
22
+ * @param interactionId - The main interaction ID
23
+ * @returns true if customer is in the call
24
+ */
25
+ export const getIsCustomerInCall = (interaction: Interaction, interactionId: string): boolean => {
26
+ const mainCallMedia = interaction.media?.[interactionId];
27
+ const participants = interaction.participants;
28
+ if (!mainCallMedia?.participants || !participants) {
29
+ return false;
30
+ }
31
+
32
+ return mainCallMedia.participants.some((participantId: string) => {
33
+ const participant = participants[participantId];
34
+
35
+ return participant?.pType === PARTICIPANT_TYPE.CUSTOMER && !participant.hasLeft;
36
+ });
37
+ };
38
+
39
+ /**
40
+ * Gets the count of active agent participants in the conference
41
+ * Excludes Customer, Supervisor, and VVA participant types
42
+ *
43
+ * @param interaction - The interaction object
44
+ * @param interactionId - The main interaction ID
45
+ * @returns Number of active agent participants
46
+ */
47
+ export const getConferenceParticipantsCount = (
48
+ interaction: Interaction,
49
+ interactionId: string
50
+ ): number => {
51
+ const mainCallMedia = interaction.media?.[interactionId];
52
+ const participants = interaction.participants;
53
+ if (!mainCallMedia?.participants || !participants) {
54
+ return 0;
55
+ }
56
+
57
+ let count = 0;
58
+ for (const participantId of mainCallMedia.participants) {
59
+ const participant = participants[participantId];
60
+ if (
61
+ participant &&
62
+ participant.pType !== PARTICIPANT_TYPE.CUSTOMER &&
63
+ participant.pType !== PARTICIPANT_TYPE.SUPERVISOR &&
64
+ participant.pType !== PARTICIPANT_TYPE.VVA &&
65
+ !participant.hasLeft
66
+ ) {
67
+ count += 1;
68
+ }
69
+ }
70
+
71
+ return count;
72
+ };
73
+
74
+ /**
75
+ * Determines if a consult is actively in-progress for conference control gating.
76
+ * This is used to disable conference controls (End/Consult) only when a consult leg
77
+ * still exists outside the main call participants.
78
+ */
79
+ export const getIsConsultInProgressForConferenceControls = (
80
+ interaction: Interaction | undefined,
81
+ mainCallId: string | undefined,
82
+ selfAgentId: string | undefined
83
+ ): boolean => {
84
+ if (!interaction || !mainCallId) return false;
85
+
86
+ const mainParticipants = interaction.media?.[mainCallId]?.participants;
87
+ if (!Array.isArray(mainParticipants) || mainParticipants.length === 0) return false;
88
+
89
+ const mainSet = new Set(mainParticipants);
90
+ const media = interaction.media;
91
+ if (!media) return false;
92
+
93
+ return Object.values(media).some((m: any) => {
94
+ if (!m || m.mType !== 'consult') return false;
95
+ if (!Array.isArray(m.participants) || m.participants.length === 0) return false;
96
+
97
+ return m.participants.some((participantId: string) => {
98
+ const p: any = interaction.participants?.[participantId];
99
+ if (!p || p.hasLeft) return false;
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
+ }
120
+
121
+ const consultState = p.consultState as string | undefined;
122
+ const isRonaPendingConsultee = consultState === 'consultReserved' && p.hasJoined === false;
123
+ const consultLegActive =
124
+ consultState === 'consulting' ||
125
+ p.currentState === 'consulting' ||
126
+ (p.isConsulted === true && consultState !== 'consultCompleted' && !isRonaPendingConsultee);
127
+
128
+ return consultLegActive;
129
+ });
130
+ });
131
+ };
132
+
133
+ export const getIsConsultedAgentForControls = (
134
+ taskData: TaskData | null,
135
+ context: TaskContext,
136
+ isConsultingState: boolean
137
+ ): boolean => {
138
+ return Boolean(taskData?.isConsulted) || (isConsultingState && !context.consultInitiator);
139
+ };
140
+
141
+ export const getServerHoldStateForControls = (
142
+ context: TaskContext,
143
+ mainCallId?: string,
144
+ fallbackTaskData?: TaskData | null
145
+ ): boolean | undefined => {
146
+ const media = context.taskData?.interaction?.media ?? fallbackTaskData?.interaction?.media;
147
+ if (!media) return undefined;
148
+
149
+ if (mainCallId && media[mainCallId]) {
150
+ return media[mainCallId].isHold ?? false;
151
+ }
152
+
153
+ const mediaId = context.taskData?.mediaResourceId ?? fallbackTaskData?.mediaResourceId;
154
+ if (!mediaId) return undefined;
155
+
156
+ return media[mediaId]?.isHold;
157
+ };
5
158
 
6
159
  /**
7
160
  * Determines if the given agent is the primary agent (owner) of the task
@@ -31,7 +184,9 @@ export const isParticipantInMainInteraction = (task: ITask, agentId: string): bo
31
184
 
32
185
  return Object.values(task.data.interaction.media).some(
33
186
  (mediaObj) =>
34
- mediaObj && mediaObj.mType === 'mainCall' && mediaObj.participants?.includes(agentId)
187
+ mediaObj &&
188
+ mediaObj.mType === MEDIA_TYPE_MAIN_CALL &&
189
+ mediaObj.participants?.includes(agentId)
35
190
  );
36
191
  };
37
192
 
@@ -55,33 +210,110 @@ export const checkParticipantNotInInteraction = (task: ITask, agentId: string):
55
210
 
56
211
  /**
57
212
  * Determines if a conference is currently in progress based on the number of active agent participants
58
- * @param TaskData - The payLoad data to check for conference status
213
+ * @param data - The task data to check for conference status
59
214
  * @returns true if there are 2 or more active agent participants in the main call, false otherwise
215
+ *
216
+ * For Agent B (consulted agent), their task's interactionId may be different from the main call.
217
+ * We use mainInteractionId from the interaction if available, otherwise fallback to interactionId.
60
218
  */
61
219
  export const getIsConferenceInProgress = (data: TaskData): boolean => {
62
- const mediaMainCall = data.interaction.media?.[data?.interactionId];
220
+ if (!data.interaction) return false;
221
+
222
+ const mainCallId = data.interaction.mainInteractionId || data.interactionId;
223
+ const mediaMainCall = data.interaction.media?.[mainCallId];
63
224
  const participantsInMainCall = new Set(mediaMainCall?.participants);
64
- const participants = data.interaction.participants;
225
+ const {participants} = data.interaction;
65
226
 
66
227
  const agentParticipants = new Set();
67
- if (participantsInMainCall.size > 0) {
68
- participantsInMainCall.forEach((participantId: string) => {
69
- const participant = participants?.[participantId];
70
- if (
71
- participant &&
72
- participant.pType !== 'Customer' &&
73
- participant.pType !== 'Supervisor' &&
74
- !participant.hasLeft &&
75
- participant.pType !== 'VVA'
76
- ) {
77
- agentParticipants.add(participantId);
78
- }
79
- });
80
- }
228
+ participantsInMainCall.forEach((participantId: string) => {
229
+ const participant = participants[participantId];
230
+ if (
231
+ participant &&
232
+ participant.pType !== PARTICIPANT_TYPE.CUSTOMER &&
233
+ participant.pType !== PARTICIPANT_TYPE.SUPERVISOR &&
234
+ participant.pType !== PARTICIPANT_TYPE.VVA &&
235
+ !participant.hasLeft
236
+ ) {
237
+ agentParticipants.add(participantId);
238
+ }
239
+ });
81
240
 
82
241
  return agentParticipants.size >= 2;
83
242
  };
84
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
+
85
317
  /**
86
318
  * Checks if the current agent is a secondary agent in a consultation scenario.
87
319
  * Secondary agents are those who were consulted (not the original call owner).
@@ -103,17 +335,30 @@ export const isSecondaryAgent = (interaction: Interaction): boolean => {
103
335
  /**
104
336
  * Checks if the current agent is a secondary EP-DN (Entry Point Dial Number) agent.
105
337
  * This is specifically for telephony consultations to external numbers/entry points.
106
- * @param task - The task object containing interaction details
338
+ * @param interaction - The interaction object
107
339
  * @returns true if this is a secondary EP-DN agent in telephony consultation, false otherwise
108
340
  */
109
341
  export const isSecondaryEpDnAgent = (interaction: Interaction): boolean => {
110
- if (!interaction) {
111
- return false;
112
- }
113
-
114
342
  return interaction.mediaType === 'telephony' && isSecondaryAgent(interaction);
115
343
  };
116
344
 
345
+ /**
346
+ * Checks if the task belongs to a campaign preview interaction.
347
+ * Campaign preview ContactEnded events are terminal cleanup events and should not trigger wrapup.
348
+ */
349
+ export const isCampaignPreviewTask = (taskData?: TaskData | null): boolean => {
350
+ const outboundType = taskData?.interaction?.outboundType ?? '';
351
+ const cpd = taskData?.interaction?.callProcessingDetails as unknown as
352
+ | Record<string, string | undefined>
353
+ | undefined;
354
+ const campaignType = cpd?.campaignType ?? '';
355
+
356
+ return (
357
+ CAMPAIGN_PREVIEW_OUTBOUND_TYPES.includes(outboundType) ||
358
+ CAMPAIGN_PREVIEW_CAMPAIGN_TYPES.includes(campaignType)
359
+ );
360
+ };
361
+
117
362
  /**
118
363
  * Checks if auto-answer is enabled for the agent participant
119
364
  * @param interaction - The interaction object
@@ -219,3 +464,48 @@ export const shouldAutoAnswerTask = (
219
464
 
220
465
  return false;
221
466
  };
467
+
468
+ /**
469
+ * Gets the consult media resource ID for switch-call operations.
470
+ * Searches for the consult media leg in the interaction.
471
+ *
472
+ * @param interaction - The interaction object
473
+ * @param consultMediaResourceId - The consult media resource ID from task data
474
+ * @param agentId - Current agent ID
475
+ * @returns The consult media resource ID or undefined
476
+ */
477
+ export const getConsultMediaResourceId = (
478
+ interaction: Interaction | undefined,
479
+ consultMediaResourceId: string | undefined,
480
+ agentId: string | undefined
481
+ ): string | undefined => {
482
+ // First priority: use consultMediaResourceId from task data if available
483
+ if (consultMediaResourceId) {
484
+ return consultMediaResourceId;
485
+ }
486
+
487
+ // Second priority: search for consult media leg in interaction.media
488
+ if (!interaction?.media || !agentId) {
489
+ return undefined;
490
+ }
491
+
492
+ // Find the consult media leg where this agent is a participant
493
+ for (const [mediaId, media] of Object.entries(interaction.media)) {
494
+ if (media.mType === 'consult' && media.participants?.includes(agentId)) {
495
+ return mediaId;
496
+ }
497
+ }
498
+
499
+ return undefined;
500
+ };
501
+
502
+ /**
503
+ * Checks if a task is a campaign preview reservation that has not yet been accepted.
504
+ * Campaign preview tasks should not trigger incoming call handling until the agent
505
+ * explicitly accepts the preview contact.
506
+ * @param task - The task to check
507
+ * @returns true if the task is a pending campaign preview reservation, false otherwise
508
+ */
509
+ export const isCampaignPreviewReservation = (task: ITask): boolean => {
510
+ return task?.data?.type === CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION;
511
+ };