@webex/contact-center 3.11.0 → 3.12.0-llmrefactor.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (267) hide show
  1. package/.sdd/manifest.json +882 -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 +379 -50
  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 +22 -2
  44. package/dist/constants.js.map +1 -1
  45. package/dist/index.js +27 -5
  46. package/dist/index.js.map +1 -1
  47. package/dist/metrics/behavioral-events.js +114 -0
  48. package/dist/metrics/behavioral-events.js.map +1 -1
  49. package/dist/metrics/constants.js +32 -3
  50. package/dist/metrics/constants.js.map +1 -1
  51. package/dist/services/ApiAiAssistant.js +318 -0
  52. package/dist/services/ApiAiAssistant.js.map +1 -0
  53. package/dist/services/UserPreference.js +427 -0
  54. package/dist/services/UserPreference.js.map +1 -0
  55. package/dist/services/agent/types.js.map +1 -1
  56. package/dist/services/config/Util.js +8 -4
  57. package/dist/services/config/Util.js.map +1 -1
  58. package/dist/services/config/constants.js +35 -2
  59. package/dist/services/config/constants.js.map +1 -1
  60. package/dist/services/config/index.js +41 -2
  61. package/dist/services/config/index.js.map +1 -1
  62. package/dist/services/config/types.js +66 -8
  63. package/dist/services/config/types.js.map +1 -1
  64. package/dist/services/constants.js +27 -1
  65. package/dist/services/constants.js.map +1 -1
  66. package/dist/services/core/Err.js.map +1 -1
  67. package/dist/services/core/Utils.js +122 -25
  68. package/dist/services/core/Utils.js.map +1 -1
  69. package/dist/services/core/aqm-reqs.js +92 -17
  70. package/dist/services/core/aqm-reqs.js.map +1 -1
  71. package/dist/services/core/websocket/WebSocketManager.js +22 -6
  72. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  73. package/dist/services/core/websocket/connection-service.js +3 -1
  74. package/dist/services/core/websocket/connection-service.js.map +1 -1
  75. package/dist/services/core/websocket/types.js.map +1 -1
  76. package/dist/services/index.js +6 -0
  77. package/dist/services/index.js.map +1 -1
  78. package/dist/services/task/Task.js +688 -0
  79. package/dist/services/task/Task.js.map +1 -0
  80. package/dist/services/task/TaskFactory.js +45 -0
  81. package/dist/services/task/TaskFactory.js.map +1 -0
  82. package/dist/services/task/TaskManager.js +751 -457
  83. package/dist/services/task/TaskManager.js.map +1 -1
  84. package/dist/services/task/TaskUtils.js +220 -23
  85. package/dist/services/task/TaskUtils.js.map +1 -1
  86. package/dist/services/task/constants.js +23 -2
  87. package/dist/services/task/constants.js.map +1 -1
  88. package/dist/services/task/dialer.js +129 -0
  89. package/dist/services/task/dialer.js.map +1 -1
  90. package/dist/services/task/digital/Digital.js +77 -0
  91. package/dist/services/task/digital/Digital.js.map +1 -0
  92. package/dist/services/task/state-machine/TaskStateMachine.js +873 -0
  93. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  94. package/dist/services/task/state-machine/actions.js +567 -0
  95. package/dist/services/task/state-machine/actions.js.map +1 -0
  96. package/dist/services/task/state-machine/constants.js +161 -0
  97. package/dist/services/task/state-machine/constants.js.map +1 -0
  98. package/dist/services/task/state-machine/guards.js +382 -0
  99. package/dist/services/task/state-machine/guards.js.map +1 -0
  100. package/dist/services/task/state-machine/index.js +53 -0
  101. package/dist/services/task/state-machine/index.js.map +1 -0
  102. package/dist/services/task/state-machine/types.js +54 -0
  103. package/dist/services/task/state-machine/types.js.map +1 -0
  104. package/dist/services/task/state-machine/uiControlsComputer.js +603 -0
  105. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  106. package/dist/services/task/taskDataNormalizer.js +99 -0
  107. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  108. package/dist/services/task/types.js +227 -4
  109. package/dist/services/task/types.js.map +1 -1
  110. package/dist/services/task/voice/Voice.js +1044 -0
  111. package/dist/services/task/voice/Voice.js.map +1 -0
  112. package/dist/services/task/voice/WebRTC.js +149 -0
  113. package/dist/services/task/voice/WebRTC.js.map +1 -0
  114. package/dist/types/cc.d.ts +894 -0
  115. package/dist/types/config.d.ts +72 -0
  116. package/dist/types/constants.d.ts +66 -0
  117. package/dist/types/index.d.ts +199 -0
  118. package/dist/types/logger-proxy.d.ts +71 -0
  119. package/dist/types/metrics/MetricsManager.d.ts +223 -0
  120. package/dist/types/metrics/behavioral-events.d.ts +29 -0
  121. package/dist/types/metrics/constants.d.ts +181 -0
  122. package/dist/types/services/AddressBook.d.ts +74 -0
  123. package/dist/types/services/ApiAiAssistant.d.ts +49 -0
  124. package/dist/types/services/EntryPoint.d.ts +67 -0
  125. package/dist/types/services/Queue.d.ts +76 -0
  126. package/dist/types/services/UserPreference.d.ts +118 -0
  127. package/dist/types/services/WebCallingService.d.ts +1 -0
  128. package/dist/types/services/agent/index.d.ts +46 -0
  129. package/dist/types/services/agent/types.d.ts +413 -0
  130. package/dist/types/services/config/Util.d.ts +20 -0
  131. package/dist/types/services/config/constants.d.ts +270 -0
  132. package/dist/types/services/config/index.d.ts +177 -0
  133. package/dist/types/services/config/types.d.ts +1368 -0
  134. package/dist/types/services/constants.d.ts +110 -0
  135. package/dist/types/services/core/Err.d.ts +125 -0
  136. package/dist/types/services/core/GlobalTypes.d.ts +58 -0
  137. package/dist/types/services/core/Utils.d.ts +121 -0
  138. package/dist/types/services/core/WebexRequest.d.ts +22 -0
  139. package/dist/types/services/core/aqm-reqs.d.ts +65 -0
  140. package/dist/types/services/core/constants.d.ts +99 -0
  141. package/dist/types/services/core/types.d.ts +47 -0
  142. package/dist/types/services/core/websocket/WebSocketManager.d.ts +36 -0
  143. package/dist/types/services/core/websocket/connection-service.d.ts +27 -0
  144. package/dist/types/services/core/websocket/keepalive.worker.d.ts +2 -0
  145. package/dist/types/services/core/websocket/types.d.ts +37 -0
  146. package/dist/types/services/index.d.ts +54 -0
  147. package/dist/types/services/task/AutoWrapup.d.ts +40 -0
  148. package/dist/types/services/task/Task.d.ts +157 -0
  149. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  150. package/dist/types/services/task/TaskManager.d.ts +1 -0
  151. package/dist/types/services/task/TaskUtils.d.ts +138 -0
  152. package/dist/types/services/task/constants.d.ts +91 -0
  153. package/dist/types/services/task/contact.d.ts +69 -0
  154. package/dist/types/services/task/dialer.d.ts +73 -0
  155. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  156. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1194 -0
  157. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  158. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  159. package/dist/types/services/task/state-machine/guards.d.ts +102 -0
  160. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  161. package/dist/types/services/task/state-machine/types.d.ts +269 -0
  162. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  163. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  164. package/dist/types/services/task/types.d.ts +1856 -0
  165. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  166. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  167. package/dist/types/types.d.ts +778 -0
  168. package/dist/types/utils/PageCache.d.ts +173 -0
  169. package/dist/types/webex-config.d.ts +53 -0
  170. package/dist/types/webex.d.ts +8 -0
  171. package/dist/types.js +130 -1
  172. package/dist/types.js.map +1 -1
  173. package/dist/webex.js +14 -2
  174. package/dist/webex.js.map +1 -1
  175. package/package.json +16 -12
  176. package/src/cc.ts +477 -51
  177. package/src/config.ts +6 -0
  178. package/src/constants.ts +21 -1
  179. package/src/index.ts +24 -5
  180. package/src/metrics/ai-docs/AGENTS.md +350 -0
  181. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  182. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  183. package/src/metrics/behavioral-events.ts +120 -0
  184. package/src/metrics/constants.ts +37 -3
  185. package/src/services/ApiAiAssistant.ts +412 -0
  186. package/src/services/UserPreference.ts +509 -0
  187. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  188. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  189. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  190. package/src/services/agent/types.ts +1 -1
  191. package/src/services/ai-docs/AGENTS.md +386 -0
  192. package/src/services/ai-docs/services-spec.md +492 -0
  193. package/src/services/config/Util.ts +10 -2
  194. package/src/services/config/ai-docs/AGENTS.md +255 -0
  195. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  196. package/src/services/config/ai-docs/config-spec.md +669 -0
  197. package/src/services/config/constants.ts +37 -1
  198. package/src/services/config/index.ts +45 -1
  199. package/src/services/config/types.ts +241 -11
  200. package/src/services/constants.ts +29 -0
  201. package/src/services/core/Err.ts +3 -0
  202. package/src/services/core/Utils.ts +143 -30
  203. package/src/services/core/ai-docs/AGENTS.md +381 -0
  204. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  205. package/src/services/core/ai-docs/core-spec.md +783 -0
  206. package/src/services/core/aqm-reqs.ts +100 -22
  207. package/src/services/core/websocket/WebSocketManager.ts +23 -6
  208. package/src/services/core/websocket/connection-service.ts +5 -1
  209. package/src/services/core/websocket/types.ts +1 -1
  210. package/src/services/index.ts +4 -0
  211. package/src/services/task/Task.ts +837 -0
  212. package/src/services/task/TaskFactory.ts +55 -0
  213. package/src/services/task/TaskManager.ts +793 -521
  214. package/src/services/task/TaskUtils.ts +314 -24
  215. package/src/services/task/ai-docs/AGENTS.md +457 -0
  216. package/src/services/task/ai-docs/ARCHITECTURE.md +594 -0
  217. package/src/services/task/ai-docs/task-spec.md +1319 -0
  218. package/src/services/task/constants.ts +23 -0
  219. package/src/services/task/dialer.ts +136 -1
  220. package/src/services/task/digital/Digital.ts +95 -0
  221. package/src/services/task/state-machine/TaskStateMachine.ts +1166 -0
  222. package/src/services/task/state-machine/actions.ts +738 -0
  223. package/src/services/task/state-machine/ai-docs/AGENTS.md +458 -0
  224. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1137 -0
  225. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2177 -0
  226. package/src/services/task/state-machine/constants.ts +172 -0
  227. package/src/services/task/state-machine/guards.ts +445 -0
  228. package/src/services/task/state-machine/index.ts +28 -0
  229. package/src/services/task/state-machine/types.ts +243 -0
  230. package/src/services/task/state-machine/uiControlsComputer.ts +961 -0
  231. package/src/services/task/taskDataNormalizer.ts +137 -0
  232. package/src/services/task/types.ts +734 -71
  233. package/src/services/task/voice/Voice.ts +1270 -0
  234. package/src/services/task/voice/WebRTC.ts +187 -0
  235. package/src/types.ts +205 -2
  236. package/src/utils/AGENTS.md +278 -0
  237. package/src/utils/ai-docs/utils-spec.md +381 -0
  238. package/src/webex.js +2 -0
  239. package/test/unit/spec/cc.ts +503 -43
  240. package/test/unit/spec/logger-proxy.ts +70 -0
  241. package/test/unit/spec/services/ApiAiAssistant.ts +273 -0
  242. package/test/unit/spec/services/UserPreference.ts +401 -0
  243. package/test/unit/spec/services/WebCallingService.ts +7 -1
  244. package/test/unit/spec/services/config/index.ts +85 -29
  245. package/test/unit/spec/services/core/Utils.ts +481 -2
  246. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +137 -41
  247. package/test/unit/spec/services/core/websocket/connection-service.ts +3 -1
  248. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  249. package/test/unit/spec/services/task/Task.ts +477 -0
  250. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  251. package/test/unit/spec/services/task/TaskManager.ts +1001 -1003
  252. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  253. package/test/unit/spec/services/task/dialer.ts +372 -96
  254. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  255. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +2651 -0
  256. package/test/unit/spec/services/task/state-machine/guards.ts +637 -0
  257. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  258. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2663 -0
  259. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  260. package/test/unit/spec/services/task/voice/Voice.ts +649 -0
  261. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  262. package/umd/contact-center.min.js +2 -2
  263. package/umd/contact-center.min.js.map +1 -1
  264. package/dist/services/task/index.js +0 -1525
  265. package/dist/services/task/index.js.map +0 -1
  266. package/src/services/task/index.ts +0 -1801
  267. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,2177 @@
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
+ // True when this agent initiated the conference transfer (widgets or desktop).
521
+ // Mirrors determineConsultInitiator — consultingAgentId === self only (not consultState).
522
+ isSelfConferenceTransferInitiator(context, event) {
523
+ if (context.transferConferenceRequested === true) return true;
524
+ if (context.consultInitiator === true) return true;
525
+
526
+ const taskData = event.taskData;
527
+ const selfAgentId = getSelfAgentId(context, taskData);
528
+ if (!selfAgentId || !taskData) return false;
529
+
530
+ return taskData.consultingAgentId === selfAgentId;
531
+ }
532
+
533
+ // Passive observer: another agent transferred; refresh data only.
534
+ isPassiveConferenceTransferObserver(context, event) {
535
+ if (isSelfConferenceTransferInitiator(context, event)) return false;
536
+
537
+ const taskData = event.taskData;
538
+ const selfAgentId = getSelfAgentId(context, taskData);
539
+ if (selfAgentId && taskData?.interaction?.participants) {
540
+ if (!(selfAgentId in taskData.interaction.participants)) return false;
541
+ if (taskData.interaction.participants[selfAgentId]?.hasLeft === true) return false;
542
+ }
543
+ return true;
544
+ }
545
+ ```
546
+
547
+ ```typescript
548
+ // Check if primary media leg is on hold
549
+ isPrimaryMediaOnHold(context, event) {
550
+ const taskData = event.taskData;
551
+ if (!taskData || !taskData.mediaResourceId) return false;
552
+
553
+ return taskData.interaction?.media?.[taskData.mediaResourceId]?.isHold === true;
554
+ }
555
+ ```
556
+
557
+ Actions are side effects executed during state machine transitions from current state to target state(next state).
558
+ Actions contain:
559
+
560
+ - Context synchronization (`initializeTask`, `updateTaskData`, `syncTaskDataFromEvent`)
561
+
562
+ - Lifecycle mutations (`clearConsultState`, `markEnded`, consult/conference flags)
563
+
564
+ - Integration hooks (`requestAutoAnswer`, `requestCleanup`, emitter placeholders)
565
+
566
+ - Context mutations should be centralized in `assign(...)` actions
567
+
568
+ - Emitter actions intentionally no-op defaults and overridden by `Task` to bridge machine transitions to SDK events.
569
+
570
+ - Deterministic updates from `taskData`.
571
+
572
+ ```typescript
573
+ // Initialize context for incoming task
574
+ initializeTask(context, event) {
575
+ return {
576
+ consultInitiator: false,
577
+ exitingConference: false,
578
+ consultDestinationType: null,
579
+ consultDestinationAgentJoined: false,
580
+ ...deriveTaskDataUpdates(context, event.taskData),
581
+ };
582
+ }
583
+
584
+ // Update taskData + derived recording/consult fields
585
+ updateTaskData(context, event) {
586
+ return deriveTaskDataUpdates(context, event.taskData);
587
+ }
588
+
589
+ // Keep Task instance data in sync (Task.ts action override)
590
+ syncTaskDataFromEvent(event) {
591
+ this.updateTaskFromEvent(event);
592
+ }
593
+
594
+ // Update hold flag on specific media leg in context.taskData.interaction.media
595
+ setHoldState(context, event) {
596
+ // Handles HOLD_SUCCESS and UNHOLD_SUCCESS for event.mediaResourceId
597
+ }
598
+
599
+ // Conference/consult lifecycle mutators
600
+ handleConferenceStarted() { return {consultInitiator: false}; }
601
+ handleConsultFailed() { return {consultDestinationAgentJoined: false, consultInitiator: false}; }
602
+ handleParticipantLeft(event) { return event.taskData ? {taskData: event.taskData} : {}; }
603
+ handleTransferConferenceSuccess(event) { return event.taskData ? {taskData: event.taskData} : {}; }
604
+
605
+ // Consult destination and mode flags
606
+ setConsultDestination(event) { /* sets consultDestinationType and resets consult flags */ }
607
+ setConsultFromConference() { return {consultFromConference: true}; }
608
+ setConsultAgentJoined(event) { /* sets consultDestinationAgentJoined on CONSULTING_ACTIVE */ }
609
+ setExitingConference() { return {exitingConference: true}; }
610
+
611
+ // Conference transfer flags
612
+ setTransferConferenceRequested() { return {transferConferenceRequested: true}; }
613
+ clearTransferConferenceRequested() { return {transferConferenceRequested: false}; }
614
+
615
+ // Consult call hold flags
616
+ setConsultCallHeld() { return {consultCallHeld: true}; }
617
+ clearConsultCallHeld() { return {consultCallHeld: false}; }
618
+
619
+ // Recording state mutator for pause/resume events
620
+ setRecordingState(event) {
621
+ // PAUSE_RECORDING => recordingInProgress false
622
+ // RESUME_RECORDING => recordingInProgress true
623
+ }
624
+
625
+ // Reset consult/conference-related context
626
+ clearConsultState() {
627
+ return {
628
+ consultDestinationType: null,
629
+ consultDestinationAgentJoined: false,
630
+ consultInitiator: false,
631
+ exitingConference: false,
632
+ consultCallHeld: false,
633
+ consultFromConference: false,
634
+ transferConferenceRequested: false,
635
+ };
636
+ }
637
+
638
+ // End-of-task cleanup for recording flags
639
+ markEnded() {
640
+ return {recordingControlsAvailable: false, recordingInProgress: false};
641
+ }
642
+ ```
643
+
644
+ > `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.
645
+
646
+ ```typescript
647
+ // Emit task incoming
648
+ emitTaskIncoming(context, event) {
649
+ task.emit(TASK_EVENTS.TASK_INCOMING, task);
650
+ }
651
+
652
+ // Emit task assigned
653
+ emitTaskAssigned(context, event) {
654
+ task.emit(TASK_EVENTS.TASK_ASSIGNED, task);
655
+ }
656
+
657
+ // Emit task hold
658
+ emitTaskHold(context, event) {
659
+ task.emit(TASK_EVENTS.TASK_HOLD, task);
660
+ }
661
+
662
+ // Emit task wrapup
663
+ emitTaskWrapup(context, event) {
664
+ if (context.taskData.wrapUpRequired) {
665
+ task.emit(TASK_EVENTS.TASK_WRAPUP, task);
666
+ }
667
+ }
668
+
669
+ // ... more emission actions for each event type
670
+ ```
671
+
672
+ ```typescript
673
+ // NOTE: These are no-op placeholders in actions.ts and are overridden in Task.ts.
674
+
675
+ // Request cleanup (remove from collection, keep task object)
676
+ requestCleanup(context, event) {
677
+ task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection: false});
678
+ }
679
+
680
+ // Cleanup resources (remove from collection)
681
+ cleanupResources(context, event) {
682
+ task.emit(TASK_EVENTS.TASK_CLEANUP, task, {removeFromCollection: true});
683
+ }
684
+ ```
685
+
686
+ ```typescript
687
+ // NOTE: requestAutoAnswer is a placeholder in actions.ts and is overridden in Task.ts.
688
+
689
+ // Request auto-answer
690
+ requestAutoAnswer(context, event) {
691
+ if (event.taskData?.isAutoAnswering) {
692
+ // Trigger accept() method
693
+ autoAnswerIfNeeded();
694
+ }
695
+ }
696
+ ```
697
+
698
+ `uiControlsComputer.ts` computes `TaskUIControls` from:
699
+
700
+ - current machine state
701
+
702
+ - current context
703
+
704
+ - channel type (voice vs digital)
705
+
706
+ - call/participant metadata from `taskData`
707
+
708
+ - config flags (`isEndTaskEnabled`, recording toggles, voice variant)
709
+
710
+ This keeps all control enablement/visibility logic centralized and testable.
711
+
712
+ - `TaskState`
713
+
714
+ - `TaskContext` (including `taskData`)
715
+
716
+ - `UIControlConfig` (channel type, agentId, voice variant, recording flags)
717
+
718
+ - `TaskUIControls` with per-control visibility and enabled state.
719
+
720
+ 1. Add event in `TaskEvent` (`constants.ts`)
721
+
722
+ 2. Add typed payload in `TaskEventPayloadMap` (`types.ts`)
723
+
724
+ 3. Wire transitions in `TaskStateMachine.ts`
725
+
726
+ 4. Add/adjust actions in `actions.ts`
727
+
728
+ 5. Add guard(s) in `guards.ts` if needed
729
+
730
+ 6. Update `TaskManager` event mapping and unit tests
731
+
732
+ 1. Implement pure guard in `guards.ts`
733
+
734
+ 2. Use guard in `TaskStateMachine.ts` transition array
735
+
736
+ 3. Keep side-effects in actions only (no side-effects in guards)
737
+
738
+ 4. Add tests for positive and negative transition paths
739
+
740
+ 1. Update control logic in `computeVoiceInteractionUIControls()` or `computeDigitalInteractionUIControls()`
741
+
742
+ 2. Preserve `getDefaultUIControls()` shape compatibility
743
+
744
+ 3. Verify behavior across `CONNECTED`, `HELD`, `CONSULTING`, `CONFERENCING`, `WRAPPING_UP`
745
+
746
+ 4. Add or update UI-control unit coverage
747
+
748
+ 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.
749
+
750
+ The task state machine is built with `xstate` and organized into:
751
+
752
+ - **State graph** (`TaskStateMachine.ts`)
753
+
754
+ - **Context mutators** (`actions.ts`)
755
+
756
+ - **Guard predicates** (`guards.ts`)
757
+
758
+ - **UI control derivation** (`uiControlsComputer.ts`)
759
+
760
+ - **Event/context contracts** (`types.ts`)
761
+
762
+ It is instantiated by `Task` and receives mapped backend/user events through `sendStateMachineEvent(...)`.
763
+
764
+ `Task` bootstraps and owns the actor lifecycle:
765
+
766
+ 1. `createTaskStateMachine(uiControlConfig, {actions: overrides})`
767
+
768
+ 2. `createActor(machine).start()`
769
+
770
+ 3. `TaskManager` and task APIs map external signals to `TaskEvent`
771
+
772
+ 4. Actor transitions update context and execute action overrides
773
+
774
+ 5. `Task` recomputes UI controls and emits task-level events
775
+
776
+ **Description**: Initial state before a task is offered or restored.
777
+
778
+ **How this state is reached (incoming transitions)**:
779
+
780
+ - Machine start -> `IDLE` (no event, no actions)
781
+
782
+ **Valid transitions from `IDLE` state**:
783
+
784
+ - `TASK_INCOMING` -> `OFFERED`
785
+
786
+ - Guard: none
787
+
788
+ - Actions: `initializeTask`, `emitTaskIncoming`
789
+
790
+ - `HYDRATE` -> `WRAPPING_UP`
791
+
792
+ - Guard: `guards.isInteractionTerminated`
793
+
794
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskHydrate`
795
+
796
+ - `HYDRATE` -> `CONSULTING`
797
+
798
+ - Guard: `guards.isInteractionConsulting`
799
+
800
+ - Actions: `updateTaskData`, `emitTaskHydrate`
801
+
802
+ - `HYDRATE` -> `HELD`
803
+
804
+ - Guard: `guards.isInteractionHeld`
805
+
806
+ - Actions: `updateTaskData`, `emitTaskHydrate`
807
+
808
+ - `HYDRATE` -> `CONNECTED`
809
+
810
+ - Guard: `guards.isInteractionConnected`
811
+
812
+ - Actions: `updateTaskData`, `emitTaskHydrate`
813
+
814
+ - `HYDRATE` -> `CONFERENCING`
815
+
816
+ - Guard: `guards.isConferencingByParticipants`
817
+
818
+ - Actions: `updateTaskData`, `emitTaskHydrate`
819
+
820
+ - `HYDRATE` -> stay `IDLE` (default hydrate branch)
821
+
822
+ - Guard: default
823
+
824
+ - Actions: `updateTaskData`, `emitTaskHydrate`
825
+
826
+ **Description**: Task has been offered/reserved and is waiting for assignment or termination paths.
827
+
828
+ **How this state is reached (incoming transitions)**:
829
+
830
+ - `IDLE --TASK_INCOMING--> OFFERED`
831
+
832
+ - Guard: none
833
+
834
+ - Actions: `initializeTask`, `emitTaskIncoming`
835
+
836
+ **Valid transitions from `OFFERED`**:
837
+
838
+ - `TASK_OFFERED` -> Stay `OFFERED`
839
+
840
+ - Guard: none
841
+
842
+ - Actions: `updateTaskData`, `emitTaskOfferContact`, `requestAutoAnswer`
843
+
844
+ - `OFFER_CONSULT` -> Stay `OFFERED`
845
+
846
+ - Guard: none
847
+
848
+ - Actions: `updateTaskData`, `emitTaskOfferConsult`, `requestAutoAnswer`
849
+
850
+ - `ASSIGN` -> `CONNECTED`
851
+
852
+ - Guard: none
853
+
854
+ - Actions: `updateTaskData`, `emitTaskAssigned`
855
+
856
+ - `CONSULTING_ACTIVE` -> `CONSULTING`
857
+
858
+ - Guard: none
859
+
860
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsultAccepted`, `emitTaskConsulting`
861
+
862
+ - `TASK_WRAPUP` -> `TERMINATED`
863
+
864
+ - Guard: none
865
+
866
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskEnd`
867
+
868
+ - `RONA` / `ASSIGN_FAILED` / `INVITE_FAILED` / `OUTBOUND_FAILED` -> `TERMINATED`
869
+
870
+ - Guard: none
871
+
872
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskReject`
873
+
874
+ **Description**: Agent is connected on the main customer interaction leg.
875
+
876
+ **How this state is reached (incoming transitions)**:
877
+
878
+ - `OFFERED --ASSIGN--> CONNECTED`
879
+
880
+ - Guard: none
881
+
882
+ - Actions: `updateTaskData`, `emitTaskAssigned`
883
+
884
+ - `IDLE --HYDRATE--> CONNECTED`
885
+
886
+ - Guard: `guards.isInteractionConnected`
887
+
888
+ - Actions: `updateTaskData`, `emitTaskHydrate`
889
+
890
+ - `RESUME_INITIATING --UNHOLD_SUCCESS--> CONNECTED`
891
+
892
+ - Guard: none
893
+
894
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskResume`
895
+
896
+ - `HELD --TRANSFER_SUCCESS--> CONNECTED` (receiver/default branch)
897
+
898
+ - Guard: default branch when `guards.shouldWrapUpOrIsInitiator` is false
899
+
900
+ - Actions: `updateTaskData`, `clearConsultState`
901
+
902
+ - `CONSULTING --ASSIGN--> CONNECTED`
903
+
904
+ - Guard: none
905
+
906
+ - Actions: `updateTaskData`, `emitTaskAssigned`
907
+
908
+ **Valid transitions from `CONNECTED`**:
909
+
910
+ - `ASSIGN` -> `CONNECTED` (self-transition)
911
+
912
+ - Guard: none
913
+
914
+ - Actions: `updateTaskData`, `emitTaskAssigned`
915
+
916
+ - `HOLD_INITIATED` -> `HOLD_INITIATING`
917
+
918
+ - Guard: none
919
+
920
+ - Actions: none
921
+
922
+ - `CONSULT` -> `CONSULT_INITIATING`
923
+
924
+ - Guard: none
925
+
926
+ - Actions: `setConsultInitiator`, `setConsultDestination`
927
+
928
+ - `CONSULTING_ACTIVE` -> `CONSULTING`
929
+
930
+ - Guard: none
931
+
932
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsultAccepted`, `emitTaskConsulting`
933
+
934
+ - `TRANSFER_SUCCESS` -> `WRAPPING_UP`
935
+
936
+ - Guard: `guards.shouldWrapUpOrIsInitiator`
937
+
938
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
939
+
940
+ - `TRANSFER_SUCCESS` -> Stay `CONNECTED` (receiver/default branch)
941
+
942
+ - Guard: default
943
+
944
+ - Actions: `updateTaskData`, `clearConsultState`
945
+
946
+ - `TRANSFER_FAILED` -> Stay `CONNECTED`
947
+
948
+ - Guard: none
949
+
950
+ - Actions: `updateTaskData`
951
+
952
+ - `CONTACT_ENDED` -> `CONFERENCING`
953
+
954
+ - Guard: `guards.conferenceInProgressFromEvent`
955
+
956
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
957
+
958
+ - `CONTACT_ENDED` -> `WRAPPING_UP`
959
+
960
+ - Guard: `guards.shouldWrapUp`
961
+
962
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`, `requestCleanup`
963
+
964
+ - `CONTACT_ENDED` -> `TERMINATED` (default branch)
965
+
966
+ - Guard: default
967
+
968
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskEnd`
969
+
970
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
971
+
972
+ - Guard: none
973
+
974
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
975
+
976
+ - `PAUSE_RECORDING` / `RESUME_RECORDING` -> Stay `CONNECTED`
977
+
978
+ - Guard: none
979
+
980
+ - Actions: `updateTaskData`, `setRecordingState`, `emitTaskRecordingPaused` / `emitTaskRecordingResumed`
981
+
982
+ **Description**: Main call is on hold.
983
+
984
+ **How this state is reached (incoming transitions)**:
985
+
986
+ - `IDLE --HYDRATE--> HELD`
987
+
988
+ - Guard: `guards.isInteractionHeld`
989
+
990
+ - Actions: `updateTaskData`, `emitTaskHydrate`
991
+
992
+ - `HOLD_INITIATING --HOLD_SUCCESS--> HELD`
993
+
994
+ - Guard: none
995
+
996
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold`
997
+
998
+ - `RESUME_INITIATING --UNHOLD_FAILED--> HELD`
999
+
1000
+ - Guard: none
1001
+
1002
+ - Actions: none
1003
+
1004
+ - `CONSULTING --CONSULT_END--> HELD`
1005
+
1006
+ - Guard: inline `context.consultInitiator === true`
1007
+
1008
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
1009
+
1010
+ - `CONSULT_INITIATING --CONSULT_FAILED--> HELD`
1011
+
1012
+ - Guard: `guards.isPrimaryMediaOnHold`
1013
+
1014
+ - Actions: `updateTaskData`, `handleConsultFailed`
1015
+
1016
+ - `CONSULT_INITIATING --CTQ_CANCEL--> HELD`
1017
+
1018
+ - Guard: `guards.isPrimaryMediaOnHold`
1019
+
1020
+ - Actions: `updateTaskData`, `clearConsultState`
1021
+
1022
+ **Valid transitions from `HELD`**:
1023
+
1024
+ - `UNHOLD_INITIATED` -> `RESUME_INITIATING`
1025
+
1026
+ - Guard: none
1027
+
1028
+ - Actions: none
1029
+
1030
+ - `CONSULT` -> `CONSULT_INITIATING`
1031
+
1032
+ - Guard: none
1033
+
1034
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1035
+
1036
+ - `TRANSFER_SUCCESS` -> `WRAPPING_UP`
1037
+
1038
+ - Guard: `guards.shouldWrapUpOrIsInitiator`
1039
+
1040
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
1041
+
1042
+ - `TRANSFER_SUCCESS` -> `CONNECTED` (receiver/default branch)
1043
+
1044
+ - Guard: default
1045
+
1046
+ - Actions: `updateTaskData`, `clearConsultState`
1047
+
1048
+ - `TRANSFER_FAILED` -> stay `HELD`
1049
+
1050
+ - Guard: none
1051
+
1052
+ - Actions: `updateTaskData`
1053
+
1054
+ - `CONTACT_ENDED` -> `CONFERENCING`
1055
+
1056
+ - Guard: `guards.conferenceInProgressFromEvent`
1057
+
1058
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1059
+
1060
+ - `CONTACT_ENDED` -> `WRAPPING_UP`
1061
+
1062
+ - Guard: `guards.shouldWrapUp`
1063
+
1064
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`, `requestCleanup`
1065
+
1066
+ - `CONTACT_ENDED` -> `TERMINATED` (default branch)
1067
+
1068
+ - Guard: default
1069
+
1070
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskEnd`
1071
+
1072
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1073
+
1074
+ - Guard: none
1075
+
1076
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
1077
+
1078
+ **Description**: Hold request has been sent and is awaiting backend confirmation.
1079
+
1080
+ **How this state is reached (incoming transitions)**:
1081
+
1082
+ - `CONNECTED --HOLD_INITIATED--> HOLD_INITIATING`
1083
+
1084
+ - Guard: none
1085
+
1086
+ - Actions: none
1087
+
1088
+ **Valid transitions from `HOLD_INITIATING`**:
1089
+
1090
+ - `HOLD_SUCCESS` -> `HELD`
1091
+
1092
+ - Guard: none
1093
+
1094
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold`
1095
+
1096
+ - `HOLD_FAILED` -> `CONNECTED`
1097
+
1098
+ - Guard: none
1099
+
1100
+ - Actions: `updateTaskData`
1101
+
1102
+ **Description**: Resume/unhold request has been sent and is awaiting backend confirmation.
1103
+
1104
+ **How this state is reached (incoming transitions)**:
1105
+
1106
+ - `HELD --UNHOLD_INITIATED--> RESUME_INITIATING`
1107
+
1108
+ - Guard: none
1109
+
1110
+ - Actions: none
1111
+
1112
+ **Valid transitions from `RESUME_INITIATING`**:
1113
+
1114
+ - `UNHOLD_SUCCESS` -> `CONNECTED`
1115
+
1116
+ - Guard: none
1117
+
1118
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskResume`
1119
+
1120
+ - `UNHOLD_FAILED` -> `HELD`
1121
+
1122
+ - Guard: none
1123
+
1124
+ - Actions: none
1125
+
1126
+ **Description**: Consult request is in-flight.
1127
+
1128
+ **How this state is reached (incoming transitions)**:
1129
+
1130
+ - `CONNECTED --CONSULT--> CONSULT_INITIATING`
1131
+
1132
+ - Guard: none
1133
+
1134
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1135
+
1136
+ - `HELD --CONSULT--> CONSULT_INITIATING`
1137
+
1138
+ - Guard: none
1139
+
1140
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1141
+
1142
+ - `CONFERENCING --CONSULT--> CONSULT_INITIATING`
1143
+
1144
+ - Guard: none
1145
+
1146
+ - Actions: `setConsultInitiator`, `setConsultDestination`, `setConsultFromConference`
1147
+
1148
+ **Valid transitions from `CONSULT_INITIATING`**:
1149
+
1150
+ - `CONSULT_SUCCESS` -> `CONSULTING`
1151
+
1152
+ - Guard: none
1153
+
1154
+ - Actions: `updateTaskData`, `setConsultInitiator`
1155
+
1156
+ - `CONSULT_FAILED` -> `CONFERENCING`
1157
+
1158
+ - Guard: inline `context.consultFromConference === true`
1159
+
1160
+ - Actions: `updateTaskData`, `handleConsultFailed`
1161
+
1162
+ - `CONSULT_FAILED` -> `HELD`
1163
+
1164
+ - Guard: `guards.isPrimaryMediaOnHold`
1165
+
1166
+ - Actions: `updateTaskData`, `handleConsultFailed`
1167
+
1168
+ - `CONSULT_FAILED` -> `CONNECTED` (default branch)
1169
+
1170
+ - Guard: default
1171
+
1172
+ - Actions: `updateTaskData`, `handleConsultFailed`
1173
+
1174
+ - `CTQ_CANCEL` -> `HELD`
1175
+
1176
+ - Guard: `guards.isPrimaryMediaOnHold`
1177
+
1178
+ - Actions: `updateTaskData`, `clearConsultState`
1179
+
1180
+ - `CTQ_CANCEL` -> `CONNECTED` (default branch)
1181
+
1182
+ - Guard: default
1183
+
1184
+ - Actions: `updateTaskData`, `clearConsultState`
1185
+
1186
+ - `HOLD_SUCCESS` -> stay `CONSULT_INITIATING`
1187
+
1188
+ - Guard: none
1189
+
1190
+ - Actions: `updateTaskData`
1191
+
1192
+ - `HOLD_FAILED` -> `CONNECTED`
1193
+
1194
+ - Guard: none
1195
+
1196
+ - Actions: `updateTaskData`, `handleConsultFailed`
1197
+
1198
+ **Description**: Agent is in active consult leg.
1199
+
1200
+ **How this state is reached (incoming transitions)**:
1201
+
1202
+ - `IDLE --HYDRATE--> CONSULTING`
1203
+
1204
+ - Guard: `guards.isInteractionConsulting`
1205
+
1206
+ - Actions: `updateTaskData`, `emitTaskHydrate`
1207
+
1208
+ - `OFFERED --CONSULTING_ACTIVE--> CONSULTING`
1209
+
1210
+ - Guard: none
1211
+
1212
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsultAccepted`, `emitTaskConsulting`
1213
+
1214
+ - `CONSULT_INITIATING --CONSULT_SUCCESS--> CONSULTING`
1215
+
1216
+ - Guard: none
1217
+
1218
+ - Actions: `updateTaskData`, `setConsultInitiator`
1219
+
1220
+ - `CONF_INITIATING --CONFERENCE_FAILED--> CONSULTING`
1221
+
1222
+ - Guard: none
1223
+
1224
+ - Actions: none
1225
+
1226
+ **Valid transitions from `CONSULTING`**:
1227
+
1228
+ - `CONSULTING_ACTIVE` -> stay `CONSULTING`
1229
+
1230
+ - Guard: none
1231
+
1232
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsulting`
1233
+
1234
+ - `CONSULT_END` -> `CONFERENCING`
1235
+
1236
+ - Guard: inline `context.consultInitiator === true && context.consultFromConference === true`
1237
+
1238
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
1239
+
1240
+ - `CONSULT_END` -> `HELD`
1241
+
1242
+ - Guard: inline `context.consultInitiator === true`
1243
+
1244
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
1245
+
1246
+ - `CONSULT_END` -> `TERMINATED` (default branch)
1247
+
1248
+ - Guard: default
1249
+
1250
+ - Actions: `updateTaskData`
1251
+
1252
+ - `HOLD_SUCCESS` -> stay `CONSULTING`
1253
+
1254
+ - Guard: none
1255
+
1256
+ - Actions: `updateTaskData`, `setHoldState`, `setConsultCallHeld`
1257
+
1258
+ - `UNHOLD_SUCCESS` -> stay `CONSULTING`
1259
+
1260
+ - Guard: none
1261
+
1262
+ - Actions: `updateTaskData`, `setHoldState`, `clearConsultCallHeld`
1263
+
1264
+ - `TRANSFER_SUCCESS` -> `WRAPPING_UP`
1265
+
1266
+ - Guard: `guards.shouldWrapUpOrIsInitiator`
1267
+
1268
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
1269
+
1270
+ - `TRANSFER_SUCCESS` -> `CONNECTED` (receiver/default branch)
1271
+
1272
+ - Guard: default
1273
+
1274
+ - Actions: `updateTaskData`, `clearConsultState`
1275
+
1276
+ - `TRANSFER_FAILED` -> stay `CONSULTING`
1277
+
1278
+ - Guard: none
1279
+
1280
+ - Actions: `updateTaskData`
1281
+
1282
+ - `TRANSFER_CONFERENCE` -> stay `CONSULTING`
1283
+
1284
+ - Guard: none
1285
+
1286
+ - Actions: `setTransferConferenceRequested`, `emitTaskTransferConference`
1287
+
1288
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `WRAPPING_UP`
1289
+
1290
+ - Guard: `guards.shouldWrapUp`
1291
+
1292
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`, `emitTaskWrapup`
1293
+
1294
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `CONFERENCING`
1295
+
1296
+ - Guard: `!guards.isSelfConferenceTransferInitiator`
1297
+
1298
+ - Actions: `updateTaskData`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1299
+
1300
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `TERMINATED` (default branch)
1301
+
1302
+ - Guard: default
1303
+
1304
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`, `emitTaskEnd`
1305
+
1306
+ - `TRANSFER_CONFERENCE_FAILED` -> stay `CONSULTING`
1307
+
1308
+ - Guard: none
1309
+
1310
+ - Actions: `clearTransferConferenceRequested`
1311
+
1312
+ - `PARTICIPANT_LEAVE` -> `WRAPPING_UP`
1313
+
1314
+ - Guard: `guards.didCurrentAgentLeaveConference && guards.shouldWrapUp`
1315
+
1316
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskWrapup`
1317
+
1318
+ - `PARTICIPANT_LEAVE` -> `TERMINATED`
1319
+
1320
+ - Guard: `guards.didCurrentAgentLeaveConference`
1321
+
1322
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskEnd`
1323
+
1324
+ - `PARTICIPANT_LEAVE` -> `CONNECTED`
1325
+
1326
+ - Guard: `!guards.didCurrentAgentLeaveConference && guards.shouldDowngradeConferenceToConnected`
1327
+
1328
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskConferenceEnded`
1329
+
1330
+ - `PARTICIPANT_LEAVE` -> stay `CONSULTING` (default)
1331
+
1332
+ - Guard: default
1333
+
1334
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `emitTaskParticipantLeft`
1335
+
1336
+ - `ASSIGN` -> `CONNECTED`
1337
+
1338
+ - Guard: none
1339
+
1340
+ - Actions: `updateTaskData`, `emitTaskAssigned`
1341
+
1342
+ - `CONTACT_ENDED` -> `WRAPPING_UP`
1343
+
1344
+ - Guard: none
1345
+
1346
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskWrapup`, `requestCleanup`
1347
+
1348
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1349
+
1350
+ - Guard: none
1351
+
1352
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskWrapup`
1353
+
1354
+ - `MERGE_TO_CONFERENCE` -> `CONF_INITIATING`
1355
+
1356
+ - Guard: none
1357
+
1358
+ - Actions: none
1359
+
1360
+ - `CONFERENCE_START` -> `CONFERENCING`
1361
+
1362
+ - Guard: none
1363
+
1364
+ - Actions: `handleConferenceStarted`, `clearConsultState`
1365
+
1366
+ **Description**: Conference merge is being established.
1367
+
1368
+ **How this state is reached (incoming transitions)**:
1369
+
1370
+ - `CONSULTING --MERGE_TO_CONFERENCE--> CONF_INITIATING`
1371
+
1372
+ - Guard: none
1373
+
1374
+ - Actions: none
1375
+
1376
+ **Valid transitions from `CONF_INITIATING`**:
1377
+
1378
+ - `CONFERENCE_START` -> `CONFERENCING`
1379
+
1380
+ - Guard: none
1381
+
1382
+ - Actions: `handleConferenceStarted`
1383
+
1384
+ - `CONFERENCE_FAILED` -> `CONSULTING`
1385
+
1386
+ - Guard: none
1387
+
1388
+ - Actions: none
1389
+
1390
+ **Description**: Active conference call state.
1391
+
1392
+ **How this state is reached (incoming transitions)**:
1393
+
1394
+ - `IDLE --HYDRATE--> CONFERENCING`
1395
+
1396
+ - Guard: `guards.isConferencingByParticipants`
1397
+
1398
+ - Actions: `updateTaskData`, `emitTaskHydrate`
1399
+
1400
+ - `CONNECTED --CONTACT_ENDED--> CONFERENCING`
1401
+
1402
+ - Guard: `guards.conferenceInProgressFromEvent`
1403
+
1404
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1405
+
1406
+ - `HELD --CONTACT_ENDED--> CONFERENCING`
1407
+
1408
+ - Guard: `guards.conferenceInProgressFromEvent`
1409
+
1410
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1411
+
1412
+ - `CONSULTING --CONFERENCE_START--> CONFERENCING`
1413
+
1414
+ - Guard: none
1415
+
1416
+ - Actions: `handleConferenceStarted`, `clearConsultState`
1417
+
1418
+ - `CONF_INITIATING --CONFERENCE_START--> CONFERENCING`
1419
+
1420
+ - Guard: none
1421
+
1422
+ - Actions: `handleConferenceStarted`
1423
+
1424
+ - `CONSULTING --TRANSFER_CONFERENCE_SUCCESS--> CONFERENCING`
1425
+
1426
+ - Guard: `!guards.isSelfConferenceTransferInitiator`
1427
+
1428
+ - Actions: `updateTaskData`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1429
+
1430
+ **Valid transitions from `CONFERENCING`**:
1431
+
1432
+ - `CONSULT` -> `CONSULT_INITIATING`
1433
+
1434
+ - Guard: none
1435
+
1436
+ - Actions: `setConsultInitiator`, `setConsultDestination`, `setConsultFromConference`
1437
+
1438
+ - `CONFERENCE_START` -> stay `CONFERENCING`
1439
+
1440
+ - Guard: none
1441
+
1442
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConferenceStarted`
1443
+
1444
+ - `CONSULT_END` -> stay `CONFERENCING`
1445
+
1446
+ - Guard: none
1447
+
1448
+ - Actions: `updateTaskData`, `clearConsultState`
1449
+
1450
+ - `HOLD_SUCCESS` / `UNHOLD_SUCCESS` -> stay `CONFERENCING`
1451
+
1452
+ - Guard: none
1453
+
1454
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold` / `emitTaskResume`
1455
+
1456
+ - `TRANSFER_CONFERENCE` -> stay `CONFERENCING`
1457
+
1458
+ - Guard: none
1459
+
1460
+ - Actions: `setTransferConferenceRequested`, `emitTaskTransferConference`
1461
+
1462
+ - `TRANSFER_CONFERENCE_SUCCESS` -> stay `CONFERENCING`
1463
+
1464
+ - Guard: `guards.isPassiveConferenceTransferObserver`
1465
+
1466
+ - Actions: `updateTaskData`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1467
+
1468
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `WRAPPING_UP`
1469
+
1470
+ - Guard: `guards.shouldWrapUp`
1471
+
1472
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`, `emitTaskWrapup`
1473
+
1474
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `CONFERENCING`
1475
+
1476
+ - Guard: `!guards.isSelfConferenceTransferInitiator`
1477
+
1478
+ - Actions: `updateTaskData`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1479
+
1480
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `TERMINATED` (default branch)
1481
+
1482
+ - Guard: default
1483
+
1484
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`, `emitTaskEnd`
1485
+
1486
+ - `TRANSFER_CONFERENCE_FAILED` -> stay `CONFERENCING`
1487
+
1488
+ - Guard: none
1489
+
1490
+ - Actions: `clearTransferConferenceRequested`
1491
+
1492
+ - `PARTICIPANT_LEAVE` -> `WRAPPING_UP`
1493
+
1494
+ - Guard: `guards.didCurrentAgentLeaveConference && guards.shouldWrapUp`
1495
+
1496
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskWrapup`
1497
+
1498
+ - `PARTICIPANT_LEAVE` -> `TERMINATED`
1499
+
1500
+ - Guard: `guards.didCurrentAgentLeaveConference`
1501
+
1502
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskEnd`
1503
+
1504
+ - `PARTICIPANT_LEAVE` -> `CONNECTED`
1505
+
1506
+ - Guard: `guards.shouldDowngradeConferenceToConnected`
1507
+
1508
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskConferenceEnded`
1509
+
1510
+ - `PARTICIPANT_LEAVE` -> stay `CONFERENCING` (default)
1511
+
1512
+ - Guard: default
1513
+
1514
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `emitTaskParticipantLeft`
1515
+
1516
+ - `CONFERENCE_END` -> `WRAPPING_UP`
1517
+
1518
+ - Guard: `guards.shouldWrapUp`
1519
+
1520
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskWrapup`
1521
+
1522
+ - `CONFERENCE_END` -> `CONNECTED`
1523
+
1524
+ - Guard: inline `!context.exitingConference && customerInCall`
1525
+
1526
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConferenceEnded`
1527
+
1528
+ - `CONFERENCE_END` -> `TERMINATED` (default branch)
1529
+
1530
+ - Guard: default
1531
+
1532
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskEnd`
1533
+
1534
+ - `CONTACT_ENDED` -> stay `CONFERENCING`
1535
+
1536
+ - Guard: none
1537
+
1538
+ - Actions: `updateTaskData`, `requestCleanup`
1539
+
1540
+ **Description**: Post-interaction work (ACW) is in progress.
1541
+
1542
+ **How this state is reached (incoming transitions)**:
1543
+
1544
+ - Reached from `CONNECTED`, `HELD`, `CONSULTING`, or `CONFERENCING` via `CONTACT_ENDED`, `TASK_WRAPUP`, `TRANSFER_SUCCESS`, `TRANSFER_CONFERENCE_SUCCESS`, `PARTICIPANT_LEAVE`, or `CONFERENCE_END` branches
1545
+
1546
+ - Entry always emits wrapup event after transition
1547
+
1548
+ **Entry Actions**:
1549
+
1550
+ - `emitTaskWrapup`
1551
+
1552
+ **Valid transitions from `WRAPPING_UP`**:
1553
+
1554
+ - `WRAPUP_COMPLETE` -> `COMPLETED`
1555
+
1556
+ - Guard: none
1557
+
1558
+ - Actions: `updateTaskData`
1559
+
1560
+ **Guards**: None
1561
+
1562
+ **Description**: Final wrapped-up terminal state.
1563
+
1564
+ **Entry Actions**:
1565
+
1566
+ - `emitTaskWrappedup`
1567
+
1568
+ - `cleanupResources`
1569
+
1570
+ **How this state is reached (incoming transitions)**:
1571
+
1572
+ - `WRAPPING_UP --WRAPUP_COMPLETE--> COMPLETED`
1573
+
1574
+ - Guard: none
1575
+
1576
+ - Actions: `updateTaskData`
1577
+
1578
+ **Valid transitions from `COMPLETED`**: None (final state)
1579
+
1580
+ **Guards**: None
1581
+
1582
+ **Description**: Final terminated terminal state.
1583
+
1584
+ **Entry Actions**:
1585
+
1586
+ - `cleanupResources`
1587
+
1588
+ **How this state is reached (incoming transitions)**:
1589
+
1590
+ - Reached from `OFFERED`, `CONNECTED`, `HELD`, `CONSULTING`, and `CONFERENCING` via terminating branches (`TASK_WRAPUP`, failure paths, and default end-of-contact/conference branches)
1591
+
1592
+ **Valid transitions from `TERMINATED`**: None (final state)
1593
+
1594
+ **Guards**: None
1595
+
1596
+ Event names below are from `TaskEvent` in `constants.ts`.
1597
+
1598
+ - `TASK_INCOMING`, `TASK_OFFERED`, `HYDRATE`
1599
+
1600
+ - `CONTACT_UPDATED`, `CONTACT_OWNER_CHANGED`
1601
+
1602
+ - `ASSIGN`, `CONTACT_ENDED`, `TASK_WRAPUP`, `WRAPUP_COMPLETE`
1603
+
1604
+ - `HOLD_INITIATED`, `HOLD_SUCCESS`, `HOLD_FAILED`
1605
+
1606
+ - `UNHOLD_INITIATED`, `UNHOLD_SUCCESS`, `UNHOLD_FAILED`
1607
+
1608
+ - `OFFER_CONSULT`, `CONSULT`, `CONSULT_SUCCESS`, `CONSULT_CREATED`
1609
+
1610
+ - `CONSULTING_ACTIVE`, `CONSULT_END`, `CONSULT_FAILED`
1611
+
1612
+ - `CTQ_CANCEL`, `CTQ_CANCEL_FAILED`
1613
+
1614
+ - `MERGE_TO_CONFERENCE`, `CONFERENCE_START`, `CONFERENCE_FAILED`, `CONFERENCE_END`
1615
+
1616
+ - `PARTICIPANT_LEAVE`
1617
+
1618
+ - `TRANSFER_CONFERENCE`, `TRANSFER_CONFERENCE_SUCCESS`, `TRANSFER_CONFERENCE_FAILED`
1619
+
1620
+ - `EXIT_CONFERENCE`, `EXIT_CONFERENCE_SUCCESS`, `EXIT_CONFERENCE_FAILED`
1621
+
1622
+ - `TRANSFER_SUCCESS`, `TRANSFER_FAILED`
1623
+
1624
+ - `RECORDING_STARTED`, `PAUSE_RECORDING`, `RESUME_RECORDING`
1625
+
1626
+ The flow below shows a single transition in the requested form:
1627
+
1628
+ ```mermaid
1629
+ flowchart LR
1630
+ A[User Action/CC Event Mapping] --> B[State Machine Event Trigger]
1631
+ B --> C{Check against Current State: Valid Transition?}
1632
+ C -- No --> X[Ignore/No-op]
1633
+ C -- Yes --> D[StateMachine evaluates guards]
1634
+ D -- No --> Y[Blocked by Guard]
1635
+ D -- Yes --> E[Execute Associated Actions]
1636
+ E --> F[Context updated]
1637
+ F --> G[UI Controls Recomputed]
1638
+ G --> H[Transition to Target State]
1639
+ ```
1640
+
1641
+ ```mermaid
1642
+ flowchart LR
1643
+ A[User invoked hold API] --> B[Event Trigger: HOLD_INITIATED]
1644
+ B --> C{State = CONNECTED?}
1645
+ C -- No --> X[Ignore/No-op]
1646
+ C -- Yes --> D[Guards: none]
1647
+ D -- Yes --> E[Actions: setHoldInitiated + updateTaskData]
1648
+ E --> F[Context updated]
1649
+ F --> G[UI controls recomputed]
1650
+ G --> H[Transition: CONNECTED -> HOLD_INITIATING]
1651
+ H --> I[CC Event: AGENT_CONTACT_HELD]
1652
+ I --> J[Mapped: HOLD_SUCCESS]
1653
+ J --> K{State = HOLD_INITIATING?}
1654
+ K -- No --> X
1655
+ K -- Yes --> L[Guards: none]
1656
+ L -- Yes --> M[Actions: setHoldSuccess + updateTaskData]
1657
+ M --> N[Context updated]
1658
+ N --> O[UI controls recomputed]
1659
+ O --> P[Transition: HOLD_INITIATING -> HELD]
1660
+ ```
1661
+
1662
+ Manage complex task lifecycle with clear states, transitions, guards, and actions.
1663
+
1664
+ ```typescript
1665
+ // Task.ts
1666
+ export default abstract class Task extends EventEmitter {
1667
+ public stateMachineService?: ActorRefFrom<TaskStateMachine>;
1668
+
1669
+ private initializeStateMachine(): void {
1670
+ const machine: TaskStateMachine = createTaskStateMachine(this.uiControlConfig, {
1671
+ actions: this.getStateMachineActionOverrides(),
1672
+ });
1673
+
1674
+ this.stateMachineService = createActor(machine);
1675
+
1676
+ // Subscribe to state changes
1677
+ this.stateMachineService.subscribe((snapshot) => {
1678
+ const currentState = snapshot.value as TaskState;
1679
+ this.state = snapshot;
1680
+ this.updateUiControls(previousState !== currentState);
1681
+ });
1682
+
1683
+ this.stateMachineService.start();
1684
+ }
1685
+
1686
+ public sendStateMachineEvent(event: TaskEventPayload): void {
1687
+ this.stateMachineService?.send(event);
1688
+ }
1689
+ }
1690
+ ```
1691
+
1692
+ ```typescript
1693
+ // state-machine/TaskStateMachine.ts
1694
+ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
1695
+ return {
1696
+ id: 'taskStateMachine',
1697
+ initial: TaskState.IDLE,
1698
+ context: createInitialContext(uiControlConfig, TaskState.IDLE),
1699
+ states: {
1700
+ [TaskState.IDLE]: {
1701
+ on: {
1702
+ [TaskEvent.TASK_INCOMING]: {
1703
+ target: TaskState.OFFERED,
1704
+ actions: ['initializeTask', 'emitTaskIncoming'],
1705
+ },
1706
+ },
1707
+ },
1708
+ [TaskState.OFFERED]: {
1709
+ on: {
1710
+ [TaskEvent.ASSIGN]: {
1711
+ target: TaskState.CONNECTED,
1712
+ actions: ['updateTaskData', 'emitTaskAssigned'],
1713
+ },
1714
+ [TaskEvent.TASK_WRAPUP]: {
1715
+ target: TaskState.TERMINATED,
1716
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
1717
+ },
1718
+ },
1719
+ },
1720
+ // ... more states
1721
+ },
1722
+ };
1723
+ }
1724
+ ```
1725
+
1726
+ Complete mapping from backend CC_EVENTS to internal TaskEvent types.
1727
+
1728
+ | Backend CC Event | TaskEvent | Typical From State(s) | Target State | Notes / Guards |
1729
+ |---|---|---|---|---|
1730
+ | `AGENT_CONTACT_RESERVED` | `TASK_INCOMING` | `IDLE` | `OFFERED` | Incoming task entry |
1731
+ | `AGENT_OFFER_CONTACT` | `TASK_OFFERED` | `OFFERED` | `OFFERED` | Offer payload refresh |
1732
+ | `AGENT_CONTACT` | `HYDRATE` | `IDLE` | `WRAPPING_UP` / `CONSULTING` / `HELD` / `CONNECTED` / `CONFERENCING` / `IDLE` | Guard-based restore |
1733
+ | `CONTACT_UPDATED` | `CONTACT_UPDATED` | any | same | Context sync |
1734
+ | `CONTACT_OWNER_CHANGED` | `CONTACT_OWNER_CHANGED` | any | same | Context sync |
1735
+ | `AGENT_OFFER_CONSULT` | `OFFER_CONSULT` | `OFFERED` | `OFFERED` | Receiver-side consult offer |
1736
+ | `AGENT_CONTACT_ASSIGNED` | `ASSIGN` | `OFFERED` / `CONNECTED` / `CONSULTING` | `CONNECTED` | Assign/reassign |
1737
+ | `AGENT_CONTACT_HELD` | `HOLD_SUCCESS` | `HOLD_INITIATING` | `HELD` | Includes `mediaResourceId` |
1738
+ | `AGENT_CONTACT_UNHELD` | `UNHOLD_SUCCESS` | `RESUME_INITIATING` | `CONNECTED` | Includes `mediaResourceId` |
1739
+ | `AGENT_CONSULT_CREATED` | `CONSULT_CREATED` | varies | same | Context + emitter action |
1740
+ | `AGENT_CONSULTING` | `CONSULTING_ACTIVE` | `OFFERED` / `CONSULTING` | `CONSULTING` | Sets consult joined flag |
1741
+ | `AGENT_CONSULT_ENDED` | `CONSULT_END` | `CONSULTING` | `CONFERENCING` / `HELD` / `TERMINATED` | Depends on initiator flags |
1742
+ | `AGENT_CONSULT_FAILED` | `CONSULT_FAILED` | `CONSULT_INITIATING` | `CONFERENCING` / `HELD` / `CONNECTED` | Guard-based fallback |
1743
+ | `AGENT_CTQ_FAILED` | `CONSULT_FAILED` | `CONSULT_INITIATING` | `CONFERENCING` / `HELD` / `CONNECTED` | Same as consult failed |
1744
+ | `AGENT_CTQ_CANCELLED` | `CTQ_CANCEL` | `CONSULT_INITIATING` | `HELD` / `CONNECTED` | Guarded by hold state |
1745
+ | `AGENT_CTQ_CANCEL_FAILED` | `CTQ_CANCEL_FAILED` | varies | same | No transition mapping |
1746
+ | `AGENT_BLIND_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | `shouldWrapUpOrIsInitiator` |
1747
+ | `AGENT_CONSULT_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | Same path |
1748
+ | `AGENT_VTEAM_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | Same path |
1749
+ | `AGENT_WRAPUP` | `TASK_WRAPUP` | `OFFERED` / `CONNECTED` / `HELD` / `CONSULTING` | `TERMINATED` / `WRAPPING_UP` | `OFFERED` terminates; others wrap |
1750
+ | `AGENT_BLIND_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1751
+ | `AGENT_VTEAM_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1752
+ | `AGENT_CONSULT_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1753
+ | `AGENT_CONFERENCE_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1754
+ | `CONTACT_ENDED` | `CONTACT_ENDED` | `CONNECTED` / `HELD` / `CONSULTING` / `CONFERENCING` | `CONFERENCING` / `WRAPPING_UP` / `TERMINATED` / same | Guard-driven branch |
1755
+ | `AGENT_INVITE_FAILED` | `INVITE_FAILED` | `OFFERED` | `TERMINATED` | Reject path |
1756
+ | `AGENT_CONTACT_ASSIGN_FAILED` | `ASSIGN_FAILED` | `OFFERED` | `TERMINATED` | Reject path |
1757
+ | `AGENT_CONTACT_OFFER_RONA` | `RONA` | `OFFERED` | `TERMINATED` | Timeout path |
1758
+ | `AGENT_OUTBOUND_FAILED` | `OUTBOUND_FAILED` | `OFFERED` | `TERMINATED` | Outbound failure |
1759
+ | `CONTACT_RECORDING_STARTED` | `RECORDING_STARTED` | any | same | Recording state update |
1760
+ | `CONTACT_RECORDING_PAUSED` | `PAUSE_RECORDING` | `CONNECTED` | same | Recording state update |
1761
+ | `CONTACT_RECORDING_RESUMED` | `RESUME_RECORDING` | `CONNECTED` | same | Recording state update |
1762
+ | `AGENT_WRAPPEDUP` | `WRAPUP_COMPLETE` | `WRAPPING_UP` | `COMPLETED` | Final completion |
1763
+ | `AGENT_CONSULT_CONFERENCED` | `CONFERENCE_START` | `CONSULTING` / `CONF_INITIATING` / `CONFERENCING` | `CONFERENCING` / same | Conference established |
1764
+ | `PARTICIPANT_JOINED_CONFERENCE` | `CONFERENCE_START` | `CONSULTING` / `CONF_INITIATING` / `CONFERENCING` | `CONFERENCING` / same | Conference participant joined |
1765
+ | `AGENT_CONSULT_CONFERENCE_FAILED` | `CONFERENCE_FAILED` | `CONF_INITIATING` | `CONSULTING` | Merge fail fallback |
1766
+ | `AGENT_CONSULT_CONFERENCE_ENDED` | `CONFERENCE_END` | `CONFERENCING` | `WRAPPING_UP` / `CONNECTED` / `TERMINATED` | Guard-driven |
1767
+ | `PARTICIPANT_LEFT_CONFERENCE` | `PARTICIPANT_LEAVE` | `CONSULTING` / `CONFERENCING` | `WRAPPING_UP` / `TERMINATED` / `CONNECTED` / same | Ownership + downgrade guards |
1768
+ | `AGENT_CONFERENCE_TRANSFERRED` | `TRANSFER_CONFERENCE_SUCCESS` | `CONSULTING` / `CONFERENCING` | `WRAPPING_UP` / `CONFERENCING` / `TERMINATED` / same | Initiator/receiver dependent |
1769
+
1770
+ - `AGENT_CONTACT_UNASSIGNED` -> returns `null` in mapper (`TaskManager.mapEventToTaskStateMachineEvent`)
1771
+
1772
+ | Backend Event | TaskEvent | State Transition | Notes |
1773
+ |---|---|---|---|
1774
+ | `AgentContactReserved` | `TASK_INCOMING` | IDLE → OFFERED | New task offered |
1775
+ | `AgentOfferContact` | `TASK_OFFERED` | Stay in OFFERED | Offer confirmation |
1776
+ | `AgentContact` | `HYDRATE` | Various | State restoration |
1777
+ | `AgentContactAssigned` | `ASSIGN` | OFFERED → CONNECTED (also CONNECTED/CONSULTING refresh paths) | Task accepted/reassigned |
1778
+ | `ContactUpdated` | `CONTACT_UPDATED` | No change | Data update only |
1779
+ | `ContactOwnerChanged` | `CONTACT_OWNER_CHANGED` | No change | Owner update only |
1780
+ | `ContactEnded` | `CONTACT_ENDED` | Guard-based branch | CONFERENCING / WRAPPING_UP / TERMINATED / stay |
1781
+ | `AgentContactUnassigned` | None | N/A | Handled by other events |
1782
+
1783
+ | Backend Event | TaskEvent | State Transition | Context Update |
1784
+ |---|---|---|---|
1785
+ | `AgentContactHeld` | `HOLD_SUCCESS` | HOLD_INITIATING → HELD | `setHoldState` updates `taskData.interaction.media[mediaResourceId].isHold = true` |
1786
+ | `AgentContactUnheld` | `UNHOLD_SUCCESS` | RESUME_INITIATING → CONNECTED | `setHoldState` updates `taskData.interaction.media[mediaResourceId].isHold = false` |
1787
+ | `AgentContactHoldFailed` | `HOLD_FAILED` | HOLD_INITIATING → CONNECTED | Context refreshed |
1788
+ | `AgentContactUnholdFailed` | `UNHOLD_FAILED` | RESUME_INITIATING → HELD | No transition action |
1789
+
1790
+ | Backend Event / API | TaskEvent | State Transition | Context Update |
1791
+ |---|---|---|---|
1792
+ | API `task.consult(...)` | `CONSULT` | CONNECTED/HELD/CONFERENCING → CONSULT_INITIATING | Sets consult initiator + destination (and `consultFromConference` in conference flow) |
1793
+ | `AgentOfferConsult` | `OFFER_CONSULT` | OFFERED → OFFERED | Offer-only path |
1794
+ | `AgentConsultCreated` | `CONSULT_CREATED` | No state transition wiring | Event exists but not consumed by transition table |
1795
+ | `AgentConsulting` | `CONSULTING_ACTIVE` | OFFERED → CONSULTING, CONSULTING → CONSULTING | Sets `consultDestinationAgentJoined` |
1796
+ | `AgentConsultEnded` | `CONSULT_END` | CONSULTING → CONFERENCING / HELD / TERMINATED | Depends on initiator and consult-from-conference |
1797
+ | `AgentConsultFailed` / `AgentCtqFailed` | `CONSULT_FAILED` | CONSULT_INITIATING → CONFERENCING / HELD / CONNECTED | Guard-based fallback |
1798
+ | `AgentCtqCancelled` | `CTQ_CANCEL` | CONSULT_INITIATING → HELD / CONNECTED | Guarded by `isPrimaryMediaOnHold` |
1799
+ | `AgentCtqCancelFailed` | `CTQ_CANCEL_FAILED` | No state transition wiring | Event mapped but not consumed |
1800
+
1801
+ | Backend Event | TaskEvent | State Transition | Wrapup Logic |
1802
+ |---|---|---|---|
1803
+ | `AgentBlindTransferred` | `TRANSFER_SUCCESS` | → WRAPPING_UP/CONNECTED | Guard `shouldWrapUpOrIsInitiator` decides wrapup vs receiver/default path |
1804
+ | `AgentVTeamTransferred` | `TRANSFER_SUCCESS` | → WRAPPING_UP/CONNECTED | Same transition logic as blind transfer |
1805
+ | `AgentConsultTransferred` | `TRANSFER_SUCCESS` | CONSULTING → WRAPPING_UP/CONNECTED | Initiator/wrapup path vs receiver/default path |
1806
+ | `AgentBlindTransferFailed` | `TRANSFER_FAILED` | No change | Emit failure, stay in current state |
1807
+ | `AgentVTeamTransferFailed` | `TRANSFER_FAILED` | No change | Queue transfer failed |
1808
+ | `AgentConsultTransferFailed` | `TRANSFER_FAILED` | No change | Consult transfer failed |
1809
+
1810
+ | Backend Event / API | TaskEvent | State Transition | Context Update |
1811
+ |---|---|---|---|
1812
+ | API `task.consultConference()` | `MERGE_TO_CONFERENCE` | CONSULTING → CONF_INITIATING | Starts merge flow |
1813
+ | `AgentConsultConferenced` | `CONFERENCE_START` | CONSULTING/CONF_INITIATING → CONFERENCING | `handleConferenceStarted` path |
1814
+ | `ParticipantJoinedConference` | `CONFERENCE_START` | CONFERENCING → CONFERENCING | Refresh + emit conference started |
1815
+ | `ParticipantLeftConference` | `PARTICIPANT_LEAVE` | CONSULTING / CONFERENCING → WRAPPING_UP / TERMINATED / CONNECTED / stay | Uses `didCurrentAgentLeaveConference`, `shouldWrapUp`, `shouldDowngradeConferenceToConnected` |
1816
+ | `AgentConsultConferenceEnded` | `CONFERENCE_END` | CONFERENCING → WRAPPING_UP / CONNECTED / TERMINATED | Guard-based branch |
1817
+ | `AgentConsultConferenceFailed` | `CONFERENCE_FAILED` | CONF_INITIATING → CONSULTING | Merge failed fallback |
1818
+ | `AgentConferenceTransferred` | `TRANSFER_CONFERENCE_SUCCESS` | CONSULTING/CONFERENCING branch logic | Initiator/receiver dependent |
1819
+ | API/SDK conference transfer fail | `TRANSFER_CONFERENCE_FAILED` | CONSULTING/CONFERENCING stay | Clears transfer request flag |
1820
+
1821
+ | Backend Event | TaskEvent | State Transition | Context Update |
1822
+ |---|---|---|---|
1823
+ | `ContactRecordingStarted` | `RECORDING_STARTED` | No change | Update recording state |
1824
+ | `ContactRecordingPaused` | `PAUSE_RECORDING` | No change | Mark recording paused |
1825
+ | `ContactRecordingResumed` | `RESUME_RECORDING` | No change | Mark recording active |
1826
+
1827
+ | Backend Event | TaskEvent | State Transition | Notes |
1828
+ |---|---|---|---|
1829
+ | `AgentWrapup` | `TASK_WRAPUP` | → WRAPPING_UP | Enter ACW |
1830
+ | `AgentWrappedup` | `WRAPUP_COMPLETE` | WRAPPING_UP → COMPLETED | Complete wrapup |
1831
+
1832
+ This diagram represents state-to-state lifecycle transitions for the task state machine.
1833
+
1834
+ ```mermaid
1835
+ stateDiagram-v2
1836
+ [*] --> IDLE
1837
+
1838
+ %% IDLE
1839
+ IDLE --> OFFERED: AGENT_CONTACT_RESERVED (CC Event) -> TASK_INCOMING (State Machine Event)
1840
+
1841
+ %% OFFERED
1842
+ OFFERED --> OFFERED: AGENT_OFFER_CONTACT (CC Event) -> TASK_OFFERED (State Machine Event)
1843
+ OFFERED --> OFFERED: AGENT_OFFER_CONSULT (CC Event) -> OFFER_CONSULT (State Machine Event)
1844
+ OFFERED --> CONNECTED: AGENT_CONTACT_ASSIGNED (CC Event) -> ASSIGN (State Machine Event)
1845
+ OFFERED --> CONSULTING: AGENT_CONSULTING (CC Event) -> CONSULTING_ACTIVE (State Machine Event)
1846
+ OFFERED --> TERMINATED: AGENT_CONTACT_OFFER_RONA (CC Event) -> RONA (State Machine Event)
1847
+ OFFERED --> TERMINATED: AGENT_CONTACT_ASSIGN_FAILED (CC Event) -> ASSIGN_FAILED (State Machine Event)
1848
+ OFFERED --> TERMINATED: AGENT_INVITE_FAILED (CC Event) -> INVITE_FAILED (State Machine Event)
1849
+ OFFERED --> TERMINATED: AGENT_OUTBOUND_FAILED (CC Event) -> OUTBOUND_FAILED (State Machine Event)
1850
+ OFFERED --> TERMINATED: AGENT_WRAPUP (CC Event) -> TASK_WRAPUP (State Machine Event)
1851
+
1852
+ %% CONNECTED
1853
+ CONNECTED --> HOLD_INITIATING: API hold() -> HOLD_INITIATED (State Machine Event)
1854
+ CONNECTED --> CONSULT_INITIATING: API consult() -> CONSULT (State Machine Event)
1855
+ CONNECTED --> WRAPPING_UP: AGENT_*TRANSFERRED (CC Event) -> TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1856
+ CONNECTED --> CONNECTED: AGENT_*TRANSFERRED (CC Event) -> TRANSFER_SUCCESS (State Machine Event) [receiver]
1857
+ CONNECTED --> CONNECTED: AGENT_*TRANSFER_FAILED (CC Event) -> TRANSFER_FAILED (State Machine Event)
1858
+ CONNECTED --> CONFERENCING: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [conferenceInProgressFromEvent]
1859
+ CONNECTED --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [shouldWrapUp]
1860
+ CONNECTED --> TERMINATED: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [default]
1861
+ CONNECTED --> WRAPPING_UP: AGENT_WRAPUP (CC Event) -> TASK_WRAPUP (State Machine Event)
1862
+
1863
+ %% HOLD_INITIATING
1864
+ HOLD_INITIATING --> HELD: HOLD_SUCCESS (State Machine Event)
1865
+ HOLD_INITIATING --> CONNECTED: HOLD_FAILED (State Machine Event)
1866
+
1867
+ %% HELD
1868
+ HELD --> RESUME_INITIATING: UNHOLD_INITIATED (State Machine Event)
1869
+ HELD --> CONSULT_INITIATING: CONSULT (State Machine Event)
1870
+ HELD --> WRAPPING_UP: TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1871
+ HELD --> CONNECTED: TRANSFER_SUCCESS (State Machine Event) [receiver]
1872
+ HELD --> CONFERENCING: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [conferenceInProgressFromEvent]
1873
+ HELD --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [shouldWrapUp]
1874
+ HELD --> TERMINATED: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [default]
1875
+ HELD --> WRAPPING_UP: TASK_WRAPUP (State Machine Event)
1876
+ HELD --> HELD: TRANSFER_FAILED (State Machine Event)
1877
+
1878
+ %% RESUME_INITIATING
1879
+ RESUME_INITIATING --> CONNECTED: UNHOLD_SUCCESS (State Machine Event)
1880
+ RESUME_INITIATING --> HELD: UNHOLD_FAILED (State Machine Event)
1881
+
1882
+ %% CONSULT_INITIATING
1883
+ CONSULT_INITIATING --> CONSULTING: CONSULT_SUCCESS (State Machine Event)
1884
+ CONSULT_INITIATING --> CONFERENCING: CONSULT_FAILED (State Machine Event) [consultFromConference]
1885
+ CONSULT_INITIATING --> HELD: CONSULT_FAILED / CTQ_CANCEL (State Machine Event) [isPrimaryMediaOnHold]
1886
+ CONSULT_INITIATING --> CONNECTED: HOLD_FAILED / CONSULT_FAILED / CTQ_CANCEL (State Machine Event) [default]
1887
+ CONSULT_INITIATING --> CONSULT_INITIATING: HOLD_SUCCESS (State Machine Event)
1888
+
1889
+ %% CONSULTING
1890
+ CONSULTING --> CONFERENCING: CONSULT_END (State Machine Event) [consultInitiator && consultFromConference]
1891
+ CONSULTING --> HELD: CONSULT_END (State Machine Event) [consultInitiator]
1892
+ CONSULTING --> TERMINATED: CONSULT_END (State Machine Event) [consulted agent]
1893
+ CONSULTING --> WRAPPING_UP: TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1894
+ CONSULTING --> CONNECTED: TRANSFER_SUCCESS (State Machine Event) [receiver]
1895
+ CONSULTING --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) / TASK_WRAPUP (State Machine Event)
1896
+ CONSULTING --> CONNECTED: ASSIGN (State Machine Event)
1897
+ CONSULTING --> CONF_INITIATING: MERGE_TO_CONFERENCE (State Machine Event)
1898
+ CONSULTING --> CONFERENCING: CONFERENCE_START (State Machine Event)
1899
+ CONSULTING --> CONFERENCING: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [!consultInitiator]
1900
+ CONSULTING --> WRAPPING_UP: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [shouldWrapUp]
1901
+ CONSULTING --> TERMINATED: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [default]
1902
+ CONSULTING --> CONSULTING: CONSULTING_ACTIVE / HOLD_SUCCESS / UNHOLD_SUCCESS / TRANSFER_FAILED / TRANSFER_CONFERENCE / TRANSFER_CONFERENCE_FAILED (State Machine Event)
1903
+
1904
+ %% CONF_INITIATING
1905
+ CONF_INITIATING --> CONFERENCING: CONFERENCE_START (State Machine Event)
1906
+ CONF_INITIATING --> CONSULTING: CONFERENCE_FAILED (State Machine Event)
1907
+
1908
+ %% CONFERENCING
1909
+ CONFERENCING --> CONSULT_INITIATING: CONSULT (State Machine Event)
1910
+ CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveConference && shouldWrapUp]
1911
+ CONFERENCING --> TERMINATED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveConference]
1912
+ CONFERENCING --> CONNECTED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [shouldDowngradeConferenceToConnected]
1913
+ CONFERENCING --> WRAPPING_UP: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [shouldWrapUp]
1914
+ CONFERENCING --> CONNECTED: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [customerInCall]
1915
+ CONFERENCING --> TERMINATED: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [default]
1916
+ 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)
1917
+
1918
+ %% WRAPPING_UP / FINAL
1919
+ WRAPPING_UP --> COMPLETED: WRAPUP_COMPLETE (State Machine Event)
1920
+ COMPLETED --> [*]
1921
+ TERMINATED --> [*]
1922
+ ```
1923
+
1924
+ The full diagram above is the source of truth. The diagrams below split above flows based on each feature.
1925
+
1926
+ ```mermaid
1927
+ stateDiagram-v2
1928
+ [*] --> IDLE
1929
+ IDLE --> OFFERED: AGENT_CONTACT_RESERVED -> TASK_INCOMING
1930
+ OFFERED --> OFFERED: AGENT_CONTACT_OFFER -> TASK_OFFERED
1931
+ OFFERED --> OFFERED: AGENT_CONSULT_OFFER -> OFFER_CONSULT
1932
+ OFFERED --> CONNECTED: AGENT_CONTACT_ASSIGNED -> ASSIGN
1933
+ 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
1934
+
1935
+ CONNECTED --> HOLD_INITIATING: task.hold() -> HOLD_INITIATED
1936
+ CONNECTED --> CONSULT_INITIATING: task.consult() -> CONSULT
1937
+
1938
+ CONNECTED --> CONNECTED: PAUSE_RECORDING/RESUME_RECORDING
1939
+ CONNECTED --> CONNECTED: AGENT_BLIND_TRANSFER_FAILED/AGENT_VTEAM_TRANSFER_FAILED -> TRANSFER_FAILED
1940
+
1941
+ CONNECTED --> WRAPPING_UP: AGENT_BLIND_TRANSFERRED/AGENT_VTEAM_TRANSFERRED -> TRANSFER_SUCCESS [shouldWrapUpOrIsInitiator]
1942
+ CONNECTED --> WRAPPING_UP: CONTACT_ENDED -> CONTACT_ENDED [shouldWrapUp]
1943
+ CONNECTED --> WRAPPING_UP: AGENT_WRAPUP -> TASK_WRAPUP
1944
+ CONNECTED --> TERMINATED: CONTACT_ENDED -> CONTACT_ENDED [default]
1945
+
1946
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
1947
+ COMPLETED --> [*]
1948
+ TERMINATED --> [*]
1949
+ ```
1950
+
1951
+ ```mermaid
1952
+ stateDiagram-v2
1953
+ [*] --> CONNECTED
1954
+ CONNECTED --> HOLD_INITIATING: task.hold() -> HOLD_INITIATED
1955
+ HOLD_INITIATING --> HELD: AGENT_CONTACT_HELD -> HOLD_SUCCESS
1956
+ HOLD_INITIATING --> CONNECTED: AGENT_CONTACT_HOLD_FAILED -> HOLD_FAILED
1957
+ HELD --> RESUME_INITIATING: task.resume() -> UNHOLD_INITIATED
1958
+ RESUME_INITIATING --> CONNECTED: AGENT_CONTACT_UNHELD -> UNHOLD_SUCCESS
1959
+ RESUME_INITIATING --> HELD: AGENT_CONTACT_UNHOLD_FAILED -> UNHOLD_FAILED
1960
+ HELD --> CONSULT_INITIATING: task.consult() -> Consult
1961
+ CONSULT_INITIATING --> HELD: CONSULT_FAILED / CTQ_CANCEL [isPrimaryMediaOnHold]
1962
+ HELD --> WRAPPING_UP: CONTACT_ENDED -> CONTACT_ENDED [shouldWrapUp]
1963
+ HELD --> TERMINATED: CONTACT_ENDED -> CONTACT_ENDED [default]
1964
+
1965
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
1966
+ COMPLETED --> [*]
1967
+ TERMINATED --> [*]
1968
+ ```
1969
+
1970
+ ```mermaid
1971
+ stateDiagram-v2
1972
+ stateDiagram-v2
1973
+ [*] --> CONNECTED
1974
+ CONNECTED --> CONSULT_INITIATING: task.consult() -> CONSULT
1975
+ HELD --> CONSULT_INITIATING: task.consult() -> CONSULT
1976
+ CONFERENCING --> CONSULT_INITIATING: task.consult() -> CONSULT
1977
+
1978
+ CONSULT_INITIATING --> CONSULT_INITIATING: AGENT_CONTACT_HELD -> HOLD_SUCCESS
1979
+ CONSULT_INITIATING --> CONNECTED: AGENT_CONTACT_HOLD_FAILED -> HOLD_FAILED
1980
+ CONSULT_INITIATING --> CONSULTING: API consult success -> CONSULT_SUCCESS
1981
+ CONSULT_INITIATING --> HELD: AGENT_CONSULT_FAILED/AGENT_CTQ_FAILED -> CONSULT_FAILED [isPrimaryMediaOnHold]
1982
+ CONSULT_INITIATING --> CONNECTED: AGENT_CONSULT_FAILED/AGENT_CTQ_FAILED -> CONSULT_FAILED [default]
1983
+ CONSULT_INITIATING --> CONFERENCING: AGENT_CONSULT_FAILED -> CONSULT_FAILED [consultFromConference]
1984
+ CONSULT_INITIATING --> HELD: AGENT_CTQ_CANCELLED -> CTQ_CANCEL [isPrimaryMediaOnHold]
1985
+ CONSULT_INITIATING --> CONNECTED: AGENT_CTQ_CANCELLED -> CTQ_CANCEL [default]
1986
+
1987
+ CONSULTING --> HELD: AGENT_CONSULT_ENDED -> CONSULT_END [consultInitiator]
1988
+ CONSULTING --> TERMINATED: AGENT_CONSULT_ENDED -> CONSULT_END [consulted agent]
1989
+ CONSULTING --> CONFERENCING: AGENT_CONSULT_ENDED -> CONSULT_END [consultInitiator && consultFromConference]
1990
+ CONSULTING --> CONNECTED: AGENT_CONSULT_TRANSFERRED/AGENT_CONTACT_ASSIGNED -> TRANSFER_SUCCESS/ASSIGN
1991
+ CONSULTING --> WRAPPING_UP: AGENT_CONSULT_TRANSFERRED -> TRANSFER_SUCCESS [shouldWrapUpOrIsInitiator]
1992
+ CONSULTING --> CONSULTING: AGENT_CONSULT_TRANSFER_FAILED -> TRANSFER_FAILED
1993
+ CONSULTING --> CONF_INITIATING: task.consultConference() -> MERGE_TO_CONFERENCE
1994
+ CONSULTING --> WRAPPING_UP: AGENT_CONTACT_ENDED -> CONTACT_ENDED
1995
+
1996
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
1997
+ COMPLETED --> [*]
1998
+ TERMINATED --> [*]
1999
+ ```
2000
+
2001
+ ```mermaid
2002
+ stateDiagram-v2
2003
+ [*] --> CONSULTING
2004
+ CONSULTING --> CONF_INITIATING: task.consultConference() -> MERGE_TO_CONFERENCE
2005
+ CONF_INITIATING --> CONFERENCING: AGENT_CONSULT_CONFERENCED -> CONFERENCE_START
2006
+ CONF_INITIATING --> CONSULTING: AGENT_CONSULT_CONFERENCE_FAILED -> CONFERENCE_FAILED
2007
+ CONFERENCING --> CONSULT_INITIATING: task.consult() -> CONSULT
2008
+ CONSULTING --> CONFERENCING: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [!consultInitiator]
2009
+ CONSULTING --> WRAPPING_UP: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [shouldWrapUp]
2010
+ CONSULTING --> TERMINATED: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [default]
2011
+ CONSULTING --> CONSULTING: AGENT_CONFERENCE_TRANSFER_FAILED -> TRANSFER_CONFERENCE_FAILED
2012
+ CONFERENCING --> CONFERENCING: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS
2013
+ CONFERENCING --> CONFERENCING: AGENT_CONFERENCE_TRANSFER_FAILED-> TRANSFER_CONFERENCE_FAILED
2014
+
2015
+ CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveConference && shouldWrapUp]
2016
+ CONFERENCING --> TERMINATED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveConference]
2017
+ CONFERENCING --> CONNECTED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [shouldDowngradeConferenceToConnected]
2018
+ CONFERENCING --> WRAPPING_UP: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [shouldWrapUp]
2019
+ CONFERENCING --> CONNECTED: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [customerInCall]
2020
+ CONFERENCING --> TERMINATED: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [default]
2021
+ WRAPPING_UP --> COMPLETED: WRAPUP_COMPLETE
2022
+ COMPLETED --> [*]
2023
+ TERMINATED --> [*]
2024
+ ```
2025
+
2026
+ ```typescript
2027
+ import {setup} from 'xstate';
2028
+ import {actions} from './actions';
2029
+ import {guards} from './guards';
2030
+
2031
+ const taskStateMachine = setup({
2032
+ types: {
2033
+ context: {} as TaskContext,
2034
+ events: {} as TaskEventPayload,
2035
+ },
2036
+ })
2037
+ .createMachine({
2038
+ id: 'taskStateMachine',
2039
+ initial: TaskState.IDLE,
2040
+ context: createInitialContext(uiControlConfig, TaskState.IDLE),
2041
+ states: {
2042
+ [TaskState.IDLE]: {
2043
+ on: {
2044
+ [TaskEvent.TASK_INCOMING]: {
2045
+ target: TaskState.OFFERED,
2046
+ actions: ['initializeTask', 'emitTaskIncoming'],
2047
+ },
2048
+ [TaskEvent.HYDRATE]: [
2049
+ {
2050
+ guard: guards.isInteractionTerminated,
2051
+ target: TaskState.WRAPPING_UP,
2052
+ actions: ['updateTaskData', 'markEnded', 'emitTaskHydrate'],
2053
+ },
2054
+ // ... more hydrate cases
2055
+ ],
2056
+ },
2057
+ },
2058
+ [TaskState.OFFERED]: {
2059
+ on: {
2060
+ [TaskEvent.ASSIGN]: {
2061
+ target: TaskState.CONNECTED,
2062
+ actions: ['updateTaskData', 'emitTaskAssigned'],
2063
+ },
2064
+ [TaskEvent.TASK_WRAPUP]: {
2065
+ target: TaskState.TERMINATED,
2066
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
2067
+ },
2068
+ // ... more transitions
2069
+ },
2070
+ },
2071
+ // ... more states
2072
+ },
2073
+ })
2074
+ .provide({actions});
2075
+ ```
2076
+
2077
+ The HYDRATE event restores state machine state after page refresh or reconnection.
2078
+
2079
+ **Algorithm**:
2080
+
2081
+ 1. Receive HYDRATE event with full task data
2082
+
2083
+ 2. Check interaction state and flags in order of precedence:
2084
+
2085
+ - If `taskData.interaction.isTerminated === true` -> WRAPPING_UP
2086
+
2087
+ - If `taskData.interaction.state === 'consulting'` -> CONSULTING
2088
+
2089
+ - If `taskData.interaction.state === 'hold'` -> HELD
2090
+
2091
+ - If `taskData.interaction.state === 'connected'` -> CONNECTED
2092
+
2093
+ - If conferencing-by-participants guard passes (`agentCount >= 2` in main call) -> CONFERENCING
2094
+
2095
+ - Default → Stay in IDLE
2096
+
2097
+ 3. Update context with hydrated data
2098
+
2099
+ 4. Emit TASK_HYDRATE event
2100
+
2101
+ ### Action implementation ownership
2102
+
2103
+ - `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.
2104
+ - `didInitiateConsult` exists in `guards.ts` but is not referenced by the current graph. Treat it as defined-but-unwired.
2105
+ - `getDefaultUIControls` is package-public through `src/index.ts`; the voice/digital computation helpers are private implementation details.
2106
+
2107
+ ## Pitfalls
2108
+ - `syncTaskDataFromEvent` is declared by the graph but implemented by Task; adding a competing default action loses integration-owned normalization.
2109
+ - `handleConferenceFailed`, `handleSwitchToMainCall`, and `handleSwitchToConsult` are wired actions, while `forceConsultInitiator` remains defined but unwired.
2110
+ - Guards/actions must remain deterministic and transport-free; logging, metrics, authentication, and request side effects belong to Task/TaskManager.
2111
+
2112
+ - `RONA`, `INVITE_FAILED`, `ASSIGN_FAILED`, `OUTBOUND_FAILED`
2113
+
2114
+ | Backend Event | TaskEvent | State Transition | Notes |
2115
+ |---|---|---|---|
2116
+ | `AgentContactOfferRona` | `RONA` | OFFERED → TERMINATED | Redirection on no answer |
2117
+ | `AgentInviteFailed` | `INVITE_FAILED` | OFFERED → TERMINATED | Invite failed |
2118
+ | `AgentContactAssignFailed` | `ASSIGN_FAILED` | OFFERED → TERMINATED | Assignment failed |
2119
+ | `AgentOutboundFailed` | `OUTBOUND_FAILED` | OFFERED → TERMINATED | Outdial failed |
2120
+
2121
+ ## Module Do's / Don'ts
2122
+ - DO map backend notifications to typed `TaskEvent` values before sending them to the actor.
2123
+ - DO preserve initiating states separately from success/failure stable states.
2124
+ - DON'T invoke WebexRequest, LoggerProxy, or MetricsManager from the state-machine layer.
2125
+ - DON'T document private UI-control helpers as public APIs or claim an action is absent without checking graph wiring.
2126
+
2127
+ ## Key Design Trade-off
2128
+ - Backend events are mapped into a typed internal event vocabulary before transition evaluation, isolating the state graph from transport details.
2129
+
2130
+ ## Test-Case Strategy (module)
2131
+ 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.
2132
+
2133
+ | Behavior / Requirement | Existing test evidence | Gap |
2134
+ |---|---|---|
2135
+ | `TASK_STATE_MACHINE-R-001` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Keep transition coverage synchronized with all mapped TaskEvent groups. |
2136
+ | `TASK_STATE_MACHINE-R-002` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Retain explicit wired/unwired assertions. |
2137
+ | `TASK_STATE_MACHINE-R-003` | `test/unit/spec/services/task/Task.ts` | None. |
2138
+ | `TASK_STATE_MACHINE-R-004` | `test/unit/spec/services/task/state-machine/uiControlsComputer.ts` | None. |
2139
+ | `TASK_STATE_MACHINE-R-005` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Transport/auth absence is also verified from imports. |
2140
+ | `TASK_STATE_MACHINE-R-006` | `test/unit/spec/services/task/Task.ts` | None. |
2141
+ | `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. |
2142
+
2143
+ ## Traceability
2144
+ - Repo architecture: `../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../ai-docs/SPEC_INDEX.md`
2145
+ - Coverage state and contracts baseline: `../../../../../.sdd/manifest.json`
2146
+
2147
+ - Task lifecycle state machine: `TaskStateMachine.ts`
2148
+
2149
+ - State machine types/events: `constants.ts`, `types.ts`
2150
+
2151
+ - Guard logic: `guards.ts`
2152
+
2153
+ - Actions and context mutation: `actions.ts`
2154
+
2155
+ - UI control computation: `uiControlsComputer.ts`
2156
+
2157
+ `computeUIControls()` in `uiControlsComputer.ts`.
2158
+
2159
+ - [../../ai-docs/task-spec.md](../../ai-docs/task-spec.md) - Task service usage guide
2160
+
2161
+ - [../../ai-docs/task-spec.md](../../ai-docs/task-spec.md) - Task service architecture
2162
+
2163
+ - `../Task.ts` - actor lifecycle, action overrides, event emission
2164
+
2165
+ - `../TaskManager.ts` - maps backend events to state-machine events
2166
+
2167
+ - `../types.ts` - shared task data structures
2168
+
2169
+ - `../../ai-docs/task-spec.md` - broader task service architecture
2170
+
2171
+ - [TaskStateMachine.ts](../TaskStateMachine.ts) - Implementation
2172
+
2173
+ - [guards.ts](../guards.ts) - Guard functions
2174
+
2175
+ - [actions.ts](../actions.ts) - Action functions
2176
+
2177
+ - [constants.ts](../constants.ts) - State and event enums