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

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