@webex/contact-center 3.12.0-next.15 → 3.12.0-next.151

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 (322) hide show
  1. package/.sdd/manifest.json +887 -0
  2. package/AGENTS.md +94 -0
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +62 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +138 -0
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +444 -0
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +52 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +382 -0
  17. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  18. package/ai-docs/features/contact-owner-change-refactor.md +78 -0
  19. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  20. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  21. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  22. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  23. package/ai-docs/patterns/testing-patterns.md +480 -0
  24. package/ai-docs/patterns/typescript-patterns.md +365 -0
  25. package/ai-docs/templates/README.md +102 -0
  26. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  27. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  28. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  29. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  30. package/ai-docs/templates/new-method/00-master.md +80 -0
  31. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  32. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  33. package/ai-docs/templates/new-method/03-tests.md +201 -0
  34. package/ai-docs/templates/new-method/04-validation.md +141 -0
  35. package/ai-docs/templates/new-service/00-master.md +109 -0
  36. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  37. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  38. package/ai-docs/templates/new-service/03-integration.md +178 -0
  39. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  40. package/ai-docs/templates/new-service/05-validation.md +145 -0
  41. package/dist/cc.js +1010 -47
  42. package/dist/cc.js.map +1 -1
  43. package/dist/config.js +13 -0
  44. package/dist/config.js.map +1 -1
  45. package/dist/constants.js +40 -3
  46. package/dist/constants.js.map +1 -1
  47. package/dist/index.js +54 -17
  48. package/dist/index.js.map +1 -1
  49. package/dist/metrics/MetricsManager.js +16 -0
  50. package/dist/metrics/MetricsManager.js.map +1 -1
  51. package/dist/metrics/behavioral-events.js +211 -0
  52. package/dist/metrics/behavioral-events.js.map +1 -1
  53. package/dist/metrics/constants.js +58 -4
  54. package/dist/metrics/constants.js.map +1 -1
  55. package/dist/services/AddressBook.js +18 -15
  56. package/dist/services/AddressBook.js.map +1 -1
  57. package/dist/services/AnswerCallOnWebexService.js +202 -0
  58. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  59. package/dist/services/ApiAiAssistant.js +351 -14
  60. package/dist/services/ApiAiAssistant.js.map +1 -1
  61. package/dist/services/EntryPoint.js +46 -68
  62. package/dist/services/EntryPoint.js.map +1 -1
  63. package/dist/services/Queue.js +27 -22
  64. package/dist/services/Queue.js.map +1 -1
  65. package/dist/services/UserPreference.js +427 -0
  66. package/dist/services/UserPreference.js.map +1 -0
  67. package/dist/services/WebexCrossClientService.js +225 -0
  68. package/dist/services/WebexCrossClientService.js.map +1 -0
  69. package/dist/services/WxAppTelephonyMercurySync.js +111 -0
  70. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  71. package/dist/services/config/Util.js +48 -48
  72. package/dist/services/config/Util.js.map +1 -1
  73. package/dist/services/config/constants.js +56 -78
  74. package/dist/services/config/constants.js.map +1 -1
  75. package/dist/services/config/index.js +100 -226
  76. package/dist/services/config/index.js.map +1 -1
  77. package/dist/services/config/types.js +97 -24
  78. package/dist/services/config/types.js.map +1 -1
  79. package/dist/services/core/Err.js.map +1 -1
  80. package/dist/services/core/Utils.js +111 -32
  81. package/dist/services/core/Utils.js.map +1 -1
  82. package/dist/services/core/WebexRequest.js +6 -2
  83. package/dist/services/core/WebexRequest.js.map +1 -1
  84. package/dist/services/core/aqm-reqs.js +28 -14
  85. package/dist/services/core/aqm-reqs.js.map +1 -1
  86. package/dist/services/core/types.js.map +1 -1
  87. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  88. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  89. package/dist/services/core/websocket/types.js.map +1 -1
  90. package/dist/services/index.js +1 -1
  91. package/dist/services/index.js.map +1 -1
  92. package/dist/services/task/Task.js +955 -0
  93. package/dist/services/task/Task.js.map +1 -0
  94. package/dist/services/task/TaskFactory.js +50 -0
  95. package/dist/services/task/TaskFactory.js.map +1 -0
  96. package/dist/services/task/TaskManager.js +1272 -523
  97. package/dist/services/task/TaskManager.js.map +1 -1
  98. package/dist/services/task/TaskUtils.js +221 -27
  99. package/dist/services/task/TaskUtils.js.map +1 -1
  100. package/dist/services/task/WebexCallingUtils.js +70 -0
  101. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  102. package/dist/services/task/constants.js +21 -2
  103. package/dist/services/task/constants.js.map +1 -1
  104. package/dist/services/task/contact.js +29 -0
  105. package/dist/services/task/contact.js.map +1 -1
  106. package/dist/services/task/dialer.js +78 -0
  107. package/dist/services/task/dialer.js.map +1 -1
  108. package/dist/services/task/digital/Digital.js +78 -0
  109. package/dist/services/task/digital/Digital.js.map +1 -0
  110. package/dist/services/task/state-machine/TaskStateMachine.js +982 -0
  111. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  112. package/dist/services/task/state-machine/actions.js +572 -0
  113. package/dist/services/task/state-machine/actions.js.map +1 -0
  114. package/dist/services/task/state-machine/constants.js +161 -0
  115. package/dist/services/task/state-machine/constants.js.map +1 -0
  116. package/dist/services/task/state-machine/guards.js +379 -0
  117. package/dist/services/task/state-machine/guards.js.map +1 -0
  118. package/dist/services/task/state-machine/index.js +53 -0
  119. package/dist/services/task/state-machine/index.js.map +1 -0
  120. package/dist/services/task/state-machine/types.js +54 -0
  121. package/dist/services/task/state-machine/types.js.map +1 -0
  122. package/dist/services/task/state-machine/uiControlsComputer.js +703 -0
  123. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  124. package/dist/services/task/taskDataNormalizer.js +99 -0
  125. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  126. package/dist/services/task/types.js +259 -5
  127. package/dist/services/task/types.js.map +1 -1
  128. package/dist/services/task/voice/Voice.js +1504 -0
  129. package/dist/services/task/voice/Voice.js.map +1 -0
  130. package/dist/services/task/voice/WebRTC.js +152 -0
  131. package/dist/services/task/voice/WebRTC.js.map +1 -0
  132. package/dist/services/task/voice/wxAppOfferObservability.js +145 -0
  133. package/dist/services/task/voice/wxAppOfferObservability.js.map +1 -0
  134. package/dist/services/task/voice/wxAppVoiceMethods.js +395 -0
  135. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  136. package/dist/services/wxAppDiagnosticLogging.js +159 -0
  137. package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
  138. package/dist/services/wxAppTelephonyUtils.js +19 -0
  139. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  140. package/dist/types/cc.d.ts +196 -9
  141. package/dist/types/config.d.ts +13 -0
  142. package/dist/types/constants.d.ts +38 -1
  143. package/dist/types/index.d.ts +54 -20
  144. package/dist/types/metrics/MetricsManager.d.ts +6 -0
  145. package/dist/types/metrics/constants.d.ts +52 -1
  146. package/dist/types/services/AddressBook.d.ts +2 -1
  147. package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
  148. package/dist/types/services/ApiAiAssistant.d.ts +58 -5
  149. package/dist/types/services/EntryPoint.d.ts +7 -5
  150. package/dist/types/services/Queue.d.ts +5 -3
  151. package/dist/types/services/UserPreference.d.ts +118 -0
  152. package/dist/types/services/WebexCrossClientService.d.ts +32 -0
  153. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  154. package/dist/types/services/config/Util.d.ts +4 -8
  155. package/dist/types/services/config/constants.d.ts +49 -76
  156. package/dist/types/services/config/index.d.ts +16 -64
  157. package/dist/types/services/config/types.d.ts +248 -114
  158. package/dist/types/services/core/Err.d.ts +6 -0
  159. package/dist/types/services/core/Utils.d.ts +35 -13
  160. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  161. package/dist/types/services/core/types.d.ts +2 -0
  162. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  163. package/dist/types/services/core/websocket/types.d.ts +1 -1
  164. package/dist/types/services/index.d.ts +1 -1
  165. package/dist/types/services/task/Task.d.ts +186 -0
  166. package/dist/types/services/task/TaskFactory.d.ts +14 -0
  167. package/dist/types/services/task/TaskUtils.d.ts +54 -2
  168. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  169. package/dist/types/services/task/constants.d.ts +19 -0
  170. package/dist/types/services/task/contact.d.ts +4 -0
  171. package/dist/types/services/task/dialer.d.ts +30 -0
  172. package/dist/types/services/task/digital/Digital.d.ts +23 -0
  173. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1442 -0
  174. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  175. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  176. package/dist/types/services/task/state-machine/guards.d.ts +103 -0
  177. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  178. package/dist/types/services/task/state-machine/types.d.ts +277 -0
  179. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  180. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  181. package/dist/types/services/task/types.d.ts +790 -66
  182. package/dist/types/services/task/voice/Voice.d.ts +241 -0
  183. package/dist/types/services/task/voice/WebRTC.d.ts +54 -0
  184. package/dist/types/services/task/voice/wxAppOfferObservability.d.ts +39 -0
  185. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +68 -0
  186. package/dist/types/services/wxAppDiagnosticLogging.d.ts +63 -0
  187. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  188. package/dist/types/types.d.ts +213 -11
  189. package/dist/types/utils/PageCache.d.ts +20 -3
  190. package/dist/types/webex.d.ts +1 -0
  191. package/dist/types.js +168 -6
  192. package/dist/types.js.map +1 -1
  193. package/dist/utils/PageCache.js +19 -5
  194. package/dist/utils/PageCache.js.map +1 -1
  195. package/dist/webex.js +22 -2
  196. package/dist/webex.js.map +1 -1
  197. package/package.json +16 -12
  198. package/src/cc.ts +1338 -121
  199. package/src/config.ts +13 -0
  200. package/src/constants.ts +41 -1
  201. package/src/index.ts +62 -15
  202. package/src/metrics/MetricsManager.ts +16 -0
  203. package/src/metrics/ai-docs/AGENTS.md +350 -0
  204. package/src/metrics/ai-docs/ARCHITECTURE.md +338 -0
  205. package/src/metrics/ai-docs/metrics-spec.md +971 -0
  206. package/src/metrics/behavioral-events.ts +218 -0
  207. package/src/metrics/constants.ts +63 -4
  208. package/src/services/AddressBook.ts +17 -6
  209. package/src/services/AnswerCallOnWebexService.ts +239 -0
  210. package/src/services/ApiAiAssistant.ts +470 -15
  211. package/src/services/EntryPoint.ts +59 -60
  212. package/src/services/Queue.ts +29 -12
  213. package/src/services/UserPreference.ts +509 -0
  214. package/src/services/WebexCrossClientService.ts +298 -0
  215. package/src/services/WxAppTelephonyMercurySync.ts +142 -0
  216. package/src/services/agent/ai-docs/AGENTS.md +240 -0
  217. package/src/services/agent/ai-docs/ARCHITECTURE.md +304 -0
  218. package/src/services/agent/ai-docs/agent-spec.md +505 -0
  219. package/src/services/ai-docs/AGENTS.md +386 -0
  220. package/src/services/ai-docs/services-spec.md +505 -0
  221. package/src/services/config/Util.ts +54 -84
  222. package/src/services/config/ai-docs/AGENTS.md +250 -0
  223. package/src/services/config/ai-docs/ARCHITECTURE.md +413 -0
  224. package/src/services/config/ai-docs/config-spec.md +669 -0
  225. package/src/services/config/constants.ts +63 -85
  226. package/src/services/config/index.ts +116 -277
  227. package/src/services/config/types.ts +233 -125
  228. package/src/services/core/Err.ts +3 -0
  229. package/src/services/core/Utils.ts +127 -37
  230. package/src/services/core/WebexRequest.ts +3 -1
  231. package/src/services/core/ai-docs/AGENTS.md +381 -0
  232. package/src/services/core/ai-docs/ARCHITECTURE.md +698 -0
  233. package/src/services/core/ai-docs/core-spec.md +787 -0
  234. package/src/services/core/aqm-reqs.ts +30 -15
  235. package/src/services/core/types.ts +33 -1
  236. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  237. package/src/services/core/websocket/types.ts +1 -1
  238. package/src/services/index.ts +1 -1
  239. package/src/services/task/Task.ts +1211 -0
  240. package/src/services/task/TaskFactory.ts +82 -0
  241. package/src/services/task/TaskManager.ts +1584 -643
  242. package/src/services/task/TaskUtils.ts +316 -25
  243. package/src/services/task/WebexCallingUtils.ts +136 -0
  244. package/src/services/task/ai-docs/AGENTS.md +457 -0
  245. package/src/services/task/ai-docs/ARCHITECTURE.md +595 -0
  246. package/src/services/task/ai-docs/task-spec.md +1501 -0
  247. package/src/services/task/constants.ts +20 -0
  248. package/src/services/task/contact.ts +30 -0
  249. package/src/services/task/dialer.ts +80 -0
  250. package/src/services/task/digital/Digital.ts +102 -0
  251. package/src/services/task/state-machine/TaskStateMachine.ts +1320 -0
  252. package/src/services/task/state-machine/actions.ts +741 -0
  253. package/src/services/task/state-machine/ai-docs/AGENTS.md +460 -0
  254. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1156 -0
  255. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2247 -0
  256. package/src/services/task/state-machine/constants.ts +172 -0
  257. package/src/services/task/state-machine/guards.ts +442 -0
  258. package/src/services/task/state-machine/index.ts +28 -0
  259. package/src/services/task/state-machine/types.ts +258 -0
  260. package/src/services/task/state-machine/uiControlsComputer.ts +1135 -0
  261. package/src/services/task/taskDataNormalizer.ts +137 -0
  262. package/src/services/task/types.ts +974 -70
  263. package/src/services/task/voice/Voice.ts +1878 -0
  264. package/src/services/task/voice/WebRTC.ts +194 -0
  265. package/src/services/task/voice/wxAppOfferObservability.ts +204 -0
  266. package/src/services/task/voice/wxAppVoiceMethods.ts +575 -0
  267. package/src/services/wxAppDiagnosticLogging.ts +286 -0
  268. package/src/services/wxAppTelephonyUtils.ts +14 -0
  269. package/src/types.ts +272 -13
  270. package/src/utils/AGENTS.md +289 -0
  271. package/src/utils/PageCache.ts +38 -5
  272. package/src/utils/ai-docs/utils-spec.md +391 -0
  273. package/src/webex.js +10 -0
  274. package/test/unit/fixtures/aiSummaryTestUtils.ts +40 -0
  275. package/test/unit/spec/cc.ts +3705 -150
  276. package/test/unit/spec/index.ts +885 -0
  277. package/test/unit/spec/logger-proxy.ts +70 -0
  278. package/test/unit/spec/metrics/MetricsManager.ts +25 -0
  279. package/test/unit/spec/metrics/behavioral-events.ts +60 -0
  280. package/test/unit/spec/services/AddressBook.ts +37 -6
  281. package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
  282. package/test/unit/spec/services/ApiAiAssistant.ts +429 -23
  283. package/test/unit/spec/services/EntryPoint.ts +87 -40
  284. package/test/unit/spec/services/Queue.ts +123 -12
  285. package/test/unit/spec/services/UserPreference.ts +401 -0
  286. package/test/unit/spec/services/WebCallingService.ts +7 -1
  287. package/test/unit/spec/services/WebexCrossClientService.ts +509 -0
  288. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
  289. package/test/unit/spec/services/config/Util.ts +501 -0
  290. package/test/unit/spec/services/config/index.ts +317 -547
  291. package/test/unit/spec/services/core/Utils.ts +425 -8
  292. package/test/unit/spec/services/core/WebexRequest.ts +8 -7
  293. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  294. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  295. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  296. package/test/unit/spec/services/task/Task.ts +2723 -0
  297. package/test/unit/spec/services/task/TaskFactory.ts +116 -0
  298. package/test/unit/spec/services/task/TaskManager.ts +4781 -1585
  299. package/test/unit/spec/services/task/TaskUtils.ts +307 -0
  300. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  301. package/test/unit/spec/services/task/contact.ts +33 -0
  302. package/test/unit/spec/services/task/dialer.ts +190 -0
  303. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  304. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +4060 -0
  305. package/test/unit/spec/services/task/state-machine/guards.ts +839 -0
  306. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  307. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +3101 -0
  308. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  309. package/test/unit/spec/services/task/voice/Voice.ts +2355 -0
  310. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  311. package/test/unit/spec/services/task/voice/wxAppOfferObservability.ts +361 -0
  312. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +719 -0
  313. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +179 -0
  314. package/test/unit/spec/wellnessSampleUtils.ts +288 -0
  315. package/typedoc.md +1 -1
  316. package/umd/contact-center.min.js +2 -2
  317. package/umd/contact-center.min.js.map +1 -1
  318. package/dist/services/task/index.js +0 -1530
  319. package/dist/services/task/index.js.map +0 -1
  320. package/dist/types/services/task/index.d.ts +0 -650
  321. package/src/services/task/index.ts +0 -1806
  322. package/test/unit/spec/services/task/index.ts +0 -2205
@@ -0,0 +1,1501 @@
1
+ # Task — 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` |
10
+ | Source path(s) | `src/services/task` |
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-10-06 |
15
+ | Validation status | AI summary methods, delivery, and cleanup updates pending independent review (2026-10-06); prior follow-up validation passed (independent Claude fallback, 2026-07-21); 1 existing test-coverage gap; 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 is one of nine confirmed Contact Center SDK modules. Own task creation, media-specific behavior, call-control operations, lifecycle orchestration, task events, and integration with the task state machine. Existing reviewed documentation is migrated by meaning and code/tests remain the behavioral referee.
27
+
28
+ Manage task lifecycle including inbound/outbound calls, hold/resume, consult, transfer, conference, and wrapup.
29
+
30
+ - **Task Creation by Channel**: `TaskFactory.ts` chooses `WebRTC`, `Voice`, or `Digital` based on `MEDIA_CHANNEL` and `webCallingService.loginOption`, so each task class exposes the correct capabilities for the media type.
31
+
32
+ - **Task Orchestration**: `TaskManager.ts` owns task lifecycle wiring—initializes listeners, receives task events, creates/updates tasks, emits SDK events, and exposes task collections for consumers.
33
+
34
+ - **Event Emission and Public APIs**: Task objects register listeners, update context, emit SDK events (e.g., `task:*`), and expose public methods that delegate to `contact.ts` for call control and to the state machine for transition validation.
35
+
36
+ - **AQM Contact Operations**: `contact.ts` builds the AQM request surface for call control (accept, hold, consult, transfer, participant Drop, wrapup, end) and is the primary bridge from `Task`/`Voice`/`WebRTC`/`Digital` methods to WCC task APIs.
37
+
38
+ - **Outbound and Preview-Campaign Dialing**: `dialer.ts` exposes `startOutdial` plus `acceptPreviewContact`, `skipPreviewContact`, and `removePreviewContact`; ContactCenter publishes these operations through typed `cc` methods.
39
+
40
+ - **State Machine Driven UI Controls**: The `state-machine/` folder provides the XState engine (`TaskStateMachine.ts`) plus `actions.ts`, `guards.ts`, `uiControlsComputer.ts`, `constants.ts`, and `types.ts` to compute valid transitions and UI control state. Capability-level details live in `state-machine/ai-docs/task-state-machine-spec.md`.
41
+
42
+ This section describes how the task layer constructs tasks, initializes the state machine, and wires AQM calls to task methods. It provides context for how the state machine fits into the end-to-end flow.
43
+
44
+ - **Listener Setup**: Registers WebSocket listeners to receive CC events and map them to `TaskEvent` payloads.
45
+
46
+ - **Task Registry**: Creates tasks via `TaskFactory`, stores them in the task collection, and updates task data on incoming events.
47
+
48
+ - **Event Emission**: Re-emits `task:*` events on the task or `cc` object for SDK consumers.
49
+
50
+ - **Hydration/Recovery**: Handles state updates and transitions during reconnect/hydrate flows.
51
+
52
+ Example (backend event to state machine):
53
+
54
+ ```typescript
55
+ const payload = TaskManager.mapEventToTaskStateMachineEvent(event, taskData);
56
+ if (payload) {
57
+ task.sendStateMachineEvent(payload);
58
+ }
59
+ ```
60
+
61
+ - **`contact.ts`**: Builds the AQM request surface for call control (hold, consult, transfer, wrapup, end). Task methods delegate to these calls, then drive state transitions based on success/failure events.
62
+
63
+ - **`dialer.ts`**: Exposes `startOutdial` and the three preview-campaign AQM requests used by `cc.startOutdial()`, `cc.acceptPreviewContact()`, `cc.skipPreviewContact()`, and `cc.removePreviewContact()`.
64
+
65
+ Example (task method delegating to AQM):
66
+
67
+ ```typescript
68
+ // task.hold() -> contact.hold(...) -> stateMachine events on response
69
+ await contact.hold({interactionId});
70
+ stateMachineService.send({type: TaskEvent.HOLD_INITIATED, mediaResourceId});
71
+ ```
72
+
73
+ ## Purpose / Responsibility
74
+ Own task creation, media-specific behavior, call-control operations, lifecycle orchestration, task events, and integration with the task state machine.
75
+
76
+ ## Stack
77
+ TypeScript 5.4, EventEmitter, XState 5, @webex/calling, AQM/WebSocket integrations, Jest 27.
78
+
79
+ ## Folder / Package Structure
80
+ ```text
81
+ src/services/task/
82
+ ├── AutoWrapup.ts
83
+ ├── Task.ts
84
+ ├── TaskFactory.ts
85
+ ├── TaskManager.ts
86
+ ├── TaskUtils.ts
87
+ ├── constants.ts
88
+ ├── contact.ts
89
+ ├── dialer.ts
90
+ ├── digital/
91
+ ├── state-machine/
92
+ ├── taskDataNormalizer.ts
93
+ ├── types.ts
94
+ ├── voice/
95
+ ```
96
+
97
+ ```text
98
+ services/task/
99
+ ├── Task.ts # Task class (ITask implementation)
100
+ ├── TaskManager.ts # Singleton task manager
101
+ ├── contact.ts # Contact operations (AQM)
102
+ ├── dialer.ts # Outbound dialing (AQM)
103
+ ├── AutoWrapup.ts # Auto wrapup handler
104
+ ├── TaskUtils.ts # Helper functions
105
+ ├── types.ts # Task types and events
106
+ ├── constants.ts # Task constants
107
+ ├── TaskFactory.ts # Task factory
108
+ ├── taskDataNormalizer.ts # Task data normalization helpers
109
+ ├── digital/ # Digital task implementations
110
+ │ └── Digital.ts
111
+ ├── voice/ # Voice task implementations
112
+ │ ├── Voice.ts
113
+ │ └── WebRTC.ts
114
+ ├── state-machine/ # XState task lifecycle engine
115
+ │ ├── TaskStateMachine.ts
116
+ │ ├── actions.ts
117
+ │ ├── guards.ts
118
+ │ ├── uiControlsComputer.ts
119
+ │ ├── constants.ts
120
+ │ ├── types.ts
121
+ │ └── ai-docs/
122
+ │ ├── AGENTS.md
123
+ │ └── ARCHITECTURE.md
124
+ └── ai-docs/
125
+ ├── AGENTS.md # Usage documentation
126
+ └── ARCHITECTURE.md # Task service architecture
127
+ ```
128
+
129
+ ## Key Files (source of truth)
130
+ | File | Holds |
131
+ |---|---|
132
+ | `src/services/task/Task.ts` | Authoritative Task implementation or contract source. |
133
+ | `src/services/task/TaskManager.ts` | Authoritative Task implementation or contract source. |
134
+ | `src/services/task/TaskFactory.ts` | Authoritative Task implementation or contract source. |
135
+ | `src/services/task/contact.ts` | Authoritative Task implementation or contract source. |
136
+ | `src/services/task/dialer.ts` | Authoritative Task implementation or contract source. |
137
+ | `src/services/task/types.ts` | Authoritative Task implementation or contract source. |
138
+
139
+ ## Public Surface
140
+ | Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index |
141
+ |---|---|---|---|---|---|---|
142
+ | `task.surface` | SDK / event / internal API | Exported Task/types/events plus application-facing task instances and call-control methods. | Stable module consumption boundary. | Additive changes by default; breaking package exports require a major-version transition. | `src/services/task/Task.ts` | `../../../../ai-docs/CONTRACTS.md` |
143
+ | `task.preview-campaign` | SDK/AQM API | `acceptPreviewContact`, `skipPreviewContact`, `removePreviewContact`, and `PreviewContactPayload`. | Accept, skip, or remove a reserved campaign preview contact; each method returns `Promise<TaskResponse>`. | Additive semver-public methods; removals or signature changes are breaking. | `src/cc.ts`, `src/services/task/dialer.ts`, `src/services/task/types.ts` | `../../../../ai-docs/CONTRACTS.md` |
144
+ | `task.consult-transfer-controls` | SDK task controls | `TaskUIControls.consultTransferDestinations` with ordered `consult` and `transfer` arrays. | Surface default destination availability on every Task without an extra policy method. | Additive semver-public field/types; array order is meaningful and the first item is the default. | `src/services/task/types.ts`, `src/services/task/state-machine/uiControlsComputer.ts` | `../../../../ai-docs/CONTRACTS.md` |
145
+ | `task.conference-participant-drop` | SDK/AQM API | `task.dropConferenceParticipant(payload: DropConferenceParticipantPayload): Promise<TaskResponse>`. | Remove a supported target from a voice conference after correlated routing completion. | Additive semver-public method and payload; removals or signature changes are breaking. | `src/services/task/voice/Voice.ts`, `src/services/task/contact.ts`, `src/services/task/types.ts` | `../../../../ai-docs/CONTRACTS.md` |
146
+ | `task.ai-summary` | SDK/RTD API | Post-call and mid-call summary request/response methods, feature enablement, and receiving-agent summary events. | Provide task-scoped generated summaries without exposing RTD correlation to SDK consumers. | Additive semver-public methods, types, and events; removals or signature changes are breaking. | `src/services/task/Task.ts`, `src/services/task/TaskManager.ts`, `src/services/task/types.ts`, `src/services/ApiAiAssistant.ts` | `../../../../ai-docs/CONTRACTS.md` |
147
+
148
+ Compatibility notes:
149
+ - Do not remove or reinterpret exported symbols/events without a documented consumer migration.
150
+
151
+ - `TASK_EVENTS` enum (`types.ts`)
152
+
153
+ - `TaskData`, `TaskId`, `TaskResponse`, `TaskUIControls` (`types.ts`)
154
+
155
+ - `AISummary`, `AISummaryResponse`, `AISummaryAction`, `AISummaryFeatureEnablement`, and `AISummaryCapabilities` (`types.ts`)
156
+
157
+ - `PreviewContactPayload` (`types.ts`) with `interactionId` and campaign-name `campaignId`
158
+
159
+ - `DropConferenceParticipantPayload` (`types.ts`) with a runtime-validated non-empty `participantId`
160
+
161
+ - `ITask`, `IVoice`, `IWebRTC`, `IDigital` (`types.ts`)
162
+
163
+ - `MEDIA_CHANNEL`, `TASK_CHANNEL_TYPE`, `VOICE_VARIANT` (`types.ts`)
164
+
165
+ - State machine: `TaskState`, `TaskEvent` (`state-machine/constants.ts`)
166
+
167
+ | Event | When Emitted |
168
+ |---|---|
169
+ | `task:incoming` | New task offered to agent |
170
+ | `task:hydrate` | Task data updated |
171
+ | `task:merged` | Tasks merged (EPDN transfer) |
172
+
173
+ | Event | When Emitted |
174
+ |---|---|
175
+ | `task:assigned` | Task assigned to agent |
176
+ | `task:media` | Media stream/track updates are available |
177
+ | `task:unassigned` | Task is unassigned from agent |
178
+ | `task:offerContact` | Contact offer received/updated |
179
+ | `task:offerConsult` | Consult offer received |
180
+ | `task:hold` | Task placed on hold |
181
+ | `task:resume` | Task resumed from hold |
182
+ | `task:end` | Task ended |
183
+ | `task:rejected` | Task rejected / failure path emitted |
184
+ | `task:wrapup` | Task entering wrapup |
185
+ | `task:wrappedup` | Wrapup completed |
186
+ | `task:consulting` | Consult is in progress |
187
+ | `task:consultAccepted` | Consult accepted by destination party |
188
+ | `task:consultCreated` | Consultation started |
189
+ | `task:consultEnd` | Consultation ended |
190
+ | `task:autoAnswered` | Task was auto-answered |
191
+ | `task:recordingStarted` / `task:recordingPaused` / `task:recordingResumed` | Recording lifecycle updates |
192
+ | `task:conferenceStarted` / `task:conferenceEnded` / `task:conferenceFailed` | Conference lifecycle updates |
193
+ | `task:participantJoined` / `task:participantLeft` | Conference participant updates |
194
+ | `task:switchCall` | Switched between consult and main call |
195
+ | `task:outdialFailed` | Outdial operation failed |
196
+ | `task:ui-controls-updated` | UI controls changed due to state transition |
197
+ | `task:midCallSummaryReceived` | Mid-call summary delivered to a receiving agent |
198
+ | `task:featureEnablement` | Per-task AI summary enablement flags received |
199
+ | `task:cleanup` | Internal cleanup signal emitted by state machine |
200
+
201
+ > Full list is defined in `TASK_EVENTS` (`types.ts`).
202
+
203
+ | Event | When Emitted |
204
+ |---|---|
205
+ | `REAL_TIME_TRANSCRIPTION` | A realtime transcript payload is received for the task interaction |
206
+ | `SUGGESTED_RESPONSE` | A final AI Assistant suggestion payload is received for the task interaction |
207
+
208
+ ### AI summary APIs
209
+
210
+ - `requestPostCallSummary(): Promise<AISummary>` requests a post-call summary.
211
+ - `sendPostCallSummaryResponse(response: AISummaryResponse): Promise<void>` reports the agent's post-call summary outcome.
212
+ - `requestMidCallSummary(action: AISummaryAction): Promise<AISummary>` requests a consult or transfer summary.
213
+ - `sendMidCallSummaryResponse(response: AISummaryResponse, action: AISummaryAction): Promise<void>` reports the corresponding mid-call outcome.
214
+
215
+ Summary requests require both the organization-level generated-summary flag and the task-level feature enablement flag. The dedicated request methods read the current organization flags directly from the existing `ApiAIAssistant.aiFeature` field, populated by ContactCenter from the agent configuration. They check `generatedSummaries.wrapUpSummariesEnabled` or `generatedSummaries.consultTransferSummariesEnabled`; missing flags disable the corresponding request. No separate feature-flags argument or task-local organization configuration is needed. A request resolves only after the event POST succeeds and the matching RTD payload arrives; otherwise it rejects with the applicable disabled, transport, or timeout error. Receiving-agent summaries are delivered separately through `task:midCallSummaryReceived`. Summary content is application data and must not be written to logs or metrics.
216
+
217
+ Task-level feature flags survive `updateTaskData()`, including interaction ID changes during consults or task re-keying. Flags remain available throughout wrapup for post-call summary requests, and TaskManager clears them when the task is removed after wrapup completion or another terminal lifecycle event. Session cleanup also clears them.
218
+
219
+ Each dedicated Task summary method owns its event selection, payload construction, metrics, and error handling. The request methods call `ApiAIAssistant.requestAndWaitForRtd()`. Both response methods read `interactionId` from the current task data and use `interaction.mainInteractionId` as `conversationId`, falling back to `interactionId`. They call `ApiAIAssistant.sendEvent()` directly without caching request IDs on the task.
220
+
221
+ TaskManager reads the receiving task directly from `taskCollection[summary.conversationId]`; the RTD conversation ID is the task's collection key (`task.data.interactionId`). It emits `task:midCallSummaryReceived` immediately when that task exists. Otherwise it stores the latest summary for that ID in an in-memory map without an expiry timer, so an RTD frame can wait for task creation. The common AQM event-processing path replays the stored payload after task creation, updates that register the task under that ID, or merge publication. Replay after creation or merge follows the public task event so consumers can attach listeners. Delivery removes the stored summary, and session cleanup clears all pending summaries.
222
+
223
+ `clearAISummaryState()` cancels pending summary requests, clears task feature enablement, and empties both pending maps. ContactCenter's `deregister()` removes the RTD message listener synchronously before asynchronous teardown. RTD listener registration and removal control inbound delivery; `setConfigFlags()` only supplies task configuration.
224
+
225
+ Initiate outbound call.
226
+
227
+ **Parameters**:
228
+
229
+ - `destination` (string): Phone number to call
230
+
231
+ - `origin` (string): Outbound ANI/caller ID
232
+
233
+ **Returns**: `Promise<TaskResponse>` (AQM response, not a Task instance)
234
+
235
+ **Example**:
236
+
237
+ > **Host-application example:** `updateCallStatus` represents consumer-owned UI handling. SDK package implementation must use `LoggerProxy` and must not log raw runtime values.
238
+
239
+ ```typescript
240
+ const response = await cc.startOutdial('+14155551234', '+18005551000');
241
+
242
+ // Outdial task object is created asynchronously via TaskManager.
243
+ // Listen on cc/task events instead of treating startOutdial response as an ITask.
244
+ cc.on('task:incoming', (task) => {
245
+ task.on('task:assigned', () => {
246
+ updateCallStatus('connected');
247
+ });
248
+
249
+ task.on('task:end', () => {
250
+ updateCallStatus('ended');
251
+ });
252
+ });
253
+ ```
254
+
255
+ Accept an incoming task.
256
+
257
+ **Returns**: `Promise<TaskResponse>`
258
+
259
+ **Example**:
260
+
261
+ ```typescript
262
+ cc.on('task:incoming', async (task) => {
263
+ await task.accept();
264
+ });
265
+ ```
266
+
267
+ Put task on hold or resume.
268
+
269
+ **Parameters**:
270
+
271
+ - Concrete `Task.hold()` / `Voice.hold()` and `Task.resume()` / `Voice.resume()` accept no parameters. The optional `mediaResourceId` exists only on the broader `ITask` compatibility declaration; the implementations derive the active media resource from task state.
272
+
273
+ **Returns**: `Promise<TaskResponse>`
274
+
275
+ **Example**:
276
+
277
+ ```typescript
278
+ // Put on hold
279
+ await task.hold();
280
+
281
+ // Resume
282
+ await task.resume();
283
+ ```
284
+
285
+ End the current task.
286
+
287
+ **Returns**: `Promise<TaskResponse>`
288
+
289
+ **Example**:
290
+
291
+ ```typescript
292
+ await task.end();
293
+ ```
294
+
295
+ Complete task with wrapup code.
296
+
297
+ **Parameters**:
298
+
299
+ - `wrapUpReason` (string, required): Wrapup reason text
300
+
301
+ - `auxCodeId` (string, required): Wrapup code ID
302
+
303
+ **Returns**: `Promise<TaskResponse>`
304
+
305
+ **Example**:
306
+
307
+ ```typescript
308
+ await task.wrapup({
309
+ wrapUpReason: 'Customer issue resolved',
310
+ auxCodeId: 'resolved-code',
311
+ });
312
+ ```
313
+
314
+ Transfer task to another destination.
315
+
316
+ **Parameters**:
317
+
318
+ - `to` (string): Agent ID, queue ID, or phone number
319
+
320
+ - `destinationType` ('queue' | 'agent' | 'dialNumber'): Destination type
321
+
322
+ **Returns**: `Promise<TaskResponse>`
323
+
324
+ **Example**:
325
+
326
+ ```typescript
327
+ // Transfer to queue
328
+ await task.transfer({
329
+ to: 'queue-123',
330
+ destinationType: 'queue',
331
+ });
332
+
333
+ // Transfer to agent
334
+ await task.transfer({
335
+ to: 'agent-456',
336
+ destinationType: 'agent',
337
+ });
338
+ ```
339
+
340
+ Start consultation.
341
+
342
+ **Parameters**:
343
+
344
+ - `to` (string): Agent/queue/phone to consult
345
+
346
+ - `destinationType` ('queue' | 'agent' | 'dialNumber' | 'entryPoint'): Type
347
+
348
+ **Returns**: `Promise<TaskResponse>`
349
+
350
+ **Example**:
351
+
352
+ ```typescript
353
+ await task.consult({
354
+ to: 'agent-456',
355
+ destinationType: 'agent',
356
+ });
357
+
358
+ // Later: complete transfer (consulting voice flow uses transfer())
359
+ await task.transfer({
360
+ to: 'queue-123',
361
+ destinationType: 'queue',
362
+ });
363
+ // Or end consult
364
+ await task.endConsult();
365
+ ```
366
+
367
+ End consultation without transfer.
368
+
369
+ **Parameters**:
370
+
371
+ - `consultEndPayload` (optional `ConsultEndPayload`)
372
+
373
+ **Returns**: `Promise<TaskResponse>`
374
+
375
+ Remove another participant from a voice conference.
376
+
377
+ **Parameters**:
378
+
379
+ - `participantId` (string, required): backend participant target identifier; blank and non-string values reject before AQM registration or telemetry.
380
+
381
+ **Returns**: `Promise<TaskResponse>` after `ParticipantLeftConference` is received for the latest `interaction.mainInteractionId || task.data.interactionId`.
382
+
383
+ **Failure**: `ParticipantDropConferenceFailed`, HTTP rejection, or the default 20-second AQM timeout rejects the promise. The operation does not add a task state-machine event or optimistically change the roster.
384
+
385
+ ```typescript
386
+ await task.dropConferenceParticipant({participantId});
387
+ ```
388
+
389
+ ### Complete TASK_EVENTS inventory
390
+
391
+ The public `TASK_EVENTS` enum contains 52 members; every member is listed below from `src/services/task/types.ts`.
392
+
393
+ | Constant | Event string |
394
+ |---|---|
395
+ | `TASK_INCOMING` | `task:incoming` |
396
+ | `TASK_ASSIGNED` | `task:assigned` |
397
+ | `TASK_MEDIA` | `task:media` |
398
+ | `TASK_UNASSIGNED` | `task:unassigned` |
399
+ | `TASK_HOLD` | `task:hold` |
400
+ | `TASK_RESUME` | `task:resume` |
401
+ | `TASK_CONSULT_END` | `task:consultEnd` |
402
+ | `TASK_CONSULT_QUEUE_CANCELLED` | `task:consultQueueCancelled` |
403
+ | `TASK_CONSULT_QUEUE_FAILED` | `task:consultQueueFailed` |
404
+ | `TASK_UI_CONTROLS_UPDATED` | `task:ui-controls-updated` |
405
+ | `TASK_WXAPP_MUTE_STATE_UPDATED` | `task:wxapp-mute-state-updated` |
406
+ | `TASK_CONSULT_ACCEPTED` | `task:consultAccepted` |
407
+ | `TASK_CONSULTING` | `task:consulting` |
408
+ | `TASK_CONSULT_CREATED` | `task:consultCreated` |
409
+ | `TASK_OFFER_CONSULT` | `task:offerConsult` |
410
+ | `TASK_END` | `task:end` |
411
+ | `TASK_WRAPUP` | `task:wrapup` |
412
+ | `TASK_WRAPPEDUP` | `task:wrappedup` |
413
+ | `TASK_CLEANUP` | `task:cleanup` |
414
+ | `TASK_RECORDING_STARTED` | `task:recordingStarted` |
415
+ | `TASK_RECORDING_PAUSED` | `task:recordingPaused` |
416
+ | `TASK_RECORDING_PAUSE_FAILED` | `task:recordingPauseFailed` |
417
+ | `TASK_RECORDING_RESUMED` | `task:recordingResumed` |
418
+ | `TASK_RECORDING_RESUME_FAILED` | `task:recordingResumeFailed` |
419
+ | `TASK_REJECT` | `task:rejected` |
420
+ | `TASK_OUTDIAL_FAILED` | `task:outdialFailed` |
421
+ | `TASK_HYDRATE` | `task:hydrate` |
422
+ | `TASK_OFFER_CONTACT` | `task:offerContact` |
423
+ | `TASK_AUTO_ANSWERED` | `task:autoAnswered` |
424
+ | `TASK_CONFERENCE_ESTABLISHING` | `task:conferenceEstablishing` |
425
+ | `TASK_CONFERENCE_STARTED` | `task:conferenceStarted` |
426
+ | `TASK_CONFERENCE_FAILED` | `task:conferenceFailed` |
427
+ | `TASK_CONFERENCE_ENDED` | `task:conferenceEnded` |
428
+ | `TASK_PARTICIPANT_JOINED` | `task:participantJoined` |
429
+ | `TASK_PARTICIPANT_LEFT` | `task:participantLeft` |
430
+ | `TASK_CONFERENCE_TRANSFERRED` | `task:conferenceTransferred` |
431
+ | `TASK_CONFERENCE_TRANSFER_FAILED` | `task:conferenceTransferFailed` |
432
+ | `TASK_CONFERENCE_END_FAILED` | `task:conferenceEndFailed` |
433
+ | `TASK_PARTICIPANT_LEFT_FAILED` | `task:participantLeftFailed` |
434
+ | `TASK_EXIT_CONFERENCE` | `task:exitConference` |
435
+ | `TASK_TRANSFER_CONFERENCE` | `task:transferConference` |
436
+ | `TASK_SWITCH_CALL` | `task:switchCall` |
437
+ | `TASK_MERGED` | `task:merged` |
438
+ | `TASK_POST_CALL_ACTIVITY` | `task:postCallActivity` |
439
+ | `TASK_MULTI_LOGIN_HYDRATE` | `task:multiLoginHydrate` |
440
+ | `TASK_CAMPAIGN_PREVIEW_RESERVATION` | `task:campaignPreviewReservation` |
441
+ | `TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED` | `task:campaignPreviewAcceptFailed` |
442
+ | `TASK_CAMPAIGN_PREVIEW_SKIP_FAILED` | `task:campaignPreviewSkipFailed` |
443
+ | `TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED` | `task:campaignPreviewRemoveFailed` |
444
+ | `TASK_CAMPAIGN_CONTACT_UPDATED` | `task:campaignContactUpdated` |
445
+ | `TASK_MID_CALL_SUMMARY_RECEIVED` | `task:midCallSummaryReceived` |
446
+ | `TASK_FEATURE_ENABLEMENT` | `task:featureEnablement` |
447
+
448
+ ## Requires (dependencies)
449
+ - Services contact/dialer AQM factories
450
+ - WebSocket and RTD WebSocket managers
451
+ - WebCallingService, MetricsManager, and task state machine
452
+
453
+ ## Requirements
454
+ | ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence |
455
+ |---|---|---|---|---|---|---|
456
+ | TASK-R-001 | Create only supported Voice/Digital Task implementations and throw `Unknown media type` for unsupported media. | Returning a generic task for unsupported channels would advertise controls the implementation cannot perform. | `src/services/task/TaskFactory.ts` | `test/unit/spec/services/task/TaskFactory.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
457
+ | TASK-R-002 | Concrete Task/Voice `hold()` and `resume()` implementations remain parameterless while `ITask` retains its optional compatibility parameter. | Documentation must distinguish the broad public interface from concrete runtime signatures. | `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 |
458
+ | TASK-R-003 | Task and media subclasses must route contact/calling operations into typed state-machine events and emit the complete TASK_EVENTS contract. | Consumers coordinate UI and interaction lifecycle from those events. | `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 |
459
+ | TASK-R-004 | TaskManager must consume primary/RTD streams and manage task creation, hydration, cleanup, campaign, and AI-assistant flows. | A single task owner prevents duplicate instances and inconsistent state across realtime sources. | `src/services/task/TaskManager.ts` | `test/unit/spec/services/task/TaskManager.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
460
+ | TASK-R-005 | The contact dependency belongs to Task/TaskFactory-created tasks; dialer is an AqmReqs request factory without that constructor. | Misattributing constructor dependencies causes invalid instantiation examples. | `src/services/task/TaskFactory.ts` | `test/unit/spec/services/task/dialer.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
461
+ | TASK-R-006 | Keep credentials and authentication outside Task; remote operations delegate through contact/dialer routing, AqmReqs, and Core/WebexRequest. | Task lifecycle objects should never duplicate host token handling or leak authentication state into interaction data. | `src/services/task/Task.ts`, `src/services/task/contact.ts`, `src/services/core/WebexRequest.ts` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/contact.ts` | None; authentication ownership is explicit. | PRESENT |
462
+ | TASK-R-007 | Route enabled preview-campaign accept, skip, and remove operations through the dialer AQM factory using `PreviewContactPayload`, returning `Promise<TaskResponse>` from the public ContactCenter methods. Before routing skip/remove, reject the operation when the matching task's disable flag is `'true'`. | Preview reservations require typed payloads and correlated backend completion, while campaign controls must block prohibited skip/remove requests before transport begins. | `src/cc.ts`, `src/services/task/dialer.ts`, `src/services/task/types.ts` | `test/unit/spec/cc.ts`, `test/unit/spec/services/task/dialer.ts` | Public delegation and dialer requests are covered; the `campaignPreviewSkipDisabled` and `campaignPreviewRemoveDisabled` early-exit guards lack direct unit coverage. Independent review identified this gap on 2026-07-15. | PRESENT |
463
+ | TASK-R-008 | Voice `dropConferenceParticipant` must validate the public target, resolve the latest main interaction, POST an empty body to the encoded participant-drop route, and settle only from `ParticipantLeftConference`, `ParticipantDropConferenceFailed`, HTTP failure, or the existing AQM timeout. | Participant removal is backend-authoritative and must preserve event-driven roster/state synchronization without exposing participant identifiers through telemetry or logs. | `src/services/task/voice/Voice.ts`, `src/services/task/contact.ts`, `src/services/task/types.ts` | `test/unit/spec/services/task/voice/Voice.ts`, `test/unit/spec/services/task/contact.ts`, `test/unit/spec/services/core/aqm-reqs.ts` | Backend authorization and media removal are remote-service responsibilities. | PRESENT |
464
+ | TASK-R-009 | ContactCenter must inject Desktop Profile collaboration policy into TaskFactory, and every created voice/digital Task must expose ordered Consult and Transfer destination categories through `TaskUIControls.consultTransferDestinations`. | Task consumers need one already-computed policy surface and must not fetch profile flags or duplicate destination decisions. | `src/cc.ts`, `src/services/task/TaskFactory.ts`, `src/services/task/types.ts` | `test/unit/spec/cc.ts`, `test/unit/spec/services/task/TaskFactory.ts` | Host-specific UI options may only further hide an SDK-allowed category. | PRESENT |
465
+ | TASK-R-010 | TaskManager must propagate backend owner promotion without electing an owner locally. `ContactOwnerChanged` resolves an exact task first, then one unique related task whose existing snapshot proves that the current Agent is active on `mainCall`, or whose incoming snapshot both names that Agent as `interaction.owner` and proves the same active-main-leg membership. Active membership requires a participant entry with `hasLeft !== true`. If no task exists, recovery is limited to that incoming owner=current-Agent proof: create the normal Task under the stable main interaction ID, silently HYDRATE its actor before listener installation, then process the original owner-change event so consumers receive one `task:hydrate` and no `task:incoming`. An owner-changing `ContactUpdated` uses the existing owner-change hydrate path only for an existing task, and a late `ParticipantLeftConference` cannot replace a confirmed active owner with the departed participant. | Every surviving agent must observe the backend-selected primary Agent immediately, including stale child-keyed and narrowly recoverable desynchronization cases, without duplicating tasks or exposing an uninitialized actor. | `src/services/task/TaskManager.ts` | `test/unit/spec/services/task/TaskManager.ts`, `test/unit/spec/services/task/Task.ts` | Backend owner selection and delivery of a complete owner-bearing `ContactOwnerChanged` payload remain remote responsibilities; missing-task `ContactUpdated` remains update-only. | PRESENT |
466
+ | TASK-R-011 | TaskManager must stamp `wrapUpRequired` once in `prepareEventContext` on `AGENT_WRAPUP`, `AGENT_CONSULT_CONFERENCE_ENDED`, `PARTICIPANT_LEFT_CONFERENCE`, and `AGENT_CONFERENCE_TRANSFERRED` before `updateTaskData` / mapper (mapper passes the already-stamped payload through). Voice `exitConference` stamps the same flag on `EXIT_CONFERENCE_SUCCESS` (that HTTP path skips TaskManager) using `shouldWrapUpForThisAgent`, then the same empty-pending self-exit fallback as leave (true unless this agent is a consulted non-owner). Voice pending-list precedence uses the correlated HTTP response only (a cached `agentsPendingWrapUp` is ignored when the response omits the list). `AGENT_WRAPUP` is always `true`. Conference-end uses explicit signals only (`agentsPendingWrapUp`, then `wrapUpRequired` / `isWrapUp`). A nonempty `agentsPendingWrapUp` is authoritative and wins over initiator fallback: `consultingAgentId === self` and actor `transferConferenceRequested` are honored only when the list is empty or absent. On participant leave: do not stamp when the actor ignores `PARTICIPANT_LEAVE` (`CONNECTED`, `HOLD_INITIATING`, `CONF_INITIATING`); stamp only in `HELD`, `RESUME_INITIATING`, `CONSULTING`, `CONSULT_INITIATING`, or `CONFERENCING` (or when no actor snapshot exists). A named other `participantId` is never self-leave (do not infer leave from a partial `participants` map); missing-map fallback applies only when `participantId` is absent (EP-DN), and when a previous task map exists it must have contained self. Remaining agents use explicit signals only; this agent left + nonempty pending uses `includes(self)`; this agent left + empty/absent pending stamps `true` unless this agent is a consulted non-owner. Remaining conference agents stay `false`. `setupAutoWrapupTimer()` still requires profile `autoWrapup !== false`, a valid interval, and a default reason. `PARTICIPANT_LEAVE` and Voice `EXIT_CONFERENCE_SUCCESS` into `WRAPPING_UP` do not transition-emit; the `WRAPPING_UP` entry still emits `task:wrapup`. `EXIT_CONFERENCE_SUCCESS` is handled in `CONNECTED`, `HELD`, and `CONFERENCING`. `TASK_WRAPUP` is handled in `HOLD_INITIATING`, `RESUME_INITIATING`, `CONSULT_INITIATING`, and `CONF_INITIATING` (transition `updateTaskData` + `markEnded` only). Late `TASK_WRAPUP` in `WRAPPING_UP` always runs `updateTaskData` and runs `emitTaskWrapup` only when `context.taskData.wrapUpRequired` was not already `true`. | Consumers rely on `task:wrapup` and `task.autoWrapup` when wrap-up starts; those surfaces only activate when ingress task data already has `wrapUpRequired === true`. | `src/services/task/TaskManager.ts`, `src/services/task/voice/Voice.ts`, `src/services/task/Task.ts`, `src/services/task/state-machine/TaskStateMachine.ts` | `test/unit/spec/services/task/TaskManager.ts`, `test/unit/spec/services/task/voice/Voice.ts`, `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Profile auto wrap-up, interval, and default reason remain agent-config responsibilities. | PRESENT |
467
+
468
+ ## Design Overview
469
+ Task separates its stable consumption boundary from collaborators so ownership and failure behavior stay explicit. A shared Task base preserves a stable API while media-specific subclasses and a separate state engine enforce capability differences.
470
+
471
+ | Channel | Description |
472
+ |---|---|
473
+ | `telephony` | Voice calls |
474
+ | `chat` | Web chat |
475
+ | `email` | Email interactions |
476
+ | `social` | Social media |
477
+ | `sms` | Unsupported by TaskFactory; throws `Unknown media type` |
478
+ | `facebook` | Unsupported by TaskFactory; throws `Unknown media type` |
479
+ | `whatsapp` | Unsupported by TaskFactory; throws `Unknown media type` |
480
+
481
+ If enabled in agent profile, wrapup completes automatically after timeout. The timer starts only when ingress task data has `wrapUpRequired === true` (TASK-R-011).
482
+
483
+ > **Host-application example:** `updateWrapupStatus` is a consumer-owned UI callback, not SDK package logging.
484
+
485
+ ```typescript
486
+ task.on('task:wrappedup', () => {
487
+ updateWrapupStatus('completed');
488
+ });
489
+ ```
490
+
491
+ > **Purpose**: Technical documentation for task lifecycle management.
492
+
493
+ TaskManager is a singleton that:
494
+
495
+ 1. Listens for WebSocket task events
496
+
497
+ 2. Creates/manages Task objects
498
+
499
+ 3. Routes events to appropriate tasks
500
+
501
+ 4. Handles WebRTC call mapping
502
+
503
+ ```typescript
504
+ // Singleton access
505
+ const taskManager = TaskManager.getTaskManager(contact, webCallingService, webSocketManager);
506
+ ```
507
+
508
+ Returns an object of AQM request methods wired to `TASK_API` and `TASK_MESSAGE_TYPE`.
509
+
510
+ **Methods**
511
+
512
+ - `accept`
513
+
514
+ - `hold`
515
+
516
+ - `unHold`
517
+
518
+ - `pauseRecording`
519
+
520
+ - `resumeRecording`
521
+
522
+ - `consult`
523
+
524
+ - `consultEnd`
525
+
526
+ - `consultAccept`
527
+
528
+ - `blindTransfer`
529
+
530
+ - `vteamTransfer`
531
+
532
+ - `consultTransfer`
533
+
534
+ - `end`
535
+
536
+ - `wrapup`
537
+
538
+ - `cancelTask`
539
+
540
+ - `cancelCtq`
541
+
542
+ - `consultConference`
543
+
544
+ - `dropConferenceParticipant`
545
+
546
+ - `exitConference`
547
+
548
+ - `conferenceTransfer`
549
+
550
+ **Notes**
551
+
552
+ - Uses `WCC_API_GATEWAY`.
553
+
554
+ - Consult with `DESTINATION_TYPE.QUEUE` uses `TIMEOUT_REQ` = `'disabled'` for the request timeout.
555
+
556
+ Returns an object of AQM request methods for outbound dialing.
557
+
558
+ **Methods**
559
+
560
+ - `startOutdial` (success: `CC_EVENTS.AGENT_OFFER_CONTACT`, failure: `CC_EVENTS.AGENT_OUTBOUND_FAILED`)
561
+
562
+ - Task/TaskFactory-created task instances receive `contact: ReturnType<typeof routingContact>`; `dialer.ts` has no such constructor and is an AqmReqs factory.
563
+
564
+ - Uses:
565
+
566
+ - `contact.vteamTransfer` / `contact.blindTransfer` in `transfer(...)`.
567
+
568
+ - While in consulting state, `transfer(...)` internally routes through consult-transfer behavior.
569
+
570
+ - `contact.end` in `end()`.
571
+
572
+ - `contact.wrapup` in `wrapup(...)`.
573
+
574
+ Uses `contact` for:
575
+
576
+ - `hold`, `unHold`
577
+
578
+ - `pauseRecording`, `resumeRecording`
579
+
580
+ - `consult`, `consultEnd`, `consultTransfer`
581
+
582
+ - `consultConference`, `exitConference`, `conferenceTransfer`
583
+
584
+ - `dropConferenceParticipant` (Voice only; base Task rejects as unsupported)
585
+
586
+ Uses `contact.accept` in `accept()`.
587
+
588
+ TaskManager maintains a map of active tasks:
589
+
590
+ ```typescript
591
+ private taskCollection: Record<TaskId, ITask> = {};
592
+
593
+ // Tasks indexed by interactionId
594
+ this.taskCollection[interactionId] = task;
595
+
596
+ // Retrieve task
597
+ const task = this.taskCollection[interactionId];
598
+ ```
599
+
600
+ TaskManager uses a staged pipeline in `registerTaskListeners()`:
601
+
602
+ ```typescript
603
+ this.webSocketManager.on('message', (event) => {
604
+ // 1) Parse and validate message
605
+ const message = TaskManager.parseWebSocketMessage(event);
606
+ if (!message) return;
607
+
608
+ // 2) Build event context (task, payload, mapped state-machine event)
609
+ const eventContext = this.prepareEventContext(message);
610
+ if (!eventContext) return;
611
+
612
+ // 3) Handle lifecycle changes (create/update/remove task)
613
+ const actions = this.handleTaskLifecycleEvent(eventContext);
614
+ const {task} = actions;
615
+ if (!task) return;
616
+
617
+ // 4) Keep task.data synchronized
618
+ const {payload, stateMachineEvent} = eventContext;
619
+ if (payload) this.updateTaskData(task, payload);
620
+
621
+ // 5) Drive state machine (which emits TASK_EVENTS)
622
+ if (stateMachineEvent) {
623
+ task.sendStateMachineEvent(stateMachineEvent);
624
+ }
625
+ });
626
+ ```
627
+
628
+ `TaskManager.handleRealtimeWebsocketEvent()` handles payloads arriving on the realtime subscription socket used for AI features. It:
629
+
630
+ 1. Parses the JSON websocket envelope
631
+
632
+ 2. Reads `conversationId` from `payload.data.data.conversationId` and resolves the owning task
633
+
634
+ 3. Emits the payload type and `payload.data` on the task for `REAL_TIME_TRANSCRIPTION`
635
+
636
+ 4. Does the same for `SUGGESTED_RESPONSE`
637
+
638
+ 5. Logs and returns when JSON parsing fails or the interaction has no task; other payload types, including `SUGGESTED_RESPONSE_ACKNOWLEDGE`, fall through without task emission
639
+
640
+ This keeps transcript and suggestion delivery aligned on the same per-task event surface.
641
+
642
+ **wxApp outdial `ContactEnded` / `AgentOutboundFailed` mapping:** Applies only when the current agent is a **wxApp-managed** outdial (`enableWxBetterTogether` + agent participant `deviceType: wxApp`). Pre-accept decline maps to `CONTACT_ENDED` when `wxAppAnswerPending` is false and `agentsPendingWrapUp` is empty — even if the backend sends misleading `interaction.state: wrapUp`. Post-accept wxApp agent end remaps to `OUTBOUND_FAILED` / `AGENT_ENDS` when `wxAppAnswerPending`, `agentsPendingWrapUp`, or `interaction.state === 'wrapUp'` indicates wrapup. **Non-wxApp** agent-terminated `AgentOutboundFailed` (`AGENT_ENDS` / `terminatingParty: Agent`) stays on `OUTBOUND_FAILED` so the OFFERED-state `shouldWrapUp` path runs; `suppressOutdialFailedPopup` on `taskData` skips the outdial-failed modal only. Non-wxApp `ContactEnded` keeps the legacy path. Other failure reasons (`CUSTOMER_BUSY`, etc.) still use `OUTBOUND_FAILED` with the popup. See `TaskManager.isWxAppManagedOutdialTask`, `isAgentTerminatedOutdialWrapup`, and `task-state-machine-spec.md`.
643
+
644
+ **wxApp consumer contract (WXCC-6026):** Hosts enable `enableWxBetterTogether` at init (Phase 1 init-only; re-init to change), bind UI to `task.uiControls` (including optional `main.keypad`), and call **`task.accept()`**, **`task.decline()`**, **`task.toggleMute({ muted? })`**, **`task.transmitDtmf({ dtmf })`**. SDK routes wxApp telephony internally on `Voice`. Shared-line `lineOwnerId` defaults from the wxApp agent participant when omitted.
645
+
646
+ **wxApp OFFERED diagnostics:** `Voice.updateUiControls` delegates offer-decision logging and deferred participant-mismatch WARN/metric to `voice/wxAppOfferObservability.ts` (500 ms grace; observability only). After usersub publish, `TaskManager.refreshWxAppOfferObservabilityForAllTasks()` re-invokes the observer without emitting UI controls. `removeTaskFromCollection` calls `disposeWxAppOfferObservability()` so a pending grace timer cannot emit after the task is dropped (including `CONTACT_MERGED` child removal). Dispose is terminal: the observer sets a disposed guard (timer cancel plus no-op `handleUiControlsUpdate`) so a later `updateUiControls` / refresh cannot schedule a new grace timer.
647
+
648
+ **wxApp offer UI (`uiControlsComputer`):** `wxAppAcceptInFlight` disables accept/decline during the accept REST call. `wxAppAnswerPending` additionally disables accept and decline for **inbound** offers until ASSIGN; wxApp **outdial** keeps decline enabled during the post-accept "Calling…" phase so `cancelTask` remains available.
649
+
650
+ **wxApp decline observability:** Inbound wxApp offers decline via telephony `rejectCall` (`runWxAppReject` → `WXAPP_TASK_DECLINE_*`). wxApp outdial cancellations use CC routing `cancelTask` (`runWxAppOutdialDecline` → additive `WXAPP_TASK_DECLINE_*` plus existing `TASK_DECLINE_*`).
651
+
652
+ **wxApp mute backfill guard (`Voice.syncWxAppMuteFromCallDetails`):** Skips telephony `GET /calls/{callId}` when the interaction is terminated or the task is a pre-accept wxApp OFFERED offer (`wxAppAnswerPending` false). Post-accept OFFERED and engaged CONNECTED sync still run. `isWxAppEngagedForControls` and `getWebexCallingCallId` exclude `TERMINATED` and `COMPLETED`. Expected 400 / "Call not found" / `101002` responses are not logged as errors.
653
+
654
+ For BROWSER login, TaskManager integrates with WebCalling:
655
+
656
+ ```mermaid
657
+ flowchart TD
658
+ A[AgentContactReserved event] --> B[Determine media + loginOption]
659
+ B --> C[TaskFactory chooses Voice/WebRTC class]
660
+ C --> D[Create Task object]
661
+ D --> E[Store in taskCollection]
662
+ E --> F[Send TASK_INCOMING to state machine]
663
+ F --> G[Emit task:incoming]
664
+ H[Independent LINE_EVENTS.INCOMING_CALL] --> I[Find current non-preview telephony task]
665
+ I --> J[Map call ID to interaction ID]
666
+ J --> K[Send TASK_INCOMING association event]
667
+ ```
668
+
669
+ ```typescript
670
+ // WebCallingService maps call IDs to interaction IDs
671
+ this.webCallingService.mapCallToTask(callId, interactionId);
672
+
673
+ // Task uses call for media operations
674
+ this.webCallingService.answerCall(localAudioStream: LocalMicrophoneStream, taskId: string);
675
+ ```
676
+
677
+ AutoWrapup handles automatic task completion:
678
+
679
+ ```typescript
680
+ // AutoWrapup.ts
681
+ export default class AutoWrapup {
682
+ private timer: ReturnType<typeof setTimeout> | null = null;
683
+ private readonly interval: number;
684
+
685
+ start(onComplete: () => void) {
686
+ this.timer = setTimeout(onComplete, this.interval);
687
+ }
688
+
689
+ clear() {
690
+ if (this.timer) {
691
+ clearTimeout(this.timer);
692
+ this.timer = null;
693
+ }
694
+ }
695
+
696
+ getTimeLeft() {}
697
+ isRunning() {}
698
+ getTimeLeftSeconds() {}
699
+ }
700
+ ```
701
+
702
+ Each task operation maps to an AQM request:
703
+
704
+ ```typescript
705
+ // contact.ts
706
+ export default function routingContact(routing: AqmReqs) {
707
+ return {
708
+ accept: routing.req((p) => ({
709
+ url: '/v1/tasks/.../accept',
710
+ notifSuccess: { bind: { type: CC_EVENTS.AGENT_CONTACT_ASSIGNED }},
711
+ notifFail: { bind: { type: CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED }},
712
+ })),
713
+
714
+ hold: routing.req((p) => ({...})),
715
+ unHold: routing.req((p) => ({...})),
716
+ consultAccept: routing.req((p) => ({...})),
717
+ cancelTask: routing.req((p) => ({...})),
718
+ cancelCtq: routing.req((p) => ({...})),
719
+ end: routing.req((p) => ({...})),
720
+ wrapup: routing.req((p) => ({...})),
721
+ blindTransfer: routing.req((p) => ({...})),
722
+ consult: routing.req((p) => ({...})),
723
+ consultTransfer: routing.req((p) => ({...})),
724
+ // ... more operations
725
+ };
726
+ }
727
+ ```
728
+
729
+ Helper functions for task state analysis:
730
+
731
+ ```typescript
732
+ // TaskUtils.ts
733
+
734
+ // Check if participant is in main interaction
735
+ isParticipantInMainInteraction(task, agentId);
736
+
737
+ // Check if conference is in progress
738
+ getIsConferenceInProgress(taskData);
739
+
740
+ // Check if agent is primary
741
+ isPrimary(task, agentId);
742
+
743
+ // Check if secondary EPDN agent
744
+ isSecondaryEpDnAgent(interaction);
745
+ ```
746
+
747
+ | Metric | Type | When Tracked |
748
+ |---|---|---|
749
+ | `TASK_ACCEPT_SUCCESS` | behavioral, business | Task accepted |
750
+ | `TASK_HOLD_SUCCESS` | operational | Hold succeeded |
751
+ | `TASK_END_SUCCESS` | behavioral, business | Task ended |
752
+ | `TASK_WRAPUP_SUCCESS` | operational | Wrapup completed |
753
+ | `TASK_TRANSFER_SUCCESS` | behavioral, business | Transfer completed |
754
+ | `TASK_OUTDIAL_SUCCESS` | behavioral, business | Outdial completed |
755
+ | `TASK_CONFERENCE_PARTICIPANT_DROP_SUCCESS` | operational, behavioral, business | Participant Drop completed |
756
+
757
+ ## Data Flow
758
+ 1. **WebSocket event arrives** → `TaskManager` maps CC event to `TaskEvent`.
759
+
760
+ 2. **Task creation** (if new) → `TaskFactory` builds `Voice`/`WebRTC`/`Digital`.
761
+
762
+ 3. **State machine actor starts** → `Task` wires emitters + UI control updates.
763
+
764
+ 4. **Task method called** (e.g., hold/transfer) → delegates to `contact.ts` or `dialer.ts`.
765
+
766
+ 5. **State transitions** → guards/actions update context and emit `task:*` events.
767
+
768
+ 6. **SDK consumers update UI** → `TaskUIControls` reflect the latest state.
769
+
770
+ ```mermaid
771
+ flowchart TD
772
+ A[WebSocket event arrives] --> B[TaskManager maps CC event to TaskEvent]
773
+ B --> C{Task exists?}
774
+ C -- No --> D[TaskFactory creates Voice/WebRTC/Digital]
775
+ C -- Yes --> E[Use existing task]
776
+ D --> F[Task initializes state machine actor]
777
+ E --> F
778
+ F --> G[Task method called (hold/transfer/etc)]
779
+ G --> H[contact.ts or dialer.ts API call]
780
+ H --> I[State machine transition]
781
+ I --> J[Actions + guards update context]
782
+ J --> K[Emit task:* events]
783
+ K --> L[TaskUIControls updated for SDK UI]
784
+ ```
785
+
786
+ ## Sequence Diagram(s)
787
+ Sequence coverage:
788
+
789
+ | Operation group | Diagram | Failure / recovery coverage |
790
+ |---|---|---|
791
+ | Incoming task creation | Incoming task | Unsupported media or incomplete event context prevents publication of a partial task. |
792
+ | Voice hold/resume | Hold or resume | Invalid state rejects before transport; failure notification/HTTP error/timeout sends the matching failure event and throws. |
793
+ | Consult/transfer/conference | Consult and transfer | Backend failure notifications drive explicit failure actions and preserve stable call context. |
794
+ | Participant Drop | Participant Drop | Validation rejects locally; AQM success/failure/HTTP/timeout paths settle and clean up without optimistic state changes. |
795
+ | Wrapup/end | Wrapup or end | Validation and backend failures throw; backend events retain distinct WRAPPING_UP/COMPLETED/TERMINATED outcomes. |
796
+ | WebRTC and digital accept | Channel-specific accept | WebRTC media/calling failure and Digital AQM failure follow different rejection paths. |
797
+
798
+ ### Incoming task
799
+
800
+ ```mermaid
801
+ sequenceDiagram
802
+ participant Backend
803
+ participant WS as Primary WebSocket
804
+ participant TM as TaskManager
805
+ participant TF as TaskFactory
806
+ participant Task
807
+ participant CC as ContactCenter
808
+ participant App as Application
809
+ Backend-->>WS: AgentContactReserved event
810
+ WS-->>TM: message event
811
+ TM->>TM: parse and prepare event context
812
+ alt task is new and media is supported
813
+ TM->>TF: createTask(data, dependencies)
814
+ TF-->>TM: Voice / WebRTC / Digital
815
+ TM->>Task: initialize and store
816
+ TM->>Task: send mapped TaskEvent
817
+ TM-->>CC: emit TASK_INCOMING
818
+ CC-->>App: task:incoming
819
+ opt a BROWSER telephony INCOMING_CALL arrives
820
+ TM->>TM: map call ID to current telephony task
821
+ TM->>Task: send TASK_INCOMING for call/task association
822
+ end
823
+ else unsupported media
824
+ TF-->>TM: throw Unknown media type
825
+ else invalid/unmapped event
826
+ TM->>TM: ignore without publishing a partial task
827
+ end
828
+ ```
829
+
830
+ ### Hold or resume
831
+
832
+ ```mermaid
833
+ sequenceDiagram
834
+ participant App
835
+ participant Voice
836
+ participant Actor as Task state-machine actor
837
+ participant Contact as routingContact
838
+ participant AQM as AqmReqs
839
+ participant WR as WebexRequest
840
+ participant WS as Primary WebSocket
841
+ App->>Voice: hold() or resume()
842
+ Voice->>Voice: derive media resource and validate CONNECTED/HELD state
843
+ alt invalid state
844
+ Voice-->>App: throw before transport
845
+ else valid state
846
+ Voice->>Actor: HOLD_INITIATED or UNHOLD_INITIATED
847
+ Voice->>Contact: hold/unHold({interactionId, data: {mediaResourceId}})
848
+ Contact->>AQM: generated request with success/failure binds
849
+ AQM->>WR: authenticated HTTP request
850
+ WR-->>AQM: acknowledgement only
851
+ alt matching success notification
852
+ WS-->>AQM: AGENT_CONTACT_HELD/UNHELD
853
+ AQM-->>Voice: TaskResponse
854
+ Voice->>Actor: HOLD_SUCCESS or UNHOLD_SUCCESS
855
+ Voice-->>App: resolve
856
+ else matching failure, HTTP rejection, or timeout
857
+ WS-->>AQM: failure notification or no completion
858
+ Voice->>Actor: HOLD_FAILED or UNHOLD_FAILED
859
+ Voice-->>App: throw detailed error
860
+ end
861
+ end
862
+ ```
863
+
864
+ ### Consult and transfer
865
+
866
+ ```mermaid
867
+ sequenceDiagram
868
+ participant App
869
+ participant Voice
870
+ participant Actor as Task state-machine actor
871
+ participant Contact as routingContact
872
+ participant WS as Primary WebSocket
873
+ App->>Voice: consult/transfer/conference operation
874
+ Voice->>Actor: initiating event
875
+ Voice->>Contact: correlated AQM operation
876
+ alt matching success notification
877
+ WS-->>Contact: consult/transfer/conference success
878
+ Contact-->>Voice: TaskResponse
879
+ Voice->>Actor: success event and updated call context
880
+ Voice-->>App: resolve
881
+ else failure/cancel/timeout
882
+ WS-->>Contact: failure or cancel notification
883
+ Voice->>Actor: matching failure/end event
884
+ Voice-->>App: throw while preserving stable main/consult context
885
+ end
886
+ ```
887
+
888
+ ### Participant Drop
889
+
890
+ ```mermaid
891
+ sequenceDiagram
892
+ participant App
893
+ participant Voice
894
+ participant Contact as routingContact
895
+ participant AQM as AqmReqs
896
+ participant Backend
897
+ participant WS as Primary WebSocket
898
+ participant TM as TaskManager/state machine
899
+ App->>Voice: dropConferenceParticipant({participantId})
900
+ alt participantId is invalid
901
+ Voice-->>App: reject before metrics, HTTP, or bind registration
902
+ else valid target
903
+ Voice->>Voice: select latest main interaction ID
904
+ Voice->>Contact: participant Drop request
905
+ Contact->>AQM: POST encoded path with empty body and sensitive-log redaction
906
+ AQM->>Backend: authenticated HTTP initiation
907
+ alt ParticipantLeftConference
908
+ Backend-->>WS: ParticipantLeftConference
909
+ WS-->>AQM: correlated success
910
+ AQM-->>Voice: TaskResponse
911
+ WS-->>TM: existing PARTICIPANT_LEAVE mapping updates roster/conference state
912
+ Voice-->>App: resolve
913
+ else ParticipantDropConferenceFailed, HTTP failure, or timeout
914
+ AQM-->>Voice: structured rejection
915
+ Voice-->>App: reject without participant data in logs/metrics
916
+ end
917
+ end
918
+ ```
919
+
920
+ `ParticipantLeftConference` is authoritative for both a customer hangup and an
921
+ agent-initiated customer Drop. The task removes only participants absent from
922
+ the latest interaction snapshot. TaskManager first uses the exact interaction
923
+ key, then the existing reservation fallback, and finally one unique task whose
924
+ `interaction.mainInteractionId`, interaction-level `parentInteractionId`, or
925
+ `callProcessingDetails.parentInteractionId` matches an identifier carried by the
926
+ lifecycle event. Object aliases are deduplicated and all aliases are removed
927
+ during terminal cleanup; ambiguous related-interaction matches are ignored
928
+ without logging participant data. Events carrying none of those correlation
929
+ identifiers are not routed. `ContactMerged` removes the EP-DN child task,
930
+ hydrates the main-interaction task, and publishes the existing `task:merged`
931
+ event so consumers can rebind to the surviving task object.
932
+
933
+ Dropping an Agent while that Agent is consulting continues through the existing
934
+ `PARTICIPANT_LEAVE` transition, which clears consult state and emits the normal
935
+ participant-left and task-end lifecycle. `PARTICIPANT_LEAVE` is handled in
936
+ `HELD`, `RESUME_INITIATING`, `CONSULTING`, `CONSULT_INITIATING`, and
937
+ `CONFERENCING`; `CONNECTED`, `HOLD_INITIATING`, and `CONF_INITIATING` ignore the
938
+ event. The current Agent is treated as departed only when the event names that
939
+ Agent or the updated participants map omits that Agent (EP-DN removal). Remaining
940
+ in the map with `hasLeft`, or disappearing only from `mainCall` media, is
941
+ non-terminal. A consulted Agent receiving `AgentConsultEnded` continues through
942
+ `CONSULT_END`; current-agent departure is evaluated before consult-initiator
943
+ recovery and does not use mainCall membership as self-departure evidence.
944
+ Accepted consultees emit `task:consultEnd` and `task:end`, while an unaccepted
945
+ OFFERED consultee retains the existing consult-end-only signal. A surviving
946
+ consult initiator still returns to the main-call state selected by the existing
947
+ guards. The state-machine transition table lives in
948
+ `src/services/task/state-machine/ai-docs/task-state-machine-spec.md`
949
+ `TASK_STATE_MACHINE-R-008`.
950
+
951
+ Primary-Agent promotion remains backend-authoritative and follows the two-event
952
+ desktop contract. `ContactOwnerChanged` updates the promoted Agent; TaskManager
953
+ prefers an exact task, then one unique related task resolved through nested
954
+ main/parent identifiers, the task collection key, or the `mainCall` media-map
955
+ identity. A related candidate is eligible when its current
956
+ snapshot contains the current Agent's participant entry with `hasLeft !== true`
957
+ and membership on the `mType: mainCall` leg. The incoming snapshot can provide
958
+ that evidence only when it also names the current Agent as `interaction.owner`;
959
+ this permits an authoritative promotion payload to repair a stale child-keyed
960
+ snapshot while still excluding consult-only tasks. The update keeps the
961
+ surviving main interaction identity even when the notification names a
962
+ promoted-Agent child interaction. Conflicting main identifiers, ambiguous
963
+ matches, and a canonical key occupied by a distinct task are ignored rather
964
+ than replacing an existing task.
965
+
966
+ If no related task exists, TaskManager recovers only the promoted current Agent
967
+ from a non-terminal telephony `ContactOwnerChanged` payload that provides the same
968
+ active-main-leg evidence. It creates the normal Task through TaskFactory under the
969
+ stable main interaction ID, sends an internal HYDRATE before installing external
970
+ listeners, and then processes the original owner-change event. Consumers
971
+ therefore receive one `task:hydrate`, never a synthetic `task:incoming`, and a
972
+ later canonical contact or merge event reuses the same task rather than creating
973
+ an alias. Missing, partial, departed, consult-only, non-promoted, and ambiguous
974
+ payloads are ignored for recovery.
975
+
976
+ For other surviving Agents, a `ContactUpdated` whose non-empty
977
+ `interaction.owner` differs from the current owner is delivered through the
978
+ existing `CONTACT_OWNER_CHANGED`/`task:hydrate` path; same-owner or owner-less
979
+ updates remain data-only, and a missing task is not created from `ContactUpdated`.
980
+ If a later `ParticipantLeftConference` still names the departed participant as
981
+ owner, an already confirmed owner is retained only when the new roster proves
982
+ that confirmed owner remains active on `mainCall` and explicitly names the
983
+ incoming owner as `participantId` or marks it `hasLeft`. Main-call omission alone
984
+ is not departure evidence. The SDK never chooses the successor locally.
985
+
986
+ In the Contact Center sample roster, non-owner and Supervisor targets remain
987
+ visible but non-actionable; the UI does not display a separate read-only label.
988
+ The roster is visible whenever the viewing agent remains active on the main leg
989
+ and at least one supported non-customer participant (Agent, EP-DN, or Supervisor)
990
+ is visible. Consequently, Customer-only calls use the original 1-to-1 UI, while
991
+ a single Agent remains visible after the Customer leaves. Dropping the last
992
+ non-customer participant while the Customer remains restores the 1-to-1 UI.
993
+ Terminated tasks and viewing-agent departure hide the roster immediately.
994
+
995
+ The current consult leg may additionally contribute a pending EP-DN row before
996
+ conference merge. The sample displays its `dn`, then its participant/media key
997
+ as a fallback, and deduplicates the row once it joins the main leg. The owner can
998
+ see a disabled Drop action until merge; other active main-call agents see the
999
+ same row without an action. Consult-only Agent targets remain excluded.
1000
+
1001
+ ### Wrapup or end
1002
+
1003
+ ```mermaid
1004
+ sequenceDiagram
1005
+ participant App
1006
+ participant Task
1007
+ participant Contact as routingContact
1008
+ participant WS as Primary WebSocket
1009
+ participant Actor as Task state-machine actor
1010
+ App->>Task: wrapup(payload) or end()
1011
+ alt invalid wrapup payload or missing task data
1012
+ Task-->>App: throw validation error
1013
+ else valid request
1014
+ Task->>Contact: wrapup/end({interactionId, data})
1015
+ alt matching success notification
1016
+ WS-->>Contact: wrapup/end success
1017
+ Contact-->>Task: TaskResponse
1018
+ WS-->>Actor: backend event selects WRAPPING_UP/COMPLETED/TERMINATED
1019
+ Task-->>App: resolve
1020
+ else failure, HTTP rejection, or timeout
1021
+ Contact-->>Task: structured rejection
1022
+ Task-->>App: throw detailed error
1023
+ end
1024
+ end
1025
+ ```
1026
+
1027
+ ### Channel-specific accept
1028
+
1029
+ ```mermaid
1030
+ sequenceDiagram
1031
+ participant App
1032
+ participant TF as TaskFactory
1033
+ participant WebRTC
1034
+ participant Digital
1035
+ participant Calling as WebCallingService
1036
+ participant Contact as routingContact
1037
+ TF-->>App: WebRTC or Digital task
1038
+ alt WebRTC accept
1039
+ App->>WebRTC: accept()
1040
+ WebRTC->>WebRTC: getUserMedia(audio)
1041
+ WebRTC->>Calling: answerCall(localAudioStream, interactionId)
1042
+ alt media/calling succeeds
1043
+ WebRTC-->>App: resolve
1044
+ else media/calling failure
1045
+ WebRTC-->>App: throw detailed error
1046
+ end
1047
+ else Digital accept
1048
+ App->>Digital: accept()
1049
+ Digital->>Contact: accept({interactionId})
1050
+ alt AQM success notification
1051
+ Contact-->>Digital: TaskResponse
1052
+ Digital-->>App: resolve
1053
+ else failure or timeout
1054
+ Digital-->>App: throw detailed error
1055
+ end
1056
+ end
1057
+ ```
1058
+
1059
+ ## Class / Component Relationships
1060
+ - **Hierarchy**: `Task` (base) → `Voice` → `WebRTC`; `Digital` extends `Task`.
1061
+
1062
+ - **`Task` (base)**: Holds task data, emits SDK events, and provides default (unsupported) implementations for call control APIs.
1063
+
1064
+ - **`Voice`**: Adds hold/resume and consult-related capabilities for telephony tasks.
1065
+
1066
+ - **`WebRTC`**: Overrides `accept/decline` for WebRTC calls and hooks media events.
1067
+
1068
+ - **`Digital`**: Implements `accept` and refreshes digital task data/UI controls.
1069
+
1070
+ | Component | File | Responsibility |
1071
+ |---|---|---|
1072
+ | `TaskManager` | `task/TaskManager.ts` | Task lifecycle coordination |
1073
+ | `Task` | `task/Task.ts` | Individual task operations |
1074
+ | `contact` | `task/contact.ts` | AQM request definitions |
1075
+ | `dialer` | `task/dialer.ts` | Outbound call initiation |
1076
+ | `AutoWrapup` | `task/AutoWrapup.ts` | Auto wrapup timer |
1077
+ | `taskDataNormalizer` | `task/taskDataNormalizer.ts` | Normalizes backend task payloads |
1078
+ | `TaskUtils` | `task/TaskUtils.ts` | Utility functions |
1079
+ | `state-machine` | `task/state-machine/*` | Task state transitions, guards, and UI control computation |
1080
+
1081
+ **File:** `Task.ts`
1082
+
1083
+ **Properties**
1084
+
1085
+ - `data: TaskData`
1086
+
1087
+ - `webCallMap: Record<TaskId, CallId>`
1088
+
1089
+ - `stateMachineService?: ActorRefFrom<TaskStateMachine>`
1090
+
1091
+ - `state?: SnapshotFrom<TaskStateMachine>`
1092
+
1093
+ - `autoWrapup?: AutoWrapup`
1094
+
1095
+ - `uiControls: TaskUIControls` (getter)
1096
+
1097
+ **Methods**
1098
+
1099
+ - `accept(): Promise<TaskResponse>` (abstract)
1100
+
1101
+ - `decline(): Promise<TaskResponse>` (default: unsupportedMethodError)
1102
+
1103
+ - `pauseRecording(): Promise<TaskResponse>` (default: unsupportedMethodError)
1104
+
1105
+ - `resumeRecording(resumeRecordingPayload: ResumeRecordingPayload): Promise<TaskResponse>` (default: unsupportedMethodError)
1106
+
1107
+ - `consult(consultPayload: ConsultPayload): Promise<TaskResponse>` (default: unsupportedMethodError)
1108
+
1109
+ - `endConsult(consultEndPayload?: ConsultEndPayload): Promise<TaskResponse>` (default: unsupportedMethodError)
1110
+
1111
+ - `consultTransfer(consultTransferPayload?: ConsultTransferPayLoad): Promise<TaskResponse>` (default: unsupportedMethodError)
1112
+
1113
+ - `consultConference(): Promise<TaskResponse>` (default: unsupportedMethodError)
1114
+
1115
+ - `exitConference(): Promise<TaskResponse>` (default: unsupportedMethodError)
1116
+
1117
+ - `transferConference(): Promise<TaskResponse>` (default: unsupportedMethodError)
1118
+
1119
+ - `switchCall(): Promise<TaskResponse>` (default: unsupportedMethodError)
1120
+
1121
+ - `toggleMute(): Promise<void>` (default: unsupportedMethodError)
1122
+
1123
+ - `unregisterWebCallListeners(): void` (default: no-op + log)
1124
+
1125
+ - `cancelAutoWrapupTimer(): void`
1126
+
1127
+ - Concrete `Task.hold()` / `Voice.hold()` are parameterless; the `ITask` compatibility interface permits `hold(mediaResourceId?: string)`.
1128
+
1129
+ - Concrete `Task.resume()` / `Voice.resume()` are parameterless; the `ITask` compatibility interface permits `resume(mediaResourceId?: string)`.
1130
+
1131
+ - `holdResume(): Promise<TaskResponse>` (default: unsupportedMethodError)
1132
+
1133
+ - `sendStateMachineEvent(event: TaskEventPayload): void`
1134
+
1135
+ - `updateTaskData(updatedData: TaskData, shouldOverwrite = false): ITask`
1136
+
1137
+ - `transfer(transferPayload: TransferPayLoad): Promise<TaskResponse>`
1138
+
1139
+ - `end(): Promise<TaskResponse>`
1140
+
1141
+ - `wrapup(wrapupPayload: WrapupPayLoad): Promise<TaskResponse>`
1142
+
1143
+ **File:** `voice/Voice.ts`
1144
+
1145
+ **Notes**
1146
+
1147
+ - Extends `Task`.
1148
+
1149
+ - Provides `hold()` and `resume()` that delegate to `holdResume()`.
1150
+
1151
+ - Explicitly overrides `accept()` and `decline()` to throw `unsupportedMethodError`.
1152
+
1153
+ - `WebRTC` then overrides these methods with concrete implementations.
1154
+
1155
+ **File:** `voice/WebRTC.ts`
1156
+
1157
+ **Notes**
1158
+
1159
+ - Extends `Voice`.
1160
+
1161
+ - Overrides `accept()` and `decline()` for WebRTC calls.
1162
+
1163
+ - Emits `TASK_EVENTS.TASK_MEDIA` on remote media (`CALL_EVENT_KEYS.REMOTE_MEDIA`).
1164
+
1165
+ - Overrides `unregisterWebCallListeners()`.
1166
+
1167
+ **File:** `digital/Digital.ts`
1168
+
1169
+ **Notes**
1170
+
1171
+ - Extends `Task`.
1172
+
1173
+ - Implements `accept()`.
1174
+
1175
+ - Overrides `updateTaskData()` to refresh digital task data and UI controls.
1176
+
1177
+ **File:** `TaskFactory.ts`
1178
+
1179
+ **API**
1180
+
1181
+ - `createTask(contact, webCallingService, data, configFlags, wrapupData?, agentId?, agentName?): Task`
1182
+
1183
+ **Behavior**
1184
+
1185
+ - Chooses `WebRTC` vs `Voice` for `MEDIA_CHANNEL.TELEPHONY` based on `webCallingService.loginOption`.
1186
+
1187
+ - Chooses `Digital` for `MEDIA_CHANNEL.CHAT`, `MEDIA_CHANNEL.EMAIL`, `MEDIA_CHANNEL.SOCIAL`.
1188
+
1189
+ - Throws `Error` for unknown media types.
1190
+
1191
+ ```mermaid
1192
+ classDiagram
1193
+ class Task {
1194
+ <<abstract>>
1195
+ # contact
1196
+ # metricsManager
1197
+ + data: TaskData
1198
+ + webCallMap: Record~TaskId, CallId~
1199
+ + stateMachineService
1200
+ + state
1201
+ # currentUiControls: TaskUIControls
1202
+ # uiControlConfig: UIControlConfig
1203
+ + autoWrapup: AutoWrapup
1204
+ + accept() TaskResponse
1205
+ + transfer(payload) TaskResponse
1206
+ + end() TaskResponse
1207
+ + wrapup(payload) TaskResponse
1208
+ + updateTaskData(updatedData, shouldOverwrite) ITask
1209
+ + sendStateMachineEvent(event) void
1210
+ + hold() TaskResponse
1211
+ + resume() TaskResponse
1212
+ + holdResume() TaskResponse
1213
+ + consult(payload) TaskResponse
1214
+ + endConsult(payload) TaskResponse
1215
+ + consultTransfer(payload) TaskResponse
1216
+ + consultConference() TaskResponse
1217
+ + exitConference() TaskResponse
1218
+ + transferConference() TaskResponse
1219
+ + pauseRecording() TaskResponse
1220
+ + resumeRecording(payload) TaskResponse
1221
+ + toggleMute() void
1222
+ + unregisterWebCallListeners() void
1223
+ + cancelAutoWrapupTimer() void
1224
+ }
1225
+
1226
+ class Voice {
1227
+ + accept() TaskResponse
1228
+ + decline() TaskResponse
1229
+ + hold() TaskResponse
1230
+ + resume() TaskResponse
1231
+ + holdResume() TaskResponse
1232
+ + pauseRecording() TaskResponse
1233
+ + resumeRecording(payload) TaskResponse
1234
+ + consult(payload) TaskResponse
1235
+ + endConsult(payload) TaskResponse
1236
+ + transfer(payload) TaskResponse
1237
+ + consultConference() TaskResponse
1238
+ + exitConference() TaskResponse
1239
+ + transferConference() TaskResponse
1240
+ }
1241
+
1242
+ class WebRTC {
1243
+ - localAudioStream: LocalMicrophoneStream
1244
+ - webCallingService: WebCallingService
1245
+ + accept() TaskResponse
1246
+ + decline() TaskResponse
1247
+ + toggleMute() void
1248
+ + unregisterWebCallListeners() void
1249
+ }
1250
+
1251
+ class Digital {
1252
+ + accept() TaskResponse
1253
+ + updateTaskData(newData, shouldOverwrite) IDigital
1254
+ }
1255
+
1256
+ class TaskFactory {
1257
+ + createTask(contact, webCallingService, data, configFlags, wrapupData, agentId, agentName) Task
1258
+ }
1259
+
1260
+ Task <|-- Voice
1261
+ Voice <|-- WebRTC
1262
+ Task <|-- Digital
1263
+
1264
+ TaskFactory ..> Task : creates
1265
+ TaskFactory ..> Voice : creates
1266
+ TaskFactory ..> WebRTC : creates
1267
+ TaskFactory ..> Digital : creates
1268
+ ```
1269
+
1270
+ ## Use Cases
1271
+ - **UC-1 Incoming task creation:** TaskManager maps a backend offer/reservation, TaskFactory creates a supported Voice/WebRTC/Digital task, and ContactCenter emits the typed incoming event. Evidence: `src/services/task/TaskManager.ts`, `src/services/task/TaskFactory.ts`, `test/unit/spec/services/task`.
1272
+ - **UC-2 Accept/hold/resume:** the Task or Voice method delegates the remote operation and sends the matching typed event to its actor; concrete hold/resume implementations are parameterless. Evidence: `src/services/task/Task.ts`, `src/services/task/voice/Voice.ts`, `test/unit/spec/services/task`.
1273
+ - **UC-3 Consult/transfer/conference:** Voice coordinates contact routing with initiating/stable actor states and emits the corresponding complete `TASK_EVENTS` contract. Evidence: `src/services/task/voice/Voice.ts`, `src/services/task/types.ts`, `test/unit/spec/services/task`.
1274
+ - **UC-4 Wrapup/end:** backend end/wrapup notifications drive WRAPPING_UP and final COMPLETED/TERMINATED outcomes without collapsing them into one result. Evidence: `src/services/task/Task.ts`, `src/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task`.
1275
+ - **UC-5 WebRTC and digital behavior:** TaskFactory selects channel-specific subclasses; unsupported SMS/Facebook/WhatsApp values throw `Unknown media type`. Evidence: `src/services/task/TaskFactory.ts`, `test/unit/spec/services/task/TaskFactory.ts`.
1276
+
1277
+ > **Host-application example:** This consumer callback performs task operations without logging raw interaction data.
1278
+
1279
+ ```typescript
1280
+ // Listen for incoming tasks
1281
+ cc.on('task:incoming', async (task) => {
1282
+ // Accept the task
1283
+ await task.accept();
1284
+
1285
+ // Task operations
1286
+ await task.hold();
1287
+ await task.resume();
1288
+ await task.end();
1289
+ await task.wrapup({
1290
+ wrapUpReason: 'Resolved',
1291
+ auxCodeId: 'wrapup-code',
1292
+ });
1293
+ });
1294
+ ```
1295
+
1296
+ ## State Model
1297
+ Each Task owns an XState actor and current task data. TaskManager maps backend notifications into actor events; backend task data remains authoritative for hydration. Stable interaction states and initiating/terminal states are defined by the nested task-state-machine module.
1298
+
1299
+ ## Business Rules & Invariants
1300
+ - TaskFactory creates only implemented media subclasses and throws for unsupported media values.
1301
+ - Concrete Task/Voice `hold()` and `resume()` methods are parameterless even though the broader `ITask` declaration retains an optional media-resource parameter.
1302
+ - Task event names come from `TASK_EVENTS`; actor transition names come from `TaskEvent`, and callers must not substitute raw strings.
1303
+ - Task owns no credentials or authentication policy; contact/dialer factories delegate authenticated requests through AqmReqs and Core/WebexRequest.
1304
+ - Preview skip/remove delegation is conditional: `campaignPreviewSkipDisabled === 'true'` or `campaignPreviewRemoveDisabled === 'true'` causes ContactCenter to throw before the dialer starts an HTTP or WebSocket-correlated AQM operation. Accept has no equivalent pre-guard.
1305
+
1306
+ ## Concurrency & Reactive Flow
1307
+ - Remote contact/dialer operations complete asynchronously through AQM correlation. Backend WebSocket notifications are separately mapped by TaskManager and delivered to the owning actor in arrival order.
1308
+
1309
+ ## State Machine
1310
+ ```mermaid
1311
+ stateDiagram-v2
1312
+ [*] --> IDLE
1313
+ IDLE --> OFFERED: task offer
1314
+ OFFERED --> CONNECTED: assignment / accept
1315
+ CONNECTED --> HELD: hold succeeds
1316
+ HELD --> CONNECTED: resume succeeds
1317
+ CONNECTED --> CONSULTING: consult succeeds
1318
+ HELD --> CONSULTING: consult succeeds
1319
+ CONSULTING --> CONFERENCING: conference succeeds
1320
+ CONNECTED --> WRAPPING_UP: wrapup required after end
1321
+ HELD --> WRAPPING_UP: wrapup required after end
1322
+ WRAPPING_UP --> COMPLETED: wrapup complete
1323
+ CONNECTED --> TERMINATED: end without wrapup
1324
+ COMPLETED --> [*]
1325
+ TERMINATED --> [*]
1326
+ ```
1327
+
1328
+ - **Factory**: `TaskFactory.ts` selects `WebRTC`, `Voice`, or `Digital` based on `MEDIA_CHANNEL` and `webCallingService.loginOption`.
1329
+
1330
+ - **Initialization**: `Task.ts` creates a state machine actor using `createTaskStateMachine(...)`, wires action overrides (emitters), and starts the actor.
1331
+
1332
+ - **Task State**: The task holds `stateMachineService` and uses it to send `TaskEvent` payloads.
1333
+
1334
+ Example (state machine init inside a task object):
1335
+
1336
+ ```typescript
1337
+ const machine = createTaskStateMachine(uiControlConfig, {
1338
+ actions: {
1339
+ emitTaskIncoming: ({event}) => task.emit('task:incoming', task),
1340
+ },
1341
+ });
1342
+ const actor = createActor(machine);
1343
+ actor.start();
1344
+ ```
1345
+
1346
+ `Task` delegates lifecycle transitions and control-state derivation to the state machine:
1347
+
1348
+ - Transition graph: `state-machine/TaskStateMachine.ts`
1349
+
1350
+ - Transition conditions: `state-machine/guards.ts`
1351
+
1352
+ - Context mutation and integration hooks: `state-machine/actions.ts`
1353
+
1354
+ - UI control derivation: `state-machine/uiControlsComputer.ts`
1355
+
1356
+ For state-machine-specific implementation guidance, use:
1357
+
1358
+ - `../state-machine/ai-docs/task-state-machine-spec.md`
1359
+
1360
+ - **Active lifecycle + intermediate states**:
1361
+
1362
+ - `IDLE`, `OFFERED`, `CONNECTED`
1363
+
1364
+ - `HOLD_INITIATING`, `HELD`, `RESUME_INITIATING`
1365
+
1366
+ - `CONSULT_INITIATING`, `CONSULTING`, `CONF_INITIATING`
1367
+
1368
+ - `CONFERENCING`, `WRAPPING_UP`, `COMPLETED`, `TERMINATED`
1369
+
1370
+ - **Future placeholders (defined, not currently implemented in transitions)**:
1371
+
1372
+ - `CONSULT_INITIATED`, `CONSULT_COMPLETED`, `POST_CALL`, `PARKED`, `MONITORING`
1373
+
1374
+ ### Signature and ownership clarifications
1375
+
1376
+ - `Task.endConsult(consultEndPayload)` requires a payload at the base class; `Voice.endConsult(consultEndPayload?)` accepts it optionally.
1377
+ - `ITask.hold(mediaResourceId?)` and `ITask.resume(mediaResourceId?)` are compatibility-interface shapes; current concrete Task/Voice methods accept no argument.
1378
+ - TaskFactory supports the media implementations present in its switch and throws for unsupported media values rather than silently constructing a generic task.
1379
+
1380
+ ## Protocol / Wire Format
1381
+ - Request, response, and event payload ownership is anchored in `src/services/task/Task.ts`. HTTP initiates backend work where applicable; WebSocket messages provide realtime events and, for AQM flows, correlated completion.
1382
+
1383
+ ## Error Handling & Failure Modes
1384
+ | Condition | Signal (error/code/result) | Caller recovery |
1385
+ |---|---|---|
1386
+ | Dependency rejection | Typed/rethrown error or failure event | Inspect structured details, preserve tracking id, and retry only when the operation is safe. |
1387
+ | Timeout or missing async completion | Timeout/recovery state | Follow the module-specific recovery path; never synthesize success. |
1388
+
1389
+ > **Host-application example:** `showTransferFailure` represents consumer-owned error presentation. SDK package implementation must use `LoggerProxy` and avoid logging raw runtime values.
1390
+
1391
+ ```typescript
1392
+ try {
1393
+ await task.transfer({
1394
+ to: 'queue-123',
1395
+ destinationType: 'queue',
1396
+ });
1397
+ } catch (error) {
1398
+ showTransferFailure(error);
1399
+ }
1400
+ ```
1401
+
1402
+ **Cause**: Agent not available or TaskManager not initialized
1403
+
1404
+ **Solution**:
1405
+
1406
+ 1. Ensure `cc.register()` completed
1407
+
1408
+ 2. Ensure `cc.stationLogin()` completed
1409
+
1410
+ 3. Ensure agent state is Available
1411
+
1412
+ **Cause**: Task state doesn't allow operation
1413
+
1414
+ **Solution**: Check task state before operation:
1415
+
1416
+ ```typescript
1417
+ if (task.uiControls.main.hold.isEnabled) {
1418
+ await task.hold();
1419
+ }
1420
+
1421
+ // During a consult, use the consult-leg controls instead.
1422
+ if (task.uiControls.consult.hold.isEnabled) {
1423
+ // Render or enable the consult-leg hold action.
1424
+ }
1425
+ ```
1426
+
1427
+ **Cause**: Call not mapped to task
1428
+
1429
+ **Solution**: Ensure BROWSER login and mercury connected:
1430
+
1431
+ ```typescript
1432
+ await webex.internal.mercury.connect();
1433
+ await cc.stationLogin({ loginOption: 'BROWSER', ... });
1434
+ ```
1435
+
1436
+ ## Pitfalls
1437
+ - Concrete `Task`/`Voice` hold and resume methods are parameterless even though the broader `ITask` declaration retains an optional compatibility argument.
1438
+ - AQM HTTP acknowledgement never completes a task operation; success/failure binds or timeout settle the promise and must stay aligned with actor events.
1439
+ - Primary and RTD WebSockets have different ownership: TaskManager uses the RTD stream for transcript/suggestion events and must not emit acknowledgement payloads as public suggestions.
1440
+
1441
+ ## Module Do's / Don'ts
1442
+ - DO send initiating and success/failure events to the task actor around remote Voice operations.
1443
+ - DO let TaskFactory select Voice/WebRTC/Digital from media type and login option.
1444
+ - DON'T create a generic task for unsupported media or treat `startOutdial()` as returning an ITask.
1445
+ - DON'T derive hold/resume completion from the HTTP response.
1446
+
1447
+ ## Key Design Trade-off
1448
+ - A shared Task base preserves a stable API while media-specific subclasses and a separate state engine enforce capability differences.
1449
+
1450
+ ## Test-Case Strategy (module)
1451
+ Use `test/unit/spec/services/task/Task.ts`, `TaskFactory.ts`, `TaskManager.ts`, media-specific suites, contact/dialer suites, and state-machine suites. Cover concrete-versus-interface method signatures, every TASK_EVENTS group, unsupported media rejection, primary/RTD event ownership, injected state actions, preview-campaign accept/skip/remove payloads and failure paths, the disabled skip/remove pre-guards, Participant Drop validation/correlation/privacy, primary-owner promotion propagation, and success/failure/timeout paths.
1452
+
1453
+ | Behavior / Requirement | Existing test evidence | Gap |
1454
+ |---|---|---|
1455
+ | `TASK-R-001` | `test/unit/spec/services/task/TaskFactory.ts` | None. |
1456
+ | `TASK-R-002` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/voice/Voice.ts` | None. |
1457
+ | `TASK-R-003` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | Keep event-catalog parity checks synchronized with `TASK_EVENTS`. |
1458
+ | `TASK-R-004` | `test/unit/spec/services/task/TaskManager.ts` | None. |
1459
+ | `TASK-R-005` | `test/unit/spec/services/task/TaskFactory.ts`, `test/unit/spec/services/task/dialer.ts` | None. |
1460
+ | `TASK-R-006` | `test/unit/spec/services/task/contact.ts`, `test/unit/spec/services/core/WebexRequest.ts` | Authentication ownership is verified across routing/Core boundaries. |
1461
+ | `TASK-R-007` | `test/unit/spec/cc.ts`, `test/unit/spec/services/task/dialer.ts` | Add direct tests proving disabled skip/remove flags throw before dialer invocation; keep public signatures, metrics/error handling, and AQM request contracts synchronized. |
1462
+ | `TASK-R-008` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/voice/Voice.ts`, `test/unit/spec/services/task/contact.ts`, `test/unit/spec/services/core/aqm-reqs.ts`, `test/unit/spec/services/task/TaskManager.ts` | None. |
1463
+ | `TASK-R-009` | `test/unit/spec/cc.ts`, `test/unit/spec/services/task/TaskFactory.ts`, `test/unit/spec/services/task/state-machine/uiControlsComputer.ts` | None. |
1464
+ | `TASK-R-010` | `test/unit/spec/services/task/TaskManager.ts`, `test/unit/spec/services/task/Task.ts` | None. |
1465
+ | `TASK-R-011` | `test/unit/spec/services/task/TaskManager.ts`, `test/unit/spec/services/task/voice/Voice.ts`, `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | None. |
1466
+
1467
+ ## Traceability
1468
+ - Repo architecture: `../../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../../ai-docs/SPEC_INDEX.md`
1469
+ - Coverage state and contracts baseline: `../../../../.sdd/manifest.json`
1470
+
1471
+ - Task creation: `TaskFactory.ts`
1472
+
1473
+ - Task APIs and behavior: `Task.ts`, `voice/Voice.ts`, `voice/WebRTC.ts`, `digital/Digital.ts`
1474
+
1475
+ - Task management: `TaskManager.ts`
1476
+
1477
+ - Shared task types: `types.ts`, `constants.ts`
1478
+
1479
+ - Task lifecycle state machine: `state-machine/TaskStateMachine.ts`
1480
+
1481
+ - State machine types/events: `state-machine/constants.ts`, `state-machine/types.ts`
1482
+
1483
+ - [TaskManager.ts](../TaskManager.ts) - Manager implementation
1484
+
1485
+ - [types.ts](../types.ts) - Type definitions
1486
+
1487
+ - [../state-machine/ai-docs/task-state-machine-spec.md](../state-machine/ai-docs/task-state-machine-spec.md) - State machine implementation guide
1488
+
1489
+ - [../state-machine/ai-docs/task-state-machine-spec.md](../state-machine/ai-docs/task-state-machine-spec.md) - State machine internals
1490
+
1491
+ - [cc.ts](../../../cc.ts) - Main plugin
1492
+
1493
+ - [TaskManager.ts](../TaskManager.ts) - Manager
1494
+
1495
+ - [contact.ts](../contact.ts) - Contact operations
1496
+
1497
+ - [types.ts](../types.ts) - Type definitions
1498
+
1499
+ - [../state-machine/ai-docs/task-state-machine-spec.md](../state-machine/ai-docs/task-state-machine-spec.md) - State machine guide
1500
+
1501
+ - [../state-machine/ai-docs/task-state-machine-spec.md](../state-machine/ai-docs/task-state-machine-spec.md) - State machine architecture