@webex/contact-center 3.12.0-next.9 → 3.12.0-next.91

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 (229) hide show
  1. package/.sdd/manifest.json +876 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +46 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +138 -0
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +444 -0
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +48 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +341 -0
  17. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  18. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  19. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  20. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  21. package/ai-docs/patterns/testing-patterns.md +480 -0
  22. package/ai-docs/patterns/typescript-patterns.md +365 -0
  23. package/ai-docs/templates/README.md +102 -0
  24. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  25. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  26. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  27. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  28. package/ai-docs/templates/new-method/00-master.md +80 -0
  29. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  30. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  31. package/ai-docs/templates/new-method/03-tests.md +201 -0
  32. package/ai-docs/templates/new-method/04-validation.md +141 -0
  33. package/ai-docs/templates/new-service/00-master.md +109 -0
  34. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  35. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  36. package/ai-docs/templates/new-service/03-integration.md +178 -0
  37. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  38. package/ai-docs/templates/new-service/05-validation.md +145 -0
  39. package/dist/cc.js +265 -29
  40. package/dist/cc.js.map +1 -1
  41. package/dist/config.js +6 -0
  42. package/dist/config.js.map +1 -1
  43. package/dist/constants.js +17 -1
  44. package/dist/constants.js.map +1 -1
  45. package/dist/index.js +20 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/metrics/behavioral-events.js +101 -0
  48. package/dist/metrics/behavioral-events.js.map +1 -1
  49. package/dist/metrics/constants.js +25 -4
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/services/ApiAiAssistant.js +153 -8
  52. package/dist/services/ApiAiAssistant.js.map +1 -1
  53. package/dist/services/UserPreference.js +427 -0
  54. package/dist/services/UserPreference.js.map +1 -0
  55. package/dist/services/config/Util.js +3 -3
  56. package/dist/services/config/Util.js.map +1 -1
  57. package/dist/services/config/constants.js +23 -2
  58. package/dist/services/config/constants.js.map +1 -1
  59. package/dist/services/config/types.js +49 -9
  60. package/dist/services/config/types.js.map +1 -1
  61. package/dist/services/core/Err.js.map +1 -1
  62. package/dist/services/core/Utils.js +107 -32
  63. package/dist/services/core/Utils.js.map +1 -1
  64. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  65. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  66. package/dist/services/core/websocket/types.js.map +1 -1
  67. package/dist/services/index.js +1 -1
  68. package/dist/services/index.js.map +1 -1
  69. package/dist/services/task/Task.js +688 -0
  70. package/dist/services/task/Task.js.map +1 -0
  71. package/dist/services/task/TaskFactory.js +45 -0
  72. package/dist/services/task/TaskFactory.js.map +1 -0
  73. package/dist/services/task/TaskManager.js +728 -527
  74. package/dist/services/task/TaskManager.js.map +1 -1
  75. package/dist/services/task/TaskUtils.js +162 -26
  76. package/dist/services/task/TaskUtils.js.map +1 -1
  77. package/dist/services/task/constants.js +9 -2
  78. package/dist/services/task/constants.js.map +1 -1
  79. package/dist/services/task/dialer.js +78 -0
  80. package/dist/services/task/dialer.js.map +1 -1
  81. package/dist/services/task/digital/Digital.js +77 -0
  82. package/dist/services/task/digital/Digital.js.map +1 -0
  83. package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
  84. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  85. package/dist/services/task/state-machine/actions.js +543 -0
  86. package/dist/services/task/state-machine/actions.js.map +1 -0
  87. package/dist/services/task/state-machine/constants.js +161 -0
  88. package/dist/services/task/state-machine/constants.js.map +1 -0
  89. package/dist/services/task/state-machine/guards.js +340 -0
  90. package/dist/services/task/state-machine/guards.js.map +1 -0
  91. package/dist/services/task/state-machine/index.js +53 -0
  92. package/dist/services/task/state-machine/index.js.map +1 -0
  93. package/dist/services/task/state-machine/types.js +54 -0
  94. package/dist/services/task/state-machine/types.js.map +1 -0
  95. package/dist/services/task/state-machine/uiControlsComputer.js +553 -0
  96. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  97. package/dist/services/task/taskDataNormalizer.js +99 -0
  98. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  99. package/dist/services/task/types.js +212 -4
  100. package/dist/services/task/types.js.map +1 -1
  101. package/dist/services/task/voice/Voice.js +1042 -0
  102. package/dist/services/task/voice/Voice.js.map +1 -0
  103. package/dist/services/task/voice/WebRTC.js +149 -0
  104. package/dist/services/task/voice/WebRTC.js.map +1 -0
  105. package/dist/types/cc.d.ts +94 -1
  106. package/dist/types/config.d.ts +6 -0
  107. package/dist/types/constants.d.ts +17 -1
  108. package/dist/types/index.d.ts +21 -6
  109. package/dist/types/metrics/constants.d.ts +21 -1
  110. package/dist/types/services/ApiAiAssistant.d.ts +22 -4
  111. package/dist/types/services/UserPreference.d.ts +118 -0
  112. package/dist/types/services/config/constants.d.ts +21 -0
  113. package/dist/types/services/config/types.d.ts +171 -10
  114. package/dist/types/services/core/Err.d.ts +4 -0
  115. package/dist/types/services/core/Utils.d.ts +33 -13
  116. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  117. package/dist/types/services/core/websocket/types.d.ts +1 -1
  118. package/dist/types/services/index.d.ts +1 -1
  119. package/dist/types/services/task/Task.d.ts +157 -0
  120. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  121. package/dist/types/services/task/TaskUtils.d.ts +46 -2
  122. package/dist/types/services/task/constants.d.ts +7 -0
  123. package/dist/types/services/task/dialer.d.ts +30 -0
  124. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  125. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
  126. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  127. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  128. package/dist/types/services/task/state-machine/guards.d.ts +90 -0
  129. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  130. package/dist/types/services/task/state-machine/types.d.ts +267 -0
  131. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  132. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  133. package/dist/types/services/task/types.d.ts +603 -66
  134. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  135. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  136. package/dist/types/types.d.ts +135 -0
  137. package/dist/types/webex.d.ts +1 -0
  138. package/dist/types.js +118 -2
  139. package/dist/types.js.map +1 -1
  140. package/dist/webex.js +14 -2
  141. package/dist/webex.js.map +1 -1
  142. package/package.json +15 -12
  143. package/src/cc.ts +329 -30
  144. package/src/config.ts +6 -0
  145. package/src/constants.ts +17 -1
  146. package/src/index.ts +23 -5
  147. package/src/metrics/ai-docs/AGENTS.md +350 -0
  148. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  149. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  150. package/src/metrics/behavioral-events.ts +106 -0
  151. package/src/metrics/constants.ts +27 -4
  152. package/src/services/ApiAiAssistant.ts +203 -8
  153. package/src/services/UserPreference.ts +509 -0
  154. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  155. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  156. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  157. package/src/services/ai-docs/AGENTS.md +386 -0
  158. package/src/services/ai-docs/services-spec.md +492 -0
  159. package/src/services/config/Util.ts +3 -3
  160. package/src/services/config/ai-docs/AGENTS.md +255 -0
  161. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  162. package/src/services/config/ai-docs/config-spec.md +669 -0
  163. package/src/services/config/constants.ts +25 -1
  164. package/src/services/config/types.ts +174 -11
  165. package/src/services/core/Err.ts +2 -0
  166. package/src/services/core/Utils.ts +123 -37
  167. package/src/services/core/ai-docs/AGENTS.md +381 -0
  168. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  169. package/src/services/core/ai-docs/core-spec.md +783 -0
  170. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  171. package/src/services/core/websocket/types.ts +1 -1
  172. package/src/services/index.ts +1 -1
  173. package/src/services/task/Task.ts +837 -0
  174. package/src/services/task/TaskFactory.ts +55 -0
  175. package/src/services/task/TaskManager.ts +739 -614
  176. package/src/services/task/TaskUtils.ts +205 -25
  177. package/src/services/task/ai-docs/AGENTS.md +457 -0
  178. package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
  179. package/src/services/task/ai-docs/task-spec.md +1319 -0
  180. package/src/services/task/constants.ts +7 -0
  181. package/src/services/task/dialer.ts +80 -0
  182. package/src/services/task/digital/Digital.ts +95 -0
  183. package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
  184. package/src/services/task/state-machine/actions.ts +685 -0
  185. package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
  186. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
  187. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2115 -0
  188. package/src/services/task/state-machine/constants.ts +172 -0
  189. package/src/services/task/state-machine/guards.ts +406 -0
  190. package/src/services/task/state-machine/index.ts +28 -0
  191. package/src/services/task/state-machine/types.ts +241 -0
  192. package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
  193. package/src/services/task/taskDataNormalizer.ts +137 -0
  194. package/src/services/task/types.ts +710 -71
  195. package/src/services/task/voice/Voice.ts +1267 -0
  196. package/src/services/task/voice/WebRTC.ts +187 -0
  197. package/src/types.ts +166 -2
  198. package/src/utils/AGENTS.md +278 -0
  199. package/src/utils/ai-docs/utils-spec.md +381 -0
  200. package/src/webex.js +2 -0
  201. package/test/unit/spec/cc.ts +343 -23
  202. package/test/unit/spec/logger-proxy.ts +70 -0
  203. package/test/unit/spec/services/ApiAiAssistant.ts +178 -20
  204. package/test/unit/spec/services/UserPreference.ts +401 -0
  205. package/test/unit/spec/services/WebCallingService.ts +7 -1
  206. package/test/unit/spec/services/config/index.ts +30 -30
  207. package/test/unit/spec/services/core/Utils.ts +425 -8
  208. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  209. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  210. package/test/unit/spec/services/task/Task.ts +477 -0
  211. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  212. package/test/unit/spec/services/task/TaskManager.ts +834 -1704
  213. package/test/unit/spec/services/task/TaskUtils.ts +206 -0
  214. package/test/unit/spec/services/task/dialer.ts +190 -0
  215. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  216. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
  217. package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
  218. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  219. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
  220. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  221. package/test/unit/spec/services/task/voice/Voice.ts +631 -0
  222. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  223. package/umd/contact-center.min.js +2 -2
  224. package/umd/contact-center.min.js.map +1 -1
  225. package/dist/services/task/index.js +0 -1525
  226. package/dist/services/task/index.js.map +0 -1
  227. package/dist/types/services/task/index.d.ts +0 -650
  228. package/src/services/task/index.ts +0 -1801
  229. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,2115 @@
1
+ # Task State Machine — 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-state-machine` |
10
+ | Source path(s) | `src/services/task/state-machine` |
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); 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 State Machine is one of nine confirmed Contact Center SDK modules. Own deterministic task lifecycle states, transition guards/actions, typed internal events, and state-derived UI-control availability. Existing reviewed documentation is migrated by meaning and code/tests remain the behavioral referee.
27
+
28
+ ## Purpose / Responsibility
29
+ Own deterministic task lifecycle states, transition guards/actions, typed internal events, and state-derived UI-control availability.
30
+
31
+ ## Stack
32
+ TypeScript 5.4, XState 5 actors, pure guards, assign actions, Jest 27.
33
+
34
+ ## Folder / Package Structure
35
+ ```text
36
+ src/services/task/state-machine/
37
+ ├── TaskStateMachine.ts
38
+ ├── actions.ts
39
+ ├── constants.ts
40
+ ├── guards.ts
41
+ ├── index.ts
42
+ ├── types.ts
43
+ ├── uiControlsComputer.ts
44
+ ```
45
+
46
+ ## Key Files (source of truth)
47
+ | File | Holds |
48
+ |---|---|
49
+ | `src/services/task/state-machine/TaskStateMachine.ts` | Authoritative Task State Machine implementation or contract source. |
50
+ | `src/services/task/state-machine/constants.ts` | Authoritative Task State Machine implementation or contract source. |
51
+ | `src/services/task/state-machine/types.ts` | Authoritative Task State Machine implementation or contract source. |
52
+ | `src/services/task/state-machine/guards.ts` | Authoritative Task State Machine implementation or contract source. |
53
+ | `src/services/task/state-machine/actions.ts` | Authoritative Task State Machine implementation or contract source. |
54
+ | `src/services/task/state-machine/uiControlsComputer.ts` | Authoritative Task State Machine implementation or contract source. |
55
+
56
+ ## Public Surface
57
+ | Contract | Availability | Source |
58
+ |---|---|---|
59
+ | Task state-machine factory/types | module-internal Task integration | `src/services/task/state-machine/index.ts`, `src/services/task/state-machine/TaskStateMachine.ts` |
60
+ | Guards/actions | internal state-graph implementations | `src/services/task/state-machine/guards.ts`, `src/services/task/state-machine/actions.ts` |
61
+ | `getDefaultUIControls` | exported from `uiControlsComputer.ts` and re-exported directly by package root; not exported by `state-machine/index.ts` | `src/services/task/state-machine/uiControlsComputer.ts`, `src/index.ts` |
62
+ | `computeVoiceInteractionUIControls` / `computeDigitalInteractionUIControls` | private helpers; not public APIs | `src/services/task/state-machine/uiControlsComputer.ts` |
63
+
64
+ See root `CONTRACTS.md` for the package-level state-control export.
65
+
66
+ ## Requires (dependencies)
67
+ - XState
68
+ - TaskData and task-event contracts
69
+ - Task and TaskManager event/action integration
70
+
71
+ ## Requirements
72
+ | ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence |
73
+ |---|---|---|---|---|---|---|
74
+ | TASK_STATE_MACHINE-R-001 | Map typed Task events through the XState graph and preserve guards/actions for offer, assignment, consult, conference, transfer, wrapup, termination, and hydration. | A deterministic event vocabulary isolates lifecycle policy from transport payloads. | `src/services/task/state-machine/TaskStateMachine.ts` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
75
+ | TASK_STATE_MACHINE-R-002 | Keep `handleConferenceFailed`, `handleSwitchToMainCall`, and `handleSwitchToConsult` wired where the graph invokes them; retain `forceConsultInitiator` as defined-but-currently-unwired. | Incorrect absence/wiring claims cause maintainers to duplicate or remove real actions. | `src/services/task/state-machine/actions.ts` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
76
+ | TASK_STATE_MACHINE-R-003 | Treat `syncTaskDataFromEvent` as a Task-supplied machine implementation, not a default action in `actions.ts`. | The reusable graph declares the action name while Task owns integration-specific data synchronization. | `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 |
77
+ | TASK_STATE_MACHINE-R-004 | Compute UI controls through the real private voice/digital helper names and preserve the public `getDefaultUIControls` shape. | Applications depend on stable control state while implementation helpers remain private. | `src/services/task/state-machine/uiControlsComputer.ts` | `test/unit/spec/services/task/state-machine/uiControlsComputer.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
78
+ | TASK_STATE_MACHINE-R-005 | Keep authentication and credentials outside the state-machine layer; it receives typed Task data/events and never invokes authenticated transport. | Pure transition logic remains reusable and cannot leak or mutate host authentication state. | `src/services/task/state-machine/TaskStateMachine.ts`, `src/services/task/state-machine/types.ts` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | None; security/auth applicability is explicitly N/A. | PRESENT |
79
+ | TASK_STATE_MACHINE-R-006 | Treat `UIControlConfig` values as Task-supplied capability configuration, not rollout flags evaluated or owned by the state machine. | Rollout and profile policy must be resolved before actor construction so transitions remain deterministic. | `src/services/task/state-machine/types.ts`, `src/services/task/Task.ts` | `test/unit/spec/services/task/Task.ts` | None; rollout ownership is explicit. | PRESENT |
80
+ | TASK_STATE_MACHINE-R-007 | Keep logging and metrics in Task/TaskManager integration; the state-machine implementation has no LoggerProxy or MetricsManager dependency. | Separating observability side effects from guards/actions preserves deterministic transition tests. | `src/services/task/state-machine/TaskStateMachine.ts`, `src/services/task/Task.ts` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task/Task.ts` | None; observability ownership is explicit. | PRESENT |
81
+
82
+ ## Design Overview
83
+ TaskManager maps Contact Center notifications to `TaskEvent` values. Each Task sends those events to its XState actor built by `createTaskStateMachine()`. The configuration applies guards and named actions, updates `TaskContext`, and computes UI controls. Task supplies the integration-specific `syncTaskDataFromEvent` implementation through machine options; it is not a default action in `actions.ts`.
84
+
85
+ ## Data Flow
86
+ ```mermaid
87
+ flowchart LR
88
+ WS[Contact Center notification] --> TM[TaskManager maps to TaskEvent]
89
+ TM --> Task[Owning Task instance]
90
+ Task --> Actor[XState task actor]
91
+ Actor --> Guards[guards.ts evaluates transition]
92
+ Guards --> Actions[actions.ts mutates TaskContext and emits]
93
+ Task --> Sync[Task-provided syncTaskDataFromEvent]
94
+ Actions --> UI[computeUIControls]
95
+ UI --> Events[Task UI controls and TASK_EVENTS]
96
+ ```
97
+
98
+ ## Sequence Diagram(s)
99
+ Sequence coverage:
100
+
101
+ | Operation group | Diagram | Failure / recovery coverage |
102
+ |---|---|---|
103
+ | Offer and assignment | `TASK_INCOMING` enters OFFERED; `TASK_OFFERED` updates that state and `ASSIGN` selects CONNECTED or CONSULTING. | RONA/invite/assign failures terminate the offered task; outbound failure may wrap up instead. |
104
+ | Hold/resume | `HOLD_INITIATED`/`UNHOLD_INITIATED` enter intermediate states; success/failure selects HELD or CONNECTED. | Failure transitions update task data and restore the stable state; Voice owns the thrown error and failure metric. |
105
+ | Consult | `CONSULT` enters `CONSULT_INITIATING`; local and backend success/failure/end events select CONSULTING, HELD, CONNECTED, CONFERENCING, WRAPPING_UP, or TERMINATED. | Consult failure/end actions update and clear consult context according to guards. |
106
+ | Conference/transfer | `MERGE_TO_CONFERENCE` and `CONFERENCE_START` use `CONF_INITIATING`/`CONFERENCING`; transfer uses the `TRANSFER_*` events. | `handleConferenceFailed` and transfer failure paths preserve or clear the relevant conference context. |
107
+ | Wrapup/termination | End/wrapup events select `WRAPPING_UP`, `COMPLETED`, or `TERMINATED`. | Terminal states are final and cannot accept normal interaction transitions. |
108
+ | Hydrate/recovery | IDLE `HYDRATE` guards restore WRAPPING_UP, CONSULTING, HELD, CONNECTED, or CONFERENCING; otherwise remain IDLE. | Root-level HYDRATE on an active task updates data without incorrectly re-entering a child state. |
109
+
110
+ ### Offer and assignment
111
+
112
+ ```mermaid
113
+ sequenceDiagram
114
+ participant TM as TaskManager
115
+ participant Task
116
+ participant Actor as XState actor
117
+ participant Guard as guards.ts
118
+ participant Action as actions.ts / Task override
119
+ TM->>Task: AgentContactReserved mapping
120
+ Task->>Actor: send(TASK_INCOMING)
121
+ Actor->>Action: initializeTask + emit incoming/reservation
122
+ Actor-->>Task: OFFERED
123
+ alt offer update then assignment
124
+ Task->>Actor: TASK_OFFERED then ASSIGN
125
+ Actor->>Action: update data + emit offer/assignment
126
+ Actor-->>Task: CONNECTED or guarded CONSULTING
127
+ else RONA/invite/assign failure
128
+ Actor->>Action: failure cleanup/event
129
+ Actor-->>Task: TERMINATED
130
+ end
131
+ ```
132
+
133
+ ### Hold and resume
134
+
135
+ ```mermaid
136
+ sequenceDiagram
137
+ participant Task
138
+ participant Actor as XState actor
139
+ participant Action as actions.ts
140
+ participant UI as uiControlsComputer
141
+ Task->>Actor: HOLD_INITIATED or UNHOLD_INITIATED
142
+ Actor-->>Task: HOLD_INITIATING or RESUME_INITIATING
143
+ alt success event
144
+ Task->>Actor: HOLD_SUCCESS or UNHOLD_SUCCESS
145
+ Actor->>Action: update media hold state; emit task event
146
+ Actor->>UI: compute controls for HELD/CONNECTED
147
+ Actor-->>Task: HELD or CONNECTED
148
+ else failure event
149
+ Task->>Actor: HOLD_FAILED or UNHOLD_FAILED
150
+ Actor->>Action: update task data
151
+ Actor-->>Task: CONNECTED or HELD
152
+ end
153
+ ```
154
+
155
+ ### Consult
156
+
157
+ ```mermaid
158
+ sequenceDiagram
159
+ participant Task
160
+ participant Actor as XState actor
161
+ participant Guard as guards.ts
162
+ participant Action as actions.ts / Task override
163
+ Task->>Actor: CONSULT
164
+ Actor->>Guard: validate consult capability/state
165
+ Actor-->>Task: CONSULT_INITIATING
166
+ alt local request or backend consult succeeds
167
+ Task->>Actor: CONSULT_SUCCESS / CONSULT_CREATED / CONSULTING_ACTIVE
168
+ Actor->>Action: syncTaskDataFromEvent + consult context actions
169
+ Actor-->>Task: CONSULTING
170
+ else consult failed/ended/cancelled
171
+ Task->>Actor: failure/end event
172
+ Actor->>Action: clear/update consult context
173
+ Actor-->>Task: HELD, CONNECTED, or termination path
174
+ end
175
+ ```
176
+
177
+ ### Conference and transfer
178
+
179
+ ```mermaid
180
+ sequenceDiagram
181
+ participant Task
182
+ participant Actor as XState actor
183
+ participant Action as actions.ts
184
+ Task->>Actor: MERGE_TO_CONFERENCE or TRANSFER_CONFERENCE
185
+ Actor-->>Task: CONF_INITIATING
186
+ alt conference/transfer succeeds
187
+ Task->>Actor: CONFERENCE_START / TRANSFER_CONFERENCE_SUCCESS
188
+ Actor->>Action: handleSwitchToMainCall or handleSwitchToConsult where wired
189
+ Actor-->>Task: CONFERENCING or final transferred context
190
+ else conference/transfer fails
191
+ Task->>Actor: CONFERENCE_FAILED / TRANSFER_CONFERENCE_FAILED
192
+ Actor->>Action: handleConferenceFailed / matching failure action
193
+ Actor-->>Task: preserve correct main/consult call and stable state
194
+ end
195
+ ```
196
+
197
+ ### Wrapup and termination
198
+
199
+ ```mermaid
200
+ sequenceDiagram
201
+ participant TM as TaskManager
202
+ participant Task
203
+ participant Actor as XState actor
204
+ TM->>Task: end/wrapup/backend terminal event
205
+ Task->>Actor: mapped TaskEvent
206
+ alt wrapup required
207
+ Actor-->>Task: WRAPPING_UP
208
+ Task->>Actor: WRAPUP_COMPLETE
209
+ Actor-->>Task: COMPLETED
210
+ else contact terminated/no wrapup
211
+ Actor-->>Task: TERMINATED or COMPLETED
212
+ end
213
+ Note over Actor: terminal states do not accept normal interaction transitions
214
+ ```
215
+
216
+ ### Hydrate and recovery
217
+
218
+ ```mermaid
219
+ sequenceDiagram
220
+ participant TM as TaskManager
221
+ participant Task
222
+ participant Actor as XState actor
223
+ participant Guard as guards.ts
224
+ participant Action as Task sync override
225
+ TM->>Task: HYDRATE(taskData)
226
+ Task->>Actor: send(HYDRATE)
227
+ alt actor is IDLE
228
+ Actor->>Guard: choose backend-represented state
229
+ Guard-->>Actor: WRAPPING_UP/CONSULTING/HELD/CONNECTED/CONFERENCING or default IDLE
230
+ Actor->>Action: initialize synchronized task data
231
+ else actor already active
232
+ Actor->>Action: syncTaskDataFromEvent
233
+ Actor-->>Task: retain current child state with updated context
234
+ end
235
+ ```
236
+
237
+ ## Class / Component Relationships
238
+ ```mermaid
239
+ classDiagram
240
+ class TaskManager
241
+ class Task
242
+ class TaskStateMachine
243
+ class Guards
244
+ class Actions
245
+ class UIControlsComputer
246
+ TaskManager --> Task : sends mapped events
247
+ Task --> TaskStateMachine : owns configured actor
248
+ TaskStateMachine --> Guards : evaluates
249
+ TaskStateMachine --> Actions : executes
250
+ Task --> Actions : provides syncTaskDataFromEvent
251
+ TaskStateMachine --> UIControlsComputer : recomputes controls
252
+ ```
253
+
254
+ ## Use Cases
255
+ - **UC-1 Offer and assignment:** map reserved/offer/assigned notifications to typed events and enter OFFERED or CONNECTED with updated task data. Evidence: `src/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task/state-machine`.
256
+ - **UC-2 Hold/resume:** represent the initiating operation separately, then settle into HELD or CONNECTED from success/failure events. Evidence: `src/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task/state-machine`.
257
+ - **UC-3 Consult:** retain main/consult context while moving through CONSULT_INITIATING and CONSULTING; use the Task-supplied synchronization action for ownership changes. Evidence: `src/services/task/Task.ts`, `test/unit/spec/services/task/Task.ts`.
258
+ - **UC-4 Conference/transfer:** execute conference and switch actions only where the graph wires them; `forceConsultInitiator` remains defined but unwired. Evidence: `src/services/task/state-machine/actions.ts`, `src/services/task/state-machine/TaskStateMachine.ts`.
259
+ - **UC-5 Wrapup/termination:** select WRAPPING_UP, COMPLETED, or TERMINATED from backend state and wrapup requirements. Evidence: `src/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task/state-machine`.
260
+ - **UC-6 Hydrate/recovery:** use IDLE HYDRATE guards to restore backend state while active-task hydration updates context without a child-state transition. Evidence: `src/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task/state-machine`.
261
+
262
+ ## State Model
263
+ The actor starts at `IDLE`. Normal interaction paths cover `OFFERED`, `CONNECTED`, `HELD`, `CONSULTING`, and `CONFERENCING`, with explicit initiating states for hold, resume, consult, and conference. `WRAPPING_UP` precedes final `COMPLETED` or `TERMINATED` outcomes. Backend task data remains authoritative for HYDRATE recovery.
264
+
265
+ ## Business Rules & Invariants
266
+ - Every transition event is a declared `TaskEvent`; raw Contact Center messages are mapped by TaskManager before actor delivery.
267
+ - `syncTaskDataFromEvent` is supplied by Task machine options; it must not be claimed as a default `actions.ts` implementation.
268
+ - `handleConferenceFailed`, `handleSwitchToMainCall`, and `handleSwitchToConsult` are wired actions. `forceConsultInitiator` is defined but currently unwired.
269
+ - `didInitiateConsult` is defined but currently unwired and must not be described as an active guard.
270
+ - Security/auth applicability is N/A inside the pure state-machine layer: it has no credential or transport dependency.
271
+ - `UIControlConfig` is supplied by Task as resolved capability configuration; the state machine owns no rollout/feature-flag evaluation.
272
+ - Observability is owned by Task/TaskManager; state-machine guards/actions remain free of LoggerProxy and MetricsManager dependencies.
273
+
274
+ ## Concurrency & Reactive Flow
275
+ - TaskManager serially maps each backend notification to a Task event; the actor applies guards/actions synchronously for that event, while remote operation completion arrives as later mapped events.
276
+ - UI controls are recomputed from the resulting context/state and emitted only through the owning Task integration.
277
+
278
+ ## State Machine
279
+ ```mermaid
280
+ stateDiagram-v2
281
+ [*] --> IDLE
282
+ IDLE --> OFFERED: TASK_OFFERED / OFFER_CONSULT
283
+ IDLE --> CONNECTED: ASSIGN or HYDRATE connected
284
+ OFFERED --> CONNECTED: ASSIGN
285
+ CONNECTED --> HOLD_INITIATING: HOLD_INITIATED
286
+ HOLD_INITIATING --> HELD: HOLD_SUCCESS
287
+ HOLD_INITIATING --> CONNECTED: HOLD_FAILED
288
+ HELD --> RESUME_INITIATING: UNHOLD_INITIATED
289
+ RESUME_INITIATING --> CONNECTED: UNHOLD_SUCCESS
290
+ HELD --> CONSULT_INITIATING: CONSULT
291
+ CONNECTED --> CONSULT_INITIATING: CONSULT
292
+ CONSULT_INITIATING --> CONSULTING: CONSULT_SUCCESS / CONSULTING_ACTIVE
293
+ CONSULTING --> CONF_INITIATING: MERGE_TO_CONFERENCE
294
+ CONF_INITIATING --> CONFERENCING: CONFERENCE_START
295
+ CONNECTED --> WRAPPING_UP: TASK_WRAPUP when required
296
+ HELD --> WRAPPING_UP: TASK_WRAPUP when required
297
+ WRAPPING_UP --> COMPLETED: WRAPUP_COMPLETE
298
+ CONNECTED --> TERMINATED: CONTACT_ENDED without wrapup
299
+ COMPLETED --> [*]
300
+ TERMINATED --> [*]
301
+ ```
302
+
303
+ Guide AI agents working on task lifecycle transitions, guard logic, executable actions and UI control computation in the XState-based task state machine.
304
+
305
+ This guide is for internal state management for the task lifecycle in:
306
+
307
+ - State machine configuration: `TaskStateMachine.ts`
308
+
309
+ - Actions and context mutation: `actions.ts`
310
+
311
+ - Guard logic: `guards.ts`
312
+
313
+ - UI control computation: `uiControlsComputer.ts`
314
+
315
+ - Event types and payloads: `constants.ts`, `types.ts`
316
+
317
+ Use this doc when implementing:
318
+
319
+ - new state transitions
320
+
321
+ - event mapping and payload extensions
322
+
323
+ - guard/action fixes
324
+
325
+ - UI control behavior changes tied to task state
326
+
327
+ ```text
328
+ state-machine/
329
+ ├── TaskStateMachine.ts # State graph and transition configuration
330
+ ├── actions.ts # Assign actions and emitter placeholders
331
+ ├── guards.ts # Pure guard predicates
332
+ ├── uiControlsComputer.ts # Voice/Digital UI control computation
333
+ ├── constants.ts # TaskState, TaskEvent, machine constants
334
+ ├── types.ts # Context and typed event payload map
335
+ ├── index.ts # Public exports
336
+ └── ai-docs/
337
+ ├── AGENTS.md # AI coding guide
338
+ └── ARCHITECTURE.md # State machine architecture guide
339
+ ```
340
+
341
+ - **State Graph and Transition Rules**: `TaskStateMachine.ts` defines all states, transition tables, and event handlers that drive the task lifecycle.
342
+
343
+ - **Deterministic Context Updates**: `actions.ts` implements XState actions for task context mutation and provides emitter placeholders that `Task` overrides to surface SDK events.
344
+
345
+ - **Transition Eligibility**: `guards.ts` contains pure predicates that gate transitions based on current context, task data, and backend state.
346
+
347
+ - **UI Controls Computation**: `uiControlsComputer.ts` derives `TaskUIControls` from state and context for voice/digital channels, keeping UI enablement centralized.
348
+
349
+ - **Typed Event Contracts**: `constants.ts` and `types.ts` define `TaskState`, `TaskEvent`, and the `TaskEventPayloadMap` so transitions and payloads stay type-safe.
350
+
351
+ - **Public Exports**: `index.ts` exposes the state machine factory, event enums, and types for consumption by the task layer.
352
+
353
+ **Transition Source**: `getTaskStateMachineConfig()` in `TaskStateMachine.ts`
354
+
355
+ API-driven transition from `voice/Voice.ts`:
356
+
357
+ ```typescript
358
+ // task.hold() / task.resume() -> holdResume()
359
+ stateMachineService.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId});
360
+ // ... backend call succeeds
361
+ stateMachineService.send({type: TaskEvent.HOLD_SUCCESS, mediaResourceId});
362
+ ```
363
+
364
+ Backend-driven transition from `TaskManager.ts`:
365
+
366
+ ```typescript
367
+ const eventPayload = TaskManager.mapEventToTaskStateMachineEvent(
368
+ CC_EVENTS.AGENT_CONTACT_RESERVED,
369
+ taskData
370
+ );
371
+ if (eventPayload) {
372
+ task.sendStateMachineEvent(eventPayload);
373
+ }
374
+ ```
375
+
376
+ Backend CC events from WebSocket are mapped to `TaskEvent` in `TaskManager.mapEventToTaskStateMachineEvent`.
377
+ The state machine consumes only `TaskEvent` and never raw CC events.
378
+
379
+ Source of truth: `TaskEventPayloadMap` in `types.ts`.
380
+ All new events must add a typed payload entry in `TaskEventPayloadMap`.
381
+
382
+ - API contracts for external services.
383
+
384
+ - Mercury or CC WebSocket protocols (see `TaskManager.ts` mapping).
385
+
386
+ Guards are boolean conditions that determine determine if a state transition is allowed. These functions validate the current context before allowing transitions.
387
+
388
+ - Guards must be pure and must return boolean only
389
+
390
+ - No mutation or side-effects.
391
+
392
+ - Reuse helper accessors (e.g., `getTaskDataFromEvent`).
393
+
394
+ ```typescript
395
+ // Check if interaction is in terminated state
396
+ isInteractionTerminated(context, event) {
397
+ return event.taskData?.interaction?.isTerminated === true;
398
+ }
399
+
400
+ // Check if interaction is consulting
401
+ isInteractionConsulting(context, event) {
402
+ return event.taskData?.interaction?.state === 'consulting';
403
+ }
404
+
405
+ // Check if interaction is held
406
+ isInteractionHeld(context, event) {
407
+ return event.taskData?.interaction?.state === 'hold';
408
+ }
409
+
410
+ // Check if interaction is connected
411
+ isInteractionConnected(context, event) {
412
+ return event.taskData?.interaction?.state === 'connected';
413
+ }
414
+ ```
415
+
416
+ ```typescript
417
+ // Check if current agent initiated consult
418
+ didInitiateConsult(context, event) {
419
+ if (event.taskData?.isConsulted === true) return false;
420
+ return event.taskData?.consultingAgentId
421
+ ? isSelfConsultingAgent(context, event.taskData)
422
+ : context.consultInitiator === true;
423
+ }
424
+ ```
425
+
426
+ ```typescript
427
+ // Check if conference is in progress from event taskData
428
+ conferenceInProgressFromEvent(context, event) {
429
+ const taskData = event.taskData;
430
+ if (!taskData?.interaction) return false;
431
+ return getIsConferenceInProgress(taskData);
432
+ }
433
+
434
+ // Check if conference is in progress by participants
435
+ isConferencingByParticipants(context, event) {
436
+ const taskData = event.taskData;
437
+ if (!taskData) return false;
438
+
439
+ const mainCallId = taskData.interaction?.mainInteractionId || taskData.interactionId;
440
+ const media = taskData.interaction?.media?.[mainCallId];
441
+ const participants = taskData.interaction?.participants;
442
+ if (!media?.participants || !participants) return false;
443
+
444
+ let agentCount = 0;
445
+ for (const pId of media.participants) {
446
+ const p = participants[pId];
447
+ if (p && p.pType !== 'Customer' && p.pType !== 'Supervisor' && !p.hasLeft) {
448
+ agentCount += 1;
449
+ }
450
+ }
451
+
452
+ return agentCount >= 2;
453
+ }
454
+
455
+ // Check if conference should downgrade to connected
456
+ shouldDowngradeConferenceToConnected(context, event) {
457
+ const taskData = event.taskData ?? context.taskData;
458
+ if (!taskData?.interaction) return false;
459
+
460
+ const selfAgentId = getSelfAgentId(context, taskData);
461
+ if (!selfAgentId) return false;
462
+
463
+ const mainCallId = taskData?.interaction?.mainInteractionId || taskData?.interactionId;
464
+ if (!mainCallId) return false;
465
+
466
+ // Do not downgrade while backend still reports active conference state
467
+ if (taskData.interaction.state === 'conference') return false;
468
+
469
+ const agentParticipantsCount = getConferenceParticipantsCount(taskData?.interaction, mainCallId);
470
+ if (agentParticipantsCount >= 2) return false;
471
+
472
+ const customerInCall = getIsCustomerInCall(taskData?.interaction, mainCallId);
473
+ if (!customerInCall) return false;
474
+
475
+ const selfInMainCall = Boolean(
476
+ taskData?.interaction?.media?.[mainCallId]?.participants?.includes(selfAgentId)
477
+ );
478
+ return selfInMainCall;
479
+ }
480
+ ```
481
+
482
+ ```typescript
483
+ // Check if this agent should move to wrapup
484
+ shouldWrapUp(context, event) {
485
+ const taskData = event.taskData;
486
+ if (!taskData) return false;
487
+
488
+ if (event.type === TaskEvent.CONFERENCE_END) {
489
+ const selfAgentId = getSelfAgentId(context, taskData);
490
+ if (!selfAgentId) return false;
491
+
492
+ const pending = taskData.agentsPendingWrapUp;
493
+ if (Array.isArray(pending) && pending.length > 0) {
494
+ return pending.includes(selfAgentId);
495
+ }
496
+
497
+ const participantWrapUp = taskData.interaction?.participants?.[selfAgentId]?.isWrapUp === true;
498
+ const wrapUpRequired = taskData.wrapUpRequired === true;
499
+ return wrapUpRequired || participantWrapUp;
500
+ }
501
+
502
+ return shouldWrapUpForThisAgent(context, taskData);
503
+ }
504
+
505
+ // Check if wrapup is required OR current agent is consult initiator
506
+ shouldWrapUpOrIsInitiator(context, event) {
507
+ return Boolean(event.taskData?.wrapUpRequired || context.consultInitiator);
508
+ }
509
+
510
+ // Check whether the leaving participant is the current agent
511
+ didCurrentAgentLeaveConference(context, event) {
512
+ const selfAgentId = getSelfAgentId(context, event.taskData);
513
+ if (!selfAgentId) return false;
514
+
515
+ const participantIdFromEvent = 'participantId' in event ? event.participantId : undefined;
516
+ const participantId = participantIdFromEvent ?? event.taskData?.participantId;
517
+ return Boolean(participantId) && participantId === selfAgentId;
518
+ }
519
+ ```
520
+
521
+ ```typescript
522
+ // Check if primary media leg is on hold
523
+ isPrimaryMediaOnHold(context, event) {
524
+ const taskData = event.taskData;
525
+ if (!taskData || !taskData.mediaResourceId) return false;
526
+
527
+ return taskData.interaction?.media?.[taskData.mediaResourceId]?.isHold === true;
528
+ }
529
+ ```
530
+
531
+ Actions are side effects executed during state machine transitions from current state to target state(next state).
532
+ Actions contain:
533
+
534
+ - Context synchronization (`initializeTask`, `updateTaskData`, `syncTaskDataFromEvent`)
535
+
536
+ - Lifecycle mutations (`clearConsultState`, `markEnded`, consult/conference flags)
537
+
538
+ - Integration hooks (`requestAutoAnswer`, `requestCleanup`, emitter placeholders)
539
+
540
+ - Context mutations should be centralized in `assign(...)` actions
541
+
542
+ - Emitter actions intentionally no-op defaults and overridden by `Task` to bridge machine transitions to SDK events.
543
+
544
+ - Deterministic updates from `taskData`.
545
+
546
+ ```typescript
547
+ // Initialize context for incoming task
548
+ initializeTask(context, event) {
549
+ return {
550
+ consultInitiator: false,
551
+ exitingConference: false,
552
+ consultDestinationType: null,
553
+ consultDestinationAgentJoined: false,
554
+ ...deriveTaskDataUpdates(context, event.taskData),
555
+ };
556
+ }
557
+
558
+ // Update taskData + derived recording/consult fields
559
+ updateTaskData(context, event) {
560
+ return deriveTaskDataUpdates(context, event.taskData);
561
+ }
562
+
563
+ // Keep Task instance data in sync (Task.ts action override)
564
+ syncTaskDataFromEvent(event) {
565
+ this.updateTaskFromEvent(event);
566
+ }
567
+
568
+ // Update hold flag on specific media leg in context.taskData.interaction.media
569
+ setHoldState(context, event) {
570
+ // Handles HOLD_SUCCESS and UNHOLD_SUCCESS for event.mediaResourceId
571
+ }
572
+
573
+ // Conference/consult lifecycle mutators
574
+ handleConferenceStarted() { return {consultInitiator: false}; }
575
+ handleConsultFailed() { return {consultDestinationAgentJoined: false, consultInitiator: false}; }
576
+ handleParticipantLeft(event) { return event.taskData ? {taskData: event.taskData} : {}; }
577
+ handleTransferConferenceSuccess(event) { return event.taskData ? {taskData: event.taskData} : {}; }
578
+
579
+ // Consult destination and mode flags
580
+ setConsultDestination(event) { /* sets consultDestinationType and resets consult flags */ }
581
+ setConsultFromConference() { return {consultFromConference: true}; }
582
+ setConsultAgentJoined(event) { /* sets consultDestinationAgentJoined on CONSULTING_ACTIVE */ }
583
+ setExitingConference() { return {exitingConference: true}; }
584
+
585
+ // Conference transfer flags
586
+ setTransferConferenceRequested() { return {transferConferenceRequested: true}; }
587
+ clearTransferConferenceRequested() { return {transferConferenceRequested: false}; }
588
+
589
+ // Consult call hold flags
590
+ setConsultCallHeld() { return {consultCallHeld: true}; }
591
+ clearConsultCallHeld() { return {consultCallHeld: false}; }
592
+
593
+ // Recording state mutator for pause/resume events
594
+ setRecordingState(event) {
595
+ // PAUSE_RECORDING => recordingInProgress false
596
+ // RESUME_RECORDING => recordingInProgress true
597
+ }
598
+
599
+ // Reset consult/conference-related context
600
+ clearConsultState() {
601
+ return {
602
+ consultDestinationType: null,
603
+ consultDestinationAgentJoined: false,
604
+ consultInitiator: false,
605
+ exitingConference: false,
606
+ consultCallHeld: false,
607
+ consultFromConference: false,
608
+ transferConferenceRequested: false,
609
+ };
610
+ }
611
+
612
+ // End-of-task cleanup for recording flags
613
+ markEnded() {
614
+ return {recordingControlsAvailable: false, recordingInProgress: false};
615
+ }
616
+ ```
617
+
618
+ > `handleConferenceFailed`, `handleSwitchToMainCall`, and `handleSwitchToConsult` are defined in `actions.ts` and wired by `TaskStateMachine.ts`. `forceConsultInitiator` is defined in `actions.ts` but is not wired in the current graph.
619
+
620
+ ```typescript
621
+ // Emit task incoming
622
+ emitTaskIncoming(context, event) {
623
+ task.emit(TASK_EVENTS.TASK_INCOMING, task);
624
+ }
625
+
626
+ // Emit task assigned
627
+ emitTaskAssigned(context, event) {
628
+ task.emit(TASK_EVENTS.TASK_ASSIGNED, task);
629
+ }
630
+
631
+ // Emit task hold
632
+ emitTaskHold(context, event) {
633
+ task.emit(TASK_EVENTS.TASK_HOLD, task);
634
+ }
635
+
636
+ // Emit task wrapup
637
+ emitTaskWrapup(context, event) {
638
+ if (context.taskData.wrapUpRequired) {
639
+ task.emit(TASK_EVENTS.TASK_WRAPUP, task);
640
+ }
641
+ }
642
+
643
+ // ... more emission actions for each event type
644
+ ```
645
+
646
+ ```typescript
647
+ // NOTE: These are no-op placeholders in actions.ts and are overridden in Task.ts.
648
+
649
+ // Request cleanup (remove from collection, keep task object)
650
+ requestCleanup(context, event) {
651
+ task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection: false});
652
+ }
653
+
654
+ // Cleanup resources (remove from collection)
655
+ cleanupResources(context, event) {
656
+ task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection: true});
657
+ }
658
+ ```
659
+
660
+ ```typescript
661
+ // NOTE: requestAutoAnswer is a placeholder in actions.ts and is overridden in Task.ts.
662
+
663
+ // Request auto-answer
664
+ requestAutoAnswer(context, event) {
665
+ if (event.taskData?.isAutoAnswering) {
666
+ // Trigger accept() method
667
+ autoAnswerIfNeeded();
668
+ }
669
+ }
670
+ ```
671
+
672
+ `uiControlsComputer.ts` computes `TaskUIControls` from:
673
+
674
+ - current machine state
675
+
676
+ - current context
677
+
678
+ - channel type (voice vs digital)
679
+
680
+ - call/participant metadata from `taskData`
681
+
682
+ - config flags (`isEndTaskEnabled`, recording toggles, voice variant)
683
+
684
+ This keeps all control enablement/visibility logic centralized and testable.
685
+
686
+ - `TaskState`
687
+
688
+ - `TaskContext` (including `taskData`)
689
+
690
+ - `UIControlConfig` (channel type, agentId, voice variant, recording flags)
691
+
692
+ - `TaskUIControls` with per-control visibility and enabled state.
693
+
694
+ 1. Add event in `TaskEvent` (`constants.ts`)
695
+
696
+ 2. Add typed payload in `TaskEventPayloadMap` (`types.ts`)
697
+
698
+ 3. Wire transitions in `TaskStateMachine.ts`
699
+
700
+ 4. Add/adjust actions in `actions.ts`
701
+
702
+ 5. Add guard(s) in `guards.ts` if needed
703
+
704
+ 6. Update `TaskManager` event mapping and unit tests
705
+
706
+ 1. Implement pure guard in `guards.ts`
707
+
708
+ 2. Use guard in `TaskStateMachine.ts` transition array
709
+
710
+ 3. Keep side-effects in actions only (no side-effects in guards)
711
+
712
+ 4. Add tests for positive and negative transition paths
713
+
714
+ 1. Update control logic in `computeVoiceInteractionUIControls()` or `computeDigitalInteractionUIControls()`
715
+
716
+ 2. Preserve `getDefaultUIControls()` shape compatibility
717
+
718
+ 3. Verify behavior across `CONNECTED`, `HELD`, `CONSULTING`, `CONFERENCING`, `WRAPPING_UP`
719
+
720
+ 4. Add or update UI-control unit coverage
721
+
722
+ Technical reference for the complete state machine using XState to drive state transitions and UI control behavior. It orchestrates state transitions, guards, and actions for task lifecycle management.
723
+
724
+ The task state machine is built with `xstate` and organized into:
725
+
726
+ - **State graph** (`TaskStateMachine.ts`)
727
+
728
+ - **Context mutators** (`actions.ts`)
729
+
730
+ - **Guard predicates** (`guards.ts`)
731
+
732
+ - **UI control derivation** (`uiControlsComputer.ts`)
733
+
734
+ - **Event/context contracts** (`types.ts`)
735
+
736
+ It is instantiated by `Task` and receives mapped backend/user events through `sendStateMachineEvent(...)`.
737
+
738
+ `Task` bootstraps and owns the actor lifecycle:
739
+
740
+ 1. `createTaskStateMachine(uiControlConfig, {actions: overrides})`
741
+
742
+ 2. `createActor(machine).start()`
743
+
744
+ 3. `TaskManager` and task APIs map external signals to `TaskEvent`
745
+
746
+ 4. Actor transitions update context and execute action overrides
747
+
748
+ 5. `Task` recomputes UI controls and emits task-level events
749
+
750
+ **Description**: Initial state before a task is offered or restored.
751
+
752
+ **How this state is reached (incoming transitions)**:
753
+
754
+ - Machine start -> `IDLE` (no event, no actions)
755
+
756
+ **Valid transitions from `IDLE` state**:
757
+
758
+ - `TASK_INCOMING` -> `OFFERED`
759
+
760
+ - Guard: none
761
+
762
+ - Actions: `initializeTask`, `emitTaskIncoming`
763
+
764
+ - `HYDRATE` -> `WRAPPING_UP`
765
+
766
+ - Guard: `guards.isInteractionTerminated`
767
+
768
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskHydrate`
769
+
770
+ - `HYDRATE` -> `CONSULTING`
771
+
772
+ - Guard: `guards.isInteractionConsulting`
773
+
774
+ - Actions: `updateTaskData`, `emitTaskHydrate`
775
+
776
+ - `HYDRATE` -> `HELD`
777
+
778
+ - Guard: `guards.isInteractionHeld`
779
+
780
+ - Actions: `updateTaskData`, `emitTaskHydrate`
781
+
782
+ - `HYDRATE` -> `CONNECTED`
783
+
784
+ - Guard: `guards.isInteractionConnected`
785
+
786
+ - Actions: `updateTaskData`, `emitTaskHydrate`
787
+
788
+ - `HYDRATE` -> `CONFERENCING`
789
+
790
+ - Guard: `guards.isConferencingByParticipants`
791
+
792
+ - Actions: `updateTaskData`, `emitTaskHydrate`
793
+
794
+ - `HYDRATE` -> stay `IDLE` (default hydrate branch)
795
+
796
+ - Guard: default
797
+
798
+ - Actions: `updateTaskData`, `emitTaskHydrate`
799
+
800
+ **Description**: Task has been offered/reserved and is waiting for assignment or termination paths.
801
+
802
+ **How this state is reached (incoming transitions)**:
803
+
804
+ - `IDLE --TASK_INCOMING--> OFFERED`
805
+
806
+ - Guard: none
807
+
808
+ - Actions: `initializeTask`, `emitTaskIncoming`
809
+
810
+ **Valid transitions from `OFFERED`**:
811
+
812
+ - `TASK_OFFERED` -> Stay `OFFERED`
813
+
814
+ - Guard: none
815
+
816
+ - Actions: `updateTaskData`, `emitTaskOfferContact`, `requestAutoAnswer`
817
+
818
+ - `OFFER_CONSULT` -> Stay `OFFERED`
819
+
820
+ - Guard: none
821
+
822
+ - Actions: `updateTaskData`, `emitTaskOfferConsult`, `requestAutoAnswer`
823
+
824
+ - `ASSIGN` -> `CONNECTED`
825
+
826
+ - Guard: none
827
+
828
+ - Actions: `updateTaskData`, `emitTaskAssigned`
829
+
830
+ - `CONSULTING_ACTIVE` -> `CONSULTING`
831
+
832
+ - Guard: none
833
+
834
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsultAccepted`, `emitTaskConsulting`
835
+
836
+ - `TASK_WRAPUP` -> `TERMINATED`
837
+
838
+ - Guard: none
839
+
840
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskEnd`
841
+
842
+ - `RONA` / `ASSIGN_FAILED` / `INVITE_FAILED` / `OUTBOUND_FAILED` -> `TERMINATED`
843
+
844
+ - Guard: none
845
+
846
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskReject`
847
+
848
+ **Description**: Agent is connected on the main customer interaction leg.
849
+
850
+ **How this state is reached (incoming transitions)**:
851
+
852
+ - `OFFERED --ASSIGN--> CONNECTED`
853
+
854
+ - Guard: none
855
+
856
+ - Actions: `updateTaskData`, `emitTaskAssigned`
857
+
858
+ - `IDLE --HYDRATE--> CONNECTED`
859
+
860
+ - Guard: `guards.isInteractionConnected`
861
+
862
+ - Actions: `updateTaskData`, `emitTaskHydrate`
863
+
864
+ - `RESUME_INITIATING --UNHOLD_SUCCESS--> CONNECTED`
865
+
866
+ - Guard: none
867
+
868
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskResume`
869
+
870
+ - `HELD --TRANSFER_SUCCESS--> CONNECTED` (receiver/default branch)
871
+
872
+ - Guard: default branch when `guards.shouldWrapUpOrIsInitiator` is false
873
+
874
+ - Actions: `updateTaskData`, `clearConsultState`
875
+
876
+ - `CONSULTING --ASSIGN--> CONNECTED`
877
+
878
+ - Guard: none
879
+
880
+ - Actions: `updateTaskData`, `emitTaskAssigned`
881
+
882
+ **Valid transitions from `CONNECTED`**:
883
+
884
+ - `ASSIGN` -> `CONNECTED` (self-transition)
885
+
886
+ - Guard: none
887
+
888
+ - Actions: `updateTaskData`, `emitTaskAssigned`
889
+
890
+ - `HOLD_INITIATED` -> `HOLD_INITIATING`
891
+
892
+ - Guard: none
893
+
894
+ - Actions: none
895
+
896
+ - `CONSULT` -> `CONSULT_INITIATING`
897
+
898
+ - Guard: none
899
+
900
+ - Actions: `setConsultInitiator`, `setConsultDestination`
901
+
902
+ - `CONSULTING_ACTIVE` -> `CONSULTING`
903
+
904
+ - Guard: none
905
+
906
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsultAccepted`, `emitTaskConsulting`
907
+
908
+ - `TRANSFER_SUCCESS` -> `WRAPPING_UP`
909
+
910
+ - Guard: `guards.shouldWrapUpOrIsInitiator`
911
+
912
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
913
+
914
+ - `TRANSFER_SUCCESS` -> Stay `CONNECTED` (receiver/default branch)
915
+
916
+ - Guard: default
917
+
918
+ - Actions: `updateTaskData`, `clearConsultState`
919
+
920
+ - `TRANSFER_FAILED` -> Stay `CONNECTED`
921
+
922
+ - Guard: none
923
+
924
+ - Actions: `updateTaskData`
925
+
926
+ - `CONTACT_ENDED` -> `CONFERENCING`
927
+
928
+ - Guard: `guards.conferenceInProgressFromEvent`
929
+
930
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
931
+
932
+ - `CONTACT_ENDED` -> `WRAPPING_UP`
933
+
934
+ - Guard: `guards.shouldWrapUp`
935
+
936
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`, `requestCleanup`
937
+
938
+ - `CONTACT_ENDED` -> `TERMINATED` (default branch)
939
+
940
+ - Guard: default
941
+
942
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskEnd`
943
+
944
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
945
+
946
+ - Guard: none
947
+
948
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
949
+
950
+ - `PAUSE_RECORDING` / `RESUME_RECORDING` -> Stay `CONNECTED`
951
+
952
+ - Guard: none
953
+
954
+ - Actions: `updateTaskData`, `setRecordingState`, `emitTaskRecordingPaused` / `emitTaskRecordingResumed`
955
+
956
+ **Description**: Main call is on hold.
957
+
958
+ **How this state is reached (incoming transitions)**:
959
+
960
+ - `IDLE --HYDRATE--> HELD`
961
+
962
+ - Guard: `guards.isInteractionHeld`
963
+
964
+ - Actions: `updateTaskData`, `emitTaskHydrate`
965
+
966
+ - `HOLD_INITIATING --HOLD_SUCCESS--> HELD`
967
+
968
+ - Guard: none
969
+
970
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold`
971
+
972
+ - `RESUME_INITIATING --UNHOLD_FAILED--> HELD`
973
+
974
+ - Guard: none
975
+
976
+ - Actions: none
977
+
978
+ - `CONSULTING --CONSULT_END--> HELD`
979
+
980
+ - Guard: inline `context.consultInitiator === true`
981
+
982
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
983
+
984
+ - `CONSULT_INITIATING --CONSULT_FAILED--> HELD`
985
+
986
+ - Guard: `guards.isPrimaryMediaOnHold`
987
+
988
+ - Actions: `updateTaskData`, `handleConsultFailed`
989
+
990
+ - `CONSULT_INITIATING --CTQ_CANCEL--> HELD`
991
+
992
+ - Guard: `guards.isPrimaryMediaOnHold`
993
+
994
+ - Actions: `updateTaskData`, `clearConsultState`
995
+
996
+ **Valid transitions from `HELD`**:
997
+
998
+ - `UNHOLD_INITIATED` -> `RESUME_INITIATING`
999
+
1000
+ - Guard: none
1001
+
1002
+ - Actions: none
1003
+
1004
+ - `CONSULT` -> `CONSULT_INITIATING`
1005
+
1006
+ - Guard: none
1007
+
1008
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1009
+
1010
+ - `TRANSFER_SUCCESS` -> `WRAPPING_UP`
1011
+
1012
+ - Guard: `guards.shouldWrapUpOrIsInitiator`
1013
+
1014
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
1015
+
1016
+ - `TRANSFER_SUCCESS` -> `CONNECTED` (receiver/default branch)
1017
+
1018
+ - Guard: default
1019
+
1020
+ - Actions: `updateTaskData`, `clearConsultState`
1021
+
1022
+ - `TRANSFER_FAILED` -> stay `HELD`
1023
+
1024
+ - Guard: none
1025
+
1026
+ - Actions: `updateTaskData`
1027
+
1028
+ - `CONTACT_ENDED` -> `CONFERENCING`
1029
+
1030
+ - Guard: `guards.conferenceInProgressFromEvent`
1031
+
1032
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1033
+
1034
+ - `CONTACT_ENDED` -> `WRAPPING_UP`
1035
+
1036
+ - Guard: `guards.shouldWrapUp`
1037
+
1038
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`, `requestCleanup`
1039
+
1040
+ - `CONTACT_ENDED` -> `TERMINATED` (default branch)
1041
+
1042
+ - Guard: default
1043
+
1044
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskEnd`
1045
+
1046
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1047
+
1048
+ - Guard: none
1049
+
1050
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
1051
+
1052
+ **Description**: Hold request has been sent and is awaiting backend confirmation.
1053
+
1054
+ **How this state is reached (incoming transitions)**:
1055
+
1056
+ - `CONNECTED --HOLD_INITIATED--> HOLD_INITIATING`
1057
+
1058
+ - Guard: none
1059
+
1060
+ - Actions: none
1061
+
1062
+ **Valid transitions from `HOLD_INITIATING`**:
1063
+
1064
+ - `HOLD_SUCCESS` -> `HELD`
1065
+
1066
+ - Guard: none
1067
+
1068
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold`
1069
+
1070
+ - `HOLD_FAILED` -> `CONNECTED`
1071
+
1072
+ - Guard: none
1073
+
1074
+ - Actions: `updateTaskData`
1075
+
1076
+ **Description**: Resume/unhold request has been sent and is awaiting backend confirmation.
1077
+
1078
+ **How this state is reached (incoming transitions)**:
1079
+
1080
+ - `HELD --UNHOLD_INITIATED--> RESUME_INITIATING`
1081
+
1082
+ - Guard: none
1083
+
1084
+ - Actions: none
1085
+
1086
+ **Valid transitions from `RESUME_INITIATING`**:
1087
+
1088
+ - `UNHOLD_SUCCESS` -> `CONNECTED`
1089
+
1090
+ - Guard: none
1091
+
1092
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskResume`
1093
+
1094
+ - `UNHOLD_FAILED` -> `HELD`
1095
+
1096
+ - Guard: none
1097
+
1098
+ - Actions: none
1099
+
1100
+ **Description**: Consult request is in-flight.
1101
+
1102
+ **How this state is reached (incoming transitions)**:
1103
+
1104
+ - `CONNECTED --CONSULT--> CONSULT_INITIATING`
1105
+
1106
+ - Guard: none
1107
+
1108
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1109
+
1110
+ - `HELD --CONSULT--> CONSULT_INITIATING`
1111
+
1112
+ - Guard: none
1113
+
1114
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1115
+
1116
+ - `CONFERENCING --CONSULT--> CONSULT_INITIATING`
1117
+
1118
+ - Guard: none
1119
+
1120
+ - Actions: `setConsultInitiator`, `setConsultDestination`, `setConsultFromConference`
1121
+
1122
+ **Valid transitions from `CONSULT_INITIATING`**:
1123
+
1124
+ - `CONSULT_SUCCESS` -> `CONSULTING`
1125
+
1126
+ - Guard: none
1127
+
1128
+ - Actions: `updateTaskData`, `setConsultInitiator`
1129
+
1130
+ - `CONSULT_FAILED` -> `CONFERENCING`
1131
+
1132
+ - Guard: inline `context.consultFromConference === true`
1133
+
1134
+ - Actions: `updateTaskData`, `handleConsultFailed`
1135
+
1136
+ - `CONSULT_FAILED` -> `HELD`
1137
+
1138
+ - Guard: `guards.isPrimaryMediaOnHold`
1139
+
1140
+ - Actions: `updateTaskData`, `handleConsultFailed`
1141
+
1142
+ - `CONSULT_FAILED` -> `CONNECTED` (default branch)
1143
+
1144
+ - Guard: default
1145
+
1146
+ - Actions: `updateTaskData`, `handleConsultFailed`
1147
+
1148
+ - `CTQ_CANCEL` -> `HELD`
1149
+
1150
+ - Guard: `guards.isPrimaryMediaOnHold`
1151
+
1152
+ - Actions: `updateTaskData`, `clearConsultState`
1153
+
1154
+ - `CTQ_CANCEL` -> `CONNECTED` (default branch)
1155
+
1156
+ - Guard: default
1157
+
1158
+ - Actions: `updateTaskData`, `clearConsultState`
1159
+
1160
+ - `HOLD_SUCCESS` -> stay `CONSULT_INITIATING`
1161
+
1162
+ - Guard: none
1163
+
1164
+ - Actions: `updateTaskData`
1165
+
1166
+ - `HOLD_FAILED` -> `CONNECTED`
1167
+
1168
+ - Guard: none
1169
+
1170
+ - Actions: `updateTaskData`, `handleConsultFailed`
1171
+
1172
+ **Description**: Agent is in active consult leg.
1173
+
1174
+ **How this state is reached (incoming transitions)**:
1175
+
1176
+ - `IDLE --HYDRATE--> CONSULTING`
1177
+
1178
+ - Guard: `guards.isInteractionConsulting`
1179
+
1180
+ - Actions: `updateTaskData`, `emitTaskHydrate`
1181
+
1182
+ - `OFFERED --CONSULTING_ACTIVE--> CONSULTING`
1183
+
1184
+ - Guard: none
1185
+
1186
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsultAccepted`, `emitTaskConsulting`
1187
+
1188
+ - `CONSULT_INITIATING --CONSULT_SUCCESS--> CONSULTING`
1189
+
1190
+ - Guard: none
1191
+
1192
+ - Actions: `updateTaskData`, `setConsultInitiator`
1193
+
1194
+ - `CONF_INITIATING --CONFERENCE_FAILED--> CONSULTING`
1195
+
1196
+ - Guard: none
1197
+
1198
+ - Actions: none
1199
+
1200
+ **Valid transitions from `CONSULTING`**:
1201
+
1202
+ - `CONSULTING_ACTIVE` -> stay `CONSULTING`
1203
+
1204
+ - Guard: none
1205
+
1206
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsulting`
1207
+
1208
+ - `CONSULT_END` -> `CONFERENCING`
1209
+
1210
+ - Guard: inline `context.consultInitiator === true && context.consultFromConference === true`
1211
+
1212
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
1213
+
1214
+ - `CONSULT_END` -> `HELD`
1215
+
1216
+ - Guard: inline `context.consultInitiator === true`
1217
+
1218
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
1219
+
1220
+ - `CONSULT_END` -> `TERMINATED` (default branch)
1221
+
1222
+ - Guard: default
1223
+
1224
+ - Actions: `updateTaskData`
1225
+
1226
+ - `HOLD_SUCCESS` -> stay `CONSULTING`
1227
+
1228
+ - Guard: none
1229
+
1230
+ - Actions: `updateTaskData`, `setHoldState`, `setConsultCallHeld`
1231
+
1232
+ - `UNHOLD_SUCCESS` -> stay `CONSULTING`
1233
+
1234
+ - Guard: none
1235
+
1236
+ - Actions: `updateTaskData`, `setHoldState`, `clearConsultCallHeld`
1237
+
1238
+ - `TRANSFER_SUCCESS` -> `WRAPPING_UP`
1239
+
1240
+ - Guard: `guards.shouldWrapUpOrIsInitiator`
1241
+
1242
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
1243
+
1244
+ - `TRANSFER_SUCCESS` -> `CONNECTED` (receiver/default branch)
1245
+
1246
+ - Guard: default
1247
+
1248
+ - Actions: `updateTaskData`, `clearConsultState`
1249
+
1250
+ - `TRANSFER_FAILED` -> stay `CONSULTING`
1251
+
1252
+ - Guard: none
1253
+
1254
+ - Actions: `updateTaskData`
1255
+
1256
+ - `TRANSFER_CONFERENCE` -> stay `CONSULTING`
1257
+
1258
+ - Guard: none
1259
+
1260
+ - Actions: `setTransferConferenceRequested`, `emitTaskTransferConference`
1261
+
1262
+ - `TRANSFER_CONFERENCE_SUCCESS` -> stay `CONSULTING`
1263
+
1264
+ - Guard: inline `context.transferConferenceRequested !== true`
1265
+
1266
+ - Actions: `updateTaskData`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1267
+
1268
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `WRAPPING_UP`
1269
+
1270
+ - Guard: `guards.shouldWrapUp`
1271
+
1272
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`, `emitTaskWrapup`
1273
+
1274
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `CONFERENCING`
1275
+
1276
+ - Guard: inline `!context.consultInitiator`
1277
+
1278
+ - Actions: `updateTaskData`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1279
+
1280
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `TERMINATED` (default branch)
1281
+
1282
+ - Guard: default
1283
+
1284
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`, `emitTaskEnd`
1285
+
1286
+ - `TRANSFER_CONFERENCE_FAILED` -> stay `CONSULTING`
1287
+
1288
+ - Guard: none
1289
+
1290
+ - Actions: `clearTransferConferenceRequested`
1291
+
1292
+ - `ASSIGN` -> `CONNECTED`
1293
+
1294
+ - Guard: none
1295
+
1296
+ - Actions: `updateTaskData`, `emitTaskAssigned`
1297
+
1298
+ - `CONTACT_ENDED` -> `WRAPPING_UP`
1299
+
1300
+ - Guard: none
1301
+
1302
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskWrapup`, `requestCleanup`
1303
+
1304
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1305
+
1306
+ - Guard: none
1307
+
1308
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskWrapup`
1309
+
1310
+ - `MERGE_TO_CONFERENCE` -> `CONF_INITIATING`
1311
+
1312
+ - Guard: none
1313
+
1314
+ - Actions: none
1315
+
1316
+ - `CONFERENCE_START` -> `CONFERENCING`
1317
+
1318
+ - Guard: none
1319
+
1320
+ - Actions: `handleConferenceStarted`, `clearConsultState`
1321
+
1322
+ **Description**: Conference merge is being established.
1323
+
1324
+ **How this state is reached (incoming transitions)**:
1325
+
1326
+ - `CONSULTING --MERGE_TO_CONFERENCE--> CONF_INITIATING`
1327
+
1328
+ - Guard: none
1329
+
1330
+ - Actions: none
1331
+
1332
+ **Valid transitions from `CONF_INITIATING`**:
1333
+
1334
+ - `CONFERENCE_START` -> `CONFERENCING`
1335
+
1336
+ - Guard: none
1337
+
1338
+ - Actions: `handleConferenceStarted`
1339
+
1340
+ - `CONFERENCE_FAILED` -> `CONSULTING`
1341
+
1342
+ - Guard: none
1343
+
1344
+ - Actions: none
1345
+
1346
+ **Description**: Active conference call state.
1347
+
1348
+ **How this state is reached (incoming transitions)**:
1349
+
1350
+ - `IDLE --HYDRATE--> CONFERENCING`
1351
+
1352
+ - Guard: `guards.isConferencingByParticipants`
1353
+
1354
+ - Actions: `updateTaskData`, `emitTaskHydrate`
1355
+
1356
+ - `CONNECTED --CONTACT_ENDED--> CONFERENCING`
1357
+
1358
+ - Guard: `guards.conferenceInProgressFromEvent`
1359
+
1360
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1361
+
1362
+ - `HELD --CONTACT_ENDED--> CONFERENCING`
1363
+
1364
+ - Guard: `guards.conferenceInProgressFromEvent`
1365
+
1366
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1367
+
1368
+ - `CONSULTING --CONFERENCE_START--> CONFERENCING`
1369
+
1370
+ - Guard: none
1371
+
1372
+ - Actions: `handleConferenceStarted`, `clearConsultState`
1373
+
1374
+ - `CONF_INITIATING --CONFERENCE_START--> CONFERENCING`
1375
+
1376
+ - Guard: none
1377
+
1378
+ - Actions: `handleConferenceStarted`
1379
+
1380
+ - `CONSULTING --TRANSFER_CONFERENCE_SUCCESS--> CONFERENCING`
1381
+
1382
+ - Guard: inline `!context.consultInitiator`
1383
+
1384
+ - Actions: `updateTaskData`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1385
+
1386
+ **Valid transitions from `CONFERENCING`**:
1387
+
1388
+ - `CONSULT` -> `CONSULT_INITIATING`
1389
+
1390
+ - Guard: none
1391
+
1392
+ - Actions: `setConsultInitiator`, `setConsultDestination`, `setConsultFromConference`
1393
+
1394
+ - `CONFERENCE_START` -> stay `CONFERENCING`
1395
+
1396
+ - Guard: none
1397
+
1398
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConferenceStarted`
1399
+
1400
+ - `CONSULT_END` -> stay `CONFERENCING`
1401
+
1402
+ - Guard: none
1403
+
1404
+ - Actions: `updateTaskData`, `clearConsultState`
1405
+
1406
+ - `HOLD_SUCCESS` / `UNHOLD_SUCCESS` -> stay `CONFERENCING`
1407
+
1408
+ - Guard: none
1409
+
1410
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold` / `emitTaskResume`
1411
+
1412
+ - `TRANSFER_CONFERENCE` -> stay `CONFERENCING`
1413
+
1414
+ - Guard: none
1415
+
1416
+ - Actions: `setTransferConferenceRequested`, `emitTaskTransferConference`
1417
+
1418
+ - `TRANSFER_CONFERENCE_SUCCESS` -> stay `CONFERENCING`
1419
+
1420
+ - Guard: inline `context.transferConferenceRequested !== true`
1421
+
1422
+ - Actions: `updateTaskData`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1423
+
1424
+ - `TRANSFER_CONFERENCE_FAILED` -> stay `CONFERENCING`
1425
+
1426
+ - Guard: none
1427
+
1428
+ - Actions: `clearTransferConferenceRequested`
1429
+
1430
+ - `PARTICIPANT_LEAVE` -> `WRAPPING_UP`
1431
+
1432
+ - Guard: `guards.didCurrentAgentLeaveConference && guards.shouldWrapUp`
1433
+
1434
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskWrapup`
1435
+
1436
+ - `PARTICIPANT_LEAVE` -> `TERMINATED`
1437
+
1438
+ - Guard: `guards.didCurrentAgentLeaveConference`
1439
+
1440
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskEnd`
1441
+
1442
+ - `PARTICIPANT_LEAVE` -> `CONNECTED`
1443
+
1444
+ - Guard: `guards.shouldDowngradeConferenceToConnected`
1445
+
1446
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskConferenceEnded`
1447
+
1448
+ - `PARTICIPANT_LEAVE` -> stay `CONFERENCING` (default)
1449
+
1450
+ - Guard: default
1451
+
1452
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `emitTaskParticipantLeft`
1453
+
1454
+ - `CONFERENCE_END` -> `WRAPPING_UP`
1455
+
1456
+ - Guard: `guards.shouldWrapUp`
1457
+
1458
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskWrapup`
1459
+
1460
+ - `CONFERENCE_END` -> `CONNECTED`
1461
+
1462
+ - Guard: inline `!context.exitingConference && customerInCall`
1463
+
1464
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConferenceEnded`
1465
+
1466
+ - `CONFERENCE_END` -> `TERMINATED` (default branch)
1467
+
1468
+ - Guard: default
1469
+
1470
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskEnd`
1471
+
1472
+ - `CONTACT_ENDED` -> stay `CONFERENCING`
1473
+
1474
+ - Guard: none
1475
+
1476
+ - Actions: `updateTaskData`, `requestCleanup`
1477
+
1478
+ **Description**: Post-interaction work (ACW) is in progress.
1479
+
1480
+ **How this state is reached (incoming transitions)**:
1481
+
1482
+ - Reached from `CONNECTED`, `HELD`, `CONSULTING`, or `CONFERENCING` via `CONTACT_ENDED`, `TASK_WRAPUP`, `TRANSFER_SUCCESS`, `TRANSFER_CONFERENCE_SUCCESS`, `PARTICIPANT_LEAVE`, or `CONFERENCE_END` branches
1483
+
1484
+ - Entry always emits wrapup event after transition
1485
+
1486
+ **Entry Actions**:
1487
+
1488
+ - `emitTaskWrapup`
1489
+
1490
+ **Valid transitions from `WRAPPING_UP`**:
1491
+
1492
+ - `WRAPUP_COMPLETE` -> `COMPLETED`
1493
+
1494
+ - Guard: none
1495
+
1496
+ - Actions: `updateTaskData`
1497
+
1498
+ **Guards**: None
1499
+
1500
+ **Description**: Final wrapped-up terminal state.
1501
+
1502
+ **Entry Actions**:
1503
+
1504
+ - `emitTaskWrappedup`
1505
+
1506
+ - `cleanupResources`
1507
+
1508
+ **How this state is reached (incoming transitions)**:
1509
+
1510
+ - `WRAPPING_UP --WRAPUP_COMPLETE--> COMPLETED`
1511
+
1512
+ - Guard: none
1513
+
1514
+ - Actions: `updateTaskData`
1515
+
1516
+ **Valid transitions from `COMPLETED`**: None (final state)
1517
+
1518
+ **Guards**: None
1519
+
1520
+ **Description**: Final terminated terminal state.
1521
+
1522
+ **Entry Actions**:
1523
+
1524
+ - `cleanupResources`
1525
+
1526
+ **How this state is reached (incoming transitions)**:
1527
+
1528
+ - Reached from `OFFERED`, `CONNECTED`, `HELD`, `CONSULTING`, and `CONFERENCING` via terminating branches (`TASK_WRAPUP`, failure paths, and default end-of-contact/conference branches)
1529
+
1530
+ **Valid transitions from `TERMINATED`**: None (final state)
1531
+
1532
+ **Guards**: None
1533
+
1534
+ Event names below are from `TaskEvent` in `constants.ts`.
1535
+
1536
+ - `TASK_INCOMING`, `TASK_OFFERED`, `HYDRATE`
1537
+
1538
+ - `CONTACT_UPDATED`, `CONTACT_OWNER_CHANGED`
1539
+
1540
+ - `ASSIGN`, `CONTACT_ENDED`, `TASK_WRAPUP`, `WRAPUP_COMPLETE`
1541
+
1542
+ - `HOLD_INITIATED`, `HOLD_SUCCESS`, `HOLD_FAILED`
1543
+
1544
+ - `UNHOLD_INITIATED`, `UNHOLD_SUCCESS`, `UNHOLD_FAILED`
1545
+
1546
+ - `OFFER_CONSULT`, `CONSULT`, `CONSULT_SUCCESS`, `CONSULT_CREATED`
1547
+
1548
+ - `CONSULTING_ACTIVE`, `CONSULT_END`, `CONSULT_FAILED`
1549
+
1550
+ - `CTQ_CANCEL`, `CTQ_CANCEL_FAILED`
1551
+
1552
+ - `MERGE_TO_CONFERENCE`, `CONFERENCE_START`, `CONFERENCE_FAILED`, `CONFERENCE_END`
1553
+
1554
+ - `PARTICIPANT_LEAVE`
1555
+
1556
+ - `TRANSFER_CONFERENCE`, `TRANSFER_CONFERENCE_SUCCESS`, `TRANSFER_CONFERENCE_FAILED`
1557
+
1558
+ - `EXIT_CONFERENCE`, `EXIT_CONFERENCE_SUCCESS`, `EXIT_CONFERENCE_FAILED`
1559
+
1560
+ - `TRANSFER_SUCCESS`, `TRANSFER_FAILED`
1561
+
1562
+ - `RECORDING_STARTED`, `PAUSE_RECORDING`, `RESUME_RECORDING`
1563
+
1564
+ The flow below shows a single transition in the requested form:
1565
+
1566
+ ```mermaid
1567
+ flowchart LR
1568
+ A[User Action/CC Event Mapping] --> B[State Machine Event Trigger]
1569
+ B --> C{Check against Current State: Valid Transition?}
1570
+ C -- No --> X[Ignore/No-op]
1571
+ C -- Yes --> D[StateMachine evaluates guards]
1572
+ D -- No --> Y[Blocked by Guard]
1573
+ D -- Yes --> E[Execute Associated Actions]
1574
+ E --> F[Context updated]
1575
+ F --> G[UI Controls Recomputed]
1576
+ G --> H[Transition to Target State]
1577
+ ```
1578
+
1579
+ ```mermaid
1580
+ flowchart LR
1581
+ A[User invoked hold API] --> B[Event Trigger: HOLD_INITIATED]
1582
+ B --> C{State = CONNECTED?}
1583
+ C -- No --> X[Ignore/No-op]
1584
+ C -- Yes --> D[Guards: none]
1585
+ D -- Yes --> E[Actions: setHoldInitiated + updateTaskData]
1586
+ E --> F[Context updated]
1587
+ F --> G[UI controls recomputed]
1588
+ G --> H[Transition: CONNECTED -> HOLD_INITIATING]
1589
+ H --> I[CC Event: AGENT_CONTACT_HELD]
1590
+ I --> J[Mapped: HOLD_SUCCESS]
1591
+ J --> K{State = HOLD_INITIATING?}
1592
+ K -- No --> X
1593
+ K -- Yes --> L[Guards: none]
1594
+ L -- Yes --> M[Actions: setHoldSuccess + updateTaskData]
1595
+ M --> N[Context updated]
1596
+ N --> O[UI controls recomputed]
1597
+ O --> P[Transition: HOLD_INITIATING -> HELD]
1598
+ ```
1599
+
1600
+ Manage complex task lifecycle with clear states, transitions, guards, and actions.
1601
+
1602
+ ```typescript
1603
+ // Task.ts
1604
+ export default abstract class Task extends EventEmitter {
1605
+ public stateMachineService?: ActorRefFrom<TaskStateMachine>;
1606
+
1607
+ private initializeStateMachine(): void {
1608
+ const machine: TaskStateMachine = createTaskStateMachine(this.uiControlConfig, {
1609
+ actions: this.getStateMachineActionOverrides(),
1610
+ });
1611
+
1612
+ this.stateMachineService = createActor(machine);
1613
+
1614
+ // Subscribe to state changes
1615
+ this.stateMachineService.subscribe((snapshot) => {
1616
+ const currentState = snapshot.value as TaskState;
1617
+ this.state = snapshot;
1618
+ this.updateUiControls(previousState !== currentState);
1619
+ });
1620
+
1621
+ this.stateMachineService.start();
1622
+ }
1623
+
1624
+ public sendStateMachineEvent(event: TaskEventPayload): void {
1625
+ this.stateMachineService?.send(event);
1626
+ }
1627
+ }
1628
+ ```
1629
+
1630
+ ```typescript
1631
+ // state-machine/TaskStateMachine.ts
1632
+ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
1633
+ return {
1634
+ id: 'taskStateMachine',
1635
+ initial: TaskState.IDLE,
1636
+ context: createInitialContext(uiControlConfig, TaskState.IDLE),
1637
+ states: {
1638
+ [TaskState.IDLE]: {
1639
+ on: {
1640
+ [TaskEvent.TASK_INCOMING]: {
1641
+ target: TaskState.OFFERED,
1642
+ actions: ['initializeTask', 'emitTaskIncoming'],
1643
+ },
1644
+ },
1645
+ },
1646
+ [TaskState.OFFERED]: {
1647
+ on: {
1648
+ [TaskEvent.ASSIGN]: {
1649
+ target: TaskState.CONNECTED,
1650
+ actions: ['updateTaskData', 'emitTaskAssigned'],
1651
+ },
1652
+ [TaskEvent.TASK_WRAPUP]: {
1653
+ target: TaskState.TERMINATED,
1654
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
1655
+ },
1656
+ },
1657
+ },
1658
+ // ... more states
1659
+ },
1660
+ };
1661
+ }
1662
+ ```
1663
+
1664
+ Complete mapping from backend CC_EVENTS to internal TaskEvent types.
1665
+
1666
+ | Backend CC Event | TaskEvent | Typical From State(s) | Target State | Notes / Guards |
1667
+ |---|---|---|---|---|
1668
+ | `AGENT_CONTACT_RESERVED` | `TASK_INCOMING` | `IDLE` | `OFFERED` | Incoming task entry |
1669
+ | `AGENT_OFFER_CONTACT` | `TASK_OFFERED` | `OFFERED` | `OFFERED` | Offer payload refresh |
1670
+ | `AGENT_CONTACT` | `HYDRATE` | `IDLE` | `WRAPPING_UP` / `CONSULTING` / `HELD` / `CONNECTED` / `CONFERENCING` / `IDLE` | Guard-based restore |
1671
+ | `CONTACT_UPDATED` | `CONTACT_UPDATED` | any | same | Context sync |
1672
+ | `CONTACT_OWNER_CHANGED` | `CONTACT_OWNER_CHANGED` | any | same | Context sync |
1673
+ | `AGENT_OFFER_CONSULT` | `OFFER_CONSULT` | `OFFERED` | `OFFERED` | Receiver-side consult offer |
1674
+ | `AGENT_CONTACT_ASSIGNED` | `ASSIGN` | `OFFERED` / `CONNECTED` / `CONSULTING` | `CONNECTED` | Assign/reassign |
1675
+ | `AGENT_CONTACT_HELD` | `HOLD_SUCCESS` | `HOLD_INITIATING` | `HELD` | Includes `mediaResourceId` |
1676
+ | `AGENT_CONTACT_UNHELD` | `UNHOLD_SUCCESS` | `RESUME_INITIATING` | `CONNECTED` | Includes `mediaResourceId` |
1677
+ | `AGENT_CONSULT_CREATED` | `CONSULT_CREATED` | varies | same | Context + emitter action |
1678
+ | `AGENT_CONSULTING` | `CONSULTING_ACTIVE` | `OFFERED` / `CONSULTING` | `CONSULTING` | Sets consult joined flag |
1679
+ | `AGENT_CONSULT_ENDED` | `CONSULT_END` | `CONSULTING` | `CONFERENCING` / `HELD` / `TERMINATED` | Depends on initiator flags |
1680
+ | `AGENT_CONSULT_FAILED` | `CONSULT_FAILED` | `CONSULT_INITIATING` | `CONFERENCING` / `HELD` / `CONNECTED` | Guard-based fallback |
1681
+ | `AGENT_CTQ_FAILED` | `CONSULT_FAILED` | `CONSULT_INITIATING` | `CONFERENCING` / `HELD` / `CONNECTED` | Same as consult failed |
1682
+ | `AGENT_CTQ_CANCELLED` | `CTQ_CANCEL` | `CONSULT_INITIATING` | `HELD` / `CONNECTED` | Guarded by hold state |
1683
+ | `AGENT_CTQ_CANCEL_FAILED` | `CTQ_CANCEL_FAILED` | varies | same | No transition mapping |
1684
+ | `AGENT_BLIND_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | `shouldWrapUpOrIsInitiator` |
1685
+ | `AGENT_CONSULT_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | Same path |
1686
+ | `AGENT_VTEAM_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | Same path |
1687
+ | `AGENT_WRAPUP` | `TASK_WRAPUP` | `OFFERED` / `CONNECTED` / `HELD` / `CONSULTING` | `TERMINATED` / `WRAPPING_UP` | `OFFERED` terminates; others wrap |
1688
+ | `AGENT_BLIND_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1689
+ | `AGENT_VTEAM_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1690
+ | `AGENT_CONSULT_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1691
+ | `AGENT_CONFERENCE_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1692
+ | `CONTACT_ENDED` | `CONTACT_ENDED` | `CONNECTED` / `HELD` / `CONSULTING` / `CONFERENCING` | `CONFERENCING` / `WRAPPING_UP` / `TERMINATED` / same | Guard-driven branch |
1693
+ | `AGENT_INVITE_FAILED` | `INVITE_FAILED` | `OFFERED` | `TERMINATED` | Reject path |
1694
+ | `AGENT_CONTACT_ASSIGN_FAILED` | `ASSIGN_FAILED` | `OFFERED` | `TERMINATED` | Reject path |
1695
+ | `AGENT_CONTACT_OFFER_RONA` | `RONA` | `OFFERED` | `TERMINATED` | Timeout path |
1696
+ | `AGENT_OUTBOUND_FAILED` | `OUTBOUND_FAILED` | `OFFERED` | `TERMINATED` | Outbound failure |
1697
+ | `CONTACT_RECORDING_STARTED` | `RECORDING_STARTED` | any | same | Recording state update |
1698
+ | `CONTACT_RECORDING_PAUSED` | `PAUSE_RECORDING` | `CONNECTED` | same | Recording state update |
1699
+ | `CONTACT_RECORDING_RESUMED` | `RESUME_RECORDING` | `CONNECTED` | same | Recording state update |
1700
+ | `AGENT_WRAPPEDUP` | `WRAPUP_COMPLETE` | `WRAPPING_UP` | `COMPLETED` | Final completion |
1701
+ | `AGENT_CONSULT_CONFERENCED` | `CONFERENCE_START` | `CONSULTING` / `CONF_INITIATING` / `CONFERENCING` | `CONFERENCING` / same | Conference established |
1702
+ | `PARTICIPANT_JOINED_CONFERENCE` | `CONFERENCE_START` | `CONSULTING` / `CONF_INITIATING` / `CONFERENCING` | `CONFERENCING` / same | Conference participant joined |
1703
+ | `AGENT_CONSULT_CONFERENCE_FAILED` | `CONFERENCE_FAILED` | `CONF_INITIATING` | `CONSULTING` | Merge fail fallback |
1704
+ | `AGENT_CONSULT_CONFERENCE_ENDED` | `CONFERENCE_END` | `CONFERENCING` | `WRAPPING_UP` / `CONNECTED` / `TERMINATED` | Guard-driven |
1705
+ | `PARTICIPANT_LEFT_CONFERENCE` | `PARTICIPANT_LEAVE` | `CONFERENCING` | `WRAPPING_UP` / `TERMINATED` / `CONNECTED` / same | Ownership + downgrade guards |
1706
+ | `AGENT_CONFERENCE_TRANSFERRED` | `TRANSFER_CONFERENCE_SUCCESS` | `CONSULTING` / `CONFERENCING` | `WRAPPING_UP` / `CONFERENCING` / `TERMINATED` / same | Initiator/receiver dependent |
1707
+
1708
+ - `AGENT_CONTACT_UNASSIGNED` -> returns `null` in mapper (`TaskManager.mapEventToTaskStateMachineEvent`)
1709
+
1710
+ | Backend Event | TaskEvent | State Transition | Notes |
1711
+ |---|---|---|---|
1712
+ | `AgentContactReserved` | `TASK_INCOMING` | IDLE → OFFERED | New task offered |
1713
+ | `AgentOfferContact` | `TASK_OFFERED` | Stay in OFFERED | Offer confirmation |
1714
+ | `AgentContact` | `HYDRATE` | Various | State restoration |
1715
+ | `AgentContactAssigned` | `ASSIGN` | OFFERED → CONNECTED (also CONNECTED/CONSULTING refresh paths) | Task accepted/reassigned |
1716
+ | `ContactUpdated` | `CONTACT_UPDATED` | No change | Data update only |
1717
+ | `ContactOwnerChanged` | `CONTACT_OWNER_CHANGED` | No change | Owner update only |
1718
+ | `ContactEnded` | `CONTACT_ENDED` | Guard-based branch | CONFERENCING / WRAPPING_UP / TERMINATED / stay |
1719
+ | `AgentContactUnassigned` | None | N/A | Handled by other events |
1720
+
1721
+ | Backend Event | TaskEvent | State Transition | Context Update |
1722
+ |---|---|---|---|
1723
+ | `AgentContactHeld` | `HOLD_SUCCESS` | HOLD_INITIATING → HELD | `setHoldState` updates `taskData.interaction.media[mediaResourceId].isHold = true` |
1724
+ | `AgentContactUnheld` | `UNHOLD_SUCCESS` | RESUME_INITIATING → CONNECTED | `setHoldState` updates `taskData.interaction.media[mediaResourceId].isHold = false` |
1725
+ | `AgentContactHoldFailed` | `HOLD_FAILED` | HOLD_INITIATING → CONNECTED | Context refreshed |
1726
+ | `AgentContactUnholdFailed` | `UNHOLD_FAILED` | RESUME_INITIATING → HELD | No transition action |
1727
+
1728
+ | Backend Event / API | TaskEvent | State Transition | Context Update |
1729
+ |---|---|---|---|
1730
+ | API `task.consult(...)` | `CONSULT` | CONNECTED/HELD/CONFERENCING → CONSULT_INITIATING | Sets consult initiator + destination (and `consultFromConference` in conference flow) |
1731
+ | `AgentOfferConsult` | `OFFER_CONSULT` | OFFERED → OFFERED | Offer-only path |
1732
+ | `AgentConsultCreated` | `CONSULT_CREATED` | No state transition wiring | Event exists but not consumed by transition table |
1733
+ | `AgentConsulting` | `CONSULTING_ACTIVE` | OFFERED → CONSULTING, CONSULTING → CONSULTING | Sets `consultDestinationAgentJoined` |
1734
+ | `AgentConsultEnded` | `CONSULT_END` | CONSULTING → CONFERENCING / HELD / TERMINATED | Depends on initiator and consult-from-conference |
1735
+ | `AgentConsultFailed` / `AgentCtqFailed` | `CONSULT_FAILED` | CONSULT_INITIATING → CONFERENCING / HELD / CONNECTED | Guard-based fallback |
1736
+ | `AgentCtqCancelled` | `CTQ_CANCEL` | CONSULT_INITIATING → HELD / CONNECTED | Guarded by `isPrimaryMediaOnHold` |
1737
+ | `AgentCtqCancelFailed` | `CTQ_CANCEL_FAILED` | No state transition wiring | Event mapped but not consumed |
1738
+
1739
+ | Backend Event | TaskEvent | State Transition | Wrapup Logic |
1740
+ |---|---|---|---|
1741
+ | `AgentBlindTransferred` | `TRANSFER_SUCCESS` | → WRAPPING_UP/CONNECTED | Guard `shouldWrapUpOrIsInitiator` decides wrapup vs receiver/default path |
1742
+ | `AgentVTeamTransferred` | `TRANSFER_SUCCESS` | → WRAPPING_UP/CONNECTED | Same transition logic as blind transfer |
1743
+ | `AgentConsultTransferred` | `TRANSFER_SUCCESS` | CONSULTING → WRAPPING_UP/CONNECTED | Initiator/wrapup path vs receiver/default path |
1744
+ | `AgentBlindTransferFailed` | `TRANSFER_FAILED` | No change | Emit failure, stay in current state |
1745
+ | `AgentVTeamTransferFailed` | `TRANSFER_FAILED` | No change | Queue transfer failed |
1746
+ | `AgentConsultTransferFailed` | `TRANSFER_FAILED` | No change | Consult transfer failed |
1747
+
1748
+ | Backend Event / API | TaskEvent | State Transition | Context Update |
1749
+ |---|---|---|---|
1750
+ | API `task.consultConference()` | `MERGE_TO_CONFERENCE` | CONSULTING → CONF_INITIATING | Starts merge flow |
1751
+ | `AgentConsultConferenced` | `CONFERENCE_START` | CONSULTING/CONF_INITIATING → CONFERENCING | `handleConferenceStarted` path |
1752
+ | `ParticipantJoinedConference` | `CONFERENCE_START` | CONFERENCING → CONFERENCING | Refresh + emit conference started |
1753
+ | `ParticipantLeftConference` | `PARTICIPANT_LEAVE` | CONFERENCING → WRAPPING_UP / TERMINATED / CONNECTED / stay | Uses `didCurrentAgentLeaveConference`, `shouldWrapUp`, `shouldDowngradeConferenceToConnected` |
1754
+ | `AgentConsultConferenceEnded` | `CONFERENCE_END` | CONFERENCING → WRAPPING_UP / CONNECTED / TERMINATED | Guard-based branch |
1755
+ | `AgentConsultConferenceFailed` | `CONFERENCE_FAILED` | CONF_INITIATING → CONSULTING | Merge failed fallback |
1756
+ | `AgentConferenceTransferred` | `TRANSFER_CONFERENCE_SUCCESS` | CONSULTING/CONFERENCING branch logic | Initiator/receiver dependent |
1757
+ | API/SDK conference transfer fail | `TRANSFER_CONFERENCE_FAILED` | CONSULTING/CONFERENCING stay | Clears transfer request flag |
1758
+
1759
+ | Backend Event | TaskEvent | State Transition | Context Update |
1760
+ |---|---|---|---|
1761
+ | `ContactRecordingStarted` | `RECORDING_STARTED` | No change | Update recording state |
1762
+ | `ContactRecordingPaused` | `PAUSE_RECORDING` | No change | Mark recording paused |
1763
+ | `ContactRecordingResumed` | `RESUME_RECORDING` | No change | Mark recording active |
1764
+
1765
+ | Backend Event | TaskEvent | State Transition | Notes |
1766
+ |---|---|---|---|
1767
+ | `AgentWrapup` | `TASK_WRAPUP` | → WRAPPING_UP | Enter ACW |
1768
+ | `AgentWrappedup` | `WRAPUP_COMPLETE` | WRAPPING_UP → COMPLETED | Complete wrapup |
1769
+
1770
+ This diagram represents state-to-state lifecycle transitions for the task state machine.
1771
+
1772
+ ```mermaid
1773
+ stateDiagram-v2
1774
+ [*] --> IDLE
1775
+
1776
+ %% IDLE
1777
+ IDLE --> OFFERED: AGENT_CONTACT_RESERVED (CC Event) -> TASK_INCOMING (State Machine Event)
1778
+
1779
+ %% OFFERED
1780
+ OFFERED --> OFFERED: AGENT_OFFER_CONTACT (CC Event) -> TASK_OFFERED (State Machine Event)
1781
+ OFFERED --> OFFERED: AGENT_OFFER_CONSULT (CC Event) -> OFFER_CONSULT (State Machine Event)
1782
+ OFFERED --> CONNECTED: AGENT_CONTACT_ASSIGNED (CC Event) -> ASSIGN (State Machine Event)
1783
+ OFFERED --> CONSULTING: AGENT_CONSULTING (CC Event) -> CONSULTING_ACTIVE (State Machine Event)
1784
+ OFFERED --> TERMINATED: AGENT_CONTACT_OFFER_RONA (CC Event) -> RONA (State Machine Event)
1785
+ OFFERED --> TERMINATED: AGENT_CONTACT_ASSIGN_FAILED (CC Event) -> ASSIGN_FAILED (State Machine Event)
1786
+ OFFERED --> TERMINATED: AGENT_INVITE_FAILED (CC Event) -> INVITE_FAILED (State Machine Event)
1787
+ OFFERED --> TERMINATED: AGENT_OUTBOUND_FAILED (CC Event) -> OUTBOUND_FAILED (State Machine Event)
1788
+ OFFERED --> TERMINATED: AGENT_WRAPUP (CC Event) -> TASK_WRAPUP (State Machine Event)
1789
+
1790
+ %% CONNECTED
1791
+ CONNECTED --> HOLD_INITIATING: API hold() -> HOLD_INITIATED (State Machine Event)
1792
+ CONNECTED --> CONSULT_INITIATING: API consult() -> CONSULT (State Machine Event)
1793
+ CONNECTED --> WRAPPING_UP: AGENT_*TRANSFERRED (CC Event) -> TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1794
+ CONNECTED --> CONNECTED: AGENT_*TRANSFERRED (CC Event) -> TRANSFER_SUCCESS (State Machine Event) [receiver]
1795
+ CONNECTED --> CONNECTED: AGENT_*TRANSFER_FAILED (CC Event) -> TRANSFER_FAILED (State Machine Event)
1796
+ CONNECTED --> CONFERENCING: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [conferenceInProgressFromEvent]
1797
+ CONNECTED --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [shouldWrapUp]
1798
+ CONNECTED --> TERMINATED: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [default]
1799
+ CONNECTED --> WRAPPING_UP: AGENT_WRAPUP (CC Event) -> TASK_WRAPUP (State Machine Event)
1800
+
1801
+ %% HOLD_INITIATING
1802
+ HOLD_INITIATING --> HELD: HOLD_SUCCESS (State Machine Event)
1803
+ HOLD_INITIATING --> CONNECTED: HOLD_FAILED (State Machine Event)
1804
+
1805
+ %% HELD
1806
+ HELD --> RESUME_INITIATING: UNHOLD_INITIATED (State Machine Event)
1807
+ HELD --> CONSULT_INITIATING: CONSULT (State Machine Event)
1808
+ HELD --> WRAPPING_UP: TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1809
+ HELD --> CONNECTED: TRANSFER_SUCCESS (State Machine Event) [receiver]
1810
+ HELD --> CONFERENCING: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [conferenceInProgressFromEvent]
1811
+ HELD --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [shouldWrapUp]
1812
+ HELD --> TERMINATED: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [default]
1813
+ HELD --> WRAPPING_UP: TASK_WRAPUP (State Machine Event)
1814
+ HELD --> HELD: TRANSFER_FAILED (State Machine Event)
1815
+
1816
+ %% RESUME_INITIATING
1817
+ RESUME_INITIATING --> CONNECTED: UNHOLD_SUCCESS (State Machine Event)
1818
+ RESUME_INITIATING --> HELD: UNHOLD_FAILED (State Machine Event)
1819
+
1820
+ %% CONSULT_INITIATING
1821
+ CONSULT_INITIATING --> CONSULTING: CONSULT_SUCCESS (State Machine Event)
1822
+ CONSULT_INITIATING --> CONFERENCING: CONSULT_FAILED (State Machine Event) [consultFromConference]
1823
+ CONSULT_INITIATING --> HELD: CONSULT_FAILED / CTQ_CANCEL (State Machine Event) [isPrimaryMediaOnHold]
1824
+ CONSULT_INITIATING --> CONNECTED: HOLD_FAILED / CONSULT_FAILED / CTQ_CANCEL (State Machine Event) [default]
1825
+ CONSULT_INITIATING --> CONSULT_INITIATING: HOLD_SUCCESS (State Machine Event)
1826
+
1827
+ %% CONSULTING
1828
+ CONSULTING --> CONFERENCING: CONSULT_END (State Machine Event) [consultInitiator && consultFromConference]
1829
+ CONSULTING --> HELD: CONSULT_END (State Machine Event) [consultInitiator]
1830
+ CONSULTING --> TERMINATED: CONSULT_END (State Machine Event) [consulted agent]
1831
+ CONSULTING --> WRAPPING_UP: TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1832
+ CONSULTING --> CONNECTED: TRANSFER_SUCCESS (State Machine Event) [receiver]
1833
+ CONSULTING --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) / TASK_WRAPUP (State Machine Event)
1834
+ CONSULTING --> CONNECTED: ASSIGN (State Machine Event)
1835
+ CONSULTING --> CONF_INITIATING: MERGE_TO_CONFERENCE (State Machine Event)
1836
+ CONSULTING --> CONFERENCING: CONFERENCE_START (State Machine Event)
1837
+ CONSULTING --> CONFERENCING: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [!consultInitiator]
1838
+ CONSULTING --> WRAPPING_UP: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [shouldWrapUp]
1839
+ CONSULTING --> TERMINATED: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [default]
1840
+ CONSULTING --> CONSULTING: CONSULTING_ACTIVE / HOLD_SUCCESS / UNHOLD_SUCCESS / TRANSFER_FAILED / TRANSFER_CONFERENCE / TRANSFER_CONFERENCE_FAILED (State Machine Event)
1841
+
1842
+ %% CONF_INITIATING
1843
+ CONF_INITIATING --> CONFERENCING: CONFERENCE_START (State Machine Event)
1844
+ CONF_INITIATING --> CONSULTING: CONFERENCE_FAILED (State Machine Event)
1845
+
1846
+ %% CONFERENCING
1847
+ CONFERENCING --> CONSULT_INITIATING: CONSULT (State Machine Event)
1848
+ CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveConference && shouldWrapUp]
1849
+ CONFERENCING --> TERMINATED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveConference]
1850
+ CONFERENCING --> CONNECTED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [shouldDowngradeConferenceToConnected]
1851
+ CONFERENCING --> WRAPPING_UP: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [shouldWrapUp]
1852
+ CONFERENCING --> CONNECTED: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [customerInCall]
1853
+ CONFERENCING --> TERMINATED: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [default]
1854
+ CONFERENCING --> CONFERENCING: CONFERENCE_START / CONSULT_END / HOLD_SUCCESS / UNHOLD_SUCCESS / PARTICIPANT_LEAVE(other) / TRANSFER_CONFERENCE / TRANSFER_CONFERENCE_SUCCESS / TRANSFER_CONFERENCE_FAILED / CONTACT_ENDED (State Machine Event)
1855
+
1856
+ %% WRAPPING_UP / FINAL
1857
+ WRAPPING_UP --> COMPLETED: WRAPUP_COMPLETE (State Machine Event)
1858
+ COMPLETED --> [*]
1859
+ TERMINATED --> [*]
1860
+ ```
1861
+
1862
+ The full diagram above is the source of truth. The diagrams below split above flows based on each feature.
1863
+
1864
+ ```mermaid
1865
+ stateDiagram-v2
1866
+ [*] --> IDLE
1867
+ IDLE --> OFFERED: AGENT_CONTACT_RESERVED -> TASK_INCOMING
1868
+ OFFERED --> OFFERED: AGENT_CONTACT_OFFER -> TASK_OFFERED
1869
+ OFFERED --> OFFERED: AGENT_CONSULT_OFFER -> OFFER_CONSULT
1870
+ OFFERED --> CONNECTED: AGENT_CONTACT_ASSIGNED -> ASSIGN
1871
+ OFFERED --> TERMINATED: AGENT_CONTACT_OFFER_RONA/AGENT_CONTACT_ASSIGN_FAILED/AGENT_INVITE_FAILED/AGENT_OUTBOUND_FAILED/AGENT_WRAPUP -> RONA/ASSIGN_FAILED/INVITE_FAILED/OUTBOUND_FAILED/TASK_WRAPUP
1872
+
1873
+ CONNECTED --> HOLD_INITIATING: task.hold() -> HOLD_INITIATED
1874
+ CONNECTED --> CONSULT_INITIATING: task.consult() -> CONSULT
1875
+
1876
+ CONNECTED --> CONNECTED: PAUSE_RECORDING/RESUME_RECORDING
1877
+ CONNECTED --> CONNECTED: AGENT_BLIND_TRANSFER_FAILED/AGENT_VTEAM_TRANSFER_FAILED -> TRANSFER_FAILED
1878
+
1879
+ CONNECTED --> WRAPPING_UP: AGENT_BLIND_TRANSFERRED/AGENT_VTEAM_TRANSFERRED -> TRANSFER_SUCCESS [shouldWrapUpOrIsInitiator]
1880
+ CONNECTED --> WRAPPING_UP: CONTACT_ENDED -> CONTACT_ENDED [shouldWrapUp]
1881
+ CONNECTED --> WRAPPING_UP: AGENT_WRAPUP -> TASK_WRAPUP
1882
+ CONNECTED --> TERMINATED: CONTACT_ENDED -> CONTACT_ENDED [default]
1883
+
1884
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
1885
+ COMPLETED --> [*]
1886
+ TERMINATED --> [*]
1887
+ ```
1888
+
1889
+ ```mermaid
1890
+ stateDiagram-v2
1891
+ [*] --> CONNECTED
1892
+ CONNECTED --> HOLD_INITIATING: task.hold() -> HOLD_INITIATED
1893
+ HOLD_INITIATING --> HELD: AGENT_CONTACT_HELD -> HOLD_SUCCESS
1894
+ HOLD_INITIATING --> CONNECTED: AGENT_CONTACT_HOLD_FAILED -> HOLD_FAILED
1895
+ HELD --> RESUME_INITIATING: task.resume() -> UNHOLD_INITIATED
1896
+ RESUME_INITIATING --> CONNECTED: AGENT_CONTACT_UNHELD -> UNHOLD_SUCCESS
1897
+ RESUME_INITIATING --> HELD: AGENT_CONTACT_UNHOLD_FAILED -> UNHOLD_FAILED
1898
+ HELD --> CONSULT_INITIATING: task.consult() -> Consult
1899
+ CONSULT_INITIATING --> HELD: CONSULT_FAILED / CTQ_CANCEL [isPrimaryMediaOnHold]
1900
+ HELD --> WRAPPING_UP: CONTACT_ENDED -> CONTACT_ENDED [shouldWrapUp]
1901
+ HELD --> TERMINATED: CONTACT_ENDED -> CONTACT_ENDED [default]
1902
+
1903
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
1904
+ COMPLETED --> [*]
1905
+ TERMINATED --> [*]
1906
+ ```
1907
+
1908
+ ```mermaid
1909
+ stateDiagram-v2
1910
+ stateDiagram-v2
1911
+ [*] --> CONNECTED
1912
+ CONNECTED --> CONSULT_INITIATING: task.consult() -> CONSULT
1913
+ HELD --> CONSULT_INITIATING: task.consult() -> CONSULT
1914
+ CONFERENCING --> CONSULT_INITIATING: task.consult() -> CONSULT
1915
+
1916
+ CONSULT_INITIATING --> CONSULT_INITIATING: AGENT_CONTACT_HELD -> HOLD_SUCCESS
1917
+ CONSULT_INITIATING --> CONNECTED: AGENT_CONTACT_HOLD_FAILED -> HOLD_FAILED
1918
+ CONSULT_INITIATING --> CONSULTING: API consult success -> CONSULT_SUCCESS
1919
+ CONSULT_INITIATING --> HELD: AGENT_CONSULT_FAILED/AGENT_CTQ_FAILED -> CONSULT_FAILED [isPrimaryMediaOnHold]
1920
+ CONSULT_INITIATING --> CONNECTED: AGENT_CONSULT_FAILED/AGENT_CTQ_FAILED -> CONSULT_FAILED [default]
1921
+ CONSULT_INITIATING --> CONFERENCING: AGENT_CONSULT_FAILED -> CONSULT_FAILED [consultFromConference]
1922
+ CONSULT_INITIATING --> HELD: AGENT_CTQ_CANCELLED -> CTQ_CANCEL [isPrimaryMediaOnHold]
1923
+ CONSULT_INITIATING --> CONNECTED: AGENT_CTQ_CANCELLED -> CTQ_CANCEL [default]
1924
+
1925
+ CONSULTING --> HELD: AGENT_CONSULT_ENDED -> CONSULT_END [consultInitiator]
1926
+ CONSULTING --> TERMINATED: AGENT_CONSULT_ENDED -> CONSULT_END [consulted agent]
1927
+ CONSULTING --> CONFERENCING: AGENT_CONSULT_ENDED -> CONSULT_END [consultInitiator && consultFromConference]
1928
+ CONSULTING --> CONNECTED: AGENT_CONSULT_TRANSFERRED/AGENT_CONTACT_ASSIGNED -> TRANSFER_SUCCESS/ASSIGN
1929
+ CONSULTING --> WRAPPING_UP: AGENT_CONSULT_TRANSFERRED -> TRANSFER_SUCCESS [shouldWrapUpOrIsInitiator]
1930
+ CONSULTING --> CONSULTING: AGENT_CONSULT_TRANSFER_FAILED -> TRANSFER_FAILED
1931
+ CONSULTING --> CONF_INITIATING: task.consultConference() -> MERGE_TO_CONFERENCE
1932
+ CONSULTING --> WRAPPING_UP: AGENT_CONTACT_ENDED -> CONTACT_ENDED
1933
+
1934
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
1935
+ COMPLETED --> [*]
1936
+ TERMINATED --> [*]
1937
+ ```
1938
+
1939
+ ```mermaid
1940
+ stateDiagram-v2
1941
+ [*] --> CONSULTING
1942
+ CONSULTING --> CONF_INITIATING: task.consultConference() -> MERGE_TO_CONFERENCE
1943
+ CONF_INITIATING --> CONFERENCING: AGENT_CONSULT_CONFERENCED -> CONFERENCE_START
1944
+ CONF_INITIATING --> CONSULTING: AGENT_CONSULT_CONFERENCE_FAILED -> CONFERENCE_FAILED
1945
+ CONFERENCING --> CONSULT_INITIATING: task.consult() -> CONSULT
1946
+ CONSULTING --> CONFERENCING: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [!consultInitiator]
1947
+ CONSULTING --> WRAPPING_UP: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [shouldWrapUp]
1948
+ CONSULTING --> TERMINATED: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [default]
1949
+ CONSULTING --> CONSULTING: AGENT_CONFERENCE_TRANSFER_FAILED -> TRANSFER_CONFERENCE_FAILED
1950
+ CONFERENCING --> CONFERENCING: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS
1951
+ CONFERENCING --> CONFERENCING: AGENT_CONFERENCE_TRANSFER_FAILED-> TRANSFER_CONFERENCE_FAILED
1952
+
1953
+ CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveConference && shouldWrapUp]
1954
+ CONFERENCING --> TERMINATED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveConference]
1955
+ CONFERENCING --> CONNECTED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [shouldDowngradeConferenceToConnected]
1956
+ CONFERENCING --> WRAPPING_UP: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [shouldWrapUp]
1957
+ CONFERENCING --> CONNECTED: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [customerInCall]
1958
+ CONFERENCING --> TERMINATED: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [default]
1959
+ WRAPPING_UP --> COMPLETED: WRAPUP_COMPLETE
1960
+ COMPLETED --> [*]
1961
+ TERMINATED --> [*]
1962
+ ```
1963
+
1964
+ ```typescript
1965
+ import {setup} from 'xstate';
1966
+ import {actions} from './actions';
1967
+ import {guards} from './guards';
1968
+
1969
+ const taskStateMachine = setup({
1970
+ types: {
1971
+ context: {} as TaskContext,
1972
+ events: {} as TaskEventPayload,
1973
+ },
1974
+ })
1975
+ .createMachine({
1976
+ id: 'taskStateMachine',
1977
+ initial: TaskState.IDLE,
1978
+ context: createInitialContext(uiControlConfig, TaskState.IDLE),
1979
+ states: {
1980
+ [TaskState.IDLE]: {
1981
+ on: {
1982
+ [TaskEvent.TASK_INCOMING]: {
1983
+ target: TaskState.OFFERED,
1984
+ actions: ['initializeTask', 'emitTaskIncoming'],
1985
+ },
1986
+ [TaskEvent.HYDRATE]: [
1987
+ {
1988
+ guard: guards.isInteractionTerminated,
1989
+ target: TaskState.WRAPPING_UP,
1990
+ actions: ['updateTaskData', 'markEnded', 'emitTaskHydrate'],
1991
+ },
1992
+ // ... more hydrate cases
1993
+ ],
1994
+ },
1995
+ },
1996
+ [TaskState.OFFERED]: {
1997
+ on: {
1998
+ [TaskEvent.ASSIGN]: {
1999
+ target: TaskState.CONNECTED,
2000
+ actions: ['updateTaskData', 'emitTaskAssigned'],
2001
+ },
2002
+ [TaskEvent.TASK_WRAPUP]: {
2003
+ target: TaskState.TERMINATED,
2004
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
2005
+ },
2006
+ // ... more transitions
2007
+ },
2008
+ },
2009
+ // ... more states
2010
+ },
2011
+ })
2012
+ .provide({actions});
2013
+ ```
2014
+
2015
+ The HYDRATE event restores state machine state after page refresh or reconnection.
2016
+
2017
+ **Algorithm**:
2018
+
2019
+ 1. Receive HYDRATE event with full task data
2020
+
2021
+ 2. Check interaction state and flags in order of precedence:
2022
+
2023
+ - If `taskData.interaction.isTerminated === true` -> WRAPPING_UP
2024
+
2025
+ - If `taskData.interaction.state === 'consulting'` -> CONSULTING
2026
+
2027
+ - If `taskData.interaction.state === 'hold'` -> HELD
2028
+
2029
+ - If `taskData.interaction.state === 'connected'` -> CONNECTED
2030
+
2031
+ - If conferencing-by-participants guard passes (`agentCount >= 2` in main call) -> CONFERENCING
2032
+
2033
+ - Default → Stay in IDLE
2034
+
2035
+ 3. Update context with hydrated data
2036
+
2037
+ 4. Emit TASK_HYDRATE event
2038
+
2039
+ ### Action implementation ownership
2040
+
2041
+ - `syncTaskDataFromEvent` appears in the reusable graph but its implementation is supplied in Task's machine options in `src/services/task/Task.ts`; it is intentionally absent from the default `actions.ts` map.
2042
+ - `didInitiateConsult` exists in `guards.ts` but is not referenced by the current graph. Treat it as defined-but-unwired.
2043
+ - `getDefaultUIControls` is package-public through `src/index.ts`; the voice/digital computation helpers are private implementation details.
2044
+
2045
+ ## Pitfalls
2046
+ - `syncTaskDataFromEvent` is declared by the graph but implemented by Task; adding a competing default action loses integration-owned normalization.
2047
+ - `handleConferenceFailed`, `handleSwitchToMainCall`, and `handleSwitchToConsult` are wired actions, while `forceConsultInitiator` remains defined but unwired.
2048
+ - Guards/actions must remain deterministic and transport-free; logging, metrics, authentication, and request side effects belong to Task/TaskManager.
2049
+
2050
+ - `RONA`, `INVITE_FAILED`, `ASSIGN_FAILED`, `OUTBOUND_FAILED`
2051
+
2052
+ | Backend Event | TaskEvent | State Transition | Notes |
2053
+ |---|---|---|---|
2054
+ | `AgentContactOfferRona` | `RONA` | OFFERED → TERMINATED | Redirection on no answer |
2055
+ | `AgentInviteFailed` | `INVITE_FAILED` | OFFERED → TERMINATED | Invite failed |
2056
+ | `AgentContactAssignFailed` | `ASSIGN_FAILED` | OFFERED → TERMINATED | Assignment failed |
2057
+ | `AgentOutboundFailed` | `OUTBOUND_FAILED` | OFFERED → TERMINATED | Outdial failed |
2058
+
2059
+ ## Module Do's / Don'ts
2060
+ - DO map backend notifications to typed `TaskEvent` values before sending them to the actor.
2061
+ - DO preserve initiating states separately from success/failure stable states.
2062
+ - DON'T invoke WebexRequest, LoggerProxy, or MetricsManager from the state-machine layer.
2063
+ - DON'T document private UI-control helpers as public APIs or claim an action is absent without checking graph wiring.
2064
+
2065
+ ## Key Design Trade-off
2066
+ - Backend events are mapped into a typed internal event vocabulary before transition evaluation, isolating the state graph from transport details.
2067
+
2068
+ ## Test-Case Strategy (module)
2069
+ Use `test/unit/spec/services/task/state-machine/TaskStateMachine.ts`, `guards.ts`, and `uiControlsComputer.ts` to cover every documented transition/action/guard/control claim. Use `test/unit/spec/services/task/Task.ts` for injected `syncTaskDataFromEvent` behavior. Explicitly assert defined-but-unwired status for `forceConsultInitiator` and `didInitiateConsult` until source wiring changes.
2070
+
2071
+ | Behavior / Requirement | Existing test evidence | Gap |
2072
+ |---|---|---|
2073
+ | `TASK_STATE_MACHINE-R-001` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Keep transition coverage synchronized with all mapped TaskEvent groups. |
2074
+ | `TASK_STATE_MACHINE-R-002` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Retain explicit wired/unwired assertions. |
2075
+ | `TASK_STATE_MACHINE-R-003` | `test/unit/spec/services/task/Task.ts` | None. |
2076
+ | `TASK_STATE_MACHINE-R-004` | `test/unit/spec/services/task/state-machine/uiControlsComputer.ts` | None. |
2077
+ | `TASK_STATE_MACHINE-R-005` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Transport/auth absence is also verified from imports. |
2078
+ | `TASK_STATE_MACHINE-R-006` | `test/unit/spec/services/task/Task.ts` | None. |
2079
+ | `TASK_STATE_MACHINE-R-007` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task/Task.ts` | Observability absence is also verified from imports. |
2080
+
2081
+ ## Traceability
2082
+ - Repo architecture: `../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../ai-docs/SPEC_INDEX.md`
2083
+ - Coverage state and contracts baseline: `../../../../../.sdd/manifest.json`
2084
+
2085
+ - Task lifecycle state machine: `TaskStateMachine.ts`
2086
+
2087
+ - State machine types/events: `constants.ts`, `types.ts`
2088
+
2089
+ - Guard logic: `guards.ts`
2090
+
2091
+ - Actions and context mutation: `actions.ts`
2092
+
2093
+ - UI control computation: `uiControlsComputer.ts`
2094
+
2095
+ `computeUIControls()` in `uiControlsComputer.ts`.
2096
+
2097
+ - [../../ai-docs/task-spec.md](../../ai-docs/task-spec.md) - Task service usage guide
2098
+
2099
+ - [../../ai-docs/task-spec.md](../../ai-docs/task-spec.md) - Task service architecture
2100
+
2101
+ - `../Task.ts` - actor lifecycle, action overrides, event emission
2102
+
2103
+ - `../TaskManager.ts` - maps backend events to state-machine events
2104
+
2105
+ - `../types.ts` - shared task data structures
2106
+
2107
+ - `../../ai-docs/task-spec.md` - broader task service architecture
2108
+
2109
+ - [TaskStateMachine.ts](../TaskStateMachine.ts) - Implementation
2110
+
2111
+ - [guards.ts](../guards.ts) - Guard functions
2112
+
2113
+ - [actions.ts](../actions.ts) - Action functions
2114
+
2115
+ - [constants.ts](../constants.ts) - State and event enums