@webex/contact-center 3.12.0-llmrefactor.8 → 3.12.0-llmrefactor.9
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.
- package/.sdd/manifest.json +3 -2
- package/ai-docs/CONTRACTS.md +2 -1
- package/dist/cc.js +108 -6
- package/dist/cc.js.map +1 -1
- package/dist/metrics/MetricsManager.js +16 -0
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +91 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +16 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +34 -6
- package/dist/services/AnswerCallOnWebexService.js.map +1 -1
- package/dist/services/WebexCrossClientService.js +54 -3
- package/dist/services/WebexCrossClientService.js.map +1 -1
- package/dist/services/WxAppTelephonyMercurySync.js +21 -6
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -1
- package/dist/services/task/voice/Voice.js +81 -14
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js +154 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -1
- package/dist/services/wxAppDiagnosticLogging.js +122 -0
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
- package/dist/types/cc.d.ts +1 -0
- package/dist/types/metrics/MetricsManager.d.ts +6 -0
- package/dist/types/metrics/constants.d.ts +15 -0
- package/dist/types/services/AnswerCallOnWebexService.d.ts +1 -0
- package/dist/types/services/WebexCrossClientService.d.ts +4 -0
- package/dist/types/services/task/voice/Voice.d.ts +5 -1
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +3 -0
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/cc.ts +155 -7
- package/src/metrics/MetricsManager.ts +16 -0
- package/src/metrics/ai-docs/metrics-spec.md +71 -9
- package/src/metrics/behavioral-events.ts +92 -0
- package/src/metrics/constants.ts +18 -0
- package/src/services/AnswerCallOnWebexService.ts +41 -8
- package/src/services/WebexCrossClientService.ts +85 -4
- package/src/services/WxAppTelephonyMercurySync.ts +31 -4
- package/src/services/agent/ai-docs/agent-spec.md +1 -0
- package/src/services/task/ai-docs/task-spec.md +2 -0
- package/src/services/task/voice/Voice.ts +115 -26
- package/src/services/task/voice/wxAppVoiceMethods.ts +211 -2
- package/src/services/wxAppDiagnosticLogging.ts +174 -0
- package/test/unit/spec/cc.ts +251 -22
- package/test/unit/spec/metrics/MetricsManager.ts +25 -0
- package/test/unit/spec/metrics/behavioral-events.ts +42 -0
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +41 -0
- package/test/unit/spec/services/WebexCrossClientService.ts +207 -1
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +23 -0
- package/test/unit/spec/services/task/voice/Voice.ts +380 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +145 -1
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -1,13 +1,30 @@
|
|
|
1
1
|
import WebexCrossClientService from '../../../../src/services/WebexCrossClientService';
|
|
2
|
+
import MetricsManager from '../../../../src/metrics/MetricsManager';
|
|
3
|
+
import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
|
|
4
|
+
|
|
5
|
+
jest.mock('../../../../src/metrics/MetricsManager', () => ({
|
|
6
|
+
__esModule: true,
|
|
7
|
+
default: {
|
|
8
|
+
getInstance: jest.fn(),
|
|
9
|
+
},
|
|
10
|
+
}));
|
|
2
11
|
|
|
3
12
|
describe('WebexCrossClientService', () => {
|
|
4
13
|
const userId = 'user-123';
|
|
5
14
|
const deviceUrl = 'https://wdm.example.com/devices/device-abc';
|
|
15
|
+
const trackEvent = jest.fn();
|
|
16
|
+
const timeEvent = jest.fn();
|
|
17
|
+
const cancelTimedEvent = jest.fn();
|
|
6
18
|
|
|
7
19
|
let webex: {request: jest.Mock; internal: {device: {userId: string; url: string}}};
|
|
8
20
|
let service: WebexCrossClientService;
|
|
9
21
|
|
|
10
22
|
beforeEach(() => {
|
|
23
|
+
trackEvent.mockClear();
|
|
24
|
+
timeEvent.mockClear();
|
|
25
|
+
cancelTimedEvent.mockClear();
|
|
26
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({trackEvent, timeEvent, cancelTimedEvent});
|
|
27
|
+
|
|
11
28
|
webex = {
|
|
12
29
|
request: jest.fn().mockResolvedValue({body: {}}),
|
|
13
30
|
internal: {
|
|
@@ -82,6 +99,26 @@ describe('WebexCrossClientService', () => {
|
|
|
82
99
|
'User ID is unavailable for cross-client publish'
|
|
83
100
|
);
|
|
84
101
|
expect(webex.request).not.toHaveBeenCalled();
|
|
102
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
103
|
+
});
|
|
104
|
+
|
|
105
|
+
it('emits usersub publish failed when user ID is unavailable and trackPublishMetrics is true', async () => {
|
|
106
|
+
webex.internal.device.userId = undefined as unknown as string;
|
|
107
|
+
|
|
108
|
+
await expect(
|
|
109
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
110
|
+
).rejects.toThrow('User ID is unavailable for cross-client publish');
|
|
111
|
+
|
|
112
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
113
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
114
|
+
expect.objectContaining({
|
|
115
|
+
enableWxBetterTogether: true,
|
|
116
|
+
skipReason: 'user_id_unavailable',
|
|
117
|
+
}),
|
|
118
|
+
['operational', 'behavioral']
|
|
119
|
+
);
|
|
120
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
121
|
+
expect(webex.request).not.toHaveBeenCalled();
|
|
85
122
|
});
|
|
86
123
|
|
|
87
124
|
it('throws when device URL is unavailable', async () => {
|
|
@@ -91,13 +128,182 @@ describe('WebexCrossClientService', () => {
|
|
|
91
128
|
'Device URL is unavailable for cross-client publish'
|
|
92
129
|
);
|
|
93
130
|
expect(webex.request).not.toHaveBeenCalled();
|
|
131
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
it('emits usersub publish failed when device URL is unavailable and trackPublishMetrics is true', async () => {
|
|
135
|
+
webex.internal.device.url = undefined as unknown as string;
|
|
136
|
+
|
|
137
|
+
await expect(
|
|
138
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
139
|
+
).rejects.toThrow('Device URL is unavailable for cross-client publish');
|
|
140
|
+
|
|
141
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
142
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
143
|
+
expect.objectContaining({
|
|
144
|
+
enableWxBetterTogether: true,
|
|
145
|
+
skipReason: 'device_url_unavailable',
|
|
146
|
+
}),
|
|
147
|
+
['operational', 'behavioral']
|
|
148
|
+
);
|
|
149
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
150
|
+
expect(webex.request).not.toHaveBeenCalled();
|
|
94
151
|
});
|
|
95
152
|
|
|
96
153
|
it('rejects when usersub publish fails', async () => {
|
|
97
154
|
const err = new Error('usersub failed');
|
|
98
155
|
webex.request = jest.fn().mockRejectedValue(err);
|
|
99
156
|
|
|
100
|
-
await expect(
|
|
157
|
+
await expect(
|
|
158
|
+
service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true})
|
|
159
|
+
).rejects.toThrow('usersub failed');
|
|
160
|
+
|
|
161
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
162
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
163
|
+
expect.objectContaining({enableWxBetterTogether: true}),
|
|
164
|
+
['operational', 'behavioral']
|
|
165
|
+
);
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
it('tracks usersub publish success when trackPublishMetrics is enabled', async () => {
|
|
169
|
+
await service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
170
|
+
|
|
171
|
+
expect(timeEvent).toHaveBeenCalledWith([
|
|
172
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
173
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
174
|
+
]);
|
|
175
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
176
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
177
|
+
{enableWxBetterTogether: true},
|
|
178
|
+
['operational', 'behavioral']
|
|
179
|
+
);
|
|
180
|
+
});
|
|
181
|
+
|
|
182
|
+
it('cancels usersub publish timer when publish completes after teardown bumps generation', async () => {
|
|
183
|
+
let resolvePublish: (value: {body: Record<string, never>}) => void;
|
|
184
|
+
const publishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
185
|
+
resolvePublish = resolve;
|
|
186
|
+
});
|
|
187
|
+
webex.request = jest.fn().mockReturnValue(publishPromise);
|
|
188
|
+
|
|
189
|
+
const publishCall = service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
190
|
+
service.teardown();
|
|
191
|
+
resolvePublish!({body: {}});
|
|
192
|
+
await publishCall;
|
|
193
|
+
|
|
194
|
+
expect(cancelTimedEvent).toHaveBeenCalledWith([
|
|
195
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
196
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
197
|
+
]);
|
|
198
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
199
|
+
});
|
|
200
|
+
|
|
201
|
+
it('cancels usersub publish timer when publish fails after teardown bumps generation', async () => {
|
|
202
|
+
let rejectPublish: (error: Error) => void;
|
|
203
|
+
const publishPromise = new Promise((_, reject) => {
|
|
204
|
+
rejectPublish = reject;
|
|
205
|
+
});
|
|
206
|
+
webex.request = jest.fn().mockReturnValue(publishPromise);
|
|
207
|
+
|
|
208
|
+
const publishCall = service.setManageWebexCallingInWxcc(true, {trackPublishMetrics: true});
|
|
209
|
+
service.teardown();
|
|
210
|
+
rejectPublish!(new Error('usersub failed'));
|
|
211
|
+
await expect(publishCall).rejects.toThrow('usersub failed');
|
|
212
|
+
|
|
213
|
+
expect(cancelTimedEvent).toHaveBeenCalledWith([
|
|
214
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
215
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
216
|
+
]);
|
|
217
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
218
|
+
});
|
|
219
|
+
|
|
220
|
+
it('does not cancel newer publish timer when stale publish completes', async () => {
|
|
221
|
+
let resolveFirstPublish: (value: {body: Record<string, never>}) => void;
|
|
222
|
+
const firstPublishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
223
|
+
resolveFirstPublish = resolve;
|
|
224
|
+
});
|
|
225
|
+
webex.request = jest
|
|
226
|
+
.fn()
|
|
227
|
+
.mockReturnValueOnce(firstPublishPromise)
|
|
228
|
+
.mockResolvedValueOnce({body: {}});
|
|
229
|
+
|
|
230
|
+
const firstPublishCall = service.setManageWebexCallingInWxcc(true, {
|
|
231
|
+
trackPublishMetrics: true,
|
|
232
|
+
});
|
|
233
|
+
const secondPublishCall = service.setManageWebexCallingInWxcc(false, {
|
|
234
|
+
trackPublishMetrics: true,
|
|
235
|
+
});
|
|
236
|
+
|
|
237
|
+
await secondPublishCall;
|
|
238
|
+
|
|
239
|
+
service.teardown();
|
|
240
|
+
resolveFirstPublish!({body: {}});
|
|
241
|
+
await firstPublishCall;
|
|
242
|
+
|
|
243
|
+
expect(cancelTimedEvent).not.toHaveBeenCalled();
|
|
244
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
245
|
+
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
246
|
+
{enableWxBetterTogether: false},
|
|
247
|
+
['operational', 'behavioral']
|
|
248
|
+
);
|
|
249
|
+
});
|
|
250
|
+
|
|
251
|
+
it('preserves duration_ms on newer publish when stale publish completes', async () => {
|
|
252
|
+
const {default: RealMetricsManager} = jest.requireActual(
|
|
253
|
+
'../../../../src/metrics/MetricsManager'
|
|
254
|
+
);
|
|
255
|
+
RealMetricsManager.resetInstance();
|
|
256
|
+
const realManager = RealMetricsManager.getInstance();
|
|
257
|
+
jest.spyOn(realManager as any, 'isMetricsDisabled').mockReturnValue(false);
|
|
258
|
+
const trackEventSpy = jest.spyOn(realManager, 'trackEvent');
|
|
259
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue(realManager);
|
|
260
|
+
|
|
261
|
+
let resolveFirstPublish: (value: {body: Record<string, never>}) => void;
|
|
262
|
+
const firstPublishPromise = new Promise<{body: Record<string, never>}>((resolve) => {
|
|
263
|
+
resolveFirstPublish = resolve;
|
|
264
|
+
});
|
|
265
|
+
webex.request = jest
|
|
266
|
+
.fn()
|
|
267
|
+
.mockReturnValueOnce(firstPublishPromise)
|
|
268
|
+
.mockResolvedValueOnce({body: {}});
|
|
269
|
+
|
|
270
|
+
const firstPublishCall = service.setManageWebexCallingInWxcc(true, {
|
|
271
|
+
trackPublishMetrics: true,
|
|
272
|
+
});
|
|
273
|
+
const secondPublishCall = service.setManageWebexCallingInWxcc(false, {
|
|
274
|
+
trackPublishMetrics: true,
|
|
275
|
+
});
|
|
276
|
+
|
|
277
|
+
await secondPublishCall;
|
|
278
|
+
service.teardown();
|
|
279
|
+
resolveFirstPublish!({body: {}});
|
|
280
|
+
await firstPublishCall;
|
|
281
|
+
|
|
282
|
+
const successCall = trackEventSpy.mock.calls.find(
|
|
283
|
+
([eventName, payload]) =>
|
|
284
|
+
eventName === METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS &&
|
|
285
|
+
(payload as {enableWxBetterTogether?: boolean}).enableWxBetterTogether === false
|
|
286
|
+
);
|
|
287
|
+
expect(successCall?.[1]).toEqual(
|
|
288
|
+
expect.objectContaining({
|
|
289
|
+
enableWxBetterTogether: false,
|
|
290
|
+
duration_ms: expect.any(Number),
|
|
291
|
+
})
|
|
292
|
+
);
|
|
293
|
+
|
|
294
|
+
RealMetricsManager.resetInstance();
|
|
295
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({
|
|
296
|
+
trackEvent,
|
|
297
|
+
timeEvent,
|
|
298
|
+
cancelTimedEvent,
|
|
299
|
+
});
|
|
300
|
+
});
|
|
301
|
+
|
|
302
|
+
it('does not track usersub metrics by default', async () => {
|
|
303
|
+
await service.setManageWebexCallingInWxcc(true);
|
|
304
|
+
|
|
305
|
+
expect(trackEvent).not.toHaveBeenCalled();
|
|
306
|
+
expect(timeEvent).not.toHaveBeenCalled();
|
|
101
307
|
});
|
|
102
308
|
|
|
103
309
|
it('uses custom ttl from options', async () => {
|
|
@@ -2,15 +2,28 @@ import WxAppTelephonyMercurySync, {
|
|
|
2
2
|
TELEPHONY_CALL_MUTED,
|
|
3
3
|
TELEPHONY_CALL_UNMUTED,
|
|
4
4
|
} from '../../../../src/services/WxAppTelephonyMercurySync';
|
|
5
|
+
import MetricsManager from '../../../../src/metrics/MetricsManager';
|
|
6
|
+
import {METRIC_EVENT_NAMES} from '../../../../src/metrics/constants';
|
|
7
|
+
|
|
8
|
+
jest.mock('../../../../src/metrics/MetricsManager', () => ({
|
|
9
|
+
__esModule: true,
|
|
10
|
+
default: {
|
|
11
|
+
getInstance: jest.fn(),
|
|
12
|
+
},
|
|
13
|
+
}));
|
|
5
14
|
|
|
6
15
|
describe('WxAppTelephonyMercurySync', () => {
|
|
7
16
|
const agentId = 'agent-123';
|
|
17
|
+
const trackEvent = jest.fn();
|
|
8
18
|
let mercury: {on: jest.Mock; off: jest.Mock};
|
|
9
19
|
let webex: {internal: {mercury?: typeof mercury}};
|
|
10
20
|
let sync: WxAppTelephonyMercurySync;
|
|
11
21
|
let onMuteChange: jest.Mock;
|
|
12
22
|
|
|
13
23
|
beforeEach(() => {
|
|
24
|
+
trackEvent.mockClear();
|
|
25
|
+
(MetricsManager.getInstance as jest.Mock).mockReturnValue({trackEvent});
|
|
26
|
+
|
|
14
27
|
mercury = {on: jest.fn(), off: jest.fn()};
|
|
15
28
|
webex = {internal: {mercury}};
|
|
16
29
|
sync = new WxAppTelephonyMercurySync(webex as never);
|
|
@@ -27,6 +40,11 @@ describe('WxAppTelephonyMercurySync', () => {
|
|
|
27
40
|
expect(mercury.on).toHaveBeenCalledWith(TELEPHONY_CALL_MUTED, expect.any(Function));
|
|
28
41
|
expect(mercury.on).toHaveBeenCalledWith(TELEPHONY_CALL_UNMUTED, expect.any(Function));
|
|
29
42
|
expect(sync.isSubscribed()).toBe(true);
|
|
43
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
44
|
+
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_SUCCESS,
|
|
45
|
+
{},
|
|
46
|
+
['operational', 'behavioral']
|
|
47
|
+
);
|
|
30
48
|
});
|
|
31
49
|
|
|
32
50
|
it('invokes callback with decoded callId and muted when actorId matches agent', () => {
|
|
@@ -77,6 +95,11 @@ describe('WxAppTelephonyMercurySync', () => {
|
|
|
77
95
|
|
|
78
96
|
expect(sync.isSubscribed()).toBe(false);
|
|
79
97
|
expect(onMuteChange).not.toHaveBeenCalled();
|
|
98
|
+
expect(trackEvent).toHaveBeenCalledWith(
|
|
99
|
+
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
|
|
100
|
+
expect.objectContaining({error: expect.stringContaining('Mercury is unavailable')}),
|
|
101
|
+
['operational', 'behavioral']
|
|
102
|
+
);
|
|
80
103
|
});
|
|
81
104
|
|
|
82
105
|
it('invokes callback with muted false on unmuted Mercury event', () => {
|