@webex/contact-center 3.12.0-next.9 → 3.12.0-task-refactor.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/AGENTS.md +438 -0
- package/ai-docs/README.md +131 -0
- package/ai-docs/RULES.md +455 -0
- package/ai-docs/patterns/event-driven-patterns.md +485 -0
- package/ai-docs/patterns/testing-patterns.md +480 -0
- package/ai-docs/patterns/typescript-patterns.md +365 -0
- package/ai-docs/templates/README.md +102 -0
- package/ai-docs/templates/documentation/create-agents-md.md +240 -0
- package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
- package/ai-docs/templates/existing-service/bug-fix.md +254 -0
- package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
- package/ai-docs/templates/new-method/00-master.md +80 -0
- package/ai-docs/templates/new-method/01-requirements.md +232 -0
- package/ai-docs/templates/new-method/02-implementation.md +295 -0
- package/ai-docs/templates/new-method/03-tests.md +201 -0
- package/ai-docs/templates/new-method/04-validation.md +141 -0
- package/ai-docs/templates/new-service/00-master.md +109 -0
- package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
- package/ai-docs/templates/new-service/02-code-generation.md +346 -0
- package/ai-docs/templates/new-service/03-integration.md +178 -0
- package/ai-docs/templates/new-service/04-test-generation.md +205 -0
- package/ai-docs/templates/new-service/05-validation.md +145 -0
- package/dist/cc.js +65 -123
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +13 -2
- package/dist/constants.js.map +1 -1
- package/dist/index.js +13 -5
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +26 -13
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +7 -6
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +0 -3
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/config/Util.js +2 -3
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/types.js +16 -14
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/constants.js +0 -1
- package/dist/services/constants.js.map +1 -1
- package/dist/services/core/Err.js.map +1 -1
- package/dist/services/core/Utils.js +79 -55
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/core/aqm-reqs.js +17 -92
- package/dist/services/core/aqm-reqs.js.map +1 -1
- package/dist/services/core/websocket/WebSocketManager.js +5 -25
- package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
- package/dist/services/core/websocket/types.js.map +1 -1
- package/dist/services/index.js +1 -2
- package/dist/services/index.js.map +1 -1
- package/dist/services/task/Task.js +644 -0
- package/dist/services/task/Task.js.map +1 -0
- package/dist/services/task/TaskFactory.js +45 -0
- package/dist/services/task/TaskFactory.js.map +1 -0
- package/dist/services/task/TaskManager.js +556 -532
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/TaskUtils.js +132 -28
- package/dist/services/task/TaskUtils.js.map +1 -1
- package/dist/services/task/constants.js +7 -6
- package/dist/services/task/constants.js.map +1 -1
- package/dist/services/task/dialer.js +0 -51
- package/dist/services/task/dialer.js.map +1 -1
- package/dist/services/task/digital/Digital.js +77 -0
- package/dist/services/task/digital/Digital.js.map +1 -0
- package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
- package/dist/services/task/state-machine/actions.js +366 -0
- package/dist/services/task/state-machine/actions.js.map +1 -0
- package/dist/services/task/state-machine/constants.js +139 -0
- package/dist/services/task/state-machine/constants.js.map +1 -0
- package/dist/services/task/state-machine/guards.js +256 -0
- package/dist/services/task/state-machine/guards.js.map +1 -0
- package/dist/services/task/state-machine/index.js +53 -0
- package/dist/services/task/state-machine/index.js.map +1 -0
- package/dist/services/task/state-machine/types.js +54 -0
- package/dist/services/task/state-machine/types.js.map +1 -0
- package/dist/services/task/state-machine/uiControlsComputer.js +369 -0
- package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
- package/dist/services/task/taskDataNormalizer.js +99 -0
- package/dist/services/task/taskDataNormalizer.js.map +1 -0
- package/dist/services/task/types.js +157 -18
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +1031 -0
- package/dist/services/task/voice/Voice.js.map +1 -0
- package/dist/services/task/voice/WebRTC.js +149 -0
- package/dist/services/task/voice/WebRTC.js.map +1 -0
- package/dist/types/cc.d.ts +4 -33
- package/dist/types/constants.d.ts +13 -2
- package/dist/types/index.d.ts +11 -5
- package/dist/types/metrics/constants.d.ts +5 -3
- package/dist/types/services/ApiAiAssistant.d.ts +1 -1
- package/dist/types/services/config/types.d.ts +97 -25
- package/dist/types/services/core/Err.d.ts +0 -2
- package/dist/types/services/core/Utils.d.ts +25 -23
- package/dist/types/services/core/aqm-reqs.d.ts +0 -49
- package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
- package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
- package/dist/types/services/core/websocket/types.d.ts +1 -1
- package/dist/types/services/index.d.ts +1 -1
- package/dist/types/services/task/Task.d.ts +146 -0
- package/dist/types/services/task/TaskFactory.d.ts +12 -0
- package/dist/types/services/task/TaskUtils.d.ts +39 -8
- package/dist/types/services/task/constants.d.ts +5 -4
- package/dist/types/services/task/dialer.d.ts +0 -15
- package/dist/types/services/task/digital/Digital.d.ts +22 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
- package/dist/types/services/task/state-machine/actions.d.ts +8 -0
- package/dist/types/services/task/state-machine/constants.d.ts +91 -0
- package/dist/types/services/task/state-machine/guards.d.ts +78 -0
- package/dist/types/services/task/state-machine/index.d.ts +13 -0
- package/dist/types/services/task/state-machine/types.d.ts +256 -0
- package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
- package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
- package/dist/types/services/task/types.d.ts +539 -88
- package/dist/types/services/task/voice/Voice.d.ts +183 -0
- package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
- package/dist/types/types.d.ts +68 -0
- package/dist/types/webex.d.ts +1 -0
- package/dist/types.js +70 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +14 -2
- package/dist/webex.js.map +1 -1
- package/package.json +14 -11
- package/src/cc.ts +91 -177
- package/src/constants.ts +13 -2
- package/src/index.ts +14 -5
- package/src/metrics/ai-docs/AGENTS.md +348 -0
- package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
- package/src/metrics/behavioral-events.ts +28 -14
- package/src/metrics/constants.ts +7 -8
- package/src/services/ApiAiAssistant.ts +2 -4
- package/src/services/agent/ai-docs/AGENTS.md +238 -0
- package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
- package/src/services/ai-docs/AGENTS.md +384 -0
- package/src/services/config/Util.ts +2 -3
- package/src/services/config/ai-docs/AGENTS.md +253 -0
- package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
- package/src/services/config/types.ts +108 -20
- package/src/services/constants.ts +0 -1
- package/src/services/core/Err.ts +0 -1
- package/src/services/core/Utils.ts +90 -67
- package/src/services/core/ai-docs/AGENTS.md +379 -0
- package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
- package/src/services/core/aqm-reqs.ts +22 -100
- package/src/services/core/websocket/WebSocketManager.ts +4 -23
- package/src/services/core/websocket/types.ts +1 -1
- package/src/services/index.ts +1 -2
- package/src/services/task/Task.ts +785 -0
- package/src/services/task/TaskFactory.ts +55 -0
- package/src/services/task/TaskManager.ts +567 -633
- package/src/services/task/TaskUtils.ts +175 -31
- package/src/services/task/ai-docs/AGENTS.md +448 -0
- package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
- package/src/services/task/constants.ts +5 -4
- package/src/services/task/dialer.ts +1 -56
- package/src/services/task/digital/Digital.ts +95 -0
- package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
- package/src/services/task/state-machine/actions.ts +409 -0
- package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
- package/src/services/task/state-machine/constants.ts +150 -0
- package/src/services/task/state-machine/guards.ts +295 -0
- package/src/services/task/state-machine/index.ts +28 -0
- package/src/services/task/state-machine/types.ts +228 -0
- package/src/services/task/state-machine/uiControlsComputer.ts +529 -0
- package/src/services/task/taskDataNormalizer.ts +137 -0
- package/src/services/task/types.ts +641 -95
- package/src/services/task/voice/Voice.ts +1255 -0
- package/src/services/task/voice/WebRTC.ts +187 -0
- package/src/types.ts +88 -5
- package/src/utils/AGENTS.md +276 -0
- package/src/webex.js +2 -0
- package/test/unit/spec/cc.ts +59 -142
- package/test/unit/spec/logger-proxy.ts +70 -0
- package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
- package/test/unit/spec/services/config/index.ts +26 -55
- package/test/unit/spec/services/core/Utils.ts +103 -52
- package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
- package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
- package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
- package/test/unit/spec/services/task/Task.ts +416 -0
- package/test/unit/spec/services/task/TaskFactory.ts +62 -0
- package/test/unit/spec/services/task/TaskManager.ts +781 -1735
- package/test/unit/spec/services/task/TaskUtils.ts +125 -0
- package/test/unit/spec/services/task/dialer.ts +112 -198
- package/test/unit/spec/services/task/digital/Digital.ts +105 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
- package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
- package/test/unit/spec/services/task/state-machine/types.ts +18 -0
- package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
- package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
- package/test/unit/spec/services/task/voice/Voice.ts +587 -0
- package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
- package/dist/services/task/index.js +0 -1525
- package/dist/services/task/index.js.map +0 -1
- package/dist/types/services/task/index.d.ts +0 -650
- package/src/services/task/index.ts +0 -1801
- package/test/unit/spec/services/task/index.ts +0 -2184
|
@@ -0,0 +1,495 @@
|
|
|
1
|
+
# Task State Machine - AI Agent Guide
|
|
2
|
+
|
|
3
|
+
## Purpose
|
|
4
|
+
|
|
5
|
+
Guide AI agents working on task lifecycle transitions, guard logic, executable actions and UI control computation in the XState-based task state machine.
|
|
6
|
+
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
## Scope
|
|
10
|
+
|
|
11
|
+
This guide is for internal state management for the task lifecycle in:
|
|
12
|
+
|
|
13
|
+
- State machine configuration: `TaskStateMachine.ts`
|
|
14
|
+
- Actions and context mutation: `actions.ts`
|
|
15
|
+
- Guard logic: `guards.ts`
|
|
16
|
+
- UI control computation: `uiControlsComputer.ts`
|
|
17
|
+
- Event types and payloads: `constants.ts`, `types.ts`
|
|
18
|
+
|
|
19
|
+
Use this doc when implementing:
|
|
20
|
+
|
|
21
|
+
- new state transitions
|
|
22
|
+
- event mapping and payload extensions
|
|
23
|
+
- guard/action fixes
|
|
24
|
+
- UI control behavior changes tied to task state
|
|
25
|
+
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
## File Structure
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
state-machine/
|
|
32
|
+
├── TaskStateMachine.ts # State graph and transition configuration
|
|
33
|
+
├── actions.ts # Assign actions and emitter placeholders
|
|
34
|
+
├── guards.ts # Pure guard predicates
|
|
35
|
+
├── uiControlsComputer.ts # Voice/Digital UI control computation
|
|
36
|
+
├── constants.ts # TaskState, TaskEvent, machine constants
|
|
37
|
+
├── types.ts # Context and typed event payload map
|
|
38
|
+
├── index.ts # Public exports
|
|
39
|
+
└── ai-docs/
|
|
40
|
+
├── AGENTS.md # AI coding guide
|
|
41
|
+
└── ARCHITECTURE.md # State machine architecture guide
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
---
|
|
45
|
+
|
|
46
|
+
## Source of Truth
|
|
47
|
+
|
|
48
|
+
- Task lifecycle state machine: `TaskStateMachine.ts`
|
|
49
|
+
- State machine types/events: `constants.ts`, `types.ts`
|
|
50
|
+
- Guard logic: `guards.ts`
|
|
51
|
+
- Actions and context mutation: `actions.ts`
|
|
52
|
+
- UI control computation: `uiControlsComputer.ts`
|
|
53
|
+
|
|
54
|
+
---
|
|
55
|
+
|
|
56
|
+
## Key Capabilities
|
|
57
|
+
|
|
58
|
+
- **State Graph and Transition Rules**: `TaskStateMachine.ts` defines all states, transition tables, and event handlers that drive the task lifecycle.
|
|
59
|
+
- **Deterministic Context Updates**: `actions.ts` implements XState actions for task context mutation and provides emitter placeholders that `Task` overrides to surface SDK events.
|
|
60
|
+
- **Transition Eligibility**: `guards.ts` contains pure predicates that gate transitions based on current context, task data, and backend state.
|
|
61
|
+
- **UI Controls Computation**: `uiControlsComputer.ts` derives `TaskUIControls` from state and context for voice/digital channels, keeping UI enablement centralized.
|
|
62
|
+
- **Typed Event Contracts**: `constants.ts` and `types.ts` define `TaskState`, `TaskEvent`, and the `TaskEventPayloadMap` so transitions and payloads stay type-safe.
|
|
63
|
+
- **Public Exports**: `index.ts` exposes the state machine factory, event enums, and types for consumption by the task layer.
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## State Machine Overview
|
|
68
|
+
|
|
69
|
+
**Transition Source**: `getTaskStateMachineConfig()` in `TaskStateMachine.ts`
|
|
70
|
+
|
|
71
|
+
API-driven transition from `voice/Voice.ts`:
|
|
72
|
+
|
|
73
|
+
```typescript
|
|
74
|
+
// task.hold() / task.resume() -> holdResume()
|
|
75
|
+
stateMachineService.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId});
|
|
76
|
+
// ... backend call succeeds
|
|
77
|
+
stateMachineService.send({type: TaskEvent.HOLD_SUCCESS, mediaResourceId});
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Backend-driven transition from `TaskManager.ts`:
|
|
81
|
+
|
|
82
|
+
```typescript
|
|
83
|
+
const eventPayload = TaskManager.mapEventToTaskStateMachineEvent(
|
|
84
|
+
CC_EVENTS.AGENT_CONTACT_RESERVED,
|
|
85
|
+
taskData
|
|
86
|
+
);
|
|
87
|
+
if (eventPayload) {
|
|
88
|
+
task.sendStateMachineEvent(eventPayload);
|
|
89
|
+
}
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
---
|
|
93
|
+
|
|
94
|
+
### Transition Contract
|
|
95
|
+
|
|
96
|
+
Backend CC events from WebSocket are mapped to `TaskEvent` in `TaskManager.mapEventToTaskStateMachineEvent`.
|
|
97
|
+
The state machine consumes only `TaskEvent` and never raw CC events.
|
|
98
|
+
|
|
99
|
+
### Payload Contract
|
|
100
|
+
|
|
101
|
+
Source of truth: `TaskEventPayloadMap` in `types.ts`.
|
|
102
|
+
All new events must add a typed payload entry in `TaskEventPayloadMap`.
|
|
103
|
+
|
|
104
|
+
---
|
|
105
|
+
|
|
106
|
+
## Non-goals
|
|
107
|
+
|
|
108
|
+
- API contracts for external services.
|
|
109
|
+
- Mercury or CC WebSocket protocols (see `TaskManager.ts` mapping).
|
|
110
|
+
|
|
111
|
+
## Guards
|
|
112
|
+
|
|
113
|
+
Guards are boolean conditions that determine determine if a state transition is allowed. These functions validate the current context before allowing transitions.
|
|
114
|
+
|
|
115
|
+
### Principles
|
|
116
|
+
|
|
117
|
+
- Guards must be pure and must return boolean only
|
|
118
|
+
- No mutation or side-effects.
|
|
119
|
+
- Reuse helper accessors (e.g., `getTaskDataFromEvent`).
|
|
120
|
+
|
|
121
|
+
### State-Based Guards
|
|
122
|
+
|
|
123
|
+
```typescript
|
|
124
|
+
// Check if interaction is in terminated state
|
|
125
|
+
isInteractionTerminated(context, event) {
|
|
126
|
+
return event.taskData?.interaction?.isTerminated === true;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
// Check if interaction is consulting
|
|
130
|
+
isInteractionConsulting(context, event) {
|
|
131
|
+
return event.taskData?.interaction?.state === 'consulting';
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
// Check if interaction is held
|
|
135
|
+
isInteractionHeld(context, event) {
|
|
136
|
+
return event.taskData?.interaction?.state === 'hold';
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Check if interaction is connected
|
|
140
|
+
isInteractionConnected(context, event) {
|
|
141
|
+
return event.taskData?.interaction?.state === 'connected';
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
### Consult Guards
|
|
146
|
+
|
|
147
|
+
```typescript
|
|
148
|
+
// Check if current agent initiated consult
|
|
149
|
+
didInitiateConsult(context, event) {
|
|
150
|
+
if (event.taskData?.isConsulted === true) return false;
|
|
151
|
+
return event.taskData?.consultingAgentId
|
|
152
|
+
? isSelfConsultingAgent(context, event.taskData)
|
|
153
|
+
: context.consultInitiator === true;
|
|
154
|
+
}
|
|
155
|
+
```
|
|
156
|
+
|
|
157
|
+
### Conference Guards
|
|
158
|
+
|
|
159
|
+
```typescript
|
|
160
|
+
// Check if conference is in progress from event taskData
|
|
161
|
+
conferenceInProgressFromEvent(context, event) {
|
|
162
|
+
const taskData = event.taskData;
|
|
163
|
+
if (!taskData?.interaction) return false;
|
|
164
|
+
return getIsConferenceInProgress(taskData);
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Check if conference is in progress by participants
|
|
168
|
+
isConferencingByParticipants(context, event) {
|
|
169
|
+
const taskData = event.taskData;
|
|
170
|
+
if (!taskData) return false;
|
|
171
|
+
|
|
172
|
+
const mainCallId = taskData.interaction?.mainInteractionId || taskData.interactionId;
|
|
173
|
+
const media = taskData.interaction?.media?.[mainCallId];
|
|
174
|
+
const participants = taskData.interaction?.participants;
|
|
175
|
+
if (!media?.participants || !participants) return false;
|
|
176
|
+
|
|
177
|
+
let agentCount = 0;
|
|
178
|
+
for (const pId of media.participants) {
|
|
179
|
+
const p = participants[pId];
|
|
180
|
+
if (p && p.pType !== 'Customer' && p.pType !== 'Supervisor' && !p.hasLeft) {
|
|
181
|
+
agentCount += 1;
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
return agentCount >= 2;
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
// Check if conference should downgrade to connected
|
|
189
|
+
shouldDowngradeConferenceToConnected(context, event) {
|
|
190
|
+
const taskData = event.taskData ?? context.taskData;
|
|
191
|
+
if (!taskData?.interaction) return false;
|
|
192
|
+
|
|
193
|
+
const selfAgentId = getSelfAgentId(context, taskData);
|
|
194
|
+
if (!selfAgentId) return false;
|
|
195
|
+
|
|
196
|
+
const mainCallId = taskData?.interaction?.mainInteractionId || taskData?.interactionId;
|
|
197
|
+
if (!mainCallId) return false;
|
|
198
|
+
|
|
199
|
+
// Do not downgrade while backend still reports active conference state
|
|
200
|
+
if (taskData.interaction.state === 'conference') return false;
|
|
201
|
+
|
|
202
|
+
const agentParticipantsCount = getConferenceParticipantsCount(taskData?.interaction, mainCallId);
|
|
203
|
+
if (agentParticipantsCount >= 2) return false;
|
|
204
|
+
|
|
205
|
+
const customerInCall = getIsCustomerInCall(taskData?.interaction, mainCallId);
|
|
206
|
+
if (!customerInCall) return false;
|
|
207
|
+
|
|
208
|
+
const selfInMainCall = Boolean(
|
|
209
|
+
taskData?.interaction?.media?.[mainCallId]?.participants?.includes(selfAgentId)
|
|
210
|
+
);
|
|
211
|
+
return selfInMainCall;
|
|
212
|
+
}
|
|
213
|
+
```
|
|
214
|
+
|
|
215
|
+
### Wrapup Guards
|
|
216
|
+
|
|
217
|
+
```typescript
|
|
218
|
+
// Check if this agent should move to wrapup
|
|
219
|
+
shouldWrapUp(context, event) {
|
|
220
|
+
const taskData = event.taskData;
|
|
221
|
+
if (!taskData) return false;
|
|
222
|
+
|
|
223
|
+
if (event.type === TaskEvent.CONFERENCE_END) {
|
|
224
|
+
const selfAgentId = getSelfAgentId(context, taskData);
|
|
225
|
+
if (!selfAgentId) return false;
|
|
226
|
+
|
|
227
|
+
const pending = taskData.agentsPendingWrapUp;
|
|
228
|
+
if (Array.isArray(pending) && pending.length > 0) {
|
|
229
|
+
return pending.includes(selfAgentId);
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
const participantWrapUp = taskData.interaction?.participants?.[selfAgentId]?.isWrapUp === true;
|
|
233
|
+
const wrapUpRequired = taskData.wrapUpRequired === true;
|
|
234
|
+
return wrapUpRequired || participantWrapUp;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
return shouldWrapUpForThisAgent(context, taskData);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// Check if wrapup is required OR current agent is consult initiator
|
|
241
|
+
shouldWrapUpOrIsInitiator(context, event) {
|
|
242
|
+
return Boolean(event.taskData?.wrapUpRequired || context.consultInitiator);
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
// Check whether the leaving participant is the current agent
|
|
246
|
+
didCurrentAgentLeaveConference(context, event) {
|
|
247
|
+
const selfAgentId = getSelfAgentId(context, event.taskData);
|
|
248
|
+
if (!selfAgentId) return false;
|
|
249
|
+
|
|
250
|
+
const participantIdFromEvent = 'participantId' in event ? event.participantId : undefined;
|
|
251
|
+
const participantId = participantIdFromEvent ?? event.taskData?.participantId;
|
|
252
|
+
return Boolean(participantId) && participantId === selfAgentId;
|
|
253
|
+
}
|
|
254
|
+
```
|
|
255
|
+
|
|
256
|
+
### Server State Guards
|
|
257
|
+
|
|
258
|
+
```typescript
|
|
259
|
+
// Check if primary media leg is on hold
|
|
260
|
+
isPrimaryMediaOnHold(context, event) {
|
|
261
|
+
const taskData = event.taskData;
|
|
262
|
+
if (!taskData || !taskData.mediaResourceId) return false;
|
|
263
|
+
|
|
264
|
+
return taskData.interaction?.media?.[taskData.mediaResourceId]?.isHold === true;
|
|
265
|
+
}
|
|
266
|
+
```
|
|
267
|
+
|
|
268
|
+
---
|
|
269
|
+
|
|
270
|
+
## Actions
|
|
271
|
+
|
|
272
|
+
Actions are side effects executed during state machine transitions from current state to target state(next state).
|
|
273
|
+
Actions contain:
|
|
274
|
+
|
|
275
|
+
- Context synchronization (`initializeTask`, `updateTaskData`, `syncTaskDataFromEvent`)
|
|
276
|
+
- Lifecycle mutations (`clearConsultState`, `markEnded`, consult/conference flags)
|
|
277
|
+
- Integration hooks (`requestAutoAnswer`, `requestCleanup`, emitter placeholders)
|
|
278
|
+
|
|
279
|
+
### Principles
|
|
280
|
+
|
|
281
|
+
- Context mutations should be centralized in `assign(...)` actions
|
|
282
|
+
- Emitter actions intentionally no-op defaults and overridden by `Task` to bridge machine transitions to SDK events.
|
|
283
|
+
- Deterministic updates from `taskData`.
|
|
284
|
+
|
|
285
|
+
### Context Update Actions
|
|
286
|
+
|
|
287
|
+
```typescript
|
|
288
|
+
// Initialize context for incoming task
|
|
289
|
+
initializeTask(context, event) {
|
|
290
|
+
return {
|
|
291
|
+
consultInitiator: false,
|
|
292
|
+
exitingConference: false,
|
|
293
|
+
consultDestinationType: null,
|
|
294
|
+
consultDestinationAgentJoined: false,
|
|
295
|
+
...deriveTaskDataUpdates(context, event.taskData),
|
|
296
|
+
};
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
// Update taskData + derived recording/consult fields
|
|
300
|
+
updateTaskData(context, event) {
|
|
301
|
+
return deriveTaskDataUpdates(context, event.taskData);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// Keep Task instance data in sync (Task.ts action override)
|
|
305
|
+
syncTaskDataFromEvent(event) {
|
|
306
|
+
this.updateTaskFromEvent(event);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
// Update hold flag on specific media leg in context.taskData.interaction.media
|
|
310
|
+
setHoldState(context, event) {
|
|
311
|
+
// Handles HOLD_SUCCESS and UNHOLD_SUCCESS for event.mediaResourceId
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
// Conference/consult lifecycle mutators
|
|
315
|
+
handleConferenceStarted() { return {consultInitiator: false}; }
|
|
316
|
+
handleConsultFailed() { return {consultDestinationAgentJoined: false, consultInitiator: false}; }
|
|
317
|
+
handleParticipantLeft(event) { return event.taskData ? {taskData: event.taskData} : {}; }
|
|
318
|
+
handleTransferConferenceSuccess(event) { return event.taskData ? {taskData: event.taskData} : {}; }
|
|
319
|
+
|
|
320
|
+
// Consult destination and mode flags
|
|
321
|
+
setConsultDestination(event) { /* sets consultDestinationType and resets consult flags */ }
|
|
322
|
+
setConsultFromConference() { return {consultFromConference: true}; }
|
|
323
|
+
setConsultAgentJoined(event) { /* sets consultDestinationAgentJoined on CONSULTING_ACTIVE */ }
|
|
324
|
+
setExitingConference() { return {exitingConference: true}; }
|
|
325
|
+
|
|
326
|
+
// Conference transfer flags
|
|
327
|
+
setTransferConferenceRequested() { return {transferConferenceRequested: true}; }
|
|
328
|
+
clearTransferConferenceRequested() { return {transferConferenceRequested: false}; }
|
|
329
|
+
|
|
330
|
+
// Consult call hold flags
|
|
331
|
+
setConsultCallHeld() { return {consultCallHeld: true}; }
|
|
332
|
+
clearConsultCallHeld() { return {consultCallHeld: false}; }
|
|
333
|
+
|
|
334
|
+
// Recording state mutator for pause/resume events
|
|
335
|
+
setRecordingState(event) {
|
|
336
|
+
// PAUSE_RECORDING => recordingInProgress false
|
|
337
|
+
// RESUME_RECORDING => recordingInProgress true
|
|
338
|
+
}
|
|
339
|
+
|
|
340
|
+
// Reset consult/conference-related context
|
|
341
|
+
clearConsultState() {
|
|
342
|
+
return {
|
|
343
|
+
consultDestinationType: null,
|
|
344
|
+
consultDestinationAgentJoined: false,
|
|
345
|
+
consultInitiator: false,
|
|
346
|
+
exitingConference: false,
|
|
347
|
+
consultCallHeld: false,
|
|
348
|
+
consultFromConference: false,
|
|
349
|
+
transferConferenceRequested: false,
|
|
350
|
+
};
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
// End-of-task cleanup for recording flags
|
|
354
|
+
markEnded() {
|
|
355
|
+
return {recordingControlsAvailable: false, recordingInProgress: false};
|
|
356
|
+
}
|
|
357
|
+
```
|
|
358
|
+
|
|
359
|
+
> Note: `forceConsultInitiator`, `handleConferenceFailed`, `handleSwitchToMainCall`, and
|
|
360
|
+
> `handleSwitchToConsult` are not present in current `actions.ts`/`TaskStateMachine.ts`.
|
|
361
|
+
|
|
362
|
+
### Event Emission Actions
|
|
363
|
+
|
|
364
|
+
```typescript
|
|
365
|
+
// Emit task incoming
|
|
366
|
+
emitTaskIncoming(context, event) {
|
|
367
|
+
task.emit(TASK_EVENTS.TASK_INCOMING, task);
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
// Emit task assigned
|
|
371
|
+
emitTaskAssigned(context, event) {
|
|
372
|
+
task.emit(TASK_EVENTS.TASK_ASSIGNED, task);
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
// Emit task hold
|
|
376
|
+
emitTaskHold(context, event) {
|
|
377
|
+
task.emit(TASK_EVENTS.TASK_HOLD, task);
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
// Emit task wrapup
|
|
381
|
+
emitTaskWrapup(context, event) {
|
|
382
|
+
if (context.taskData.wrapUpRequired) {
|
|
383
|
+
task.emit(TASK_EVENTS.TASK_WRAPUP, task);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
|
|
387
|
+
// ... more emission actions for each event type
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
### Cleanup Actions
|
|
391
|
+
|
|
392
|
+
```typescript
|
|
393
|
+
// NOTE: These are no-op placeholders in actions.ts and are overridden in Task.ts.
|
|
394
|
+
|
|
395
|
+
// Request cleanup (remove from collection, keep task object)
|
|
396
|
+
requestCleanup(context, event) {
|
|
397
|
+
task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection: false});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Cleanup resources (remove from collection)
|
|
401
|
+
cleanupResources(context, event) {
|
|
402
|
+
task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection: true});
|
|
403
|
+
}
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
### Auto-Answer Actions
|
|
407
|
+
|
|
408
|
+
```typescript
|
|
409
|
+
// NOTE: requestAutoAnswer is a placeholder in actions.ts and is overridden in Task.ts.
|
|
410
|
+
|
|
411
|
+
// Request auto-answer
|
|
412
|
+
requestAutoAnswer(context, event) {
|
|
413
|
+
if (event.taskData?.isAutoAnswering) {
|
|
414
|
+
// Trigger accept() method
|
|
415
|
+
autoAnswerIfNeeded();
|
|
416
|
+
}
|
|
417
|
+
}
|
|
418
|
+
```
|
|
419
|
+
|
|
420
|
+
---
|
|
421
|
+
|
|
422
|
+
## UI Controls
|
|
423
|
+
|
|
424
|
+
`uiControlsComputer.ts` computes `TaskUIControls` from:
|
|
425
|
+
|
|
426
|
+
- current machine state
|
|
427
|
+
- current context
|
|
428
|
+
- channel type (voice vs digital)
|
|
429
|
+
- call/participant metadata from `taskData`
|
|
430
|
+
- config flags (`isEndTaskEnabled`, recording toggles, voice variant)
|
|
431
|
+
|
|
432
|
+
This keeps all control enablement/visibility logic centralized and testable.
|
|
433
|
+
|
|
434
|
+
### Source of truth
|
|
435
|
+
|
|
436
|
+
`computeUIControls()` in `uiControlsComputer.ts`.
|
|
437
|
+
|
|
438
|
+
### Inputs
|
|
439
|
+
|
|
440
|
+
- `TaskState`
|
|
441
|
+
- `TaskContext` (including `taskData`)
|
|
442
|
+
- `UIControlConfig` (channel type, agentId, voice variant, recording flags)
|
|
443
|
+
|
|
444
|
+
### Output
|
|
445
|
+
|
|
446
|
+
- `TaskUIControls` with per-control visibility and enabled state.
|
|
447
|
+
|
|
448
|
+
---
|
|
449
|
+
|
|
450
|
+
## Common Workflows
|
|
451
|
+
|
|
452
|
+
### Add New Event
|
|
453
|
+
|
|
454
|
+
1. Add event in `TaskEvent` (`constants.ts`)
|
|
455
|
+
2. Add typed payload in `TaskEventPayloadMap` (`types.ts`)
|
|
456
|
+
3. Wire transitions in `TaskStateMachine.ts`
|
|
457
|
+
4. Add/adjust actions in `actions.ts`
|
|
458
|
+
5. Add guard(s) in `guards.ts` if needed
|
|
459
|
+
6. Update `TaskManager` event mapping and unit tests
|
|
460
|
+
|
|
461
|
+
### Add New Transition Rule
|
|
462
|
+
|
|
463
|
+
1. Implement pure guard in `guards.ts`
|
|
464
|
+
2. Use guard in `TaskStateMachine.ts` transition array
|
|
465
|
+
3. Keep side-effects in actions only (no side-effects in guards)
|
|
466
|
+
4. Add tests for positive and negative transition paths
|
|
467
|
+
|
|
468
|
+
### Update UI Controls
|
|
469
|
+
|
|
470
|
+
1. Update control logic in `computeVoiceUIControls()` or `computeDigitalUIControls()`
|
|
471
|
+
2. Preserve `getDefaultUIControls()` shape compatibility
|
|
472
|
+
3. Verify behavior across `CONNECTED`, `HELD`, `CONSULTING`, `CONFERENCING`, `WRAPPING_UP`
|
|
473
|
+
4. Add or update UI-control unit coverage
|
|
474
|
+
|
|
475
|
+
---
|
|
476
|
+
|
|
477
|
+
## Testing Checklist
|
|
478
|
+
|
|
479
|
+
- [ ] Added event is defined in `TaskEvent`
|
|
480
|
+
- [ ] Added payload is typed in `TaskEventPayloadMap`
|
|
481
|
+
- [ ] Add transition(s) in `TaskStateMachine.ts` and validate it for success and failure
|
|
482
|
+
- [ ] Update mapping in `TaskManager.mapEventToTaskStateMachineEvent`
|
|
483
|
+
- [ ] Add/update guards and actions
|
|
484
|
+
- [ ] Update UI controls if state impacts UX and validate them for voice and digital where applicable
|
|
485
|
+
- [ ] Reconnect/hydrate behavior validated
|
|
486
|
+
- [ ] Add/adjust unit tests.
|
|
487
|
+
- [ ] Update diagrams + mapping tables in `ARCHITECTURE.md`.
|
|
488
|
+
|
|
489
|
+
---
|
|
490
|
+
|
|
491
|
+
## Related Docs
|
|
492
|
+
|
|
493
|
+
- [ARCHITECTURE.md](ARCHITECTURE.md) - State machine internals and flow diagrams
|
|
494
|
+
- [../../ai-docs/AGENTS.md](../../ai-docs/AGENTS.md) - Task service usage guide
|
|
495
|
+
- [../../ai-docs/ARCHITECTURE.md](../../ai-docs/ARCHITECTURE.md) - Task service architecture
|