@webex/contact-center 3.12.0-next.13 → 3.12.0-next.130

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 (312) 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 +53 -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 +52 -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 +382 -0
  17. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  18. package/ai-docs/features/contact-owner-change-refactor.md +78 -0
  19. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  20. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  21. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  22. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  23. package/ai-docs/patterns/testing-patterns.md +480 -0
  24. package/ai-docs/patterns/typescript-patterns.md +365 -0
  25. package/ai-docs/templates/README.md +102 -0
  26. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  27. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  28. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  29. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  30. package/ai-docs/templates/new-method/00-master.md +80 -0
  31. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  32. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  33. package/ai-docs/templates/new-method/03-tests.md +201 -0
  34. package/ai-docs/templates/new-method/04-validation.md +141 -0
  35. package/ai-docs/templates/new-service/00-master.md +109 -0
  36. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  37. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  38. package/ai-docs/templates/new-service/03-integration.md +178 -0
  39. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  40. package/ai-docs/templates/new-service/05-validation.md +145 -0
  41. package/dist/cc.js +971 -42
  42. package/dist/cc.js.map +1 -1
  43. package/dist/config.js +13 -0
  44. package/dist/config.js.map +1 -1
  45. package/dist/constants.js +31 -3
  46. package/dist/constants.js.map +1 -1
  47. package/dist/index.js +45 -15
  48. package/dist/index.js.map +1 -1
  49. package/dist/metrics/MetricsManager.js +16 -0
  50. package/dist/metrics/MetricsManager.js.map +1 -1
  51. package/dist/metrics/behavioral-events.js +205 -0
  52. package/dist/metrics/behavioral-events.js.map +1 -1
  53. package/dist/metrics/constants.js +48 -4
  54. package/dist/metrics/constants.js.map +1 -1
  55. package/dist/services/AddressBook.js +18 -15
  56. package/dist/services/AddressBook.js.map +1 -1
  57. package/dist/services/AnswerCallOnWebexService.js +202 -0
  58. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  59. package/dist/services/ApiAiAssistant.js +269 -9
  60. package/dist/services/ApiAiAssistant.js.map +1 -1
  61. package/dist/services/EntryPoint.js +46 -68
  62. package/dist/services/EntryPoint.js.map +1 -1
  63. package/dist/services/Queue.js +27 -22
  64. package/dist/services/Queue.js.map +1 -1
  65. package/dist/services/UserPreference.js +427 -0
  66. package/dist/services/UserPreference.js.map +1 -0
  67. package/dist/services/WebexCrossClientService.js +222 -0
  68. package/dist/services/WebexCrossClientService.js.map +1 -0
  69. package/dist/services/WxAppTelephonyMercurySync.js +108 -0
  70. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  71. package/dist/services/config/Util.js +11 -4
  72. package/dist/services/config/Util.js.map +1 -1
  73. package/dist/services/config/constants.js +41 -7
  74. package/dist/services/config/constants.js.map +1 -1
  75. package/dist/services/config/index.js +51 -0
  76. package/dist/services/config/index.js.map +1 -1
  77. package/dist/services/config/types.js +57 -12
  78. package/dist/services/config/types.js.map +1 -1
  79. package/dist/services/core/Err.js.map +1 -1
  80. package/dist/services/core/Utils.js +111 -32
  81. package/dist/services/core/Utils.js.map +1 -1
  82. package/dist/services/core/WebexRequest.js +6 -2
  83. package/dist/services/core/WebexRequest.js.map +1 -1
  84. package/dist/services/core/aqm-reqs.js +28 -14
  85. package/dist/services/core/aqm-reqs.js.map +1 -1
  86. package/dist/services/core/types.js.map +1 -1
  87. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  88. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  89. package/dist/services/core/websocket/types.js.map +1 -1
  90. package/dist/services/index.js +1 -1
  91. package/dist/services/index.js.map +1 -1
  92. package/dist/services/task/Task.js +754 -0
  93. package/dist/services/task/Task.js.map +1 -0
  94. package/dist/services/task/TaskFactory.js +49 -0
  95. package/dist/services/task/TaskFactory.js.map +1 -0
  96. package/dist/services/task/TaskManager.js +1172 -521
  97. package/dist/services/task/TaskManager.js.map +1 -1
  98. package/dist/services/task/TaskUtils.js +211 -27
  99. package/dist/services/task/TaskUtils.js.map +1 -1
  100. package/dist/services/task/WebexCallingUtils.js +70 -0
  101. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  102. package/dist/services/task/constants.js +12 -2
  103. package/dist/services/task/constants.js.map +1 -1
  104. package/dist/services/task/contact.js +29 -0
  105. package/dist/services/task/contact.js.map +1 -1
  106. package/dist/services/task/dialer.js +78 -0
  107. package/dist/services/task/dialer.js.map +1 -1
  108. package/dist/services/task/digital/Digital.js +78 -0
  109. package/dist/services/task/digital/Digital.js.map +1 -0
  110. package/dist/services/task/state-machine/TaskStateMachine.js +982 -0
  111. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  112. package/dist/services/task/state-machine/actions.js +572 -0
  113. package/dist/services/task/state-machine/actions.js.map +1 -0
  114. package/dist/services/task/state-machine/constants.js +161 -0
  115. package/dist/services/task/state-machine/constants.js.map +1 -0
  116. package/dist/services/task/state-machine/guards.js +379 -0
  117. package/dist/services/task/state-machine/guards.js.map +1 -0
  118. package/dist/services/task/state-machine/index.js +53 -0
  119. package/dist/services/task/state-machine/index.js.map +1 -0
  120. package/dist/services/task/state-machine/types.js +54 -0
  121. package/dist/services/task/state-machine/types.js.map +1 -0
  122. package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
  123. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  124. package/dist/services/task/taskDataNormalizer.js +99 -0
  125. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  126. package/dist/services/task/types.js +228 -5
  127. package/dist/services/task/types.js.map +1 -1
  128. package/dist/services/task/voice/Voice.js +1485 -0
  129. package/dist/services/task/voice/Voice.js.map +1 -0
  130. package/dist/services/task/voice/WebRTC.js +152 -0
  131. package/dist/services/task/voice/WebRTC.js.map +1 -0
  132. package/dist/services/task/voice/wxAppVoiceMethods.js +354 -0
  133. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  134. package/dist/services/wxAppDiagnosticLogging.js +122 -0
  135. package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
  136. package/dist/services/wxAppTelephonyUtils.js +19 -0
  137. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  138. package/dist/types/cc.d.ts +193 -6
  139. package/dist/types/config.d.ts +13 -0
  140. package/dist/types/constants.d.ts +29 -1
  141. package/dist/types/index.d.ts +35 -8
  142. package/dist/types/metrics/MetricsManager.d.ts +6 -0
  143. package/dist/types/metrics/constants.d.ts +43 -1
  144. package/dist/types/services/AddressBook.d.ts +2 -1
  145. package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
  146. package/dist/types/services/ApiAiAssistant.d.ts +56 -5
  147. package/dist/types/services/EntryPoint.d.ts +7 -5
  148. package/dist/types/services/Queue.d.ts +5 -3
  149. package/dist/types/services/UserPreference.d.ts +118 -0
  150. package/dist/types/services/WebexCrossClientService.d.ts +32 -0
  151. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  152. package/dist/types/services/config/constants.d.ts +34 -5
  153. package/dist/types/services/config/types.d.ts +197 -12
  154. package/dist/types/services/core/Err.d.ts +6 -0
  155. package/dist/types/services/core/Utils.d.ts +35 -13
  156. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  157. package/dist/types/services/core/types.d.ts +2 -0
  158. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  159. package/dist/types/services/core/websocket/types.d.ts +1 -1
  160. package/dist/types/services/index.d.ts +1 -1
  161. package/dist/types/services/task/Task.d.ts +175 -0
  162. package/dist/types/services/task/TaskFactory.d.ts +13 -0
  163. package/dist/types/services/task/TaskUtils.d.ts +48 -2
  164. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  165. package/dist/types/services/task/constants.d.ts +10 -0
  166. package/dist/types/services/task/contact.d.ts +4 -0
  167. package/dist/types/services/task/dialer.d.ts +30 -0
  168. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  169. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1442 -0
  170. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  171. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  172. package/dist/types/services/task/state-machine/guards.d.ts +103 -0
  173. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  174. package/dist/types/services/task/state-machine/types.d.ts +277 -0
  175. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  176. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  177. package/dist/types/services/task/types.d.ts +680 -66
  178. package/dist/types/services/task/voice/Voice.d.ts +227 -0
  179. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  180. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +55 -0
  181. package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
  182. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  183. package/dist/types/types.d.ts +205 -9
  184. package/dist/types/utils/PageCache.d.ts +20 -3
  185. package/dist/types/webex.d.ts +1 -0
  186. package/dist/types.js +162 -4
  187. package/dist/types.js.map +1 -1
  188. package/dist/utils/PageCache.js +19 -5
  189. package/dist/utils/PageCache.js.map +1 -1
  190. package/dist/webex.js +22 -2
  191. package/dist/webex.js.map +1 -1
  192. package/package.json +16 -12
  193. package/src/cc.ts +1289 -112
  194. package/src/config.ts +13 -0
  195. package/src/constants.ts +29 -1
  196. package/src/index.ts +36 -5
  197. package/src/metrics/MetricsManager.ts +16 -0
  198. package/src/metrics/ai-docs/AGENTS.md +350 -0
  199. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  200. package/src/metrics/ai-docs/metrics-spec.md +921 -0
  201. package/src/metrics/behavioral-events.ts +212 -0
  202. package/src/metrics/constants.ts +52 -4
  203. package/src/services/AddressBook.ts +17 -6
  204. package/src/services/AnswerCallOnWebexService.ts +239 -0
  205. package/src/services/ApiAiAssistant.ts +356 -9
  206. package/src/services/EntryPoint.ts +59 -60
  207. package/src/services/Queue.ts +29 -12
  208. package/src/services/UserPreference.ts +509 -0
  209. package/src/services/WebexCrossClientService.ts +293 -0
  210. package/src/services/WxAppTelephonyMercurySync.ts +142 -0
  211. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  212. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  213. package/src/services/agent/ai-docs/agent-spec.md +505 -0
  214. package/src/services/ai-docs/AGENTS.md +386 -0
  215. package/src/services/ai-docs/services-spec.md +505 -0
  216. package/src/services/config/Util.ts +12 -3
  217. package/src/services/config/ai-docs/AGENTS.md +255 -0
  218. package/src/services/config/ai-docs/ARCHITECTURE.md +426 -0
  219. package/src/services/config/ai-docs/config-spec.md +686 -0
  220. package/src/services/config/constants.ts +46 -6
  221. package/src/services/config/index.ts +65 -0
  222. package/src/services/config/types.ts +197 -13
  223. package/src/services/core/Err.ts +3 -0
  224. package/src/services/core/Utils.ts +127 -37
  225. package/src/services/core/WebexRequest.ts +3 -1
  226. package/src/services/core/ai-docs/AGENTS.md +381 -0
  227. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  228. package/src/services/core/ai-docs/core-spec.md +787 -0
  229. package/src/services/core/aqm-reqs.ts +30 -15
  230. package/src/services/core/types.ts +2 -0
  231. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  232. package/src/services/core/websocket/types.ts +1 -1
  233. package/src/services/index.ts +1 -1
  234. package/src/services/task/Task.ts +908 -0
  235. package/src/services/task/TaskFactory.ts +60 -0
  236. package/src/services/task/TaskManager.ts +1419 -608
  237. package/src/services/task/TaskUtils.ts +303 -25
  238. package/src/services/task/WebexCallingUtils.ts +136 -0
  239. package/src/services/task/ai-docs/AGENTS.md +457 -0
  240. package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
  241. package/src/services/task/ai-docs/task-spec.md +1474 -0
  242. package/src/services/task/constants.ts +10 -0
  243. package/src/services/task/contact.ts +30 -0
  244. package/src/services/task/dialer.ts +80 -0
  245. package/src/services/task/digital/Digital.ts +97 -0
  246. package/src/services/task/state-machine/TaskStateMachine.ts +1320 -0
  247. package/src/services/task/state-machine/actions.ts +741 -0
  248. package/src/services/task/state-machine/ai-docs/AGENTS.md +460 -0
  249. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1156 -0
  250. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2247 -0
  251. package/src/services/task/state-machine/constants.ts +172 -0
  252. package/src/services/task/state-machine/guards.ts +442 -0
  253. package/src/services/task/state-machine/index.ts +28 -0
  254. package/src/services/task/state-machine/types.ts +258 -0
  255. package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
  256. package/src/services/task/taskDataNormalizer.ts +137 -0
  257. package/src/services/task/types.ts +819 -70
  258. package/src/services/task/voice/Voice.ts +1851 -0
  259. package/src/services/task/voice/WebRTC.ts +191 -0
  260. package/src/services/task/voice/wxAppVoiceMethods.ts +516 -0
  261. package/src/services/wxAppDiagnosticLogging.ts +174 -0
  262. package/src/services/wxAppTelephonyUtils.ts +14 -0
  263. package/src/types.ts +262 -11
  264. package/src/utils/AGENTS.md +289 -0
  265. package/src/utils/PageCache.ts +38 -5
  266. package/src/utils/ai-docs/utils-spec.md +391 -0
  267. package/src/webex.js +10 -0
  268. package/test/unit/spec/cc.ts +2210 -119
  269. package/test/unit/spec/logger-proxy.ts +70 -0
  270. package/test/unit/spec/metrics/MetricsManager.ts +25 -0
  271. package/test/unit/spec/metrics/behavioral-events.ts +60 -0
  272. package/test/unit/spec/services/AddressBook.ts +37 -6
  273. package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
  274. package/test/unit/spec/services/ApiAiAssistant.ts +311 -21
  275. package/test/unit/spec/services/EntryPoint.ts +87 -40
  276. package/test/unit/spec/services/Queue.ts +123 -12
  277. package/test/unit/spec/services/UserPreference.ts +401 -0
  278. package/test/unit/spec/services/WebCallingService.ts +7 -1
  279. package/test/unit/spec/services/WebexCrossClientService.ts +467 -0
  280. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
  281. package/test/unit/spec/services/config/Util.ts +129 -0
  282. package/test/unit/spec/services/config/index.ts +97 -29
  283. package/test/unit/spec/services/core/Utils.ts +425 -8
  284. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  285. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  286. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  287. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  288. package/test/unit/spec/services/task/Task.ts +745 -0
  289. package/test/unit/spec/services/task/TaskFactory.ts +99 -0
  290. package/test/unit/spec/services/task/TaskManager.ts +2518 -1494
  291. package/test/unit/spec/services/task/TaskUtils.ts +235 -0
  292. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  293. package/test/unit/spec/services/task/contact.ts +33 -0
  294. package/test/unit/spec/services/task/dialer.ts +190 -0
  295. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  296. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +4060 -0
  297. package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
  298. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  299. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
  300. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  301. package/test/unit/spec/services/task/voice/Voice.ts +2061 -0
  302. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  303. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +603 -0
  304. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
  305. package/test/unit/spec/wellnessSampleUtils.ts +288 -0
  306. package/umd/contact-center.min.js +2 -2
  307. package/umd/contact-center.min.js.map +1 -1
  308. package/dist/services/task/index.js +0 -1525
  309. package/dist/services/task/index.js.map +0 -1
  310. package/dist/types/services/task/index.d.ts +0 -650
  311. package/src/services/task/index.ts +0 -1801
  312. package/test/unit/spec/services/task/index.ts +0 -2184
@@ -0,0 +1,2247 @@
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`; `WRAPPING_UP` entry emits `task:wrapup`
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`; `WRAPPING_UP` entry emits `task:wrapup`
992
+
993
+ - `PAUSE_RECORDING` / `RESUME_RECORDING` -> Stay `CONNECTED`
994
+
995
+ - Guard: none
996
+
997
+ - Actions: `updateTaskData`, `setRecordingState`, `emitTaskRecordingPaused` / `emitTaskRecordingResumed`
998
+
999
+ - `EXIT_CONFERENCE_SUCCESS` -> `WRAPPING_UP` or `TERMINATED`
1000
+
1001
+ - Guard: `guards.shouldWrapUp` (wrap-up branch) or default (terminate)
1002
+
1003
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, or `emitTaskEnd`; `WRAPPING_UP` entry emits `task:wrapup` (same as `CONFERENCING`; Voice `exitConference()` while actor stays `CONNECTED` with conference in task data)
1004
+
1005
+ **Description**: Main call is on hold.
1006
+
1007
+ **How this state is reached (incoming transitions)**:
1008
+
1009
+ - `IDLE --HYDRATE--> HELD`
1010
+
1011
+ - Guard: `guards.isInteractionHeld`
1012
+
1013
+ - Actions: `updateTaskData`, `emitTaskHydrate`
1014
+
1015
+ - `HOLD_INITIATING --HOLD_SUCCESS--> HELD`
1016
+
1017
+ - Guard: none
1018
+
1019
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold`
1020
+
1021
+ - `RESUME_INITIATING --UNHOLD_FAILED--> HELD`
1022
+
1023
+ - Guard: none
1024
+
1025
+ - Actions: none
1026
+
1027
+ - `CONSULTING --CONSULT_END--> HELD`
1028
+
1029
+ - Guard: inline `context.consultInitiator === true`
1030
+
1031
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
1032
+
1033
+ - `CONSULT_INITIATING --CONSULT_FAILED--> HELD`
1034
+
1035
+ - Guard: `guards.isPrimaryMediaOnHold`
1036
+
1037
+ - Actions: `updateTaskData`, `handleConsultFailed`
1038
+
1039
+ - `CONSULT_INITIATING --CTQ_CANCEL--> HELD`
1040
+
1041
+ - Guard: `guards.isPrimaryMediaOnHold`
1042
+
1043
+ - Actions: `updateTaskData`, `clearConsultState`
1044
+
1045
+ **Valid transitions from `HELD`**:
1046
+
1047
+ - `UNHOLD_INITIATED` -> `RESUME_INITIATING`
1048
+
1049
+ - Guard: none
1050
+
1051
+ - Actions: none
1052
+
1053
+ - `CONSULT` -> `CONSULT_INITIATING`
1054
+
1055
+ - Guard: none
1056
+
1057
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1058
+
1059
+ - `TRANSFER_SUCCESS` -> `WRAPPING_UP`
1060
+
1061
+ - Guard: `guards.shouldWrapUpOrIsInitiator`
1062
+
1063
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
1064
+
1065
+ - `TRANSFER_SUCCESS` -> `CONNECTED` (receiver/default branch)
1066
+
1067
+ - Guard: default
1068
+
1069
+ - Actions: `updateTaskData`, `clearConsultState`
1070
+
1071
+ - `TRANSFER_FAILED` -> stay `HELD`
1072
+
1073
+ - Guard: none
1074
+
1075
+ - Actions: `updateTaskData`
1076
+
1077
+ - `CONTACT_ENDED` -> `CONFERENCING`
1078
+
1079
+ - Guard: `guards.conferenceInProgressFromEvent`
1080
+
1081
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1082
+
1083
+ - `CONTACT_ENDED` -> `WRAPPING_UP`
1084
+
1085
+ - Guard: `guards.shouldWrapUp`
1086
+
1087
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`, `requestCleanup`
1088
+
1089
+ - `CONTACT_ENDED` -> `TERMINATED` (default branch)
1090
+
1091
+ - Guard: default
1092
+
1093
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskEnd`
1094
+
1095
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1096
+
1097
+ - Guard: none
1098
+
1099
+ - Actions: `updateTaskData`, `markEnded`; `WRAPPING_UP` entry emits `task:wrapup`
1100
+
1101
+ - `EXIT_CONFERENCE_SUCCESS` -> `WRAPPING_UP` or `TERMINATED`
1102
+
1103
+ - Guard: `guards.shouldWrapUp` or default
1104
+
1105
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, or `emitTaskEnd`; `WRAPPING_UP` entry emits `task:wrapup` (shared with `CONNECTED` / `CONFERENCING`)
1106
+
1107
+ **Description**: Hold request has been sent and is awaiting backend confirmation.
1108
+
1109
+ **How this state is reached (incoming transitions)**:
1110
+
1111
+ - `CONNECTED --HOLD_INITIATED--> HOLD_INITIATING`
1112
+
1113
+ - Guard: none
1114
+
1115
+ - Actions: none
1116
+
1117
+ **Valid transitions from `HOLD_INITIATING`**:
1118
+
1119
+ - `HOLD_SUCCESS` -> `HELD`
1120
+
1121
+ - Guard: none
1122
+
1123
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold`
1124
+
1125
+ - `HOLD_FAILED` -> `CONNECTED`
1126
+
1127
+ - Guard: none
1128
+
1129
+ - Actions: `updateTaskData`
1130
+
1131
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1132
+
1133
+ - Guard: none
1134
+
1135
+ - Actions: `updateTaskData`, `markEnded` (`WRAPPING_UP` entry emits `task:wrapup`)
1136
+
1137
+ **Description**: Resume/unhold request has been sent and is awaiting backend confirmation.
1138
+
1139
+ **How this state is reached (incoming transitions)**:
1140
+
1141
+ - `HELD --UNHOLD_INITIATED--> RESUME_INITIATING`
1142
+
1143
+ - Guard: none
1144
+
1145
+ - Actions: none
1146
+
1147
+ **Valid transitions from `RESUME_INITIATING`**:
1148
+
1149
+ - `UNHOLD_SUCCESS` -> `CONNECTED`
1150
+
1151
+ - Guard: none
1152
+
1153
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskResume`
1154
+
1155
+ - `UNHOLD_FAILED` -> `HELD`
1156
+
1157
+ - Guard: none
1158
+
1159
+ - Actions: none
1160
+
1161
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1162
+
1163
+ - Guard: none
1164
+
1165
+ - Actions: `updateTaskData`, `markEnded` (`WRAPPING_UP` entry emits `task:wrapup`)
1166
+
1167
+ **Description**: Consult request is in-flight.
1168
+
1169
+ **How this state is reached (incoming transitions)**:
1170
+
1171
+ - `CONNECTED --CONSULT--> CONSULT_INITIATING`
1172
+
1173
+ - Guard: none
1174
+
1175
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1176
+
1177
+ - `HELD --CONSULT--> CONSULT_INITIATING`
1178
+
1179
+ - Guard: none
1180
+
1181
+ - Actions: `setConsultInitiator`, `setConsultDestination`
1182
+
1183
+ - `CONFERENCING --CONSULT--> CONSULT_INITIATING`
1184
+
1185
+ - Guard: none
1186
+
1187
+ - Actions: `setConsultInitiator`, `setConsultDestination`, `setConsultFromConference`
1188
+
1189
+ **Valid transitions from `CONSULT_INITIATING`**:
1190
+
1191
+ - `CONSULT_SUCCESS` -> `CONSULTING`
1192
+
1193
+ - Guard: none
1194
+
1195
+ - Actions: `updateTaskData`, `setConsultInitiator`
1196
+
1197
+ - `CONSULT_FAILED` -> `CONFERENCING`
1198
+
1199
+ - Guard: inline `context.consultFromConference === true`
1200
+
1201
+ - Actions: `updateTaskData`, `handleConsultFailed`
1202
+
1203
+ - `CONSULT_FAILED` -> `HELD`
1204
+
1205
+ - Guard: `guards.isPrimaryMediaOnHold`
1206
+
1207
+ - Actions: `updateTaskData`, `handleConsultFailed`
1208
+
1209
+ - `CONSULT_FAILED` -> `CONNECTED` (default branch)
1210
+
1211
+ - Guard: default
1212
+
1213
+ - Actions: `updateTaskData`, `handleConsultFailed`
1214
+
1215
+ - `CTQ_CANCEL` -> `HELD`
1216
+
1217
+ - Guard: `guards.isPrimaryMediaOnHold`
1218
+
1219
+ - Actions: `updateTaskData`, `clearConsultState`
1220
+
1221
+ - `CTQ_CANCEL` -> `CONNECTED` (default branch)
1222
+
1223
+ - Guard: default
1224
+
1225
+ - Actions: `updateTaskData`, `clearConsultState`
1226
+
1227
+ - `HOLD_SUCCESS` -> stay `CONSULT_INITIATING`
1228
+
1229
+ - Guard: none
1230
+
1231
+ - Actions: `updateTaskData`
1232
+
1233
+ - `HOLD_FAILED` -> `CONNECTED`
1234
+
1235
+ - Guard: none
1236
+
1237
+ - Actions: `updateTaskData`, `handleConsultFailed`
1238
+
1239
+ **Description**: Agent is in active consult leg.
1240
+
1241
+ **How this state is reached (incoming transitions)**:
1242
+
1243
+ - `IDLE --HYDRATE--> CONSULTING`
1244
+
1245
+ - Guard: `guards.isInteractionConsulting`
1246
+
1247
+ - Actions: `updateTaskData`, `emitTaskHydrate`
1248
+
1249
+ - `OFFERED --CONSULTING_ACTIVE--> CONSULTING`
1250
+
1251
+ - Guard: none
1252
+
1253
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsultAccepted`, `emitTaskConsulting`
1254
+
1255
+ - `CONSULT_INITIATING --CONSULT_SUCCESS--> CONSULTING`
1256
+
1257
+ - Guard: none
1258
+
1259
+ - Actions: `updateTaskData`, `setConsultInitiator`
1260
+
1261
+ - `CONF_INITIATING --CONFERENCE_FAILED--> CONSULTING`
1262
+
1263
+ - Guard: none
1264
+
1265
+ - Actions: none
1266
+
1267
+ **Valid transitions from `CONSULTING`**:
1268
+
1269
+ - `CONSULTING_ACTIVE` -> stay `CONSULTING`
1270
+
1271
+ - Guard: none
1272
+
1273
+ - Actions: `updateTaskData`, `setConsultAgentJoined`, `emitTaskConsulting`
1274
+
1275
+ - `CONSULT_END` -> `CONFERENCING`
1276
+
1277
+ - Guard: inline `context.consultInitiator === true && context.consultFromConference === true`
1278
+
1279
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
1280
+
1281
+ - `CONSULT_END` -> `HELD`
1282
+
1283
+ - Guard: inline `context.consultInitiator === true`
1284
+
1285
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConsultEnd`
1286
+
1287
+ - `CONSULT_END` -> `TERMINATED` (default branch)
1288
+
1289
+ - Guard: default
1290
+
1291
+ - Actions: `updateTaskData`
1292
+
1293
+ - `HOLD_SUCCESS` -> stay `CONSULTING`
1294
+
1295
+ - Guard: none
1296
+
1297
+ - Actions: `updateTaskData`, `setHoldState`, `setConsultCallHeld`
1298
+
1299
+ - `UNHOLD_SUCCESS` -> stay `CONSULTING`
1300
+
1301
+ - Guard: none
1302
+
1303
+ - Actions: `updateTaskData`, `setHoldState`, `clearConsultCallHeld`
1304
+
1305
+ - `TRANSFER_SUCCESS` -> `WRAPPING_UP`
1306
+
1307
+ - Guard: `guards.shouldWrapUpOrIsInitiator`
1308
+
1309
+ - Actions: `updateTaskData`, `markEnded`, `emitTaskWrapup`
1310
+
1311
+ - `TRANSFER_SUCCESS` -> `CONNECTED` (receiver/default branch)
1312
+
1313
+ - Guard: default
1314
+
1315
+ - Actions: `updateTaskData`, `clearConsultState`
1316
+
1317
+ - `TRANSFER_FAILED` -> stay `CONSULTING`
1318
+
1319
+ - Guard: none
1320
+
1321
+ - Actions: `updateTaskData`
1322
+
1323
+ - `TRANSFER_CONFERENCE` -> stay `CONSULTING`
1324
+
1325
+ - Guard: none
1326
+
1327
+ - Actions: `setTransferConferenceRequested`, `emitTaskTransferConference`
1328
+
1329
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `WRAPPING_UP`
1330
+
1331
+ - Guard: `guards.shouldWrapUp`
1332
+
1333
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`; `WRAPPING_UP` entry emits `task:wrapup`
1334
+
1335
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `CONFERENCING`
1336
+
1337
+ - Guard: `!guards.isSelfConferenceTransferInitiator`
1338
+
1339
+ - Actions: `updateTaskData`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1340
+
1341
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `TERMINATED` (default branch)
1342
+
1343
+ - Guard: default
1344
+
1345
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`, `emitTaskEnd`
1346
+
1347
+ - `TRANSFER_CONFERENCE_FAILED` -> stay `CONSULTING`
1348
+
1349
+ - Guard: none
1350
+
1351
+ - Actions: `clearTransferConferenceRequested`
1352
+
1353
+ - `PARTICIPANT_LEAVE` -> `WRAPPING_UP`
1354
+
1355
+ - Guard: `guards.didCurrentAgentLeaveMainInteraction && guards.shouldWrapUp`
1356
+
1357
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft` (entry emits `task:wrapup`)
1358
+
1359
+ - `PARTICIPANT_LEAVE` -> `TERMINATED`
1360
+
1361
+ - Guard: `guards.didCurrentAgentLeaveMainInteraction`
1362
+
1363
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskEnd`
1364
+
1365
+ - `PARTICIPANT_LEAVE` -> `CONNECTED`
1366
+
1367
+ - Guard: `guards.shouldDowngradeConferenceToConnected` (after current-agent departure guards)
1368
+
1369
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskConferenceEnded`
1370
+
1371
+ - `PARTICIPANT_LEAVE` -> stay `CONSULTING` (default)
1372
+
1373
+ - Guard: default
1374
+
1375
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `emitTaskParticipantLeft`
1376
+
1377
+ - `ASSIGN` -> `CONNECTED`
1378
+
1379
+ - Guard: none
1380
+
1381
+ - Actions: `updateTaskData`, `emitTaskAssigned`
1382
+
1383
+ - `CONTACT_ENDED` -> `WRAPPING_UP`
1384
+
1385
+ - Guard: none
1386
+
1387
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskWrapup`, `requestCleanup`
1388
+
1389
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1390
+
1391
+ - Guard: none
1392
+
1393
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`; `WRAPPING_UP` entry emits `task:wrapup`
1394
+
1395
+ - `MERGE_TO_CONFERENCE` -> `CONF_INITIATING`
1396
+
1397
+ - Guard: none
1398
+
1399
+ - Actions: none
1400
+
1401
+ - `CONFERENCE_START` -> `CONFERENCING`
1402
+
1403
+ - Guard: none
1404
+
1405
+ - Actions: `handleConferenceStarted`, `clearConsultState`
1406
+
1407
+ **Description**: Conference merge is being established.
1408
+
1409
+ **How this state is reached (incoming transitions)**:
1410
+
1411
+ - `CONSULTING --MERGE_TO_CONFERENCE--> CONF_INITIATING`
1412
+
1413
+ - Guard: none
1414
+
1415
+ - Actions: none
1416
+
1417
+ **Valid transitions from `CONF_INITIATING`**:
1418
+
1419
+ - `CONFERENCE_START` -> `CONFERENCING`
1420
+
1421
+ - Guard: none
1422
+
1423
+ - Actions: `handleConferenceStarted`
1424
+
1425
+ - `CONFERENCE_FAILED` -> `CONSULTING`
1426
+
1427
+ - Guard: none
1428
+
1429
+ - Actions: none
1430
+
1431
+ **Description**: Active conference call state.
1432
+
1433
+ **How this state is reached (incoming transitions)**:
1434
+
1435
+ - `IDLE --HYDRATE--> CONFERENCING`
1436
+
1437
+ - Guard: `guards.isConferencingByParticipants`
1438
+
1439
+ - Actions: `updateTaskData`, `emitTaskHydrate`
1440
+
1441
+ - `CONNECTED --CONTACT_ENDED--> CONFERENCING`
1442
+
1443
+ - Guard: `guards.conferenceInProgressFromEvent`
1444
+
1445
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1446
+
1447
+ - `HELD --CONTACT_ENDED--> CONFERENCING`
1448
+
1449
+ - Guard: `guards.conferenceInProgressFromEvent`
1450
+
1451
+ - Actions: `updateTaskData`, `emitTaskConferenceStarted`, `requestCleanup`
1452
+
1453
+ - `CONSULTING --CONFERENCE_START--> CONFERENCING`
1454
+
1455
+ - Guard: none
1456
+
1457
+ - Actions: `handleConferenceStarted`, `clearConsultState`
1458
+
1459
+ - `CONF_INITIATING --CONFERENCE_START--> CONFERENCING`
1460
+
1461
+ - Guard: none
1462
+
1463
+ - Actions: `handleConferenceStarted`
1464
+
1465
+ - `CONSULTING --TRANSFER_CONFERENCE_SUCCESS--> CONFERENCING`
1466
+
1467
+ - Guard: `!guards.isSelfConferenceTransferInitiator`
1468
+
1469
+ - Actions: `updateTaskData`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1470
+
1471
+ **Valid transitions from `CONFERENCING`**:
1472
+
1473
+ - `CONSULT` -> `CONSULT_INITIATING`
1474
+
1475
+ - Guard: none
1476
+
1477
+ - Actions: `setConsultInitiator`, `setConsultDestination`, `setConsultFromConference`
1478
+
1479
+ - `CONFERENCE_START` -> stay `CONFERENCING`
1480
+
1481
+ - Guard: none
1482
+
1483
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConferenceStarted`
1484
+
1485
+ - `EXIT_CONFERENCE_SUCCESS` -> `WRAPPING_UP` or `TERMINATED`
1486
+
1487
+ - Guard: `guards.shouldWrapUp` or default
1488
+
1489
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, or `emitTaskEnd`; `WRAPPING_UP` entry emits `task:wrapup` (shared with `CONNECTED` / `HELD`)
1490
+
1491
+ - `CONSULT_END` -> stay `CONFERENCING`
1492
+
1493
+ - Guard: none
1494
+
1495
+ - Actions: `updateTaskData`, `clearConsultState`
1496
+
1497
+ - `HOLD_SUCCESS` / `UNHOLD_SUCCESS` -> stay `CONFERENCING`
1498
+
1499
+ - Guard: none
1500
+
1501
+ - Actions: `updateTaskData`, `setHoldState`, `emitTaskHold` / `emitTaskResume`
1502
+
1503
+ - `TRANSFER_CONFERENCE` -> stay `CONFERENCING`
1504
+
1505
+ - Guard: none
1506
+
1507
+ - Actions: `setTransferConferenceRequested`, `emitTaskTransferConference`
1508
+
1509
+ - `TRANSFER_CONFERENCE_SUCCESS` -> stay `CONFERENCING`
1510
+
1511
+ - Guard: `guards.isPassiveConferenceTransferObserver`
1512
+
1513
+ - Actions: `updateTaskData`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1514
+
1515
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `WRAPPING_UP`
1516
+
1517
+ - Guard: `guards.shouldWrapUp`
1518
+
1519
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`; `WRAPPING_UP` entry emits `task:wrapup`
1520
+
1521
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `CONFERENCING`
1522
+
1523
+ - Guard: `!guards.isSelfConferenceTransferInitiator`
1524
+
1525
+ - Actions: `updateTaskData`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`
1526
+
1527
+ - `TRANSFER_CONFERENCE_SUCCESS` -> `TERMINATED` (default branch)
1528
+
1529
+ - Guard: default
1530
+
1531
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `handleTransferConferenceSuccess`, `clearTransferConferenceRequested`, `emitTaskEnd`
1532
+
1533
+ - `TRANSFER_CONFERENCE_FAILED` -> stay `CONFERENCING`
1534
+
1535
+ - Guard: none
1536
+
1537
+ - Actions: `clearTransferConferenceRequested`
1538
+
1539
+ - `PARTICIPANT_LEAVE` -> `WRAPPING_UP`
1540
+
1541
+ - Guard: `guards.didCurrentAgentLeaveMainInteraction && guards.shouldWrapUp`
1542
+
1543
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft` (entry emits `task:wrapup`)
1544
+
1545
+ - `PARTICIPANT_LEAVE` -> `TERMINATED`
1546
+
1547
+ - Guard: `guards.didCurrentAgentLeaveMainInteraction`
1548
+
1549
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `markEnded`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskEnd`
1550
+
1551
+ - `PARTICIPANT_LEAVE` -> `CONNECTED`
1552
+
1553
+ - Guard: `guards.shouldDowngradeConferenceToConnected`
1554
+
1555
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `clearConsultState`, `emitTaskParticipantLeft`, `emitTaskConferenceEnded`
1556
+
1557
+ - `PARTICIPANT_LEAVE` -> stay `CONFERENCING` (default)
1558
+
1559
+ - Guard: default
1560
+
1561
+ - Actions: `updateTaskData`, `handleParticipantLeft`, `emitTaskParticipantLeft`
1562
+
1563
+ Customer hangup and agent-initiated customer Drop share this backend event.
1564
+ When two or more active conference participants survive on the main leg, the
1565
+ customer is removed from the authoritative snapshot while the task remains
1566
+ `CONFERENCING`; no `task:conferenceEnded` event is emitted. When another
1567
+ agent/endpoint leaves, the same rule preserves the remaining conference, and
1568
+ the guarded `CONNECTED` branch is used only after the conference participant
1569
+ count falls below two while the customer remains.
1570
+
1571
+ - `CONFERENCE_END` -> `WRAPPING_UP`
1572
+
1573
+ - Guard: `guards.shouldWrapUp`
1574
+
1575
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`; `WRAPPING_UP` entry emits `task:wrapup`
1576
+
1577
+ - `CONFERENCE_END` -> `CONNECTED`
1578
+
1579
+ - Guard: inline `!context.exitingConference && customerInCall`
1580
+
1581
+ - Actions: `updateTaskData`, `clearConsultState`, `emitTaskConferenceEnded`
1582
+
1583
+ - `CONFERENCE_END` -> `TERMINATED` (default branch)
1584
+
1585
+ - Guard: default
1586
+
1587
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`, `emitTaskEnd`
1588
+
1589
+ - `CONTACT_ENDED` -> stay `CONFERENCING`
1590
+
1591
+ - Guard: none
1592
+
1593
+ - Actions: `updateTaskData`, `requestCleanup`
1594
+
1595
+ - `TASK_WRAPUP` -> `WRAPPING_UP`
1596
+
1597
+ - Guard: `guards.shouldWrapUp`
1598
+
1599
+ - Actions: `updateTaskData`, `markEnded`, `clearConsultState`; `WRAPPING_UP` entry emits `task:wrapup`
1600
+
1601
+ **Description**: Post-interaction work (ACW) is in progress.
1602
+
1603
+ **How this state is reached (incoming transitions)**:
1604
+
1605
+ - Reached from `CONNECTED`, `HELD`, `CONSULTING`, or `CONFERENCING` via `CONTACT_ENDED`, `TASK_WRAPUP`, `TRANSFER_SUCCESS`, `TRANSFER_CONFERENCE_SUCCESS`, `PARTICIPANT_LEAVE`, or `CONFERENCE_END` branches
1606
+
1607
+ - Entry always emits wrapup event after transition
1608
+
1609
+ **Entry Actions**:
1610
+
1611
+ - `emitTaskWrapup`
1612
+
1613
+ **Valid transitions from `WRAPPING_UP`**:
1614
+
1615
+ - `TASK_WRAPUP` -> stay `WRAPPING_UP`
1616
+
1617
+ - Guard: if `context.taskData.wrapUpRequired === true` before the event, actions are `updateTaskData` only; otherwise `updateTaskData`, `emitTaskWrapup` (late AgentWrapup after wrap-up was already published)
1618
+
1619
+ - `WRAPUP_COMPLETE` -> `COMPLETED`
1620
+
1621
+ - Guard: none
1622
+
1623
+ - Actions: `updateTaskData`
1624
+
1625
+ **Guards**: None
1626
+
1627
+ **Description**: Final wrapped-up terminal state.
1628
+
1629
+ **Entry Actions** (order matters — `emitTaskWrappedup` before `cleanupResources` so subscribers receive intact task data):
1630
+
1631
+ - `emitTaskWrappedup`
1632
+
1633
+ - `cleanupResources`
1634
+
1635
+ **How this state is reached (incoming transitions)**:
1636
+
1637
+ - `WRAPPING_UP --WRAPUP_COMPLETE--> COMPLETED`
1638
+
1639
+ - Guard: none
1640
+
1641
+ - Actions: `updateTaskData`
1642
+
1643
+ **Valid transitions from `COMPLETED`**: None (final state)
1644
+
1645
+ **Guards**: None
1646
+
1647
+ **Description**: Final terminated terminal state.
1648
+
1649
+ **Entry Actions**:
1650
+
1651
+ - `cleanupResources`
1652
+
1653
+ **How this state is reached (incoming transitions)**:
1654
+
1655
+ - Reached from `OFFERED`, `CONNECTED`, `HELD`, `CONSULTING`, and `CONFERENCING` via terminating branches (`TASK_WRAPUP`, failure paths, and default end-of-contact/conference branches)
1656
+
1657
+ **Valid transitions from `TERMINATED`**: None (final state)
1658
+
1659
+ **Guards**: None
1660
+
1661
+ Event names below are from `TaskEvent` in `constants.ts`.
1662
+
1663
+ - `TASK_INCOMING`, `TASK_OFFERED`, `HYDRATE`
1664
+
1665
+ - `CONTACT_UPDATED`, `CONTACT_OWNER_CHANGED`
1666
+
1667
+ - `ASSIGN`, `CONTACT_ENDED`, `TASK_WRAPUP`, `WRAPUP_COMPLETE`
1668
+
1669
+ - `HOLD_INITIATED`, `HOLD_SUCCESS`, `HOLD_FAILED`
1670
+
1671
+ - `UNHOLD_INITIATED`, `UNHOLD_SUCCESS`, `UNHOLD_FAILED`
1672
+
1673
+ - `OFFER_CONSULT`, `CONSULT`, `CONSULT_SUCCESS`, `CONSULT_CREATED`
1674
+
1675
+ - `CONSULTING_ACTIVE`, `CONSULT_END`, `CONSULT_FAILED`
1676
+
1677
+ - `CTQ_CANCEL`, `CTQ_CANCEL_FAILED`
1678
+
1679
+ - `MERGE_TO_CONFERENCE`, `CONFERENCE_START`, `CONFERENCE_FAILED`, `CONFERENCE_END`
1680
+
1681
+ - `PARTICIPANT_LEAVE`
1682
+
1683
+ - `TRANSFER_CONFERENCE`, `TRANSFER_CONFERENCE_SUCCESS`, `TRANSFER_CONFERENCE_FAILED`
1684
+
1685
+ - `EXIT_CONFERENCE`, `EXIT_CONFERENCE_SUCCESS`, `EXIT_CONFERENCE_FAILED`
1686
+
1687
+ - `TRANSFER_SUCCESS`, `TRANSFER_FAILED`
1688
+
1689
+ - `RECORDING_STARTED`, `PAUSE_RECORDING`, `RESUME_RECORDING`
1690
+
1691
+ The flow below shows a single transition in the requested form:
1692
+
1693
+ ```mermaid
1694
+ flowchart LR
1695
+ A[User Action/CC Event Mapping] --> B[State Machine Event Trigger]
1696
+ B --> C{Check against Current State: Valid Transition?}
1697
+ C -- No --> X[Ignore/No-op]
1698
+ C -- Yes --> D[StateMachine evaluates guards]
1699
+ D -- No --> Y[Blocked by Guard]
1700
+ D -- Yes --> E[Execute Associated Actions]
1701
+ E --> F[Context updated]
1702
+ F --> G[UI Controls Recomputed]
1703
+ G --> H[Transition to Target State]
1704
+ ```
1705
+
1706
+ ```mermaid
1707
+ flowchart LR
1708
+ A[User invoked hold API] --> B[Event Trigger: HOLD_INITIATED]
1709
+ B --> C{State = CONNECTED?}
1710
+ C -- No --> X[Ignore/No-op]
1711
+ C -- Yes --> D[Guards: none]
1712
+ D -- Yes --> E[Actions: setHoldInitiated + updateTaskData]
1713
+ E --> F[Context updated]
1714
+ F --> G[UI controls recomputed]
1715
+ G --> H[Transition: CONNECTED -> HOLD_INITIATING]
1716
+ H --> I[CC Event: AGENT_CONTACT_HELD]
1717
+ I --> J[Mapped: HOLD_SUCCESS]
1718
+ J --> K{State = HOLD_INITIATING?}
1719
+ K -- No --> X
1720
+ K -- Yes --> L[Guards: none]
1721
+ L -- Yes --> M[Actions: setHoldSuccess + updateTaskData]
1722
+ M --> N[Context updated]
1723
+ N --> O[UI controls recomputed]
1724
+ O --> P[Transition: HOLD_INITIATING -> HELD]
1725
+ ```
1726
+
1727
+ Manage complex task lifecycle with clear states, transitions, guards, and actions.
1728
+
1729
+ ```typescript
1730
+ // Task.ts
1731
+ export default abstract class Task extends EventEmitter {
1732
+ public stateMachineService?: ActorRefFrom<TaskStateMachine>;
1733
+
1734
+ private initializeStateMachine(): void {
1735
+ const machine: TaskStateMachine = createTaskStateMachine(this.uiControlConfig, {
1736
+ actions: this.getStateMachineActionOverrides(),
1737
+ });
1738
+
1739
+ this.stateMachineService = createActor(machine);
1740
+
1741
+ // Subscribe to state changes
1742
+ this.stateMachineService.subscribe((snapshot) => {
1743
+ const currentState = snapshot.value as TaskState;
1744
+ this.state = snapshot;
1745
+ this.updateUiControls(previousState !== currentState);
1746
+ });
1747
+
1748
+ this.stateMachineService.start();
1749
+ }
1750
+
1751
+ public sendStateMachineEvent(event: TaskEventPayload): void {
1752
+ this.stateMachineService?.send(event);
1753
+ }
1754
+ }
1755
+ ```
1756
+
1757
+ ```typescript
1758
+ // state-machine/TaskStateMachine.ts
1759
+ export function getTaskStateMachineConfig(uiControlConfig: UIControlConfig) {
1760
+ return {
1761
+ id: 'taskStateMachine',
1762
+ initial: TaskState.IDLE,
1763
+ context: createInitialContext(uiControlConfig, TaskState.IDLE),
1764
+ states: {
1765
+ [TaskState.IDLE]: {
1766
+ on: {
1767
+ [TaskEvent.TASK_INCOMING]: {
1768
+ target: TaskState.OFFERED,
1769
+ actions: ['initializeTask', 'emitTaskIncoming'],
1770
+ },
1771
+ },
1772
+ },
1773
+ [TaskState.OFFERED]: {
1774
+ on: {
1775
+ [TaskEvent.ASSIGN]: {
1776
+ target: TaskState.CONNECTED,
1777
+ actions: ['updateTaskData', 'emitTaskAssigned'],
1778
+ },
1779
+ [TaskEvent.TASK_WRAPUP]: {
1780
+ target: TaskState.TERMINATED,
1781
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
1782
+ },
1783
+ },
1784
+ },
1785
+ // ... more states
1786
+ },
1787
+ };
1788
+ }
1789
+ ```
1790
+
1791
+ Complete mapping from backend CC_EVENTS to internal TaskEvent types.
1792
+
1793
+ | Backend CC Event | TaskEvent | Typical From State(s) | Target State | Notes / Guards |
1794
+ |---|---|---|---|---|
1795
+ | `AGENT_CONTACT_RESERVED` | `TASK_INCOMING` | `IDLE` | `OFFERED` | Incoming task entry |
1796
+ | `AGENT_OFFER_CONTACT` | `TASK_OFFERED` | `OFFERED` | `OFFERED` | Offer payload refresh |
1797
+ | `AGENT_CONTACT` | `HYDRATE` | `IDLE` | `WRAPPING_UP` / `CONSULTING` / `HELD` / `CONNECTED` / `CONFERENCING` / `IDLE` | Guard-based restore |
1798
+ | `CONTACT_UPDATED` | `CONTACT_UPDATED` or `CONTACT_OWNER_CHANGED` | any | same | Owner delta uses existing hydrate path; same/missing owner is context sync only |
1799
+ | `CONTACT_OWNER_CHANGED` | `CONTACT_OWNER_CHANGED` | any | same | Context/data sync + `task:hydrate` |
1800
+ | `AGENT_OFFER_CONSULT` | `OFFER_CONSULT` | `OFFERED` | `OFFERED` | Receiver-side consult offer |
1801
+ | `AGENT_CONTACT_ASSIGNED` | `ASSIGN` | `OFFERED` / `CONNECTED` / `CONSULTING` | `CONNECTED` | Assign/reassign |
1802
+ | `AGENT_CONTACT_HELD` | `HOLD_SUCCESS` | `HOLD_INITIATING` | `HELD` | Includes `mediaResourceId` |
1803
+ | `AGENT_CONTACT_UNHELD` | `UNHOLD_SUCCESS` | `RESUME_INITIATING` | `CONNECTED` | Includes `mediaResourceId` |
1804
+ | `AGENT_CONSULT_CREATED` | `CONSULT_CREATED` | varies | same | Context + emitter action |
1805
+ | `AGENT_CONSULTING` | `CONSULTING_ACTIVE` | `OFFERED` / `CONSULTING` | `CONSULTING` | Sets consult joined flag |
1806
+ | `AGENT_CONSULT_ENDED` | `CONSULT_END` | `CONSULTING` | `CONFERENCING` / `HELD` / `TERMINATED` | Depends on initiator flags |
1807
+ | `AGENT_CONSULT_FAILED` | `CONSULT_FAILED` | `CONSULT_INITIATING` | `CONFERENCING` / `HELD` / `CONNECTED` | Guard-based fallback |
1808
+ | `AGENT_CTQ_FAILED` | `CONSULT_FAILED` | `CONSULT_INITIATING` | `CONFERENCING` / `HELD` / `CONNECTED` | Same as consult failed |
1809
+ | `AGENT_CTQ_CANCELLED` | `CTQ_CANCEL` | `CONSULT_INITIATING` | `HELD` / `CONNECTED` | Guarded by hold state |
1810
+ | `AGENT_CTQ_CANCEL_FAILED` | `CTQ_CANCEL_FAILED` | varies | same | No transition mapping |
1811
+ | `AGENT_BLIND_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | `shouldWrapUpOrIsInitiator` |
1812
+ | `AGENT_CONSULT_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | Same path |
1813
+ | `AGENT_VTEAM_TRANSFERRED` | `TRANSFER_SUCCESS` | `CONNECTED` / `HELD` / `CONSULTING` | `WRAPPING_UP` / `CONNECTED` | Same path |
1814
+ | `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`) |
1815
+ | `AGENT_BLIND_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1816
+ | `AGENT_VTEAM_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1817
+ | `AGENT_CONSULT_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1818
+ | `AGENT_CONFERENCE_TRANSFER_FAILED` | `TRANSFER_FAILED` | `CONNECTED` / `HELD` / `CONSULTING` | same | Context update |
1819
+ | `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`. |
1820
+ | `AGENT_INVITE_FAILED` | `INVITE_FAILED` | `OFFERED` | `TERMINATED` | Reject path |
1821
+ | `AGENT_CONTACT_ASSIGN_FAILED` | `ASSIGN_FAILED` | `OFFERED` | `TERMINATED` | Reject path |
1822
+ | `AGENT_CONTACT_OFFER_RONA` | `RONA` | `OFFERED` | `TERMINATED` | Timeout path |
1823
+ | `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`. |
1824
+ | `CONTACT_RECORDING_STARTED` | `RECORDING_STARTED` | any | same | Recording state update |
1825
+ | `CONTACT_RECORDING_PAUSED` | `PAUSE_RECORDING` | `CONNECTED` | same | Recording state update |
1826
+ | `CONTACT_RECORDING_RESUMED` | `RESUME_RECORDING` | `CONNECTED` | same | Recording state update |
1827
+ | `AGENT_WRAPPEDUP` | `WRAPUP_COMPLETE` | `WRAPPING_UP` | `COMPLETED` | Final completion |
1828
+ | `AGENT_CONSULT_CONFERENCED` | `CONFERENCE_START` | `CONSULTING` / `CONF_INITIATING` / `CONFERENCING` | `CONFERENCING` / same | Conference established |
1829
+ | `PARTICIPANT_JOINED_CONFERENCE` | `CONFERENCE_START` | `CONSULTING` / `CONF_INITIATING` / `CONFERENCING` | `CONFERENCING` / same | Conference participant joined |
1830
+ | `AGENT_CONSULT_CONFERENCE_FAILED` | `CONFERENCE_FAILED` | `CONF_INITIATING` | `CONSULTING` | Merge fail fallback |
1831
+ | `AGENT_CONSULT_CONFERENCE_ENDED` | `CONFERENCE_END` | `CONFERENCING` | `WRAPPING_UP` / `CONNECTED` / `TERMINATED` | Guard-driven |
1832
+ | `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 |
1833
+ | `AGENT_CONFERENCE_TRANSFERRED` | `TRANSFER_CONFERENCE_SUCCESS` | `CONSULTING` / `CONFERENCING` | `WRAPPING_UP` / `CONFERENCING` / `TERMINATED` / same | Initiator/receiver dependent |
1834
+
1835
+ - `AGENT_CONTACT_UNASSIGNED` -> returns `null` in mapper (`TaskManager.mapEventToTaskStateMachineEvent`)
1836
+
1837
+ | Backend Event | TaskEvent | State Transition | Notes |
1838
+ |---|---|---|---|
1839
+ | `AgentContactReserved` | `TASK_INCOMING` | IDLE → OFFERED | New task offered |
1840
+ | `AgentOfferContact` | `TASK_OFFERED` | Stay in OFFERED | Offer confirmation |
1841
+ | `AgentContact` | `HYDRATE` | Various | State restoration |
1842
+ | `AgentContactAssigned` | `ASSIGN` | OFFERED → CONNECTED (also CONNECTED/CONSULTING refresh paths) | Task accepted/reassigned |
1843
+ | `ContactUpdated` | `CONTACT_UPDATED` or `CONTACT_OWNER_CHANGED` | No change | Changed non-empty owner also emits `task:hydrate`; same/missing owner is data-only |
1844
+ | `ContactOwnerChanged` | `CONTACT_OWNER_CHANGED` | No change | Owner update plus `task:hydrate` emission |
1845
+ | `ContactEnded` | `CONTACT_ENDED` | Guard-based branch | CONFERENCING / WRAPPING_UP / TERMINATED / stay |
1846
+ | `AgentContactUnassigned` | None | N/A | Handled by other events |
1847
+
1848
+ | Backend Event | TaskEvent | State Transition | Context Update |
1849
+ |---|---|---|---|
1850
+ | `AgentContactHeld` | `HOLD_SUCCESS` | HOLD_INITIATING → HELD | `setHoldState` updates `taskData.interaction.media[mediaResourceId].isHold = true` |
1851
+ | `AgentContactUnheld` | `UNHOLD_SUCCESS` | RESUME_INITIATING → CONNECTED | `setHoldState` updates `taskData.interaction.media[mediaResourceId].isHold = false` |
1852
+ | `AgentContactHoldFailed` | `HOLD_FAILED` | HOLD_INITIATING → CONNECTED | Context refreshed |
1853
+ | `AgentContactUnholdFailed` | `UNHOLD_FAILED` | RESUME_INITIATING → HELD | No transition action |
1854
+
1855
+ | Backend Event / API | TaskEvent | State Transition | Context Update |
1856
+ |---|---|---|---|
1857
+ | API `task.consult(...)` | `CONSULT` | CONNECTED/HELD/CONFERENCING → CONSULT_INITIATING | Sets consult initiator + destination (and `consultFromConference` in conference flow) |
1858
+ | `AgentOfferConsult` | `OFFER_CONSULT` | OFFERED → OFFERED | Offer-only path |
1859
+ | `AgentConsultCreated` | `CONSULT_CREATED` | No state transition wiring | Event exists but not consumed by transition table |
1860
+ | `AgentConsulting` | `CONSULTING_ACTIVE` | OFFERED → CONSULTING, CONSULTING → CONSULTING | Sets `consultDestinationAgentJoined` |
1861
+ | `AgentConsultEnded` | `CONSULT_END` | CONSULTING → CONFERENCING / HELD / TERMINATED | Depends on initiator and consult-from-conference |
1862
+ | `AgentConsultFailed` / `AgentCtqFailed` | `CONSULT_FAILED` | CONSULT_INITIATING → CONFERENCING / HELD / CONNECTED | Guard-based fallback |
1863
+ | `AgentCtqCancelled` | `CTQ_CANCEL` | CONSULT_INITIATING → HELD / CONNECTED | Guarded by `isPrimaryMediaOnHold` |
1864
+ | `AgentCtqCancelFailed` | `CTQ_CANCEL_FAILED` | No state transition wiring | Event mapped but not consumed |
1865
+
1866
+ | Backend Event | TaskEvent | State Transition | Wrapup Logic |
1867
+ |---|---|---|---|
1868
+ | `AgentBlindTransferred` | `TRANSFER_SUCCESS` | → WRAPPING_UP/CONNECTED | Guard `shouldWrapUpOrIsInitiator` decides wrapup vs receiver/default path |
1869
+ | `AgentVTeamTransferred` | `TRANSFER_SUCCESS` | → WRAPPING_UP/CONNECTED | Same transition logic as blind transfer |
1870
+ | `AgentConsultTransferred` | `TRANSFER_SUCCESS` | CONSULTING → WRAPPING_UP/CONNECTED | Initiator/wrapup path vs receiver/default path |
1871
+ | `AgentBlindTransferFailed` | `TRANSFER_FAILED` | No change | Emit failure, stay in current state |
1872
+ | `AgentVTeamTransferFailed` | `TRANSFER_FAILED` | No change | Queue transfer failed |
1873
+ | `AgentConsultTransferFailed` | `TRANSFER_FAILED` | No change | Consult transfer failed |
1874
+
1875
+ | Backend Event / API | TaskEvent | State Transition | Context Update |
1876
+ |---|---|---|---|
1877
+ | API `task.consultConference()` | `MERGE_TO_CONFERENCE` | CONSULTING → CONF_INITIATING | Starts merge flow |
1878
+ | API `task.dropConferenceParticipant()` | None | No direct state transition | AQM waits for backend `ParticipantLeftConference`; that event follows the existing `PARTICIPANT_LEAVE` path |
1879
+ | `AgentConsultConferenced` | `CONFERENCE_START` | CONSULTING/CONF_INITIATING → CONFERENCING | `handleConferenceStarted` path |
1880
+ | `ParticipantJoinedConference` | `CONFERENCE_START` | CONFERENCING → CONFERENCING | Refresh + emit conference started |
1881
+ | `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` |
1882
+ | `AgentConsultConferenceEnded` | `CONFERENCE_END` | CONFERENCING → WRAPPING_UP / CONNECTED / TERMINATED | Guard-based branch |
1883
+ | `AgentConsultConferenceFailed` | `CONFERENCE_FAILED` | CONF_INITIATING → CONSULTING | Merge failed fallback |
1884
+ | `AgentConferenceTransferred` | `TRANSFER_CONFERENCE_SUCCESS` | CONSULTING/CONFERENCING branch logic | Initiator/receiver dependent |
1885
+ | API/SDK conference transfer fail | `TRANSFER_CONFERENCE_FAILED` | CONSULTING/CONFERENCING stay | Clears transfer request flag |
1886
+
1887
+ | Backend Event | TaskEvent | State Transition | Context Update |
1888
+ |---|---|---|---|
1889
+ | `ContactRecordingStarted` | `RECORDING_STARTED` | No change | Update recording state |
1890
+ | `ContactRecordingPaused` | `PAUSE_RECORDING` | No change | Mark recording paused |
1891
+ | `ContactRecordingResumed` | `RESUME_RECORDING` | No change | Mark recording active |
1892
+
1893
+ | Backend Event | TaskEvent | State Transition | Notes |
1894
+ |---|---|---|---|
1895
+ | `AgentWrapup` | `TASK_WRAPUP` | → WRAPPING_UP | Enter ACW |
1896
+ | `AgentWrappedup` | `WRAPUP_COMPLETE` | WRAPPING_UP → COMPLETED | Complete wrapup |
1897
+
1898
+ This diagram represents state-to-state lifecycle transitions for the task state machine.
1899
+
1900
+ ```mermaid
1901
+ stateDiagram-v2
1902
+ [*] --> IDLE
1903
+
1904
+ %% IDLE
1905
+ IDLE --> OFFERED: AGENT_CONTACT_RESERVED (CC Event) -> TASK_INCOMING (State Machine Event)
1906
+
1907
+ %% OFFERED
1908
+ OFFERED --> OFFERED: AGENT_OFFER_CONTACT (CC Event) -> TASK_OFFERED (State Machine Event)
1909
+ OFFERED --> OFFERED: AGENT_OFFER_CONSULT (CC Event) -> OFFER_CONSULT (State Machine Event)
1910
+ OFFERED --> CONNECTED: AGENT_CONTACT_ASSIGNED (CC Event) -> ASSIGN (State Machine Event)
1911
+ OFFERED --> CONSULTING: AGENT_CONSULTING (CC Event) -> CONSULTING_ACTIVE (State Machine Event)
1912
+ OFFERED --> TERMINATED: AGENT_CONTACT_OFFER_RONA (CC Event) -> RONA (State Machine Event)
1913
+ OFFERED --> TERMINATED: AGENT_CONTACT_ASSIGN_FAILED (CC Event) -> ASSIGN_FAILED (State Machine Event)
1914
+ OFFERED --> TERMINATED: AGENT_INVITE_FAILED (CC Event) -> INVITE_FAILED (State Machine Event)
1915
+ OFFERED --> TERMINATED: AGENT_OUTBOUND_FAILED (CC Event) -> OUTBOUND_FAILED (State Machine Event)
1916
+ OFFERED --> WRAPPING_UP: AGENT_WRAPUP (CC Event) -> TASK_WRAPUP (State Machine Event) [shouldWrapUp]
1917
+ OFFERED --> TERMINATED: AGENT_WRAPUP (CC Event) -> TASK_WRAPUP (State Machine Event)
1918
+
1919
+ %% CONNECTED
1920
+ CONNECTED --> HOLD_INITIATING: API hold() -> HOLD_INITIATED (State Machine Event)
1921
+ CONNECTED --> CONSULT_INITIATING: API consult() -> CONSULT (State Machine Event)
1922
+ CONNECTED --> WRAPPING_UP: AGENT_*TRANSFERRED (CC Event) -> TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1923
+ CONNECTED --> CONNECTED: AGENT_*TRANSFERRED (CC Event) -> TRANSFER_SUCCESS (State Machine Event) [receiver]
1924
+ CONNECTED --> CONNECTED: AGENT_*TRANSFER_FAILED (CC Event) -> TRANSFER_FAILED (State Machine Event)
1925
+ CONNECTED --> CONFERENCING: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [conferenceInProgressFromEvent]
1926
+ CONNECTED --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [shouldWrapUp]
1927
+ CONNECTED --> TERMINATED: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [default]
1928
+ CONNECTED --> WRAPPING_UP: AGENT_WRAPUP (CC Event) -> TASK_WRAPUP (State Machine Event)
1929
+
1930
+ %% HOLD_INITIATING
1931
+ HOLD_INITIATING --> HELD: HOLD_SUCCESS (State Machine Event)
1932
+ HOLD_INITIATING --> CONNECTED: HOLD_FAILED (State Machine Event)
1933
+
1934
+ %% HELD
1935
+ HELD --> RESUME_INITIATING: UNHOLD_INITIATED (State Machine Event)
1936
+ HELD --> CONSULT_INITIATING: CONSULT (State Machine Event)
1937
+ HELD --> WRAPPING_UP: TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1938
+ HELD --> CONNECTED: TRANSFER_SUCCESS (State Machine Event) [receiver]
1939
+ HELD --> CONFERENCING: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [conferenceInProgressFromEvent]
1940
+ HELD --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [shouldWrapUp]
1941
+ HELD --> TERMINATED: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) [default]
1942
+ HELD --> WRAPPING_UP: TASK_WRAPUP (State Machine Event)
1943
+ HELD --> HELD: TRANSFER_FAILED (State Machine Event)
1944
+
1945
+ %% RESUME_INITIATING
1946
+ RESUME_INITIATING --> CONNECTED: UNHOLD_SUCCESS (State Machine Event)
1947
+ RESUME_INITIATING --> HELD: UNHOLD_FAILED (State Machine Event)
1948
+
1949
+ %% CONSULT_INITIATING
1950
+ CONSULT_INITIATING --> CONSULTING: CONSULT_SUCCESS (State Machine Event)
1951
+ CONSULT_INITIATING --> CONFERENCING: CONSULT_FAILED (State Machine Event) [consultFromConference]
1952
+ CONSULT_INITIATING --> HELD: CONSULT_FAILED / CTQ_CANCEL (State Machine Event) [isPrimaryMediaOnHold]
1953
+ CONSULT_INITIATING --> CONNECTED: HOLD_FAILED / CONSULT_FAILED / CTQ_CANCEL (State Machine Event) [default]
1954
+ CONSULT_INITIATING --> CONSULT_INITIATING: HOLD_SUCCESS (State Machine Event)
1955
+
1956
+ %% CONSULTING
1957
+ CONSULTING --> CONFERENCING: CONSULT_END (State Machine Event) [consultInitiator && consultFromConference]
1958
+ CONSULTING --> HELD: CONSULT_END (State Machine Event) [consultInitiator]
1959
+ CONSULTING --> TERMINATED: CONSULT_END (State Machine Event) [consulted agent]
1960
+ CONSULTING --> WRAPPING_UP: TRANSFER_SUCCESS (State Machine Event) [shouldWrapUpOrIsInitiator]
1961
+ CONSULTING --> CONNECTED: TRANSFER_SUCCESS (State Machine Event) [receiver]
1962
+ CONSULTING --> WRAPPING_UP: CONTACT_ENDED (CC Event) -> CONTACT_ENDED (State Machine Event) / TASK_WRAPUP (State Machine Event)
1963
+ CONSULTING --> CONNECTED: ASSIGN (State Machine Event)
1964
+ CONSULTING --> CONF_INITIATING: MERGE_TO_CONFERENCE (State Machine Event)
1965
+ CONSULTING --> CONFERENCING: CONFERENCE_START (State Machine Event)
1966
+ CONSULTING --> CONFERENCING: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [!consultInitiator]
1967
+ CONSULTING --> WRAPPING_UP: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [shouldWrapUp]
1968
+ CONSULTING --> TERMINATED: TRANSFER_CONFERENCE_SUCCESS (State Machine Event) [default]
1969
+ CONSULTING --> CONSULTING: CONSULTING_ACTIVE / HOLD_SUCCESS / UNHOLD_SUCCESS / TRANSFER_FAILED / TRANSFER_CONFERENCE / TRANSFER_CONFERENCE_FAILED (State Machine Event)
1970
+
1971
+ %% CONF_INITIATING
1972
+ CONF_INITIATING --> CONFERENCING: CONFERENCE_START (State Machine Event)
1973
+ CONF_INITIATING --> CONSULTING: CONFERENCE_FAILED (State Machine Event)
1974
+
1975
+ %% CONFERENCING
1976
+ CONFERENCING --> CONSULT_INITIATING: CONSULT (State Machine Event)
1977
+ CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveMainInteraction && shouldWrapUp]
1978
+ CONFERENCING --> TERMINATED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [didCurrentAgentLeaveMainInteraction]
1979
+ CONFERENCING --> CONNECTED: PARTICIPANT_LEAVE (CC Event) -> PARTICIPANT_LEAVE (State Machine Event) [shouldDowngradeConferenceToConnected]
1980
+ CONFERENCING --> WRAPPING_UP: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [shouldWrapUp]
1981
+ CONFERENCING --> CONNECTED: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [customerInCall]
1982
+ CONFERENCING --> TERMINATED: CONFERENCE_END (CC Event) -> CONFERENCE_END (State Machine Event) [default]
1983
+ 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)
1984
+
1985
+ %% WRAPPING_UP / FINAL
1986
+ WRAPPING_UP --> COMPLETED: WRAPUP_COMPLETE (State Machine Event)
1987
+ COMPLETED --> [*]
1988
+ TERMINATED --> [*]
1989
+ ```
1990
+
1991
+ The full diagram above is the source of truth. The diagrams below split above flows based on each feature.
1992
+
1993
+ ```mermaid
1994
+ stateDiagram-v2
1995
+ [*] --> IDLE
1996
+ IDLE --> OFFERED: AGENT_CONTACT_RESERVED -> TASK_INCOMING
1997
+ OFFERED --> OFFERED: AGENT_CONTACT_OFFER -> TASK_OFFERED
1998
+ OFFERED --> OFFERED: AGENT_CONSULT_OFFER -> OFFER_CONSULT
1999
+ OFFERED --> CONNECTED: AGENT_CONTACT_ASSIGNED -> ASSIGN
2000
+ OFFERED --> TERMINATED: AGENT_CONTACT_OFFER_RONA/AGENT_CONTACT_ASSIGN_FAILED/AGENT_INVITE_FAILED/AGENT_OUTBOUND_FAILED -> RONA/ASSIGN_FAILED/INVITE_FAILED/OUTBOUND_FAILED
2001
+ OFFERED --> WRAPPING_UP: AGENT_WRAPUP -> TASK_WRAPUP [shouldWrapUp]
2002
+ OFFERED --> TERMINATED: AGENT_WRAPUP -> TASK_WRAPUP
2003
+
2004
+ CONNECTED --> HOLD_INITIATING: task.hold() -> HOLD_INITIATED
2005
+ CONNECTED --> CONSULT_INITIATING: task.consult() -> CONSULT
2006
+
2007
+ CONNECTED --> CONNECTED: PAUSE_RECORDING/RESUME_RECORDING
2008
+ CONNECTED --> CONNECTED: AGENT_BLIND_TRANSFER_FAILED/AGENT_VTEAM_TRANSFER_FAILED -> TRANSFER_FAILED
2009
+
2010
+ CONNECTED --> WRAPPING_UP: AGENT_BLIND_TRANSFERRED/AGENT_VTEAM_TRANSFERRED -> TRANSFER_SUCCESS [shouldWrapUpOrIsInitiator]
2011
+ CONNECTED --> WRAPPING_UP: CONTACT_ENDED -> CONTACT_ENDED [shouldWrapUp]
2012
+ CONNECTED --> WRAPPING_UP: AGENT_WRAPUP -> TASK_WRAPUP
2013
+ CONNECTED --> TERMINATED: CONTACT_ENDED -> CONTACT_ENDED [default]
2014
+
2015
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
2016
+ COMPLETED --> [*]
2017
+ TERMINATED --> [*]
2018
+ ```
2019
+
2020
+ ```mermaid
2021
+ stateDiagram-v2
2022
+ [*] --> CONNECTED
2023
+ CONNECTED --> HOLD_INITIATING: task.hold() -> HOLD_INITIATED
2024
+ HOLD_INITIATING --> HELD: AGENT_CONTACT_HELD -> HOLD_SUCCESS
2025
+ HOLD_INITIATING --> CONNECTED: AGENT_CONTACT_HOLD_FAILED -> HOLD_FAILED
2026
+ HELD --> RESUME_INITIATING: task.resume() -> UNHOLD_INITIATED
2027
+ RESUME_INITIATING --> CONNECTED: AGENT_CONTACT_UNHELD -> UNHOLD_SUCCESS
2028
+ RESUME_INITIATING --> HELD: AGENT_CONTACT_UNHOLD_FAILED -> UNHOLD_FAILED
2029
+ HELD --> CONSULT_INITIATING: task.consult() -> Consult
2030
+ CONSULT_INITIATING --> HELD: CONSULT_FAILED / CTQ_CANCEL [isPrimaryMediaOnHold]
2031
+ HELD --> WRAPPING_UP: CONTACT_ENDED -> CONTACT_ENDED [shouldWrapUp]
2032
+ HELD --> TERMINATED: CONTACT_ENDED -> CONTACT_ENDED [default]
2033
+
2034
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
2035
+ COMPLETED --> [*]
2036
+ TERMINATED --> [*]
2037
+ ```
2038
+
2039
+ ```mermaid
2040
+ stateDiagram-v2
2041
+ stateDiagram-v2
2042
+ [*] --> CONNECTED
2043
+ CONNECTED --> CONSULT_INITIATING: task.consult() -> CONSULT
2044
+ HELD --> CONSULT_INITIATING: task.consult() -> CONSULT
2045
+ CONFERENCING --> CONSULT_INITIATING: task.consult() -> CONSULT
2046
+
2047
+ CONSULT_INITIATING --> CONSULT_INITIATING: AGENT_CONTACT_HELD -> HOLD_SUCCESS
2048
+ CONSULT_INITIATING --> CONNECTED: AGENT_CONTACT_HOLD_FAILED -> HOLD_FAILED
2049
+ CONSULT_INITIATING --> CONSULTING: API consult success -> CONSULT_SUCCESS
2050
+ CONSULT_INITIATING --> HELD: AGENT_CONSULT_FAILED/AGENT_CTQ_FAILED -> CONSULT_FAILED [isPrimaryMediaOnHold]
2051
+ CONSULT_INITIATING --> CONNECTED: AGENT_CONSULT_FAILED/AGENT_CTQ_FAILED -> CONSULT_FAILED [default]
2052
+ CONSULT_INITIATING --> CONFERENCING: AGENT_CONSULT_FAILED -> CONSULT_FAILED [consultFromConference]
2053
+ CONSULT_INITIATING --> HELD: AGENT_CTQ_CANCELLED -> CTQ_CANCEL [isPrimaryMediaOnHold]
2054
+ CONSULT_INITIATING --> CONNECTED: AGENT_CTQ_CANCELLED -> CTQ_CANCEL [default]
2055
+
2056
+ CONSULTING --> HELD: AGENT_CONSULT_ENDED -> CONSULT_END [consultInitiator]
2057
+ CONSULTING --> TERMINATED: AGENT_CONSULT_ENDED -> CONSULT_END [consulted agent]
2058
+ CONSULTING --> CONFERENCING: AGENT_CONSULT_ENDED -> CONSULT_END [consultInitiator && consultFromConference]
2059
+ CONSULTING --> CONNECTED: AGENT_CONSULT_TRANSFERRED/AGENT_CONTACT_ASSIGNED -> TRANSFER_SUCCESS/ASSIGN
2060
+ CONSULTING --> WRAPPING_UP: AGENT_CONSULT_TRANSFERRED -> TRANSFER_SUCCESS [shouldWrapUpOrIsInitiator]
2061
+ CONSULTING --> CONSULTING: AGENT_CONSULT_TRANSFER_FAILED -> TRANSFER_FAILED
2062
+ CONSULTING --> CONF_INITIATING: task.consultConference() -> MERGE_TO_CONFERENCE
2063
+ CONSULTING --> WRAPPING_UP: AGENT_CONTACT_ENDED -> CONTACT_ENDED
2064
+
2065
+ WRAPPING_UP --> COMPLETED: AGENT_WRAPPEDUP -> WRAPUP_COMPLETE
2066
+ COMPLETED --> [*]
2067
+ TERMINATED --> [*]
2068
+ ```
2069
+
2070
+ ```mermaid
2071
+ stateDiagram-v2
2072
+ [*] --> CONSULTING
2073
+ CONSULTING --> CONF_INITIATING: task.consultConference() -> MERGE_TO_CONFERENCE
2074
+ CONF_INITIATING --> CONFERENCING: AGENT_CONSULT_CONFERENCED -> CONFERENCE_START
2075
+ CONF_INITIATING --> CONSULTING: AGENT_CONSULT_CONFERENCE_FAILED -> CONFERENCE_FAILED
2076
+ CONFERENCING --> CONSULT_INITIATING: task.consult() -> CONSULT
2077
+ CONSULTING --> CONFERENCING: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [!consultInitiator]
2078
+ CONSULTING --> WRAPPING_UP: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [shouldWrapUp]
2079
+ CONSULTING --> TERMINATED: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS [default]
2080
+ CONSULTING --> CONSULTING: AGENT_CONFERENCE_TRANSFER_FAILED -> TRANSFER_CONFERENCE_FAILED
2081
+ CONFERENCING --> CONFERENCING: AGENT_CONFERENCE_TRANSFERRED -> TRANSFER_CONFERENCE_SUCCESS
2082
+ CONFERENCING --> CONFERENCING: AGENT_CONFERENCE_TRANSFER_FAILED-> TRANSFER_CONFERENCE_FAILED
2083
+
2084
+ CONFERENCING --> WRAPPING_UP: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveMainInteraction && shouldWrapUp]
2085
+ CONFERENCING --> TERMINATED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [didCurrentAgentLeaveMainInteraction]
2086
+ CONFERENCING --> CONNECTED: PARTICIPANT_LEFT_CONFERENCE -> PARTICIPANT_LEAVE [shouldDowngradeConferenceToConnected]
2087
+ CONFERENCING --> WRAPPING_UP: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [shouldWrapUp]
2088
+ CONFERENCING --> CONNECTED: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [customerInCall]
2089
+ CONFERENCING --> TERMINATED: AGENT_CONSULT_CONFERENCE_ENDED -> CONFERENCE_END [default]
2090
+ WRAPPING_UP --> COMPLETED: WRAPUP_COMPLETE
2091
+ COMPLETED --> [*]
2092
+ TERMINATED --> [*]
2093
+ ```
2094
+
2095
+ ```typescript
2096
+ import {setup} from 'xstate';
2097
+ import {actions} from './actions';
2098
+ import {guards} from './guards';
2099
+
2100
+ const taskStateMachine = setup({
2101
+ types: {
2102
+ context: {} as TaskContext,
2103
+ events: {} as TaskEventPayload,
2104
+ },
2105
+ })
2106
+ .createMachine({
2107
+ id: 'taskStateMachine',
2108
+ initial: TaskState.IDLE,
2109
+ context: createInitialContext(uiControlConfig, TaskState.IDLE),
2110
+ states: {
2111
+ [TaskState.IDLE]: {
2112
+ on: {
2113
+ [TaskEvent.TASK_INCOMING]: {
2114
+ target: TaskState.OFFERED,
2115
+ actions: ['initializeTask', 'emitTaskIncoming'],
2116
+ },
2117
+ [TaskEvent.HYDRATE]: [
2118
+ {
2119
+ guard: guards.isInteractionTerminated,
2120
+ target: TaskState.WRAPPING_UP,
2121
+ actions: ['updateTaskData', 'markEnded', 'emitTaskHydrate'],
2122
+ },
2123
+ // ... more hydrate cases
2124
+ ],
2125
+ },
2126
+ },
2127
+ [TaskState.OFFERED]: {
2128
+ on: {
2129
+ [TaskEvent.ASSIGN]: {
2130
+ target: TaskState.CONNECTED,
2131
+ actions: ['updateTaskData', 'emitTaskAssigned'],
2132
+ },
2133
+ [TaskEvent.TASK_WRAPUP]: {
2134
+ target: TaskState.TERMINATED,
2135
+ actions: ['updateTaskData', 'markEnded', 'emitTaskEnd'],
2136
+ },
2137
+ // ... more transitions
2138
+ },
2139
+ },
2140
+ // ... more states
2141
+ },
2142
+ })
2143
+ .provide({actions});
2144
+ ```
2145
+
2146
+ The HYDRATE event restores state machine state after page refresh or reconnection.
2147
+
2148
+ **Algorithm**:
2149
+
2150
+ 1. Receive HYDRATE event with full task data
2151
+
2152
+ 2. Check interaction state and flags in order of precedence:
2153
+
2154
+ - If `taskData.interaction.isTerminated === true` -> WRAPPING_UP
2155
+
2156
+ - If `taskData.interaction.state === 'consulting'` -> CONSULTING
2157
+
2158
+ - If `taskData.interaction.state === 'hold'` -> HELD
2159
+
2160
+ - If `taskData.interaction.state === 'connected'` -> CONNECTED
2161
+
2162
+ - If conferencing-by-participants guard passes (`agentCount >= 2` in main call) -> CONFERENCING
2163
+
2164
+ - Default → Stay in IDLE
2165
+
2166
+ 3. Update context with hydrated data
2167
+
2168
+ 4. Emit TASK_HYDRATE event
2169
+
2170
+ ### Action implementation ownership
2171
+
2172
+ - `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.
2173
+ - `didInitiateConsult` exists in `guards.ts` but is not referenced by the current graph. Treat it as defined-but-unwired.
2174
+ - `getDefaultUIControls` is package-public through `src/index.ts`; the voice/digital computation helpers are private implementation details.
2175
+
2176
+ ## Pitfalls
2177
+ - `syncTaskDataFromEvent` is declared by the graph but implemented by Task; adding a competing default action loses integration-owned normalization.
2178
+ - `handleConferenceFailed`, `handleSwitchToMainCall`, and `handleSwitchToConsult` are wired actions, while `forceConsultInitiator` remains defined but unwired.
2179
+ - Guards/actions must remain deterministic and transport-free; logging, metrics, authentication, and request side effects belong to Task/TaskManager.
2180
+
2181
+ - `RONA`, `INVITE_FAILED`, `ASSIGN_FAILED`, `OUTBOUND_FAILED`
2182
+
2183
+ | Backend Event | TaskEvent | State Transition | Notes |
2184
+ |---|---|---|---|
2185
+ | `AgentContactOfferRona` | `RONA` | OFFERED → TERMINATED | Redirection on no answer |
2186
+ | `AgentInviteFailed` | `INVITE_FAILED` | OFFERED → TERMINATED | Invite failed |
2187
+ | `AgentContactAssignFailed` | `ASSIGN_FAILED` | OFFERED → TERMINATED | Assignment failed |
2188
+ | `AgentOutboundFailed` | `OUTBOUND_FAILED` | OFFERED → TERMINATED | Outdial failed |
2189
+
2190
+ ## Module Do's / Don'ts
2191
+ - DO map backend notifications to typed `TaskEvent` values before sending them to the actor.
2192
+ - DO preserve initiating states separately from success/failure stable states.
2193
+ - DON'T invoke WebexRequest, LoggerProxy, or MetricsManager from the state-machine layer.
2194
+ - DON'T document private UI-control helpers as public APIs or claim an action is absent without checking graph wiring.
2195
+
2196
+ ## Key Design Trade-off
2197
+ - Backend events are mapped into a typed internal event vocabulary before transition evaluation, isolating the state graph from transport details.
2198
+
2199
+ ## Test-Case Strategy (module)
2200
+ 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.
2201
+
2202
+ | Behavior / Requirement | Existing test evidence | Gap |
2203
+ |---|---|---|
2204
+ | `TASK_STATE_MACHINE-R-001` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Keep transition coverage synchronized with all mapped TaskEvent groups. |
2205
+ | `TASK_STATE_MACHINE-R-002` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Retain explicit wired/unwired assertions. |
2206
+ | `TASK_STATE_MACHINE-R-003` | `test/unit/spec/services/task/Task.ts` | None. |
2207
+ | `TASK_STATE_MACHINE-R-004` | `test/unit/spec/services/task/state-machine/uiControlsComputer.ts` | None. |
2208
+ | `TASK_STATE_MACHINE-R-005` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Transport/auth absence is also verified from imports. |
2209
+ | `TASK_STATE_MACHINE-R-006` | `test/unit/spec/services/task/Task.ts` | None. |
2210
+ | `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. |
2211
+ | `TASK_STATE_MACHINE-R-008` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/TaskManager.ts` | None. |
2212
+
2213
+ ## Traceability
2214
+ - Repo architecture: `../../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../../ai-docs/SPEC_INDEX.md`
2215
+ - Coverage state and contracts baseline: `../../../../../.sdd/manifest.json`
2216
+
2217
+ - Task lifecycle state machine: `TaskStateMachine.ts`
2218
+
2219
+ - State machine types/events: `constants.ts`, `types.ts`
2220
+
2221
+ - Guard logic: `guards.ts`
2222
+
2223
+ - Actions and context mutation: `actions.ts`
2224
+
2225
+ - UI control computation: `uiControlsComputer.ts`
2226
+
2227
+ `computeUIControls()` in `uiControlsComputer.ts`.
2228
+
2229
+ - [../../ai-docs/task-spec.md](../../ai-docs/task-spec.md) - Task service usage guide
2230
+
2231
+ - [../../ai-docs/task-spec.md](../../ai-docs/task-spec.md) - Task service architecture
2232
+
2233
+ - `../Task.ts` - actor lifecycle, action overrides, event emission
2234
+
2235
+ - `../TaskManager.ts` - maps backend events to state-machine events
2236
+
2237
+ - `../types.ts` - shared task data structures
2238
+
2239
+ - `../../ai-docs/task-spec.md` - broader task service architecture
2240
+
2241
+ - [TaskStateMachine.ts](../TaskStateMachine.ts) - Implementation
2242
+
2243
+ - [guards.ts](../guards.ts) - Guard functions
2244
+
2245
+ - [actions.ts](../actions.ts) - Action functions
2246
+
2247
+ - [constants.ts](../constants.ts) - State and event enums