@webex/contact-center 3.12.0-next.73 → 3.12.0-next.74

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 (188) hide show
  1. package/AGENTS.md +438 -0
  2. package/ai-docs/README.md +131 -0
  3. package/ai-docs/RULES.md +455 -0
  4. package/ai-docs/patterns/event-driven-patterns.md +485 -0
  5. package/ai-docs/patterns/testing-patterns.md +480 -0
  6. package/ai-docs/patterns/typescript-patterns.md +365 -0
  7. package/ai-docs/templates/README.md +102 -0
  8. package/ai-docs/templates/documentation/create-agents-md.md +240 -0
  9. package/ai-docs/templates/documentation/create-architecture-md.md +295 -0
  10. package/ai-docs/templates/existing-service/bug-fix.md +254 -0
  11. package/ai-docs/templates/existing-service/feature-enhancement.md +450 -0
  12. package/ai-docs/templates/new-method/00-master.md +80 -0
  13. package/ai-docs/templates/new-method/01-requirements.md +232 -0
  14. package/ai-docs/templates/new-method/02-implementation.md +295 -0
  15. package/ai-docs/templates/new-method/03-tests.md +201 -0
  16. package/ai-docs/templates/new-method/04-validation.md +141 -0
  17. package/ai-docs/templates/new-service/00-master.md +109 -0
  18. package/ai-docs/templates/new-service/01-pre-questions.md +159 -0
  19. package/ai-docs/templates/new-service/02-code-generation.md +346 -0
  20. package/ai-docs/templates/new-service/03-integration.md +178 -0
  21. package/ai-docs/templates/new-service/04-test-generation.md +205 -0
  22. package/ai-docs/templates/new-service/05-validation.md +145 -0
  23. package/dist/cc.js +53 -38
  24. package/dist/cc.js.map +1 -1
  25. package/dist/config.js +6 -0
  26. package/dist/config.js.map +1 -1
  27. package/dist/constants.js +14 -1
  28. package/dist/constants.js.map +1 -1
  29. package/dist/index.js +13 -5
  30. package/dist/index.js.map +1 -1
  31. package/dist/metrics/behavioral-events.js +26 -0
  32. package/dist/metrics/behavioral-events.js.map +1 -1
  33. package/dist/metrics/constants.js +9 -3
  34. package/dist/metrics/constants.js.map +1 -1
  35. package/dist/services/ApiAiAssistant.js +74 -3
  36. package/dist/services/ApiAiAssistant.js.map +1 -1
  37. package/dist/services/config/Util.js +2 -2
  38. package/dist/services/config/Util.js.map +1 -1
  39. package/dist/services/config/types.js +25 -9
  40. package/dist/services/config/types.js.map +1 -1
  41. package/dist/services/core/Utils.js +74 -27
  42. package/dist/services/core/Utils.js.map +1 -1
  43. package/dist/services/core/websocket/WebSocketManager.js +2 -1
  44. package/dist/services/core/websocket/WebSocketManager.js.map +1 -1
  45. package/dist/services/core/websocket/types.js.map +1 -1
  46. package/dist/services/index.js +1 -1
  47. package/dist/services/index.js.map +1 -1
  48. package/dist/services/task/Task.js +688 -0
  49. package/dist/services/task/Task.js.map +1 -0
  50. package/dist/services/task/TaskFactory.js +45 -0
  51. package/dist/services/task/TaskFactory.js.map +1 -0
  52. package/dist/services/task/TaskManager.js +725 -612
  53. package/dist/services/task/TaskManager.js.map +1 -1
  54. package/dist/services/task/TaskUtils.js +162 -26
  55. package/dist/services/task/TaskUtils.js.map +1 -1
  56. package/dist/services/task/constants.js +6 -1
  57. package/dist/services/task/constants.js.map +1 -1
  58. package/dist/services/task/digital/Digital.js +77 -0
  59. package/dist/services/task/digital/Digital.js.map +1 -0
  60. package/dist/services/task/state-machine/TaskStateMachine.js +837 -0
  61. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -0
  62. package/dist/services/task/state-machine/actions.js +543 -0
  63. package/dist/services/task/state-machine/actions.js.map +1 -0
  64. package/dist/services/task/state-machine/constants.js +161 -0
  65. package/dist/services/task/state-machine/constants.js.map +1 -0
  66. package/dist/services/task/state-machine/guards.js +340 -0
  67. package/dist/services/task/state-machine/guards.js.map +1 -0
  68. package/dist/services/task/state-machine/index.js +53 -0
  69. package/dist/services/task/state-machine/index.js.map +1 -0
  70. package/dist/services/task/state-machine/types.js +54 -0
  71. package/dist/services/task/state-machine/types.js.map +1 -0
  72. package/dist/services/task/state-machine/uiControlsComputer.js +553 -0
  73. package/dist/services/task/state-machine/uiControlsComputer.js.map +1 -0
  74. package/dist/services/task/taskDataNormalizer.js +99 -0
  75. package/dist/services/task/taskDataNormalizer.js.map +1 -0
  76. package/dist/services/task/types.js +168 -16
  77. package/dist/services/task/types.js.map +1 -1
  78. package/dist/services/task/voice/Voice.js +1042 -0
  79. package/dist/services/task/voice/Voice.js.map +1 -0
  80. package/dist/services/task/voice/WebRTC.js +149 -0
  81. package/dist/services/task/voice/WebRTC.js.map +1 -0
  82. package/dist/types/cc.d.ts +19 -18
  83. package/dist/types/config.d.ts +6 -0
  84. package/dist/types/constants.d.ts +14 -1
  85. package/dist/types/index.d.ts +11 -5
  86. package/dist/types/metrics/constants.d.ts +7 -1
  87. package/dist/types/services/ApiAiAssistant.d.ts +11 -3
  88. package/dist/types/services/config/types.d.ts +113 -9
  89. package/dist/types/services/core/Utils.d.ts +23 -10
  90. package/dist/types/services/core/websocket/WebSocketManager.d.ts +1 -0
  91. package/dist/types/services/core/websocket/types.d.ts +1 -1
  92. package/dist/types/services/index.d.ts +1 -1
  93. package/dist/types/services/task/Task.d.ts +157 -0
  94. package/dist/types/services/task/TaskFactory.d.ts +12 -0
  95. package/dist/types/services/task/TaskUtils.d.ts +46 -2
  96. package/dist/types/services/task/constants.d.ts +5 -0
  97. package/dist/types/services/task/digital/Digital.d.ts +22 -0
  98. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +1144 -0
  99. package/dist/types/services/task/state-machine/actions.d.ts +10 -0
  100. package/dist/types/services/task/state-machine/constants.d.ts +107 -0
  101. package/dist/types/services/task/state-machine/guards.d.ts +90 -0
  102. package/dist/types/services/task/state-machine/index.d.ts +13 -0
  103. package/dist/types/services/task/state-machine/types.d.ts +267 -0
  104. package/dist/types/services/task/state-machine/uiControlsComputer.d.ts +9 -0
  105. package/dist/types/services/task/taskDataNormalizer.d.ts +10 -0
  106. package/dist/types/services/task/types.d.ts +551 -78
  107. package/dist/types/services/task/voice/Voice.d.ts +184 -0
  108. package/dist/types/services/task/voice/WebRTC.d.ts +53 -0
  109. package/dist/types/types.d.ts +92 -0
  110. package/dist/types/webex.d.ts +1 -0
  111. package/dist/types.js +85 -0
  112. package/dist/types.js.map +1 -1
  113. package/dist/webex.js +14 -2
  114. package/dist/webex.js.map +1 -1
  115. package/package.json +15 -12
  116. package/src/cc.ts +63 -43
  117. package/src/config.ts +6 -0
  118. package/src/constants.ts +14 -1
  119. package/src/index.ts +14 -5
  120. package/src/metrics/ai-docs/AGENTS.md +348 -0
  121. package/src/metrics/ai-docs/ARCHITECTURE.md +336 -0
  122. package/src/metrics/behavioral-events.ts +28 -0
  123. package/src/metrics/constants.ts +9 -4
  124. package/src/services/ApiAiAssistant.ts +104 -3
  125. package/src/services/agent/ai-docs/AGENTS.md +238 -0
  126. package/src/services/agent/ai-docs/ARCHITECTURE.md +302 -0
  127. package/src/services/ai-docs/AGENTS.md +384 -0
  128. package/src/services/config/Util.ts +2 -2
  129. package/src/services/config/ai-docs/AGENTS.md +253 -0
  130. package/src/services/config/ai-docs/ARCHITECTURE.md +424 -0
  131. package/src/services/config/types.ts +116 -10
  132. package/src/services/core/Utils.ts +85 -34
  133. package/src/services/core/ai-docs/AGENTS.md +379 -0
  134. package/src/services/core/ai-docs/ARCHITECTURE.md +696 -0
  135. package/src/services/core/websocket/WebSocketManager.ts +2 -0
  136. package/src/services/core/websocket/types.ts +1 -1
  137. package/src/services/index.ts +1 -1
  138. package/src/services/task/Task.ts +837 -0
  139. package/src/services/task/TaskFactory.ts +55 -0
  140. package/src/services/task/TaskManager.ts +738 -697
  141. package/src/services/task/TaskUtils.ts +205 -25
  142. package/src/services/task/ai-docs/AGENTS.md +455 -0
  143. package/src/services/task/ai-docs/ARCHITECTURE.md +585 -0
  144. package/src/services/task/constants.ts +5 -0
  145. package/src/services/task/digital/Digital.ts +95 -0
  146. package/src/services/task/state-machine/TaskStateMachine.ts +1077 -0
  147. package/src/services/task/state-machine/actions.ts +685 -0
  148. package/src/services/task/state-machine/ai-docs/AGENTS.md +495 -0
  149. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +1135 -0
  150. package/src/services/task/state-machine/constants.ts +172 -0
  151. package/src/services/task/state-machine/guards.ts +406 -0
  152. package/src/services/task/state-machine/index.ts +28 -0
  153. package/src/services/task/state-machine/types.ts +241 -0
  154. package/src/services/task/state-machine/uiControlsComputer.ts +867 -0
  155. package/src/services/task/taskDataNormalizer.ts +137 -0
  156. package/src/services/task/types.ts +654 -85
  157. package/src/services/task/voice/Voice.ts +1267 -0
  158. package/src/services/task/voice/WebRTC.ts +187 -0
  159. package/src/types.ts +112 -1
  160. package/src/utils/AGENTS.md +276 -0
  161. package/src/webex.js +2 -0
  162. package/test/unit/spec/cc.ts +133 -3
  163. package/test/unit/spec/logger-proxy.ts +70 -0
  164. package/test/unit/spec/services/ApiAiAssistant.ts +122 -17
  165. package/test/unit/spec/services/WebCallingService.ts +7 -1
  166. package/test/unit/spec/services/config/index.ts +27 -27
  167. package/test/unit/spec/services/core/Utils.ts +335 -1
  168. package/test/unit/spec/services/core/websocket/WebSocketManager.ts +66 -40
  169. package/test/unit/spec/services/task/AutoWrapup.ts +63 -0
  170. package/test/unit/spec/services/task/Task.ts +477 -0
  171. package/test/unit/spec/services/task/TaskFactory.ts +62 -0
  172. package/test/unit/spec/services/task/TaskManager.ts +821 -1936
  173. package/test/unit/spec/services/task/TaskUtils.ts +206 -0
  174. package/test/unit/spec/services/task/digital/Digital.ts +105 -0
  175. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +1825 -0
  176. package/test/unit/spec/services/task/state-machine/guards.ts +479 -0
  177. package/test/unit/spec/services/task/state-machine/types.ts +18 -0
  178. package/test/unit/spec/services/task/state-machine/uiControlsComputer.ts +2020 -0
  179. package/test/unit/spec/services/task/taskTestUtils.ts +87 -0
  180. package/test/unit/spec/services/task/voice/Voice.ts +631 -0
  181. package/test/unit/spec/services/task/voice/WebRTC.ts +235 -0
  182. package/umd/contact-center.min.js +2 -2
  183. package/umd/contact-center.min.js.map +1 -1
  184. package/dist/services/task/index.js +0 -1530
  185. package/dist/services/task/index.js.map +0 -1
  186. package/dist/types/services/task/index.d.ts +0 -650
  187. package/src/services/task/index.ts +0 -1806
  188. package/test/unit/spec/services/task/index.ts +0 -2205
@@ -12,11 +12,15 @@ var _constants2 = require("./constants");
12
12
  var _types2 = require("../config/types");
13
13
  var _types3 = require("../../types");
14
14
  var _loggerProxy = _interopRequireDefault(require("../../logger-proxy"));
15
- var _ = _interopRequireDefault(require("."));
16
- var _MetricsManager = _interopRequireDefault(require("../../metrics/MetricsManager"));
17
- var _constants3 = require("../../metrics/constants");
18
15
  var _TaskUtils = require("./TaskUtils");
16
+ var _TaskFactory = _interopRequireDefault(require("./TaskFactory"));
17
+ var _WebRTC = _interopRequireDefault(require("./voice/WebRTC"));
18
+ var _stateMachine = require("./state-machine");
19
+ var _taskDataNormalizer = require("./taskDataNormalizer");
19
20
  function _interopRequireDefault(e) { return e && e.__esModule ? e : { default: e }; }
21
+ const CC_EVENT_SET = new Set(Object.values(_types2.CC_EVENTS));
22
+ const isCcEvent = value => CC_EVENT_SET.has(value);
23
+
20
24
  /** @internal */
21
25
  class TaskManager extends _events.default {
22
26
  /**
@@ -25,46 +29,30 @@ class TaskManager extends _events.default {
25
29
  * @private
26
30
  */
27
31
 
32
+ // eslint-disable-next-line no-use-before-define
33
+
28
34
  /**
29
35
  * @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
30
36
  * @param webCallingService - Webrtc Service Layer
31
37
  * @param webSocketManager - Websocket Manager to maintain websocket connection and keepalives
32
38
  */
33
- constructor(apiAIAssistant, contact, webCallingService, webSocketManager) {
39
+ constructor(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager) {
34
40
  super();
35
41
  this.apiAIAssistant = apiAIAssistant;
36
42
  this.contact = contact;
37
- this.taskCollection = {};
38
43
  this.webCallingService = webCallingService;
39
44
  this.webSocketManager = webSocketManager;
40
- this.metricsManager = _MetricsManager.default.getInstance();
45
+ this.rtdWebSocketManager = rtdWebSocketManager;
46
+ this.taskCollection = {};
47
+ this.webRtcEnabled = false;
41
48
  this.registerTaskListeners();
42
49
  this.registerIncomingCallEvent();
43
50
  }
44
- setWrapupData(wrapupData) {
45
- this.wrapupData = wrapupData;
46
- }
47
- setAgentId(agentId) {
48
- this.agentId = agentId;
49
- }
50
-
51
- /**
52
- * Gets the current agent ID
53
- * @returns {string} The agent ID set for this task manager instance
54
- * @public
55
- */
56
- getAgentId() {
57
- return this.agentId;
58
- }
59
- setWebRtcEnabled(webRtcEnabled) {
60
- this.webRtcEnabled = webRtcEnabled;
61
- }
62
51
  handleRealtimeWebsocketEvent(event) {
63
52
  try {
64
53
  const payload = JSON.parse(event);
65
- const eventType = payload?.type || payload?.data?.notifType;
66
54
  const interactionId = payload?.data?.data?.conversationId;
67
- if (!eventType || !interactionId) return;
55
+ if (!interactionId) return;
68
56
  const task = this.taskCollection[interactionId];
69
57
  if (!task) {
70
58
  _loggerProxy.default.info(`Realtime transcription task not found`, {
@@ -74,7 +62,12 @@ class TaskManager extends _events.default {
74
62
  });
75
63
  return;
76
64
  }
77
- task.emit(eventType, payload.data);
65
+ switch (payload.type) {
66
+ case _types2.CC_EVENTS.REAL_TIME_TRANSCRIPTION:
67
+ case _types2.CC_EVENTS.SUGGESTED_RESPONSE:
68
+ task.emit(payload.type, payload.data);
69
+ break;
70
+ }
78
71
  } catch (error) {
79
72
  _loggerProxy.default.error('Failed to parse RTD WebSocket message', {
80
73
  module: _constants.TASK_MANAGER_FILE,
@@ -83,6 +76,39 @@ class TaskManager extends _events.default {
83
76
  });
84
77
  }
85
78
  }
79
+
80
+ /**
81
+ * Set config flags for task creation
82
+ */
83
+ setConfigFlags(configFlags) {
84
+ this.configFlags = configFlags;
85
+ }
86
+
87
+ /**
88
+ * Set wrapup configuration data
89
+ */
90
+ setWrapupData(wrapupData) {
91
+ this.wrapupData = wrapupData;
92
+ }
93
+
94
+ /**
95
+ * Set agent ID for task operations
96
+ */
97
+ setAgentId(agentId) {
98
+ this.agentId = agentId;
99
+ }
100
+
101
+ /**
102
+ * Gets the current agent ID
103
+ * @returns {string} The agent ID set for this task manager instance
104
+ * @public
105
+ */
106
+ getAgentId() {
107
+ return this.agentId;
108
+ }
109
+ setWebRtcEnabled(webRtcEnabled) {
110
+ this.webRtcEnabled = webRtcEnabled;
111
+ }
86
112
  handleIncomingWebCall = call => {
87
113
  const currentTask = Object.values(this.taskCollection).find(task => task.data.interaction.mediaType === 'telephony' && !(0, _TaskUtils.isCampaignPreviewReservation)(task));
88
114
  if (currentTask) {
@@ -92,7 +118,12 @@ class TaskManager extends _events.default {
92
118
  method: _constants2.METHODS.HANDLE_INCOMING_WEB_CALL,
93
119
  interactionId: currentTask.data.interactionId
94
120
  });
95
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, currentTask);
121
+
122
+ // Send TASK_INCOMING to state machine - it will emit on the task object
123
+ const eventPayload = TaskManager.mapEventToTaskStateMachineEvent(_types2.CC_EVENTS.AGENT_CONTACT_RESERVED, currentTask.data);
124
+ if (eventPayload && currentTask) {
125
+ currentTask.sendStateMachineEvent(eventPayload);
126
+ }
96
127
  }
97
128
  this.call = call;
98
129
  };
@@ -102,554 +133,637 @@ class TaskManager extends _events.default {
102
133
  unregisterIncomingCallEvent() {
103
134
  this.webCallingService.off(_calling.LINE_EVENTS.INCOMING_CALL, this.handleIncomingWebCall);
104
135
  }
136
+
137
+ /**
138
+ * Map WebSocket CC_EVENTS to state machine TaskEvent
139
+ * @param ccEvent - The CC_EVENT type from WebSocket
140
+ * @param payload - The event payload
141
+ * @param agentId - Optional agent ID for state detection (needed for HYDRATE)
142
+ * @returns TaskEventPayload for state machine or null if no mapping
143
+ */
144
+ static mapEventToTaskStateMachineEvent(ccEvent, payload, agentId) {
145
+ const mediaResourceId = payload.mediaResourceId || payload.interaction?.media?.[payload.interactionId]?.mediaResourceId;
146
+ switch (ccEvent) {
147
+ // CC -> TaskEvent mappings (see TaskStateMachine comment for quick reference)
148
+ case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
149
+ // AgentContactReserved -> TASK_INCOMING
150
+ if ((0, _TaskUtils.isCampaignPreviewTask)(payload)) {
151
+ return {
152
+ type: _stateMachine.TaskEvent.TASK_INCOMING,
153
+ taskData: payload,
154
+ isCampaignReservationAccept: true
155
+ };
156
+ }
157
+ return {
158
+ type: _stateMachine.TaskEvent.TASK_INCOMING,
159
+ taskData: payload
160
+ };
161
+ case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
162
+ // -> TASK_INCOMING (campaign branch via guard)
163
+ return {
164
+ type: _stateMachine.TaskEvent.TASK_INCOMING,
165
+ taskData: payload,
166
+ isCampaignReservationAccept: true
167
+ };
168
+ case _types2.CC_EVENTS.AGENT_OFFER_CONTACT:
169
+ // AgentOfferContact -> TASK_OFFERED
170
+ return {
171
+ type: _stateMachine.TaskEvent.TASK_OFFERED,
172
+ taskData: payload
173
+ };
174
+ case _types2.CC_EVENTS.AGENT_CONTACT:
175
+ // AgentContact -> HYDRATE
176
+ // Include agentId for state detection (e.g., checking isWrapUp in participant data)
177
+ return {
178
+ type: _stateMachine.TaskEvent.HYDRATE,
179
+ taskData: payload,
180
+ agentId
181
+ };
182
+ case _types2.CC_EVENTS.CONTACT_UPDATED:
183
+ return {
184
+ type: _stateMachine.TaskEvent.CONTACT_UPDATED,
185
+ taskData: payload
186
+ };
187
+ case _types2.CC_EVENTS.CONTACT_OWNER_CHANGED:
188
+ return {
189
+ type: _stateMachine.TaskEvent.CONTACT_OWNER_CHANGED,
190
+ taskData: payload
191
+ };
192
+ case _types2.CC_EVENTS.AGENT_OFFER_CONSULT:
193
+ // AgentOfferConsult -> OFFER_CONSULT
194
+ return {
195
+ type: _stateMachine.TaskEvent.OFFER_CONSULT,
196
+ taskData: {
197
+ ...payload,
198
+ isConsulted: true
199
+ }
200
+ };
201
+ case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGNED:
202
+ // AgentContactAssigned -> ASSIGN
203
+ return {
204
+ type: _stateMachine.TaskEvent.ASSIGN,
205
+ taskData: payload
206
+ };
207
+ case _types2.CC_EVENTS.AGENT_CONTACT_HELD:
208
+ return {
209
+ type: _stateMachine.TaskEvent.HOLD_SUCCESS,
210
+ mediaResourceId: mediaResourceId || '',
211
+ taskData: payload
212
+ };
213
+ case _types2.CC_EVENTS.AGENT_CONTACT_UNHELD:
214
+ return {
215
+ type: _stateMachine.TaskEvent.UNHOLD_SUCCESS,
216
+ mediaResourceId: mediaResourceId || '',
217
+ taskData: payload
218
+ };
219
+ case _types2.CC_EVENTS.AGENT_CONSULT_CREATED:
220
+ return {
221
+ type: _stateMachine.TaskEvent.CONSULT_CREATED,
222
+ taskData: {
223
+ ...payload,
224
+ isConsulted: false
225
+ }
226
+ };
227
+ case _types2.CC_EVENTS.AGENT_CONSULTING:
228
+ // AgentConsulting -> CONSULTING_ACTIVE
229
+ // use context to figure out if it's the initiator or receiver using consultInitiator from context
230
+ return {
231
+ type: _stateMachine.TaskEvent.CONSULTING_ACTIVE,
232
+ consultDestinationAgentJoined: true,
233
+ taskData: payload
234
+ };
235
+ case _types2.CC_EVENTS.AGENT_CONSULT_ENDED:
236
+ // AgentConsultEnded -> CONSULT_END
237
+ return {
238
+ type: _stateMachine.TaskEvent.CONSULT_END,
239
+ taskData: payload
240
+ };
241
+ case _types2.CC_EVENTS.AGENT_CONSULT_FAILED:
242
+ case _types2.CC_EVENTS.AGENT_CTQ_FAILED:
243
+ return {
244
+ type: _stateMachine.TaskEvent.CONSULT_FAILED,
245
+ reason: payload.reason,
246
+ taskData: payload
247
+ };
248
+ case _types2.CC_EVENTS.AGENT_CTQ_CANCELLED:
249
+ return {
250
+ type: _stateMachine.TaskEvent.CTQ_CANCEL,
251
+ taskData: payload
252
+ };
253
+ case _types2.CC_EVENTS.AGENT_CTQ_CANCEL_FAILED:
254
+ return {
255
+ type: _stateMachine.TaskEvent.CTQ_CANCEL_FAILED,
256
+ taskData: payload
257
+ };
258
+ case _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED: // AgentBlindTransferred -> TRANSFER_SUCCESS
259
+ case _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED: // AgentConsultTransferred -> TRANSFER_SUCCESS
260
+ case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED:
261
+ // AgentVTeamTransferred -> TRANSFER_SUCCESS
262
+ return {
263
+ type: _stateMachine.TaskEvent.TRANSFER_SUCCESS,
264
+ taskData: payload
265
+ };
266
+ case _types2.CC_EVENTS.AGENT_WRAPUP:
267
+ return {
268
+ type: _stateMachine.TaskEvent.TASK_WRAPUP,
269
+ taskData: {
270
+ ...payload,
271
+ wrapUpRequired: true
272
+ }
273
+ };
274
+ case _types2.CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
275
+ return null;
276
+ // Add WRAPUP if needed
277
+
278
+ case _types2.CC_EVENTS.AGENT_BLIND_TRANSFER_FAILED:
279
+ case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFER_FAILED:
280
+ case _types2.CC_EVENTS.AGENT_CONSULT_TRANSFER_FAILED:
281
+ case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED:
282
+ return {
283
+ type: _stateMachine.TaskEvent.TRANSFER_FAILED,
284
+ taskData: payload
285
+ };
286
+ 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
292
+ }
293
+ };
294
+ case _types2.CC_EVENTS.AGENT_INVITE_FAILED:
295
+ return {
296
+ type: _stateMachine.TaskEvent.INVITE_FAILED,
297
+ reason: payload.reason
298
+ };
299
+ case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
300
+ return {
301
+ type: _stateMachine.TaskEvent.ASSIGN_FAILED,
302
+ reason: payload.reason
303
+ };
304
+ case _types2.CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
305
+ return {
306
+ type: _stateMachine.TaskEvent.RONA,
307
+ taskData: payload,
308
+ reason: payload.reason
309
+ };
310
+ case _types2.CC_EVENTS.AGENT_OUTBOUND_FAILED:
311
+ return {
312
+ type: _stateMachine.TaskEvent.OUTBOUND_FAILED,
313
+ taskData: payload,
314
+ reason: payload.reason
315
+ };
316
+ case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
317
+ return {
318
+ type: _stateMachine.TaskEvent.CAMPAIGN_PREVIEW_ACCEPT_FAILED,
319
+ taskData: payload
320
+ };
321
+ case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED:
322
+ return {
323
+ type: _stateMachine.TaskEvent.CAMPAIGN_PREVIEW_SKIP_FAILED,
324
+ taskData: payload
325
+ };
326
+ case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED:
327
+ return {
328
+ type: _stateMachine.TaskEvent.CAMPAIGN_PREVIEW_REMOVE_FAILED,
329
+ taskData: payload
330
+ };
331
+ case _types2.CC_EVENTS.CONTACT_RECORDING_STARTED:
332
+ return {
333
+ type: _stateMachine.TaskEvent.RECORDING_STARTED,
334
+ taskData: payload
335
+ };
336
+ case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSED:
337
+ return {
338
+ type: _stateMachine.TaskEvent.PAUSE_RECORDING,
339
+ taskData: payload
340
+ };
341
+ case _types2.CC_EVENTS.CONTACT_RECORDING_RESUMED:
342
+ return {
343
+ type: _stateMachine.TaskEvent.RESUME_RECORDING,
344
+ taskData: payload
345
+ };
346
+ case _types2.CC_EVENTS.AGENT_WRAPPEDUP:
347
+ return {
348
+ type: _stateMachine.TaskEvent.WRAPUP_COMPLETE,
349
+ taskData: payload
350
+ };
351
+
352
+ // Conference events - these trigger state machine transition to CONFERENCING
353
+ case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCED:
354
+ case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCING:
355
+ case _types2.CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE:
356
+ return {
357
+ type: _stateMachine.TaskEvent.CONFERENCE_START,
358
+ taskData: payload
359
+ };
360
+ case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED:
361
+ return {
362
+ type: _stateMachine.TaskEvent.CONFERENCE_FAILED,
363
+ reason: payload.reason,
364
+ taskData: payload
365
+ };
366
+ case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED:
367
+ return {
368
+ type: _stateMachine.TaskEvent.CONFERENCE_END,
369
+ taskData: payload
370
+ };
371
+ case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE:
372
+ return {
373
+ type: _stateMachine.TaskEvent.PARTICIPANT_LEAVE,
374
+ taskData: payload,
375
+ participantId: payload?.participantId
376
+ };
377
+ case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED:
378
+ return {
379
+ type: _stateMachine.TaskEvent.TRANSFER_CONFERENCE_SUCCESS,
380
+ taskData: payload
381
+ };
382
+ default:
383
+ // Not all events need state machine mapping
384
+ return null;
385
+ }
386
+ }
387
+
388
+ /**
389
+ * Register WebSocket message listeners for task events
390
+ *
391
+ * Main entry point that orchestrates event processing through a clear pipeline:
392
+ * 1. Parse and validate incoming WebSocket messages
393
+ * 2. Prepare event context with task and state machine mappings
394
+ * 3. Handle task lifecycle (creation, updates, collection management)
395
+ * 4. Send events to state machine (task-level transitions/emissions)
396
+ * 5. Cleanup is triggered via task events emitted by the state machine
397
+ *
398
+ * This architecture separates concerns:
399
+ * - TaskManager: Manages task collection lifecycle and operational concerns
400
+ * - State Machine: Manages individual task state and event emissions
401
+ */
105
402
  registerTaskListeners() {
106
403
  this.webSocketManager.on('message', event => {
107
- const payload = JSON.parse(event);
108
- // Re-emit the task events to the task object
109
- let task;
110
- if (payload.data?.type || payload.type) {
111
- if (Object.values(_types2.CC_TASK_EVENTS).includes(payload.data.type || payload.type)) {
112
- task = this.taskCollection[payload.data?.interactionId] || this.taskCollection[payload.data?.data?.conversationId];
113
- }
114
- _loggerProxy.default.info(`Handling task event ${payload.data?.type}`, {
115
- module: _constants.TASK_MANAGER_FILE,
116
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
117
- interactionId: payload.data?.interactionId
118
- });
119
- switch (payload.data.type) {
120
- case _types2.CC_EVENTS.AGENT_CONTACT:
121
- // Case1 : Task is already present in taskCollection
122
- if (this.taskCollection[payload.data.interactionId]) {
123
- _loggerProxy.default.log(`Got AGENT_CONTACT: Task already exists in collection`, {
124
- module: _constants.TASK_MANAGER_FILE,
125
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
126
- interactionId: payload.data.interactionId
127
- });
128
- break;
129
- } else if (!this.taskCollection[payload.data.interactionId]) {
130
- // Case2 : Task is not present in taskCollection
131
- _loggerProxy.default.log(`Got AGENT_CONTACT : Creating new task in taskManager`, {
132
- module: _constants.TASK_MANAGER_FILE,
133
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
134
- interactionId: payload.data.interactionId
135
- });
136
-
137
- // Check if auto-answer should happen for this task
138
- const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload.data, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
139
- task = new _.default(this.contact, this.webCallingService, {
140
- ...payload.data,
141
- wrapUpRequired: payload.data.interaction?.participants?.[this.agentId]?.isWrapUp || false,
142
- isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data),
143
- isAutoAnswering: shouldAutoAnswer // Set flag before emitting
144
- }, this.wrapupData, this.agentId);
145
- this.taskCollection[payload.data.interactionId] = task;
146
- // Condition 1: The state is=new i.e it is a incoming task
147
- if (payload.data.interaction.state === 'new') {
148
- _loggerProxy.default.log(`Got AGENT_CONTACT for a task with state=new, sending TASK_INCOMING event`, {
149
- module: _constants.TASK_MANAGER_FILE,
150
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
151
- interactionId: payload.data.interactionId
152
- });
153
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
154
- } else {
155
- // Condition 2: The state is anything else i.e the task was connected
156
- _loggerProxy.default.log(`Got AGENT_CONTACT for a task with state=${payload.data.interaction.state}, sending TASK_HYDRATE event`, {
157
- module: _constants.TASK_MANAGER_FILE,
158
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
159
- interactionId: payload.data.interactionId
160
- });
161
- this.emit(_types.TASK_EVENTS.TASK_HYDRATE, task);
162
- }
163
- }
164
- break;
165
- case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
166
- {
167
- // Check if auto-answer should happen for this task
168
- const shouldAutoAnswerReserved = (0, _TaskUtils.shouldAutoAnswerTask)(payload.data, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
169
- task = new _.default(this.contact, this.webCallingService, {
170
- ...payload.data,
171
- isConsulted: false,
172
- isAutoAnswering: shouldAutoAnswerReserved // Set flag before emitting
173
- }, this.wrapupData, this.agentId);
174
- this.taskCollection[payload.data.interactionId] = task;
175
- if (this.webCallingService.loginOption !== _types3.LoginOption.BROWSER || task.data.interaction.mediaType !== _types.MEDIA_CHANNEL.TELEPHONY // for digital channels
176
- ) {
177
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
178
- } else if (this.call) {
179
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, task);
180
- }
181
- break;
182
- }
183
- case _types2.CC_EVENTS.AGENT_OFFER_CONTACT:
184
- // We don't have to emit any event here since this will be result of promise.
185
- task = this.updateTaskData(task, payload.data);
186
- _loggerProxy.default.log(`Agent offer contact received for task`, {
187
- module: _constants.TASK_MANAGER_FILE,
188
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
189
- interactionId: payload.data?.interactionId
190
- });
191
- this.emit(_types.TASK_EVENTS.TASK_OFFER_CONTACT, task);
192
-
193
- // Handle auto-answer for offer contact
194
- this.handleAutoAnswer(task);
195
- break;
196
- case _types2.CC_EVENTS.AGENT_OUTBOUND_FAILED:
197
- if (task) {
198
- task = this.updateTaskData(task, payload.data);
199
- this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES.TASK_OUTDIAL_FAILED, {
200
- ..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(payload.data),
201
- taskId: payload.data.interactionId,
202
- reason: payload.data.reasonCode || payload.data.reason
203
- }, ['behavioral', 'operational']);
204
- _loggerProxy.default.log(`Agent outbound failed for task`, {
205
- module: _constants.TASK_MANAGER_FILE,
206
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
207
- interactionId: payload.data.interactionId
208
- });
209
- task.emit(_types.TASK_EVENTS.TASK_OUTDIAL_FAILED, payload.data.reason ?? 'UNKNOWN_REASON');
210
- }
211
- break;
212
- case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGNED:
213
- // When a campaign preview contact is accepted, the assigned event may arrive
214
- // with a new interactionId while the task is stored under the original
215
- // reservationInteractionId. Fall back to that key so the task is found.
216
- if (!task && payload.data.reservationInteractionId) {
217
- task = this.taskCollection[payload.data.reservationInteractionId];
218
- if (task) {
219
- // Re-key the task under the new interaction ID and remove the old entry
220
- delete this.taskCollection[payload.data.reservationInteractionId];
221
- this.taskCollection[payload.data.interactionId] = task;
222
- }
223
- }
224
- if (task) {
225
- task = this.updateTaskData(task, payload.data);
226
- task.emit(_types.TASK_EVENTS.TASK_ASSIGNED, task);
227
- }
228
- // We emit the event to the task manager to send the task data to the instance without mobius registration in case of multi login with desktop login
229
- this.emit(_types.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
230
- break;
231
- case _types2.CC_EVENTS.AGENT_CONTACT_UNASSIGNED:
232
- task = this.updateTaskData(task, {
233
- ...payload.data,
234
- wrapUpRequired: true
235
- });
236
- task.emit(_types.TASK_EVENTS.TASK_END, task);
237
- break;
238
- case _types2.CC_EVENTS.AGENT_CONTACT_OFFER_RONA:
239
- case _types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED:
240
- case _types2.CC_EVENTS.AGENT_INVITE_FAILED:
241
- {
242
- _loggerProxy.default.info(`Task removal triggered by ${payload.data.type}`, {
243
- module: _constants.TASK_MANAGER_FILE,
244
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
245
- interactionId: payload.data.interactionId
246
- });
247
- task = this.updateTaskData(task, payload.data);
248
- const eventTypeToMetricMap = {
249
- [_types2.CC_EVENTS.AGENT_CONTACT_ASSIGN_FAILED]: 'AGENT_CONTACT_ASSIGN_FAILED',
250
- [_types2.CC_EVENTS.AGENT_INVITE_FAILED]: 'AGENT_INVITE_FAILED'
251
- };
252
- const metricEventName = eventTypeToMetricMap[payload.data.type] || 'AGENT_RONA';
253
- this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES[metricEventName], {
254
- ..._MetricsManager.default.getCommonTrackingFieldForAQMResponse(payload.data),
255
- taskId: payload.data.interactionId,
256
- reason: payload.data.reason
257
- }, ['behavioral', 'operational']);
258
- this.handleTaskCleanup(task);
259
- task.emit(_types.TASK_EVENTS.TASK_REJECT, payload.data.reason);
260
- break;
261
- }
262
- case _types2.CC_EVENTS.CONTACT_ENDED:
263
- // Update task data.
264
- if (task) {
265
- _loggerProxy.default.info(`Contact ended for interaction`, {
266
- module: _constants.TASK_MANAGER_FILE,
267
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
268
- interactionId: payload.data.interactionId
269
- });
270
-
271
- // Campaign preview tasks should never trigger wrapup on ContactEnded —
272
- // they are terminal cleanup events. For all other tasks, derive
273
- // wrapUpRequired from agentsPendingWrapUp as before.
274
- const CAMPAIGN_OUTBOUND_TYPES = ['STANDARD_PREVIEW_CAMPAIGN', 'DIRECT_PREVIEW_CAMPAIGN'];
275
- const isCampaignPreview = CAMPAIGN_OUTBOUND_TYPES.includes(task.data?.interaction?.outboundType ?? '');
276
- const wrapUpRequired = isCampaignPreview ? false : payload.data.agentsPendingWrapUp?.includes(this.agentId) || false;
277
- task = this.updateTaskData(task, {
278
- ...payload.data,
279
- wrapUpRequired
280
- });
281
-
282
- // Handle cleanup based on whether task should be deleted
283
- this.handleTaskCleanup(task);
284
- task?.emit(_types.TASK_EVENTS.TASK_END, task);
285
- }
286
- break;
287
- case _types2.CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
288
- {
289
- // CampaignContactUpdated is a non-terminal event (e.g., next contact after skip/remove).
290
- // Update the task data and emit an event so consumers can react to the updated contact.
291
- // Do NOT remove the task or emit TASK_END — cleanup is handled by CONTACT_ENDED.
292
- if (task) {
293
- // Carry forward campaign preview fields from existing task data since the updated
294
- // contact payload may not include them, and reconcileData would delete them.
295
- const existingCpd = task.data?.interaction?.callProcessingDetails;
296
- const updatedData = {
297
- ...payload.data
298
- };
299
- if (existingCpd) {
300
- const campaignFields = {
301
- ...(existingCpd.campaignPreviewAutoAction && {
302
- campaignPreviewAutoAction: existingCpd.campaignPreviewAutoAction
303
- }),
304
- ...(existingCpd.campaignPreviewOfferTimeout && {
305
- campaignPreviewOfferTimeout: existingCpd.campaignPreviewOfferTimeout
306
- }),
307
- ...(existingCpd.campaignPreviewSkipDisabled && {
308
- campaignPreviewSkipDisabled: existingCpd.campaignPreviewSkipDisabled
309
- }),
310
- ...(existingCpd.campaignPreviewRemoveDisabled && {
311
- campaignPreviewRemoveDisabled: existingCpd.campaignPreviewRemoveDisabled
312
- })
313
- };
314
- if (!updatedData.interaction) {
315
- updatedData.interaction = {};
316
- }
317
- updatedData.interaction = {
318
- ...updatedData.interaction,
319
- callProcessingDetails: {
320
- ...campaignFields,
321
- ...(updatedData.interaction.callProcessingDetails || {})
322
- }
323
- };
324
- }
325
- _loggerProxy.default.log('Campaign contact updated - carrying forward preview fields', {
326
- module: _constants.TASK_MANAGER_FILE,
327
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
328
- interactionId: payload.data.interactionId,
329
- data: {
330
- hasCpd: !!updatedData.interaction?.callProcessingDetails,
331
- autoAction: updatedData.interaction?.callProcessingDetails?.campaignPreviewAutoAction,
332
- skipDisabled: updatedData.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled,
333
- removeDisabled: updatedData.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled
334
- }
335
- });
336
- task = this.updateTaskData(task, updatedData);
337
- task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, task);
338
- }
339
- break;
340
- }
341
- case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_ACCEPT_FAILED:
342
- if (task) {
343
- // Failure payloads are sparse (no interaction field). Spread existing
344
- // task data first so reconcileData doesn't delete interaction/cpd.
345
- task = this.updateTaskData(task, {
346
- ...task.data,
347
- ...payload.data
348
- });
349
- task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_ACCEPT_FAILED, task);
350
- }
351
- break;
352
- case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_SKIP_FAILED:
353
- if (task) {
354
- task = this.updateTaskData(task, {
355
- ...task.data,
356
- ...payload.data
357
- });
358
- task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_SKIP_FAILED, task);
359
- }
360
- break;
361
- case _types2.CC_EVENTS.CAMPAIGN_PREVIEW_REMOVE_FAILED:
362
- if (task) {
363
- task = this.updateTaskData(task, {
364
- ...task.data,
365
- ...payload.data
366
- });
367
- task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_REMOVE_FAILED, task);
368
- }
369
- break;
370
- case _types2.CC_EVENTS.CONTACT_MERGED:
371
- task = this.handleContactMerged(task, payload.data);
372
- break;
373
- case _types2.CC_EVENTS.AGENT_CONTACT_HELD:
374
- // As soon as the main interaction is held, we need to emit TASK_HOLD
375
- task = this.updateTaskData(task, payload.data);
376
- task.emit(_types.TASK_EVENTS.TASK_HOLD, task);
377
- break;
378
- case _types2.CC_EVENTS.AGENT_CONTACT_UNHELD:
379
- // As soon as the main interaction is unheld, we need to emit TASK_RESUME
380
- task = this.updateTaskData(task, payload.data);
381
- task.emit(_types.TASK_EVENTS.TASK_RESUME, task);
382
- break;
383
- case _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED:
384
- task = this.updateTaskData(task, {
385
- ...payload.data,
386
- wrapUpRequired: true
387
- });
388
- task.emit(_types.TASK_EVENTS.TASK_END, task);
389
- break;
390
- case _types2.CC_EVENTS.AGENT_CTQ_CANCEL_FAILED:
391
- task = this.updateTaskData(task, payload.data);
392
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_QUEUE_FAILED, task);
393
- break;
394
- case _types2.CC_EVENTS.AGENT_CONSULT_CREATED:
395
- // Received when self agent initiates a consult
396
- task = this.updateTaskData(task, {
397
- ...payload.data,
398
- isConsulted: false // This ensures that the task consult status is always reset
399
- });
400
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_CREATED, task);
401
- break;
402
- case _types2.CC_EVENTS.AGENT_OFFER_CONSULT:
403
- // Received when other agent sends us a consult offer
404
- task = this.updateTaskData(task, {
405
- ...payload.data,
406
- isConsulted: true // This ensures that the task is marked as us being requested for a consult
407
- });
408
- task.emit(_types.TASK_EVENTS.TASK_OFFER_CONSULT, task);
409
-
410
- // Handle auto-answer for consult offer
411
- this.handleAutoAnswer(task);
412
- break;
413
- case _types2.CC_EVENTS.AGENT_CONSULTING:
414
- // Received when agent is in an active consult state
415
- // TODO: Check if we can use backend consult state instead of isConsulted
416
- task = this.updateTaskData(task, payload.data);
417
- if (task.data.isConsulted) {
418
- // Fire only if you are the agent who received the consult request
419
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_ACCEPTED, task);
420
- } else {
421
- // Fire only if you are the agent who initiated the consult
422
- task.emit(_types.TASK_EVENTS.TASK_CONSULTING, task);
423
- }
424
- // Also hydrate multi-login listeners when consulting state is received.
425
- this.emit(_types.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, task);
426
- break;
427
- case _types2.CC_EVENTS.AGENT_CONSULT_FAILED:
428
- // This can only be received by the agent who initiated the consult.
429
- // We need not emit any event here since this will be result of promise
430
- task = this.updateTaskData(task, payload.data);
431
- break;
432
- case _types2.CC_EVENTS.AGENT_CONSULT_ENDED:
433
- task = this.updateTaskData(task, payload.data);
434
- if (task.data.isConsulted) {
435
- // This will be the end state of the task as soon as we end the consult in case of
436
- // us being offered a consult
437
- this.removeTaskFromCollection(task);
438
- }
439
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_END, task);
440
- break;
441
- case _types2.CC_EVENTS.AGENT_CTQ_CANCELLED:
442
- // This event is received when the consult using queue is cancelled using API
443
- task = this.updateTaskData(task, payload.data);
444
- task.emit(_types.TASK_EVENTS.TASK_CONSULT_QUEUE_CANCELLED, task);
445
- break;
446
- case _types2.CC_EVENTS.AGENT_WRAPUP:
447
- task = this.updateTaskData(task, {
448
- ...payload.data,
449
- wrapUpRequired: true
450
- });
451
- task.emit(_types.TASK_EVENTS.TASK_END, task);
452
- break;
453
- case _types2.CC_EVENTS.AGENT_WRAPPEDUP:
454
- task.cancelAutoWrapupTimer();
455
- this.removeTaskFromCollection(task);
456
- task.emit(_types.TASK_EVENTS.TASK_WRAPPEDUP, task);
457
- break;
458
- case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSED:
459
- task = this.updateTaskData(task, payload.data);
460
- task.emit(_types.TASK_EVENTS.TASK_RECORDING_PAUSED, task);
461
- break;
462
- case _types2.CC_EVENTS.CONTACT_RECORDING_PAUSE_FAILED:
463
- task = this.updateTaskData(task, payload.data);
464
- task.emit(_types.TASK_EVENTS.TASK_RECORDING_PAUSE_FAILED, task);
465
- break;
466
- case _types2.CC_EVENTS.CONTACT_RECORDING_RESUMED:
467
- task = this.updateTaskData(task, payload.data);
468
- task.emit(_types.TASK_EVENTS.TASK_RECORDING_RESUMED, task);
469
- break;
470
- case _types2.CC_EVENTS.CONTACT_RECORDING_RESUME_FAILED:
471
- task = this.updateTaskData(task, payload.data);
472
- task.emit(_types.TASK_EVENTS.TASK_RECORDING_RESUME_FAILED, task);
473
- break;
474
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCING:
475
- // Conference is being established - update task state and emit establishing event
476
- task = this.updateTaskData(task, payload.data);
477
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_ESTABLISHING, task);
478
- break;
479
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCED:
480
- // Conference started successfully - update task state and emit event
481
- task = this.updateTaskData(task, payload.data);
482
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_STARTED, task);
483
- break;
484
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_FAILED:
485
- // Conference failed - update task state and emit failure event
486
- task = this.updateTaskData(task, payload.data);
487
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_FAILED, task);
488
- break;
489
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_ENDED:
490
- // Conference ended - update task state and emit event
491
- task = this.updateTaskData(task, payload.data);
492
- if (!task || (0, _TaskUtils.isPrimary)(task, this.agentId) || (0, _TaskUtils.isParticipantInMainInteraction)(task, this.agentId)) {
493
- _loggerProxy.default.log('Primary or main interaction participant leaving conference');
494
- } else {
495
- this.removeTaskFromCollection(task);
496
- }
497
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_ENDED, task);
498
- break;
499
- case _types2.CC_EVENTS.PARTICIPANT_JOINED_CONFERENCE:
500
- {
501
- task = this.updateTaskData(task, {
502
- ...payload.data,
503
- isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data)
504
- });
505
- task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_JOINED, task);
506
- break;
507
- }
508
- case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE:
509
- {
510
- // Conference ended - update task state and emit event
511
-
512
- task = this.updateTaskData(task, {
513
- ...payload.data,
514
- isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload.data)
515
- });
516
- if ((0, _TaskUtils.checkParticipantNotInInteraction)(task, this.agentId)) {
517
- if ((0, _TaskUtils.isParticipantInMainInteraction)(task, this.agentId) || (0, _TaskUtils.isPrimary)(task, this.agentId)) {
518
- _loggerProxy.default.log('Primary or main interaction participant leaving conference');
519
- } else {
520
- this.removeTaskFromCollection(task);
521
- }
522
- }
523
- task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_LEFT, task);
524
- break;
525
- }
526
- case _types2.CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE_FAILED:
527
- // Conference exit failed - update task state and emit failure event
528
- task = this.updateTaskData(task, payload.data);
529
- task.emit(_types.TASK_EVENTS.TASK_PARTICIPANT_LEFT_FAILED, task);
530
- break;
531
- case _types2.CC_EVENTS.AGENT_CONSULT_CONFERENCE_END_FAILED:
532
- // Conference end failed - update task state with error details and emit failure event
533
- task = this.updateTaskData(task, payload.data);
534
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_END_FAILED, task);
535
- break;
536
- case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFERRED:
537
- // Conference was transferred - update task state and emit transfer success event
538
- // Note: Backend should provide hasLeft and wrapUpRequired status
539
- task = this.updateTaskData(task, payload.data);
540
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_TRANSFERRED, task);
541
- break;
542
- case _types2.CC_EVENTS.AGENT_CONFERENCE_TRANSFER_FAILED:
543
- // Conference transfer failed - update task state with error details and emit failure event
544
- task = this.updateTaskData(task, payload.data);
545
- task.emit(_types.TASK_EVENTS.TASK_CONFERENCE_TRANSFER_FAILED, task);
546
- break;
547
- case _types2.CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY:
548
- // Post-call activity for participant - update task state with activity details
549
- task = this.updateTaskData(task, payload.data);
550
- task.emit(_types.TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
551
- break;
552
- case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
553
- {
554
- // Campaign preview contact offered to agent
555
- // Create a task in the collection so subsequent events (e.g. AGENT_CONTACT_ASSIGNED
556
- // after acceptPreviewContact) can find and update it.
557
- // Emit TASK_CAMPAIGN_PREVIEW_RESERVATION instead of TASK_INCOMING so the call
558
- // does not ring out to the customer before the agent explicitly accepts the preview contact.
559
- _loggerProxy.default.log('Campaign preview reservation received', {
560
- module: _constants.TASK_MANAGER_FILE,
561
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
562
- interactionId: payload.data.interactionId
563
- });
564
- if (!this.taskCollection[payload.data.interactionId]) {
565
- task = new _.default(this.contact, this.webCallingService, {
566
- ...payload.data,
567
- wrapUpRequired: false,
568
- isConferenceInProgress: false,
569
- isAutoAnswering: false
570
- }, this.wrapupData, this.agentId);
571
- this.taskCollection[payload.data.interactionId] = task;
572
- } else {
573
- task = this.updateTaskData(task, payload.data);
574
- }
575
- this.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, task);
576
- break;
577
- }
578
- default:
579
- break;
580
- }
581
- if (task) {
582
- task.emit(payload.data.type, payload.data);
583
- }
584
- const transcriptInteractionId = payload.data?.interactionId || payload.data?.data?.conversationId || task?.data?.interactionId;
585
- if (_constants2.TRANSCRIPT_EVENT_MAP[payload.data.type] && transcriptInteractionId) {
586
- this.requestRealTimeTranscripts(payload.data.type, transcriptInteractionId);
587
- }
404
+ // Step 1: Parse and validate the message
405
+ const message = TaskManager.parseWebSocketMessage(event);
406
+ if (!message) return;
407
+
408
+ // Step 2: Prepare event context
409
+ const eventContext = this.prepareEventContext(message);
410
+ if (!eventContext) return;
411
+ const actions = this.handleTaskLifecycleEvent(eventContext);
412
+ const {
413
+ task
414
+ } = actions;
415
+ if (!task) return;
416
+ const {
417
+ payload,
418
+ stateMachineEvent
419
+ } = eventContext;
420
+
421
+ // Always keep task.data updated (even for mapped events) so consumers relying
422
+ // on TaskManager-managed task instances see the latest payload.
423
+ if (payload) {
424
+ this.updateTaskData(task, payload);
425
+ }
426
+
427
+ // Send event to state machine - this will trigger all TASK_EVENTS emissions
428
+ // including TASK_INCOMING which is now handled via the state machine callbacks
429
+ if (stateMachineEvent) {
430
+ task.sendStateMachineEvent(stateMachineEvent);
431
+ }
432
+
433
+ // Emit TASK_POST_CALL_ACTIVITY for ParticipantPostCallActivity events so
434
+ // consumers (Widgets) can detect the interaction state change to post_call.
435
+ if (eventContext.eventType === _types2.CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY) {
436
+ task.emit(_types.TASK_EVENTS.TASK_POST_CALL_ACTIVITY, task);
588
437
  }
438
+
439
+ // Send transcript start/stop events for relevant CC events
440
+ this.requestRealTimeTranscripts(eventContext.eventType, payload.interactionId);
589
441
  });
590
442
  }
591
- updateTaskData(task, taskData) {
592
- if (!task) {
593
- return undefined;
594
- }
595
- if (!taskData?.interactionId) {
596
- _loggerProxy.default.warn('Received task update with missing interactionId', {
597
- module: _constants.TASK_MANAGER_FILE,
598
- method: _constants2.METHODS.UPDATE_TASK_DATA
599
- });
600
- }
443
+
444
+ /**
445
+ * Parse and validate WebSocket message
446
+ * @returns Parsed message or null if invalid/keepalive
447
+ */
448
+ static parseWebSocketMessage(event) {
601
449
  try {
602
- const currentTask = task.updateTaskData(taskData);
603
- this.taskCollection[taskData.interactionId] = currentTask;
604
- return currentTask;
450
+ const payload = JSON.parse(event);
451
+
452
+ // Filter out keepalive messages
453
+ if (payload?.keepalive === 'true' || payload?.keepalive === true) {
454
+ return null;
455
+ }
456
+
457
+ // Normalize task data if present
458
+ if (payload?.data?.interaction) {
459
+ payload.data = (0, _taskDataNormalizer.normalizeTaskData)(payload.data);
460
+ }
461
+ return payload;
605
462
  } catch (error) {
606
- _loggerProxy.default.error(`Failed to update task`, {
463
+ _loggerProxy.default.error('Failed to parse WebSocket message', {
607
464
  module: _constants.TASK_MANAGER_FILE,
608
- method: _constants2.METHODS.UPDATE_TASK_DATA,
609
- interactionId: taskData.interactionId
465
+ method: 'parseWebSocketMessage',
466
+ error
610
467
  });
611
- return task;
468
+ return null;
612
469
  }
613
470
  }
614
471
 
615
472
  /**
616
- * Handles CONTACT_MERGED event logic
617
- * @param task - The task to process
618
- * @param taskData - The task data from the event payload
619
- * @returns Updated or newly created task
620
- * @private
473
+ * Prepare context for event processing
474
+ * @returns Event context or null if event type is invalid
621
475
  */
622
- handleContactMerged(task, taskData) {
623
- if (taskData.childInteractionId) {
624
- // remove the child task from collection
625
- this.removeTaskFromCollection(this.taskCollection[taskData.childInteractionId]);
476
+ prepareEventContext(message) {
477
+ const eventType = message.data?.type || message.type;
478
+ if (!eventType || !isCcEvent(eventType)) {
479
+ return null;
626
480
  }
627
- if (this.taskCollection[taskData.interactionId]) {
628
- _loggerProxy.default.log(`Got CONTACT_MERGED: Task already exists in collection`, {
481
+ const interactionId = message.data.interactionId;
482
+ const reservationInteractionId = message.data.reservationInteractionId;
483
+ let task = this.taskCollection[interactionId];
484
+
485
+ // When a campaign preview contact is accepted, the assigned event may arrive
486
+ // with a new interactionId while the task is stored under the original
487
+ // reservationInteractionId. Fall back to that key so the task is found.
488
+ if (!task && reservationInteractionId) {
489
+ task = this.taskCollection[reservationInteractionId];
490
+ if (task) {
491
+ // Re-key the task under the new interaction ID and remove the old entry
492
+ delete this.taskCollection[reservationInteractionId];
493
+ this.taskCollection[interactionId] = task;
494
+ }
495
+ }
496
+ const wasConsultedTask = Boolean(task?.data?.isConsulted);
497
+ const computeWrapUpRequired = () => {
498
+ if (message.data.wrapUpRequired !== undefined) {
499
+ return message.data.wrapUpRequired;
500
+ }
501
+ if (message.data.isConsulted !== undefined) {
502
+ return !message.data.isConsulted;
503
+ }
504
+ return !wasConsultedTask;
505
+ };
506
+ const adjustedPayload = eventType === _types2.CC_EVENTS.AGENT_CONSULT_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_BLIND_TRANSFERRED || eventType === _types2.CC_EVENTS.AGENT_VTEAM_TRANSFERRED ? {
507
+ ...message.data,
508
+ wrapUpRequired: computeWrapUpRequired()
509
+ } : message.data;
510
+ const stateMachineEvent = TaskManager.mapEventToTaskStateMachineEvent(eventType, adjustedPayload, this.agentId);
511
+ _loggerProxy.default.info(`Handling task event ${eventType}`, {
512
+ module: _constants.TASK_MANAGER_FILE,
513
+ method: 'prepareEventContext',
514
+ interactionId
515
+ });
516
+ return {
517
+ eventType,
518
+ payload: adjustedPayload,
519
+ task,
520
+ stateMachineEvent
521
+ };
522
+ }
523
+
524
+ /**
525
+ * Handle task lifecycle events and determine required actions
526
+ *
527
+ * Delegates to specific event handlers based on event type. Each handler
528
+ * is responsible for TaskManager-level concerns:
529
+ * - Task creation and collection management
530
+ * - Metrics tracking
531
+ * - Resource cleanup decisions
532
+ *
533
+ * Note: Task-level state transitions and event emissions are handled by
534
+ * the task state machine via sendStateMachineEvent()
535
+ */
536
+ handleTaskLifecycleEvent(context) {
537
+ const {
538
+ eventType
539
+ } = context;
540
+ switch (eventType) {
541
+ case _types2.CC_EVENTS.AGENT_CONTACT_RESERVED:
542
+ return this.handleContactReserved(context);
543
+ case _types2.CC_EVENTS.AGENT_CONTACT:
544
+ return this.handleAgentContact(context);
545
+ case _types2.CC_EVENTS.CONTACT_MERGED:
546
+ return this.handleContactMergedEvent(context);
547
+ case _types2.CC_EVENTS.AGENT_OFFER_CAMPAIGN_RESERVATION:
548
+ return this.handleCampaignPreviewReservation(context);
549
+ case _types2.CC_EVENTS.CAMPAIGN_CONTACT_UPDATED:
550
+ return this.handleCampaignContactUpdated(context);
551
+ default:
552
+ return {
553
+ task: context.task
554
+ };
555
+ }
556
+ }
557
+ handleCampaignContactUpdated(context) {
558
+ const {
559
+ payload
560
+ } = context;
561
+ let {
562
+ task
563
+ } = context;
564
+ // CampaignContactUpdated is a non-terminal event (e.g., next contact after skip/remove).
565
+ // Update the task data and emit an event so consumers can react to the updated contact.
566
+ // Do NOT remove the task or emit TASK_END — cleanup is handled by CONTACT_ENDED.
567
+ if (task) {
568
+ // Carry forward campaign preview fields from existing task data since the updated
569
+ // contact payload may not include them, and reconcileData would delete them.
570
+ const existingCpd = task.data?.interaction?.callProcessingDetails;
571
+ const updatedData = {
572
+ ...payload
573
+ };
574
+ if (existingCpd) {
575
+ const campaignFields = {
576
+ ...(existingCpd.campaignPreviewAutoAction && {
577
+ campaignPreviewAutoAction: existingCpd.campaignPreviewAutoAction
578
+ }),
579
+ ...(existingCpd.campaignPreviewOfferTimeout && {
580
+ campaignPreviewOfferTimeout: existingCpd.campaignPreviewOfferTimeout
581
+ }),
582
+ ...(existingCpd.campaignPreviewSkipDisabled && {
583
+ campaignPreviewSkipDisabled: existingCpd.campaignPreviewSkipDisabled
584
+ }),
585
+ ...(existingCpd.campaignPreviewRemoveDisabled && {
586
+ campaignPreviewRemoveDisabled: existingCpd.campaignPreviewRemoveDisabled
587
+ })
588
+ };
589
+ if (!updatedData.interaction) {
590
+ updatedData.interaction = {};
591
+ }
592
+ updatedData.interaction = {
593
+ ...updatedData.interaction,
594
+ callProcessingDetails: {
595
+ ...campaignFields,
596
+ ...(updatedData.interaction.callProcessingDetails || {})
597
+ }
598
+ };
599
+ }
600
+ _loggerProxy.default.log('Campaign contact updated - carrying forward preview fields', {
629
601
  module: _constants.TASK_MANAGER_FILE,
630
602
  method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
631
- interactionId: taskData.interactionId
603
+ interactionId: payload.interactionId,
604
+ data: {
605
+ hasCpd: !!updatedData.interaction?.callProcessingDetails,
606
+ autoAction: updatedData.interaction?.callProcessingDetails?.campaignPreviewAutoAction,
607
+ skipDisabled: updatedData.interaction?.callProcessingDetails?.campaignPreviewSkipDisabled,
608
+ removeDisabled: updatedData.interaction?.callProcessingDetails?.campaignPreviewRemoveDisabled
609
+ }
632
610
  });
633
- // update the task data
634
- task = this.updateTaskData(task, taskData);
611
+ task = this.updateTaskData(task, updatedData);
612
+ task.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_CONTACT_UPDATED, task);
613
+ }
614
+ return {
615
+ task
616
+ };
617
+ }
618
+
619
+ /**
620
+ * Creates or updates a task for campaign preview reservation.
621
+ * TASK_CAMPAIGN_PREVIEW_RESERVATION is emitted by the state machine (campaign TASK_INCOMING branch).
622
+ */
623
+ handleCampaignPreviewReservation(context) {
624
+ const {
625
+ payload
626
+ } = context;
627
+ let {
628
+ task
629
+ } = context;
630
+ _loggerProxy.default.log('Campaign preview reservation received', {
631
+ module: _constants.TASK_MANAGER_FILE,
632
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
633
+ interactionId: payload.interactionId
634
+ });
635
+ if (!task) {
636
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, {
637
+ ...payload,
638
+ wrapUpRequired: false,
639
+ isConferenceInProgress: false,
640
+ isAutoAnswering: false
641
+ }, this.configFlags, this.wrapupData, this.agentId);
642
+ this.setupTaskListeners(task);
643
+ this.taskCollection[payload.interactionId] = task;
635
644
  } else {
636
- // Case2 : Task is not present in taskCollection
637
- _loggerProxy.default.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
645
+ task = this.updateTaskData(task, payload);
646
+ }
647
+ return {
648
+ task
649
+ };
650
+ }
651
+
652
+ /**
653
+ * Handle AGENT_CONTACT_RESERVED event
654
+ * Creates a new task; state machine event is sent during processing
655
+ */
656
+ handleContactReserved(context) {
657
+ const {
658
+ payload
659
+ } = context;
660
+ const isConsultedTask = payload.isConsulted === true || (0, _TaskUtils.isSecondaryEpDnAgent)(payload.interaction);
661
+ const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
662
+ const taskData = {
663
+ ...payload,
664
+ isConsulted: isConsultedTask,
665
+ isAutoAnswering: shouldAutoAnswer
666
+ };
667
+ const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
668
+ this.setupTaskListeners(task);
669
+ this.taskCollection[payload.interactionId] = task;
670
+ return {
671
+ task
672
+ };
673
+ }
674
+
675
+ /**
676
+ * Handle AGENT_CONTACT event
677
+ * Re-creates task if missing (multi-session scenario)
678
+ */
679
+ handleAgentContact(context) {
680
+ let {
681
+ task
682
+ } = context;
683
+ const {
684
+ payload
685
+ } = context;
686
+ if (!task) {
687
+ const isConsultedTask = payload.isConsulted === true || (0, _TaskUtils.isSecondaryEpDnAgent)(payload.interaction);
688
+ const shouldAutoAnswer = (0, _TaskUtils.shouldAutoAnswerTask)(payload, this.agentId, this.webCallingService.loginOption, this.webRtcEnabled);
689
+ const taskData = {
690
+ ...payload,
691
+ isConsulted: isConsultedTask,
692
+ wrapUpRequired: payload.interaction?.participants?.[this.agentId]?.isWrapUp || false,
693
+ isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
694
+ isAutoAnswering: shouldAutoAnswer
695
+ };
696
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
697
+ this.setupTaskListeners(task);
698
+ this.taskCollection[payload.interactionId] = task;
699
+ }
700
+ return {
701
+ task
702
+ };
703
+ }
704
+ updateTaskData(task, taskData) {
705
+ if (!task) {
706
+ throw new Error('Task not found for update');
707
+ }
708
+ const snapshot = task.stateMachineService?.getSnapshot?.();
709
+ const isConsultingFlow = snapshot?.value === 'CONSULTING' || taskData.interaction?.state === 'consulting';
710
+ const updateTaskData = isConsultingFlow ? {
711
+ ...taskData,
712
+ destAgentId: taskData.destAgentId ?? snapshot?.context?.consultDestinationAgentId ?? null,
713
+ destinationType: taskData.destinationType ?? snapshot?.context?.consultDestinationType ?? null
714
+ } : taskData;
715
+ task.updateTaskData(updateTaskData);
716
+ this.taskCollection[taskData.interactionId] = task;
717
+ return task;
718
+ }
719
+
720
+ /**
721
+ * Setup listeners for task events that need to be bubbled up to TaskManager
722
+ * This replaces the previous callback injection pattern
723
+ */
724
+ setupTaskListeners(task) {
725
+ // Listen for TASK_INCOMING and re-emit so webex.cc can notify consumers
726
+ task.on(_types.TASK_EVENTS.TASK_INCOMING, t => {
727
+ _loggerProxy.default.log(`Task incoming event received`, {
638
728
  module: _constants.TASK_MANAGER_FILE,
639
729
  method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
640
- interactionId: taskData.interactionId
730
+ interactionId: t.data?.interactionId
641
731
  });
642
- task = new _.default(this.contact, this.webCallingService, {
643
- ...taskData,
644
- wrapUpRequired: taskData.interaction?.participants?.[this.agentId]?.isWrapUp || false,
645
- isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(taskData)
646
- }, this.wrapupData, this.agentId);
647
- this.taskCollection[taskData.interactionId] = task;
648
- }
649
- this.emit(_types.TASK_EVENTS.TASK_MERGED, task);
650
- return task;
732
+ this.emit(_types.TASK_EVENTS.TASK_INCOMING, t);
733
+ });
734
+ task.on(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, t => {
735
+ _loggerProxy.default.log(`Campaign preview reservation event received`, {
736
+ module: _constants.TASK_MANAGER_FILE,
737
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
738
+ interactionId: t.data?.interactionId
739
+ });
740
+ this.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, t);
741
+ });
742
+
743
+ // Listen for TASK_HYDRATE on the task and re-emit on TaskManager
744
+ task.on(_types.TASK_EVENTS.TASK_HYDRATE, t => {
745
+ // Task data is already updated by the task itself before emitting
746
+ this.emit(_types.TASK_EVENTS.TASK_HYDRATE, t);
747
+ });
748
+ task.on(_types.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, t => {
749
+ this.emit(_types.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, t);
750
+ });
751
+
752
+ // Listen for internal cleanup signal emitted by the state machine
753
+ task.on(_types.TASK_EVENTS.TASK_CLEANUP, (t, options) => {
754
+ this.handleTaskCleanup(t);
755
+ if (options?.removeFromCollection) {
756
+ const interactionId = t?.data?.interactionId;
757
+ if (interactionId && this.taskCollection[interactionId]) {
758
+ this.removeTaskFromCollection(t);
759
+ }
760
+ }
761
+ });
651
762
  }
652
763
  removeTaskFromCollection(task) {
764
+ if (typeof task.cancelAutoWrapupTimer === 'function') {
765
+ task.cancelAutoWrapupTimer();
766
+ }
653
767
  if (task?.data?.interactionId) {
654
768
  delete this.taskCollection[task.data.interactionId];
655
769
  _loggerProxy.default.info(`Task removed from collection`, {
@@ -661,62 +775,62 @@ class TaskManager extends _events.default {
661
775
  }
662
776
 
663
777
  /**
664
- * Handles auto-answer logic for incoming tasks
665
- * Automatically accepts tasks when isAutoAnswering flag is set
666
- * The flag is set during task creation based on:
667
- * 1. WebRTC calls with auto-answer enabled in agent profile
668
- * 2. Agent-initiated WebRTC outdial calls
669
- * 3. Agent-initiated digital outbound (Email/SMS) without previous transfers
670
- *
671
- * @param task - The task to auto-answer
778
+ * Handles CONTACT_MERGED event logic
779
+ * @param task - The task to process
780
+ * @param taskData - The task data from the event payload
781
+ * @returns Updated or newly created task
672
782
  * @private
673
783
  */
674
- async handleAutoAnswer(task) {
675
- if (!task || !task.data || !task.data.isAutoAnswering) {
676
- return;
784
+ handleContactMergedEvent(context) {
785
+ const {
786
+ payload
787
+ } = context;
788
+ let task = context.task;
789
+ if (payload.childInteractionId && this.taskCollection[payload.childInteractionId]) {
790
+ this.removeTaskFromCollection(this.taskCollection[payload.childInteractionId]);
677
791
  }
678
- _loggerProxy.default.info(`Auto-answering task`, {
679
- module: _constants.TASK_MANAGER_FILE,
680
- method: 'handleAutoAnswer',
681
- interactionId: task.data.interactionId
682
- });
683
- try {
684
- await task.accept();
685
- _loggerProxy.default.info(`Task auto-answered successfully`, {
792
+ if (task) {
793
+ _loggerProxy.default.log(`Got CONTACT_MERGED: Task already exists in collection`, {
686
794
  module: _constants.TASK_MANAGER_FILE,
687
- method: 'handleAutoAnswer',
688
- interactionId: task.data.interactionId
689
- });
690
-
691
- // Track successful auto-answer
692
- this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_SUCCESS, {
693
- taskId: task.data.interactionId,
694
- mediaType: task.data.interaction.mediaType,
695
- isAutoAnswered: true
696
- }, ['behavioral', 'operational']);
697
- // Emit task:autoAnswered event for widgets/UI to react
698
- task.emit(_types.TASK_EVENTS.TASK_AUTO_ANSWERED, task);
699
- } catch (error) {
700
- // Reset isAutoAnswering flag on failure
701
- task.updateTaskData({
702
- ...task.data,
703
- isAutoAnswering: false
795
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
796
+ interactionId: payload.interactionId
704
797
  });
705
- _loggerProxy.default.error(`Failed to auto-answer task`, {
798
+ // update the task data
799
+ this.updateTaskData(task, payload);
800
+ } else {
801
+ // Case2 : Task is not present in taskCollection
802
+ _loggerProxy.default.log(`Got CONTACT_MERGED : Creating new task in taskManager`, {
706
803
  module: _constants.TASK_MANAGER_FILE,
707
- method: 'handleAutoAnswer',
708
- interactionId: task.data.interactionId,
709
- error
804
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
805
+ interactionId: payload.interactionId
710
806
  });
807
+ const taskData = {
808
+ ...payload,
809
+ wrapUpRequired: payload.interaction?.participants?.[this.agentId]?.isWrapUp || false,
810
+ isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
811
+ isConsulted: false
812
+ };
813
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId);
814
+ this.taskCollection[payload.interactionId] = task;
711
815
 
712
- // Track auto-answer failure
713
- this.metricsManager.trackEvent(_constants3.METRIC_EVENT_NAMES.TASK_AUTO_ANSWER_FAILED, {
714
- taskId: task.data.interactionId,
715
- mediaType: task.data.interaction.mediaType,
716
- error: error?.message || 'Unknown error',
717
- isAutoAnswered: false
718
- }, ['behavioral', 'operational']);
816
+ // Transition the new task out of IDLE immediately so UI controls are
817
+ // computed before TASK_MERGED is emitted. This handles the race where
818
+ // AgentContactAssigned arrives before ContactMerged and gets dropped.
819
+ // Send HYDRATE before setupTaskListeners so the emitTaskHydrate action
820
+ // doesn't bubble up to the Widget (avoids duplicate listener registration).
821
+ task.sendStateMachineEvent({
822
+ type: _stateMachine.TaskEvent.HYDRATE,
823
+ taskData,
824
+ agentId: this.agentId
825
+ });
826
+ this.setupTaskListeners(task);
719
827
  }
828
+ if (task) {
829
+ this.emit(_types.TASK_EVENTS.TASK_MERGED, task);
830
+ }
831
+ return {
832
+ task
833
+ };
720
834
  }
721
835
 
722
836
  /**
@@ -726,7 +840,7 @@ class TaskManager extends _events.default {
726
840
  */
727
841
  handleTaskCleanup(task) {
728
842
  // Clean up Desktop/WebRTC calling resources for browser-based telephony tasks
729
- if (this.webCallingService.loginOption === _types3.LoginOption.BROWSER && task.data.interaction.mediaType === 'telephony') {
843
+ if (this.webCallingService.loginOption === _types3.LoginOption.BROWSER && task.data.interaction.mediaType === _types.MEDIA_CHANNEL.TELEPHONY && task instanceof _WebRTC.default) {
730
844
  task.unregisterWebCallListeners();
731
845
  this.webCallingService.cleanUpCall();
732
846
  }
@@ -749,11 +863,12 @@ class TaskManager extends _events.default {
749
863
  */
750
864
  requestRealTimeTranscripts(eventType, interactionId) {
751
865
  const action = _constants2.TRANSCRIPT_EVENT_MAP[eventType];
752
- if (!action || !this.apiAIAssistant || this.apiAIAssistant.aiFeature?.realtimeTranscripts?.enable === false) return;
866
+ if (!action || !this.apiAIAssistant) return;
867
+ if (this.configFlags?.aiFeature?.realtimeTranscripts?.enable !== true) return;
753
868
  this.apiAIAssistant.sendEvent(this.agentId, interactionId, _types3.AIAssistantEventType.CUSTOM_EVENT, _types3.AIAssistantEventName.GET_TRANSCRIPTS, action).catch(error => {
754
869
  _loggerProxy.default.error(`Failed to send transcript ${action} event`, {
755
870
  module: _constants.TASK_MANAGER_FILE,
756
- method: 'requestRealTimeTranscripts',
871
+ method: _constants2.METHODS.REQUEST_REAL_TIME_TRANSCRIPTS,
757
872
  interactionId,
758
873
  error
759
874
  });
@@ -762,18 +877,16 @@ class TaskManager extends _events.default {
762
877
  getTask(taskId) {
763
878
  return this.taskCollection[taskId];
764
879
  }
765
-
766
- /**
767
- * @param taskId - Unique identifier for each task
768
- */
769
- getAllTasks = () => {
770
- return this.taskCollection;
771
- };
772
- static getTaskManager(apiAIAssistant, contact, webCallingService, webSocketManager) {
880
+ getAllTasks() {
881
+ return {
882
+ ...this.taskCollection
883
+ };
884
+ }
885
+ static getTaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager) {
773
886
  if (!TaskManager.taskManager) {
774
- TaskManager.taskManager = new TaskManager(apiAIAssistant, contact, webCallingService, webSocketManager);
887
+ TaskManager.taskManager = new TaskManager(apiAIAssistant, contact, webCallingService, webSocketManager, rtdWebSocketManager);
775
888
  }
776
- return this.taskManager;
889
+ return TaskManager.taskManager;
777
890
  }
778
891
  }
779
892
  exports.default = TaskManager;