@webex/contact-center 3.12.0-next.147 → 3.12.0-next.149

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 (82) hide show
  1. package/.sdd/manifest.json +4 -2
  2. package/ai-docs/CONTRACTS.md +2 -0
  3. package/dist/cc.js +3 -1
  4. package/dist/cc.js.map +1 -1
  5. package/dist/constants.js +10 -1
  6. package/dist/constants.js.map +1 -1
  7. package/dist/index.js +9 -2
  8. package/dist/index.js.map +1 -1
  9. package/dist/metrics/constants.js +9 -0
  10. package/dist/metrics/constants.js.map +1 -1
  11. package/dist/services/ApiAiAssistant.js +87 -10
  12. package/dist/services/ApiAiAssistant.js.map +1 -1
  13. package/dist/services/config/types.js +10 -4
  14. package/dist/services/config/types.js.map +1 -1
  15. package/dist/services/core/types.js.map +1 -1
  16. package/dist/services/task/Task.js +202 -1
  17. package/dist/services/task/Task.js.map +1 -1
  18. package/dist/services/task/TaskFactory.js +4 -4
  19. package/dist/services/task/TaskFactory.js.map +1 -1
  20. package/dist/services/task/TaskManager.js +113 -26
  21. package/dist/services/task/TaskManager.js.map +1 -1
  22. package/dist/services/task/TaskUtils.js +11 -1
  23. package/dist/services/task/TaskUtils.js.map +1 -1
  24. package/dist/services/task/constants.js +11 -2
  25. package/dist/services/task/constants.js.map +1 -1
  26. package/dist/services/task/digital/Digital.js +2 -2
  27. package/dist/services/task/digital/Digital.js.map +1 -1
  28. package/dist/services/task/types.js +31 -0
  29. package/dist/services/task/types.js.map +1 -1
  30. package/dist/services/task/voice/Voice.js +2 -2
  31. package/dist/services/task/voice/Voice.js.map +1 -1
  32. package/dist/services/task/voice/WebRTC.js +2 -2
  33. package/dist/services/task/voice/WebRTC.js.map +1 -1
  34. package/dist/types/constants.d.ts +9 -0
  35. package/dist/types/index.d.ts +4 -5
  36. package/dist/types/metrics/constants.d.ts +8 -0
  37. package/dist/types/services/ApiAiAssistant.d.ts +4 -2
  38. package/dist/types/services/config/types.d.ts +17 -7
  39. package/dist/types/services/task/Task.d.ts +15 -4
  40. package/dist/types/services/task/TaskFactory.d.ts +3 -2
  41. package/dist/types/services/task/TaskUtils.d.ts +6 -0
  42. package/dist/types/services/task/constants.d.ts +9 -0
  43. package/dist/types/services/task/digital/Digital.d.ts +3 -2
  44. package/dist/types/services/task/types.d.ts +109 -1
  45. package/dist/types/services/task/voice/Voice.d.ts +3 -2
  46. package/dist/types/services/task/voice/WebRTC.d.ts +3 -2
  47. package/dist/types/types.d.ts +10 -4
  48. package/dist/types.js +8 -4
  49. package/dist/types.js.map +1 -1
  50. package/dist/webex.js +1 -1
  51. package/package.json +3 -3
  52. package/src/cc.ts +4 -0
  53. package/src/constants.ts +12 -0
  54. package/src/index.ts +10 -2
  55. package/src/metrics/ai-docs/metrics-spec.md +12 -4
  56. package/src/metrics/constants.ts +10 -0
  57. package/src/services/ApiAiAssistant.ts +120 -12
  58. package/src/services/config/types.ts +10 -4
  59. package/src/services/core/types.ts +31 -1
  60. package/src/services/task/Task.ts +308 -5
  61. package/src/services/task/TaskFactory.ts +26 -5
  62. package/src/services/task/TaskManager.ts +148 -29
  63. package/src/services/task/TaskUtils.ts +13 -0
  64. package/src/services/task/ai-docs/task-spec.md +30 -5
  65. package/src/services/task/constants.ts +10 -0
  66. package/src/services/task/digital/Digital.ts +8 -3
  67. package/src/services/task/types.ts +139 -0
  68. package/src/services/task/voice/Voice.ts +8 -3
  69. package/src/services/task/voice/WebRTC.ts +6 -3
  70. package/src/types.ts +10 -4
  71. package/test/unit/fixtures/aiSummaryTestUtils.ts +40 -0
  72. package/test/unit/spec/cc.ts +1314 -32
  73. package/test/unit/spec/index.ts +881 -0
  74. package/test/unit/spec/services/ApiAiAssistant.ts +122 -6
  75. package/test/unit/spec/services/core/WebexRequest.ts +5 -6
  76. package/test/unit/spec/services/task/Task.ts +1988 -10
  77. package/test/unit/spec/services/task/TaskFactory.ts +17 -0
  78. package/test/unit/spec/services/task/TaskManager.ts +2327 -180
  79. package/test/unit/spec/services/task/TaskUtils.ts +72 -0
  80. package/test/unit/spec/services/task/voice/Voice.ts +353 -18
  81. package/umd/contact-center.min.js +2 -2
  82. package/umd/contact-center.min.js.map +1 -1
@@ -36,6 +36,11 @@ class TaskManager extends _events.default {
36
36
 
37
37
  // eslint-disable-next-line no-use-before-define
38
38
 
39
+ receivingSummaryBuffer = new Map();
40
+
41
+ // RTD feature frames can arrive before AQM creates the task.
42
+ pendingFeatureEnablement = new Map();
43
+
39
44
  /**
40
45
  * @param contact - Routing Contact layer. Talks to AQMReq layer to convert events to promises
41
46
  * @param webCallingService - Webrtc Service Layer
@@ -56,6 +61,50 @@ class TaskManager extends _events.default {
56
61
  handleRealtimeWebsocketEvent(event) {
57
62
  try {
58
63
  const payload = JSON.parse(event);
64
+ switch (payload?.type) {
65
+ case _types2.CC_TASK_EVENTS.FEATURE_ENABLEMENT:
66
+ {
67
+ const enablement = payload?.data?.data;
68
+ if (!enablement?.interactionId) return;
69
+ const task = this.taskCollection[enablement.interactionId];
70
+ if (task) {
71
+ this.pendingFeatureEnablement.delete(enablement.interactionId);
72
+ task.setFeatureEnablement?.(enablement);
73
+ task.emit(_types.TASK_EVENTS.TASK_FEATURE_ENABLEMENT, enablement);
74
+ } else {
75
+ this.pendingFeatureEnablement.set(enablement.interactionId, enablement);
76
+ }
77
+ return;
78
+ }
79
+ case _types2.CC_TASK_EVENTS.POST_CALL_SUMMARY:
80
+ case _types2.CC_TASK_EVENTS.MID_CALL_SUMMARY:
81
+ {
82
+ if (!payload?.data?.data?.conversationId) return;
83
+ this.apiAIAssistant?.resolveFromRtdEvent(payload.type, payload.data.data.conversationId, payload.data.data);
84
+ return;
85
+ }
86
+ case _types2.CC_TASK_EVENTS.MID_CALL_SUMMARY_RESPONSE_SUBSEQUENT_AGENT:
87
+ {
88
+ if (!payload?.data?.data?.conversationId) return;
89
+ const summary = payload.data.data;
90
+ const {
91
+ conversationId
92
+ } = summary;
93
+ const task = this.taskCollection[conversationId];
94
+ const taskState = task?.state?.value;
95
+
96
+ // Emit only once the task is answered; until then it waits for the answer event.
97
+ if (task && taskState !== _stateMachine.TaskState.IDLE && taskState !== _stateMachine.TaskState.OFFERED) {
98
+ this.receivingSummaryBuffer.delete(conversationId);
99
+ task.emit(_types.TASK_EVENTS.TASK_MID_CALL_SUMMARY_RECEIVED, summary);
100
+ } else {
101
+ this.receivingSummaryBuffer.set(conversationId, summary);
102
+ }
103
+ return;
104
+ }
105
+ default:
106
+ break;
107
+ }
59
108
  const interactionId = payload?.data?.data?.conversationId;
60
109
  if (!interactionId) return;
61
110
  const task = this.taskCollection[interactionId];
@@ -72,6 +121,8 @@ class TaskManager extends _events.default {
72
121
  case _types2.CC_EVENTS.SUGGESTED_RESPONSE:
73
122
  task.emit(payload.type, payload.data);
74
123
  break;
124
+ default:
125
+ break;
75
126
  }
76
127
  } catch (error) {
77
128
  _loggerProxy.default.error('Failed to parse RTD WebSocket message', {
@@ -88,6 +139,12 @@ class TaskManager extends _events.default {
88
139
  setConfigFlags(configFlags) {
89
140
  this.configFlags = configFlags;
90
141
  }
142
+ clearAISummaryState() {
143
+ this.apiAIAssistant?.clearAllRtdRequests();
144
+ Object.values(this.taskCollection).forEach(task => task.clearFeatureEnablement?.());
145
+ this.receivingSummaryBuffer.clear();
146
+ this.pendingFeatureEnablement.clear();
147
+ }
91
148
 
92
149
  /**
93
150
  * Set wrapup configuration data
@@ -102,6 +159,9 @@ class TaskManager extends _events.default {
102
159
  setAgentId(agentId) {
103
160
  this.agentId = agentId;
104
161
  }
162
+ setAgentName(agentName) {
163
+ this.agentName = agentName;
164
+ }
105
165
 
106
166
  /**
107
167
  * Gets the current agent ID
@@ -639,6 +699,15 @@ class TaskManager extends _events.default {
639
699
  task.sendStateMachineEvent(stateMachineEvent);
640
700
  }
641
701
 
702
+ // A receiving summary held while the task rang is emitted right after the answer
703
+ // event above (AgentContactAssigned / AgentConsulting) has been emitted.
704
+ const bufferedSummary = this.receivingSummaryBuffer.get(task.data.interactionId);
705
+ const taskState = task.state?.value;
706
+ if (bufferedSummary && taskState !== _stateMachine.TaskState.IDLE && taskState !== _stateMachine.TaskState.OFFERED) {
707
+ this.receivingSummaryBuffer.delete(task.data.interactionId);
708
+ task.emit(_types.TASK_EVENTS.TASK_MID_CALL_SUMMARY_RECEIVED, bufferedSummary);
709
+ }
710
+
642
711
  // Emit TASK_POST_CALL_ACTIVITY for ParticipantPostCallActivity events so
643
712
  // consumers (Widgets) can detect the interaction state change to post_call.
644
713
  if (eventContext.eventType === _types2.CC_EVENTS.PARTICIPANT_POST_CALL_ACTIVITY) {
@@ -701,6 +770,7 @@ class TaskManager extends _events.default {
701
770
  // Re-key the task under the new interaction ID and remove the old entry
702
771
  delete this.taskCollection[reservationInteractionId];
703
772
  this.taskCollection[interactionId] = task;
773
+ this.pendingFeatureEnablement.delete(reservationInteractionId);
704
774
  }
705
775
  }
706
776
  if (!task && eventType === _types2.CC_EVENTS.CONTACT_OWNER_CHANGED) {
@@ -926,7 +996,7 @@ class TaskManager extends _events.default {
926
996
  isConsulted: false,
927
997
  isAutoAnswering: false
928
998
  };
929
- const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
999
+ const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
930
1000
  this.taskCollection[stableInteractionId] = task;
931
1001
 
932
1002
  // Restore the actor before installing listeners so the internal hydrate does
@@ -1030,7 +1100,7 @@ class TaskManager extends _events.default {
1030
1100
  wrapUpRequired: false,
1031
1101
  isConferenceInProgress: false,
1032
1102
  isAutoAnswering: false
1033
- }, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
1103
+ }, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1034
1104
  this.setupTaskListeners(task);
1035
1105
  this.taskCollection[payload.interactionId] = task;
1036
1106
  } else {
@@ -1056,7 +1126,7 @@ class TaskManager extends _events.default {
1056
1126
  isConsulted: isConsultedTask,
1057
1127
  isAutoAnswering: shouldAutoAnswer
1058
1128
  };
1059
- const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
1129
+ const task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1060
1130
  this.setupTaskListeners(task);
1061
1131
  this.taskCollection[payload.interactionId] = task;
1062
1132
  return {
@@ -1085,7 +1155,7 @@ class TaskManager extends _events.default {
1085
1155
  isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
1086
1156
  isAutoAnswering: shouldAutoAnswer
1087
1157
  };
1088
- task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
1158
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1089
1159
  this.setupTaskListeners(task);
1090
1160
  this.taskCollection[payload.interactionId] = task;
1091
1161
  }
@@ -1114,6 +1184,10 @@ class TaskManager extends _events.default {
1114
1184
  }
1115
1185
  });
1116
1186
  this.taskCollection[taskData.interactionId] = task;
1187
+ const enablement = this.pendingFeatureEnablement.get(task.data.interactionId);
1188
+ if (enablement) {
1189
+ task.setFeatureEnablement?.(enablement);
1190
+ }
1117
1191
  return task;
1118
1192
  }
1119
1193
 
@@ -1122,28 +1196,31 @@ class TaskManager extends _events.default {
1122
1196
  * This replaces the previous callback injection pattern
1123
1197
  */
1124
1198
  setupTaskListeners(task) {
1125
- // Listen for TASK_INCOMING and re-emit so webex.cc can notify consumers
1126
- task.on(_types.TASK_EVENTS.TASK_INCOMING, t => {
1127
- _loggerProxy.default.log(`Task incoming event received`, {
1128
- module: _constants.TASK_MANAGER_FILE,
1129
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
1130
- interactionId: t.data?.interactionId
1131
- });
1132
- this.emit(_types.TASK_EVENTS.TASK_INCOMING, t);
1133
- });
1134
- task.on(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, t => {
1135
- _loggerProxy.default.log(`Campaign preview reservation event received`, {
1136
- module: _constants.TASK_MANAGER_FILE,
1137
- method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
1138
- interactionId: t.data?.interactionId
1139
- });
1140
- this.emit(_types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, t);
1141
- });
1199
+ // Apply flags before publication; retain the frame until consumers can attach listeners.
1200
+ const enablement = this.pendingFeatureEnablement.get(task.data.interactionId);
1201
+ if (enablement) {
1202
+ task.setFeatureEnablement?.(enablement);
1203
+ }
1204
+ [_types.TASK_EVENTS.TASK_INCOMING, _types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION, _types.TASK_EVENTS.TASK_HYDRATE, _types.TASK_EVENTS.TASK_ASSIGNED].forEach(eventName => {
1205
+ task.on(eventName, t => {
1206
+ if (eventName === _types.TASK_EVENTS.TASK_INCOMING || eventName === _types.TASK_EVENTS.TASK_CAMPAIGN_PREVIEW_RESERVATION) {
1207
+ _loggerProxy.default.log(eventName === _types.TASK_EVENTS.TASK_INCOMING ? 'Task incoming event received' : 'Campaign preview reservation event received', {
1208
+ module: _constants.TASK_MANAGER_FILE,
1209
+ method: _constants2.METHODS.REGISTER_TASK_LISTENERS,
1210
+ interactionId: t.data?.interactionId
1211
+ });
1212
+ }
1142
1213
 
1143
- // Listen for TASK_HYDRATE on the task and re-emit on TaskManager
1144
- task.on(_types.TASK_EVENTS.TASK_HYDRATE, t => {
1145
- // Task data is already updated by the task itself before emitting
1146
- this.emit(_types.TASK_EVENTS.TASK_HYDRATE, t);
1214
+ // Assignment is already emitted on the task; the other events publish it to consumers.
1215
+ if (eventName !== _types.TASK_EVENTS.TASK_ASSIGNED) {
1216
+ this.emit(eventName, t);
1217
+ }
1218
+ const pendingEnablement = this.pendingFeatureEnablement.get(task.data.interactionId);
1219
+ if (pendingEnablement) {
1220
+ this.pendingFeatureEnablement.delete(task.data.interactionId);
1221
+ task.emit(_types.TASK_EVENTS.TASK_FEATURE_ENABLEMENT, pendingEnablement);
1222
+ }
1223
+ });
1147
1224
  });
1148
1225
  task.on(_types.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, t => {
1149
1226
  this.emit(_types.TASK_EVENTS.TASK_MULTI_LOGIN_HYDRATE, t);
@@ -1161,11 +1238,16 @@ class TaskManager extends _events.default {
1161
1238
  });
1162
1239
  }
1163
1240
  removeTaskFromCollection(task) {
1241
+ task.clearFeatureEnablement?.();
1164
1242
  task.disposeWxAppOfferObservability?.();
1165
1243
  if (typeof task.cancelAutoWrapupTimer === 'function') {
1166
1244
  task.cancelAutoWrapupTimer();
1167
1245
  }
1168
1246
  if (task?.data?.interactionId) {
1247
+ this.pendingFeatureEnablement.delete(task.data.interactionId);
1248
+ // An offer that ends unanswered (RONA, declined, consult cancelled) is removed here,
1249
+ // so its held receiving summary is dropped instead of being emitted.
1250
+ this.receivingSummaryBuffer.delete(task.data.interactionId);
1169
1251
  Object.entries(this.taskCollection).forEach(([taskId, candidate]) => {
1170
1252
  if (candidate === task) {
1171
1253
  delete this.taskCollection[taskId];
@@ -1215,7 +1297,7 @@ class TaskManager extends _events.default {
1215
1297
  isConferenceInProgress: (0, _TaskUtils.getIsConferenceInProgress)(payload),
1216
1298
  isConsulted: false
1217
1299
  };
1218
- task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.answerCallOnWebexService);
1300
+ task = _TaskFactory.default.createTask(this.contact, this.webCallingService, taskData, this.configFlags, this.wrapupData, this.agentId, this.agentName, this.answerCallOnWebexService, this.apiAIAssistant);
1219
1301
  this.taskCollection[payload.interactionId] = task;
1220
1302
 
1221
1303
  // Transition the new task out of IDLE immediately so UI controls are
@@ -1232,6 +1314,11 @@ class TaskManager extends _events.default {
1232
1314
  }
1233
1315
  if (task) {
1234
1316
  this.emit(_types.TASK_EVENTS.TASK_MERGED, task);
1317
+ const enablement = this.pendingFeatureEnablement.get(task.data.interactionId);
1318
+ if (enablement) {
1319
+ this.pendingFeatureEnablement.delete(task.data.interactionId);
1320
+ task.emit(_types.TASK_EVENTS.TASK_FEATURE_ENABLEMENT, enablement);
1321
+ }
1235
1322
  }
1236
1323
  return {
1237
1324
  task