@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.
Files changed (200) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +65 -123
  24. package/dist/cc.js.map +1 -1
  25. package/dist/constants.js +13 -2
  26. package/dist/constants.js.map +1 -1
  27. package/dist/index.js +13 -5
  28. package/dist/index.js.map +1 -1
  29. package/dist/metrics/behavioral-events.js +26 -13
  30. package/dist/metrics/behavioral-events.js.map +1 -1
  31. package/dist/metrics/constants.js +7 -6
  32. package/dist/metrics/constants.js.map +1 -1
  33. package/dist/services/ApiAiAssistant.js +0 -3
  34. package/dist/services/ApiAiAssistant.js.map +1 -1
  35. package/dist/services/config/Util.js +2 -3
  36. package/dist/services/config/Util.js.map +1 -1
  37. package/dist/services/config/types.js +16 -14
  38. package/dist/services/config/types.js.map +1 -1
  39. package/dist/services/constants.js +0 -1
  40. package/dist/services/constants.js.map +1 -1
  41. package/dist/services/core/Err.js.map +1 -1
  42. package/dist/services/core/Utils.js +79 -55
  43. package/dist/services/core/Utils.js.map +1 -1
  44. package/dist/services/core/aqm-reqs.js +17 -92
  45. package/dist/services/core/aqm-reqs.js.map +1 -1
  46. package/dist/services/core/websocket/WebSocketManager.js +5 -25
  47. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  48. package/dist/services/core/websocket/types.js.map +1 -1
  49. package/dist/services/index.js +1 -2
  50. package/dist/services/index.js.map +1 -1
  51. package/dist/services/task/Task.js +644 -0
  52. package/dist/services/task/Task.js.map +1 -0
  53. package/dist/services/task/TaskFactory.js +45 -0
  54. package/dist/services/task/TaskFactory.js.map +1 -0
  55. package/dist/services/task/TaskManager.js +556 -532
  56. package/dist/services/task/TaskManager.js.map +1 -1
  57. package/dist/services/task/TaskUtils.js +132 -28
  58. package/dist/services/task/TaskUtils.js.map +1 -1
  59. package/dist/services/task/constants.js +7 -6
  60. package/dist/services/task/constants.js.map +1 -1
  61. package/dist/services/task/dialer.js +0 -51
  62. package/dist/services/task/dialer.js.map +1 -1
  63. package/dist/services/task/digital/Digital.js +77 -0
  64. package/dist/services/task/digital/Digital.js.map +1 -0
  65. package/dist/services/task/state-machine/TaskStateMachine.js +634 -0
  66. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  67. package/dist/services/task/state-machine/actions.js +366 -0
  68. package/dist/services/task/state-machine/actions.js.map +1 -0
  69. package/dist/services/task/state-machine/constants.js +139 -0
  70. package/dist/services/task/state-machine/constants.js.map +1 -0
  71. package/dist/services/task/state-machine/guards.js +256 -0
  72. package/dist/services/task/state-machine/guards.js.map +1 -0
  73. package/dist/services/task/state-machine/index.js +53 -0
  74. package/dist/services/task/state-machine/index.js.map +1 -0
  75. package/dist/services/task/state-machine/types.js +54 -0
  76. package/dist/services/task/state-machine/types.js.map +1 -0
  77. package/dist/services/task/state-machine/uiControlsComputer.js +369 -0
  78. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  79. package/dist/services/task/taskDataNormalizer.js +99 -0
  80. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  81. package/dist/services/task/types.js +157 -18
  82. package/dist/services/task/types.js.map +1 -1
  83. package/dist/services/task/voice/Voice.js +1031 -0
  84. package/dist/services/task/voice/Voice.js.map +1 -0
  85. package/dist/services/task/voice/WebRTC.js +149 -0
  86. package/dist/services/task/voice/WebRTC.js.map +1 -0
  87. package/dist/types/cc.d.ts +4 -33
  88. package/dist/types/constants.d.ts +13 -2
  89. package/dist/types/index.d.ts +11 -5
  90. package/dist/types/metrics/constants.d.ts +5 -3
  91. package/dist/types/services/ApiAiAssistant.d.ts +1 -1
  92. package/dist/types/services/config/types.d.ts +97 -25
  93. package/dist/types/services/core/Err.d.ts +0 -2
  94. package/dist/types/services/core/Utils.d.ts +25 -23
  95. package/dist/types/services/core/aqm-reqs.d.ts +0 -49
  96. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -1
  97. package/dist/types/services/core/websocket/connection-service.d.ts +0 -1
  98. package/dist/types/services/core/websocket/types.d.ts +1 -1
  99. package/dist/types/services/index.d.ts +1 -1
  100. package/dist/types/services/task/Task.d.ts +146 -0
  101. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  102. package/dist/types/services/task/TaskUtils.d.ts +39 -8
  103. package/dist/types/services/task/constants.d.ts +5 -4
  104. package/dist/types/services/task/dialer.d.ts +0 -15
  105. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  106. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +906 -0
  107. package/dist/types/services/task/state-machine/actions.d.ts +8 -0
  108. package/dist/types/services/task/state-machine/constants.d.ts +91 -0
  109. package/dist/types/services/task/state-machine/guards.d.ts +78 -0
  110. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  111. package/dist/types/services/task/state-machine/types.d.ts +256 -0
  112. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  113. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  114. package/dist/types/services/task/types.d.ts +539 -88
  115. package/dist/types/services/task/voice/Voice.d.ts +183 -0
  116. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  117. package/dist/types/types.d.ts +68 -0
  118. package/dist/types/webex.d.ts +1 -0
  119. package/dist/types.js +70 -0
  120. package/dist/types.js.map +1 -1
  121. package/dist/webex.js +14 -2
  122. package/dist/webex.js.map +1 -1
  123. package/package.json +14 -11
  124. package/src/cc.ts +91 -177
  125. package/src/constants.ts +13 -2
  126. package/src/index.ts +14 -5
  127. package/src/metrics/ai-docs/AGENTS.md +348 -0
  128. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  129. package/src/metrics/behavioral-events.ts +28 -14
  130. package/src/metrics/constants.ts +7 -8
  131. package/src/services/ApiAiAssistant.ts +2 -4
  132. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  133. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  134. package/src/services/ai-docs/AGENTS.md +384 -0
  135. package/src/services/config/Util.ts +2 -3
  136. package/src/services/config/ai-docs/AGENTS.md +253 -0
  137. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  138. package/src/services/config/types.ts +108 -20
  139. package/src/services/constants.ts +0 -1
  140. package/src/services/core/Err.ts +0 -1
  141. package/src/services/core/Utils.ts +90 -67
  142. package/src/services/core/ai-docs/AGENTS.md +379 -0
  143. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  144. package/src/services/core/aqm-reqs.ts +22 -100
  145. package/src/services/core/websocket/WebSocketManager.ts +4 -23
  146. package/src/services/core/websocket/types.ts +1 -1
  147. package/src/services/index.ts +1 -2
  148. package/src/services/task/Task.ts +785 -0
  149. package/src/services/task/TaskFactory.ts +55 -0
  150. package/src/services/task/TaskManager.ts +567 -633
  151. package/src/services/task/TaskUtils.ts +175 -31
  152. package/src/services/task/ai-docs/AGENTS.md +448 -0
  153. package/src/services/task/ai-docs/ARCHITECTURE.md +573 -0
  154. package/src/services/task/constants.ts +5 -4
  155. package/src/services/task/dialer.ts +1 -56
  156. package/src/services/task/digital/Digital.ts +95 -0
  157. package/src/services/task/state-machine/TaskStateMachine.ts +793 -0
  158. package/src/services/task/state-machine/actions.ts +409 -0
  159. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  160. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  161. package/src/services/task/state-machine/constants.ts +150 -0
  162. package/src/services/task/state-machine/guards.ts +295 -0
  163. package/src/services/task/state-machine/index.ts +28 -0
  164. package/src/services/task/state-machine/types.ts +228 -0
  165. package/src/services/task/state-machine/uiControlsComputer.ts +529 -0
  166. package/src/services/task/taskDataNormalizer.ts +137 -0
  167. package/src/services/task/types.ts +641 -95
  168. package/src/services/task/voice/Voice.ts +1255 -0
  169. package/src/services/task/voice/WebRTC.ts +187 -0
  170. package/src/types.ts +88 -5
  171. package/src/utils/AGENTS.md +276 -0
  172. package/src/webex.js +2 -0
  173. package/test/unit/spec/cc.ts +59 -142
  174. package/test/unit/spec/logger-proxy.ts +70 -0
  175. package/test/unit/spec/services/ApiAiAssistant.ts +17 -0
  176. package/test/unit/spec/services/config/index.ts +26 -55
  177. package/test/unit/spec/services/core/Utils.ts +103 -52
  178. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +48 -112
  179. package/test/unit/spec/services/core/websocket/connection-service.ts +5 -4
  180. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  181. package/test/unit/spec/services/task/Task.ts +416 -0
  182. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  183. package/test/unit/spec/services/task/TaskManager.ts +781 -1735
  184. package/test/unit/spec/services/task/TaskUtils.ts +125 -0
  185. package/test/unit/spec/services/task/dialer.ts +112 -198
  186. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  187. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +473 -0
  188. package/test/unit/spec/services/task/state-machine/guards.ts +288 -0
  189. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  190. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +147 -0
  191. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  192. package/test/unit/spec/services/task/voice/Voice.ts +587 -0
  193. package/test/unit/spec/services/task/voice/WebRTC.ts +242 -0
  194. package/umd/contact-center.min.js +2 -2
  195. package/umd/contact-center.min.js.map +1 -1
  196. package/dist/services/task/index.js +0 -1525
  197. package/dist/services/task/index.js.map +0 -1
  198. package/dist/types/services/task/index.d.ts +0 -650
  199. package/src/services/task/index.ts +0 -1801
  200. 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