@webex/contact-center 3.12.0-next.9 → 3.12.0-task-refactor.1

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 +556 -532
  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 +366 -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 +256 -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 +369 -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 +567 -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 +409 -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 +295 -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 +529 -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
@@ -15,7 +15,7 @@ import type {ContactServiceQueuesResponse} from '../../../src/types';
15
15
  import MockWebex from '@webex/test-helper-mock-webex';
16
16
  import {StationLoginSuccess, AGENT_EVENTS} from '../../../src/services/agent/types';
17
17
  import {SetStateResponse} from '../../../src/types';
18
- import {AGENT, SUBSCRIBE_API, WEB_RTC_PREFIX} from '../../../src/services/constants';
18
+ import {AGENT, WEB_RTC_PREFIX} from '../../../src/services/constants';
19
19
  import Services from '../../../src/services';
20
20
  import config from '../../../src/config';
21
21
  import {CC_EVENTS} from '../../../src/services/config/types';
@@ -59,6 +59,7 @@ global.URL.createObjectURL = jest.fn(() => 'blob:http://localhost:3000/12345');
59
59
 
60
60
  describe('webex.cc', () => {
61
61
  let webex;
62
+ let mockApiAIAssistant;
62
63
  let mockContact;
63
64
  let mockTaskManager;
64
65
  let mockMetricsManager;
@@ -88,8 +89,6 @@ describe('webex.cc', () => {
88
89
  initWebSocket: jest.fn(),
89
90
  on: jest.fn(),
90
91
  off: jest.fn(),
91
- close: jest.fn(),
92
- isSocketClosed: false,
93
92
  };
94
93
 
95
94
  mockContact = {
@@ -109,6 +108,12 @@ describe('webex.cc', () => {
109
108
  cancelCtq: jest.fn(),
110
109
  };
111
110
 
111
+ mockApiAIAssistant = {
112
+ sendEvent: jest.fn(),
113
+ fetchHistoricTranscripts: jest.fn(),
114
+ setAIFeatureFlags: jest.fn(),
115
+ };
116
+
112
117
  // Mock Services instance
113
118
  const mockServicesInstance = {
114
119
  agent: {
@@ -123,13 +128,6 @@ describe('webex.cc', () => {
123
128
  getOutdialAniEntries: jest.fn(),
124
129
  },
125
130
  webSocketManager: mockWebSocketManager,
126
- rtdWebSocketManager: {
127
- initWebSocket: jest.fn().mockResolvedValue({}),
128
- on: jest.fn(),
129
- off: jest.fn(),
130
- close: jest.fn(),
131
- isSocketClosed: false,
132
- },
133
131
  connectionService: {
134
132
  on: jest.fn(),
135
133
  off: jest.fn(),
@@ -138,26 +136,21 @@ describe('webex.cc', () => {
138
136
 
139
137
  dialer: {
140
138
  startOutdial: jest.fn(),
141
- acceptPreviewContact: jest.fn(),
142
- },
143
- apiAIAssistant: {
144
- sendEvent: jest.fn(),
145
- fetchHistoricTranscripts: jest.fn(),
146
139
  },
147
140
  };
148
141
 
149
142
  mockTaskManager = {
143
+ apiAIAssistant: mockApiAIAssistant,
150
144
  contact: mockContact,
151
145
  call: undefined,
152
146
  taskCollection: {},
153
147
  webCallingService: undefined,
154
148
  webSocketManager: mockWebSocketManager,
155
149
  task: undefined,
150
+ setConfigFlags: jest.fn(),
156
151
  setWrapupData: jest.fn(),
157
152
  setAgentId: jest.fn(),
158
153
  setWebRtcEnabled: jest.fn(),
159
- handleRealtimeTranscriptEvent: jest.fn(),
160
- setApiAIAssistant: jest.fn(),
161
154
  registerIncomingCallEvent: jest.fn(),
162
155
  registerTaskListeners: jest.fn(),
163
156
  getTask: jest.fn(),
@@ -267,7 +260,7 @@ describe('webex.cc', () => {
267
260
  isAgentAvailableAfterOutdial: false,
268
261
  isCampaignManagementEnabled: false,
269
262
  outDialEp: '',
270
- isEndCallEnabled: false,
263
+ isEndTaskEnabled: false,
271
264
  isEndConsultEnabled: false,
272
265
  agentDbId: '',
273
266
  allowConsultToQueue: false,
@@ -278,7 +271,6 @@ describe('webex.cc', () => {
278
271
  };
279
272
 
280
273
  it('should register successfully and return agent profile', async () => {
281
- mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: true}} as any;
282
274
  const mercuryConnect = jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
283
275
  const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
284
276
  const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
@@ -322,7 +314,7 @@ describe('webex.cc', () => {
322
314
  clientType: 'WebexCCSDK',
323
315
  allowMultiLogin: false,
324
316
  },
325
- resource: SUBSCRIBE_API,
317
+ resource: 'v1/notification/subscribe',
326
318
  });
327
319
 
328
320
  // TODO: https://jira-eng-gpk2.cisco.com/jira/browse/SPARK-626777 Implement the de-register method and close the listener there
@@ -335,25 +327,24 @@ describe('webex.cc', () => {
335
327
  expect.any(Function)
336
328
  );
337
329
  expect(mockWebSocketManager.on).toHaveBeenCalledWith('message', expect.any(Function));
338
- expect(webex.cc.services.rtdWebSocketManager.initWebSocket).toHaveBeenCalledWith({
339
- body: {
340
- force: true,
341
- isKeepAliveEnabled: false,
342
- clientType: 'WebexCCSDK',
343
- allowMultiLogin: false,
344
- },
345
- resource: 'v1/realtime/subscribe',
346
- });
347
- expect(webex.cc.services.rtdWebSocketManager.on).toHaveBeenCalledWith(
348
- 'message',
349
- expect.any(Function)
350
- );
351
330
 
352
331
  expect(configSpy).toHaveBeenCalled();
353
332
  expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
354
333
  module: CC_FILE,
355
334
  method: 'connectWebsocket',
356
335
  });
336
+ expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith({
337
+ isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
338
+ isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
339
+ webRtcEnabled: mockAgentProfile.webRtcEnabled,
340
+ autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
341
+ });
342
+ expect(mockTaskManager.setConfigFlags).toHaveBeenCalledWith({
343
+ isEndTaskEnabled: mockAgentProfile.isEndTaskEnabled,
344
+ isEndConsultEnabled: mockAgentProfile.isEndConsultEnabled,
345
+ webRtcEnabled: mockAgentProfile.webRtcEnabled,
346
+ autoWrapup: mockAgentProfile.wrapUpData.wrapUpProps.autoWrapup ?? false,
347
+ });
357
348
  expect(reloadSpy).toHaveBeenCalled();
358
349
  expect(result).toEqual(mockAgentProfile);
359
350
  expect(mockMetricsManager.timeEvent).toHaveBeenCalledWith([
@@ -391,7 +382,7 @@ describe('webex.cc', () => {
391
382
  clientType: 'WebexCCSDK',
392
383
  allowMultiLogin: true,
393
384
  },
394
- resource: SUBSCRIBE_API,
385
+ resource: 'v1/notification/subscribe',
395
386
  });
396
387
  expect(configSpy).toHaveBeenCalled();
397
388
  expect(LoggerProxy.log).toHaveBeenCalledWith('Agent config is fetched successfully', {
@@ -471,7 +462,7 @@ describe('webex.cc', () => {
471
462
  clientType: 'WebexCCSDK',
472
463
  allowMultiLogin: false,
473
464
  },
474
- resource: SUBSCRIBE_API,
465
+ resource: 'v1/notification/subscribe',
475
466
  });
476
467
 
477
468
  expect(mockTaskManager.on).toHaveBeenCalledWith(
@@ -495,7 +486,6 @@ describe('webex.cc', () => {
495
486
 
496
487
  it('should not attempt for mercury connection when webrtc is disabled', async () => {
497
488
  mockAgentProfile.webRtcEnabled = false;
498
- mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
499
489
  const mercurySpy = jest.spyOn(webex.internal.mercury, 'connect');
500
490
  const connectWebsocketSpy = jest.spyOn(webex.cc, 'connectWebsocket');
501
491
  const setupEventListenersSpy = jest.spyOn(webex.cc, 'setupEventListeners');
@@ -525,39 +515,13 @@ describe('webex.cc', () => {
525
515
  clientType: 'WebexCCSDK',
526
516
  allowMultiLogin: false,
527
517
  },
528
- resource: SUBSCRIBE_API,
518
+ resource: 'v1/notification/subscribe',
529
519
  });
530
520
 
531
521
  expect(configSpy).toHaveBeenCalled();
532
522
  expect(mercurySpy).not.toHaveBeenCalled();
533
- expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
534
523
  expect(result).toEqual(mockAgentProfile);
535
524
  });
536
-
537
- it('should not connect RTD websocket when realtime transcripts feature is disabled', async () => {
538
- mockAgentProfile.aiFeature = {realtimeTranscripts: {enable: false}} as any;
539
- jest.spyOn(webex.internal.mercury, 'connect').mockResolvedValue(true);
540
- jest.spyOn(webex.cc.services.agent, 'reload').mockResolvedValue({
541
- data: {
542
- auxCodeId: 'auxCodeId',
543
- agentId: 'agentId',
544
- deviceType: LoginOption.EXTENSION,
545
- dn: '12345',
546
- },
547
- });
548
- jest.spyOn(webex.cc.services.config, 'getAgentConfig').mockResolvedValue(mockAgentProfile);
549
- mockWebSocketManager.initWebSocket.mockResolvedValue({
550
- agentId: 'agent123',
551
- });
552
-
553
- await webex.cc.register();
554
-
555
- expect(webex.cc.services.rtdWebSocketManager.initWebSocket).not.toHaveBeenCalled();
556
- expect(webex.cc.services.rtdWebSocketManager.on).not.toHaveBeenCalledWith(
557
- 'message',
558
- expect.any(Function)
559
- );
560
- });
561
525
  });
562
526
 
563
527
  describe('stationLogin', () => {
@@ -1500,6 +1464,27 @@ describe('webex.cc', () => {
1500
1464
  });
1501
1465
  });
1502
1466
 
1467
+ describe('getQueues', () => {
1468
+ it('delegates to the queue service when successful', async () => {
1469
+ const mockQueuesResponse = [{queueId: 'queue1', queueName: 'Queue 1'}];
1470
+ const queueSpy = jest
1471
+ .spyOn(webex.cc.queue, 'getQueues')
1472
+ .mockResolvedValue(mockQueuesResponse as any);
1473
+
1474
+ const result = await webex.cc.getQueues({page: 1});
1475
+
1476
+ expect(queueSpy).toHaveBeenCalledWith({page: 1});
1477
+ expect(result).toBe(mockQueuesResponse);
1478
+ });
1479
+
1480
+ it('propagates queue service errors', async () => {
1481
+ const error = new Error('Test error.');
1482
+ jest.spyOn(webex.cc.queue, 'getQueues').mockRejectedValue(error);
1483
+
1484
+ await expect(webex.cc.getQueues()).rejects.toThrow('Test error.');
1485
+ });
1486
+ });
1487
+
1503
1488
  describe('uploadLogs', () => {
1504
1489
  it('should upload logs successfully', async () => {
1505
1490
  const uploadLogsMock = jest.spyOn(webex.cc.webexRequest, 'uploadLogs').mockResolvedValue({
@@ -1529,6 +1514,7 @@ describe('webex.cc', () => {
1529
1514
 
1530
1515
  describe('unregister', () => {
1531
1516
  let mockWebSocketManager;
1517
+ let mockRTDWebSocketManager;
1532
1518
  let mercuryDisconnectSpy;
1533
1519
  let deviceUnregisterSpy;
1534
1520
 
@@ -1545,8 +1531,15 @@ describe('webex.cc', () => {
1545
1531
  off: jest.fn(),
1546
1532
  on: jest.fn(),
1547
1533
  };
1534
+ mockRTDWebSocketManager = {
1535
+ isSocketClosed: false,
1536
+ close: jest.fn(),
1537
+ off: jest.fn(),
1538
+ on: jest.fn(),
1539
+ };
1548
1540
 
1549
1541
  webex.cc.services.webSocketManager = mockWebSocketManager;
1542
+ webex.cc.services.rtdWebSocketManager = mockRTDWebSocketManager;
1550
1543
 
1551
1544
  webex.internal = webex.internal || {};
1552
1545
  webex.internal.mercury = {
@@ -1563,13 +1556,6 @@ describe('webex.cc', () => {
1563
1556
  });
1564
1557
 
1565
1558
  it('should unregister successfully and clean up all resources when webrtc is enabled', async () => {
1566
- webex.cc.services.rtdWebSocketManager = {
1567
- isSocketClosed: false,
1568
- close: jest.fn(),
1569
- off: jest.fn(),
1570
- on: jest.fn(),
1571
- } as any;
1572
-
1573
1559
  await webex.cc.deregister();
1574
1560
 
1575
1561
  expect(mockTaskManager.off).toHaveBeenCalledWith(
@@ -1581,20 +1567,13 @@ describe('webex.cc', () => {
1581
1567
  expect.any(Function)
1582
1568
  );
1583
1569
  expect(mockWebSocketManager.off).toHaveBeenCalledWith('message', expect.any(Function));
1584
- expect(webex.cc.services.rtdWebSocketManager.off).toHaveBeenCalledWith(
1585
- 'message',
1586
- expect.any(Function)
1587
- );
1588
1570
  expect(webex.cc.services.connectionService.off).toHaveBeenCalledWith(
1589
1571
  'connectionLost',
1590
1572
  expect.any(Function)
1591
1573
  );
1592
1574
 
1593
1575
  expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1594
- expect(webex.cc.services.rtdWebSocketManager.close).toHaveBeenCalledWith(
1595
- false,
1596
- 'Unregistering the RTD websocket'
1597
- );
1576
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1598
1577
  expect(webex.cc.agentConfig).toBeNull();
1599
1578
 
1600
1579
  expect(webex.internal.mercury.off).toHaveBeenCalledWith('online');
@@ -1670,6 +1649,7 @@ describe('webex.cc', () => {
1670
1649
  expect(webex.internal.mercury.off).not.toHaveBeenCalled();
1671
1650
  expect(mercuryDisconnectSpy).not.toHaveBeenCalled();
1672
1651
  expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1652
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1673
1653
  });
1674
1654
 
1675
1655
  it('should skip internal mercury cleanup when loginVoiceOptions does not include BROWSER', async () => {
@@ -1687,6 +1667,7 @@ describe('webex.cc', () => {
1687
1667
  expect(deviceUnregisterSpy).not.toHaveBeenCalled();
1688
1668
 
1689
1669
  expect(mockWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1670
+ expect(mockRTDWebSocketManager.close).toHaveBeenCalledWith(false, 'Unregistering the SDK');
1690
1671
  expect(webex.cc.agentConfig).toBeNull();
1691
1672
  });
1692
1673
 
@@ -2256,68 +2237,4 @@ describe('webex.cc', () => {
2256
2237
  );
2257
2238
  });
2258
2239
  });
2259
-
2260
- describe('acceptPreviewContact', () => {
2261
- const previewPayload = {
2262
- interactionId: 'interaction-123',
2263
- campaignId: 'campaign-456',
2264
- };
2265
-
2266
- it('should accept preview contact successfully', async () => {
2267
- const mockResponse = {trackingId: 'track-123'} as AgentContact;
2268
-
2269
- const acceptPreviewContactMock = jest
2270
- .spyOn(webex.cc.services.dialer, 'acceptPreviewContact')
2271
- .mockResolvedValue(mockResponse);
2272
-
2273
- const result = await webex.cc.acceptPreviewContact(previewPayload);
2274
-
2275
- expect(LoggerProxy.info).toHaveBeenCalledWith('Accepting campaign preview contact', {
2276
- module: CC_FILE,
2277
- method: 'acceptPreviewContact',
2278
- });
2279
- expect(LoggerProxy.log).toHaveBeenCalledWith(
2280
- 'Campaign preview contact accepted successfully',
2281
- {
2282
- module: CC_FILE,
2283
- method: 'acceptPreviewContact',
2284
- trackingId: 'track-123',
2285
- interactionId: previewPayload.interactionId,
2286
- }
2287
- );
2288
-
2289
- expect(acceptPreviewContactMock).toHaveBeenCalledWith({data: previewPayload});
2290
- expect(result).toEqual(mockResponse);
2291
- });
2292
-
2293
- it('should handle error during acceptPreviewContact', async () => {
2294
- getErrorDetailsSpy.mockRestore();
2295
- getErrorDetailsSpy = jest.spyOn(Utils, 'getErrorDetails');
2296
-
2297
- const error = {
2298
- details: {
2299
- trackingId: '1234',
2300
- data: {
2301
- reason: 'Error while performing acceptPreviewContact',
2302
- },
2303
- },
2304
- };
2305
-
2306
- jest.spyOn(webex.cc.services.dialer, 'acceptPreviewContact').mockRejectedValue(error);
2307
-
2308
- await expect(webex.cc.acceptPreviewContact(previewPayload)).rejects.toThrow(
2309
- error.details.data.reason
2310
- );
2311
-
2312
- expect(LoggerProxy.info).toHaveBeenCalledWith('Accepting campaign preview contact', {
2313
- module: CC_FILE,
2314
- method: 'acceptPreviewContact',
2315
- });
2316
- expect(LoggerProxy.error).toHaveBeenCalledWith(
2317
- `acceptPreviewContact failed with reason: ${error.details.data.reason}`,
2318
- {module: CC_FILE, method: 'acceptPreviewContact', trackingId: error.details.trackingId}
2319
- );
2320
- expect(getErrorDetailsSpy).toHaveBeenCalledWith(error, 'acceptPreviewContact', CC_FILE);
2321
- });
2322
- });
2323
2240
  });
@@ -0,0 +1,70 @@
1
+ import LoggerProxy from '../../../src/logger-proxy';
2
+
3
+ describe('LoggerProxy', () => {
4
+ beforeEach(() => {
5
+ // Reset between tests
6
+ (LoggerProxy as any).logger = undefined;
7
+ });
8
+
9
+ it('no-ops when logger is not initialized', () => {
10
+ expect(() => {
11
+ LoggerProxy.log('msg');
12
+ LoggerProxy.info('msg');
13
+ LoggerProxy.warn('msg');
14
+ LoggerProxy.trace('msg');
15
+ LoggerProxy.error('msg');
16
+ }).not.toThrow();
17
+ });
18
+
19
+ it('routes calls to the injected logger with formatted context', () => {
20
+ const logger = {
21
+ log: jest.fn(),
22
+ info: jest.fn(),
23
+ warn: jest.fn(),
24
+ trace: jest.fn(),
25
+ error: jest.fn(),
26
+ };
27
+
28
+ LoggerProxy.initialize(logger as any);
29
+
30
+ LoggerProxy.log('hello', {module: 'm', method: 'fn', interactionId: 'i', trackingId: 't'});
31
+ expect(logger.log).toHaveBeenCalled();
32
+ expect(String(logger.log.mock.calls[0][0])).toContain('module:m - method:fn');
33
+ expect(String(logger.log.mock.calls[0][0])).toContain('interactionId:i');
34
+ expect(String(logger.log.mock.calls[0][0])).toContain('trackingId:t');
35
+
36
+ LoggerProxy.info('hello', {module: 'm', method: 'fn'});
37
+ LoggerProxy.warn('hello', {module: 'm', method: 'fn'});
38
+ LoggerProxy.trace('hello', {module: 'm', method: 'fn'});
39
+ LoggerProxy.error('hello', {module: 'm', method: 'fn'});
40
+ expect(logger.info).toHaveBeenCalled();
41
+ expect(logger.warn).toHaveBeenCalled();
42
+ expect(logger.trace).toHaveBeenCalled();
43
+ expect(logger.error).toHaveBeenCalled();
44
+ });
45
+
46
+ it('handles unserializable data and different error shapes', () => {
47
+ const logger = {
48
+ log: jest.fn(),
49
+ info: jest.fn(),
50
+ warn: jest.fn(),
51
+ trace: jest.fn(),
52
+ error: jest.fn(),
53
+ };
54
+ LoggerProxy.initialize(logger as any);
55
+
56
+ const circular: any = {};
57
+ circular.self = circular;
58
+
59
+ LoggerProxy.info('msg', {module: 'm', method: 'fn', data: circular, error: 'oops'});
60
+ expect(String(logger.info.mock.calls[0][0])).toContain('data:[object]');
61
+ expect(String(logger.info.mock.calls[0][0])).toContain('error:oops');
62
+
63
+ const err = new Error('boom');
64
+ LoggerProxy.error('msg', {module: 'm', method: 'fn', error: err});
65
+ const errorLine = String(logger.error.mock.calls[0][0]);
66
+ expect(errorLine).toContain('error:Error:boom');
67
+ expect(errorLine).toContain('stack:');
68
+ });
69
+ });
70
+
@@ -1,6 +1,7 @@
1
1
  import ApiAIAssistant from '../../../../src/services/ApiAiAssistant';
2
2
  import MetricsManager from '../../../../src/metrics/MetricsManager';
3
3
  import LoggerProxy from '../../../../src/logger-proxy';
4
+ import WebexRequest from '../../../../src/services/core/WebexRequest';
4
5
  import {HTTP_METHODS, WebexSDK} from '../../../../src/types';
5
6
 
6
7
  jest.mock('../../../../src/metrics/MetricsManager');
@@ -13,6 +14,9 @@ describe('ApiAIAssistant', () => {
13
14
 
14
15
  beforeEach(() => {
15
16
  jest.clearAllMocks();
17
+ jest.spyOn(WebexRequest, 'getInstance').mockReturnValue({
18
+ uploadLogs: jest.fn(),
19
+ } as any);
16
20
 
17
21
  mockWebex = {
18
22
  credentials: {
@@ -112,4 +116,17 @@ describe('ApiAIAssistant', () => {
112
116
  expect(failed).toBe(true);
113
117
  expect(LoggerProxy.error).toHaveBeenCalled();
114
118
  });
119
+
120
+ it('should fail when realtime transcripts feature is disabled', async () => {
121
+ apiAIAssistant.setAIFeatureFlags({realtimeTranscripts: {enable: false}} as any);
122
+ let errorMessage = '';
123
+
124
+ try {
125
+ await apiAIAssistant.fetchHistoricTranscripts('test-agent-id', 'interaction-1');
126
+ } catch (error) {
127
+ errorMessage = (error as Error)?.message || '';
128
+ }
129
+
130
+ expect(errorMessage).toBe('Error while performing fetchHistoricTranscripts');
131
+ });
115
132
  });
@@ -66,7 +66,6 @@ describe('AgentConfigService', () => {
66
66
  method: 'GET',
67
67
  });
68
68
  expect(result).toEqual(mockResponse.body);
69
-
70
69
  expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching user data using CI', {
71
70
  module: CONFIG_FILE_NAME,
72
71
  method: 'getUserUsingCI',
@@ -124,7 +123,6 @@ describe('AgentConfigService', () => {
124
123
  method: 'GET',
125
124
  });
126
125
  expect(result).toEqual(mockResponse.body);
127
-
128
126
  expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching desktop profile', {
129
127
  module: CONFIG_FILE_NAME,
130
128
  method: 'getDesktopProfileById',
@@ -183,7 +181,6 @@ describe('AgentConfigService', () => {
183
181
  method: 'GET',
184
182
  });
185
183
  expect(result).toEqual(mockResponse.body);
186
-
187
184
  expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching list of teams', {
188
185
  module: CONFIG_FILE_NAME,
189
186
  method: 'getListOfTeams',
@@ -267,7 +264,6 @@ describe('AgentConfigService', () => {
267
264
  method: 'GET',
268
265
  });
269
266
  expect(result).toEqual(mockResponse.body);
270
-
271
267
  expect(LoggerProxy.info).toHaveBeenCalledWith('Fetching list of aux codes', {
272
268
  module: CONFIG_FILE_NAME,
273
269
  method: 'getListOfAuxCodes',
@@ -495,7 +491,7 @@ describe('AgentConfigService', () => {
495
491
  const mockResponse = {
496
492
  statusCode: 200,
497
493
  body: {
498
- realtimeTranscripts: {enable: true},
494
+ data: [{realtimeTranscripts: {enable: true}}],
499
495
  },
500
496
  };
501
497
  mockWebexRequest.request.mockResolvedValue(mockResponse);
@@ -710,6 +706,31 @@ describe('AgentConfigService', () => {
710
706
  });
711
707
 
712
708
  describe('getAgentConfig', () => {
709
+ const mockTeamData = [
710
+ {id: 'team1', name: 'Support Team'},
711
+ {id: 'team2', name: 'Sales Team'},
712
+ ];
713
+
714
+ const mockOrgInfo = {
715
+ tenantId: 'tenant123',
716
+ timezone: 'GMT',
717
+ };
718
+
719
+ const mockSiteInfo = {
720
+ id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
721
+ name: 'Site-1',
722
+ multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
723
+ };
724
+
725
+ const mockURLMapping = [
726
+ {key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
727
+ {key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
728
+ ];
729
+
730
+ const mockAIFeatureFlags = {
731
+ data: [{realtimeTranscripts: {enable: true}}],
732
+ };
733
+
713
734
  beforeEach(() => {
714
735
  jest.clearAllMocks();
715
736
  });
@@ -753,29 +774,12 @@ describe('AgentConfigService', () => {
753
774
 
754
775
  const mockDialPlanData = [];
755
776
 
756
- const mockTeamData = [
757
- {id: 'team1', name: 'Support Team'},
758
- {id: 'team2', name: 'Sales Team'},
759
- ];
760
-
761
- const mockOrgInfo = {
762
- tenantId: 'tenant123',
763
- timezone: 'GMT',
764
- environment: 'produs1',
765
- };
766
-
767
777
  const mockOrgSettings = {
768
778
  campaignManagerEnabled: true,
769
779
  webRtcEnabled: true,
770
780
  maskSensitiveData: false,
771
781
  };
772
782
 
773
- const mockSiteInfo = {
774
- id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
775
- name: 'Site-1',
776
- multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
777
- };
778
-
779
783
  const mockTenantData = {
780
784
  timeoutDesktopInactivityEnabled: false,
781
785
  timeoutDesktopInactivityMins: 15,
@@ -789,18 +793,10 @@ describe('AgentConfigService', () => {
789
793
  callVariablesSuppressed: false,
790
794
  };
791
795
 
792
- const mockURLMapping = [
793
- {key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
794
- {key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
795
- ];
796
-
797
796
  const mockAuxCodes = [
798
797
  {id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1', isDefault: true},
799
798
  {id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1', isDefault: true},
800
799
  ];
801
- const mockAIFeatureFlags = {
802
- data: [{realtimeTranscripts: {enable: true}}],
803
- };
804
800
 
805
801
  const parseAgentConfigsSpy = jest.spyOn(util, 'parseAgentConfigs');
806
802
  agentConfigService.getUserUsingCI = jest.fn().mockResolvedValue(mockUserConfig);
@@ -898,29 +894,12 @@ describe('AgentConfigService', () => {
898
894
  {id: 'dialPlan2', name: 'Plan 2'},
899
895
  ];
900
896
 
901
- const mockTeamData = [
902
- {id: 'team1', name: 'Support Team'},
903
- {id: 'team2', name: 'Sales Team'},
904
- ];
905
-
906
- const mockOrgInfo = {
907
- tenantId: 'tenant123',
908
- timezone: 'GMT',
909
- environment: 'produs1',
910
- };
911
-
912
897
  const mockOrgSettings = {
913
898
  campaignManagerEnabled: true,
914
899
  webRtcEnabled: true,
915
900
  maskSensitiveData: true,
916
901
  };
917
902
 
918
- const mockSiteInfo = {
919
- id: 'c6a5451f-5ba7-49a1-aee8-fbef70c19ece',
920
- name: 'Site-1',
921
- multimediaProfileId: 'c5888e6f-5661-4871-9936-cbcec7658d41',
922
- };
923
-
924
903
  const mockTenantData = {
925
904
  timeoutDesktopInactivityEnabled: true,
926
905
  timeoutDesktopInactivityMins: 15,
@@ -935,18 +914,10 @@ describe('AgentConfigService', () => {
935
914
  lostConnectionRecoveryTimeout: 30,
936
915
  };
937
916
 
938
- const mockURLMapping = [
939
- {key: 'ACQUEON_API_URL', url: 'https://api.example.com'},
940
- {key: 'ACQUEON_CONSOLE_URL', url: 'https://console.example.com'},
941
- ];
942
-
943
917
  const mockAuxCodes = [
944
918
  {id: 'aux1', type: 'WRAP_UP_CODE', name: 'Wrap Up Code 1'},
945
919
  {id: 'aux2', type: 'IDLE_CODE', name: 'Idle Code 1'},
946
920
  ];
947
- const mockAIFeatureFlags = {
948
- data: [{realtimeTranscripts: {enable: true}}],
949
- };
950
921
 
951
922
  const parseAgentConfigsSpy = jest.spyOn(util, 'parseAgentConfigs');
952
923
  agentConfigService.getUserUsingCI = jest.fn().mockResolvedValue(mockUserConfig);