@webex/contact-center 3.11.0 → 3.12.0-auth-prejoin-fetch.1

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