@webex/contact-center 3.12.0-next.9 → 3.12.0-task-refactor.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 (200) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +65 -123
  24. package/dist/cc.js.map +1 -1
  25. package/dist/constants.js +13 -2
  26. package/dist/constants.js.map +1 -1
  27. package/dist/index.js +13 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/metrics/behavioral-events.js +26 -13
  30. package/dist/metrics/behavioral-events.js.map +1 -1
  31. package/dist/metrics/constants.js +7 -6
  32. package/dist/metrics/constants.js.map +1 -1
  33. package/dist/services/ApiAiAssistant.js +0 -3
  34. package/dist/services/ApiAiAssistant.js.map +1 -1
  35. package/dist/services/config/Util.js +2 -3
  36. package/dist/services/config/Util.js.map +1 -1
  37. package/dist/services/config/types.js +16 -14
  38. package/dist/services/config/types.js.map +1 -1
  39. package/dist/services/constants.js +0 -1
  40. package/dist/services/constants.js.map +1 -1
  41. package/dist/services/core/Err.js.map +1 -1
  42. package/dist/services/core/Utils.js +79 -55
  43. package/dist/services/core/Utils.js.map +1 -1
  44. package/dist/services/core/aqm-reqs.js +17 -92
  45. package/dist/services/core/aqm-reqs.js.map +1 -1
  46. package/dist/services/core/websocket/WebSocketManager.js +5 -25
  47. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  48. package/dist/services/core/websocket/types.js.map +1 -1
  49. package/dist/services/index.js +1 -2
  50. package/dist/services/index.js.map +1 -1
  51. package/dist/services/task/Task.js +644 -0
  52. package/dist/services/task/Task.js.map +1 -0
  53. package/dist/services/task/TaskFactory.js +45 -0
  54. package/dist/services/task/TaskFactory.js.map +1 -0
  55. package/dist/services/task/TaskManager.js +570 -535
  56. package/dist/services/task/TaskManager.js.map +1 -1
  57. package/dist/services/task/TaskUtils.js +132 -28
  58. package/dist/services/task/TaskUtils.js.map +1 -1
  59. package/dist/services/task/constants.js +7 -6
  60. package/dist/services/task/constants.js.map +1 -1
  61. package/dist/services/task/dialer.js +0 -51
  62. package/dist/services/task/dialer.js.map +1 -1
  63. package/dist/services/task/digital/Digital.js +77 -0
  64. package/dist/services/task/digital/Digital.js.map +1 -0
  65. package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
  66. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  67. package/dist/services/task/state-machine/actions.js +372 -0
  68. package/dist/services/task/state-machine/actions.js.map +1 -0
  69. package/dist/services/task/state-machine/constants.js +139 -0
  70. package/dist/services/task/state-machine/constants.js.map +1 -0
  71. package/dist/services/task/state-machine/guards.js +263 -0
  72. package/dist/services/task/state-machine/guards.js.map +1 -0
  73. package/dist/services/task/state-machine/index.js +53 -0
  74. package/dist/services/task/state-machine/index.js.map +1 -0
  75. package/dist/services/task/state-machine/types.js +54 -0
  76. package/dist/services/task/state-machine/types.js.map +1 -0
  77. package/dist/services/task/state-machine/uiControlsComputer.js +377 -0
  78. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  79. package/dist/services/task/taskDataNormalizer.js +99 -0
  80. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  81. package/dist/services/task/types.js +157 -18
  82. package/dist/services/task/types.js.map +1 -1
  83. package/dist/services/task/voice/Voice.js +1031 -0
  84. package/dist/services/task/voice/Voice.js.map +1 -0
  85. package/dist/services/task/voice/WebRTC.js +149 -0
  86. package/dist/services/task/voice/WebRTC.js.map +1 -0
  87. package/dist/types/cc.d.ts +4 -33
  88. package/dist/types/constants.d.ts +13 -2
  89. package/dist/types/index.d.ts +11 -5
  90. package/dist/types/metrics/constants.d.ts +5 -3
  91. package/dist/types/services/ApiAiAssistant.d.ts +1 -1
  92. package/dist/types/services/config/types.d.ts +97 -25
  93. package/dist/types/services/core/Err.d.ts +0 -2
  94. package/dist/types/services/core/Utils.d.ts +25 -23
  95. package/dist/types/services/core/aqm-reqs.d.ts +0 -49
  96. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
  97. package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
  98. package/dist/types/services/core/websocket/types.d.ts +1 -1
  99. package/dist/types/services/index.d.ts +1 -1
  100. package/dist/types/services/task/Task.d.ts +146 -0
  101. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  102. package/dist/types/services/task/TaskUtils.d.ts +39 -8
  103. package/dist/types/services/task/constants.d.ts +5 -4
  104. package/dist/types/services/task/dialer.d.ts +0 -15
  105. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  106. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
  107. package/dist/types/services/task/state-machine/actions.d.ts +8 -0
  108. package/dist/types/services/task/state-machine/constants.d.ts +91 -0
  109. package/dist/types/services/task/state-machine/guards.d.ts +78 -0
  110. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  111. package/dist/types/services/task/state-machine/types.d.ts +256 -0
  112. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  113. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  114. package/dist/types/services/task/types.d.ts +539 -88
  115. package/dist/types/services/task/voice/Voice.d.ts +183 -0
  116. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  117. package/dist/types/types.d.ts +68 -0
  118. package/dist/types/webex.d.ts +1 -0
  119. package/dist/types.js +70 -0
  120. package/dist/types.js.map +1 -1
  121. package/dist/webex.js +14 -2
  122. package/dist/webex.js.map +1 -1
  123. package/package.json +14 -11
  124. package/src/cc.ts +91 -177
  125. package/src/constants.ts +13 -2
  126. package/src/index.ts +14 -5
  127. package/src/metrics/ai-docs/AGENTS.md +348 -0
  128. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  129. package/src/metrics/behavioral-events.ts +28 -14
  130. package/src/metrics/constants.ts +7 -8
  131. package/src/services/ApiAiAssistant.ts +2 -4
  132. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  133. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  134. package/src/services/ai-docs/AGENTS.md +384 -0
  135. package/src/services/config/Util.ts +2 -3
  136. package/src/services/config/ai-docs/AGENTS.md +253 -0
  137. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  138. package/src/services/config/types.ts +108 -20
  139. package/src/services/constants.ts +0 -1
  140. package/src/services/core/Err.ts +0 -1
  141. package/src/services/core/Utils.ts +90 -67
  142. package/src/services/core/ai-docs/AGENTS.md +379 -0
  143. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  144. package/src/services/core/aqm-reqs.ts +22 -100
  145. package/src/services/core/websocket/WebSocketManager.ts +4 -23
  146. package/src/services/core/websocket/types.ts +1 -1
  147. package/src/services/index.ts +1 -2
  148. package/src/services/task/Task.ts +785 -0
  149. package/src/services/task/TaskFactory.ts +55 -0
  150. package/src/services/task/TaskManager.ts +579 -633
  151. package/src/services/task/TaskUtils.ts +175 -31
  152. package/src/services/task/ai-docs/AGENTS.md +448 -0
  153. package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
  154. package/src/services/task/constants.ts +5 -4
  155. package/src/services/task/dialer.ts +1 -56
  156. package/src/services/task/digital/Digital.ts +95 -0
  157. package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
  158. package/src/services/task/state-machine/actions.ts +422 -0
  159. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  160. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  161. package/src/services/task/state-machine/constants.ts +150 -0
  162. package/src/services/task/state-machine/guards.ts +303 -0
  163. package/src/services/task/state-machine/index.ts +28 -0
  164. package/src/services/task/state-machine/types.ts +228 -0
  165. package/src/services/task/state-machine/uiControlsComputer.ts +542 -0
  166. package/src/services/task/taskDataNormalizer.ts +137 -0
  167. package/src/services/task/types.ts +641 -95
  168. package/src/services/task/voice/Voice.ts +1255 -0
  169. package/src/services/task/voice/WebRTC.ts +187 -0
  170. package/src/types.ts +88 -5
  171. package/src/utils/AGENTS.md +276 -0
  172. package/src/webex.js +2 -0
  173. package/test/unit/spec/cc.ts +59 -142
  174. package/test/unit/spec/logger-proxy.ts +70 -0
  175. package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
  176. package/test/unit/spec/services/config/index.ts +26 -55
  177. package/test/unit/spec/services/core/Utils.ts +103 -52
  178. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
  179. package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
  180. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  181. package/test/unit/spec/services/task/Task.ts +416 -0
  182. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  183. package/test/unit/spec/services/task/TaskManager.ts +781 -1735
  184. package/test/unit/spec/services/task/TaskUtils.ts +125 -0
  185. package/test/unit/spec/services/task/dialer.ts +112 -198
  186. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  187. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
  188. package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
  189. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  190. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
  191. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  192. package/test/unit/spec/services/task/voice/Voice.ts +587 -0
  193. package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
  194. package/umd/contact-center.min.js +2 -2
  195. package/umd/contact-center.min.js.map +1 -1
  196. package/dist/services/task/index.js +0 -1525
  197. package/dist/services/task/index.js.map +0 -1
  198. package/dist/types/services/task/index.d.ts +0 -650
  199. package/src/services/task/index.ts +0 -1801
  200. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,288 @@
1
+ import {guards, GuardParams} from '../../../../../../src/services/task/state-machine/guards';
2
+ import {
3
+ TaskContext,
4
+ TaskEventPayload,
5
+ } from '../../../../../../src/services/task/state-machine/types';
6
+ import {TaskData, InteractionParticipant} from '../../../../../../src/services/task/types';
7
+
8
+ describe('State Machine Guards', () => {
9
+ const createParticipant = (
10
+ id: string,
11
+ pType: string,
12
+ hasLeft = false
13
+ ): InteractionParticipant => ({
14
+ id,
15
+ pType,
16
+ type: pType,
17
+ hasJoined: true,
18
+ hasLeft,
19
+ isInPredial: false,
20
+ });
21
+ // Note: media key must match mainInteractionId for getIsConferenceInProgress to work
22
+ const INTERACTION_ID = 'interaction-123';
23
+
24
+ const createTaskData = (overrides: Partial<TaskData> = {}): TaskData =>
25
+ ({
26
+ mediaResourceId: INTERACTION_ID,
27
+ eventType: 'TEST_EVENT',
28
+ agentId: 'agent-123',
29
+ destAgentId: '',
30
+ trackingId: 'track-123',
31
+ consultMediaResourceId: '',
32
+ interactionId: INTERACTION_ID,
33
+ orgId: 'org-123',
34
+ owner: 'agent-123',
35
+ queueMgr: 'queue-mgr',
36
+ type: 'voice',
37
+ isConferencing: false,
38
+ interaction: {
39
+ mainInteractionId: INTERACTION_ID,
40
+ interactionId: INTERACTION_ID,
41
+ participants: {},
42
+ media: {
43
+ [INTERACTION_ID]: {
44
+ mediaResourceId: INTERACTION_ID,
45
+ mediaType: 'telephony',
46
+ mediaMgr: 'media-mgr',
47
+ participants: ['agent-123'],
48
+ mType: 'mainCall',
49
+ isHold: false,
50
+ holdTimestamp: null,
51
+ },
52
+ },
53
+ owner: 'agent-123',
54
+ mediaChannel: 'telephony',
55
+ contactDirection: {type: 'inbound'},
56
+ },
57
+ ...overrides,
58
+ } as TaskData);
59
+
60
+ const createContext = (overrides: Partial<TaskContext> = {}): TaskContext =>
61
+ ({
62
+ recordingControlsAvailable: false,
63
+ recordingInProgress: false,
64
+ consultDestinationAgentJoined: false,
65
+ consultCallHeld: false,
66
+ consultInitiator: false,
67
+ exitingConference: false,
68
+ consultDestinationType: null,
69
+ taskData: createTaskData(),
70
+ uiControlConfig: {agentId: 'agent-123'},
71
+ uiControls: {
72
+ main: {
73
+ accept: {isVisible: false, isEnabled: false},
74
+ decline: {isVisible: false, isEnabled: false},
75
+ hold: {isVisible: false, isEnabled: false},
76
+ end: {isVisible: false, isEnabled: false},
77
+ transfer: {isVisible: false, isEnabled: false},
78
+ consult: {isVisible: false, isEnabled: false},
79
+ consultTransfer: {isVisible: false, isEnabled: false},
80
+ endConsult: {isVisible: false, isEnabled: false},
81
+ recording: {isVisible: false, isEnabled: false},
82
+ conference: {isVisible: false, isEnabled: false},
83
+ wrapup: {isVisible: false, isEnabled: false},
84
+ exitConference: {isVisible: false, isEnabled: false},
85
+ transferConference: {isVisible: false, isEnabled: false},
86
+ mergeToConference: {isVisible: false, isEnabled: false},
87
+ switch: {isVisible: false, isEnabled: false},
88
+ mute: {isVisible: false, isEnabled: false},
89
+ },
90
+ consult: {
91
+ accept: {isVisible: false, isEnabled: false},
92
+ decline: {isVisible: false, isEnabled: false},
93
+ hold: {isVisible: false, isEnabled: false},
94
+ end: {isVisible: false, isEnabled: false},
95
+ transfer: {isVisible: false, isEnabled: false},
96
+ consult: {isVisible: false, isEnabled: false},
97
+ consultTransfer: {isVisible: false, isEnabled: false},
98
+ endConsult: {isVisible: false, isEnabled: false},
99
+ recording: {isVisible: false, isEnabled: false},
100
+ conference: {isVisible: false, isEnabled: false},
101
+ wrapup: {isVisible: false, isEnabled: false},
102
+ exitConference: {isVisible: false, isEnabled: false},
103
+ transferConference: {isVisible: false, isEnabled: false},
104
+ mergeToConference: {isVisible: false, isEnabled: false},
105
+ switch: {isVisible: false, isEnabled: false},
106
+ mute: {isVisible: false, isEnabled: false},
107
+ },
108
+ activeLeg: 'main',
109
+ },
110
+ ...overrides,
111
+ } as TaskContext);
112
+
113
+ const createParams = (context: TaskContext, event?: TaskEventPayload): GuardParams => ({
114
+ context,
115
+ event,
116
+ });
117
+
118
+ const createEventWithTaskData = (taskData: TaskData): TaskEventPayload =>
119
+ ({taskData} as TaskEventPayload);
120
+
121
+ describe('Conference Guards', () => {
122
+ it('conferenceInProgressFromEvent returns true with 2+ agents in event', () => {
123
+ const ctx = createContext();
124
+ const taskData = createTaskData({
125
+ interaction: {
126
+ ...createTaskData().interaction,
127
+ participants: {
128
+ a1: createParticipant('a1', 'Agent'),
129
+ a2: createParticipant('a2', 'Agent'),
130
+ },
131
+ media: {
132
+ [INTERACTION_ID]: {
133
+ mediaResourceId: INTERACTION_ID,
134
+ mediaType: 'telephony',
135
+ mediaMgr: 'media-mgr',
136
+ participants: ['a1', 'a2'],
137
+ mType: 'mainCall',
138
+ isHold: false,
139
+ holdTimestamp: null,
140
+ },
141
+ },
142
+ },
143
+ });
144
+ expect(
145
+ guards.conferenceInProgressFromEvent(createParams(ctx, createEventWithTaskData(taskData)))
146
+ ).toBe(true);
147
+ });
148
+
149
+ it('conferenceInProgressFromEvent returns false with 1 agent', () => {
150
+ const ctx = createContext();
151
+ const taskData = createTaskData({
152
+ interaction: {
153
+ ...createTaskData().interaction,
154
+ participants: {
155
+ a1: createParticipant('a1', 'Agent'),
156
+ },
157
+ media: {
158
+ [INTERACTION_ID]: {
159
+ mediaResourceId: INTERACTION_ID,
160
+ mediaType: 'telephony',
161
+ mediaMgr: 'media-mgr',
162
+ participants: ['a1'],
163
+ mType: 'mainCall',
164
+ isHold: false,
165
+ holdTimestamp: null,
166
+ },
167
+ },
168
+ },
169
+ });
170
+ expect(
171
+ guards.conferenceInProgressFromEvent(createParams(ctx, createEventWithTaskData(taskData)))
172
+ ).toBe(false);
173
+ });
174
+ });
175
+
176
+ describe('Consult Guards', () => {
177
+ it('didInitiateConsult returns true when consultingAgentId matches', () => {
178
+ const ctx = createContext();
179
+ const taskData = createTaskData({
180
+ consultingAgentId: 'agent-123',
181
+ isConsulted: false,
182
+ });
183
+ expect(guards.didInitiateConsult(createParams(ctx, createEventWithTaskData(taskData)))).toBe(
184
+ true
185
+ );
186
+ });
187
+
188
+ it('didInitiateConsult returns false when isConsulted is true', () => {
189
+ const ctx = createContext();
190
+ const taskData = createTaskData({isConsulted: true});
191
+ expect(guards.didInitiateConsult(createParams(ctx, createEventWithTaskData(taskData)))).toBe(
192
+ false
193
+ );
194
+ });
195
+ });
196
+
197
+ describe('Wrap-up Guards', () => {
198
+ it('shouldWrapUp returns true for owner', () => {
199
+ const ctx = createContext();
200
+ const taskData = createTaskData({
201
+ interaction: {
202
+ ...createTaskData().interaction,
203
+ owner: 'agent-123',
204
+ },
205
+ });
206
+ expect(guards.shouldWrapUp(createParams(ctx, createEventWithTaskData(taskData)))).toBe(true);
207
+ });
208
+
209
+ it('shouldWrapUp returns false for non-owner', () => {
210
+ const ctx = createContext();
211
+ const taskData = createTaskData({
212
+ interaction: {
213
+ ...createTaskData().interaction,
214
+ owner: 'other-agent',
215
+ },
216
+ });
217
+ expect(guards.shouldWrapUp(createParams(ctx, createEventWithTaskData(taskData)))).toBe(false);
218
+ });
219
+
220
+ it('shouldWrapUp returns true when agentsPendingWrapUp includes agent', () => {
221
+ const ctx = createContext();
222
+ const taskData = createTaskData({
223
+ agentsPendingWrapUp: ['agent-123', 'other-agent'],
224
+ } as Partial<TaskData>);
225
+ expect(guards.shouldWrapUp(createParams(ctx, createEventWithTaskData(taskData)))).toBe(true);
226
+ });
227
+
228
+ it('shouldWrapUp uses isConsulted fallback when no owner', () => {
229
+ const taskData = createTaskData({
230
+ isConsulted: false,
231
+ interaction: {
232
+ ...createTaskData().interaction,
233
+ owner: undefined as unknown as string, // No owner
234
+ },
235
+ });
236
+ const ctx = createContext({taskData});
237
+ expect(guards.shouldWrapUp(createParams(ctx, createEventWithTaskData(taskData)))).toBe(true);
238
+ });
239
+ });
240
+
241
+ describe('Server State Guards', () => {
242
+ it('isPrimaryMediaOnHold returns true when media isHold is true', () => {
243
+ const ctx = createContext();
244
+ const taskData = createTaskData({
245
+ interaction: {
246
+ ...createTaskData().interaction,
247
+ media: {
248
+ [INTERACTION_ID]: {
249
+ mediaResourceId: INTERACTION_ID,
250
+ mediaType: 'telephony',
251
+ mediaMgr: 'media-mgr',
252
+ participants: ['agent-123'],
253
+ mType: 'mainCall',
254
+ isHold: true,
255
+ holdTimestamp: Date.now(),
256
+ },
257
+ },
258
+ },
259
+ });
260
+ expect(
261
+ guards.isPrimaryMediaOnHold(createParams(ctx, createEventWithTaskData(taskData)))
262
+ ).toBe(true);
263
+ });
264
+
265
+ it('isPrimaryMediaOnHold returns false when media isHold is false', () => {
266
+ const ctx = createContext();
267
+ const taskData = createTaskData({
268
+ interaction: {
269
+ ...createTaskData().interaction,
270
+ media: {
271
+ [INTERACTION_ID]: {
272
+ mediaResourceId: INTERACTION_ID,
273
+ mediaType: 'telephony',
274
+ mediaMgr: 'media-mgr',
275
+ participants: ['agent-123'],
276
+ mType: 'mainCall',
277
+ isHold: false,
278
+ holdTimestamp: null,
279
+ },
280
+ },
281
+ },
282
+ });
283
+ expect(
284
+ guards.isPrimaryMediaOnHold(createParams(ctx, createEventWithTaskData(taskData)))
285
+ ).toBe(false);
286
+ });
287
+ });
288
+ });
@@ -0,0 +1,18 @@
1
+ import {isEventOfType} from '../../../../../../src/services/task/state-machine/types';
2
+ import {TaskEvent} from '../../../../../../src/services/task/state-machine';
3
+
4
+ describe('state-machine types', () => {
5
+ describe('isEventOfType', () => {
6
+ it('returns true for matching type', () => {
7
+ const event = {type: TaskEvent.TASK_INCOMING, taskData: {interactionId: 'id'} as any};
8
+ expect(isEventOfType(event as any, TaskEvent.TASK_INCOMING)).toBe(true);
9
+ });
10
+
11
+ it('returns false for non-matching type or undefined', () => {
12
+ const event = {type: TaskEvent.TASK_INCOMING, taskData: {interactionId: 'id'} as any};
13
+ expect(isEventOfType(event as any, TaskEvent.ASSIGN)).toBe(false);
14
+ expect(isEventOfType(undefined, TaskEvent.ASSIGN)).toBe(false);
15
+ });
16
+ });
17
+ });
18
+
@@ -0,0 +1,147 @@
1
+ import {TASK_CHANNEL_TYPE} from '../../../../../../src/services/task/types';
2
+ import {TaskState} from '../../../../../../src/services/task/state-machine/constants';
3
+ import {
4
+ computeUIControls,
5
+ getDefaultUIControls,
6
+ } from '../../../../../../src/services/task/state-machine/uiControlsComputer';
7
+ import {TaskContext} from '../../../../../../src/services/task/state-machine/types';
8
+ import {createTaskData} from '../taskTestUtils';
9
+
10
+ function createConsultTaskData() {
11
+ return createTaskData({
12
+ agentId: 'agent-1',
13
+ mediaResourceId: 'interaction-1',
14
+ consultMediaResourceId: 'consult-media',
15
+ consultingAgentId: 'agent-1',
16
+ destAgentId: 'agent-2',
17
+ interaction: {
18
+ interactionId: 'interaction-1',
19
+ mainInteractionId: 'interaction-1',
20
+ participants: {
21
+ 'agent-1': {id: 'agent-1', pType: 'AGENT', hasLeft: false},
22
+ 'agent-2': {
23
+ id: 'agent-2',
24
+ pType: 'AGENT',
25
+ hasLeft: false,
26
+ consultState: 'consulting',
27
+ currentState: 'consulting',
28
+ isConsulted: true,
29
+ },
30
+ 'customer-1': {id: 'customer-1', pType: 'CUSTOMER', hasLeft: false},
31
+ } as any,
32
+ media: {
33
+ 'interaction-1': {
34
+ mediaResourceId: 'interaction-1',
35
+ isHold: false,
36
+ participants: ['agent-1', 'customer-1'],
37
+ },
38
+ 'consult-media': {
39
+ mediaResourceId: 'consult-media',
40
+ isHold: false,
41
+ mType: 'consult',
42
+ participants: ['agent-2'],
43
+ },
44
+ } as any,
45
+ } as any,
46
+ });
47
+ }
48
+
49
+ function createVoiceContext(overrides: Partial<TaskContext> = {}): TaskContext {
50
+ return {
51
+ taskData: createConsultTaskData(),
52
+ consultInitiator: true,
53
+ exitingConference: false,
54
+ consultFromConference: false,
55
+ transferConferenceRequested: false,
56
+ consultDestinationType: null,
57
+ consultDestinationAgentId: null,
58
+ consultDestinationAgentJoined: true,
59
+ consultCallHeld: false,
60
+ recordingControlsAvailable: true,
61
+ recordingInProgress: true,
62
+ uiControlConfig: {
63
+ isEndTaskEnabled: true,
64
+ isEndConsultEnabled: true,
65
+ channelType: TASK_CHANNEL_TYPE.VOICE,
66
+ isRecordingEnabled: true,
67
+ agentId: 'agent-1',
68
+ },
69
+ uiControls: getDefaultUIControls(),
70
+ ...overrides,
71
+ };
72
+ }
73
+
74
+ describe('uiControlsComputer consult initiator controls', () => {
75
+ it('returns separate main and consult controls when consult leg is active', () => {
76
+ const context = createVoiceContext();
77
+
78
+ const uiControls = computeUIControls(TaskState.CONSULTING, context, context.taskData);
79
+
80
+ expect(uiControls.activeLeg).toBe('consult');
81
+ expect(uiControls.consult).toBeDefined();
82
+
83
+ expect(uiControls.main.hold).toEqual({isVisible: false, isEnabled: false});
84
+ expect(uiControls.main.transfer).toEqual({isVisible: true, isEnabled: false});
85
+ expect(uiControls.main.conference).toEqual({isVisible: true, isEnabled: false});
86
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: false});
87
+
88
+ expect(uiControls.consult.hold).toEqual({isVisible: false, isEnabled: false});
89
+ expect(uiControls.consult.transfer).toEqual({isVisible: true, isEnabled: true});
90
+ expect(uiControls.consult.conference).toEqual({isVisible: true, isEnabled: true});
91
+ expect(uiControls.consult.endConsult).toEqual({isVisible: true, isEnabled: true});
92
+ expect(uiControls.consult.switch).toEqual({isVisible: true, isEnabled: true});
93
+ });
94
+
95
+ it('switches top-level controls to main leg while keeping consult leg visible', () => {
96
+ const context = createVoiceContext({
97
+ consultCallHeld: true,
98
+ });
99
+
100
+ const uiControls = computeUIControls(TaskState.CONNECTED, context, context.taskData);
101
+
102
+ expect(uiControls.activeLeg).toBe('main');
103
+ expect(uiControls.consult).toBeDefined();
104
+
105
+ expect(uiControls.main.hold).toEqual({isVisible: false, isEnabled: false});
106
+ expect(uiControls.main.switch).toEqual({isVisible: true, isEnabled: true});
107
+ expect(uiControls.main.transfer).toEqual({isVisible: true, isEnabled: true});
108
+ expect(uiControls.main.conference).toEqual({isVisible: true, isEnabled: true});
109
+ expect(uiControls.main.end).toEqual({isVisible: true, isEnabled: false});
110
+
111
+ expect(uiControls.consult.hold).toEqual({isVisible: false, isEnabled: false});
112
+ expect(uiControls.consult.transfer).toEqual({isVisible: true, isEnabled: false});
113
+ expect(uiControls.consult.conference).toEqual({isVisible: true, isEnabled: false});
114
+ expect(uiControls.consult.endConsult).toEqual({isVisible: true, isEnabled: true});
115
+ expect(uiControls.consult.end).toEqual({isVisible: false, isEnabled: false});
116
+ expect(uiControls.consult.switch).toEqual({isVisible: false, isEnabled: false});
117
+ });
118
+
119
+ it('hides transfer for the consulted agent during consult', () => {
120
+ const consultedTaskData = createConsultTaskData();
121
+ const consultedContext = createVoiceContext({
122
+ consultInitiator: false,
123
+ taskData: {
124
+ ...consultedTaskData,
125
+ isConsulted: true,
126
+ } as any,
127
+ });
128
+
129
+ const uiControls = computeUIControls(
130
+ TaskState.CONSULTING,
131
+ consultedContext,
132
+ consultedContext.taskData
133
+ );
134
+
135
+ expect(uiControls.consult.transfer).toEqual({isVisible: false, isEnabled: false});
136
+ });
137
+
138
+ it('collapses stale consult leg controls during wrapup', () => {
139
+ const context = createVoiceContext();
140
+
141
+ const uiControls = computeUIControls(TaskState.WRAPPING_UP, context, context.taskData);
142
+
143
+ expect(uiControls.activeLeg).toBe('main');
144
+ expect(uiControls.main.wrapup).toEqual({isVisible: true, isEnabled: true});
145
+ expect(uiControls.consult).toEqual(getDefaultUIControls().consult);
146
+ });
147
+ });
@@ -0,0 +1,87 @@
1
+ import {MEDIA_CHANNEL, TaskData} from '../../../../../src/services/task/types';
2
+
3
+ type TaskDataOverrides = Partial<TaskData> & {
4
+ interaction?: Partial<TaskData['interaction']> & {
5
+ media?: Record<string, {mediaResourceId: string; isHold: boolean}>;
6
+ callProcessingDetails?: Record<string, any>;
7
+ };
8
+ };
9
+
10
+ /**
11
+ * Utility to create task data for tests with sensible defaults while allowing overrides.
12
+ */
13
+ export function createTaskData(overrides: TaskDataOverrides = {}): TaskData {
14
+ const base: TaskData = {
15
+ interactionId: 'interaction-1',
16
+ mediaResourceId: 'media-1',
17
+ eventType: 'OFFER',
18
+ agentId: 'agent-1',
19
+ destAgentId: 'agent-2',
20
+ trackingId: 'tracking-1',
21
+ consultMediaResourceId: 'media-1',
22
+ interaction: {
23
+ isFcManaged: false,
24
+ isTerminated: false,
25
+ mediaType: MEDIA_CHANNEL.TELEPHONY,
26
+ previousVTeams: [],
27
+ state: 'new',
28
+ currentVTeam: 'team-1',
29
+ participants: [],
30
+ interactionId: 'interaction-1',
31
+ orgId: 'org-1',
32
+ callProcessingDetails: {
33
+ recordingStarted: true,
34
+ recordInProgress: true,
35
+ },
36
+ media: {
37
+ 'media-1': {
38
+ mediaResourceId: 'media-1',
39
+ isHold: false,
40
+ },
41
+ },
42
+ } as any,
43
+ } as TaskData;
44
+
45
+ const mergedInteraction = {
46
+ ...(base.interaction as any),
47
+ ...(overrides.interaction || {}),
48
+ media: {
49
+ ...((base.interaction as any).media || {}),
50
+ ...((overrides.interaction as any)?.media || {}),
51
+ },
52
+ callProcessingDetails: {
53
+ ...((base.interaction as any).callProcessingDetails || {}),
54
+ ...((overrides.interaction as any)?.callProcessingDetails || {}),
55
+ },
56
+ };
57
+
58
+ return {
59
+ ...base,
60
+ ...overrides,
61
+ interaction: mergedInteraction,
62
+ } as TaskData;
63
+ }
64
+
65
+ describe('taskTestUtils', () => {
66
+ it('creates sensible defaults when no overrides are provided', () => {
67
+ const task = createTaskData();
68
+
69
+ expect(task.interactionId).toBe('interaction-1');
70
+ expect(task.interaction?.state).toBe('new');
71
+ expect(task.interaction?.media?.['media-1']?.isHold).toBe(false);
72
+ });
73
+
74
+ it('merges nested interaction overrides', () => {
75
+ const task = createTaskData({
76
+ interaction: {
77
+ state: 'connected',
78
+ media: {
79
+ 'media-1': {mediaResourceId: 'media-1', isHold: true},
80
+ },
81
+ },
82
+ });
83
+
84
+ expect(task.interaction?.state).toBe('connected');
85
+ expect(task.interaction?.media?.['media-1']?.isHold).toBe(true);
86
+ });
87
+ });