@webex/contact-center 3.12.0-llmrefactor.13 → 3.12.0-llmrefactor.14
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 +2 -2
- 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
package/.sdd/manifest.json
CHANGED
|
@@ -51,7 +51,7 @@
|
|
|
51
51
|
"canonical_spec": "ai-docs/contact-center-spec.md",
|
|
52
52
|
"contracts": {
|
|
53
53
|
"provides": [
|
|
54
|
-
"Published @webex/contact-center package; ContactCenter WebexPlugin; public SDK methods, events, types, Task with ordered action-specific consult/transfer destination controls, AddressBook, ApiAIAssistant, UserPreference, cc.userPreference, existing getQueues/getEntryPoints methods with consult/transfer defaults, full Queue records, mapped EntryPoint records with optional numbers, exported user-preference request/response types, preview-campaign methods acceptPreviewContact/skipPreviewContact/removePreviewContact, and Agent Wellness Break effective-profile, exported action constants/types, internally sourced action context, system-code, and primary notification event APIs",
|
|
54
|
+
"Published @webex/contact-center package; ContactCenter WebexPlugin; public SDK methods, events, types, Task with ordered action-specific consult/transfer destination controls, AddressBook, ApiAIAssistant, UserPreference, cc.userPreference, existing getQueues/getEntryPoints methods with consult/transfer defaults, full Queue records, mapped EntryPoint records with optional numbers, exported user-preference request/response types, preview-campaign methods acceptPreviewContact/skipPreviewContact/removePreviewContact, and Agent Wellness Break effective-profile, registered-session Profile projection, exported action constants/types, internally sourced action context, system-code, and primary/RTD notification event APIs",
|
|
55
55
|
"WXCC-6026 wxApp Better Together: enableWxBetterTogether init config, isWxBetterTogetherEnabled read API, unified task telephony (task.accept/decline/toggleMute/transmitDtmf), TASK_WXAPP_MUTE_STATE_UPDATED event, optional main.keypad uiControl, multi-login supported; runtime toggle deferred to Phase 2 (private)",
|
|
56
56
|
"BROWSER stationLogin payload: dialNumber and deviceId are webrtc-{agentId} (contact-center.station-login-browser-dn / AGENT-R-006); public stationLogin({loginOption: 'BROWSER'}) input unchanged"
|
|
57
57
|
],
|
|
@@ -96,7 +96,8 @@
|
|
|
96
96
|
"canonical_spec": "src/metrics/ai-docs/metrics-spec.md",
|
|
97
97
|
"contracts": {
|
|
98
98
|
"provides": [
|
|
99
|
-
"MetricsManager timing, behavioral/operational/business event submission, taxonomy, queues, tracking-field helpers, and operational Agent Wellness Break action/notification/system-code event names"
|
|
99
|
+
"MetricsManager timing, behavioral/operational/business event submission, taxonomy, queues, tracking-field helpers, and operational Agent Wellness Break action/notification/system-code event names",
|
|
100
|
+
"WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING operational/behavioral telemetry event (contract metrics.wxapp-offer-participant-fields-missing; emit site voice/wxAppOfferObservability.ts)"
|
|
100
101
|
],
|
|
101
102
|
"requires": [
|
|
102
103
|
"Webex internal metrics service; Webex ready lifecycle; LoggerProxy; browser document visibility"
|
package/ai-docs/CONTRACTS.md
CHANGED
|
@@ -17,7 +17,7 @@
|
|
|
17
17
|
| `contact-center.state-controls` | Task state machine | `getDefaultUIControls`, `TaskUIControls` with ordered `consultTransferDestinations` arrays | Task controls include per-leg action state plus ordered `consult`/`transfer` destination categories derived from profile and interaction policy | additive semver public field; destination arrays are SDK-owned and first item is the consumer default | `src/services/task/state-machine/ai-docs/task-state-machine-spec.md` | `src/index.ts`, `src/services/task/types.ts`, `src/services/task/state-machine/uiControlsComputer.ts` |
|
|
18
18
|
| `contact-center.conference-participant-drop` | Task | `DropConferenceParticipantPayload`, `ITask.dropConferenceParticipant` | `(payload: {participantId: string}) => Promise<TaskResponse>`; Voice resolves from correlated `ParticipantLeftConference`, while non-voice base Task rejects as unsupported. Existing participant/consult lifecycle events may resolve one unique child-keyed task by `mainInteractionId`; no new public event is introduced. | semver public | `src/services/task/ai-docs/task-spec.md`, `src/services/task/state-machine/ai-docs/task-state-machine-spec.md` | `src/index.ts`, `src/services/task/types.ts`, `src/services/task/voice/Voice.ts`, `src/services/task/contact.ts`, `src/services/task/TaskManager.ts` |
|
|
19
19
|
| `contact-center.wxapp-answer` | Contact Center / Task | `enableWxBetterTogether`, `isWxBetterTogetherEnabled()`, `getWxAppMuted`, `syncWxAppMuteFromCallDetails`, unified task telephony (`ITask.accept`, `decline`, `toggleMute({ muted?, lineOwnerId? })`, `transmitDtmf({ dtmf, lineOwnerId? })`) | init flag ON → usersub `true` + Mercury on supported station login **and silent relogin**; init flag OFF → force usersub `false` on supported station login **and silent relogin** (clears stale suppression after refresh); **Phase 1 init-only** — change flag via re-init; multi-login supported; mute backfill via GET call details + `getWxAppMuted()` on hydrate/refresh; shared-line `lineOwnerId` defaults from participant; SDK `Voice` routes wxApp telephony when flag active | semver public | `src/services/task/ai-docs/task-spec.md`, `ai-docs/contact-center-spec.md`, `src/services/ai-docs/services-spec.md` | `src/cc.ts`, `src/services/task/voice/Voice.ts`, `src/services/task/voice/wxAppVoiceMethods.ts` |
|
|
20
|
-
| `contact-center.agent-wellness-break` | Contact Center / Services / Config | `Profile.isWellnessBreakEnabled`, `requestWellnessBreak`, `respondToWellnessBreak`, `getWellbeingBreakIdleCode`, `WELLNESS_BREAK_NOTIFICATION_ACTIONS`, `WELLNESS_BREAK_USER_ACTIONS`, `WellnessBreakEvent` | Effective gate = backend-delivered `agentWellbeing.enable` + reminders `ENABLED` + `aiAssistantQuantity > 0`;
|
|
20
|
+
| `contact-center.agent-wellness-break` | Contact Center / Services / Config | `Profile.isWellnessBreakEnabled`, optional `Profile.agentSessionId` after logged-in registration, `requestWellnessBreak`, `respondToWellnessBreak`, `getWellbeingBreakIdleCode`, `WELLNESS_BREAK_NOTIFICATION_ACTIONS`, `WELLNESS_BREAK_USER_ACTIONS`, `WellnessBreakEvent` | Effective gate = backend-delivered `agentWellbeing.enable` + reminders `ENABLED` + `aiAssistantQuantity > 0`; registration projects the SDK-owned active station session after silent relogin so hosts can validate refresh recovery. Action calls accept no identity or context-mutating fields, read the current organization/agent/session from a read-only provider supplied to ApiAIAssistant at construction, and POST a direct `WellnessBreakAction` custom event; current-session logout, multi-login close, relogin, and deregistration entry update or invalidate the provider's source state; system idle code lookup is exact, registration-generation cached, and rejects stale results; valid wellness notifications from either primary or RTD sockets emit the same public event. | additive semver public; no client-side Split dependency or invented rollout field—the delivered AI feature configuration is the server rollout boundary | `ai-docs/contact-center-spec.md`, `src/services/ai-docs/services-spec.md`, `src/services/config/ai-docs/config-spec.md` | `src/index.ts`, `src/types.ts`, `src/cc.ts`, `src/services/config/types.ts`, `src/services/ApiAiAssistant.ts` |
|
|
21
21
|
|
|
22
22
|
### Events
|
|
23
23
|
|
|
@@ -30,6 +30,12 @@
|
|
|
30
30
|
| `rtd.events` | Task | realtime transcription/suggestion | consume then publish per task | task spec | websocket best-effort according to remote service | additive payloads | `src/services/task/TaskManager.ts` |
|
|
31
31
|
| `wellness.notification.events` | Contact Center | `CC_AGENT_EVENTS.WELLNESS_BREAK` | consume validated wellness payloads from the primary data-notification socket, then publish normalized application events | Contact Center spec/types | exact current agent/org; notification session is diagnostic and is not compared with the active session; canonical optional `actionText` is normalized to public `actionText`; malformed/stale payloads are ignored | additive | `src/cc.ts`, `src/types.ts`, `src/services/config/types.ts` |
|
|
32
32
|
|
|
33
|
+
### Telemetry / Metrics Events
|
|
34
|
+
|
|
35
|
+
| Contract ID | Owner module | Event constant | Wire name | Payload schema link | Tags | Defined at |
|
|
36
|
+
|---|---|---|---|---|---|---|
|
|
37
|
+
| `metrics.wxapp-offer-participant-fields-missing` | Task / Metrics | `WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING` | `WxApp Offer Participant Fields Missing` | `src/metrics/ai-docs/metrics-spec.md` | operational, behavioral | `src/metrics/constants.ts`, `src/services/task/voice/wxAppOfferObservability.ts` |
|
|
38
|
+
|
|
33
39
|
## Requires — what this repo depends on
|
|
34
40
|
|
|
35
41
|
| Dependency | What is consumed | Schema / detail link | Availability assumption | Fallback on failure | Version floor |
|
|
@@ -100,8 +100,8 @@ Compatibility notes:
|
|
|
100
100
|
| CONTACT_CENTER-R-004 | `deregister()` must remove registered listeners, stop applicable host/calling resources, close primary and RTD WebSockets, clear agent configuration, and surface cleanup failures. | Listener or connection leaks create duplicate events and stale authenticated sessions in long-lived hosts. | `src/cc.ts` | `test/unit/spec/cc.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
101
101
|
| CONTACT_CENTER-R-005 | On `connectionLost`, ContactCenter must own recovery policy and invoke private `silentRelogin()` only when automated relogin is allowed. | ConnectionService reports transport state; only ContactCenter has agent profile and policy context for authentication recovery. | `src/cc.ts` | `test/unit/spec/cc.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
|
|
102
102
|
| CONTACT_CENTER-R-006 | Expose Agent Wellness Break methods, action-value objects, and types only through the package façade. Derive the public action unions from those `as const` value objects. Action calls accept no identity or context-mutating API; ApiAIAssistant reads effective enablement and organization/agent/session identity from a read-only provider supplied by ContactCenter at construction and rejects when that context is incomplete. Logout, deregistration entry, a current-session multi-login close, and relogin update the provider's source state. The exact system idle-code cache and its in-flight lookup are owned by one registration generation; a newer registration or deregistration rejects a late result instead of caching it. | Wellness actions and cached configuration are registration-scoped and must not leak across agents, relogins, multi-login closures, organizations, or registrations; one public value/type source prevents wire-value drift. | `src/cc.ts`, `src/types.ts`, `src/services/ApiAiAssistant.ts`, `src/index.ts` | `test/unit/spec/cc.ts`, `test/unit/spec/services/ApiAiAssistant.ts`, `docs/samples/contact-center/` | The backend-delivered `agentWellbeing.enable` value is treated as the server rollout decision because no distinct rollout field exists in the supported SDK response contracts. | PRESENT |
|
|
103
|
-
| CONTACT_CENTER-R-007 | When wellness is effectively enabled,
|
|
104
|
-
| CONTACT_CENTER-R-008 | The standalone SDK sample must keep `WellbeingBreak` out of the ordinary idle-code selector, wait for legacy state confirmation and all tasks to become safe, restore
|
|
103
|
+
| CONTACT_CENTER-R-007 | When wellness is effectively enabled, connect the RTD WebSocket even if transcripts and suggested responses are disabled. Route valid `Wellness_Break_Handler` messages from the primary or RTD data-notification socket to `CC_AGENT_EVENTS.WELLNESS_BREAK`. Notification eligibility is scoped to the current agent and organization and deliberately does not compare the notification-provided `agentSessionId` with the active session; outbound wellness APIs remain active-session validated. Ignore malformed or foreign-identity messages without logging the full payload. | A long-lived host needs parity with Desktop notification routing without dropping legitimate nudges whose backend session metadata differs, while actions must still be sent only for the active local session. | `src/cc.ts`, `src/constants.ts`, `src/services/config/types.ts`, `src/services/ApiAiAssistant.ts` | `test/unit/spec/cc.ts`, `test/unit/spec/services/ApiAiAssistant.ts`, `docs/samples/contact-center/` | Notification delivery remains best-effort; the backend owns offer generation and ordering. | PRESENT |
|
|
104
|
+
| CONTACT_CENTER-R-008 | The standalone SDK sample must keep `WellbeingBreak` out of the ordinary idle-code selector, wait for legacy state confirmation and all tasks to become safe, restore `Available` after a completed break with bounded recovery, synchronize authoritative external state changes, use a session-scoped refresh marker without replaying an offer, action, or break, and revoke manual-request eligibility on `WELLNESS_BREAK_NOT_ALLOWED`. A rejected `REJECTED` or `NO_RESPONSE` delivery must retain and re-arm the offer for retry. Logout cleanup must ignore a different agent session, and deregistration cleanup must run when the SDK has completed its local teardown even if a deferred cleanup error is reported. Pure transition helpers own these decisions and are covered behaviorally; browser/source checks are limited to bundle and copy surfaces. | The sample is the executable public-contract reference and must track backend eligibility, avoid duplicated wire literals, and avoid state stranding or cross-session cleanup. | `docs/samples/contact-center/app.js`, `docs/samples/contact-center/wellness-utils.js`, `docs/samples/contact-center/index.html`, `src/webex.js` | `test/unit/spec/wellnessSampleUtils.ts`, sample build | Live QA still supplies authoritative backend/session integration evidence. | PRESENT |
|
|
105
105
|
|
|
106
106
|
## Design Overview
|
|
107
107
|
`ContactCenter` is the package façade and lifecycle owner. Its constructor waits for the host Webex SDK `READY` event, validates plugin configuration, initializes `WebexRequest`, obtains the singleton `Services` graph, and constructs calling, AI-assistant, metrics, task-management, `UserPreference`, and data-service collaborators. `register()` is deliberately narrower: it attaches runtime listeners and establishes the primary Contact Center WebSocket subscription.
|
|
@@ -114,11 +114,11 @@ Durable agent, task, and configuration records remain remote-system owned. The p
|
|
|
114
114
|
|
|
115
115
|
The public enablement signal is `Profile.isWellnessBreakEnabled`. It is true only when the backend-delivered AI feature row has `agentWellbeing.enable === true`, `wellnessBreakReminders === 'ENABLED'`, and the organization reports `aiAssistantQuantity > 0`. The SDK does not create a Desktop Split client or invent a rollout response field: the backend controls rollout by deciding whether the delivered AI feature configuration enables wellness for the organization.
|
|
116
116
|
|
|
117
|
-
After station login or relogin, ContactCenter retains the current agent-session id. ApiAIAssistant reads the current effective gate and organization/agent/session identity through a read-only provider supplied by ContactCenter at construction; it exposes no context-mutating method or separate factory. Logout, deregistration entry, and a current-session multi-login close invalidate the provider's source state. `requestWellnessBreak()` and `respondToWellnessBreak()` do not accept identity fields; they POST a direct custom event with a fresh numeric timestamp and reject disabled or incomplete context before transport. Public action types are derived from exported `as const` action-value objects so applications and samples can use the same wire values without an enum. `getWellbeingBreakIdleCode()` resolves the exact active system code named `WellbeingBreak` and caches it for the registration lifetime. The lookup captures its registration generation and organization before transport; if either ownership value changes before the response arrives, the stale result is rejected and never repopulates the cache.
|
|
117
|
+
After station login or relogin, ContactCenter retains the current agent-session id. When registration silently restores an already logged-in station, the returned `Profile.agentSessionId` projects that active session. It is absent when registration has no active session. This lets hosts recover session-scoped state after refresh without reading SDK internals or trusting a notification's session id. ApiAIAssistant reads the current effective gate and organization/agent/session identity through a read-only provider supplied by ContactCenter at construction; it exposes no context-mutating method or separate factory. Logout, deregistration entry, and a current-session multi-login close invalidate the provider's source state. `requestWellnessBreak()` and `respondToWellnessBreak()` do not accept identity fields; they POST a direct custom event with a fresh numeric timestamp and reject disabled or incomplete context before transport. Public action types are derived from exported `as const` action-value objects so applications and samples can use the same wire values without an enum. `getWellbeingBreakIdleCode()` resolves the exact active system code named `WellbeingBreak` and caches it for the registration lifetime. The lookup captures its registration generation and organization before transport; if either ownership value changes before the response arrives, the stale result is rejected and never repopulates the cache.
|
|
118
118
|
|
|
119
|
-
Wellness payloads are handled by ContactCenter on the primary data-notification socket before ordinary routing. Only known actions with matching agent and organization are emitted; the notification session is retained as diagnostic metadata and is not treated as action authority. The canonical optional `actionText` field is normalized to public `actionText`. Deregistration clears the wellness session and system-code cache.
|
|
119
|
+
Wellness payloads are handled by ContactCenter on the primary or RTD data-notification socket before ordinary routing. Only known actions with matching agent and organization are emitted; the notification session is retained as diagnostic metadata and is not treated as action authority. The canonical optional `actionText` field is normalized to public `actionText`. Deregistration clears the wellness session and system-code cache.
|
|
120
120
|
|
|
121
|
-
The standalone SDK browser sample keeps `WellbeingBreak` out of the ordinary status selector. It reads the exported wellness wire constants from the standalone `Webex` constructor, creates a session-scoped recovery marker before changing legacy state, confirms the transition, sends `ACCEPTED` only after state success, and waits for every task to leave incoming, active, consult, conference, campaign, and wrap-up work before starting its 60-second demonstration timer. Restoration targets `Available / 0`, retries bounded failures, and retains reconnect recovery ownership after exhaustion. Authoritative external Available changes end the local lifecycle, while refresh recovery restores a matching current session without replaying the offer, response action, or break. Pure helpers own notification, response, logout, and deregistration transition decisions; a failed rejection/no-response delivery retains and re-arms its offer. If SDK deregistration completes its local teardown but reports a deferred error, the sample still detaches listeners and resets its local controls.
|
|
121
|
+
The standalone SDK browser sample keeps `WellbeingBreak` out of the ordinary status selector. It reads the exported wellness wire constants from the standalone `Webex` constructor, seeds its current session and legacy state from a logged-in registration profile after silent relogin, and shows only the action name in its latest-event display. It creates a session-scoped recovery marker before changing legacy state, clears manual-request eligibility when a break starts so the request control remains disabled after restoration until another `SUGGEST_WELLNESS_BREAK`, confirms the transition, sends `ACCEPTED` only after state success, and waits for every task to leave incoming, active, consult, conference, campaign, and wrap-up work before starting its 60-second demonstration timer. Restoration targets `Available / 0`, retries bounded failures, and retains reconnect recovery ownership after exhaustion. Authoritative external Available changes end the local lifecycle, while refresh recovery restores a matching current session without replaying the offer, response action, or break. Pure helpers own notification, response, logout, and deregistration transition decisions; a failed rejection/no-response delivery retains and re-arms its offer. If SDK deregistration completes its local teardown but reports a deferred error, the sample still detaches listeners and resets its local controls.
|
|
122
122
|
|
|
123
123
|
### wxApp Better Together (WXCC-6026)
|
|
124
124
|
|
|
@@ -335,7 +335,7 @@ stateDiagram-v2
|
|
|
335
335
|
```
|
|
336
336
|
|
|
337
337
|
## Protocol / Wire Format
|
|
338
|
-
Authenticated REST initiates direct data/config operations and AQM agent/task operations. For AQM, the HTTP response is acknowledgement only; `notifSuccess.bind`/`notifFail.bind` match WebSocket payloads that settle the promise. Wellness actions are the exception: ApiAIAssistant posts a direct `CUSTOM_EVENT` to `/event` and requires HTTP 202. The primary
|
|
338
|
+
Authenticated REST initiates direct data/config operations and AQM agent/task operations. For AQM, the HTTP response is acknowledgement only; `notifSuccess.bind`/`notifFail.bind` match WebSocket payloads that settle the promise. Wellness actions are the exception: ApiAIAssistant posts a direct `CUSTOM_EVENT` to `/event` and requires HTTP 202. The primary and RTD WebSockets can carry `Wellness_Break_Handler`; ContactCenter routes it before task-specific traffic. Payload and event names are owned by `src/types.ts`, `src/services/config/types.ts`, `src/services/agent/types.ts`, and `src/services/task/types.ts`.
|
|
339
339
|
|
|
340
340
|
## Error Handling & Failure Modes
|
|
341
341
|
| Condition | Signal (error/code/result) | Caller recovery |
|
|
@@ -374,7 +374,7 @@ The module registers as `cc` through the Webex SDK plugin system and depends on
|
|
|
374
374
|
| CONTACT_CENTER-R-004 | `test/unit/spec/cc.ts` | listener/resource cleanup and error propagation |
|
|
375
375
|
| CONTACT_CENTER-R-005 | `test/unit/spec/cc.ts` | relogin policy ownership |
|
|
376
376
|
| CONTACT_CENTER-R-006 | `test/unit/spec/cc.ts`, `test/unit/spec/services/ApiAiAssistant.ts` | Effective-gate and internal organization/agent/session validation, current-session multi-login invalidation, registration-generation idle-code cache ownership, identity-free public input, exact request body, action validation, and direct-HTTP failure paths. |
|
|
377
|
-
| CONTACT_CENTER-R-007 | `test/unit/spec/cc.ts`, `test/unit/spec/services/ApiAiAssistant.ts` | Wellness normalization on
|
|
377
|
+
| CONTACT_CENTER-R-007 | `test/unit/spec/cc.ts`, `test/unit/spec/services/ApiAiAssistant.ts` | Wellness normalization on primary and RTD notification sockets, notification-session mismatch acceptance, invalid agent/org filtering, and active-session outbound validation. |
|
|
378
378
|
| CONTACT_CENTER-R-008 | `test/unit/spec/wellnessSampleUtils.ts`, sample build | Safe-work classification, legacy external/RONA decisions, minimal refresh marker, recovery decisions, retryable offer-response transitions, session-scoped logout cleanup, deferred-error deregistration cleanup, and standalone browser constant exposure. |
|
|
379
379
|
|
|
380
380
|
## Traceability
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
# Contact Owner Change Refactor
|
|
2
|
+
|
|
3
|
+
## Scope
|
|
4
|
+
|
|
5
|
+
This change refactors only the owner-change propagation introduced by
|
|
6
|
+
webex-js-sdk PR #5187. It preserves the behavior required by Participant Drop:
|
|
7
|
+
when the backend promotes a new primary Agent, existing SDK tasks publish the
|
|
8
|
+
updated owner through the existing `task:hydrate` event so widgets immediately
|
|
9
|
+
recompute the Primary label, icon, and Drop permission.
|
|
10
|
+
|
|
11
|
+
There is no public API, routing-event, state-machine transition, Participant
|
|
12
|
+
Drop request, or widget contract change.
|
|
13
|
+
|
|
14
|
+
## Retained behavior
|
|
15
|
+
|
|
16
|
+
| Behavior | Why it remains | Existing-flow impact |
|
|
17
|
+
|---|---|---|
|
|
18
|
+
| Map an owner-changing `ContactUpdated` to `CONTACT_OWNER_CHANGED` | Remaining conference Agents receive `ContactUpdated`, not necessarily `ContactOwnerChanged`. The existing state-machine action synchronizes task data and emits `task:hydrate`. | Preserves immediate owner/Primary UI updates without adding a new public event or state transition. Same-owner and owner-less updates remain data-only. |
|
|
19
|
+
| Resolve `ContactOwnerChanged` by exact ID before related IDs | Exact identity remains the safest and existing lookup contract. | Existing exact task routing is unchanged. |
|
|
20
|
+
| Resolve one unique related task using nested interaction IDs, the collection key, or the `mainCall` media-map key | Some supported owner-change payloads identify the surviving call through a child top-level ID and omit `mainInteractionId`. | Preserves cross-channel promotion across the documented payload variants. Task aliases are deduplicated and ambiguous matches remain ignored. |
|
|
21
|
+
| Accept current-Agent main-leg evidence from the existing task or authoritative incoming promotion | A local child task can be briefly stale when the owner-change payload first proves promotion. | Preserves the stale-snapshot fix while continuing to reject ordinary consult-only tasks. |
|
|
22
|
+
| Recover a missing promoted-Agent task from a complete `ContactOwnerChanged` payload | Reloaded or desynchronized clients may receive the authoritative promotion before normal task creation catches up. Agent Desktop supports the same recovery path. | A complete telephony main-leg payload restores the task under the stable main interaction ID, emits one `task:hydrate`, and emits no synthetic `task:incoming`. |
|
|
23
|
+
| Preserve a confirmed owner across a late `ParticipantLeftConference` snapshot that still names the departed owner | Participant Drop can deliver owner change and participant-left notifications in either order. | Prevents the Primary UI from reverting after it has already received the authoritative new owner. |
|
|
24
|
+
|
|
25
|
+
## Refactoring and safety constraints
|
|
26
|
+
|
|
27
|
+
Owner-change correlation and recovery retain the following protections:
|
|
28
|
+
|
|
29
|
+
- generic lifecycle-event correlation is unchanged; collection-key and media-key
|
|
30
|
+
matching are enabled only for `ContactOwnerChanged`;
|
|
31
|
+
- only a task selected by the related-interaction fallback may be re-keyed;
|
|
32
|
+
- conflicting task/payload main IDs are ignored;
|
|
33
|
+
- a stable main key already owned by a different Task causes the event to be
|
|
34
|
+
ignored; and
|
|
35
|
+
- an exact child task is never normalized over a distinct main task;
|
|
36
|
+
- aliases of the same Task object are deduplicated before uniqueness is tested;
|
|
37
|
+
- consult-only and ambiguous matches are rejected; and
|
|
38
|
+
- missing-task recovery is rejected whenever any related task or canonical-key
|
|
39
|
+
occupant already exists.
|
|
40
|
+
|
|
41
|
+
This closes the PR review scenario where an exact child task could previously
|
|
42
|
+
be normalized to a main key and silently replace the actual main Task, orphaning
|
|
43
|
+
its listeners and resources.
|
|
44
|
+
|
|
45
|
+
Recovery is intentionally narrow. It requires a non-terminal telephony payload,
|
|
46
|
+
`interaction.owner` equal to the current Agent, an active participant entry for
|
|
47
|
+
that Agent on an `mType: mainCall` leg, and a stable main interaction ID. It does
|
|
48
|
+
not use `toOwner`, elect an owner locally, or create a task for a missing
|
|
49
|
+
owner-changing `ContactUpdated` event.
|
|
50
|
+
|
|
51
|
+
## Resulting event flow
|
|
52
|
+
|
|
53
|
+
1. The backend chooses the new `interaction.owner`.
|
|
54
|
+
2. The promoted Agent receives `ContactOwnerChanged`; other surviving Agents may
|
|
55
|
+
receive an owner-changing `ContactUpdated`.
|
|
56
|
+
3. TaskManager first uses an exact task match and otherwise accepts one unique,
|
|
57
|
+
eligible related task.
|
|
58
|
+
4. If the promoted Agent has no related task, a complete authoritative payload
|
|
59
|
+
may recover the task under its stable main interaction ID. An internal
|
|
60
|
+
pre-listener `HYDRATE` restores the actor state without publishing an event.
|
|
61
|
+
5. Both notification forms use the existing `CONTACT_OWNER_CHANGED` state-machine
|
|
62
|
+
event.
|
|
63
|
+
6. The state machine updates task/context data, keeps the current task state, and
|
|
64
|
+
emits exactly one public `task:hydrate`.
|
|
65
|
+
7. Widget consumers receive the hydrated Task and render the backend-provided
|
|
66
|
+
owner immediately.
|
|
67
|
+
|
|
68
|
+
`ParticipantLeftConference` continues through its existing event/state-machine
|
|
69
|
+
path. No client elects a successor and no Drop completion fabricates an owner.
|
|
70
|
+
|
|
71
|
+
## Verification coverage
|
|
72
|
+
|
|
73
|
+
The focused TaskManager tests cover owner-changing versus data-only
|
|
74
|
+
`ContactUpdated`, exact and child-keyed correlation, collection-key and
|
|
75
|
+
`mainCall` media-key payloads, stale incoming promotion evidence, consult-only
|
|
76
|
+
and ambiguous rejection, narrow missing-task recovery, listener/alias safety,
|
|
77
|
+
later `AgentContact` and `ContactMerged` reuse, exact-child/main-task collision
|
|
78
|
+
protection, and both owner-change/participant-left orders.
|
package/dist/cc.js
CHANGED
|
@@ -523,6 +523,15 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
523
523
|
return true;
|
|
524
524
|
}
|
|
525
525
|
handleRTDWebsocketMessage = event => {
|
|
526
|
+
let payload;
|
|
527
|
+
try {
|
|
528
|
+
payload = JSON.parse(event);
|
|
529
|
+
} catch {
|
|
530
|
+
// TaskManager retains its existing malformed RTD message handling.
|
|
531
|
+
}
|
|
532
|
+
if ((0, _Utils.isRecord)(payload) && this.handleWellnessBreakWebsocketPayload(payload)) {
|
|
533
|
+
return;
|
|
534
|
+
}
|
|
526
535
|
this.taskManager.handleRealtimeWebsocketEvent(event);
|
|
527
536
|
};
|
|
528
537
|
|
|
@@ -597,7 +606,14 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
597
606
|
module: _constants.CC_FILE,
|
|
598
607
|
method: _constants.METHODS.REGISTER
|
|
599
608
|
});
|
|
600
|
-
|
|
609
|
+
|
|
610
|
+
// Registration can silently restore a station session without emitting a
|
|
611
|
+
// relogin event. Project the active session for consumers that must
|
|
612
|
+
// recover session-scoped state after a page refresh.
|
|
613
|
+
return resp.isAgentLoggedIn && this.currentAgentSessionId ? {
|
|
614
|
+
...resp,
|
|
615
|
+
agentSessionId: this.currentAgentSessionId
|
|
616
|
+
} : resp;
|
|
601
617
|
} catch (error) {
|
|
602
618
|
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.WEBSOCKET_REGISTER_FAILED, {
|
|
603
619
|
orgId: error.orgId
|
|
@@ -827,7 +843,8 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
827
843
|
accessEntryPoint: this.agentConfig.accessEntryPoint,
|
|
828
844
|
accessBuddyTeam: this.agentConfig.accessBuddyTeam
|
|
829
845
|
},
|
|
830
|
-
enableWxBetterTogether: this.isWxBetterTogetherEnabled()
|
|
846
|
+
enableWxBetterTogether: this.isWxBetterTogetherEnabled(),
|
|
847
|
+
getWxAppUsersubPublished: () => this.webexCrossClientService.isAnswerCallsStateActive()
|
|
831
848
|
};
|
|
832
849
|
this.taskManager.setConfigFlags(configFlags);
|
|
833
850
|
// TODO: Make profile a singleton to make it available throughout app/sdk so we dont need to inject info everywhere
|
|
@@ -841,8 +858,9 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
841
858
|
/**
|
|
842
859
|
* RTD websocket currently supports realtime transcripts and suggested responses.
|
|
843
860
|
* Extend this condition when additional AI RTD features are introduced.
|
|
861
|
+
* Wellness break notifications also arrive on this websocket.
|
|
844
862
|
*/
|
|
845
|
-
if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable || this.agentConfig.aiFeature?.suggestedResponses?.enable) {
|
|
863
|
+
if (this.agentConfig.aiFeature?.realtimeTranscripts?.enable || this.agentConfig.aiFeature?.suggestedResponses?.enable || this.agentConfig.isWellnessBreakEnabled) {
|
|
846
864
|
_loggerProxy.default.info('Connecting to RTD websocket', {
|
|
847
865
|
module: _constants.CC_FILE,
|
|
848
866
|
method: _constants.METHODS.CONNECT_WEBSOCKET
|
|
@@ -1481,6 +1499,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1481
1499
|
await this.publishAnswerOnWebexCrossClientState(true);
|
|
1482
1500
|
publishedEnable = true;
|
|
1483
1501
|
this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
|
|
1502
|
+
this.taskManager.refreshWxAppOfferObservabilityForAllTasks();
|
|
1484
1503
|
} else {
|
|
1485
1504
|
await this.publishAnswerOnWebexCrossClientState(false);
|
|
1486
1505
|
this.wxAppTelephonyMercurySync.unsubscribe();
|
|
@@ -1543,6 +1562,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1543
1562
|
await this.publishAnswerOnWebexCrossClientState(true);
|
|
1544
1563
|
publishedEnable = true;
|
|
1545
1564
|
this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks();
|
|
1565
|
+
this.taskManager.refreshWxAppOfferObservabilityForAllTasks();
|
|
1546
1566
|
const mercurySubscribed = this.wxAppTelephonyMercurySync.isSubscribed();
|
|
1547
1567
|
const usersubPublished = this.webexCrossClientService.isAnswerCallsStateActive();
|
|
1548
1568
|
(0, _wxAppDiagnosticLogging.logWxAppSessionReadiness)({
|
|
@@ -1557,12 +1577,18 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1557
1577
|
if (sessionInitReady) {
|
|
1558
1578
|
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_SUCCESS, {
|
|
1559
1579
|
loginOption,
|
|
1560
|
-
enableWxBetterTogether: true
|
|
1580
|
+
enableWxBetterTogether: true,
|
|
1581
|
+
usersubPublished,
|
|
1582
|
+
mercurySubscribed,
|
|
1583
|
+
telephonyTaskType
|
|
1561
1584
|
}, ['operational', 'behavioral']);
|
|
1562
1585
|
} else {
|
|
1563
1586
|
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED, {
|
|
1564
1587
|
loginOption,
|
|
1565
1588
|
enableWxBetterTogether: true,
|
|
1589
|
+
usersubPublished,
|
|
1590
|
+
mercurySubscribed,
|
|
1591
|
+
telephonyTaskType,
|
|
1566
1592
|
skipReason: !usersubPublished ? 'usersub_not_published' : 'mercury_not_subscribed'
|
|
1567
1593
|
}, ['operational', 'behavioral']);
|
|
1568
1594
|
}
|
|
@@ -1599,6 +1625,9 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1599
1625
|
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.WXAPP_SESSION_INIT_FAILED, {
|
|
1600
1626
|
loginOption,
|
|
1601
1627
|
enableWxBetterTogether: true,
|
|
1628
|
+
usersubPublished: this.webexCrossClientService.isAnswerCallsStateActive(),
|
|
1629
|
+
mercurySubscribed: this.wxAppTelephonyMercurySync.isSubscribed(),
|
|
1630
|
+
telephonyTaskType,
|
|
1602
1631
|
skipReason: sessionInitFailureReason,
|
|
1603
1632
|
error: error instanceof Error ? error.toString() : String(error)
|
|
1604
1633
|
}, ['operational', 'behavioral']);
|
|
@@ -1606,7 +1635,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1606
1635
|
enableWxBetterTogether: true,
|
|
1607
1636
|
loginOption,
|
|
1608
1637
|
wxAppHooksApplied: false,
|
|
1609
|
-
usersubPublished:
|
|
1638
|
+
usersubPublished: this.webexCrossClientService.isAnswerCallsStateActive(),
|
|
1610
1639
|
mercurySubscribed: this.wxAppTelephonyMercurySync.isSubscribed(),
|
|
1611
1640
|
telephonyTaskType,
|
|
1612
1641
|
skipReason: sessionInitFailureReason
|
|
@@ -1812,7 +1841,8 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1812
1841
|
method: _constants.METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY
|
|
1813
1842
|
});
|
|
1814
1843
|
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED, {
|
|
1815
|
-
error: 'agentId unavailable'
|
|
1844
|
+
error: 'agentId unavailable',
|
|
1845
|
+
mercurySubscribed: false
|
|
1816
1846
|
}, ['operational', 'behavioral']);
|
|
1817
1847
|
return;
|
|
1818
1848
|
}
|
|
@@ -1827,7 +1857,8 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1827
1857
|
method: _constants.METHODS.SYNC_WXAPP_MUTE_FROM_MERCURY
|
|
1828
1858
|
});
|
|
1829
1859
|
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.WXAPP_MERCURY_SUBSCRIBE_FAILED, {
|
|
1830
|
-
error: error instanceof Error ? error.toString() : String(error)
|
|
1860
|
+
error: error instanceof Error ? error.toString() : String(error),
|
|
1861
|
+
mercurySubscribed: false
|
|
1831
1862
|
}, ['operational', 'behavioral']);
|
|
1832
1863
|
this.wxAppTelephonyMercurySync.unsubscribe();
|
|
1833
1864
|
await this.releaseWxAppMercuryResources();
|
|
@@ -1846,6 +1877,7 @@ class ContactCenter extends _webexCore.WebexPlugin {
|
|
|
1846
1877
|
if (enable && this.isWxBetterTogetherEnabled()) {
|
|
1847
1878
|
this.metricsManager.trackEvent(_constants4.METRIC_EVENT_NAMES.WXAPP_USERSUB_PUBLISH_FAILED, {
|
|
1848
1879
|
enableWxBetterTogether: true,
|
|
1880
|
+
usersubPublished: this.webexCrossClientService.isAnswerCallsStateActive(),
|
|
1849
1881
|
skipReason: 'user_id_unavailable',
|
|
1850
1882
|
error: 'User ID is unavailable for cross-client publish'
|
|
1851
1883
|
}, ['operational', 'behavioral']);
|