@webex/contact-center 3.12.0-next.8 → 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.
- package/AGENTS.md +438 -0
- package/ai-docs/README.md +131 -0
- package/ai-docs/RULES.md +455 -0
- package/ai-docs/patterns/event-driven-patterns.md +485 -0
- package/ai-docs/patterns/testing-patterns.md +480 -0
- package/ai-docs/patterns/typescript-patterns.md +365 -0
- package/ai-docs/templates/README.md +102 -0
- package/ai-docs/templates/documentation/create-agents-md.md +240 -0
- package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
- package/ai-docs/templates/existing-service/bug-fix.md +254 -0
- package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
- package/ai-docs/templates/new-method/00-master.md +80 -0
- package/ai-docs/templates/new-method/01-requirements.md +232 -0
- package/ai-docs/templates/new-method/02-implementation.md +295 -0
- package/ai-docs/templates/new-method/03-tests.md +201 -0
- package/ai-docs/templates/new-method/04-validation.md +141 -0
- package/ai-docs/templates/new-service/00-master.md +109 -0
- package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
- package/ai-docs/templates/new-service/02-code-generation.md +346 -0
- package/ai-docs/templates/new-service/03-integration.md +178 -0
- package/ai-docs/templates/new-service/04-test-generation.md +205 -0
- package/ai-docs/templates/new-service/05-validation.md +145 -0
- package/dist/cc.js +65 -123
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +13 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +26 -13
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +7 -6
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +0 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/Util.js +2 -3
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/types.js +16 -14
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js +0 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +79 -55
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +17 -92
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +5 -25
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -2
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +644 -0
- package/dist/services/task/Task.js.map +1 -0
- package/dist/services/task/TaskFactory.js +45 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +556 -532
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +132 -28
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +7 -6
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +0 -51
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/digital/Digital.js +77 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +366 -0
- package/dist/services/task/state-machine/actions.js.map +1 -0
- package/dist/services/task/state-machine/constants.js +139 -0
- package/dist/services/task/state-machine/constants.js.map +1 -0
- package/dist/services/task/state-machine/guards.js +256 -0
- package/dist/services/task/state-machine/guards.js.map +1 -0
- package/dist/services/task/state-machine/index.js +53 -0
- package/dist/services/task/state-machine/index.js.map +1 -0
- package/dist/services/task/state-machine/types.js +54 -0
- package/dist/services/task/state-machine/types.js.map +1 -0
- package/dist/services/task/state-machine/uiControlsComputer.js +369 -0
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
- package/dist/services/task/taskDataNormalizer.js +99 -0
- package/dist/services/task/taskDataNormalizer.js.map +1 -0
- package/dist/services/task/types.js +157 -18
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1031 -0
- package/dist/services/task/voice/Voice.js.map +1 -0
- package/dist/services/task/voice/WebRTC.js +149 -0
- package/dist/services/task/voice/WebRTC.js.map +1 -0
- package/dist/types/cc.d.ts +4 -33
- package/dist/types/constants.d.ts +13 -2
- package/dist/types/index.d.ts +11 -5
- package/dist/types/metrics/constants.d.ts +5 -3
- package/dist/types/services/ApiAiAssistant.d.ts +1 -1
- package/dist/types/services/config/types.d.ts +97 -25
- package/dist/types/services/core/Err.d.ts +0 -2
- package/dist/types/services/core/Utils.d.ts +25 -23
- package/dist/types/services/core/aqm-reqs.d.ts +0 -49
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
- package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
- package/dist/types/services/core/websocket/types.d.ts +1 -1
- package/dist/types/services/index.d.ts +1 -1
- package/dist/types/services/task/Task.d.ts +146 -0
- package/dist/types/services/task/TaskFactory.d.ts +12 -0
- package/dist/types/services/task/TaskUtils.d.ts +39 -8
- package/dist/types/services/task/constants.d.ts +5 -4
- package/dist/types/services/task/dialer.d.ts +0 -15
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
- package/dist/types/services/task/state-machine/actions.d.ts +8 -0
- package/dist/types/services/task/state-machine/constants.d.ts +91 -0
- package/dist/types/services/task/state-machine/guards.d.ts +78 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +256 -0
- package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
- package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
- package/dist/types/services/task/types.d.ts +539 -88
- package/dist/types/services/task/voice/Voice.d.ts +183 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/types.d.ts +68 -0
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +70 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +14 -11
- package/src/cc.ts +91 -177
- package/src/constants.ts +13 -2
- package/src/index.ts +14 -5
- package/src/metrics/ai-docs/AGENTS.md +348 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
- package/src/metrics/behavioral-events.ts +28 -14
- package/src/metrics/constants.ts +7 -8
- package/src/services/ApiAiAssistant.ts +2 -4
- package/src/services/agent/ai-docs/AGENTS.md +238 -0
- package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
- package/src/services/ai-docs/AGENTS.md +384 -0
- package/src/services/config/Util.ts +2 -3
- package/src/services/config/ai-docs/AGENTS.md +253 -0
- package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
- package/src/services/config/types.ts +108 -20
- package/src/services/constants.ts +0 -1
- package/src/services/core/Err.ts +0 -1
- package/src/services/core/Utils.ts +90 -67
- package/src/services/core/ai-docs/AGENTS.md +379 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
- package/src/services/core/aqm-reqs.ts +22 -100
- package/src/services/core/websocket/WebSocketManager.ts +4 -23
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -2
- package/src/services/task/Task.ts +785 -0
- package/src/services/task/TaskFactory.ts +55 -0
- package/src/services/task/TaskManager.ts +567 -633
- package/src/services/task/TaskUtils.ts +175 -31
- package/src/services/task/ai-docs/AGENTS.md +448 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
- package/src/services/task/constants.ts +5 -4
- package/src/services/task/dialer.ts +1 -56
- package/src/services/task/digital/Digital.ts +95 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
- package/src/services/task/state-machine/actions.ts +409 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
- package/src/services/task/state-machine/constants.ts +150 -0
- package/src/services/task/state-machine/guards.ts +295 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +228 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +529 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +641 -95
- package/src/services/task/voice/Voice.ts +1255 -0
- package/src/services/task/voice/WebRTC.ts +187 -0
- package/src/types.ts +88 -5
- package/src/utils/AGENTS.md +276 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +59 -142
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
- package/test/unit/spec/services/config/index.ts +26 -55
- package/test/unit/spec/services/core/Utils.ts +103 -52
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
- package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +416 -0
- package/test/unit/spec/services/task/TaskFactory.ts +62 -0
- package/test/unit/spec/services/task/TaskManager.ts +781 -1735
- package/test/unit/spec/services/task/TaskUtils.ts +125 -0
- package/test/unit/spec/services/task/dialer.ts +112 -198
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +587 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
- package/dist/services/task/index.js +0 -1525
- package/dist/services/task/index.js.map +0 -1
- package/dist/types/services/task/index.d.ts +0 -650
- package/src/services/task/index.ts +0 -1801
- package/test/unit/spec/services/task/index.ts +0 -2184
|
@@ -4,13 +4,16 @@ import {Failure, AugmentedError} from './GlobalTypes';
|
|
|
4
4
|
import LoggerProxy from '../../logger-proxy';
|
|
5
5
|
import WebexRequest from './WebexRequest';
|
|
6
6
|
import {
|
|
7
|
+
ConsultConferenceData,
|
|
8
|
+
consultConferencePayloadData,
|
|
9
|
+
ConsultTransferDestinationType,
|
|
7
10
|
TaskData,
|
|
8
|
-
ConsultTransferPayLoad,
|
|
9
11
|
CONSULT_TRANSFER_DESTINATION_TYPE,
|
|
12
|
+
DESTINATION_TYPE,
|
|
10
13
|
Interaction,
|
|
14
|
+
InteractionParticipant,
|
|
11
15
|
} from '../task/types';
|
|
12
16
|
import {PARTICIPANT_TYPES, STATE_CONSULT} from './constants';
|
|
13
|
-
import {DialPlan} from '../config/types';
|
|
14
17
|
|
|
15
18
|
/**
|
|
16
19
|
* Extracts common error details from a Webex request payload.
|
|
@@ -27,59 +30,11 @@ const getCommonErrorDetails = (errObj: WebexRequestPayload) => {
|
|
|
27
30
|
};
|
|
28
31
|
};
|
|
29
32
|
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
const isEntryPointOrEpdn = (destAgentType?: string): boolean => {
|
|
34
|
-
return destAgentType === 'EPDN' || destAgentType === 'ENTRYPOINT';
|
|
35
|
-
};
|
|
36
|
-
|
|
37
|
-
/**
|
|
38
|
-
* Determines if the task involves dialing a number based on the destination type.
|
|
39
|
-
* Returns 'DIAL_NUMBER' for dial-related destinations, empty string otherwise.
|
|
40
|
-
*/
|
|
41
|
-
const getAgentActionTypeFromTask = (taskData?: TaskData): 'DIAL_NUMBER' | '' => {
|
|
42
|
-
const destAgentType = taskData?.destinationType;
|
|
33
|
+
export const isValidDialNumber = (input: string): boolean => {
|
|
34
|
+
// This regex checks for a valid dial number format for only few countries such as US, Canada.
|
|
35
|
+
const regexForDn = /1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}/;
|
|
43
36
|
|
|
44
|
-
|
|
45
|
-
const isDialNumber = destAgentType === 'DN';
|
|
46
|
-
const isEntryPointVariant = isEntryPointOrEpdn(destAgentType);
|
|
47
|
-
|
|
48
|
-
// If the destination type is a dial number or an entry point variant, return 'DIAL_NUMBER'
|
|
49
|
-
return isDialNumber || isEntryPointVariant ? 'DIAL_NUMBER' : '';
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
// Fallback regex for US/Canada dial numbers when no dial plan entries are configured
|
|
53
|
-
export const FALLBACK_DIAL_NUMBER_REGEX = /1[0-9]{3}[2-9][0-9]{6}([,]{1,10}[0-9]+){0,1}/;
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* Validates a dial number against the provided dial plan regex patterns.
|
|
57
|
-
* A number is valid if it matches at least one regex pattern in the dial plans.
|
|
58
|
-
* Falls back to US/Canada regex validation if no dial plan entries are configured.
|
|
59
|
-
*
|
|
60
|
-
* @param input - The dial number to validate
|
|
61
|
-
* @param dialPlanEntries - Array of dial plan entries containing regex patterns
|
|
62
|
-
* @returns true if the input matches at least one dial plan regex pattern, false otherwise
|
|
63
|
-
*/
|
|
64
|
-
export const isValidDialNumber = (
|
|
65
|
-
input: string,
|
|
66
|
-
dialPlanEntries: DialPlan['dialPlanEntity']
|
|
67
|
-
): boolean => {
|
|
68
|
-
if (!dialPlanEntries || dialPlanEntries.length === 0) {
|
|
69
|
-
LoggerProxy.info('No dial plan entries found. Falling back to US number validation.');
|
|
70
|
-
|
|
71
|
-
return FALLBACK_DIAL_NUMBER_REGEX.test(input);
|
|
72
|
-
}
|
|
73
|
-
|
|
74
|
-
return dialPlanEntries.some((entry) => {
|
|
75
|
-
try {
|
|
76
|
-
const regex = new RegExp(entry.regex);
|
|
77
|
-
|
|
78
|
-
return regex.test(input);
|
|
79
|
-
} catch {
|
|
80
|
-
return false;
|
|
81
|
-
}
|
|
82
|
-
});
|
|
37
|
+
return regexForDn.test(input);
|
|
83
38
|
};
|
|
84
39
|
|
|
85
40
|
export const getStationLoginErrorData = (failure: Failure, loginOption: LoginOption) => {
|
|
@@ -101,7 +56,7 @@ export const getStationLoginErrorData = (failure: Failure, loginOption: LoginOpt
|
|
|
101
56
|
},
|
|
102
57
|
INVALID_DIAL_NUMBER: {
|
|
103
58
|
message:
|
|
104
|
-
'Enter a valid dial number. For help, reach out to your administrator or support team.',
|
|
59
|
+
'Enter a valid US dial number. For help, reach out to your administrator or support team.',
|
|
105
60
|
fieldName: loginOption,
|
|
106
61
|
},
|
|
107
62
|
};
|
|
@@ -243,7 +198,7 @@ export const createErrDetailsObject = (errObj: WebexRequestPayload) => {
|
|
|
243
198
|
return new Err.Details('Service.reqs.generic.failure', details);
|
|
244
199
|
};
|
|
245
200
|
|
|
246
|
-
|
|
201
|
+
/*
|
|
247
202
|
* Gets the consulted agent ID from the media object by finding the agent
|
|
248
203
|
* in the consult media participants (excluding the current agent).
|
|
249
204
|
*
|
|
@@ -321,9 +276,16 @@ export const calculateDestAgentId = (interaction: Interaction, agentId: string):
|
|
|
321
276
|
return destAgentIdCBT;
|
|
322
277
|
}
|
|
323
278
|
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
279
|
+
const participant = interaction.participants[consultingAgent];
|
|
280
|
+
if (!participant) {
|
|
281
|
+
return '';
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
if (participant.type === PARTICIPANT_TYPES.EP_DN) {
|
|
285
|
+
return (participant as InteractionParticipant & {epId?: string}).epId ?? '';
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
return participant.id ?? '';
|
|
327
289
|
};
|
|
328
290
|
|
|
329
291
|
/**
|
|
@@ -358,16 +320,77 @@ export const calculateDestType = (interaction: Interaction, agentId: string): st
|
|
|
358
320
|
return CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
|
|
359
321
|
};
|
|
360
322
|
|
|
323
|
+
/**
|
|
324
|
+
* Gets the destination agent ID from participants.
|
|
325
|
+
* Finds a participant who is not the current agent and is an agent type.
|
|
326
|
+
*
|
|
327
|
+
* @param participants - The participants object from interaction
|
|
328
|
+
* @param agentId - The current agent's ID
|
|
329
|
+
* @returns The destination agent ID, or undefined if none found
|
|
330
|
+
*/
|
|
331
|
+
export const buildConsultConferenceParamData = (
|
|
332
|
+
dataPassed: consultConferencePayloadData,
|
|
333
|
+
interactionIdPassed: string
|
|
334
|
+
): {interactionId: string; data: ConsultConferenceData} => {
|
|
335
|
+
const data: ConsultConferenceData = {
|
|
336
|
+
...('agentId' in dataPassed && {agentId: dataPassed.agentId}),
|
|
337
|
+
to: dataPassed.destAgentId,
|
|
338
|
+
destinationType: '',
|
|
339
|
+
};
|
|
340
|
+
|
|
341
|
+
if ('destinationType' in dataPassed) {
|
|
342
|
+
const destinationType = String(dataPassed.destinationType || '').trim();
|
|
343
|
+
const normalizedDestinationType = destinationType.toUpperCase().replace(/[-_\s]/g, '');
|
|
344
|
+
|
|
345
|
+
if (normalizedDestinationType === 'DN' || normalizedDestinationType === 'DIALNUMBER') {
|
|
346
|
+
data.destinationType = DESTINATION_TYPE.DIALNUMBER;
|
|
347
|
+
} else if (normalizedDestinationType === 'EPDN' || normalizedDestinationType === 'ENTRYPOINT') {
|
|
348
|
+
data.destinationType = DESTINATION_TYPE.ENTRYPOINT;
|
|
349
|
+
} else if (normalizedDestinationType === 'QUEUE') {
|
|
350
|
+
data.destinationType = DESTINATION_TYPE.QUEUE;
|
|
351
|
+
} else if (normalizedDestinationType === 'AGENT') {
|
|
352
|
+
data.destinationType = DESTINATION_TYPE.AGENT;
|
|
353
|
+
} else {
|
|
354
|
+
data.destinationType = destinationType as ConsultConferenceData['destinationType'];
|
|
355
|
+
}
|
|
356
|
+
} else {
|
|
357
|
+
data.destinationType = DESTINATION_TYPE.AGENT;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
return {
|
|
361
|
+
interactionId: interactionIdPassed,
|
|
362
|
+
data,
|
|
363
|
+
};
|
|
364
|
+
};
|
|
365
|
+
|
|
366
|
+
/**
|
|
367
|
+
* Derives the consult transfer destination type based on task data.
|
|
368
|
+
* This function determines the appropriate destination type for a consult transfer
|
|
369
|
+
* by examining the destination type stored in the task data.
|
|
370
|
+
*
|
|
371
|
+
* @param taskData - The task data containing destination information
|
|
372
|
+
* @returns The derived consult transfer destination type
|
|
373
|
+
* @public
|
|
374
|
+
*/
|
|
361
375
|
export const deriveConsultTransferDestinationType = (
|
|
362
|
-
taskData
|
|
363
|
-
):
|
|
364
|
-
const
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
376
|
+
taskData: TaskData
|
|
377
|
+
): ConsultTransferDestinationType => {
|
|
378
|
+
const destType = taskData?.destinationType;
|
|
379
|
+
const normalizedDestType = String(destType || '')
|
|
380
|
+
.toUpperCase()
|
|
381
|
+
.replace(/[-_\s]/g, '');
|
|
382
|
+
|
|
383
|
+
// Map destination types to consult transfer destination types
|
|
384
|
+
if (normalizedDestType === 'DN' || normalizedDestType === 'DIALNUMBER') {
|
|
385
|
+
return CONSULT_TRANSFER_DESTINATION_TYPE.DIALNUMBER;
|
|
386
|
+
}
|
|
387
|
+
if (normalizedDestType === 'EPDN' || normalizedDestType === 'ENTRYPOINT') {
|
|
388
|
+
return CONSULT_TRANSFER_DESTINATION_TYPE.ENTRYPOINT;
|
|
389
|
+
}
|
|
390
|
+
if (normalizedDestType === 'QUEUE') {
|
|
391
|
+
return CONSULT_TRANSFER_DESTINATION_TYPE.QUEUE;
|
|
370
392
|
}
|
|
371
393
|
|
|
394
|
+
// Default to agent if no specific type matches
|
|
372
395
|
return CONSULT_TRANSFER_DESTINATION_TYPE.AGENT;
|
|
373
396
|
};
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
# Core Service - AI Agent Guide
|
|
2
|
+
|
|
3
|
+
> **This is the authoritative documentation for the Core service scope.** Core infrastructure components including WebSocket management, HTTP requests, error handling, and utilities. For task routing, critical rules, and cross-service patterns, see the [root orchestrator AGENTS.md](../../../AGENTS.md).
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Key Capabilities
|
|
8
|
+
|
|
9
|
+
The Core service provides the foundational infrastructure layer that all other services depend on:
|
|
10
|
+
|
|
11
|
+
- **WebSocket Communication**: Real-time bidirectional messaging with the contact center backend, including automatic reconnection and keepalive management
|
|
12
|
+
- **HTTP Request Handling**: Authenticated REST API calls to WCC API Gateway with built-in error handling and log upload support
|
|
13
|
+
- **AQM Request/Response Pattern**: A structured pattern used by the routing and contact layers to send HTTP requests to the contact center backend and correlate responses/failures via WebSocket notifications
|
|
14
|
+
- **Error Handling & Logging**: Standardized error extraction, logging via `LoggerProxy`, and log upload utilities that all services use for consistent error reporting
|
|
15
|
+
|
|
16
|
+
| Component | File | Description |
|
|
17
|
+
|-----------|------|-------------|
|
|
18
|
+
| `WebSocketManager` | [`WebSocketManager.ts`](../websocket/WebSocketManager.ts) | Manages the WebSocket connection lifecycle including initialization, message dispatch, and graceful shutdown. Emits `message` events for incoming data and `socketClose` when the connection drops while reconnect is allowed. |
|
|
19
|
+
| `ConnectionService` | [`connection-service.ts`](../websocket/connection-service.ts) | Orchestrates reconnection logic and keepalive heartbeats on top of `WebSocketManager`. Detects connection loss and triggers `silentRelogin()` to restore agent state transparently. |
|
|
20
|
+
| `WebexRequest` | [`WebexRequest.ts`](../WebexRequest.ts) | Singleton HTTP client that wraps authenticated requests to the WCC API Gateway. Handles service routing, response parsing, and provides a `uploadLogs` method for diagnostics. |
|
|
21
|
+
| `AqmReqs` | [`aqm-reqs.ts`](../aqm-reqs.ts) | Factory for creating request methods that send HTTP requests and wait for correlated WebSocket notifications (success or failure). Used by routing and task services to implement their API methods. |
|
|
22
|
+
| `Utils` | [`Utils.ts`](../Utils.ts) | Shared utility functions including `getErrorDetails()` for standardized error handling, `generateTaskErrorObject()` for task-specific errors, and `createErrDetailsObject()` for constructing error detail objects. |
|
|
23
|
+
| `Err` | [`Err.ts`](../Err.ts) | Error class definitions. `Err.Details` carries structured error metadata (status, type, trackingId) for consistent error propagation. |
|
|
24
|
+
| `constants` | [`constants.ts`](../constants.ts) | Timeout values, interval durations, participant types, interaction states, and method name constants used throughout the core layer. Any new constants for core should be defined here. |
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## File Structure
|
|
29
|
+
|
|
30
|
+
```
|
|
31
|
+
services/core/
|
|
32
|
+
├── aqm-reqs.ts # AQM request handler
|
|
33
|
+
├── constants.ts # Core constants
|
|
34
|
+
├── Err.ts # Error classes
|
|
35
|
+
├── GlobalTypes.ts # Failure, Msg<T>, etc.
|
|
36
|
+
├── types.ts # Request/response types
|
|
37
|
+
├── Utils.ts # Utility functions
|
|
38
|
+
├── WebexRequest.ts # HTTP client
|
|
39
|
+
└── websocket/
|
|
40
|
+
├── WebSocketManager.ts # Main WS handler
|
|
41
|
+
├── connection-service.ts # Connection lifecycle
|
|
42
|
+
├── keepalive.worker.js # Keepalive worker
|
|
43
|
+
└── types.ts # WS types
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
---
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
## WebSocketManager
|
|
50
|
+
|
|
51
|
+
`WebSocketManager` handles the raw WebSocket connection to the contact center backend. It is instantiated by the `Services` layer and used internally — other services interact with it through `ConnectionService`.
|
|
52
|
+
|
|
53
|
+
### Reference Usage
|
|
54
|
+
|
|
55
|
+
```typescript
|
|
56
|
+
// Initialize in Services
|
|
57
|
+
this.webSocketManager = new WebSocketManager({webex});
|
|
58
|
+
|
|
59
|
+
// Connect
|
|
60
|
+
const welcomeEvent = await webSocketManager.initWebSocket({
|
|
61
|
+
body: connectionConfig,
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// Listen for messages
|
|
65
|
+
webSocketManager.on('message', (event) => {
|
|
66
|
+
const data = JSON.parse(event);
|
|
67
|
+
// Handle event
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
// Close
|
|
71
|
+
webSocketManager.close(false, 'Reason');
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Events
|
|
75
|
+
|
|
76
|
+
| Event | Data | Description |
|
|
77
|
+
|-------|------|-------------|
|
|
78
|
+
| `message` | string (JSON) | WebSocket message received |
|
|
79
|
+
| `socketClose` | - | Socket closed while reconnect is allowed |
|
|
80
|
+
|
|
81
|
+
### Connection Lifecycle
|
|
82
|
+
|
|
83
|
+
`WebSocketManager` and `ConnectionService` work together to manage the full connection lifecycle. `WebSocketManager` owns the raw socket while `ConnectionService` adds reconnection intelligence and keepalive on top.
|
|
84
|
+
|
|
85
|
+
**Connection Flow:**
|
|
86
|
+
|
|
87
|
+
```
|
|
88
|
+
1. initWebSocket() called
|
|
89
|
+
2. ConnectionService listeners attached (`message`, `socketClose`) during construction
|
|
90
|
+
3. WebSocket connects to backend
|
|
91
|
+
4. Keepalive worker started on `onopen`
|
|
92
|
+
5. Welcome event received
|
|
93
|
+
6. Runtime messages/keepalive processed via existing listeners
|
|
94
|
+
```
|
|
95
|
+
|
|
96
|
+
**Reconnection Flow:**
|
|
97
|
+
|
|
98
|
+
```
|
|
99
|
+
1. Connection lost detected
|
|
100
|
+
2. ConnectionService emits 'connectionLost'
|
|
101
|
+
3. cc.handleConnectionLost() called
|
|
102
|
+
4. silentRelogin() attempted
|
|
103
|
+
5. On success: state restored
|
|
104
|
+
6. On AGENT_NOT_FOUND: handle silently
|
|
105
|
+
```
|
|
106
|
+
|
|
107
|
+
### Keepalive
|
|
108
|
+
|
|
109
|
+
A Web Worker ([`keepalive.worker.js`](../websocket/keepalive.worker.js)) runs alongside the WebSocket to detect connection loss and keep the socket alive. It starts on `onopen` and sends `{keepalive: 'true'}` to the backend every **4 seconds** (`KEEPALIVE_WORKER_INTERVAL`). The worker also monitors `navigator.onLine` — if the network goes offline and the socket doesn't close within **16 seconds** (`CLOSE_SOCKET_TIMEOUT`), it force-closes the socket.
|
|
110
|
+
|
|
111
|
+
On the receiving side, `ConnectionService.onPing()` listens for all WebSocket messages and resets two timers on each message:
|
|
112
|
+
- **`reconnectingTimer`** (8s / `WS_DISCONNECT_ALLOWED`) — if no message arrives within 8s, marks connection as lost
|
|
113
|
+
- **`restoreTimer`** (`lostConnectionRecoveryTimeout` from agent config) — if connection isn't restored within this window, marks restore as failed
|
|
114
|
+
|
|
115
|
+
When a keepalive response arrives after a lost-connection state, `ConnectionService` resets its flags and dispatches a recovery event. If the socket fully closes, `ConnectionService` retries `initWebSocket()` every **5 seconds** (`CONNECTIVITY_CHECK_INTERVAL`).
|
|
116
|
+
|
|
117
|
+
---
|
|
118
|
+
|
|
119
|
+
## WebexRequest
|
|
120
|
+
|
|
121
|
+
`WebexRequest` is a singleton HTTP client that all services use to make authenticated REST API calls to the contact center backend. It handles service URL resolution, request formatting, and response parsing.
|
|
122
|
+
|
|
123
|
+
### The `service` Property
|
|
124
|
+
|
|
125
|
+
The `service` field in request options is a **service identifier string** that the Webex SDK's internal service catalog (`this.webex.request()`) resolves to a base URL at runtime. All contact center API calls use:
|
|
126
|
+
|
|
127
|
+
```typescript
|
|
128
|
+
import {WCC_API_GATEWAY} from '../constants';
|
|
129
|
+
// WCC_API_GATEWAY = 'wcc-api-gateway'
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
This constant is defined in [`services/constants.ts`](../../constants.ts). The Webex SDK maps `'wcc-api-gateway'` to the appropriate contact center API gateway URL based on the environment. The `resource` is then appended as the path.
|
|
133
|
+
|
|
134
|
+
### Reference Usage
|
|
135
|
+
|
|
136
|
+
```typescript
|
|
137
|
+
// Get singleton
|
|
138
|
+
const webexReq = WebexRequest.getInstance({webex});
|
|
139
|
+
|
|
140
|
+
// Make request
|
|
141
|
+
const response = await webexReq.request({
|
|
142
|
+
service: WCC_API_GATEWAY, // resolved to base URL by Webex SDK
|
|
143
|
+
resource: '/v1/endpoint', // appended as path
|
|
144
|
+
method: HTTP_METHODS.POST,
|
|
145
|
+
body: { key: 'value' }, // optional request payload
|
|
146
|
+
});
|
|
147
|
+
|
|
148
|
+
// Upload logs
|
|
149
|
+
await webexReq.uploadLogs({
|
|
150
|
+
correlationId: trackingId,
|
|
151
|
+
});
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
### Response Structure
|
|
155
|
+
|
|
156
|
+
```typescript
|
|
157
|
+
{
|
|
158
|
+
statusCode: 200,
|
|
159
|
+
body: { /* response data */ },
|
|
160
|
+
headers: {
|
|
161
|
+
trackingid: 'uuid',
|
|
162
|
+
},
|
|
163
|
+
}
|
|
164
|
+
```
|
|
165
|
+
|
|
166
|
+
---
|
|
167
|
+
|
|
168
|
+
## AqmReqs
|
|
169
|
+
|
|
170
|
+
The AQM (Agent Queue Manager) layer provides a request/response pattern over HTTP + WebSocket. Services like **routing** and **contact** use `AqmReqs` to define API methods that:
|
|
171
|
+
|
|
172
|
+
1. Send an HTTP request to the contact center backend via `WebexRequest`
|
|
173
|
+
2. Wait for a correlated WebSocket notification indicating success or failure
|
|
174
|
+
3. Return the typed result or throw a structured error
|
|
175
|
+
|
|
176
|
+
This decouples the request initiation (HTTP) from the asynchronous result delivery (WebSocket), which matches the contact center backend's event-driven architecture.
|
|
177
|
+
|
|
178
|
+
### Reference Usage
|
|
179
|
+
|
|
180
|
+
```typescript
|
|
181
|
+
// Define request configuration
|
|
182
|
+
const serviceMethod = routing.req((p: {data: ParamType}) => ({
|
|
183
|
+
url: '/v1/endpoint',
|
|
184
|
+
host: WCC_API_GATEWAY,
|
|
185
|
+
data: p.data,
|
|
186
|
+
method: HTTP_METHODS.POST,
|
|
187
|
+
err: errorHandler,
|
|
188
|
+
notifSuccess: {
|
|
189
|
+
bind: {type: CC_EVENTS.SUCCESS, data: {type: CC_EVENTS.SUCCESS}},
|
|
190
|
+
msg: {} as SuccessType,
|
|
191
|
+
},
|
|
192
|
+
notifFail: {
|
|
193
|
+
bind: {type: CC_EVENTS.FAIL, data: {type: CC_EVENTS.FAIL}},
|
|
194
|
+
errId: 'Service.aqm.operation',
|
|
195
|
+
},
|
|
196
|
+
}));
|
|
197
|
+
|
|
198
|
+
// Call method
|
|
199
|
+
const result = await serviceMethod({data: params});
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
---
|
|
203
|
+
|
|
204
|
+
## Error Handling Utilities
|
|
205
|
+
|
|
206
|
+
### `getErrorDetails()`
|
|
207
|
+
|
|
208
|
+
Standard error handler for SDK operations:
|
|
209
|
+
|
|
210
|
+
```typescript
|
|
211
|
+
import {getErrorDetails} from './services/core/Utils';
|
|
212
|
+
|
|
213
|
+
try {
|
|
214
|
+
await operation();
|
|
215
|
+
} catch (error) {
|
|
216
|
+
const {error: detailedError, reason} = getErrorDetails(
|
|
217
|
+
error,
|
|
218
|
+
'methodName', // Method name for logging
|
|
219
|
+
'ModuleName' // Module name for logging
|
|
220
|
+
);
|
|
221
|
+
|
|
222
|
+
// getErrorDetails automatically:
|
|
223
|
+
// 1. Logs the error
|
|
224
|
+
// 2. Uploads logs (unless AGENT_NOT_FOUND in silentRelogin)
|
|
225
|
+
// 3. Extracts reason from error.details
|
|
226
|
+
// 4. Creates Error with reason as message
|
|
227
|
+
|
|
228
|
+
throw detailedError;
|
|
229
|
+
}
|
|
230
|
+
```
|
|
231
|
+
|
|
232
|
+
### `generateTaskErrorObject()`
|
|
233
|
+
|
|
234
|
+
Error handler for task operations:
|
|
235
|
+
|
|
236
|
+
```typescript
|
|
237
|
+
import {generateTaskErrorObject} from './services/core/Utils';
|
|
238
|
+
|
|
239
|
+
try {
|
|
240
|
+
await taskOperation();
|
|
241
|
+
} catch (error) {
|
|
242
|
+
const taskError = generateTaskErrorObject(
|
|
243
|
+
error,
|
|
244
|
+
'transfer',
|
|
245
|
+
'TaskModule'
|
|
246
|
+
);
|
|
247
|
+
throw taskError;
|
|
248
|
+
}
|
|
249
|
+
```
|
|
250
|
+
|
|
251
|
+
---
|
|
252
|
+
|
|
253
|
+
## Type Definitions
|
|
254
|
+
|
|
255
|
+
### Msg\<T\>
|
|
256
|
+
|
|
257
|
+
Generic message wrapper used throughout the SDK. All WebSocket messages and AQM responses conform to this shape:
|
|
258
|
+
|
|
259
|
+
```typescript
|
|
260
|
+
export type Msg<T = any> = {
|
|
261
|
+
type: string; // Message/Event type identifier
|
|
262
|
+
orgId: string; // Organization identifier
|
|
263
|
+
trackingId: string; // Unique tracking identifier
|
|
264
|
+
data: T; // Message/Event payload data
|
|
265
|
+
};
|
|
266
|
+
|
|
267
|
+
// Usage — the payload type goes into `data`
|
|
268
|
+
export type LoginSuccess = Msg<{
|
|
269
|
+
agentId: string;
|
|
270
|
+
status: string;
|
|
271
|
+
// ...
|
|
272
|
+
}>;
|
|
273
|
+
// Resulting shape: { type, orgId, trackingId, data: { agentId, status, ... } }
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
### Failure
|
|
277
|
+
|
|
278
|
+
A specific `Msg<T>` for failure responses. Access error details via `failure.data`:
|
|
279
|
+
|
|
280
|
+
```typescript
|
|
281
|
+
export type Failure = Msg<{
|
|
282
|
+
agentId: string;
|
|
283
|
+
trackingId: string;
|
|
284
|
+
reasonCode: number;
|
|
285
|
+
orgId: string;
|
|
286
|
+
reason: string;
|
|
287
|
+
}>;
|
|
288
|
+
|
|
289
|
+
// Usage in catch
|
|
290
|
+
const failure = error.details as Failure;
|
|
291
|
+
LoggerProxy.error(`Operation failed: ${failure.data?.reason}`, {
|
|
292
|
+
module: 'MyService',
|
|
293
|
+
method: 'myMethod',
|
|
294
|
+
trackingId: failure?.trackingId,
|
|
295
|
+
});
|
|
296
|
+
```
|
|
297
|
+
|
|
298
|
+
### AugmentedError
|
|
299
|
+
|
|
300
|
+
Error interface with a flexible data field for additional context:
|
|
301
|
+
|
|
302
|
+
```typescript
|
|
303
|
+
export interface AugmentedError extends Error {
|
|
304
|
+
data?: Record<string, any>;
|
|
305
|
+
}
|
|
306
|
+
```
|
|
307
|
+
|
|
308
|
+
---
|
|
309
|
+
|
|
310
|
+
## Error Classes
|
|
311
|
+
|
|
312
|
+
### Err.Details
|
|
313
|
+
|
|
314
|
+
```typescript
|
|
315
|
+
import * as Err from './Err';
|
|
316
|
+
|
|
317
|
+
const error = new Err.Details('Service.aqm.agent.login', {
|
|
318
|
+
status: 401,
|
|
319
|
+
type: 'UNAUTHORIZED',
|
|
320
|
+
trackingId: 'uuid',
|
|
321
|
+
});
|
|
322
|
+
```
|
|
323
|
+
|
|
324
|
+
### createErrDetailsObject
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
import {createErrDetailsObject} from './Utils';
|
|
328
|
+
|
|
329
|
+
const errDetails = createErrDetailsObject(webexRequestPayload);
|
|
330
|
+
// Returns Err.Details with trackingId and body
|
|
331
|
+
```
|
|
332
|
+
|
|
333
|
+
---
|
|
334
|
+
|
|
335
|
+
## Constants
|
|
336
|
+
|
|
337
|
+
All core-level constants (timeouts, intervals, participant types, method names) are defined in [`constants.ts`](../constants.ts). Any new constants for the core layer should be added there.
|
|
338
|
+
|
|
339
|
+
---
|
|
340
|
+
|
|
341
|
+
## Best Practices
|
|
342
|
+
|
|
343
|
+
### Always Use Error Utilities
|
|
344
|
+
|
|
345
|
+
```typescript
|
|
346
|
+
// Correct
|
|
347
|
+
const {error: detailedError} = getErrorDetails(error, method, module);
|
|
348
|
+
throw detailedError;
|
|
349
|
+
|
|
350
|
+
// Wrong - loses context
|
|
351
|
+
throw error;
|
|
352
|
+
```
|
|
353
|
+
|
|
354
|
+
### Check Response Status
|
|
355
|
+
|
|
356
|
+
```typescript
|
|
357
|
+
const response = await webexReq.request({...});
|
|
358
|
+
|
|
359
|
+
if (response.statusCode !== 200) {
|
|
360
|
+
throw new Error(`API call failed with ${response.statusCode}`);
|
|
361
|
+
}
|
|
362
|
+
```
|
|
363
|
+
|
|
364
|
+
### Extract TrackingId
|
|
365
|
+
|
|
366
|
+
```typescript
|
|
367
|
+
const trackingId = response.headers?.trackingid ||
|
|
368
|
+
response.headers?.TrackingID;
|
|
369
|
+
```
|
|
370
|
+
|
|
371
|
+
---
|
|
372
|
+
|
|
373
|
+
## Related
|
|
374
|
+
|
|
375
|
+
- [Root Orchestrator AGENTS.md](../../../AGENTS.md) - Task routing, critical rules, cross-service patterns
|
|
376
|
+
- [WebSocketManager.ts](../websocket/WebSocketManager.ts)
|
|
377
|
+
- [WebexRequest.ts](../WebexRequest.ts)
|
|
378
|
+
- [Utils.ts](../Utils.ts)
|
|
379
|
+
- [GlobalTypes.ts](../GlobalTypes.ts)
|