@webex/contact-center 3.12.0-next.12 → 3.12.0-next.121

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