@webex/contact-center 3.12.0-llmrefactor.1 → 3.12.0-llmrefactor.10

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 (198) hide show
  1. package/.sdd/manifest.json +7 -5
  2. package/ai-docs/ARCHITECTURE.md +1 -1
  3. package/ai-docs/CONTRACTS.md +8 -3
  4. package/ai-docs/SECURITY.md +1 -1
  5. package/ai-docs/contact-center-spec.md +22 -4
  6. package/ai-docs/features/consult-transfer-list-policy/spec/feature-spec.md +362 -0
  7. package/dist/cc.js +544 -12
  8. package/dist/cc.js.map +1 -1
  9. package/dist/config.js +7 -0
  10. package/dist/config.js.map +1 -1
  11. package/dist/constants.js +10 -2
  12. package/dist/constants.js.map +1 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/metrics/MetricsManager.js +16 -0
  15. package/dist/metrics/MetricsManager.js.map +1 -1
  16. package/dist/metrics/behavioral-events.js +104 -0
  17. package/dist/metrics/behavioral-events.js.map +1 -1
  18. package/dist/metrics/constants.js +18 -0
  19. package/dist/metrics/constants.js.map +1 -1
  20. package/dist/services/AddressBook.js +18 -15
  21. package/dist/services/AddressBook.js.map +1 -1
  22. package/dist/services/AnswerCallOnWebexService.js +202 -0
  23. package/dist/services/AnswerCallOnWebexService.js.map +1 -0
  24. package/dist/services/EntryPoint.js +46 -68
  25. package/dist/services/EntryPoint.js.map +1 -1
  26. package/dist/services/Queue.js +27 -22
  27. package/dist/services/Queue.js.map +1 -1
  28. package/dist/services/WebexCrossClientService.js +222 -0
  29. package/dist/services/WebexCrossClientService.js.map +1 -0
  30. package/dist/services/WxAppTelephonyMercurySync.js +108 -0
  31. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -0
  32. package/dist/services/config/Util.js +3 -0
  33. package/dist/services/config/Util.js.map +1 -1
  34. package/dist/services/config/constants.js +10 -6
  35. package/dist/services/config/constants.js.map +1 -1
  36. package/dist/services/config/types.js +4 -0
  37. package/dist/services/config/types.js.map +1 -1
  38. package/dist/services/core/Err.js.map +1 -1
  39. package/dist/services/core/WebexRequest.js +6 -2
  40. package/dist/services/core/WebexRequest.js.map +1 -1
  41. package/dist/services/core/aqm-reqs.js +28 -14
  42. package/dist/services/core/aqm-reqs.js.map +1 -1
  43. package/dist/services/core/types.js.map +1 -1
  44. package/dist/services/task/Task.js +73 -7
  45. package/dist/services/task/Task.js.map +1 -1
  46. package/dist/services/task/TaskFactory.js +8 -4
  47. package/dist/services/task/TaskFactory.js.map +1 -1
  48. package/dist/services/task/TaskManager.js +490 -21
  49. package/dist/services/task/TaskManager.js.map +1 -1
  50. package/dist/services/task/WebexCallingUtils.js +70 -0
  51. package/dist/services/task/WebexCallingUtils.js.map +1 -0
  52. package/dist/services/task/constants.js +4 -1
  53. package/dist/services/task/constants.js.map +1 -1
  54. package/dist/services/task/contact.js +29 -0
  55. package/dist/services/task/contact.js.map +1 -1
  56. package/dist/services/task/digital/Digital.js +3 -2
  57. package/dist/services/task/digital/Digital.js.map +1 -1
  58. package/dist/services/task/state-machine/TaskStateMachine.js +162 -53
  59. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  60. package/dist/services/task/state-machine/actions.js +6 -1
  61. package/dist/services/task/state-machine/actions.js.map +1 -1
  62. package/dist/services/task/state-machine/guards.js +7 -10
  63. package/dist/services/task/state-machine/guards.js.map +1 -1
  64. package/dist/services/task/state-machine/types.js.map +1 -1
  65. package/dist/services/task/state-machine/uiControlsComputer.js +118 -18
  66. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -1
  67. package/dist/services/task/types.js +16 -1
  68. package/dist/services/task/types.js.map +1 -1
  69. package/dist/services/task/voice/Voice.js +456 -15
  70. package/dist/services/task/voice/Voice.js.map +1 -1
  71. package/dist/services/task/voice/WebRTC.js +4 -1
  72. package/dist/services/task/voice/WebRTC.js.map +1 -1
  73. package/dist/services/task/voice/wxAppVoiceMethods.js +354 -0
  74. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -0
  75. package/dist/services/wxAppDiagnosticLogging.js +122 -0
  76. package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
  77. package/dist/services/wxAppTelephonyUtils.js +19 -0
  78. package/dist/services/wxAppTelephonyUtils.js.map +1 -0
  79. package/dist/types/cc.d.ts +78 -4
  80. package/dist/types/config.d.ts +7 -0
  81. package/dist/types/constants.d.ts +8 -0
  82. package/dist/types/index.d.ts +3 -1
  83. package/dist/types/metrics/MetricsManager.d.ts +6 -0
  84. package/dist/types/metrics/constants.d.ts +17 -0
  85. package/dist/types/services/AddressBook.d.ts +2 -1
  86. package/dist/types/services/AnswerCallOnWebexService.d.ts +38 -0
  87. package/dist/types/services/EntryPoint.d.ts +7 -5
  88. package/dist/types/services/Queue.d.ts +5 -3
  89. package/dist/types/services/WebexCrossClientService.d.ts +32 -0
  90. package/dist/types/services/WxAppTelephonyMercurySync.d.ts +28 -0
  91. package/dist/types/services/config/constants.d.ts +8 -5
  92. package/dist/types/services/config/types.d.ts +17 -4
  93. package/dist/types/services/core/Err.d.ts +2 -0
  94. package/dist/types/services/core/WebexRequest.d.ts +1 -0
  95. package/dist/types/services/core/types.d.ts +2 -0
  96. package/dist/types/services/task/Task.d.ts +20 -2
  97. package/dist/types/services/task/TaskFactory.d.ts +2 -1
  98. package/dist/types/services/task/WebexCallingUtils.d.ts +11 -0
  99. package/dist/types/services/task/constants.d.ts +3 -0
  100. package/dist/types/services/task/contact.d.ts +4 -0
  101. package/dist/types/services/task/digital/Digital.d.ts +2 -2
  102. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +304 -56
  103. package/dist/types/services/task/state-machine/guards.d.ts +6 -5
  104. package/dist/types/services/task/state-machine/types.d.ts +9 -1
  105. package/dist/types/services/task/types.d.ts +81 -4
  106. package/dist/types/services/task/voice/Voice.d.ts +52 -9
  107. package/dist/types/services/task/voice/WebRTC.d.ts +2 -2
  108. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +55 -0
  109. package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
  110. package/dist/types/services/wxAppTelephonyUtils.d.ts +5 -0
  111. package/dist/types/types.d.ts +14 -8
  112. package/dist/types/utils/PageCache.d.ts +20 -3
  113. package/dist/types.js +7 -2
  114. package/dist/types.js.map +1 -1
  115. package/dist/utils/PageCache.js +19 -5
  116. package/dist/utils/PageCache.js.map +1 -1
  117. package/dist/webex.js +1 -1
  118. package/package.json +8 -8
  119. package/src/cc.ts +657 -12
  120. package/src/config.ts +7 -0
  121. package/src/constants.ts +8 -0
  122. package/src/index.ts +2 -0
  123. package/src/metrics/MetricsManager.ts +16 -0
  124. package/src/metrics/ai-docs/metrics-spec.md +78 -10
  125. package/src/metrics/behavioral-events.ts +106 -0
  126. package/src/metrics/constants.ts +20 -0
  127. package/src/services/AddressBook.ts +17 -6
  128. package/src/services/AnswerCallOnWebexService.ts +239 -0
  129. package/src/services/EntryPoint.ts +59 -60
  130. package/src/services/Queue.ts +29 -12
  131. package/src/services/WebexCrossClientService.ts +293 -0
  132. package/src/services/WxAppTelephonyMercurySync.ts +142 -0
  133. package/src/services/agent/ai-docs/agent-spec.md +1 -0
  134. package/src/services/ai-docs/AGENTS.md +10 -10
  135. package/src/services/ai-docs/services-spec.md +6 -1
  136. package/src/services/config/Util.ts +3 -0
  137. package/src/services/config/ai-docs/AGENTS.md +1 -1
  138. package/src/services/config/ai-docs/ARCHITECTURE.md +2 -2
  139. package/src/services/config/ai-docs/config-spec.md +6 -0
  140. package/src/services/config/constants.ts +10 -6
  141. package/src/services/config/types.ts +16 -4
  142. package/src/services/core/Err.ts +1 -0
  143. package/src/services/core/WebexRequest.ts +3 -1
  144. package/src/services/core/ai-docs/core-spec.md +5 -1
  145. package/src/services/core/aqm-reqs.ts +30 -15
  146. package/src/services/core/types.ts +2 -0
  147. package/src/services/task/Task.ts +79 -8
  148. package/src/services/task/TaskFactory.ts +8 -3
  149. package/src/services/task/TaskManager.ts +716 -17
  150. package/src/services/task/WebexCallingUtils.ts +136 -0
  151. package/src/services/task/ai-docs/ARCHITECTURE.md +1 -0
  152. package/src/services/task/ai-docs/task-spec.md +156 -3
  153. package/src/services/task/constants.ts +3 -0
  154. package/src/services/task/contact.ts +30 -0
  155. package/src/services/task/digital/Digital.ts +4 -2
  156. package/src/services/task/state-machine/TaskStateMachine.ts +235 -81
  157. package/src/services/task/state-machine/actions.ts +5 -2
  158. package/src/services/task/state-machine/ai-docs/AGENTS.md +6 -4
  159. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +35 -16
  160. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +103 -33
  161. package/src/services/task/state-machine/guards.ts +7 -10
  162. package/src/services/task/state-machine/types.ts +16 -1
  163. package/src/services/task/state-machine/uiControlsComputer.ts +206 -32
  164. package/src/services/task/types.ts +114 -4
  165. package/src/services/task/voice/Voice.ts +595 -14
  166. package/src/services/task/voice/WebRTC.ts +5 -1
  167. package/src/services/task/voice/wxAppVoiceMethods.ts +516 -0
  168. package/src/services/wxAppDiagnosticLogging.ts +174 -0
  169. package/src/services/wxAppTelephonyUtils.ts +14 -0
  170. package/src/types.ts +33 -9
  171. package/src/utils/AGENTS.md +21 -10
  172. package/src/utils/PageCache.ts +38 -5
  173. package/src/utils/ai-docs/utils-spec.md +21 -11
  174. package/test/unit/spec/cc.ts +1522 -19
  175. package/test/unit/spec/metrics/MetricsManager.ts +25 -0
  176. package/test/unit/spec/metrics/behavioral-events.ts +60 -0
  177. package/test/unit/spec/services/AddressBook.ts +37 -6
  178. package/test/unit/spec/services/AnswerCallOnWebexService.ts +264 -0
  179. package/test/unit/spec/services/EntryPoint.ts +87 -40
  180. package/test/unit/spec/services/Queue.ts +123 -12
  181. package/test/unit/spec/services/WebexCrossClientService.ts +467 -0
  182. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +136 -0
  183. package/test/unit/spec/services/config/Util.ts +85 -0
  184. package/test/unit/spec/services/core/WebexRequest.ts +3 -1
  185. package/test/unit/spec/services/core/aqm-reqs.ts +113 -1
  186. package/test/unit/spec/services/task/Task.ts +274 -6
  187. package/test/unit/spec/services/task/TaskFactory.ts +39 -2
  188. package/test/unit/spec/services/task/TaskManager.ts +1717 -9
  189. package/test/unit/spec/services/task/WebexCallingUtils.ts +153 -0
  190. package/test/unit/spec/services/task/contact.ts +33 -0
  191. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1485 -109
  192. package/test/unit/spec/services/task/state-machine/guards.ts +210 -8
  193. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +445 -7
  194. package/test/unit/spec/services/task/voice/Voice.ts +1412 -0
  195. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +603 -0
  196. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
  197. package/umd/contact-center.min.js +2 -2
  198. package/umd/contact-center.min.js.map +1 -1
@@ -14,12 +14,17 @@ var _types3 = require("../../types");
14
14
  var _loggerProxy = _interopRequireDefault(require("../../logger-proxy"));
15
15
  var _TaskUtils = require("./TaskUtils");
16
16
  var _TaskFactory = _interopRequireDefault(require("./TaskFactory"));
17
+ var _WebexCallingUtils = require("./WebexCallingUtils");
17
18
  var _WebRTC = _interopRequireDefault(require("./voice/WebRTC"));
18
19
  var _stateMachine = require("./state-machine");
20
+ var _constants3 = require("./state-machine/constants");
19
21
  var _taskDataNormalizer = require("./taskDataNormalizer");
20
22
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
21
23
  const CC_EVENT_SET = new Set(Object.values(_types2.CC_EVENTS));
24
+ const MAIN_INTERACTION_CORRELATED_EVENTS = new Set([_types2.CC_EVENTS.AGENT_CONSULT_ENDED, _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE]);
22
25
  const isCcEvent = value => CC_EVENT_SET.has(value);
26
+ const WRAP_UP_STAMP_EVENTS = new Set([_types2.CC_EVENTS.AGENT_WRAPUP, _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED, _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE, _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED]);
27
+ const PARTICIPANT_LEAVE_WRAP_STATES = new Set([_stateMachine.TaskState.HELD, _stateMachine.TaskState.RESUME_INITIATING, _stateMachine.TaskState.CONSULTING, _stateMachine.TaskState.CONSULT_INITIATING, _stateMachine.TaskState.CONFERENCING]);
23
28
 
24
29
  /** @internal */
25
30
  class TaskManager extends _events.default {
@@ -109,6 +114,9 @@ class TaskManager extends _events.default {
109
114
  setWebRtcEnabled(webRtcEnabled) {
110
115
  this.webRtcEnabled = webRtcEnabled;
111
116
  }
117
+ setAnswerCallOnWebexService(answerCallOnWebexService) {
118
+ this.answerCallOnWebexService = answerCallOnWebexService;
119
+ }
112
120
  handleIncomingWebCall = call => {
113
121
  const currentTask = Object.values(this.taskCollection).find(task => task.data.interaction.mediaType === 'telephony' && !(0, _TaskUtils.isCampaignPreviewReservation)(task));
114
122
  if (currentTask) {
@@ -134,14 +142,147 @@ class TaskManager extends _events.default {
134
142
  this.webCallingService.off(_calling.LINE_EVENTS.INCOMING_CALL, this.handleIncomingWebCall);
135
143
  }
136
144
 
145
+ /**
146
+ * True when this agent left on the current payload.
147
+ * A named other participantId is never self-leave (even if the participants
148
+ * map is partial). Missing-map fallback is EP-DN only: participantId absent
149
+ * and self omitted now, with previous-task evidence when a prior map exists.
150
+ */
151
+ static didSelfLeaveConference(payload, agentId, task) {
152
+ if (!agentId) {
153
+ return false;
154
+ }
155
+ if (payload.participantId === agentId) {
156
+ return true;
157
+ }
158
+ if (payload.participantId && payload.participantId !== agentId) {
159
+ return false;
160
+ }
161
+ const participants = payload.interaction?.participants;
162
+ if (!participants || agentId in participants) {
163
+ return false;
164
+ }
165
+ const previousParticipants = task?.data?.interaction?.participants;
166
+ if (previousParticipants && !(agentId in previousParticipants)) {
167
+ return false;
168
+ }
169
+ return true;
170
+ }
171
+
172
+ /**
173
+ * Consulted non-owner: Voice exitConference / shouldWrapUpForThisAgent stays
174
+ * false unless an explicit wrap-up signal includes this agent.
175
+ */
176
+ static isConsultedNonOwner(payload, agentId, task) {
177
+ const isConsulted = payload.isConsulted ?? task?.data?.isConsulted;
178
+ if (isConsulted !== true) {
179
+ return false;
180
+ }
181
+ const owner = payload.interaction?.owner ?? task?.data?.interaction?.owner;
182
+ return Boolean(agentId && owner && owner !== agentId);
183
+ }
184
+ static hasNonemptyPendingWrapUp(payload) {
185
+ return Array.isArray(payload.agentsPendingWrapUp) && payload.agentsPendingWrapUp.length > 0;
186
+ }
187
+
188
+ /**
189
+ * Transfer initiator fallback: widgets set transferConferenceRequested on the
190
+ * actor; desktop success payloads may include consultingAgentId. Only used
191
+ * when agentsPendingWrapUp is empty or absent.
192
+ */
193
+ static isSelfConferenceTransferInitiator(payload, agentId, task) {
194
+ const snapshotContext = task?.stateMachineService?.getSnapshot?.()?.context;
195
+ if (snapshotContext?.transferConferenceRequested === true) {
196
+ return true;
197
+ }
198
+ return Boolean(agentId && payload.consultingAgentId === agentId);
199
+ }
200
+
201
+ /**
202
+ * Explicit wrap-up signals only (no owner / isConsulted fallback).
203
+ * Used for events that remaining conference agents also receive.
204
+ * nonempty agentsPendingWrapUp is authoritative, then wrapUpRequired / isWrapUp.
205
+ */
206
+ static wrapUpRequiredFromExplicitSignals(payload, agentId) {
207
+ const pending = payload.agentsPendingWrapUp;
208
+ if (Array.isArray(pending) && pending.length > 0) {
209
+ return Boolean(agentId && pending.includes(agentId));
210
+ }
211
+ if (payload.wrapUpRequired === true) {
212
+ return true;
213
+ }
214
+ if (!agentId) {
215
+ return false;
216
+ }
217
+ return payload.interaction?.participants?.[agentId]?.isWrapUp === true;
218
+ }
219
+
220
+ /**
221
+ * Stamp wrapUpRequired once at ingress (prepareEventContext) so updateTaskData
222
+ * and the mapper see the same flag. Remaining agents stay false.
223
+ */
224
+ static stampWrapUpRequiredForEvent(eventType, payload, agentId, task) {
225
+ if (!WRAP_UP_STAMP_EVENTS.has(eventType)) {
226
+ return payload;
227
+ }
228
+ if (eventType === _types2.CC_EVENTS.AGENT_WRAPUP) {
229
+ return {
230
+ ...payload,
231
+ wrapUpRequired: true
232
+ };
233
+ }
234
+ if (eventType === _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED) {
235
+ return {
236
+ ...payload,
237
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId)
238
+ };
239
+ }
240
+ if (eventType === _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE) {
241
+ const actorState = task?.stateMachineService?.getSnapshot?.()?.value;
242
+ if (actorState && !PARTICIPANT_LEAVE_WRAP_STATES.has(actorState)) {
243
+ return payload;
244
+ }
245
+ const left = TaskManager.didSelfLeaveConference(payload, agentId, task);
246
+ if (!left) {
247
+ return {
248
+ ...payload,
249
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId)
250
+ };
251
+ }
252
+ if (TaskManager.hasNonemptyPendingWrapUp(payload)) {
253
+ return {
254
+ ...payload,
255
+ wrapUpRequired: Boolean(agentId && payload.agentsPendingWrapUp?.includes(agentId))
256
+ };
257
+ }
258
+ return {
259
+ ...payload,
260
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId) || !TaskManager.isConsultedNonOwner(payload, agentId, task)
261
+ };
262
+ }
263
+
264
+ // AGENT_CONFERENCE_TRANSFERRED — pending list wins; initiator only if empty/absent
265
+ if (TaskManager.hasNonemptyPendingWrapUp(payload)) {
266
+ return {
267
+ ...payload,
268
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId)
269
+ };
270
+ }
271
+ return {
272
+ ...payload,
273
+ wrapUpRequired: TaskManager.wrapUpRequiredFromExplicitSignals(payload, agentId) || TaskManager.isSelfConferenceTransferInitiator(payload, agentId, task)
274
+ };
275
+ }
276
+
137
277
  /**
138
278
  * Map WebSocket CC_EVENTS to state machine TaskEvent
139
279
  * @param ccEvent - The CC_EVENT type from WebSocket
140
280
  * @param payload - The event payload
141
281
  * @param agentId - Optional agent ID for state detection (needed for HYDRATE)
282
+ * @param task - Optional task instance for state-aware CONTACT_ENDED mapping
142
283
  * @returns TaskEventPayload for state machine or null if no mapping
143
284
  */
144
- static mapEventToTaskStateMachineEvent(ccEvent, payload, agentId) {
285
+ static mapEventToTaskStateMachineEvent(ccEvent, payload, agentId, task, enableWxBetterTogether) {
145
286
  const mediaResourceId = payload.mediaResourceId || payload.interaction?.media?.[payload.interactionId]?.mediaResourceId;
146
287
  switch (ccEvent) {
147
288
  // CC -> TaskEvent mappings (see TaskStateMachine comment for quick reference)
@@ -180,6 +321,12 @@ class TaskManager extends _events.default {
180
321
  agentId
181
322
  };
182
323
  case _types2.CC_EVENTS.CONTACT_UPDATED:
324
+ if (task && typeof payload.interaction?.owner === 'string' && payload.interaction.owner.trim().length > 0 && payload.interaction.owner !== task.data?.interaction?.owner) {
325
+ return {
326
+ type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
327
+ taskData: payload
328
+ };
329
+ }
183
330
  return {
184
331
  type: _stateMachine.TaskEvent.CONTACT_UPDATED,
185
332
  taskData: payload
@@ -284,13 +431,31 @@ class TaskManager extends _events.default {
284
431
  taskData: payload
285
432
  };
286
433
  case _types2.CC_EVENTS.CONTACT_ENDED:
287
- return {
288
- type: _stateMachine.TaskEvent.CONTACT_ENDED,
289
- taskData: {
290
- ...payload,
291
- wrapUpRequired: (0, _TaskUtils.isCampaignPreviewTask)(payload) ? false : payload.agentsPendingWrapUp?.includes(agentId || '') || false
434
+ {
435
+ const wrapUpRequired = (0, _TaskUtils.isCampaignPreviewTask)(payload) ? false : payload.agentsPendingWrapUp?.includes(agentId || '') || false;
436
+
437
+ // Post-accept wxApp outdial agent-end delivers ContactEnded instead of
438
+ // AgentOutboundFailed. Normalize to OUTBOUND_FAILED so state machine
439
+ // enters WRAPPING_UP and emits TASK_OUTDIAL_FAILED (AGENT_ENDS).
440
+ // Pre-accept cancelTask decline stays CONTACT_ENDED → TERMINATED (no wrapup).
441
+ if (TaskManager.isAgentTerminatedOutdialWrapup(payload, wrapUpRequired, task, agentId, enableWxBetterTogether)) {
442
+ return {
443
+ type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
444
+ reason: 'AGENT_ENDS',
445
+ taskData: {
446
+ ...payload,
447
+ wrapUpRequired: true
448
+ }
449
+ };
292
450
  }
293
- };
451
+ return {
452
+ type: _stateMachine.TaskEvent.CONTACT_ENDED,
453
+ taskData: {
454
+ ...payload,
455
+ wrapUpRequired
456
+ }
457
+ };
458
+ }
294
459
  case _types2.CC_EVENTS.AGENT_INVITE_FAILED:
295
460
  return {
296
461
  type: _stateMachine.TaskEvent.INVITE_FAILED,
@@ -308,11 +473,18 @@ class TaskManager extends _events.default {
308
473
  reason: payload.reason
309
474
  };
310
475
  case _types2.CC_EVENTS.AGENT_OUTBOUND_FAILED:
311
- return {
312
- type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
313
- taskData: payload,
314
- reason: payload.reason
315
- };
476
+ {
477
+ const isAgentTerminatedOutdial = payload.interaction?.outboundType === 'OUTDIAL' && (payload.terminatingParty === 'Agent' || payload.reason === 'AGENT_ENDS');
478
+ const suppressOutdialFailedPopup = isAgentTerminatedOutdial && !TaskManager.isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether);
479
+ return {
480
+ type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
481
+ taskData: suppressOutdialFailedPopup ? {
482
+ ...payload,
483
+ suppressOutdialFailedPopup: true
484
+ } : payload,
485
+ reason: payload.reason
486
+ };
487
+ }
316
488
  case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
317
489
  return {
318
490
  type: _stateMachine.TaskEvent.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
@@ -385,6 +557,43 @@ class TaskManager extends _events.default {
385
557
  }
386
558
  }
387
559
 
560
+ /**
561
+ * True when the current agent's task is a wxApp-managed outdial (thick-client telephony).
562
+ */
563
+ static isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether) {
564
+ const wxAppEnabled = task?.uiControlConfig?.enableWxBetterTogether ?? enableWxBetterTogether ?? false;
565
+ if (!wxAppEnabled) {
566
+ return false;
567
+ }
568
+ const effectiveAgentId = agentId ?? task?.data?.agentId;
569
+ const participants = payload.interaction?.participants ?? task?.data?.interaction?.participants;
570
+ const details = (0, _WebexCallingUtils.getWebexCallingDeviceDetailsForAgent)(effectiveAgentId, participants);
571
+ return details?.deviceType === 'wxApp';
572
+ }
573
+
574
+ /**
575
+ * True when ContactEnded represents an agent-terminated wxApp outdial that should
576
+ * enter wrapup with an outdial-failed popup (legacy AgentOutboundFailed parity).
577
+ */
578
+ static isAgentTerminatedOutdialWrapup(payload, wrapUpRequired, task, agentId, enableWxBetterTogether) {
579
+ const isOutdial = payload.interaction?.outboundType === 'OUTDIAL';
580
+ const agentTerminated = payload.terminatingParty === 'Agent';
581
+ if (!isOutdial || !agentTerminated) {
582
+ return false;
583
+ }
584
+ if (!TaskManager.isWxAppManagedOutdialTask(agentId, payload, task, enableWxBetterTogether)) {
585
+ return false;
586
+ }
587
+ const wxAppAnswerPending = task?.uiControlConfig?.wxAppAnswerPending ?? false;
588
+
589
+ // Pre-accept decline: decline() clears wxAppAnswerPending; backend may still send state wrapUp.
590
+ if (!wxAppAnswerPending && !wrapUpRequired) {
591
+ return false;
592
+ }
593
+ const interactionWrapUp = payload.interaction?.state === 'wrapUp';
594
+ return wrapUpRequired || interactionWrapUp || wxAppAnswerPending;
595
+ }
596
+
388
597
  /**
389
598
  * Register WebSocket message listeners for task events
390
599
  *
@@ -493,6 +702,12 @@ class TaskManager extends _events.default {
493
702
  this.taskCollection[interactionId] = task;
494
703
  }
495
704
  }
705
+ if (!task && eventType === _types2.CC_EVENTS.CONTACT_OWNER_CHANGED) {
706
+ task = this.findTaskForContactOwnerChange(message.data);
707
+ }
708
+ if (!task && MAIN_INTERACTION_CORRELATED_EVENTS.has(eventType)) {
709
+ task = this.findUniqueTaskByRelatedInteraction(message.data);
710
+ }
496
711
  const wasConsultedTask = Boolean(task?.data?.isConsulted);
497
712
  const computeWrapUpRequired = () => {
498
713
  if (message.data.wrapUpRequired !== undefined) {
@@ -503,11 +718,20 @@ class TaskManager extends _events.default {
503
718
  }
504
719
  return !wasConsultedTask;
505
720
  };
506
- const adjustedPayload = eventType === _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED ? {
721
+ let adjustedPayload = eventType === _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED ? {
507
722
  ...message.data,
508
723
  wrapUpRequired: computeWrapUpRequired()
509
724
  } : message.data;
510
- const stateMachineEvent = TaskManager.mapEventToTaskStateMachineEvent(eventType, adjustedPayload, this.agentId);
725
+ if (task && eventType === _types2.CC_EVENTS.CONTACT_OWNER_CHANGED) {
726
+ adjustedPayload = TaskManager.preserveMainTaskIdentity(task, adjustedPayload);
727
+ }
728
+ if (task && eventType === _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE) {
729
+ adjustedPayload = TaskManager.preserveConfirmedOwner(task, adjustedPayload);
730
+ }
731
+ if (WRAP_UP_STAMP_EVENTS.has(eventType)) {
732
+ adjustedPayload = TaskManager.stampWrapUpRequiredForEvent(eventType, adjustedPayload, this.agentId, task);
733
+ }
734
+ const stateMachineEvent = TaskManager.mapEventToTaskStateMachineEvent(eventType, adjustedPayload, this.agentId, task, this.configFlags?.enableWxBetterTogether);
511
735
  _loggerProxy.default.info(`Handling task event ${eventType}`, {
512
736
  module: _constants.TASK_MANAGER_FILE,
513
737
  method: 'prepareEventContext',
@@ -521,6 +745,134 @@ class TaskManager extends _events.default {
521
745
  };
522
746
  }
523
747
 
748
+ /**
749
+ * ContactOwnerChanged can identify the promoted agent's child interaction while
750
+ * the surviving task is stored under the main interaction. Accept one related
751
+ * task when its existing snapshot or the authoritative incoming promotion proves
752
+ * current-agent main-call membership, while excluding non-promoted and ambiguous matches.
753
+ */
754
+ findTaskForContactOwnerChange(payload) {
755
+ const relatedCandidates = this.findRelatedTasks(payload, {
756
+ includeCollectionKeys: true,
757
+ includeMainCallMediaKeys: true
758
+ });
759
+ const incomingShowsCurrentAgentPromotion = this.isCurrentAgentPromotedOnIncomingMainCall(payload);
760
+ const eligibleCandidates = relatedCandidates.filter(candidate => this.isCurrentAgentActiveOnMainInteraction(candidate.data) || incomingShowsCurrentAgentPromotion);
761
+ if (eligibleCandidates.length === 1) {
762
+ return eligibleCandidates[0];
763
+ }
764
+ if (eligibleCandidates.length > 1) {
765
+ _loggerProxy.default.warn('Unable to correlate task event to a unique main interaction task', {
766
+ module: _constants.TASK_MANAGER_FILE,
767
+ method: 'findTaskForContactOwnerChange',
768
+ interactionId: payload.interaction?.mainInteractionId || payload.interaction?.parentInteractionId || payload.interaction?.callProcessingDetails?.parentInteractionId || payload.interactionId
769
+ });
770
+ return undefined;
771
+ }
772
+ return undefined;
773
+ }
774
+ isCurrentAgentActiveOnMainInteraction(taskData) {
775
+ const currentAgentId = this.agentId || taskData?.agentId;
776
+ if (!currentAgentId) return false;
777
+ return TaskManager.isParticipantActiveOnMainInteraction(taskData, currentAgentId);
778
+ }
779
+ static isParticipantActiveOnMainInteraction(taskData, participantId) {
780
+ const interaction = taskData?.interaction;
781
+ const participant = interaction?.participants?.[participantId];
782
+ if (!participant || participant.hasLeft === true) return false;
783
+ return Object.values(interaction?.media ?? {}).some(media => media?.mType === _constants3.MEDIA_TYPE_MAIN_CALL && media.participants?.includes(participantId));
784
+ }
785
+ isCurrentAgentPromotedOnIncomingMainCall(payload) {
786
+ return Boolean(this.agentId && payload.interaction?.owner === this.agentId && TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId));
787
+ }
788
+ static getMainCallMediaId(interaction) {
789
+ return Object.entries(interaction?.media ?? {}).find(([, media]) => media?.mType === _constants3.MEDIA_TYPE_MAIN_CALL)?.[0];
790
+ }
791
+ static getStableMainInteractionId(payload) {
792
+ return payload.interaction?.mainInteractionId || TaskManager.getMainCallMediaId(payload.interaction) || payload.interaction?.interactionId || payload.interactionId;
793
+ }
794
+ canRecoverTaskFromContactOwnerChanged(payload) {
795
+ const interaction = payload.interaction;
796
+ const stableInteractionId = TaskManager.getStableMainInteractionId(payload);
797
+ return Boolean(this.agentId && stableInteractionId && interaction?.mediaType === _types.MEDIA_CHANNEL.TELEPHONY && interaction.isTerminated !== true && interaction.owner === this.agentId && TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId));
798
+ }
799
+
800
+ /** Keep a related owner-change event from re-keying the surviving main task to a child ID. */
801
+ static preserveMainTaskIdentity(task, payload) {
802
+ const currentMainInteractionId = task.data?.interaction?.mainInteractionId;
803
+ const payloadMainInteractionId = payload.interaction?.mainInteractionId;
804
+ const currentMainMediaId = TaskManager.getMainCallMediaId(task.data?.interaction);
805
+ const payloadMainMediaId = TaskManager.getMainCallMediaId(payload.interaction);
806
+ const stableInteractionId = currentMainInteractionId || payloadMainInteractionId || currentMainMediaId || payloadMainMediaId || payload.interaction?.interactionId || task.data?.interaction?.interactionId || task.data?.interactionId;
807
+ if (!stableInteractionId || stableInteractionId === payload.interactionId) {
808
+ return payload;
809
+ }
810
+ return {
811
+ ...payload,
812
+ interactionId: stableInteractionId
813
+ };
814
+ }
815
+
816
+ /**
817
+ * ParticipantLeftConference may arrive after an owner update while still carrying
818
+ * the departed owner. Keep the confirmed owner only when the new roster proves that
819
+ * owner is active on mainCall and identifies the incoming owner as departed.
820
+ */
821
+ static preserveConfirmedOwner(task, payload) {
822
+ const confirmedOwner = task.data?.interaction?.owner;
823
+ const incomingOwner = payload.interaction?.owner;
824
+ if (!confirmedOwner || !incomingOwner || confirmedOwner === incomingOwner) {
825
+ return payload;
826
+ }
827
+ const confirmedOwnerIsActive = TaskManager.isParticipantActiveOnMainInteraction(payload, confirmedOwner);
828
+ const incomingOwnerIsExplicitlyDeparted = payload.participantId === incomingOwner || payload.interaction?.participants?.[incomingOwner]?.hasLeft === true;
829
+ if (!confirmedOwnerIsActive || !incomingOwnerIsExplicitlyDeparted) {
830
+ return payload;
831
+ }
832
+ return {
833
+ ...payload,
834
+ owner: confirmedOwner,
835
+ interaction: {
836
+ ...payload.interaction,
837
+ owner: confirmedOwner
838
+ }
839
+ };
840
+ }
841
+
842
+ /**
843
+ * Resolve lifecycle events that are emitted for the main interaction while the
844
+ * local task can still be indexed by a child consult interaction. Exact task
845
+ * keys are handled before this fallback. Multiple aliases of the same task are
846
+ * treated as one candidate; genuinely ambiguous matches are intentionally ignored.
847
+ */
848
+ findUniqueTaskByRelatedInteraction(payload, candidateFilter = () => true) {
849
+ const candidates = this.findRelatedTasks(payload).filter(candidateFilter);
850
+ if (candidates.length === 1) {
851
+ return candidates[0];
852
+ }
853
+ if (candidates.length > 1) {
854
+ _loggerProxy.default.warn('Unable to correlate task event to a unique main interaction task', {
855
+ module: _constants.TASK_MANAGER_FILE,
856
+ method: 'findUniqueTaskByRelatedInteraction',
857
+ interactionId: payload.interaction?.mainInteractionId || payload.interaction?.parentInteractionId || payload.interaction?.callProcessingDetails?.parentInteractionId || payload.interactionId
858
+ });
859
+ }
860
+ return undefined;
861
+ }
862
+ findRelatedTasks(payload, options = {}) {
863
+ const {
864
+ includeCollectionKeys = false,
865
+ includeMainCallMediaKeys = false
866
+ } = options;
867
+ const payloadInteractionIds = new Set([payload.interactionId, payload.interaction?.interactionId, payload.interaction?.mainInteractionId, payload.interaction?.parentInteractionId, payload.interaction?.callProcessingDetails?.parentInteractionId, ...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(payload.interaction)] : [])].filter(interactionId => Boolean(interactionId)));
868
+ if (payloadInteractionIds.size === 0) return [];
869
+ return [...new Set(Object.entries(this.taskCollection).filter(([taskId, candidate]) => {
870
+ const taskInteraction = candidate?.data?.interaction;
871
+ const candidateInteractionIds = [...(includeCollectionKeys ? [taskId] : []), candidate?.data?.interactionId, taskInteraction?.interactionId, taskInteraction?.mainInteractionId, taskInteraction?.parentInteractionId, taskInteraction?.callProcessingDetails?.parentInteractionId, ...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(taskInteraction)] : [])];
872
+ return candidateInteractionIds.some(interactionId => Boolean(interactionId) && payloadInteractionIds.has(interactionId));
873
+ }).map(([, candidate]) => candidate))];
874
+ }
875
+
524
876
  /**
525
877
  * Handle task lifecycle events and determine required actions
526
878
  *
@@ -544,6 +896,8 @@ class TaskManager extends _events.default {
544
896
  return this.handleAgentContact(context);
545
897
  case _types2.CC_EVENTS.CONTACT_MERGED:
546
898
  return this.handleContactMergedEvent(context);
899
+ case _types2.CC_EVENTS.CONTACT_OWNER_CHANGED:
900
+ return this.handleContactOwnerChanged(context);
547
901
  case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
548
902
  return this.handleCampaignPreviewReservation(context);
549
903
  case _types2.CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
@@ -554,6 +908,63 @@ class TaskManager extends _events.default {
554
908
  };
555
909
  }
556
910
  }
911
+
912
+ /**
913
+ * Recover the promoted agent's active voice task when local task state was lost.
914
+ * Existing related tasks are never replaced: an unresolved or ambiguous relation
915
+ * is safer to ignore than to create a duplicate task.
916
+ */
917
+ handleContactOwnerChanged(context) {
918
+ if (context.task) {
919
+ return {
920
+ task: context.task
921
+ };
922
+ }
923
+ const {
924
+ payload
925
+ } = context;
926
+ if (this.findRelatedTasks(payload, {
927
+ includeCollectionKeys: true,
928
+ includeMainCallMediaKeys: true
929
+ }).length > 0 || !this.canRecoverTaskFromContactOwnerChanged(payload)) {
930
+ return {};
931
+ }
932
+ const stableInteractionId = TaskManager.getStableMainInteractionId(payload);
933
+ if (!stableInteractionId || this.taskCollection[stableInteractionId]) {
934
+ return {};
935
+ }
936
+ const normalizedPayload = payload.interactionId === stableInteractionId ? payload : {
937
+ ...payload,
938
+ interactionId: stableInteractionId
939
+ };
940
+ const taskData = {
941
+ ...normalizedPayload,
942
+ owner: normalizedPayload.interaction.owner,
943
+ wrapUpRequired: normalizedPayload.interaction.participants?.[this.agentId]?.isWrapUp || false,
944
+ isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(normalizedPayload),
945
+ isConsulted: false,
946
+ isAutoAnswering: false
947
+ };
948
+ const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
949
+ this.taskCollection[stableInteractionId] = task;
950
+
951
+ // Restore the actor before installing listeners so the internal hydrate does
952
+ // not leak as a second public hydrate or as an incoming-task notification.
953
+ task.sendStateMachineEvent({
954
+ type: _stateMachine.TaskEvent.HYDRATE,
955
+ taskData,
956
+ agentId: this.agentId
957
+ });
958
+ this.setupTaskListeners(task);
959
+ context.payload = taskData;
960
+ context.stateMachineEvent = {
961
+ type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
962
+ taskData
963
+ };
964
+ return {
965
+ task
966
+ };
967
+ }
557
968
  handleCampaignContactUpdated(context) {
558
969
  const {
559
970
  payload
@@ -638,7 +1049,7 @@ class TaskManager extends _events.default {
638
1049
  wrapUpRequired: false,
639
1050
  isConferenceInProgress: false,
640
1051
  isAutoAnswering: false
641
- }, this.configFlags, this.wrapupData, this.agentId);
1052
+ }, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
642
1053
  this.setupTaskListeners(task);
643
1054
  this.taskCollection[payload.interactionId] = task;
644
1055
  } else {
@@ -664,7 +1075,7 @@ class TaskManager extends _events.default {
664
1075
  isConsulted: isConsultedTask,
665
1076
  isAutoAnswering: shouldAutoAnswer
666
1077
  };
667
- const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
1078
+ const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
668
1079
  this.setupTaskListeners(task);
669
1080
  this.taskCollection[payload.interactionId] = task;
670
1081
  return {
@@ -693,7 +1104,7 @@ class TaskManager extends _events.default {
693
1104
  isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
694
1105
  isAutoAnswering: shouldAutoAnswer
695
1106
  };
696
- task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
1107
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
697
1108
  this.setupTaskListeners(task);
698
1109
  this.taskCollection[payload.interactionId] = task;
699
1110
  }
@@ -713,6 +1124,14 @@ class TaskManager extends _events.default {
713
1124
  destinationType: taskData.destinationType ?? snapshot?.context?.consultDestinationType ?? null
714
1125
  } : taskData;
715
1126
  task.updateTaskData(updateTaskData);
1127
+
1128
+ // A main-interaction lifecycle event can re-key a child consult task. Remove
1129
+ // any aliases first so consumers never observe duplicate task entries.
1130
+ Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
1131
+ if (candidate === task && taskId !== taskData.interactionId) {
1132
+ delete this.taskCollection[taskId];
1133
+ }
1134
+ });
716
1135
  this.taskCollection[taskData.interactionId] = task;
717
1136
  return task;
718
1137
  }
@@ -753,8 +1172,8 @@ class TaskManager extends _events.default {
753
1172
  task.on(_types.TASK_EVENTS.TASK_CLEANUP, (t, options) => {
754
1173
  this.handleTaskCleanup(t);
755
1174
  if (options?.removeFromCollection) {
756
- const interactionId = t?.data?.interactionId;
757
- if (interactionId && this.taskCollection[interactionId]) {
1175
+ const taskIsStillCollected = Object.values(this.taskCollection).some(candidate => candidate === t);
1176
+ if (taskIsStillCollected) {
758
1177
  this.removeTaskFromCollection(t);
759
1178
  }
760
1179
  }
@@ -765,7 +1184,11 @@ class TaskManager extends _events.default {
765
1184
  task.cancelAutoWrapupTimer();
766
1185
  }
767
1186
  if (task?.data?.interactionId) {
768
- delete this.taskCollection[task.data.interactionId];
1187
+ Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
1188
+ if (candidate === task) {
1189
+ delete this.taskCollection[taskId];
1190
+ }
1191
+ });
769
1192
  _loggerProxy.default.info(`Task removed from collection`, {
770
1193
  module: _constants.TASK_MANAGER_FILE,
771
1194
  method: _constants2.METHODS.REMOVE_TASK_FROM_COLLECTION,
@@ -810,7 +1233,7 @@ class TaskManager extends _events.default {
810
1233
  isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
811
1234
  isConsulted: false
812
1235
  };
813
- task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
1236
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
814
1237
  this.taskCollection[payload.interactionId] = task;
815
1238
 
816
1239
  // Transition the new task out of IDLE immediately so UI controls are
@@ -884,6 +1307,52 @@ class TaskManager extends _events.default {
884
1307
  ...this.taskCollection
885
1308
  };
886
1309
  }
1310
+
1311
+ /**
1312
+ * Propagate runtime enableWxBetterTogether changes to config flags and active tasks.
1313
+ */
1314
+ applyEnableWxBetterTogether(enabled) {
1315
+ if (this.configFlags) {
1316
+ this.configFlags = {
1317
+ ...this.configFlags,
1318
+ enableWxBetterTogether: enabled
1319
+ };
1320
+ }
1321
+ Object.values(this.taskCollection).forEach(task => {
1322
+ task.setEnableWxBetterTogether(enabled);
1323
+ if (enabled) {
1324
+ TaskManager.syncWxAppMuteFromCallDetailsForTask(task);
1325
+ }
1326
+ });
1327
+ }
1328
+
1329
+ /**
1330
+ * Propagate wxApp mute state from Mercury sync to active voice tasks.
1331
+ */
1332
+ applyWxAppMuteStateFromSync(callId, muted) {
1333
+ Object.values(this.taskCollection).forEach(task => {
1334
+ task.applyWxAppMuteStateFromSync(callId, muted);
1335
+ });
1336
+ }
1337
+
1338
+ /**
1339
+ * Re-seeds wxApp mute state from GET telephony/calls/{callId} for one task.
1340
+ */
1341
+ static syncWxAppMuteFromCallDetailsForTask(task) {
1342
+ const sync = task.syncWxAppMuteFromCallDetails?.bind(task);
1343
+ if (typeof sync === 'function') {
1344
+ sync().catch(() => undefined);
1345
+ }
1346
+ }
1347
+
1348
+ /**
1349
+ * Re-seeds wxApp mute for all engaged voice tasks (reload / runtime enable backfill).
1350
+ */
1351
+ syncWxAppMuteFromCallDetailsForAllTasks() {
1352
+ Object.values(this.taskCollection).forEach(task => {
1353
+ TaskManager.syncWxAppMuteFromCallDetailsForTask(task);
1354
+ });
1355
+ }
887
1356
  static getTaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager) {
888
1357
  if (!TaskManager.taskManager) {
889
1358
  TaskManager.taskManager = new TaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager);