@webex/contact-center 3.12.0-next.14 → 3.12.0-next.140

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