@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.
- 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 +570 -535
- 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 +372 -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 +263 -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 +377 -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 +579 -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 +422 -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 +303 -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 +542 -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
|
@@ -0,0 +1,205 @@
|
|
|
1
|
+
# New Service - Test Generation
|
|
2
|
+
|
|
3
|
+
> **Purpose**: Create unit tests for the new service.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Test File Location
|
|
8
|
+
|
|
9
|
+
Create: `test/unit/spec/services/ServiceName.ts`
|
|
10
|
+
|
|
11
|
+
---
|
|
12
|
+
|
|
13
|
+
## Test File Template
|
|
14
|
+
|
|
15
|
+
```typescript
|
|
16
|
+
import 'jsdom-global/register';
|
|
17
|
+
import MockWebex from '@webex/test-helper-mock-webex';
|
|
18
|
+
import ServiceName, {
|
|
19
|
+
ServiceListResponse,
|
|
20
|
+
ServiceSearchParams,
|
|
21
|
+
} from '../../../../src/services/ServiceName';
|
|
22
|
+
|
|
23
|
+
jest.mock('../../../../src/logger-proxy', () => ({
|
|
24
|
+
__esModule: true,
|
|
25
|
+
default: {
|
|
26
|
+
log: jest.fn(),
|
|
27
|
+
error: jest.fn(),
|
|
28
|
+
info: jest.fn(),
|
|
29
|
+
warn: jest.fn(),
|
|
30
|
+
trace: jest.fn(),
|
|
31
|
+
initialize: jest.fn(),
|
|
32
|
+
},
|
|
33
|
+
}));
|
|
34
|
+
|
|
35
|
+
describe('ServiceName', () => {
|
|
36
|
+
let webex: any;
|
|
37
|
+
let service: ServiceName;
|
|
38
|
+
|
|
39
|
+
// Mock data
|
|
40
|
+
const mockOrgId = 'mock-org-id';
|
|
41
|
+
const mockResponse: ServiceListResponse = {
|
|
42
|
+
data: [
|
|
43
|
+
{ id: 'item-1', name: 'Item 1' },
|
|
44
|
+
{ id: 'item-2', name: 'Item 2' },
|
|
45
|
+
],
|
|
46
|
+
meta: {
|
|
47
|
+
page: 0,
|
|
48
|
+
pageSize: 50,
|
|
49
|
+
totalPages: 1,
|
|
50
|
+
totalRecords: 2,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
webex = MockWebex({
|
|
56
|
+
logger: {
|
|
57
|
+
log: jest.fn(),
|
|
58
|
+
error: jest.fn(),
|
|
59
|
+
info: jest.fn(),
|
|
60
|
+
},
|
|
61
|
+
credentials: {
|
|
62
|
+
getOrgId: jest.fn(() => mockOrgId),
|
|
63
|
+
},
|
|
64
|
+
request: jest.fn(),
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
service = new ServiceName(webex);
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
afterEach(() => {
|
|
71
|
+
jest.clearAllMocks();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
describe('getItems', () => {
|
|
75
|
+
it('should fetch items successfully', async () => {
|
|
76
|
+
// Arrange
|
|
77
|
+
webex.request.mockResolvedValue({ body: mockResponse });
|
|
78
|
+
|
|
79
|
+
// Act
|
|
80
|
+
const result = await service.getItems();
|
|
81
|
+
|
|
82
|
+
// Assert
|
|
83
|
+
expect(result).toEqual(mockResponse);
|
|
84
|
+
expect(webex.request).toHaveBeenCalledWith({
|
|
85
|
+
method: 'GET',
|
|
86
|
+
uri: expect.stringContaining(mockOrgId),
|
|
87
|
+
});
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it('should apply pagination parameters', async () => {
|
|
91
|
+
// Arrange
|
|
92
|
+
webex.request.mockResolvedValue({ body: mockResponse });
|
|
93
|
+
const params: ServiceSearchParams = { page: 1, pageSize: 25 };
|
|
94
|
+
|
|
95
|
+
// Act
|
|
96
|
+
await service.getItems(params);
|
|
97
|
+
|
|
98
|
+
// Assert
|
|
99
|
+
expect(webex.request).toHaveBeenCalledWith({
|
|
100
|
+
method: 'GET',
|
|
101
|
+
uri: expect.stringContaining('page=1'),
|
|
102
|
+
});
|
|
103
|
+
expect(webex.request).toHaveBeenCalledWith({
|
|
104
|
+
method: 'GET',
|
|
105
|
+
uri: expect.stringContaining('pageSize=25'),
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
it('should apply search parameter', async () => {
|
|
110
|
+
// Arrange
|
|
111
|
+
webex.request.mockResolvedValue({ body: mockResponse });
|
|
112
|
+
const params: ServiceSearchParams = { search: 'test' };
|
|
113
|
+
|
|
114
|
+
// Act
|
|
115
|
+
await service.getItems(params);
|
|
116
|
+
|
|
117
|
+
// Assert
|
|
118
|
+
expect(webex.request).toHaveBeenCalledWith({
|
|
119
|
+
method: 'GET',
|
|
120
|
+
uri: expect.stringContaining('search=test'),
|
|
121
|
+
});
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
it('should throw error on API failure', async () => {
|
|
125
|
+
// Arrange
|
|
126
|
+
const mockError = new Error('API Error');
|
|
127
|
+
webex.request.mockRejectedValue(mockError);
|
|
128
|
+
|
|
129
|
+
// Act & Assert
|
|
130
|
+
await expect(service.getItems()).rejects.toThrow('API Error');
|
|
131
|
+
});
|
|
132
|
+
});
|
|
133
|
+
|
|
134
|
+
describe('getItemById', () => {
|
|
135
|
+
it('should fetch single item successfully', async () => {
|
|
136
|
+
// Arrange
|
|
137
|
+
const mockItem = { id: 'item-1', name: 'Item 1' };
|
|
138
|
+
webex.request.mockResolvedValue({ body: mockItem });
|
|
139
|
+
|
|
140
|
+
// Act
|
|
141
|
+
const result = await service.getItemById('item-1');
|
|
142
|
+
|
|
143
|
+
// Assert
|
|
144
|
+
expect(result).toEqual(mockItem);
|
|
145
|
+
expect(webex.request).toHaveBeenCalledWith({
|
|
146
|
+
method: 'GET',
|
|
147
|
+
uri: expect.stringContaining('item-1'),
|
|
148
|
+
});
|
|
149
|
+
});
|
|
150
|
+
|
|
151
|
+
it('should throw error if item not found', async () => {
|
|
152
|
+
// Arrange
|
|
153
|
+
const mockError = new Error('Not Found');
|
|
154
|
+
webex.request.mockRejectedValue(mockError);
|
|
155
|
+
|
|
156
|
+
// Act & Assert
|
|
157
|
+
await expect(service.getItemById('invalid-id')).rejects.toThrow('Not Found');
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## Test Integration with cc.ts
|
|
166
|
+
|
|
167
|
+
Add to `test/unit/spec/cc.ts`:
|
|
168
|
+
|
|
169
|
+
```typescript
|
|
170
|
+
describe('cc.serviceName', () => {
|
|
171
|
+
it('should initialize service on ready', () => {
|
|
172
|
+
expect(webex.cc.serviceName).toBeDefined();
|
|
173
|
+
});
|
|
174
|
+
|
|
175
|
+
it('should fetch items through service', async () => {
|
|
176
|
+
// Mock the service method
|
|
177
|
+
const mockResponse = { data: [], meta: {} };
|
|
178
|
+
jest.spyOn(webex.cc.serviceName, 'getItems').mockResolvedValue(mockResponse);
|
|
179
|
+
|
|
180
|
+
// Act
|
|
181
|
+
const result = await webex.cc.serviceName.getItems();
|
|
182
|
+
|
|
183
|
+
// Assert
|
|
184
|
+
expect(result).toEqual(mockResponse);
|
|
185
|
+
});
|
|
186
|
+
});
|
|
187
|
+
```
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Running Tests
|
|
192
|
+
|
|
193
|
+
```bash
|
|
194
|
+
# Run specific test file
|
|
195
|
+
yarn workspace @webex/contact-center test -- --testPathPattern=ServiceName
|
|
196
|
+
|
|
197
|
+
# Run with coverage
|
|
198
|
+
yarn workspace @webex/contact-center test -- --coverage --testPathPattern=ServiceName
|
|
199
|
+
```
|
|
200
|
+
|
|
201
|
+
---
|
|
202
|
+
|
|
203
|
+
## Next Step
|
|
204
|
+
|
|
205
|
+
Proceed to: [`05-validation.md`](05-validation.md)
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# New Service - Validation Checklist
|
|
2
|
+
|
|
3
|
+
> **Purpose**: Final quality check before completing service creation.
|
|
4
|
+
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
## Code Quality Checklist
|
|
8
|
+
|
|
9
|
+
### Service Class
|
|
10
|
+
- [ ] File created at correct location (based on placement from pre-questions Q3)
|
|
11
|
+
- [ ] Class has proper JSDoc with `@public` tag
|
|
12
|
+
- [ ] Methods have JSDoc with `@param`, `@returns`, `@example`
|
|
13
|
+
- [ ] Uses `LoggerProxy` for all logging (no `console.log`)
|
|
14
|
+
- [ ] Error handling uses `getErrorDetails` pattern (logs and re-throws)
|
|
15
|
+
- [ ] Module name constant defined (`SERVICE_FILE`)
|
|
16
|
+
- [ ] Method name constants defined (`METHODS`)
|
|
17
|
+
|
|
18
|
+
### Types & Constants
|
|
19
|
+
- [ ] Types placed in the correct location:
|
|
20
|
+
- Folder-based service: service folder's `types.ts`
|
|
21
|
+
- Single-file service: root `src/types.ts`
|
|
22
|
+
- Sub-module: parent service's `types.ts`
|
|
23
|
+
- [ ] Constants: no duplicates — every constant was searched across the hierarchy before adding:
|
|
24
|
+
- `src/constants.ts` (SDK-wide: file names, method names, global settings)
|
|
25
|
+
- `src/services/constants.ts` (shared: API gateways, auth, network)
|
|
26
|
+
- `src/metrics/constants.ts` (all metric event names)
|
|
27
|
+
- `src/services/config/constants.ts` (endpoint maps, pagination, agent states)
|
|
28
|
+
- `src/services/{ServiceName}/constants.ts` (service-specific only)
|
|
29
|
+
- [ ] Constants placed at the correct level (not duplicated at a lower level when shared exists)
|
|
30
|
+
- [ ] New `constants.ts` file created ONLY if service is folder-based AND no existing file AND constants are service-specific
|
|
31
|
+
- [ ] All public types have JSDoc
|
|
32
|
+
- [ ] Response types match actual API response
|
|
33
|
+
- [ ] Parameter types define all optional/required fields
|
|
34
|
+
|
|
35
|
+
### Metrics
|
|
36
|
+
- [ ] `metricsManager.timeEvent` called at method entry with success + failure event names
|
|
37
|
+
- [ ] `metricsManager.trackEvent` called on success path
|
|
38
|
+
- [ ] `metricsManager.trackEvent` called on failure path (in catch block)
|
|
39
|
+
- [ ] Metric event names added to `src/metrics/constants.ts` (`METRIC_EVENT_NAMES`)
|
|
40
|
+
|
|
41
|
+
### Integration
|
|
42
|
+
- [ ] Service initialized at the correct integration point:
|
|
43
|
+
- Folder-based / single-file: in `cc.ts` or `Services` singleton (depends on AQM vs non-AQM)
|
|
44
|
+
- Sub-module: instantiated by parent service
|
|
45
|
+
- [ ] Types re-exported from `src/types.ts` (if public)
|
|
46
|
+
|
|
47
|
+
### Tests
|
|
48
|
+
- [ ] Test file created mirroring source path under `test/unit/spec/`
|
|
49
|
+
- [ ] LoggerProxy mocked
|
|
50
|
+
- [ ] Success cases tested
|
|
51
|
+
- [ ] Error cases tested
|
|
52
|
+
- [ ] Metrics tracking verified (timeEvent and trackEvent calls asserted)
|
|
53
|
+
- [ ] Tests pass: `yarn workspace @webex/contact-center test:unit`
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Pattern Compliance
|
|
58
|
+
|
|
59
|
+
### LoggerProxy Usage
|
|
60
|
+
```typescript
|
|
61
|
+
// ✅ Correct
|
|
62
|
+
LoggerProxy.info('Starting operation', {
|
|
63
|
+
module: SERVICE_FILE,
|
|
64
|
+
method: METHODS.GET_ITEMS,
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// ❌ Wrong
|
|
68
|
+
console.log('Starting operation');
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### Error Handling
|
|
72
|
+
```typescript
|
|
73
|
+
// ✅ Correct
|
|
74
|
+
catch (error) {
|
|
75
|
+
LoggerProxy.error(`Failed: ${error}`, {
|
|
76
|
+
module: SERVICE_FILE,
|
|
77
|
+
method: METHODS.GET_ITEMS,
|
|
78
|
+
error,
|
|
79
|
+
});
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// ❌ Wrong - swallowing error
|
|
84
|
+
catch (error) {
|
|
85
|
+
console.error(error);
|
|
86
|
+
}
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
### Type Exports
|
|
90
|
+
```typescript
|
|
91
|
+
// ✅ Correct - in src/types.ts
|
|
92
|
+
export type {
|
|
93
|
+
ServiceItem,
|
|
94
|
+
ServiceSearchParams,
|
|
95
|
+
ServiceListResponse,
|
|
96
|
+
} from './services/ServiceName';
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Build & Test Verification
|
|
102
|
+
|
|
103
|
+
```bash
|
|
104
|
+
# Lint
|
|
105
|
+
yarn workspace @webex/contact-center test:styles
|
|
106
|
+
|
|
107
|
+
# Test unit tests
|
|
108
|
+
yarn workspace @webex/contact-center test:unit
|
|
109
|
+
|
|
110
|
+
# Build
|
|
111
|
+
yarn workspace @webex/contact-center build:src
|
|
112
|
+
```
|
|
113
|
+
|
|
114
|
+
All should pass without errors.
|
|
115
|
+
|
|
116
|
+
---
|
|
117
|
+
|
|
118
|
+
## Documentation
|
|
119
|
+
|
|
120
|
+
### Update Root AGENTS.md?
|
|
121
|
+
If this is a significant new service, update the root [`AGENTS.md`](../../../AGENTS.md):
|
|
122
|
+
- [ ] Added new service to the [Service Routing Table](../../../AGENTS.md#service-routing-table)
|
|
123
|
+
- [ ] Added to repository structure tree
|
|
124
|
+
- [ ] Added usage example if applicable
|
|
125
|
+
|
|
126
|
+
### Create Service ai-docs?
|
|
127
|
+
For complex services, create service-level documentation (use [`create-agents-md.md`](../documentation/create-agents-md.md) and [`create-architecture-md.md`](../documentation/create-architecture-md.md) templates):
|
|
128
|
+
- [ ] `src/services/ServiceName/ai-docs/AGENTS.md` — usage guide, API reference
|
|
129
|
+
- [ ] `src/services/ServiceName/ai-docs/ARCHITECTURE.md` — technical deep-dive, data flow
|
|
130
|
+
|
|
131
|
+
---
|
|
132
|
+
|
|
133
|
+
## Final Review
|
|
134
|
+
|
|
135
|
+
Ask yourself:
|
|
136
|
+
1. Can another developer understand this service by reading the JSDoc?
|
|
137
|
+
2. Are all error paths properly handled and logged?
|
|
138
|
+
3. Do tests cover the main use cases?
|
|
139
|
+
4. Is the API surface clean and consistent with other services?
|
|
140
|
+
|
|
141
|
+
---
|
|
142
|
+
|
|
143
|
+
## Complete!
|
|
144
|
+
|
|
145
|
+
Service creation is complete when all checkboxes are checked.
|
package/dist/cc.js
CHANGED
|
@@ -85,7 +85,6 @@ function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e
|
|
|
85
85
|
* - `task:established` - Task/call has been connected
|
|
86
86
|
* - `task:ended` - Task/call has ended
|
|
87
87
|
* - `task:error` - An error occurred during task handling
|
|
88
|
-
* - `task:campaignPreviewReservation` - Campaign preview contact offered to agent
|
|
89
88
|
*
|
|
90
89
|
* @public
|
|
91
90
|
*
|
|
@@ -318,7 +317,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
318
317
|
this.metricsManager = _MetricsManager.default.getInstance({
|
|
319
318
|
webex: this.$webex
|
|
320
319
|
});
|
|
321
|
-
this.taskManager = _TaskManager.default.getTaskManager(this.apiAIAssistant, this.services.contact, this.webCallingService, this.services.webSocketManager);
|
|
320
|
+
this.taskManager = _TaskManager.default.getTaskManager(this.apiAIAssistant, this.services.contact, this.webCallingService, this.services.webSocketManager, this.services.rtdWebSocketManager);
|
|
322
321
|
this.incomingTaskListener();
|
|
323
322
|
|
|
324
323
|
// Initialize API instances
|
|
@@ -360,19 +359,6 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
360
359
|
this.trigger(_types4.TASK_EVENTS.TASK_MERGED, task);
|
|
361
360
|
};
|
|
362
361
|
|
|
363
|
-
/**
|
|
364
|
-
* Handles campaign preview reservation events when a contact is offered to the agent
|
|
365
|
-
* @private
|
|
366
|
-
* @param {ITask} task The campaign reservation task
|
|
367
|
-
*/
|
|
368
|
-
handleCampaignPreviewReservation = task => {
|
|
369
|
-
// @ts-ignore
|
|
370
|
-
this.trigger(_types4.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
|
|
371
|
-
};
|
|
372
|
-
handleRTDWebsocketMessage = payload => {
|
|
373
|
-
this.taskManager.handleRealtimeWebsocketEvent(payload);
|
|
374
|
-
};
|
|
375
|
-
|
|
376
362
|
/**
|
|
377
363
|
* Sets up event listeners for incoming tasks and task hydration
|
|
378
364
|
* Subscribes to task events from the task manager
|
|
@@ -382,7 +368,6 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
382
368
|
this.taskManager.on(_types4.TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
|
|
383
369
|
this.taskManager.on(_types4.TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
|
|
384
370
|
this.taskManager.on(_types4.TASK_EVENTS.TASK_MERGED, this.handleTaskMerged);
|
|
385
|
-
this.taskManager.on(_types4.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, this.handleCampaignPreviewReservation);
|
|
386
371
|
}
|
|
387
372
|
|
|
388
373
|
/**
|
|
@@ -491,7 +476,6 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
491
476
|
this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_SUCCESS, _constants4.METRIC_EVENT_NAMES.WEBSOCKET_DEREGISTER_FAIL]);
|
|
492
477
|
this.taskManager.off(_types4.TASK_EVENTS.TASK_INCOMING, this.handleIncomingTask);
|
|
493
478
|
this.taskManager.off(_types4.TASK_EVENTS.TASK_HYDRATE, this.handleTaskHydrate);
|
|
494
|
-
this.taskManager.off(_types4.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, this.handleCampaignPreviewReservation);
|
|
495
479
|
this.taskManager.unregisterIncomingCallEvent();
|
|
496
480
|
this.services.webSocketManager.off('message', this.handleWebsocketMessage);
|
|
497
481
|
this.services.rtdWebSocketManager.off('message', this.handleRTDWebsocketMessage);
|
|
@@ -513,7 +497,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
513
497
|
this.services.webSocketManager.close(false, 'Unregistering the SDK');
|
|
514
498
|
}
|
|
515
499
|
if (this.services.rtdWebSocketManager && !this.services.rtdWebSocketManager.isSocketClosed) {
|
|
516
|
-
this.services.rtdWebSocketManager.close(false, 'Unregistering the
|
|
500
|
+
this.services.rtdWebSocketManager.close(false, 'Unregistering the SDK');
|
|
517
501
|
}
|
|
518
502
|
|
|
519
503
|
// Clear any cached agent configuration
|
|
@@ -621,62 +605,76 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
621
605
|
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
622
606
|
});
|
|
623
607
|
try {
|
|
624
|
-
|
|
608
|
+
return this.services.webSocketManager.initWebSocket({
|
|
625
609
|
body: this.getConnectionConfig(),
|
|
626
610
|
resource: _constants3.SUBSCRIBE_API
|
|
627
|
-
})
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
module: _constants.CC_FILE,
|
|
633
|
-
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
634
|
-
});
|
|
635
|
-
|
|
636
|
-
// TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
|
|
637
|
-
this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
|
|
638
|
-
this.taskManager.setAgentId(this.agentConfig.agentId);
|
|
639
|
-
this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
|
|
640
|
-
this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
|
|
641
|
-
if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable) {
|
|
642
|
-
_loggerProxy.default.info('Connecting to RTD websocket', {
|
|
611
|
+
}).then(async data => {
|
|
612
|
+
const agentId = data.agentId;
|
|
613
|
+
const orgId = this.$webex.credentials.getOrgId();
|
|
614
|
+
this.agentConfig = await this.services.config.getAgentConfig(orgId, agentId);
|
|
615
|
+
_loggerProxy.default.log(`Agent config is fetched successfully`, {
|
|
643
616
|
module: _constants.CC_FILE,
|
|
644
617
|
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
645
618
|
});
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
|
|
619
|
+
const configFlags = {
|
|
620
|
+
isEndTaskEnabled: this.agentConfig.isEndTaskEnabled,
|
|
621
|
+
isEndConsultEnabled: this.agentConfig.isEndConsultEnabled,
|
|
622
|
+
webRtcEnabled: this.agentConfig.webRtcEnabled,
|
|
623
|
+
autoWrapup: this.agentConfig.wrapUpData?.wrapUpProps?.autoWrapup ?? false,
|
|
624
|
+
aiFeature: this.agentConfig.aiFeature
|
|
625
|
+
};
|
|
626
|
+
this.taskManager.setConfigFlags(configFlags);
|
|
627
|
+
// TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
|
|
628
|
+
this.taskManager.setWrapupData(this.agentConfig.wrapUpData);
|
|
629
|
+
this.taskManager.setAgentId(this.agentConfig.agentId);
|
|
630
|
+
this.taskManager.setWebRtcEnabled(this.agentConfig.webRtcEnabled);
|
|
631
|
+
this.apiAIAssistant.setAIFeatureFlags(this.agentConfig.aiFeature);
|
|
632
|
+
/**
|
|
633
|
+
* TODO: We need to re-check this condition if this websocket is only for realtime transcripts
|
|
634
|
+
* or other AI Assistant features will also use the same.
|
|
635
|
+
* If the latter is true, we need to update this condition.
|
|
636
|
+
*/
|
|
637
|
+
if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable) {
|
|
638
|
+
_loggerProxy.default.info('Connecting to RTD websocket', {
|
|
657
639
|
module: _constants.CC_FILE,
|
|
658
640
|
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
659
641
|
});
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
642
|
+
this.services.rtdWebSocketManager.initWebSocket({
|
|
643
|
+
body: this.getConnectionConfig(),
|
|
644
|
+
resource: _constants3.RTD_SUBSCRIBE_API
|
|
645
|
+
}).then(() => {
|
|
646
|
+
_loggerProxy.default.log('RTD websocket connected successfully', {
|
|
647
|
+
module: _constants.CC_FILE,
|
|
648
|
+
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
649
|
+
});
|
|
650
|
+
this.services.rtdWebSocketManager.on('message', this.handleRTDWebsocketMessage);
|
|
651
|
+
}).catch(error => {
|
|
652
|
+
_loggerProxy.default.error(`Error connecting to RTD websocket ${error}`, {
|
|
653
|
+
module: _constants.CC_FILE,
|
|
654
|
+
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
655
|
+
});
|
|
668
656
|
});
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
657
|
+
}
|
|
658
|
+
if (this.agentConfig.webRtcEnabled && this.agentConfig.loginVoiceOptions.includes(_types.LoginOption.BROWSER)) {
|
|
659
|
+
this.$webex.internal.mercury.connect().then(() => {
|
|
660
|
+
_loggerProxy.default.log('Authentication: webex.internal.mercury.connect successful', {
|
|
661
|
+
module: _constants.CC_FILE,
|
|
662
|
+
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
663
|
+
});
|
|
664
|
+
}).catch(error => {
|
|
665
|
+
_loggerProxy.default.error(`Error occurred during mercury.connect() ${error}`, {
|
|
666
|
+
module: _constants.CC_FILE,
|
|
667
|
+
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
668
|
+
});
|
|
673
669
|
});
|
|
674
670
|
}
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
671
|
+
if (this.$config && this.$config.allowAutomatedRelogin) {
|
|
672
|
+
await this.silentRelogin();
|
|
673
|
+
}
|
|
674
|
+
return this.agentConfig;
|
|
675
|
+
}).catch(error => {
|
|
676
|
+
throw error;
|
|
677
|
+
});
|
|
680
678
|
} catch (error) {
|
|
681
679
|
_loggerProxy.default.error(`Error during register: ${error}`, {
|
|
682
680
|
module: _constants.CC_FILE,
|
|
@@ -721,8 +719,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
721
719
|
});
|
|
722
720
|
try {
|
|
723
721
|
this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS, _constants4.METRIC_EVENT_NAMES.STATION_LOGIN_FAILED]);
|
|
724
|
-
|
|
725
|
-
if (data.loginOption === _types.LoginOption.AGENT_DN && !(0, _Utils.isValidDialNumber)(data.dialNumber, dialPlanEntries)) {
|
|
722
|
+
if (data.loginOption === _types.LoginOption.AGENT_DN && !(0, _Utils.isValidDialNumber)(data.dialNumber)) {
|
|
726
723
|
const error = new Error('INVALID_DIAL_NUMBER');
|
|
727
724
|
// @ts-ignore - adding custom key to the error object
|
|
728
725
|
error.details = {
|
|
@@ -1070,6 +1067,9 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1070
1067
|
break;
|
|
1071
1068
|
}
|
|
1072
1069
|
};
|
|
1070
|
+
handleRTDWebsocketMessage = event => {
|
|
1071
|
+
this.taskManager.handleRealtimeWebsocketEvent(event);
|
|
1072
|
+
};
|
|
1073
1073
|
|
|
1074
1074
|
/**
|
|
1075
1075
|
* Initializes event listeners for the Contact Center service
|
|
@@ -1386,64 +1386,6 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1386
1386
|
}
|
|
1387
1387
|
}
|
|
1388
1388
|
|
|
1389
|
-
/**
|
|
1390
|
-
* Accepts a campaign preview contact, initiating the outbound call.
|
|
1391
|
-
*
|
|
1392
|
-
* When a campaign manager reserves a contact for an agent, the agent receives an
|
|
1393
|
-
* `AgentOfferCampaignReservation` event. The agent can then accept the preview contact
|
|
1394
|
-
* to initiate the outbound call.
|
|
1395
|
-
*
|
|
1396
|
-
* @param {PreviewContactPayload} payload - The preview contact payload containing interactionId and campaignId (campaign name, not UUID).
|
|
1397
|
-
* @returns {Promise<TaskResponse>} Promise resolving with agent contact on success.
|
|
1398
|
-
* @throws {Error} If the operation fails (network error, customer unavailable, etc.)
|
|
1399
|
-
*
|
|
1400
|
-
* @example
|
|
1401
|
-
* ```typescript
|
|
1402
|
-
* webex.cc.on('task:campaignPreviewReservation', async (task) => {
|
|
1403
|
-
* const { interactionId } = task.data;
|
|
1404
|
-
* // campaignId is the campaign name (e.g. "MyCampaign"), not a UUID
|
|
1405
|
-
* const campaignId = task.data.interaction.callProcessingDetails.campaignId;
|
|
1406
|
-
*
|
|
1407
|
-
* const result = await webex.cc.acceptPreviewContact({ interactionId, campaignId });
|
|
1408
|
-
* });
|
|
1409
|
-
* ```
|
|
1410
|
-
*/
|
|
1411
|
-
async acceptPreviewContact(payload) {
|
|
1412
|
-
_loggerProxy.default.info('Accepting campaign preview contact', {
|
|
1413
|
-
module: _constants.CC_FILE,
|
|
1414
|
-
method: _constants.METHODS.ACCEPT_PREVIEW_CONTACT
|
|
1415
|
-
});
|
|
1416
|
-
try {
|
|
1417
|
-
this.metricsManager.timeEvent([_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS, _constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED]);
|
|
1418
|
-
const result = await this.services.dialer.acceptPreviewContact({
|
|
1419
|
-
data: payload
|
|
1420
|
-
});
|
|
1421
|
-
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_SUCCESS, {
|
|
1422
|
-
..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(result),
|
|
1423
|
-
interactionId: payload.interactionId,
|
|
1424
|
-
campaignId: payload.campaignId
|
|
1425
|
-
}, ['behavioral', 'business', 'operational']);
|
|
1426
|
-
_loggerProxy.default.log('Campaign preview contact accepted successfully', {
|
|
1427
|
-
module: _constants.CC_FILE,
|
|
1428
|
-
method: _constants.METHODS.ACCEPT_PREVIEW_CONTACT,
|
|
1429
|
-
trackingId: result.trackingId,
|
|
1430
|
-
interactionId: payload.interactionId
|
|
1431
|
-
});
|
|
1432
|
-
return result;
|
|
1433
|
-
} catch (error) {
|
|
1434
|
-
const failure = error.details;
|
|
1435
|
-
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.CAMPAIGN_PREVIEW_ACCEPT_FAILED, {
|
|
1436
|
-
..._MetricsManager.default.getCommonTrackingFieldForAQMResponseFailed(failure),
|
|
1437
|
-
interactionId: payload.interactionId,
|
|
1438
|
-
campaignId: payload.campaignId
|
|
1439
|
-
}, ['behavioral', 'business', 'operational']);
|
|
1440
|
-
const {
|
|
1441
|
-
error: detailedError
|
|
1442
|
-
} = (0, _Utils.getErrorDetails)(error, _constants.METHODS.ACCEPT_PREVIEW_CONTACT, _constants.CC_FILE);
|
|
1443
|
-
throw detailedError;
|
|
1444
|
-
}
|
|
1445
|
-
}
|
|
1446
|
-
|
|
1447
1389
|
/**
|
|
1448
1390
|
* Fetches outdial ANI (Automatic Number Identification) entries for an outdial ANI ID.
|
|
1449
1391
|
*
|