@webex/contact-center 3.12.0-next.12 → 3.12.0-next.121

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