@webex/contact-center 3.12.0-llmrefactor.13 → 3.12.0-llmrefactor.15
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.
- package/.sdd/manifest.json +3 -2
- package/ai-docs/CONTRACTS.md +7 -1
- package/ai-docs/contact-center-spec.md +7 -7
- package/ai-docs/features/contact-owner-change-refactor.md +78 -0
- package/dist/cc.js +39 -7
- package/dist/cc.js.map +1 -1
- package/dist/metrics/behavioral-events.js +6 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +1 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +4 -4
- package/dist/services/AnswerCallOnWebexService.js.map +1 -1
- package/dist/services/WebexCrossClientService.js +5 -2
- package/dist/services/WebexCrossClientService.js.map +1 -1
- package/dist/services/WxAppTelephonyMercurySync.js +6 -3
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -1
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/task/TaskFactory.js +1 -0
- package/dist/services/task/TaskFactory.js.map +1 -1
- package/dist/services/task/TaskManager.js +50 -58
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +63 -44
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/wxAppOfferObservability.js +145 -0
- package/dist/services/task/voice/wxAppOfferObservability.js.map +1 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js +52 -11
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -1
- package/dist/services/wxAppDiagnosticLogging.js +50 -13
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -1
- package/dist/types/metrics/constants.d.ts +1 -0
- package/dist/types/services/config/types.d.ts +2 -0
- package/dist/types/services/task/types.d.ts +2 -0
- package/dist/types/services/task/voice/Voice.d.ts +15 -2
- package/dist/types/services/task/voice/wxAppOfferObservability.d.ts +39 -0
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +19 -6
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +12 -0
- package/dist/types.js.map +1 -1
- package/dist/webex.js +1 -1
- package/package.json +9 -9
- package/src/cc.ts +43 -7
- package/src/metrics/ai-docs/metrics-spec.md +44 -2
- package/src/metrics/behavioral-events.ts +6 -0
- package/src/metrics/constants.ts +1 -0
- package/src/services/AnswerCallOnWebexService.ts +4 -4
- package/src/services/WebexCrossClientService.ts +7 -2
- package/src/services/WxAppTelephonyMercurySync.ts +3 -3
- package/src/services/config/types.ts +2 -0
- package/src/services/task/TaskFactory.ts +1 -0
- package/src/services/task/TaskManager.ts +85 -87
- package/src/services/task/ai-docs/task-spec.md +7 -3
- package/src/services/task/types.ts +16 -0
- package/src/services/task/voice/Voice.ts +74 -52
- package/src/services/task/voice/wxAppOfferObservability.ts +204 -0
- package/src/services/task/voice/wxAppVoiceMethods.ts +83 -24
- package/src/services/wxAppDiagnosticLogging.ts +135 -23
- package/src/types.ts +2 -0
- package/test/unit/spec/cc.ts +201 -19
- package/test/unit/spec/services/WebexCrossClientService.ts +45 -3
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +1 -1
- package/test/unit/spec/services/task/TaskManager.ts +214 -3
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +33 -0
- package/test/unit/spec/services/task/voice/Voice.ts +10 -51
- package/test/unit/spec/services/task/voice/wxAppOfferObservability.ts +361 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +121 -5
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +44 -8
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -246,6 +246,7 @@ All event names are defined in `METRIC_EVENT_NAMES` (`constants.ts`). Events fol
|
|
|
246
246
|
| `WXAPP_SESSION_SKIPPED` | `'WxApp Session Skipped'` | Webex Together session skipped (`wxcc_sdk.user.webex_together_session_init.ignore`) |
|
|
247
247
|
| `WXAPP_USERSUB_PUBLISH_SUCCESS` / `FAILED` | `'WxApp Usersub Publish ...'` | Cross-client usersub publish (`wxcc_sdk.user.webex_together_usersub_publish.complete\|fail`). Preflight failures (missing `userId` or device URL before HTTP publish) emit `WXAPP_USERSUB_PUBLISH_FAILED` with `skipReason` (`user_id_unavailable` / `device_url_unavailable`) and no `timeEvent` timer. Stale-generation discards (teardown or concurrent disable while HTTP publish is in flight) call `cancelTimedEvent` without emitting success/fail only when the completing publish still owns the active timer (`usersubPublishMetricsId` guard — a newer overlapping publish must not have its timer cleared). |
|
|
248
248
|
| `WXAPP_MERCURY_SUBSCRIBE_SUCCESS` / `FAILED` | `'WxApp Mercury Subscribe ...'` | Telephony Mercury mute-sync subscribe (`wxcc_sdk.user.webex_together_mercury_subscribe.complete\|fail`) |
|
|
249
|
+
| `WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING` | `'WxApp Offer Participant Fields Missing'` | Read-only observability when usersub is active but inbound OFFERED participant **still** lacks valid wxApp fields after a **500 ms grace period** (orchestrated in `voice/wxAppOfferObservability.ts`; avoids false alarms from transient WS ordering). After usersub publish, `TaskManager.refreshWxAppOfferObservabilityForAllTasks()` re-runs the observer so a pre-publish OFFERED task can still schedule grace. Task removal calls `disposeWxAppOfferObservability()` so a pending timer cannot emit after `CONTACT_MERGED` / collection drop. Emitted with matching `[CC wxApp] participant fields mismatch` WARN (`wxcc_sdk.user.webex_together_offer_participant_fields.fail`) |
|
|
249
250
|
| `TASK_CONFERENCE_START_SUCCESS` / `FAILED` | `'Task Conference Start ...'` | Conference start result |
|
|
250
251
|
| `TASK_CONFERENCE_END_SUCCESS` / `FAILED` | `'Task Conference End ...'` | Conference end result |
|
|
251
252
|
| `TASK_CONFERENCE_TRANSFER_SUCCESS` / `FAILED` | `'Task Conference Transfer ...'` | Conference transfer result |
|
|
@@ -301,6 +302,7 @@ All event names are defined in `constants.ts` as `METRIC_EVENT_NAMES`. Events fo
|
|
|
301
302
|
| WxApp Session Init | `WXAPP_SESSION_INIT_SUCCESS` | `WXAPP_SESSION_INIT_FAILED` |
|
|
302
303
|
| WxApp Usersub Publish | `WXAPP_USERSUB_PUBLISH_SUCCESS` | `WXAPP_USERSUB_PUBLISH_FAILED` |
|
|
303
304
|
| WxApp Mercury Subscribe | `WXAPP_MERCURY_SUBSCRIBE_SUCCESS` | `WXAPP_MERCURY_SUBSCRIBE_FAILED` |
|
|
305
|
+
| WxApp Offer Participant Fields | — | `WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING` |
|
|
304
306
|
| Upload Logs | `UPLOAD_LOGS_SUCCESS` | `UPLOAD_LOGS_FAILED` |
|
|
305
307
|
| WebSocket Deregister | `WEBSOCKET_DEREGISTER_SUCCESS` | `WEBSOCKET_DEREGISTER_FAIL` |
|
|
306
308
|
| Device Type Update | `AGENT_DEVICE_TYPE_UPDATE_SUCCESS` | `AGENT_DEVICE_TYPE_UPDATE_FAILED` |
|
|
@@ -322,9 +324,48 @@ Webex Together events (`WXAPP_*` constants) use `*_webex_together` compound targ
|
|
|
322
324
|
| Usersub publish | `webex_together_usersub_publish` | `wxcc_sdk.user.webex_together_usersub_publish.complete` | `wxcc_sdk.user.webex_together_usersub_publish.fail` | — |
|
|
323
325
|
| Mercury subscribe | `webex_together_mercury_subscribe` | `wxcc_sdk.user.webex_together_mercury_subscribe.complete` | `wxcc_sdk.user.webex_together_mercury_subscribe.fail` | — |
|
|
324
326
|
| Session init | `webex_together_session_init` | `wxcc_sdk.user.webex_together_session_init.complete` | `wxcc_sdk.user.webex_together_session_init.fail` | `wxcc_sdk.user.webex_together_session_init.ignore` |
|
|
327
|
+
| Offer participant fields | `webex_together_offer_participant_fields` | — | `wxcc_sdk.user.webex_together_offer_participant_fields.fail` | — |
|
|
325
328
|
|
|
326
329
|
Operational and business wire names are unchanged (`WXCC_SDK_WXAPP_*`).
|
|
327
330
|
|
|
331
|
+
### WxApp metric payload fields (operational / behavioral tags)
|
|
332
|
+
|
|
333
|
+
| Event | Payload fields (booleans/enums; no raw IDs) |
|
|
334
|
+
|---|---|
|
|
335
|
+
| `WXAPP_SESSION_INIT_SUCCESS` | `loginOption`, `enableWxBetterTogether`, `usersubPublished`, `mercurySubscribed`, `telephonyTaskType` |
|
|
336
|
+
| `WXAPP_SESSION_INIT_FAILED` | above + `skipReason` (`usersub_not_published`, `mercury_not_subscribed`, `mercury_subscribe_failed`, `publish_failed`) + optional `error`. `usersubPublished` reflects **retained session state** (`isAnswerCallsStateActive()` / `answerCallsState`), including on catch-path failures — not gated on the current init attempt's `publishedEnable`. |
|
|
337
|
+
| `WXAPP_USERSUB_PUBLISH_SUCCESS` | `enableWxBetterTogether`, `usersubPublished` |
|
|
338
|
+
| `WXAPP_USERSUB_PUBLISH_FAILED` | `enableWxBetterTogether`, `usersubPublished`, optional `skipReason`, `error`. `usersubPublished` reflects **retained session state** (`answerCallsState`), not the requested enable value — a failed disable/refresh while usersub remains active reports `true`. |
|
|
339
|
+
| `WXAPP_MERCURY_SUBSCRIBE_SUCCESS` | `mercurySubscribed: true` |
|
|
340
|
+
| `WXAPP_MERCURY_SUBSCRIBE_FAILED` | `mercurySubscribed: false`, `error` |
|
|
341
|
+
| `WXAPP_TASK_ACCEPT_*` / `DECLINE_*` | `taskId`, `hasDeviceCallId`, `hasDeviceId`, optional `acceptReason`; failures add `trackingId` when available. `acceptReason` is captured **before** the telephony REST call (not hard-coded `wxApp_offer_ready`) so success and failure both retain `wxApp_answer_pending` / `wxApp_accept_in_flight` when those flags were already set. Outdial-cancel AQM failures (`error.details`) include the same participant context fields via `getWxAppTelephonyMetricContext` merged with AQM correlation fields. Outdial cancel during post-accept pending phase may report `acceptReason: wxApp_answer_pending`. |
|
|
342
|
+
| `WXAPP_TASK_MUTE_*` / `DTMF_*` | `taskId`, `hasDeviceCallId`, `hasDeviceId`; failures add `trackingId` when available |
|
|
343
|
+
| `WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING` | `taskId`, `usersubPublished`, `hasDeviceCallId`, `hasDeviceId`, `acceptReason` |
|
|
344
|
+
|
|
345
|
+
### Customer console log triage (MMT / support)
|
|
346
|
+
|
|
347
|
+
Customers often embed with `logger.level: 'log'`. wxApp diagnostics use **`LoggerProxy.log`** / **`warn`** / **`error`** with prefix **`[CC wxApp]`** and grep-friendly `key=value` suffixes on the message string (fields also appear in structured `data`).
|
|
348
|
+
|
|
349
|
+
| Search in browser console | Meaning |
|
|
350
|
+
|---|---|
|
|
351
|
+
| `[CC wxApp]` | All wxApp diagnostic lines (primary search) |
|
|
352
|
+
| `WXCC_SDK_WXAPP` | Operational metric wire names (name only in console; payloads go to backend) |
|
|
353
|
+
| `session readiness` | usersub + Mercury state after station login |
|
|
354
|
+
| `offer decision` | Why Accept is visible/enabled; includes `acceptReason`, `hasDeviceCallId`, `usersubPublished` |
|
|
355
|
+
| `participant fields mismatch` | usersub active but WS participant **still** missing wxApp triple after ~500 ms (transient gaps on first OFFERED tick are normal — look for `wxApp_offer_ready` within 1 s) |
|
|
356
|
+
| `telephony accept failed` | REST answer failure; includes `trackingId` when available |
|
|
357
|
+
|
|
358
|
+
Do **not** search `WXAPP` alone — it matches metric wire names but misses `[CC wxApp]` diagnostic lines.
|
|
359
|
+
|
|
360
|
+
Example lines at `logger.level: 'log'`:
|
|
361
|
+
|
|
362
|
+
```text
|
|
363
|
+
PLUGIN_CC - [LOG]: ... [CC wxApp] session readiness usersubPublished=true mercurySubscribed=true loginOption=EXTENSION ...
|
|
364
|
+
PLUGIN_CC - [LOG]: ... [CC wxApp] offer decision acceptReason=wxApp_offer_ready hasDeviceCallId=true usersubPublished=true interactionId=...
|
|
365
|
+
PLUGIN_CC - [WARN]: ... [CC wxApp] participant fields mismatch usersubPublished=true hasDeviceCallId=false acceptReason=extension_non_wxApp_offer interactionId=...
|
|
366
|
+
PLUGIN_CC - [LOG]: ... operational-events -> @submitEvent. Submit event: WXCC_SDK_WXAPP_TASK_ACCEPT_SUCCESS
|
|
367
|
+
```
|
|
368
|
+
|
|
328
369
|
Special events (no success/failure pair):
|
|
329
370
|
|
|
330
371
|
- `AGENT_RONA` — has behavioral taxonomy (`service.agent_rona.set`)
|
|
@@ -337,11 +378,11 @@ Special events (no success/failure pair):
|
|
|
337
378
|
|
|
338
379
|
- `WXAPP_SESSION_SKIPPED` — has behavioral taxonomy (`wxcc_sdk.user.webex_together_session_init.ignore`)
|
|
339
380
|
|
|
340
|
-
Of the
|
|
381
|
+
Of the 113 defined metric names, 97 have behavioral taxonomy and 16 do not. Events **without** an `eventTaxonomyMap` entry are the original eight `AI_ASSISTANT_*` names, the three Agent Wellness Break `AI_ASSISTANT_WELLNESS_*` names, the two `WELLBEING_BREAK_IDLE_CODE_FETCH_*` names, plus `WEBSOCKET_DEREGISTER_SUCCESS`, `WEBSOCKET_DEREGISTER_FAIL`, and `WEBSOCKET_EVENT_RECEIVED`. The Agent Wellness Break names are intentionally operational-only and therefore have no `eventTaxonomyMap` entries.
|
|
341
382
|
|
|
342
383
|
### Complete METRIC_EVENT_NAMES catalog
|
|
343
384
|
|
|
344
|
-
This table contains all
|
|
385
|
+
This table contains all 113 names from `src/metrics/constants.ts`; taxonomy presence is checked against `src/metrics/behavioral-events.ts`: 97 mapped and 16 unmapped.
|
|
345
386
|
|
|
346
387
|
| Constant | Emitted name | Behavioral taxonomy? |
|
|
347
388
|
|---|---|---|
|
|
@@ -415,6 +456,7 @@ This table contains all 116 names from `src/metrics/constants.ts`; taxonomy pres
|
|
|
415
456
|
| `WXAPP_USERSUB_PUBLISH_FAILED` | `WxApp Usersub Publish Failed` | yes |
|
|
416
457
|
| `WXAPP_MERCURY_SUBSCRIBE_SUCCESS` | `WxApp Mercury Subscribe Success` | yes |
|
|
417
458
|
| `WXAPP_MERCURY_SUBSCRIBE_FAILED` | `WxApp Mercury Subscribe Failed` | yes |
|
|
459
|
+
| `WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING` | `WxApp Offer Participant Fields Missing` | yes |
|
|
418
460
|
| `UPLOAD_LOGS_SUCCESS` | `Upload Logs Success` | yes |
|
|
419
461
|
| `UPLOAD_LOGS_FAILED` | `Upload Logs Failed` | yes |
|
|
420
462
|
| `WEBSOCKET_DEREGISTER_SUCCESS` | `Websocket Deregister Success` | no |
|
|
@@ -405,6 +405,12 @@ const eventTaxonomyMap: Record<string, BehavioralEventTaxonomy> = {
|
|
|
405
405
|
target: 'webex_together_mercury_subscribe',
|
|
406
406
|
verb: 'fail',
|
|
407
407
|
},
|
|
408
|
+
[METRIC_EVENT_NAMES.WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING]: {
|
|
409
|
+
product,
|
|
410
|
+
agent: 'user',
|
|
411
|
+
target: 'webex_together_offer_participant_fields',
|
|
412
|
+
verb: 'fail',
|
|
413
|
+
},
|
|
408
414
|
|
|
409
415
|
// Conference Tasks
|
|
410
416
|
[METRIC_EVENT_NAMES.TASK_CONFERENCE_START_SUCCESS]: {
|
package/src/metrics/constants.ts
CHANGED
|
@@ -156,6 +156,7 @@ export const METRIC_EVENT_NAMES = {
|
|
|
156
156
|
WXAPP_USERSUB_PUBLISH_FAILED: 'WxApp Usersub Publish Failed',
|
|
157
157
|
WXAPP_MERCURY_SUBSCRIBE_SUCCESS: 'WxApp Mercury Subscribe Success',
|
|
158
158
|
WXAPP_MERCURY_SUBSCRIBE_FAILED: 'WxApp Mercury Subscribe Failed',
|
|
159
|
+
WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING: 'WxApp Offer Participant Fields Missing',
|
|
159
160
|
|
|
160
161
|
UPLOAD_LOGS_SUCCESS: 'Upload Logs Success',
|
|
161
162
|
UPLOAD_LOGS_FAILED: 'Upload Logs Failed',
|
|
@@ -97,7 +97,7 @@ export default class AnswerCallOnWebexService {
|
|
|
97
97
|
try {
|
|
98
98
|
const uri = `${this.getTelephonyBaseUrl()}${resource}`;
|
|
99
99
|
|
|
100
|
-
LoggerProxy.
|
|
100
|
+
LoggerProxy.log(`AnswerCallOnWebexService.${logMethod} request`, {
|
|
101
101
|
module: ANSWER_CALL_ON_WEBEX_FILE,
|
|
102
102
|
method: logMethod,
|
|
103
103
|
data: {uri, bodyKeys: Object.keys(body)},
|
|
@@ -110,7 +110,7 @@ export default class AnswerCallOnWebexService {
|
|
|
110
110
|
addAuthHeader: true,
|
|
111
111
|
})) as IHttpResponse;
|
|
112
112
|
|
|
113
|
-
LoggerProxy.
|
|
113
|
+
LoggerProxy.log(`AnswerCallOnWebexService.${logMethod} success`, {
|
|
114
114
|
module: ANSWER_CALL_ON_WEBEX_FILE,
|
|
115
115
|
method: logMethod,
|
|
116
116
|
data: {statusCode: response.statusCode},
|
|
@@ -197,10 +197,10 @@ export default class AnswerCallOnWebexService {
|
|
|
197
197
|
const uri = `${this.getTelephonyBaseUrl()}/${callId}${lineOwnerIdQuery}`;
|
|
198
198
|
|
|
199
199
|
try {
|
|
200
|
-
LoggerProxy.
|
|
200
|
+
LoggerProxy.log('AnswerCallOnWebexService.getCallDetails request', {
|
|
201
201
|
module: ANSWER_CALL_ON_WEBEX_FILE,
|
|
202
202
|
method: METHODS.GET_CALL_DETAILS_ON_WEBEX,
|
|
203
|
-
data: {callId},
|
|
203
|
+
data: {callIdSuffix: callId.length <= 8 ? callId : callId.slice(-8)},
|
|
204
204
|
});
|
|
205
205
|
|
|
206
206
|
const response = (await this.webex.request({
|
|
@@ -213,6 +213,7 @@ export default class WebexCrossClientService {
|
|
|
213
213
|
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
214
214
|
{
|
|
215
215
|
enableWxBetterTogether: enable,
|
|
216
|
+
usersubPublished: this.answerCallsState,
|
|
216
217
|
error: error instanceof Error ? error.toString() : String(error),
|
|
217
218
|
},
|
|
218
219
|
['operational', 'behavioral']
|
|
@@ -243,12 +244,15 @@ export default class WebexCrossClientService {
|
|
|
243
244
|
if (trackPublishMetrics) {
|
|
244
245
|
metricsManager.trackEvent(
|
|
245
246
|
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_SUCCESS,
|
|
246
|
-
{
|
|
247
|
+
{
|
|
248
|
+
enableWxBetterTogether: enable,
|
|
249
|
+
usersubPublished: enable,
|
|
250
|
+
},
|
|
247
251
|
['operational', 'behavioral']
|
|
248
252
|
);
|
|
249
253
|
}
|
|
250
254
|
|
|
251
|
-
LoggerProxy.
|
|
255
|
+
LoggerProxy.log(`Cross-client answer-calls-on-wxcc set to ${enable}`, {
|
|
252
256
|
module: WEBEX_CROSS_CLIENT_FILE,
|
|
253
257
|
method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC,
|
|
254
258
|
data: {
|
|
@@ -273,6 +277,7 @@ export default class WebexCrossClientService {
|
|
|
273
277
|
METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED,
|
|
274
278
|
{
|
|
275
279
|
enableWxBetterTogether: enable,
|
|
280
|
+
usersubPublished: this.answerCallsState,
|
|
276
281
|
skipReason,
|
|
277
282
|
error,
|
|
278
283
|
},
|
|
@@ -88,7 +88,7 @@ export default class WxAppTelephonyMercurySync {
|
|
|
88
88
|
|
|
89
89
|
MetricsManager.getInstance().trackEvent(
|
|
90
90
|
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED,
|
|
91
|
-
{error: 'Mercury is unavailable for wxApp mute sync'},
|
|
91
|
+
{error: 'Mercury is unavailable for wxApp mute sync', mercurySubscribed: false},
|
|
92
92
|
['operational', 'behavioral']
|
|
93
93
|
);
|
|
94
94
|
|
|
@@ -110,11 +110,11 @@ export default class WxAppTelephonyMercurySync {
|
|
|
110
110
|
|
|
111
111
|
MetricsManager.getInstance().trackEvent(
|
|
112
112
|
METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_SUCCESS,
|
|
113
|
-
{},
|
|
113
|
+
{mercurySubscribed: true},
|
|
114
114
|
['operational', 'behavioral']
|
|
115
115
|
);
|
|
116
116
|
|
|
117
|
-
LoggerProxy.
|
|
117
|
+
LoggerProxy.log('Subscribed to wxApp telephony Mercury mute sync', {
|
|
118
118
|
module: WXAPP_TELEPHONY_MERCURY_SYNC_FILE,
|
|
119
119
|
method: METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY,
|
|
120
120
|
});
|
|
@@ -1240,6 +1240,8 @@ export type Profile = {
|
|
|
1240
1240
|
maskSensitiveData?: boolean;
|
|
1241
1241
|
/** Whether agent is currently logged in */
|
|
1242
1242
|
isAgentLoggedIn?: boolean;
|
|
1243
|
+
/** Active station session after a successful registered relogin. */
|
|
1244
|
+
agentSessionId?: string;
|
|
1243
1245
|
/** Last auxiliary code ID used for state change */
|
|
1244
1246
|
lastStateAuxCodeId?: string;
|
|
1245
1247
|
/** Timestamp of last state change */
|
|
@@ -31,6 +31,7 @@ export default class TaskFactory {
|
|
|
31
31
|
isRecordingEnabled: recordingEnabled,
|
|
32
32
|
enableWxBetterTogether: configFlags.enableWxBetterTogether ?? false,
|
|
33
33
|
answerCallOnWebexService,
|
|
34
|
+
getUsersubPublished: configFlags.getWxAppUsersubPublished,
|
|
34
35
|
consultTransferConfig: consultTransfer,
|
|
35
36
|
};
|
|
36
37
|
switch (mediaType) {
|
|
@@ -778,6 +778,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
778
778
|
const interactionId = message.data.interactionId;
|
|
779
779
|
const reservationInteractionId = message.data.reservationInteractionId;
|
|
780
780
|
let task = this.taskCollection[interactionId];
|
|
781
|
+
let ownerChangeResolvedByRelatedInteraction = false;
|
|
781
782
|
|
|
782
783
|
// When a campaign preview contact is accepted, the assigned event may arrive
|
|
783
784
|
// with a new interactionId while the task is stored under the original
|
|
@@ -792,7 +793,66 @@ export default class TaskManager extends EventEmitter {
|
|
|
792
793
|
}
|
|
793
794
|
|
|
794
795
|
if (!task && eventType === CC_EVENTS.CONTACT_OWNER_CHANGED) {
|
|
795
|
-
|
|
796
|
+
const incomingShowsCurrentAgentPromotion = Boolean(
|
|
797
|
+
this.agentId &&
|
|
798
|
+
message.data.interaction?.owner === this.agentId &&
|
|
799
|
+
TaskManager.isParticipantActiveOnMainInteraction(message.data, this.agentId)
|
|
800
|
+
);
|
|
801
|
+
|
|
802
|
+
task = this.findUniqueTaskByRelatedInteraction(
|
|
803
|
+
message.data,
|
|
804
|
+
(candidate) => {
|
|
805
|
+
const currentAgentId = this.agentId || candidate.data?.agentId;
|
|
806
|
+
|
|
807
|
+
return Boolean(
|
|
808
|
+
currentAgentId &&
|
|
809
|
+
(TaskManager.isParticipantActiveOnMainInteraction(candidate.data, currentAgentId) ||
|
|
810
|
+
incomingShowsCurrentAgentPromotion)
|
|
811
|
+
);
|
|
812
|
+
},
|
|
813
|
+
{includeCollectionKeys: true, includeMainCallMediaKeys: true}
|
|
814
|
+
);
|
|
815
|
+
ownerChangeResolvedByRelatedInteraction = Boolean(task);
|
|
816
|
+
}
|
|
817
|
+
|
|
818
|
+
let ownerChangeMainInteractionId: string | undefined;
|
|
819
|
+
if (task && eventType === CC_EVENTS.CONTACT_OWNER_CHANGED) {
|
|
820
|
+
const taskMainInteractionId = task.data?.interaction?.mainInteractionId;
|
|
821
|
+
const payloadMainInteractionId = message.data.interaction?.mainInteractionId;
|
|
822
|
+
const hasConflictingMainInteractionIds = Boolean(
|
|
823
|
+
taskMainInteractionId &&
|
|
824
|
+
payloadMainInteractionId &&
|
|
825
|
+
taskMainInteractionId !== payloadMainInteractionId
|
|
826
|
+
);
|
|
827
|
+
// A promoted child task can retain stale embedded IDs. Prefer the
|
|
828
|
+
// authoritative incoming main-call identity before local snapshot IDs.
|
|
829
|
+
const stableMainInteractionId =
|
|
830
|
+
payloadMainInteractionId ||
|
|
831
|
+
TaskManager.getMainCallMediaId(message.data.interaction) ||
|
|
832
|
+
TaskManager.getMainCallMediaId(task.data?.interaction) ||
|
|
833
|
+
taskMainInteractionId ||
|
|
834
|
+
message.data.interaction?.interactionId ||
|
|
835
|
+
task.data?.interaction?.interactionId ||
|
|
836
|
+
task.data?.interactionId;
|
|
837
|
+
const taskAtStableKey = stableMainInteractionId
|
|
838
|
+
? this.taskCollection[stableMainInteractionId]
|
|
839
|
+
: undefined;
|
|
840
|
+
|
|
841
|
+
if (
|
|
842
|
+
!stableMainInteractionId ||
|
|
843
|
+
hasConflictingMainInteractionIds ||
|
|
844
|
+
(taskAtStableKey && taskAtStableKey !== task)
|
|
845
|
+
) {
|
|
846
|
+
LoggerProxy.warn('Unable to safely correlate owner change to the main interaction task', {
|
|
847
|
+
module: TASK_MANAGER_FILE,
|
|
848
|
+
method: 'prepareEventContext',
|
|
849
|
+
interactionId:
|
|
850
|
+
payloadMainInteractionId || taskMainInteractionId || message.data.interactionId,
|
|
851
|
+
});
|
|
852
|
+
task = undefined;
|
|
853
|
+
} else if (ownerChangeResolvedByRelatedInteraction) {
|
|
854
|
+
ownerChangeMainInteractionId = stableMainInteractionId;
|
|
855
|
+
}
|
|
796
856
|
}
|
|
797
857
|
|
|
798
858
|
if (!task && MAIN_INTERACTION_CORRELATED_EVENTS.has(eventType)) {
|
|
@@ -821,8 +881,11 @@ export default class TaskManager extends EventEmitter {
|
|
|
821
881
|
}
|
|
822
882
|
: message.data;
|
|
823
883
|
|
|
824
|
-
if (task &&
|
|
825
|
-
adjustedPayload =
|
|
884
|
+
if (task && ownerChangeMainInteractionId) {
|
|
885
|
+
adjustedPayload = {
|
|
886
|
+
...adjustedPayload,
|
|
887
|
+
interactionId: ownerChangeMainInteractionId,
|
|
888
|
+
};
|
|
826
889
|
}
|
|
827
890
|
|
|
828
891
|
if (task && eventType === CC_EVENTS.PARTICIPANT_LEFT_CONFERENCE) {
|
|
@@ -860,53 +923,6 @@ export default class TaskManager extends EventEmitter {
|
|
|
860
923
|
};
|
|
861
924
|
}
|
|
862
925
|
|
|
863
|
-
/**
|
|
864
|
-
* ContactOwnerChanged can identify the promoted agent's child interaction while
|
|
865
|
-
* the surviving task is stored under the main interaction. Accept one related
|
|
866
|
-
* task when its existing snapshot or the authoritative incoming promotion proves
|
|
867
|
-
* current-agent main-call membership, while excluding non-promoted and ambiguous matches.
|
|
868
|
-
*/
|
|
869
|
-
private findTaskForContactOwnerChange(payload: WebSocketPayload): ITask | undefined {
|
|
870
|
-
const relatedCandidates = this.findRelatedTasks(payload, {
|
|
871
|
-
includeCollectionKeys: true,
|
|
872
|
-
includeMainCallMediaKeys: true,
|
|
873
|
-
});
|
|
874
|
-
const incomingShowsCurrentAgentPromotion =
|
|
875
|
-
this.isCurrentAgentPromotedOnIncomingMainCall(payload);
|
|
876
|
-
const eligibleCandidates = relatedCandidates.filter(
|
|
877
|
-
(candidate) =>
|
|
878
|
-
this.isCurrentAgentActiveOnMainInteraction(candidate.data) ||
|
|
879
|
-
incomingShowsCurrentAgentPromotion
|
|
880
|
-
);
|
|
881
|
-
|
|
882
|
-
if (eligibleCandidates.length === 1) {
|
|
883
|
-
return eligibleCandidates[0];
|
|
884
|
-
}
|
|
885
|
-
|
|
886
|
-
if (eligibleCandidates.length > 1) {
|
|
887
|
-
LoggerProxy.warn('Unable to correlate task event to a unique main interaction task', {
|
|
888
|
-
module: TASK_MANAGER_FILE,
|
|
889
|
-
method: 'findTaskForContactOwnerChange',
|
|
890
|
-
interactionId:
|
|
891
|
-
payload.interaction?.mainInteractionId ||
|
|
892
|
-
payload.interaction?.parentInteractionId ||
|
|
893
|
-
payload.interaction?.callProcessingDetails?.parentInteractionId ||
|
|
894
|
-
payload.interactionId,
|
|
895
|
-
});
|
|
896
|
-
|
|
897
|
-
return undefined;
|
|
898
|
-
}
|
|
899
|
-
|
|
900
|
-
return undefined;
|
|
901
|
-
}
|
|
902
|
-
|
|
903
|
-
private isCurrentAgentActiveOnMainInteraction(taskData: TaskData | undefined): boolean {
|
|
904
|
-
const currentAgentId = this.agentId || taskData?.agentId;
|
|
905
|
-
if (!currentAgentId) return false;
|
|
906
|
-
|
|
907
|
-
return TaskManager.isParticipantActiveOnMainInteraction(taskData, currentAgentId);
|
|
908
|
-
}
|
|
909
|
-
|
|
910
926
|
private static isParticipantActiveOnMainInteraction(
|
|
911
927
|
taskData: TaskData | WebSocketPayload | undefined,
|
|
912
928
|
participantId: string
|
|
@@ -921,14 +937,6 @@ export default class TaskManager extends EventEmitter {
|
|
|
921
937
|
);
|
|
922
938
|
}
|
|
923
939
|
|
|
924
|
-
private isCurrentAgentPromotedOnIncomingMainCall(payload: WebSocketPayload): boolean {
|
|
925
|
-
return Boolean(
|
|
926
|
-
this.agentId &&
|
|
927
|
-
payload.interaction?.owner === this.agentId &&
|
|
928
|
-
TaskManager.isParticipantActiveOnMainInteraction(payload, this.agentId)
|
|
929
|
-
);
|
|
930
|
-
}
|
|
931
|
-
|
|
932
940
|
private static getMainCallMediaId(
|
|
933
941
|
interaction: TaskData['interaction'] | undefined
|
|
934
942
|
): string | undefined {
|
|
@@ -960,31 +968,6 @@ export default class TaskManager extends EventEmitter {
|
|
|
960
968
|
);
|
|
961
969
|
}
|
|
962
970
|
|
|
963
|
-
/** Keep a related owner-change event from re-keying the surviving main task to a child ID. */
|
|
964
|
-
private static preserveMainTaskIdentity(
|
|
965
|
-
task: ITask,
|
|
966
|
-
payload: WebSocketPayload
|
|
967
|
-
): WebSocketPayload {
|
|
968
|
-
const currentMainInteractionId = task.data?.interaction?.mainInteractionId;
|
|
969
|
-
const payloadMainInteractionId = payload.interaction?.mainInteractionId;
|
|
970
|
-
const currentMainMediaId = TaskManager.getMainCallMediaId(task.data?.interaction);
|
|
971
|
-
const payloadMainMediaId = TaskManager.getMainCallMediaId(payload.interaction);
|
|
972
|
-
const stableInteractionId =
|
|
973
|
-
currentMainInteractionId ||
|
|
974
|
-
payloadMainInteractionId ||
|
|
975
|
-
currentMainMediaId ||
|
|
976
|
-
payloadMainMediaId ||
|
|
977
|
-
payload.interaction?.interactionId ||
|
|
978
|
-
task.data?.interaction?.interactionId ||
|
|
979
|
-
task.data?.interactionId;
|
|
980
|
-
|
|
981
|
-
if (!stableInteractionId || stableInteractionId === payload.interactionId) {
|
|
982
|
-
return payload;
|
|
983
|
-
}
|
|
984
|
-
|
|
985
|
-
return {...payload, interactionId: stableInteractionId};
|
|
986
|
-
}
|
|
987
|
-
|
|
988
971
|
/**
|
|
989
972
|
* ParticipantLeftConference may arrive after an owner update while still carrying
|
|
990
973
|
* the departed owner. Keep the confirmed owner only when the new roster proves that
|
|
@@ -1024,9 +1007,10 @@ export default class TaskManager extends EventEmitter {
|
|
|
1024
1007
|
*/
|
|
1025
1008
|
private findUniqueTaskByRelatedInteraction(
|
|
1026
1009
|
payload: WebSocketPayload,
|
|
1027
|
-
candidateFilter: (task: ITask) => boolean = () => true
|
|
1010
|
+
candidateFilter: (task: ITask) => boolean = () => true,
|
|
1011
|
+
options: {includeCollectionKeys?: boolean; includeMainCallMediaKeys?: boolean} = {}
|
|
1028
1012
|
): ITask | undefined {
|
|
1029
|
-
const candidates = this.findRelatedTasks(payload).filter(candidateFilter);
|
|
1013
|
+
const candidates = this.findRelatedTasks(payload, options).filter(candidateFilter);
|
|
1030
1014
|
|
|
1031
1015
|
if (candidates.length === 1) {
|
|
1032
1016
|
return candidates[0];
|
|
@@ -1060,7 +1044,9 @@ export default class TaskManager extends EventEmitter {
|
|
|
1060
1044
|
payload.interaction?.parentInteractionId,
|
|
1061
1045
|
payload.interaction?.callProcessingDetails?.parentInteractionId,
|
|
1062
1046
|
...(includeMainCallMediaKeys ? [TaskManager.getMainCallMediaId(payload.interaction)] : []),
|
|
1063
|
-
].filter((
|
|
1047
|
+
].filter((relatedInteractionId): relatedInteractionId is string =>
|
|
1048
|
+
Boolean(relatedInteractionId)
|
|
1049
|
+
)
|
|
1064
1050
|
);
|
|
1065
1051
|
if (payloadInteractionIds.size === 0) return [];
|
|
1066
1052
|
|
|
@@ -1082,7 +1068,8 @@ export default class TaskManager extends EventEmitter {
|
|
|
1082
1068
|
];
|
|
1083
1069
|
|
|
1084
1070
|
return candidateInteractionIds.some(
|
|
1085
|
-
(
|
|
1071
|
+
(relatedInteractionId) =>
|
|
1072
|
+
Boolean(relatedInteractionId) && payloadInteractionIds.has(relatedInteractionId)
|
|
1086
1073
|
);
|
|
1087
1074
|
})
|
|
1088
1075
|
.map(([, candidate]) => candidate)
|
|
@@ -1457,6 +1444,7 @@ export default class TaskManager extends EventEmitter {
|
|
|
1457
1444
|
}
|
|
1458
1445
|
|
|
1459
1446
|
private removeTaskFromCollection(task: ITask) {
|
|
1447
|
+
task.disposeWxAppOfferObservability?.();
|
|
1460
1448
|
if (typeof task.cancelAutoWrapupTimer === 'function') {
|
|
1461
1449
|
task.cancelAutoWrapupTimer();
|
|
1462
1450
|
}
|
|
@@ -1655,6 +1643,16 @@ export default class TaskManager extends EventEmitter {
|
|
|
1655
1643
|
});
|
|
1656
1644
|
}
|
|
1657
1645
|
|
|
1646
|
+
/**
|
|
1647
|
+
* Re-run OFFERED wxApp offer diagnostics for collected voice tasks.
|
|
1648
|
+
* Used after usersub publish so pre-publish offers can schedule mismatch grace.
|
|
1649
|
+
*/
|
|
1650
|
+
public refreshWxAppOfferObservabilityForAllTasks(): void {
|
|
1651
|
+
Object.values(this.taskCollection).forEach((task) => {
|
|
1652
|
+
task.refreshWxAppOfferObservability?.();
|
|
1653
|
+
});
|
|
1654
|
+
}
|
|
1655
|
+
|
|
1658
1656
|
public static getTaskManager(
|
|
1659
1657
|
apiAIAssistant: ApiAIAssistant,
|
|
1660
1658
|
contact: ReturnType<typeof routingContact>,
|
|
@@ -618,6 +618,8 @@ This keeps transcript and suggestion delivery aligned on the same per-task event
|
|
|
618
618
|
|
|
619
619
|
**wxApp consumer contract (WXCC-6026):** Hosts enable `enableWxBetterTogether` at init (Phase 1 init-only; re-init to change), bind UI to `task.uiControls` (including optional `main.keypad`), and call **`task.accept()`**, **`task.decline()`**, **`task.toggleMute({ muted? })`**, **`task.transmitDtmf({ dtmf })`**. SDK routes wxApp telephony internally on `Voice`. Shared-line `lineOwnerId` defaults from the wxApp agent participant when omitted.
|
|
620
620
|
|
|
621
|
+
**wxApp OFFERED diagnostics:** `Voice.updateUiControls` delegates offer-decision logging and deferred participant-mismatch WARN/metric to `voice/wxAppOfferObservability.ts` (500 ms grace; observability only). After usersub publish, `TaskManager.refreshWxAppOfferObservabilityForAllTasks()` re-invokes the observer without emitting UI controls. `removeTaskFromCollection` calls `disposeWxAppOfferObservability()` so a pending grace timer cannot emit after the task is dropped (including `CONTACT_MERGED` child removal). Dispose is terminal: the observer sets a disposed guard (timer cancel plus no-op `handleUiControlsUpdate`) so a later `updateUiControls` / refresh cannot schedule a new grace timer.
|
|
622
|
+
|
|
621
623
|
**wxApp offer UI (`uiControlsComputer`):** `wxAppAcceptInFlight` disables accept/decline during the accept REST call. `wxAppAnswerPending` additionally disables accept and decline for **inbound** offers until ASSIGN; wxApp **outdial** keeps decline enabled during the post-accept "Calling…" phase so `cancelTask` remains available.
|
|
622
624
|
|
|
623
625
|
**wxApp decline observability:** Inbound wxApp offers decline via telephony `rejectCall` (`runWxAppReject` → `WXAPP_TASK_DECLINE_*`). wxApp outdial cancellations use CC routing `cancelTask` (`runWxAppOutdialDecline` → additive `WXAPP_TASK_DECLINE_*` plus existing `TASK_DECLINE_*`).
|
|
@@ -924,15 +926,17 @@ guards. The state-machine transition table lives in
|
|
|
924
926
|
Primary-Agent promotion remains backend-authoritative and follows the two-event
|
|
925
927
|
desktop contract. `ContactOwnerChanged` updates the promoted Agent; TaskManager
|
|
926
928
|
prefers an exact task, then one unique related task resolved through nested
|
|
927
|
-
main/parent identifiers or the `mainCall` media-map
|
|
928
|
-
candidate is eligible when its current
|
|
929
|
+
main/parent identifiers, the task collection key, or the `mainCall` media-map
|
|
930
|
+
identity. A related candidate is eligible when its current
|
|
929
931
|
snapshot contains the current Agent's participant entry with `hasLeft !== true`
|
|
930
932
|
and membership on the `mType: mainCall` leg. The incoming snapshot can provide
|
|
931
933
|
that evidence only when it also names the current Agent as `interaction.owner`;
|
|
932
934
|
this permits an authoritative promotion payload to repair a stale child-keyed
|
|
933
935
|
snapshot while still excluding consult-only tasks. The update keeps the
|
|
934
936
|
surviving main interaction identity even when the notification names a
|
|
935
|
-
promoted-Agent child interaction.
|
|
937
|
+
promoted-Agent child interaction. Conflicting main identifiers, ambiguous
|
|
938
|
+
matches, and a canonical key occupied by a distinct task are ignored rather
|
|
939
|
+
than replacing an existing task.
|
|
936
940
|
|
|
937
941
|
If no related task exists, TaskManager recovers only the promoted current Agent
|
|
938
942
|
from a non-terminal telephony `ContactOwnerChanged` payload that provides the same
|
|
@@ -1759,6 +1759,20 @@ export interface ITask extends IEventEmitter {
|
|
|
1759
1759
|
*/
|
|
1760
1760
|
syncWxAppMuteFromCallDetails?(): Promise<boolean | undefined>;
|
|
1761
1761
|
|
|
1762
|
+
/**
|
|
1763
|
+
* Re-run OFFERED wxApp offer diagnostics without emitting UI controls.
|
|
1764
|
+
* Voice tasks implement this; other channel types omit it.
|
|
1765
|
+
* @internal
|
|
1766
|
+
*/
|
|
1767
|
+
refreshWxAppOfferObservability?(): void;
|
|
1768
|
+
|
|
1769
|
+
/**
|
|
1770
|
+
* Cancel pending wxApp offer observability timers (participant-mismatch grace).
|
|
1771
|
+
* Voice tasks implement this; other channel types omit it.
|
|
1772
|
+
* @internal
|
|
1773
|
+
*/
|
|
1774
|
+
disposeWxAppOfferObservability?(): void;
|
|
1775
|
+
|
|
1762
1776
|
/**
|
|
1763
1777
|
* State machine instance for managing task state transitions and derived properties.
|
|
1764
1778
|
* The state machine handles:
|
|
@@ -2070,6 +2084,8 @@ export type VoiceUIControlOptions = {
|
|
|
2070
2084
|
enableWxBetterTogether?: boolean;
|
|
2071
2085
|
consultTransferConfig?: ConsultTransferDestinationConfig;
|
|
2072
2086
|
answerCallOnWebexService?: import('../AnswerCallOnWebexService').default;
|
|
2087
|
+
/** Read-only observability: whether usersub answer-calls-on-wxcc is active this session. */
|
|
2088
|
+
getUsersubPublished?: () => boolean;
|
|
2073
2089
|
};
|
|
2074
2090
|
|
|
2075
2091
|
/**
|