@webex/contact-center 3.12.0-next.83 → 3.12.0-next.85
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 +876 -0
- package/AGENTS.md +65 -409
- package/ai-docs/ARCHITECTURE.md +168 -0
- package/ai-docs/CONTRACTS.md +46 -0
- package/ai-docs/GETTING_STARTED.md +168 -0
- package/ai-docs/GLOSSARY.md +43 -0
- package/ai-docs/README.md +25 -18
- package/ai-docs/REVIEW_CHECKLIST.md +41 -0
- package/ai-docs/RULES.md +187 -198
- package/ai-docs/SECURITY.md +52 -0
- package/ai-docs/SERVICE_STATE.md +48 -0
- package/ai-docs/SPEC_INDEX.md +65 -0
- package/ai-docs/adr/0001-spec-source-policy.md +55 -0
- package/ai-docs/adr/README.md +8 -0
- package/ai-docs/adr/_adr-template.md +31 -0
- package/ai-docs/contact-center-spec.md +341 -0
- package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
- package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
- package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
- package/ai-docs/templates/README.md +1 -1
- package/ai-docs/templates/new-method/04-validation.md +1 -1
- package/ai-docs/templates/new-service/05-validation.md +1 -1
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/metrics/ai-docs/AGENTS.md +2 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +2 -0
- package/src/metrics/ai-docs/metrics-spec.md +854 -0
- package/src/services/agent/ai-docs/AGENTS.md +3 -1
- package/src/services/agent/ai-docs/ARCHITECTURE.md +2 -0
- package/src/services/agent/ai-docs/agent-spec.md +504 -0
- package/src/services/ai-docs/AGENTS.md +4 -2
- package/src/services/ai-docs/services-spec.md +492 -0
- package/src/services/config/ai-docs/AGENTS.md +3 -1
- package/src/services/config/ai-docs/ARCHITECTURE.md +2 -0
- package/src/services/config/ai-docs/config-spec.md +669 -0
- package/src/services/core/ai-docs/AGENTS.md +4 -2
- package/src/services/core/ai-docs/ARCHITECTURE.md +2 -0
- package/src/services/core/ai-docs/core-spec.md +783 -0
- package/src/services/task/ai-docs/AGENTS.md +2 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +11 -2
- package/src/services/task/ai-docs/task-spec.md +1319 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +38 -75
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +6 -4
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2115 -0
- package/src/utils/AGENTS.md +3 -1
- package/src/utils/ai-docs/utils-spec.md +381 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -0,0 +1,1319 @@
|
|
|
1
|
+
# Task — SPEC
|
|
2
|
+
|
|
3
|
+
> Start here → root [`AGENTS.md`](../../../../AGENTS.md) · router [`SPEC_INDEX.md`](../../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../../ai-docs/ARCHITECTURE.md). This is the module's canonical specification.
|
|
4
|
+
|
|
5
|
+
## Metadata
|
|
6
|
+
|
|
7
|
+
| Field | Value |
|
|
8
|
+
|---|---|
|
|
9
|
+
| Module id | `task` |
|
|
10
|
+
| Source path(s) | `src/services/task` |
|
|
11
|
+
| Doc kind | Module spec |
|
|
12
|
+
| Coverage score | Partial (manifest-authoritative); 15/15 required document fields present |
|
|
13
|
+
| Generated from | `module-spec` @ SDLC template library `0.2.1` |
|
|
14
|
+
| generated_by / approved_by / updated_at | Codex generator / developer-approved follow-up review remediation / 2026-07-21 |
|
|
15
|
+
| Validation status | Follow-up validation passed (independent Claude fallback, 2026-07-21); 1 existing test-coverage gap; coverage remains Partial |
|
|
16
|
+
|
|
17
|
+
## Evidence Rules
|
|
18
|
+
Every requirement cites stable source and test file paths. Code/tests are the behavioral referee; routed source text supplies explicit intent and rationale. Missing or contradictory evidence blocks promotion.
|
|
19
|
+
|
|
20
|
+
## Source Material Register
|
|
21
|
+
| Source material | Scope | Decision | Detail location or disposition |
|
|
22
|
+
|---|---|---|---|
|
|
23
|
+
| Reviewed prior module guides and architecture material | overview / architecture / API / tests | used and code-checked | Content is placed by meaning throughout this specification; exact routing remains in the manifest. |
|
|
24
|
+
|
|
25
|
+
## Overview
|
|
26
|
+
Task is one of nine confirmed Contact Center SDK modules. Own task creation, media-specific behavior, call-control operations, lifecycle orchestration, task events, and integration with the task state machine. Existing reviewed documentation is migrated by meaning and code/tests remain the behavioral referee.
|
|
27
|
+
|
|
28
|
+
Manage task lifecycle including inbound/outbound calls, hold/resume, consult, transfer, conference, and wrapup.
|
|
29
|
+
|
|
30
|
+
- **Task Creation by Channel**: `TaskFactory.ts` chooses `WebRTC`, `Voice`, or `Digital` based on `MEDIA_CHANNEL` and `webCallingService.loginOption`, so each task class exposes the correct capabilities for the media type.
|
|
31
|
+
|
|
32
|
+
- **Task Orchestration**: `TaskManager.ts` owns task lifecycle wiring—initializes listeners, receives task events, creates/updates tasks, emits SDK events, and exposes task collections for consumers.
|
|
33
|
+
|
|
34
|
+
- **Event Emission and Public APIs**: Task objects register listeners, update context, emit SDK events (e.g., `task:*`), and expose public methods that delegate to `contact.ts` for call control and to the state machine for transition validation.
|
|
35
|
+
|
|
36
|
+
- **AQM Contact Operations**: `contact.ts` builds the AQM request surface for call control (accept, hold, consult, transfer, wrapup, end) and is the primary bridge from `Task`/`Voice`/`WebRTC`/`Digital` methods to WCC task APIs.
|
|
37
|
+
|
|
38
|
+
- **Outbound and Preview-Campaign Dialing**: `dialer.ts` exposes `startOutdial` plus `acceptPreviewContact`, `skipPreviewContact`, and `removePreviewContact`; ContactCenter publishes these operations through typed `cc` methods.
|
|
39
|
+
|
|
40
|
+
- **State Machine Driven UI Controls**: The `state-machine/` folder provides the XState engine (`TaskStateMachine.ts`) plus `actions.ts`, `guards.ts`, `uiControlsComputer.ts`, `constants.ts`, and `types.ts` to compute valid transitions and UI control state. Capability-level details live in `state-machine/ai-docs/task-state-machine-spec.md`.
|
|
41
|
+
|
|
42
|
+
This section describes how the task layer constructs tasks, initializes the state machine, and wires AQM calls to task methods. It provides context for how the state machine fits into the end-to-end flow.
|
|
43
|
+
|
|
44
|
+
- **Listener Setup**: Registers WebSocket listeners to receive CC events and map them to `TaskEvent` payloads.
|
|
45
|
+
|
|
46
|
+
- **Task Registry**: Creates tasks via `TaskFactory`, stores them in the task collection, and updates task data on incoming events.
|
|
47
|
+
|
|
48
|
+
- **Event Emission**: Re-emits `task:*` events on the task or `cc` object for SDK consumers.
|
|
49
|
+
|
|
50
|
+
- **Hydration/Recovery**: Handles state updates and transitions during reconnect/hydrate flows.
|
|
51
|
+
|
|
52
|
+
Example (backend event to state machine):
|
|
53
|
+
|
|
54
|
+
```typescript
|
|
55
|
+
const payload = TaskManager.mapEventToTaskStateMachineEvent(event, taskData);
|
|
56
|
+
if (payload) {
|
|
57
|
+
task.sendStateMachineEvent(payload);
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
- **`contact.ts`**: Builds the AQM request surface for call control (hold, consult, transfer, wrapup, end). Task methods delegate to these calls, then drive state transitions based on success/failure events.
|
|
62
|
+
|
|
63
|
+
- **`dialer.ts`**: Exposes `startOutdial` and the three preview-campaign AQM requests used by `cc.startOutdial()`, `cc.acceptPreviewContact()`, `cc.skipPreviewContact()`, and `cc.removePreviewContact()`.
|
|
64
|
+
|
|
65
|
+
Example (task method delegating to AQM):
|
|
66
|
+
|
|
67
|
+
```typescript
|
|
68
|
+
// task.hold() -> contact.hold(...) -> stateMachine events on response
|
|
69
|
+
await contact.hold({interactionId});
|
|
70
|
+
stateMachineService.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId});
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## Purpose / Responsibility
|
|
74
|
+
Own task creation, media-specific behavior, call-control operations, lifecycle orchestration, task events, and integration with the task state machine.
|
|
75
|
+
|
|
76
|
+
## Stack
|
|
77
|
+
TypeScript 5.4, EventEmitter, XState 5, @webex/calling, AQM/WebSocket integrations, Jest 27.
|
|
78
|
+
|
|
79
|
+
## Folder / Package Structure
|
|
80
|
+
```text
|
|
81
|
+
src/services/task/
|
|
82
|
+
├── AutoWrapup.ts
|
|
83
|
+
├── Task.ts
|
|
84
|
+
├── TaskFactory.ts
|
|
85
|
+
├── TaskManager.ts
|
|
86
|
+
├── TaskUtils.ts
|
|
87
|
+
├── constants.ts
|
|
88
|
+
├── contact.ts
|
|
89
|
+
├── dialer.ts
|
|
90
|
+
├── digital/
|
|
91
|
+
├── state-machine/
|
|
92
|
+
├── taskDataNormalizer.ts
|
|
93
|
+
├── types.ts
|
|
94
|
+
├── voice/
|
|
95
|
+
```
|
|
96
|
+
|
|
97
|
+
```text
|
|
98
|
+
services/task/
|
|
99
|
+
├── Task.ts # Task class (ITask implementation)
|
|
100
|
+
├── TaskManager.ts # Singleton task manager
|
|
101
|
+
├── contact.ts # Contact operations (AQM)
|
|
102
|
+
├── dialer.ts # Outbound dialing (AQM)
|
|
103
|
+
├── AutoWrapup.ts # Auto wrapup handler
|
|
104
|
+
├── TaskUtils.ts # Helper functions
|
|
105
|
+
├── types.ts # Task types and events
|
|
106
|
+
├── constants.ts # Task constants
|
|
107
|
+
├── TaskFactory.ts # Task factory
|
|
108
|
+
├── taskDataNormalizer.ts # Task data normalization helpers
|
|
109
|
+
├── digital/ # Digital task implementations
|
|
110
|
+
│ └── Digital.ts
|
|
111
|
+
├── voice/ # Voice task implementations
|
|
112
|
+
│ ├── Voice.ts
|
|
113
|
+
│ └── WebRTC.ts
|
|
114
|
+
├── state-machine/ # XState task lifecycle engine
|
|
115
|
+
│ ├── TaskStateMachine.ts
|
|
116
|
+
│ ├── actions.ts
|
|
117
|
+
│ ├── guards.ts
|
|
118
|
+
│ ├── uiControlsComputer.ts
|
|
119
|
+
│ ├── constants.ts
|
|
120
|
+
│ ├── types.ts
|
|
121
|
+
│ └── ai-docs/
|
|
122
|
+
│ ├── AGENTS.md
|
|
123
|
+
│ └── ARCHITECTURE.md
|
|
124
|
+
└── ai-docs/
|
|
125
|
+
├── AGENTS.md # Usage documentation
|
|
126
|
+
└── ARCHITECTURE.md # Task service architecture
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
## Key Files (source of truth)
|
|
130
|
+
| File | Holds |
|
|
131
|
+
|---|---|
|
|
132
|
+
| `src/services/task/Task.ts` | Authoritative Task implementation or contract source. |
|
|
133
|
+
| `src/services/task/TaskManager.ts` | Authoritative Task implementation or contract source. |
|
|
134
|
+
| `src/services/task/TaskFactory.ts` | Authoritative Task implementation or contract source. |
|
|
135
|
+
| `src/services/task/contact.ts` | Authoritative Task implementation or contract source. |
|
|
136
|
+
| `src/services/task/dialer.ts` | Authoritative Task implementation or contract source. |
|
|
137
|
+
| `src/services/task/types.ts` | Authoritative Task implementation or contract source. |
|
|
138
|
+
|
|
139
|
+
## Public Surface
|
|
140
|
+
| Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index |
|
|
141
|
+
|---|---|---|---|---|---|---|
|
|
142
|
+
| `task.surface` | SDK / event / internal API | Exported Task/types/events plus application-facing task instances and call-control methods. | Stable module consumption boundary. | Additive changes by default; breaking package exports require a major-version transition. | `src/services/task/Task.ts` | `../../../../ai-docs/CONTRACTS.md` |
|
|
143
|
+
| `task.preview-campaign` | SDK/AQM API | `acceptPreviewContact`, `skipPreviewContact`, `removePreviewContact`, and `PreviewContactPayload`. | Accept, skip, or remove a reserved campaign preview contact; each method returns `Promise<TaskResponse>`. | Additive semver-public methods; removals or signature changes are breaking. | `src/cc.ts`, `src/services/task/dialer.ts`, `src/services/task/types.ts` | `../../../../ai-docs/CONTRACTS.md` |
|
|
144
|
+
|
|
145
|
+
Compatibility notes:
|
|
146
|
+
- Do not remove or reinterpret exported symbols/events without a documented consumer migration.
|
|
147
|
+
|
|
148
|
+
- `TASK_EVENTS` enum (`types.ts`)
|
|
149
|
+
|
|
150
|
+
- `TaskData`, `TaskId`, `TaskResponse`, `TaskUIControls` (`types.ts`)
|
|
151
|
+
|
|
152
|
+
- `PreviewContactPayload` (`types.ts`) with `interactionId` and campaign-name `campaignId`
|
|
153
|
+
|
|
154
|
+
- `ITask`, `IVoice`, `IWebRTC`, `IDigital` (`types.ts`)
|
|
155
|
+
|
|
156
|
+
- `MEDIA_CHANNEL`, `TASK_CHANNEL_TYPE`, `VOICE_VARIANT` (`types.ts`)
|
|
157
|
+
|
|
158
|
+
- State machine: `TaskState`, `TaskEvent` (`state-machine/constants.ts`)
|
|
159
|
+
|
|
160
|
+
| Event | When Emitted |
|
|
161
|
+
|---|---|
|
|
162
|
+
| `task:incoming` | New task offered to agent |
|
|
163
|
+
| `task:hydrate` | Task data updated |
|
|
164
|
+
| `task:merged` | Tasks merged (EPDN transfer) |
|
|
165
|
+
|
|
166
|
+
| Event | When Emitted |
|
|
167
|
+
|---|---|
|
|
168
|
+
| `task:assigned` | Task assigned to agent |
|
|
169
|
+
| `task:media` | Media stream/track updates are available |
|
|
170
|
+
| `task:unassigned` | Task is unassigned from agent |
|
|
171
|
+
| `task:offerContact` | Contact offer received/updated |
|
|
172
|
+
| `task:offerConsult` | Consult offer received |
|
|
173
|
+
| `task:hold` | Task placed on hold |
|
|
174
|
+
| `task:resume` | Task resumed from hold |
|
|
175
|
+
| `task:end` | Task ended |
|
|
176
|
+
| `task:rejected` | Task rejected / failure path emitted |
|
|
177
|
+
| `task:wrapup` | Task entering wrapup |
|
|
178
|
+
| `task:wrappedup` | Wrapup completed |
|
|
179
|
+
| `task:consulting` | Consult is in progress |
|
|
180
|
+
| `task:consultAccepted` | Consult accepted by destination party |
|
|
181
|
+
| `task:consultCreated` | Consultation started |
|
|
182
|
+
| `task:consultEnd` | Consultation ended |
|
|
183
|
+
| `task:autoAnswered` | Task was auto-answered |
|
|
184
|
+
| `task:recordingStarted` / `task:recordingPaused` / `task:recordingResumed` | Recording lifecycle updates |
|
|
185
|
+
| `task:conferenceStarted` / `task:conferenceEnded` / `task:conferenceFailed` | Conference lifecycle updates |
|
|
186
|
+
| `task:participantJoined` / `task:participantLeft` | Conference participant updates |
|
|
187
|
+
| `task:switchCall` | Switched between consult and main call |
|
|
188
|
+
| `task:outdialFailed` | Outdial operation failed |
|
|
189
|
+
| `task:ui-controls-updated` | UI controls changed due to state transition |
|
|
190
|
+
| `task:cleanup` | Internal cleanup signal emitted by state machine |
|
|
191
|
+
|
|
192
|
+
> Full list is defined in `TASK_EVENTS` (`types.ts`).
|
|
193
|
+
|
|
194
|
+
| Event | When Emitted |
|
|
195
|
+
|---|---|
|
|
196
|
+
| `REAL_TIME_TRANSCRIPTION` | A realtime transcript payload is received for the task interaction |
|
|
197
|
+
| `SUGGESTED_RESPONSE` | A final AI Assistant suggestion payload is received for the task interaction |
|
|
198
|
+
|
|
199
|
+
Initiate outbound call.
|
|
200
|
+
|
|
201
|
+
**Parameters**:
|
|
202
|
+
|
|
203
|
+
- `destination` (string): Phone number to call
|
|
204
|
+
|
|
205
|
+
- `origin` (string): Outbound ANI/caller ID
|
|
206
|
+
|
|
207
|
+
**Returns**: `Promise<TaskResponse>` (AQM response, not a Task instance)
|
|
208
|
+
|
|
209
|
+
**Example**:
|
|
210
|
+
|
|
211
|
+
> **Host-application example:** `updateCallStatus` represents consumer-owned UI handling. SDK package implementation must use `LoggerProxy` and must not log raw runtime values.
|
|
212
|
+
|
|
213
|
+
```typescript
|
|
214
|
+
const response = await cc.startOutdial('+14155551234', '+18005551000');
|
|
215
|
+
|
|
216
|
+
// Outdial task object is created asynchronously via TaskManager.
|
|
217
|
+
// Listen on cc/task events instead of treating startOutdial response as an ITask.
|
|
218
|
+
cc.on('task:incoming', (task) => {
|
|
219
|
+
task.on('task:assigned', () => {
|
|
220
|
+
updateCallStatus('connected');
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
task.on('task:end', () => {
|
|
224
|
+
updateCallStatus('ended');
|
|
225
|
+
});
|
|
226
|
+
});
|
|
227
|
+
```
|
|
228
|
+
|
|
229
|
+
Accept an incoming task.
|
|
230
|
+
|
|
231
|
+
**Returns**: `Promise<TaskResponse>`
|
|
232
|
+
|
|
233
|
+
**Example**:
|
|
234
|
+
|
|
235
|
+
```typescript
|
|
236
|
+
cc.on('task:incoming', async (task) => {
|
|
237
|
+
await task.accept();
|
|
238
|
+
});
|
|
239
|
+
```
|
|
240
|
+
|
|
241
|
+
Put task on hold or resume.
|
|
242
|
+
|
|
243
|
+
**Parameters**:
|
|
244
|
+
|
|
245
|
+
- Concrete `Task.hold()` / `Voice.hold()` and `Task.resume()` / `Voice.resume()` accept no parameters. The optional `mediaResourceId` exists only on the broader `ITask` compatibility declaration; the implementations derive the active media resource from task state.
|
|
246
|
+
|
|
247
|
+
**Returns**: `Promise<TaskResponse>`
|
|
248
|
+
|
|
249
|
+
**Example**:
|
|
250
|
+
|
|
251
|
+
```typescript
|
|
252
|
+
// Put on hold
|
|
253
|
+
await task.hold();
|
|
254
|
+
|
|
255
|
+
// Resume
|
|
256
|
+
await task.resume();
|
|
257
|
+
```
|
|
258
|
+
|
|
259
|
+
End the current task.
|
|
260
|
+
|
|
261
|
+
**Returns**: `Promise<TaskResponse>`
|
|
262
|
+
|
|
263
|
+
**Example**:
|
|
264
|
+
|
|
265
|
+
```typescript
|
|
266
|
+
await task.end();
|
|
267
|
+
```
|
|
268
|
+
|
|
269
|
+
Complete task with wrapup code.
|
|
270
|
+
|
|
271
|
+
**Parameters**:
|
|
272
|
+
|
|
273
|
+
- `wrapUpReason` (string, required): Wrapup reason text
|
|
274
|
+
|
|
275
|
+
- `auxCodeId` (string, required): Wrapup code ID
|
|
276
|
+
|
|
277
|
+
**Returns**: `Promise<TaskResponse>`
|
|
278
|
+
|
|
279
|
+
**Example**:
|
|
280
|
+
|
|
281
|
+
```typescript
|
|
282
|
+
await task.wrapup({
|
|
283
|
+
wrapUpReason: 'Customer issue resolved',
|
|
284
|
+
auxCodeId: 'resolved-code',
|
|
285
|
+
});
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
Transfer task to another destination.
|
|
289
|
+
|
|
290
|
+
**Parameters**:
|
|
291
|
+
|
|
292
|
+
- `to` (string): Agent ID, queue ID, or phone number
|
|
293
|
+
|
|
294
|
+
- `destinationType` ('queue' | 'agent' | 'dialNumber'): Destination type
|
|
295
|
+
|
|
296
|
+
**Returns**: `Promise<TaskResponse>`
|
|
297
|
+
|
|
298
|
+
**Example**:
|
|
299
|
+
|
|
300
|
+
```typescript
|
|
301
|
+
// Transfer to queue
|
|
302
|
+
await task.transfer({
|
|
303
|
+
to: 'queue-123',
|
|
304
|
+
destinationType: 'queue',
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// Transfer to agent
|
|
308
|
+
await task.transfer({
|
|
309
|
+
to: 'agent-456',
|
|
310
|
+
destinationType: 'agent',
|
|
311
|
+
});
|
|
312
|
+
```
|
|
313
|
+
|
|
314
|
+
Start consultation.
|
|
315
|
+
|
|
316
|
+
**Parameters**:
|
|
317
|
+
|
|
318
|
+
- `to` (string): Agent/queue/phone to consult
|
|
319
|
+
|
|
320
|
+
- `destinationType` ('queue' | 'agent' | 'dialNumber' | 'entryPoint'): Type
|
|
321
|
+
|
|
322
|
+
**Returns**: `Promise<TaskResponse>`
|
|
323
|
+
|
|
324
|
+
**Example**:
|
|
325
|
+
|
|
326
|
+
```typescript
|
|
327
|
+
await task.consult({
|
|
328
|
+
to: 'agent-456',
|
|
329
|
+
destinationType: 'agent',
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
// Later: complete transfer (consulting voice flow uses transfer())
|
|
333
|
+
await task.transfer({
|
|
334
|
+
to: 'queue-123',
|
|
335
|
+
destinationType: 'queue',
|
|
336
|
+
});
|
|
337
|
+
// Or end consult
|
|
338
|
+
await task.endConsult();
|
|
339
|
+
```
|
|
340
|
+
|
|
341
|
+
End consultation without transfer.
|
|
342
|
+
|
|
343
|
+
**Parameters**:
|
|
344
|
+
|
|
345
|
+
- `consultEndPayload` (optional `ConsultEndPayload`)
|
|
346
|
+
|
|
347
|
+
**Returns**: `Promise<TaskResponse>`
|
|
348
|
+
|
|
349
|
+
### Complete TASK_EVENTS inventory
|
|
350
|
+
|
|
351
|
+
The public `TASK_EVENTS` enum contains 49 members; every member is listed below from `src/services/task/types.ts`.
|
|
352
|
+
|
|
353
|
+
| Constant | Event string |
|
|
354
|
+
|---|---|
|
|
355
|
+
| `TASK_INCOMING` | `task:incoming` |
|
|
356
|
+
| `TASK_ASSIGNED` | `task:assigned` |
|
|
357
|
+
| `TASK_MEDIA` | `task:media` |
|
|
358
|
+
| `TASK_UNASSIGNED` | `task:unassigned` |
|
|
359
|
+
| `TASK_HOLD` | `task:hold` |
|
|
360
|
+
| `TASK_RESUME` | `task:resume` |
|
|
361
|
+
| `TASK_CONSULT_END` | `task:consultEnd` |
|
|
362
|
+
| `TASK_CONSULT_QUEUE_CANCELLED` | `task:consultQueueCancelled` |
|
|
363
|
+
| `TASK_CONSULT_QUEUE_FAILED` | `task:consultQueueFailed` |
|
|
364
|
+
| `TASK_UI_CONTROLS_UPDATED` | `task:ui-controls-updated` |
|
|
365
|
+
| `TASK_CONSULT_ACCEPTED` | `task:consultAccepted` |
|
|
366
|
+
| `TASK_CONSULTING` | `task:consulting` |
|
|
367
|
+
| `TASK_CONSULT_CREATED` | `task:consultCreated` |
|
|
368
|
+
| `TASK_OFFER_CONSULT` | `task:offerConsult` |
|
|
369
|
+
| `TASK_END` | `task:end` |
|
|
370
|
+
| `TASK_WRAPUP` | `task:wrapup` |
|
|
371
|
+
| `TASK_WRAPPEDUP` | `task:wrappedup` |
|
|
372
|
+
| `TASK_CLEANUP` | `task:cleanup` |
|
|
373
|
+
| `TASK_RECORDING_STARTED` | `task:recordingStarted` |
|
|
374
|
+
| `TASK_RECORDING_PAUSED` | `task:recordingPaused` |
|
|
375
|
+
| `TASK_RECORDING_PAUSE_FAILED` | `task:recordingPauseFailed` |
|
|
376
|
+
| `TASK_RECORDING_RESUMED` | `task:recordingResumed` |
|
|
377
|
+
| `TASK_RECORDING_RESUME_FAILED` | `task:recordingResumeFailed` |
|
|
378
|
+
| `TASK_REJECT` | `task:rejected` |
|
|
379
|
+
| `TASK_OUTDIAL_FAILED` | `task:outdialFailed` |
|
|
380
|
+
| `TASK_HYDRATE` | `task:hydrate` |
|
|
381
|
+
| `TASK_OFFER_CONTACT` | `task:offerContact` |
|
|
382
|
+
| `TASK_AUTO_ANSWERED` | `task:autoAnswered` |
|
|
383
|
+
| `TASK_CONFERENCE_ESTABLISHING` | `task:conferenceEstablishing` |
|
|
384
|
+
| `TASK_CONFERENCE_STARTED` | `task:conferenceStarted` |
|
|
385
|
+
| `TASK_CONFERENCE_FAILED` | `task:conferenceFailed` |
|
|
386
|
+
| `TASK_CONFERENCE_ENDED` | `task:conferenceEnded` |
|
|
387
|
+
| `TASK_PARTICIPANT_JOINED` | `task:participantJoined` |
|
|
388
|
+
| `TASK_PARTICIPANT_LEFT` | `task:participantLeft` |
|
|
389
|
+
| `TASK_CONFERENCE_TRANSFERRED` | `task:conferenceTransferred` |
|
|
390
|
+
| `TASK_CONFERENCE_TRANSFER_FAILED` | `task:conferenceTransferFailed` |
|
|
391
|
+
| `TASK_CONFERENCE_END_FAILED` | `task:conferenceEndFailed` |
|
|
392
|
+
| `TASK_PARTICIPANT_LEFT_FAILED` | `task:participantLeftFailed` |
|
|
393
|
+
| `TASK_EXIT_CONFERENCE` | `task:exitConference` |
|
|
394
|
+
| `TASK_TRANSFER_CONFERENCE` | `task:transferConference` |
|
|
395
|
+
| `TASK_SWITCH_CALL` | `task:switchCall` |
|
|
396
|
+
| `TASK_MERGED` | `task:merged` |
|
|
397
|
+
| `TASK_POST_CALL_ACTIVITY` | `task:postCallActivity` |
|
|
398
|
+
| `TASK_MULTI_LOGIN_HYDRATE` | `task:multiLoginHydrate` |
|
|
399
|
+
| `TASK_CAMPAIGN_PREVIEW_RESERVATION` | `task:campaignPreviewReservation` |
|
|
400
|
+
| `TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED` | `task:campaignPreviewAcceptFailed` |
|
|
401
|
+
| `TASK_CAMPAIGN_PREVIEW_SKIP_FAILED` | `task:campaignPreviewSkipFailed` |
|
|
402
|
+
| `TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED` | `task:campaignPreviewRemoveFailed` |
|
|
403
|
+
| `TASK_CAMPAIGN_CONTACT_UPDATED` | `task:campaignContactUpdated` |
|
|
404
|
+
|
|
405
|
+
## Requires (dependencies)
|
|
406
|
+
- Services contact/dialer AQM factories
|
|
407
|
+
- WebSocket and RTD WebSocket managers
|
|
408
|
+
- WebCallingService, MetricsManager, and task state machine
|
|
409
|
+
|
|
410
|
+
## Requirements
|
|
411
|
+
| ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence |
|
|
412
|
+
|---|---|---|---|---|---|---|
|
|
413
|
+
| TASK-R-001 | Create only supported Voice/Digital Task implementations and throw `Unknown media type` for unsupported media. | Returning a generic task for unsupported channels would advertise controls the implementation cannot perform. | `src/services/task/TaskFactory.ts` | `test/unit/spec/services/task/TaskFactory.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
414
|
+
| TASK-R-002 | Concrete Task/Voice `hold()` and `resume()` implementations remain parameterless while `ITask` retains its optional compatibility parameter. | Documentation must distinguish the broad public interface from concrete runtime signatures. | `src/services/task/Task.ts` | `test/unit/spec/services/task/Task.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
415
|
+
| TASK-R-003 | Task and media subclasses must route contact/calling operations into typed state-machine events and emit the complete TASK_EVENTS contract. | Consumers coordinate UI and interaction lifecycle from those events. | `src/services/task/Task.ts` | `test/unit/spec/services/task/Task.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
416
|
+
| TASK-R-004 | TaskManager must consume primary/RTD streams and manage task creation, hydration, cleanup, campaign, and AI-assistant flows. | A single task owner prevents duplicate instances and inconsistent state across realtime sources. | `src/services/task/TaskManager.ts` | `test/unit/spec/services/task/TaskManager.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
417
|
+
| TASK-R-005 | The contact dependency belongs to Task/TaskFactory-created tasks; dialer is an AqmReqs request factory without that constructor. | Misattributing constructor dependencies causes invalid instantiation examples. | `src/services/task/TaskFactory.ts` | `test/unit/spec/services/task/dialer.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
418
|
+
| TASK-R-006 | Keep credentials and authentication outside Task; remote operations delegate through contact/dialer routing, AqmReqs, and Core/WebexRequest. | Task lifecycle objects should never duplicate host token handling or leak authentication state into interaction data. | `src/services/task/Task.ts`, `src/services/task/contact.ts`, `src/services/core/WebexRequest.ts` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/contact.ts` | None; authentication ownership is explicit. | PRESENT |
|
|
419
|
+
| TASK-R-007 | Route enabled preview-campaign accept, skip, and remove operations through the dialer AQM factory using `PreviewContactPayload`, returning `Promise<TaskResponse>` from the public ContactCenter methods. Before routing skip/remove, reject the operation when the matching task's disable flag is `'true'`. | Preview reservations require typed payloads and correlated backend completion, while campaign controls must block prohibited skip/remove requests before transport begins. | `src/cc.ts`, `src/services/task/dialer.ts`, `src/services/task/types.ts` | `test/unit/spec/cc.ts`, `test/unit/spec/services/task/dialer.ts` | Public delegation and dialer requests are covered; the `campaignPreviewSkipDisabled` and `campaignPreviewRemoveDisabled` early-exit guards lack direct unit coverage. Independent review identified this gap on 2026-07-15. | PRESENT |
|
|
420
|
+
|
|
421
|
+
## Design Overview
|
|
422
|
+
Task separates its stable consumption boundary from collaborators so ownership and failure behavior stay explicit. A shared Task base preserves a stable API while media-specific subclasses and a separate state engine enforce capability differences.
|
|
423
|
+
|
|
424
|
+
| Channel | Description |
|
|
425
|
+
|---|---|
|
|
426
|
+
| `telephony` | Voice calls |
|
|
427
|
+
| `chat` | Web chat |
|
|
428
|
+
| `email` | Email interactions |
|
|
429
|
+
| `social` | Social media |
|
|
430
|
+
| `sms` | Unsupported by TaskFactory; throws `Unknown media type` |
|
|
431
|
+
| `facebook` | Unsupported by TaskFactory; throws `Unknown media type` |
|
|
432
|
+
| `whatsapp` | Unsupported by TaskFactory; throws `Unknown media type` |
|
|
433
|
+
|
|
434
|
+
If enabled in agent profile, wrapup completes automatically after timeout:
|
|
435
|
+
|
|
436
|
+
> **Host-application example:** `updateWrapupStatus` is a consumer-owned UI callback, not SDK package logging.
|
|
437
|
+
|
|
438
|
+
```typescript
|
|
439
|
+
task.on('task:wrappedup', () => {
|
|
440
|
+
updateWrapupStatus('completed');
|
|
441
|
+
});
|
|
442
|
+
```
|
|
443
|
+
|
|
444
|
+
> **Purpose**: Technical documentation for task lifecycle management.
|
|
445
|
+
|
|
446
|
+
TaskManager is a singleton that:
|
|
447
|
+
|
|
448
|
+
1. Listens for WebSocket task events
|
|
449
|
+
|
|
450
|
+
2. Creates/manages Task objects
|
|
451
|
+
|
|
452
|
+
3. Routes events to appropriate tasks
|
|
453
|
+
|
|
454
|
+
4. Handles WebRTC call mapping
|
|
455
|
+
|
|
456
|
+
```typescript
|
|
457
|
+
// Singleton access
|
|
458
|
+
const taskManager = TaskManager.getTaskManager(contact, webCallingService, webSocketManager);
|
|
459
|
+
```
|
|
460
|
+
|
|
461
|
+
Returns an object of AQM request methods wired to `TASK_API` and `TASK_MESSAGE_TYPE`.
|
|
462
|
+
|
|
463
|
+
**Methods**
|
|
464
|
+
|
|
465
|
+
- `accept`
|
|
466
|
+
|
|
467
|
+
- `hold`
|
|
468
|
+
|
|
469
|
+
- `unHold`
|
|
470
|
+
|
|
471
|
+
- `pauseRecording`
|
|
472
|
+
|
|
473
|
+
- `resumeRecording`
|
|
474
|
+
|
|
475
|
+
- `consult`
|
|
476
|
+
|
|
477
|
+
- `consultEnd`
|
|
478
|
+
|
|
479
|
+
- `consultAccept`
|
|
480
|
+
|
|
481
|
+
- `blindTransfer`
|
|
482
|
+
|
|
483
|
+
- `vteamTransfer`
|
|
484
|
+
|
|
485
|
+
- `consultTransfer`
|
|
486
|
+
|
|
487
|
+
- `end`
|
|
488
|
+
|
|
489
|
+
- `wrapup`
|
|
490
|
+
|
|
491
|
+
- `cancelTask`
|
|
492
|
+
|
|
493
|
+
- `cancelCtq`
|
|
494
|
+
|
|
495
|
+
- `consultConference`
|
|
496
|
+
|
|
497
|
+
- `exitConference`
|
|
498
|
+
|
|
499
|
+
- `conferenceTransfer`
|
|
500
|
+
|
|
501
|
+
**Notes**
|
|
502
|
+
|
|
503
|
+
- Uses `WCC_API_GATEWAY`.
|
|
504
|
+
|
|
505
|
+
- Consult with `DESTINATION_TYPE.QUEUE` uses `TIMEOUT_REQ` = `'disabled'` for the request timeout.
|
|
506
|
+
|
|
507
|
+
Returns an object of AQM request methods for outbound dialing.
|
|
508
|
+
|
|
509
|
+
**Methods**
|
|
510
|
+
|
|
511
|
+
- `startOutdial` (success: `CC_EVENTS.AGENT_OFFER_CONTACT`, failure: `CC_EVENTS.AGENT_OUTBOUND_FAILED`)
|
|
512
|
+
|
|
513
|
+
- Task/TaskFactory-created task instances receive `contact: ReturnType<typeof routingContact>`; `dialer.ts` has no such constructor and is an AqmReqs factory.
|
|
514
|
+
|
|
515
|
+
- Uses:
|
|
516
|
+
|
|
517
|
+
- `contact.vteamTransfer` / `contact.blindTransfer` in `transfer(...)`.
|
|
518
|
+
|
|
519
|
+
- While in consulting state, `transfer(...)` internally routes through consult-transfer behavior.
|
|
520
|
+
|
|
521
|
+
- `contact.end` in `end()`.
|
|
522
|
+
|
|
523
|
+
- `contact.wrapup` in `wrapup(...)`.
|
|
524
|
+
|
|
525
|
+
Uses `contact` for:
|
|
526
|
+
|
|
527
|
+
- `hold`, `unHold`
|
|
528
|
+
|
|
529
|
+
- `pauseRecording`, `resumeRecording`
|
|
530
|
+
|
|
531
|
+
- `consult`, `consultEnd`, `consultTransfer`
|
|
532
|
+
|
|
533
|
+
- `consultConference`, `exitConference`, `conferenceTransfer`
|
|
534
|
+
|
|
535
|
+
Uses `contact.accept` in `accept()`.
|
|
536
|
+
|
|
537
|
+
TaskManager maintains a map of active tasks:
|
|
538
|
+
|
|
539
|
+
```typescript
|
|
540
|
+
private taskCollection: Record<TaskId, ITask> = {};
|
|
541
|
+
|
|
542
|
+
// Tasks indexed by interactionId
|
|
543
|
+
this.taskCollection[interactionId] = task;
|
|
544
|
+
|
|
545
|
+
// Retrieve task
|
|
546
|
+
const task = this.taskCollection[interactionId];
|
|
547
|
+
```
|
|
548
|
+
|
|
549
|
+
TaskManager uses a staged pipeline in `registerTaskListeners()`:
|
|
550
|
+
|
|
551
|
+
```typescript
|
|
552
|
+
this.webSocketManager.on('message', (event) => {
|
|
553
|
+
// 1) Parse and validate message
|
|
554
|
+
const message = TaskManager.parseWebSocketMessage(event);
|
|
555
|
+
if (!message) return;
|
|
556
|
+
|
|
557
|
+
// 2) Build event context (task, payload, mapped state-machine event)
|
|
558
|
+
const eventContext = this.prepareEventContext(message);
|
|
559
|
+
if (!eventContext) return;
|
|
560
|
+
|
|
561
|
+
// 3) Handle lifecycle changes (create/update/remove task)
|
|
562
|
+
const actions = this.handleTaskLifecycleEvent(eventContext);
|
|
563
|
+
const {task} = actions;
|
|
564
|
+
if (!task) return;
|
|
565
|
+
|
|
566
|
+
// 4) Keep task.data synchronized
|
|
567
|
+
const {payload, stateMachineEvent} = eventContext;
|
|
568
|
+
if (payload) this.updateTaskData(task, payload);
|
|
569
|
+
|
|
570
|
+
// 5) Drive state machine (which emits TASK_EVENTS)
|
|
571
|
+
if (stateMachineEvent) {
|
|
572
|
+
task.sendStateMachineEvent(stateMachineEvent);
|
|
573
|
+
}
|
|
574
|
+
});
|
|
575
|
+
```
|
|
576
|
+
|
|
577
|
+
`TaskManager.handleRealtimeWebsocketEvent()` handles payloads arriving on the realtime subscription socket used for AI features. It:
|
|
578
|
+
|
|
579
|
+
1. Parses the JSON websocket envelope
|
|
580
|
+
|
|
581
|
+
2. Reads `conversationId` from `payload.data.data.conversationId` and resolves the owning task
|
|
582
|
+
|
|
583
|
+
3. Emits the payload type and `payload.data` on the task for `REAL_TIME_TRANSCRIPTION`
|
|
584
|
+
|
|
585
|
+
4. Does the same for `SUGGESTED_RESPONSE`
|
|
586
|
+
|
|
587
|
+
5. Logs and returns when JSON parsing fails or the interaction has no task; other payload types, including `SUGGESTED_RESPONSE_ACKNOWLEDGE`, fall through without task emission
|
|
588
|
+
|
|
589
|
+
This keeps transcript and suggestion delivery aligned on the same per-task event surface.
|
|
590
|
+
|
|
591
|
+
For BROWSER login, TaskManager integrates with WebCalling:
|
|
592
|
+
|
|
593
|
+
```mermaid
|
|
594
|
+
flowchart TD
|
|
595
|
+
A[AgentContactReserved event] --> B[Determine media + loginOption]
|
|
596
|
+
B --> C[TaskFactory chooses Voice/WebRTC class]
|
|
597
|
+
C --> D[Create Task object]
|
|
598
|
+
D --> E[Store in taskCollection]
|
|
599
|
+
E --> F[Send TASK_INCOMING to state machine]
|
|
600
|
+
F --> G[Emit task:incoming]
|
|
601
|
+
H[Independent LINE_EVENTS.INCOMING_CALL] --> I[Find current non-preview telephony task]
|
|
602
|
+
I --> J[Map call ID to interaction ID]
|
|
603
|
+
J --> K[Send TASK_INCOMING association event]
|
|
604
|
+
```
|
|
605
|
+
|
|
606
|
+
```typescript
|
|
607
|
+
// WebCallingService maps call IDs to interaction IDs
|
|
608
|
+
this.webCallingService.mapCallToTask(callId, interactionId);
|
|
609
|
+
|
|
610
|
+
// Task uses call for media operations
|
|
611
|
+
this.webCallingService.answerCall(localAudioStream: LocalMicrophoneStream, taskId: string);
|
|
612
|
+
```
|
|
613
|
+
|
|
614
|
+
AutoWrapup handles automatic task completion:
|
|
615
|
+
|
|
616
|
+
```typescript
|
|
617
|
+
// AutoWrapup.ts
|
|
618
|
+
export default class AutoWrapup {
|
|
619
|
+
private timer: ReturnType<typeof setTimeout> | null = null;
|
|
620
|
+
private readonly interval: number;
|
|
621
|
+
|
|
622
|
+
start(onComplete: () => void) {
|
|
623
|
+
this.timer = setTimeout(onComplete, this.interval);
|
|
624
|
+
}
|
|
625
|
+
|
|
626
|
+
clear() {
|
|
627
|
+
if (this.timer) {
|
|
628
|
+
clearTimeout(this.timer);
|
|
629
|
+
this.timer = null;
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
|
|
633
|
+
getTimeLeft() {}
|
|
634
|
+
isRunning() {}
|
|
635
|
+
getTimeLeftSeconds() {}
|
|
636
|
+
}
|
|
637
|
+
```
|
|
638
|
+
|
|
639
|
+
Each task operation maps to an AQM request:
|
|
640
|
+
|
|
641
|
+
```typescript
|
|
642
|
+
// contact.ts
|
|
643
|
+
export default function routingContact(routing: AqmReqs) {
|
|
644
|
+
return {
|
|
645
|
+
accept: routing.req((p) => ({
|
|
646
|
+
url: '/v1/tasks/.../accept',
|
|
647
|
+
notifSuccess: { bind: { type: CC_EVENTS.AGENT_CONTACT_ASSIGNED }},
|
|
648
|
+
notifFail: { bind: { type: CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED }},
|
|
649
|
+
})),
|
|
650
|
+
|
|
651
|
+
hold: routing.req((p) => ({...})),
|
|
652
|
+
unHold: routing.req((p) => ({...})),
|
|
653
|
+
consultAccept: routing.req((p) => ({...})),
|
|
654
|
+
cancelTask: routing.req((p) => ({...})),
|
|
655
|
+
cancelCtq: routing.req((p) => ({...})),
|
|
656
|
+
end: routing.req((p) => ({...})),
|
|
657
|
+
wrapup: routing.req((p) => ({...})),
|
|
658
|
+
blindTransfer: routing.req((p) => ({...})),
|
|
659
|
+
consult: routing.req((p) => ({...})),
|
|
660
|
+
consultTransfer: routing.req((p) => ({...})),
|
|
661
|
+
// ... more operations
|
|
662
|
+
};
|
|
663
|
+
}
|
|
664
|
+
```
|
|
665
|
+
|
|
666
|
+
Helper functions for task state analysis:
|
|
667
|
+
|
|
668
|
+
```typescript
|
|
669
|
+
// TaskUtils.ts
|
|
670
|
+
|
|
671
|
+
// Check if participant is in main interaction
|
|
672
|
+
isParticipantInMainInteraction(task, agentId);
|
|
673
|
+
|
|
674
|
+
// Check if conference is in progress
|
|
675
|
+
getIsConferenceInProgress(taskData);
|
|
676
|
+
|
|
677
|
+
// Check if agent is primary
|
|
678
|
+
isPrimary(task, agentId);
|
|
679
|
+
|
|
680
|
+
// Check if secondary EPDN agent
|
|
681
|
+
isSecondaryEpDnAgent(interaction);
|
|
682
|
+
```
|
|
683
|
+
|
|
684
|
+
| Metric | Type | When Tracked |
|
|
685
|
+
|---|---|---|
|
|
686
|
+
| `TASK_ACCEPT_SUCCESS` | behavioral, business | Task accepted |
|
|
687
|
+
| `TASK_HOLD_SUCCESS` | operational | Hold succeeded |
|
|
688
|
+
| `TASK_END_SUCCESS` | behavioral, business | Task ended |
|
|
689
|
+
| `TASK_WRAPUP_SUCCESS` | operational | Wrapup completed |
|
|
690
|
+
| `TASK_TRANSFER_SUCCESS` | behavioral, business | Transfer completed |
|
|
691
|
+
| `TASK_OUTDIAL_SUCCESS` | behavioral, business | Outdial completed |
|
|
692
|
+
|
|
693
|
+
## Data Flow
|
|
694
|
+
1. **WebSocket event arrives** → `TaskManager` maps CC event to `TaskEvent`.
|
|
695
|
+
|
|
696
|
+
2. **Task creation** (if new) → `TaskFactory` builds `Voice`/`WebRTC`/`Digital`.
|
|
697
|
+
|
|
698
|
+
3. **State machine actor starts** → `Task` wires emitters + UI control updates.
|
|
699
|
+
|
|
700
|
+
4. **Task method called** (e.g., hold/transfer) → delegates to `contact.ts` or `dialer.ts`.
|
|
701
|
+
|
|
702
|
+
5. **State transitions** → guards/actions update context and emit `task:*` events.
|
|
703
|
+
|
|
704
|
+
6. **SDK consumers update UI** → `TaskUIControls` reflect the latest state.
|
|
705
|
+
|
|
706
|
+
```mermaid
|
|
707
|
+
flowchart TD
|
|
708
|
+
A[WebSocket event arrives] --> B[TaskManager maps CC event to TaskEvent]
|
|
709
|
+
B --> C{Task exists?}
|
|
710
|
+
C -- No --> D[TaskFactory creates Voice/WebRTC/Digital]
|
|
711
|
+
C -- Yes --> E[Use existing task]
|
|
712
|
+
D --> F[Task initializes state machine actor]
|
|
713
|
+
E --> F
|
|
714
|
+
F --> G[Task method called (hold/transfer/etc)]
|
|
715
|
+
G --> H[contact.ts or dialer.ts API call]
|
|
716
|
+
H --> I[State machine transition]
|
|
717
|
+
I --> J[Actions + guards update context]
|
|
718
|
+
J --> K[Emit task:* events]
|
|
719
|
+
K --> L[TaskUIControls updated for SDK UI]
|
|
720
|
+
```
|
|
721
|
+
|
|
722
|
+
## Sequence Diagram(s)
|
|
723
|
+
Sequence coverage:
|
|
724
|
+
|
|
725
|
+
| Operation group | Diagram | Failure / recovery coverage |
|
|
726
|
+
|---|---|---|
|
|
727
|
+
| Incoming task creation | Incoming task | Unsupported media or incomplete event context prevents publication of a partial task. |
|
|
728
|
+
| Voice hold/resume | Hold or resume | Invalid state rejects before transport; failure notification/HTTP error/timeout sends the matching failure event and throws. |
|
|
729
|
+
| Consult/transfer/conference | Consult and transfer | Backend failure notifications drive explicit failure actions and preserve stable call context. |
|
|
730
|
+
| Wrapup/end | Wrapup or end | Validation and backend failures throw; backend events retain distinct WRAPPING_UP/COMPLETED/TERMINATED outcomes. |
|
|
731
|
+
| WebRTC and digital accept | Channel-specific accept | WebRTC media/calling failure and Digital AQM failure follow different rejection paths. |
|
|
732
|
+
|
|
733
|
+
### Incoming task
|
|
734
|
+
|
|
735
|
+
```mermaid
|
|
736
|
+
sequenceDiagram
|
|
737
|
+
participant Backend
|
|
738
|
+
participant WS as Primary WebSocket
|
|
739
|
+
participant TM as TaskManager
|
|
740
|
+
participant TF as TaskFactory
|
|
741
|
+
participant Task
|
|
742
|
+
participant CC as ContactCenter
|
|
743
|
+
participant App as Application
|
|
744
|
+
Backend-->>WS: AgentContactReserved event
|
|
745
|
+
WS-->>TM: message event
|
|
746
|
+
TM->>TM: parse and prepare event context
|
|
747
|
+
alt task is new and media is supported
|
|
748
|
+
TM->>TF: createTask(data, dependencies)
|
|
749
|
+
TF-->>TM: Voice / WebRTC / Digital
|
|
750
|
+
TM->>Task: initialize and store
|
|
751
|
+
TM->>Task: send mapped TaskEvent
|
|
752
|
+
TM-->>CC: emit TASK_INCOMING
|
|
753
|
+
CC-->>App: task:incoming
|
|
754
|
+
opt a BROWSER telephony INCOMING_CALL arrives
|
|
755
|
+
TM->>TM: map call ID to current telephony task
|
|
756
|
+
TM->>Task: send TASK_INCOMING for call/task association
|
|
757
|
+
end
|
|
758
|
+
else unsupported media
|
|
759
|
+
TF-->>TM: throw Unknown media type
|
|
760
|
+
else invalid/unmapped event
|
|
761
|
+
TM->>TM: ignore without publishing a partial task
|
|
762
|
+
end
|
|
763
|
+
```
|
|
764
|
+
|
|
765
|
+
### Hold or resume
|
|
766
|
+
|
|
767
|
+
```mermaid
|
|
768
|
+
sequenceDiagram
|
|
769
|
+
participant App
|
|
770
|
+
participant Voice
|
|
771
|
+
participant Actor as Task state-machine actor
|
|
772
|
+
participant Contact as routingContact
|
|
773
|
+
participant AQM as AqmReqs
|
|
774
|
+
participant WR as WebexRequest
|
|
775
|
+
participant WS as Primary WebSocket
|
|
776
|
+
App->>Voice: hold() or resume()
|
|
777
|
+
Voice->>Voice: derive media resource and validate CONNECTED/HELD state
|
|
778
|
+
alt invalid state
|
|
779
|
+
Voice-->>App: throw before transport
|
|
780
|
+
else valid state
|
|
781
|
+
Voice->>Actor: HOLD_INITIATED or UNHOLD_INITIATED
|
|
782
|
+
Voice->>Contact: hold/unHold({interactionId, data: {mediaResourceId}})
|
|
783
|
+
Contact->>AQM: generated request with success/failure binds
|
|
784
|
+
AQM->>WR: authenticated HTTP request
|
|
785
|
+
WR-->>AQM: acknowledgement only
|
|
786
|
+
alt matching success notification
|
|
787
|
+
WS-->>AQM: AGENT_CONTACT_HELD/UNHELD
|
|
788
|
+
AQM-->>Voice: TaskResponse
|
|
789
|
+
Voice->>Actor: HOLD_SUCCESS or UNHOLD_SUCCESS
|
|
790
|
+
Voice-->>App: resolve
|
|
791
|
+
else matching failure, HTTP rejection, or timeout
|
|
792
|
+
WS-->>AQM: failure notification or no completion
|
|
793
|
+
Voice->>Actor: HOLD_FAILED or UNHOLD_FAILED
|
|
794
|
+
Voice-->>App: throw detailed error
|
|
795
|
+
end
|
|
796
|
+
end
|
|
797
|
+
```
|
|
798
|
+
|
|
799
|
+
### Consult and transfer
|
|
800
|
+
|
|
801
|
+
```mermaid
|
|
802
|
+
sequenceDiagram
|
|
803
|
+
participant App
|
|
804
|
+
participant Voice
|
|
805
|
+
participant Actor as Task state-machine actor
|
|
806
|
+
participant Contact as routingContact
|
|
807
|
+
participant WS as Primary WebSocket
|
|
808
|
+
App->>Voice: consult/transfer/conference operation
|
|
809
|
+
Voice->>Actor: initiating event
|
|
810
|
+
Voice->>Contact: correlated AQM operation
|
|
811
|
+
alt matching success notification
|
|
812
|
+
WS-->>Contact: consult/transfer/conference success
|
|
813
|
+
Contact-->>Voice: TaskResponse
|
|
814
|
+
Voice->>Actor: success event and updated call context
|
|
815
|
+
Voice-->>App: resolve
|
|
816
|
+
else failure/cancel/timeout
|
|
817
|
+
WS-->>Contact: failure or cancel notification
|
|
818
|
+
Voice->>Actor: matching failure/end event
|
|
819
|
+
Voice-->>App: throw while preserving stable main/consult context
|
|
820
|
+
end
|
|
821
|
+
```
|
|
822
|
+
|
|
823
|
+
### Wrapup or end
|
|
824
|
+
|
|
825
|
+
```mermaid
|
|
826
|
+
sequenceDiagram
|
|
827
|
+
participant App
|
|
828
|
+
participant Task
|
|
829
|
+
participant Contact as routingContact
|
|
830
|
+
participant WS as Primary WebSocket
|
|
831
|
+
participant Actor as Task state-machine actor
|
|
832
|
+
App->>Task: wrapup(payload) or end()
|
|
833
|
+
alt invalid wrapup payload or missing task data
|
|
834
|
+
Task-->>App: throw validation error
|
|
835
|
+
else valid request
|
|
836
|
+
Task->>Contact: wrapup/end({interactionId, data})
|
|
837
|
+
alt matching success notification
|
|
838
|
+
WS-->>Contact: wrapup/end success
|
|
839
|
+
Contact-->>Task: TaskResponse
|
|
840
|
+
WS-->>Actor: backend event selects WRAPPING_UP/COMPLETED/TERMINATED
|
|
841
|
+
Task-->>App: resolve
|
|
842
|
+
else failure, HTTP rejection, or timeout
|
|
843
|
+
Contact-->>Task: structured rejection
|
|
844
|
+
Task-->>App: throw detailed error
|
|
845
|
+
end
|
|
846
|
+
end
|
|
847
|
+
```
|
|
848
|
+
|
|
849
|
+
### Channel-specific accept
|
|
850
|
+
|
|
851
|
+
```mermaid
|
|
852
|
+
sequenceDiagram
|
|
853
|
+
participant App
|
|
854
|
+
participant TF as TaskFactory
|
|
855
|
+
participant WebRTC
|
|
856
|
+
participant Digital
|
|
857
|
+
participant Calling as WebCallingService
|
|
858
|
+
participant Contact as routingContact
|
|
859
|
+
TF-->>App: WebRTC or Digital task
|
|
860
|
+
alt WebRTC accept
|
|
861
|
+
App->>WebRTC: accept()
|
|
862
|
+
WebRTC->>WebRTC: getUserMedia(audio)
|
|
863
|
+
WebRTC->>Calling: answerCall(localAudioStream, interactionId)
|
|
864
|
+
alt media/calling succeeds
|
|
865
|
+
WebRTC-->>App: resolve
|
|
866
|
+
else media/calling failure
|
|
867
|
+
WebRTC-->>App: throw detailed error
|
|
868
|
+
end
|
|
869
|
+
else Digital accept
|
|
870
|
+
App->>Digital: accept()
|
|
871
|
+
Digital->>Contact: accept({interactionId})
|
|
872
|
+
alt AQM success notification
|
|
873
|
+
Contact-->>Digital: TaskResponse
|
|
874
|
+
Digital-->>App: resolve
|
|
875
|
+
else failure or timeout
|
|
876
|
+
Digital-->>App: throw detailed error
|
|
877
|
+
end
|
|
878
|
+
end
|
|
879
|
+
```
|
|
880
|
+
|
|
881
|
+
## Class / Component Relationships
|
|
882
|
+
- **Hierarchy**: `Task` (base) → `Voice` → `WebRTC`; `Digital` extends `Task`.
|
|
883
|
+
|
|
884
|
+
- **`Task` (base)**: Holds task data, emits SDK events, and provides default (unsupported) implementations for call control APIs.
|
|
885
|
+
|
|
886
|
+
- **`Voice`**: Adds hold/resume and consult-related capabilities for telephony tasks.
|
|
887
|
+
|
|
888
|
+
- **`WebRTC`**: Overrides `accept/decline` for WebRTC calls and hooks media events.
|
|
889
|
+
|
|
890
|
+
- **`Digital`**: Implements `accept` and refreshes digital task data/UI controls.
|
|
891
|
+
|
|
892
|
+
| Component | File | Responsibility |
|
|
893
|
+
|---|---|---|
|
|
894
|
+
| `TaskManager` | `task/TaskManager.ts` | Task lifecycle coordination |
|
|
895
|
+
| `Task` | `task/Task.ts` | Individual task operations |
|
|
896
|
+
| `contact` | `task/contact.ts` | AQM request definitions |
|
|
897
|
+
| `dialer` | `task/dialer.ts` | Outbound call initiation |
|
|
898
|
+
| `AutoWrapup` | `task/AutoWrapup.ts` | Auto wrapup timer |
|
|
899
|
+
| `taskDataNormalizer` | `task/taskDataNormalizer.ts` | Normalizes backend task payloads |
|
|
900
|
+
| `TaskUtils` | `task/TaskUtils.ts` | Utility functions |
|
|
901
|
+
| `state-machine` | `task/state-machine/*` | Task state transitions, guards, and UI control computation |
|
|
902
|
+
|
|
903
|
+
**File:** `Task.ts`
|
|
904
|
+
|
|
905
|
+
**Properties**
|
|
906
|
+
|
|
907
|
+
- `data: TaskData`
|
|
908
|
+
|
|
909
|
+
- `webCallMap: Record<TaskId, CallId>`
|
|
910
|
+
|
|
911
|
+
- `stateMachineService?: ActorRefFrom<TaskStateMachine>`
|
|
912
|
+
|
|
913
|
+
- `state?: SnapshotFrom<TaskStateMachine>`
|
|
914
|
+
|
|
915
|
+
- `autoWrapup?: AutoWrapup`
|
|
916
|
+
|
|
917
|
+
- `uiControls: TaskUIControls` (getter)
|
|
918
|
+
|
|
919
|
+
**Methods**
|
|
920
|
+
|
|
921
|
+
- `accept(): Promise<TaskResponse>` (abstract)
|
|
922
|
+
|
|
923
|
+
- `decline(): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
924
|
+
|
|
925
|
+
- `pauseRecording(): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
926
|
+
|
|
927
|
+
- `resumeRecording(resumeRecordingPayload: ResumeRecordingPayload): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
928
|
+
|
|
929
|
+
- `consult(consultPayload: ConsultPayload): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
930
|
+
|
|
931
|
+
- `endConsult(consultEndPayload?: ConsultEndPayload): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
932
|
+
|
|
933
|
+
- `consultTransfer(consultTransferPayload?: ConsultTransferPayLoad): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
934
|
+
|
|
935
|
+
- `consultConference(): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
936
|
+
|
|
937
|
+
- `exitConference(): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
938
|
+
|
|
939
|
+
- `transferConference(): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
940
|
+
|
|
941
|
+
- `switchCall(): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
942
|
+
|
|
943
|
+
- `toggleMute(): Promise<void>` (default: unsupportedMethodError)
|
|
944
|
+
|
|
945
|
+
- `unregisterWebCallListeners(): void` (default: no-op + log)
|
|
946
|
+
|
|
947
|
+
- `cancelAutoWrapupTimer(): void`
|
|
948
|
+
|
|
949
|
+
- Concrete `Task.hold()` / `Voice.hold()` are parameterless; the `ITask` compatibility interface permits `hold(mediaResourceId?: string)`.
|
|
950
|
+
|
|
951
|
+
- Concrete `Task.resume()` / `Voice.resume()` are parameterless; the `ITask` compatibility interface permits `resume(mediaResourceId?: string)`.
|
|
952
|
+
|
|
953
|
+
- `holdResume(): Promise<TaskResponse>` (default: unsupportedMethodError)
|
|
954
|
+
|
|
955
|
+
- `sendStateMachineEvent(event: TaskEventPayload): void`
|
|
956
|
+
|
|
957
|
+
- `updateTaskData(updatedData: TaskData, shouldOverwrite = false): ITask`
|
|
958
|
+
|
|
959
|
+
- `transfer(transferPayload: TransferPayLoad): Promise<TaskResponse>`
|
|
960
|
+
|
|
961
|
+
- `end(): Promise<TaskResponse>`
|
|
962
|
+
|
|
963
|
+
- `wrapup(wrapupPayload: WrapupPayLoad): Promise<TaskResponse>`
|
|
964
|
+
|
|
965
|
+
**File:** `voice/Voice.ts`
|
|
966
|
+
|
|
967
|
+
**Notes**
|
|
968
|
+
|
|
969
|
+
- Extends `Task`.
|
|
970
|
+
|
|
971
|
+
- Provides `hold()` and `resume()` that delegate to `holdResume()`.
|
|
972
|
+
|
|
973
|
+
- Explicitly overrides `accept()` and `decline()` to throw `unsupportedMethodError`.
|
|
974
|
+
|
|
975
|
+
- `WebRTC` then overrides these methods with concrete implementations.
|
|
976
|
+
|
|
977
|
+
**File:** `voice/WebRTC.ts`
|
|
978
|
+
|
|
979
|
+
**Notes**
|
|
980
|
+
|
|
981
|
+
- Extends `Voice`.
|
|
982
|
+
|
|
983
|
+
- Overrides `accept()` and `decline()` for WebRTC calls.
|
|
984
|
+
|
|
985
|
+
- Emits `TASK_EVENTS.TASK_MEDIA` on remote media (`CALL_EVENT_KEYS.REMOTE_MEDIA`).
|
|
986
|
+
|
|
987
|
+
- Overrides `unregisterWebCallListeners()`.
|
|
988
|
+
|
|
989
|
+
**File:** `digital/Digital.ts`
|
|
990
|
+
|
|
991
|
+
**Notes**
|
|
992
|
+
|
|
993
|
+
- Extends `Task`.
|
|
994
|
+
|
|
995
|
+
- Implements `accept()`.
|
|
996
|
+
|
|
997
|
+
- Overrides `updateTaskData()` to refresh digital task data and UI controls.
|
|
998
|
+
|
|
999
|
+
**File:** `TaskFactory.ts`
|
|
1000
|
+
|
|
1001
|
+
**API**
|
|
1002
|
+
|
|
1003
|
+
- `createTask(contact, webCallingService, data, configFlags, wrapupData?, agentId?): Task`
|
|
1004
|
+
|
|
1005
|
+
**Behavior**
|
|
1006
|
+
|
|
1007
|
+
- Chooses `WebRTC` vs `Voice` for `MEDIA_CHANNEL.TELEPHONY` based on `webCallingService.loginOption`.
|
|
1008
|
+
|
|
1009
|
+
- Chooses `Digital` for `MEDIA_CHANNEL.CHAT`, `MEDIA_CHANNEL.EMAIL`, `MEDIA_CHANNEL.SOCIAL`.
|
|
1010
|
+
|
|
1011
|
+
- Throws `Error` for unknown media types.
|
|
1012
|
+
|
|
1013
|
+
```mermaid
|
|
1014
|
+
classDiagram
|
|
1015
|
+
class Task {
|
|
1016
|
+
<<abstract>>
|
|
1017
|
+
# contact
|
|
1018
|
+
# metricsManager
|
|
1019
|
+
+ data: TaskData
|
|
1020
|
+
+ webCallMap: Record~TaskId, CallId~
|
|
1021
|
+
+ stateMachineService
|
|
1022
|
+
+ state
|
|
1023
|
+
# currentUiControls: TaskUIControls
|
|
1024
|
+
# uiControlConfig: UIControlConfig
|
|
1025
|
+
+ autoWrapup: AutoWrapup
|
|
1026
|
+
+ accept() TaskResponse
|
|
1027
|
+
+ transfer(payload) TaskResponse
|
|
1028
|
+
+ end() TaskResponse
|
|
1029
|
+
+ wrapup(payload) TaskResponse
|
|
1030
|
+
+ updateTaskData(updatedData, shouldOverwrite) ITask
|
|
1031
|
+
+ sendStateMachineEvent(event) void
|
|
1032
|
+
+ hold() TaskResponse
|
|
1033
|
+
+ resume() TaskResponse
|
|
1034
|
+
+ holdResume() TaskResponse
|
|
1035
|
+
+ consult(payload) TaskResponse
|
|
1036
|
+
+ endConsult(payload) TaskResponse
|
|
1037
|
+
+ consultTransfer(payload) TaskResponse
|
|
1038
|
+
+ consultConference() TaskResponse
|
|
1039
|
+
+ exitConference() TaskResponse
|
|
1040
|
+
+ transferConference() TaskResponse
|
|
1041
|
+
+ pauseRecording() TaskResponse
|
|
1042
|
+
+ resumeRecording(payload) TaskResponse
|
|
1043
|
+
+ toggleMute() void
|
|
1044
|
+
+ unregisterWebCallListeners() void
|
|
1045
|
+
+ cancelAutoWrapupTimer() void
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1048
|
+
class Voice {
|
|
1049
|
+
+ accept() TaskResponse
|
|
1050
|
+
+ decline() TaskResponse
|
|
1051
|
+
+ hold() TaskResponse
|
|
1052
|
+
+ resume() TaskResponse
|
|
1053
|
+
+ holdResume() TaskResponse
|
|
1054
|
+
+ pauseRecording() TaskResponse
|
|
1055
|
+
+ resumeRecording(payload) TaskResponse
|
|
1056
|
+
+ consult(payload) TaskResponse
|
|
1057
|
+
+ endConsult(payload) TaskResponse
|
|
1058
|
+
+ transfer(payload) TaskResponse
|
|
1059
|
+
+ consultConference() TaskResponse
|
|
1060
|
+
+ exitConference() TaskResponse
|
|
1061
|
+
+ transferConference() TaskResponse
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
class WebRTC {
|
|
1065
|
+
- localAudioStream: LocalMicrophoneStream
|
|
1066
|
+
- webCallingService: WebCallingService
|
|
1067
|
+
+ accept() TaskResponse
|
|
1068
|
+
+ decline() TaskResponse
|
|
1069
|
+
+ toggleMute() void
|
|
1070
|
+
+ unregisterWebCallListeners() void
|
|
1071
|
+
}
|
|
1072
|
+
|
|
1073
|
+
class Digital {
|
|
1074
|
+
+ accept() TaskResponse
|
|
1075
|
+
+ updateTaskData(newData, shouldOverwrite) IDigital
|
|
1076
|
+
}
|
|
1077
|
+
|
|
1078
|
+
class TaskFactory {
|
|
1079
|
+
+ createTask(contact, webCallingService, data, configFlags, wrapupData, agentId) Task
|
|
1080
|
+
}
|
|
1081
|
+
|
|
1082
|
+
Task <|-- Voice
|
|
1083
|
+
Voice <|-- WebRTC
|
|
1084
|
+
Task <|-- Digital
|
|
1085
|
+
|
|
1086
|
+
TaskFactory ..> Task : creates
|
|
1087
|
+
TaskFactory ..> Voice : creates
|
|
1088
|
+
TaskFactory ..> WebRTC : creates
|
|
1089
|
+
TaskFactory ..> Digital : creates
|
|
1090
|
+
```
|
|
1091
|
+
|
|
1092
|
+
## Use Cases
|
|
1093
|
+
- **UC-1 Incoming task creation:** TaskManager maps a backend offer/reservation, TaskFactory creates a supported Voice/WebRTC/Digital task, and ContactCenter emits the typed incoming event. Evidence: `src/services/task/TaskManager.ts`, `src/services/task/TaskFactory.ts`, `test/unit/spec/services/task`.
|
|
1094
|
+
- **UC-2 Accept/hold/resume:** the Task or Voice method delegates the remote operation and sends the matching typed event to its actor; concrete hold/resume implementations are parameterless. Evidence: `src/services/task/Task.ts`, `src/services/task/voice/Voice.ts`, `test/unit/spec/services/task`.
|
|
1095
|
+
- **UC-3 Consult/transfer/conference:** Voice coordinates contact routing with initiating/stable actor states and emits the corresponding complete `TASK_EVENTS` contract. Evidence: `src/services/task/voice/Voice.ts`, `src/services/task/types.ts`, `test/unit/spec/services/task`.
|
|
1096
|
+
- **UC-4 Wrapup/end:** backend end/wrapup notifications drive WRAPPING_UP and final COMPLETED/TERMINATED outcomes without collapsing them into one result. Evidence: `src/services/task/Task.ts`, `src/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task`.
|
|
1097
|
+
- **UC-5 WebRTC and digital behavior:** TaskFactory selects channel-specific subclasses; unsupported SMS/Facebook/WhatsApp values throw `Unknown media type`. Evidence: `src/services/task/TaskFactory.ts`, `test/unit/spec/services/task/TaskFactory.ts`.
|
|
1098
|
+
|
|
1099
|
+
> **Host-application example:** This consumer callback performs task operations without logging raw interaction data.
|
|
1100
|
+
|
|
1101
|
+
```typescript
|
|
1102
|
+
// Listen for incoming tasks
|
|
1103
|
+
cc.on('task:incoming', async (task) => {
|
|
1104
|
+
// Accept the task
|
|
1105
|
+
await task.accept();
|
|
1106
|
+
|
|
1107
|
+
// Task operations
|
|
1108
|
+
await task.hold();
|
|
1109
|
+
await task.resume();
|
|
1110
|
+
await task.end();
|
|
1111
|
+
await task.wrapup({
|
|
1112
|
+
wrapUpReason: 'Resolved',
|
|
1113
|
+
auxCodeId: 'wrapup-code',
|
|
1114
|
+
});
|
|
1115
|
+
});
|
|
1116
|
+
```
|
|
1117
|
+
|
|
1118
|
+
## State Model
|
|
1119
|
+
Each Task owns an XState actor and current task data. TaskManager maps backend notifications into actor events; backend task data remains authoritative for hydration. Stable interaction states and initiating/terminal states are defined by the nested task-state-machine module.
|
|
1120
|
+
|
|
1121
|
+
## Business Rules & Invariants
|
|
1122
|
+
- TaskFactory creates only implemented media subclasses and throws for unsupported media values.
|
|
1123
|
+
- Concrete Task/Voice `hold()` and `resume()` methods are parameterless even though the broader `ITask` declaration retains an optional media-resource parameter.
|
|
1124
|
+
- Task event names come from `TASK_EVENTS`; actor transition names come from `TaskEvent`, and callers must not substitute raw strings.
|
|
1125
|
+
- Task owns no credentials or authentication policy; contact/dialer factories delegate authenticated requests through AqmReqs and Core/WebexRequest.
|
|
1126
|
+
- Preview skip/remove delegation is conditional: `campaignPreviewSkipDisabled === 'true'` or `campaignPreviewRemoveDisabled === 'true'` causes ContactCenter to throw before the dialer starts an HTTP or WebSocket-correlated AQM operation. Accept has no equivalent pre-guard.
|
|
1127
|
+
|
|
1128
|
+
## Concurrency & Reactive Flow
|
|
1129
|
+
- Remote contact/dialer operations complete asynchronously through AQM correlation. Backend WebSocket notifications are separately mapped by TaskManager and delivered to the owning actor in arrival order.
|
|
1130
|
+
|
|
1131
|
+
## State Machine
|
|
1132
|
+
```mermaid
|
|
1133
|
+
stateDiagram-v2
|
|
1134
|
+
[*] --> IDLE
|
|
1135
|
+
IDLE --> OFFERED: task offer
|
|
1136
|
+
OFFERED --> CONNECTED: assignment / accept
|
|
1137
|
+
CONNECTED --> HELD: hold succeeds
|
|
1138
|
+
HELD --> CONNECTED: resume succeeds
|
|
1139
|
+
CONNECTED --> CONSULTING: consult succeeds
|
|
1140
|
+
HELD --> CONSULTING: consult succeeds
|
|
1141
|
+
CONSULTING --> CONFERENCING: conference succeeds
|
|
1142
|
+
CONNECTED --> WRAPPING_UP: wrapup required after end
|
|
1143
|
+
HELD --> WRAPPING_UP: wrapup required after end
|
|
1144
|
+
WRAPPING_UP --> COMPLETED: wrapup complete
|
|
1145
|
+
CONNECTED --> TERMINATED: end without wrapup
|
|
1146
|
+
COMPLETED --> [*]
|
|
1147
|
+
TERMINATED --> [*]
|
|
1148
|
+
```
|
|
1149
|
+
|
|
1150
|
+
- **Factory**: `TaskFactory.ts` selects `WebRTC`, `Voice`, or `Digital` based on `MEDIA_CHANNEL` and `webCallingService.loginOption`.
|
|
1151
|
+
|
|
1152
|
+
- **Initialization**: `Task.ts` creates a state machine actor using `createTaskStateMachine(...)`, wires action overrides (emitters), and starts the actor.
|
|
1153
|
+
|
|
1154
|
+
- **Task State**: The task holds `stateMachineService` and uses it to send `TaskEvent` payloads.
|
|
1155
|
+
|
|
1156
|
+
Example (state machine init inside a task object):
|
|
1157
|
+
|
|
1158
|
+
```typescript
|
|
1159
|
+
const machine = createTaskStateMachine(uiControlConfig, {
|
|
1160
|
+
actions: {
|
|
1161
|
+
emitTaskIncoming: ({event}) => task.emit('task:incoming', task),
|
|
1162
|
+
},
|
|
1163
|
+
});
|
|
1164
|
+
const actor = createActor(machine);
|
|
1165
|
+
actor.start();
|
|
1166
|
+
```
|
|
1167
|
+
|
|
1168
|
+
`Task` delegates lifecycle transitions and control-state derivation to the state machine:
|
|
1169
|
+
|
|
1170
|
+
- Transition graph: `state-machine/TaskStateMachine.ts`
|
|
1171
|
+
|
|
1172
|
+
- Transition conditions: `state-machine/guards.ts`
|
|
1173
|
+
|
|
1174
|
+
- Context mutation and integration hooks: `state-machine/actions.ts`
|
|
1175
|
+
|
|
1176
|
+
- UI control derivation: `state-machine/uiControlsComputer.ts`
|
|
1177
|
+
|
|
1178
|
+
For state-machine-specific implementation guidance, use:
|
|
1179
|
+
|
|
1180
|
+
- `../state-machine/ai-docs/task-state-machine-spec.md`
|
|
1181
|
+
|
|
1182
|
+
- **Active lifecycle + intermediate states**:
|
|
1183
|
+
|
|
1184
|
+
- `IDLE`, `OFFERED`, `CONNECTED`
|
|
1185
|
+
|
|
1186
|
+
- `HOLD_INITIATING`, `HELD`, `RESUME_INITIATING`
|
|
1187
|
+
|
|
1188
|
+
- `CONSULT_INITIATING`, `CONSULTING`, `CONF_INITIATING`
|
|
1189
|
+
|
|
1190
|
+
- `CONFERENCING`, `WRAPPING_UP`, `COMPLETED`, `TERMINATED`
|
|
1191
|
+
|
|
1192
|
+
- **Future placeholders (defined, not currently implemented in transitions)**:
|
|
1193
|
+
|
|
1194
|
+
- `CONSULT_INITIATED`, `CONSULT_COMPLETED`, `POST_CALL`, `PARKED`, `MONITORING`
|
|
1195
|
+
|
|
1196
|
+
### Signature and ownership clarifications
|
|
1197
|
+
|
|
1198
|
+
- `Task.endConsult(consultEndPayload)` requires a payload at the base class; `Voice.endConsult(consultEndPayload?)` accepts it optionally.
|
|
1199
|
+
- `ITask.hold(mediaResourceId?)` and `ITask.resume(mediaResourceId?)` are compatibility-interface shapes; current concrete Task/Voice methods accept no argument.
|
|
1200
|
+
- TaskFactory supports the media implementations present in its switch and throws for unsupported media values rather than silently constructing a generic task.
|
|
1201
|
+
|
|
1202
|
+
## Protocol / Wire Format
|
|
1203
|
+
- Request, response, and event payload ownership is anchored in `src/services/task/Task.ts`. HTTP initiates backend work where applicable; WebSocket messages provide realtime events and, for AQM flows, correlated completion.
|
|
1204
|
+
|
|
1205
|
+
## Error Handling & Failure Modes
|
|
1206
|
+
| Condition | Signal (error/code/result) | Caller recovery |
|
|
1207
|
+
|---|---|---|
|
|
1208
|
+
| Dependency rejection | Typed/rethrown error or failure event | Inspect structured details, preserve tracking id, and retry only when the operation is safe. |
|
|
1209
|
+
| Timeout or missing async completion | Timeout/recovery state | Follow the module-specific recovery path; never synthesize success. |
|
|
1210
|
+
|
|
1211
|
+
> **Host-application example:** `showTransferFailure` represents consumer-owned error presentation. SDK package implementation must use `LoggerProxy` and avoid logging raw runtime values.
|
|
1212
|
+
|
|
1213
|
+
```typescript
|
|
1214
|
+
try {
|
|
1215
|
+
await task.transfer({
|
|
1216
|
+
to: 'queue-123',
|
|
1217
|
+
destinationType: 'queue',
|
|
1218
|
+
});
|
|
1219
|
+
} catch (error) {
|
|
1220
|
+
showTransferFailure(error);
|
|
1221
|
+
}
|
|
1222
|
+
```
|
|
1223
|
+
|
|
1224
|
+
**Cause**: Agent not available or TaskManager not initialized
|
|
1225
|
+
|
|
1226
|
+
**Solution**:
|
|
1227
|
+
|
|
1228
|
+
1. Ensure `cc.register()` completed
|
|
1229
|
+
|
|
1230
|
+
2. Ensure `cc.stationLogin()` completed
|
|
1231
|
+
|
|
1232
|
+
3. Ensure agent state is Available
|
|
1233
|
+
|
|
1234
|
+
**Cause**: Task state doesn't allow operation
|
|
1235
|
+
|
|
1236
|
+
**Solution**: Check task state before operation:
|
|
1237
|
+
|
|
1238
|
+
```typescript
|
|
1239
|
+
if (task.uiControls.main.hold.isEnabled) {
|
|
1240
|
+
await task.hold();
|
|
1241
|
+
}
|
|
1242
|
+
|
|
1243
|
+
// During a consult, use the consult-leg controls instead.
|
|
1244
|
+
if (task.uiControls.consult.hold.isEnabled) {
|
|
1245
|
+
// Render or enable the consult-leg hold action.
|
|
1246
|
+
}
|
|
1247
|
+
```
|
|
1248
|
+
|
|
1249
|
+
**Cause**: Call not mapped to task
|
|
1250
|
+
|
|
1251
|
+
**Solution**: Ensure BROWSER login and mercury connected:
|
|
1252
|
+
|
|
1253
|
+
```typescript
|
|
1254
|
+
await webex.internal.mercury.connect();
|
|
1255
|
+
await cc.stationLogin({ loginOption: 'BROWSER', ... });
|
|
1256
|
+
```
|
|
1257
|
+
|
|
1258
|
+
## Pitfalls
|
|
1259
|
+
- Concrete `Task`/`Voice` hold and resume methods are parameterless even though the broader `ITask` declaration retains an optional compatibility argument.
|
|
1260
|
+
- AQM HTTP acknowledgement never completes a task operation; success/failure binds or timeout settle the promise and must stay aligned with actor events.
|
|
1261
|
+
- Primary and RTD WebSockets have different ownership: TaskManager uses the RTD stream for transcript/suggestion events and must not emit acknowledgement payloads as public suggestions.
|
|
1262
|
+
|
|
1263
|
+
## Module Do's / Don'ts
|
|
1264
|
+
- DO send initiating and success/failure events to the task actor around remote Voice operations.
|
|
1265
|
+
- DO let TaskFactory select Voice/WebRTC/Digital from media type and login option.
|
|
1266
|
+
- DON'T create a generic task for unsupported media or treat `startOutdial()` as returning an ITask.
|
|
1267
|
+
- DON'T derive hold/resume completion from the HTTP response.
|
|
1268
|
+
|
|
1269
|
+
## Key Design Trade-off
|
|
1270
|
+
- A shared Task base preserves a stable API while media-specific subclasses and a separate state engine enforce capability differences.
|
|
1271
|
+
|
|
1272
|
+
## Test-Case Strategy (module)
|
|
1273
|
+
Use `test/unit/spec/services/task/Task.ts`, `TaskFactory.ts`, `TaskManager.ts`, media-specific suites, contact/dialer suites, and state-machine suites. Cover concrete-versus-interface method signatures, every TASK_EVENTS group, unsupported media rejection, primary/RTD event ownership, injected state actions, preview-campaign accept/skip/remove payloads and failure paths, the disabled skip/remove pre-guards, and success/failure/timeout paths.
|
|
1274
|
+
|
|
1275
|
+
| Behavior / Requirement | Existing test evidence | Gap |
|
|
1276
|
+
|---|---|---|
|
|
1277
|
+
| `TASK-R-001` | `test/unit/spec/services/task/TaskFactory.ts` | None. |
|
|
1278
|
+
| `TASK-R-002` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/voice/Voice.ts` | None. |
|
|
1279
|
+
| `TASK-R-003` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Keep event-catalog parity checks synchronized with `TASK_EVENTS`. |
|
|
1280
|
+
| `TASK-R-004` | `test/unit/spec/services/task/TaskManager.ts` | None. |
|
|
1281
|
+
| `TASK-R-005` | `test/unit/spec/services/task/TaskFactory.ts`, `test/unit/spec/services/task/dialer.ts` | None. |
|
|
1282
|
+
| `TASK-R-006` | `test/unit/spec/services/task/contact.ts`, `test/unit/spec/services/core/WebexRequest.ts` | Authentication ownership is verified across routing/Core boundaries. |
|
|
1283
|
+
| `TASK-R-007` | `test/unit/spec/cc.ts`, `test/unit/spec/services/task/dialer.ts` | Add direct tests proving disabled skip/remove flags throw before dialer invocation; keep public signatures, metrics/error handling, and AQM request contracts synchronized. |
|
|
1284
|
+
|
|
1285
|
+
## Traceability
|
|
1286
|
+
- Repo architecture: `../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../ai-docs/SPEC_INDEX.md`
|
|
1287
|
+
- Coverage state and contracts baseline: `../../../../.sdd/manifest.json`
|
|
1288
|
+
|
|
1289
|
+
- Task creation: `TaskFactory.ts`
|
|
1290
|
+
|
|
1291
|
+
- Task APIs and behavior: `Task.ts`, `voice/Voice.ts`, `voice/WebRTC.ts`, `digital/Digital.ts`
|
|
1292
|
+
|
|
1293
|
+
- Task management: `TaskManager.ts`
|
|
1294
|
+
|
|
1295
|
+
- Shared task types: `types.ts`, `constants.ts`
|
|
1296
|
+
|
|
1297
|
+
- Task lifecycle state machine: `state-machine/TaskStateMachine.ts`
|
|
1298
|
+
|
|
1299
|
+
- State machine types/events: `state-machine/constants.ts`, `state-machine/types.ts`
|
|
1300
|
+
|
|
1301
|
+
- [TaskManager.ts](../TaskManager.ts) - Manager implementation
|
|
1302
|
+
|
|
1303
|
+
- [types.ts](../types.ts) - Type definitions
|
|
1304
|
+
|
|
1305
|
+
- [../state-machine/ai-docs/task-state-machine-spec.md](../state-machine/ai-docs/task-state-machine-spec.md) - State machine implementation guide
|
|
1306
|
+
|
|
1307
|
+
- [../state-machine/ai-docs/task-state-machine-spec.md](../state-machine/ai-docs/task-state-machine-spec.md) - State machine internals
|
|
1308
|
+
|
|
1309
|
+
- [cc.ts](../../../cc.ts) - Main plugin
|
|
1310
|
+
|
|
1311
|
+
- [TaskManager.ts](../TaskManager.ts) - Manager
|
|
1312
|
+
|
|
1313
|
+
- [contact.ts](../contact.ts) - Contact operations
|
|
1314
|
+
|
|
1315
|
+
- [types.ts](../types.ts) - Type definitions
|
|
1316
|
+
|
|
1317
|
+
- [../state-machine/ai-docs/task-state-machine-spec.md](../state-machine/ai-docs/task-state-machine-spec.md) - State machine guide
|
|
1318
|
+
|
|
1319
|
+
- [../state-machine/ai-docs/task-state-machine-spec.md](../state-machine/ai-docs/task-state-machine-spec.md) - State machine architecture
|