feat(contact-center): add agent wellness breaks (WXCC-12423) - #741
Conversation
Codex Review SummaryThis comment shows the latest Codex review activity on this pull request.
ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings. |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6bf2b5296d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ? await import('./assets/WellnessBreakAnimationDark.json') | ||
| : await import('./assets/WellnessBreakAnimationLight.json'); |
There was a problem hiding this comment.
Commit the imported wellness animations
Both literal dynamic imports reference files that are absent from the commit: a repo-wide git ls-tree -r 2feb787 search finds neither WellnessBreakAnimationDark.json nor WellnessBreakAnimationLight.json (and .gitignore currently ignores *.json). Webpack must resolve literal dynamic imports while building, so the AI Assistant and aggregate widget builds fail before the runtime catch can provide the intended media fallback; force-add the two approved JSON assets or remove/replace these imports.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 88a2cf9. Both animation JSON files and the MP3 are tracked in packages/contact-center/ai-assistant/src/wellness/assets. The production AI Assistant build resolves and emits the media.
| /** Returns the system-owned `WellbeingBreak` idle code for the active registration. */ | ||
| getWellbeingBreakIdleCode(): Promise<IdleCode>; | ||
| /** Changes one or more Agent State Control channels and resolves from the matching SDK event. */ | ||
| setAgentChannelState(data: SetAgentChannelStateParams): Promise<AgentChannelStateChangedEvent>; |
There was a problem hiding this comment.
Upgrade the SDK before calling wellness APIs
This commit leaves @webex/contact-center pinned to the pre-existing 3.12.0-next.109 build while locally extending IContactCenter with wellness APIs; the added feature intake explicitly identifies these declarations as temporary and requires replacing the SDK dependency and using its package-root types before release. Until that dependency is supplied, the type checker cannot verify these methods/events against the runtime SDK, and a missing getWellbeingBreakIdleCode disables wellness while a missing state/action API makes accepted breaks fail. Upgrade to the WXCC-12423 SDK surface rather than asserting it locally.
AGENTS.md reference: AGENTS.md:L56-L57
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The runtime dependency is now @webex/contact-center@3.12.0-next.131. Its published declarations include the wellness code, request, response, event, and action APIs. The store still uses its existing local IContactCenter facade. The separate registration session projection remains in SDK PR webex/webex-js-sdk#5251.
| ccSDK.off(CC_EVENTS.WELLNESS_BREAK, this.handleWellnessBreak); | ||
| ccSDK.off(CC_EVENTS.AI_ASSISTANT_RTD_STATUS_CHANGED, this.handleAIAssistantRtdStatus); | ||
| ccSDK.off(CC_EVENTS.AGENT_CHANNEL_RELOGIN_SUCCESS, this.handleAgentChannelRelogin); | ||
| ccSDK.off(CC_EVENTS.AGENT_CHANNEL_STATE_CHANGED, this.handleAgentChannelStateChanged); |
There was a problem hiding this comment.
Re-add wellness listeners after logout
After a normal logout, handleLogOut invokes removeEventListeners, and these lines detach all four registration-level wellness/ASC callbacks. On the next station login or DN registration in the same page, addEventListeners only restores the task and legacy-state callbacks; the wellness on(...) calls at the end of setupIncomingTaskHandler are not executed again. Consequently subsequent login sessions stop receiving wellness offers, RTD status, and channel-state updates until the store is initialized again.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in the current branch. CC_EVENTS.WELLNESS_BREAK is registered independently of the task listeners that logout removes. The store test covers logout followed by relogin in the same page.
| const legacyRecoveryCountRef = useRef(0); | ||
| const ascReconnectAttemptedRef = useRef(false); |
There was a problem hiding this comment.
Reset recovery retry guards for each break
These counters live for the entire mounted hook and are never reset when enterBreak initializes a new lifecycle. If one ASC break consumes its reconnect attempt and later succeeds, a restoration failure on a subsequent break cannot retry after relogin; likewise, once the five legacy background attempts have been consumed, later breaks receive no background recovery. Reset both refs when a new break is accepted so the documented retry bounds apply per break rather than per widget mount.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed for the supported legacy flow: the background restore retry count resets on every enterBreak and session change. The two-break regression test exercises exhaustion followed by a fresh budget. V2 channel-state restoration was removed from this PR.
|
This pull request is automatically being deployed by Amplify Hosting (learn more). |
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 0cab52827a
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| !this.store.isWellnessBreakEnabled || | ||
| !payload?.agentSessionId || | ||
| payload.agentId !== this.store.agentId || | ||
| !WELLNESS_NOTIFICATION_ACTIONS.has(payload.actionEvent) |
There was a problem hiding this comment.
Reject wellness events from other organizations
When the current profile has an organization ID, an SDK event with the same agentId but a different orgId passes this predicate and is exposed as an actionable offer. Accepting it then sends a response using the current tenant's local session, potentially acting on a foreign-organization notification; compare payload.orgId with this.store.agentProfile.orgId before storing the event.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
This PR supports one active organization and one station session per widget store. Cross-organization switching is outside that scope, so we are not adding multi-organization event handling here. The widget still requires the event agent ID to match the current agent.
| await api.respondToWellnessBreak({ | ||
| agentId: latest.agentId, | ||
| agentSessionId: latest.agentSessionId, | ||
| action: 'ACCEPTED', | ||
| }); |
There was a problem hiding this comment.
Abort acceptance when the active session rotates
If station relogin rotates agentSessionId while the preceding state-change request is pending, the session effect clears the old lifecycle, but this coroutine resumes with the latest snapshot captured before the await and sends ACCEPTED for the old session; it subsequently resurrects the old offer as waiting-for-safe-state in the new session. Capture an operation/session token before changing state and recheck it before responding, invoking callbacks, or advancing the phase.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed. enterBreak captures the operation and active session, then rechecks both after the state-change request before sending ACCEPTED, and again after that response before advancing the lifecycle.
| void groups.reduce( | ||
| (sequence, group) => | ||
| sequence.then(() => | ||
| store.cc |
There was a problem hiding this comment.
Continue restoring ASC groups after an unmount failure
When the widget unmounts during an ASC break with multiple restore groups, rejection of the first setAgentChannelState call rejects this .then chain, prevents every later group from running, and leaves the rejection unhandled. A transient failure restoring one channel can therefore leave all subsequent channels in WellbeingBreak; catch each group failure while preserving sequential execution so the remaining groups still receive a best-effort restore.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
V2 channel-state control was removed from this PR. The supported wellness lifecycle uses the legacy agent state API, so there is no ASC restore-group chain on unmount in the current code.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ece9358c66
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| capturedRef.current = { | ||
| stateModel: marker.stateModel, | ||
| theme: input.theme, | ||
| channelTypes: marker.channelTypes || [], | ||
| preBreakChannelStates: marker.preBreakChannelStates || {}, |
There was a problem hiding this comment.
Preserve the legacy pre-break state across refresh recovery
When the page refreshes during a legacy break that began from an idle code such as Meeting, the recovery marker contains no preBreakLegacyState or preBreakLegacyAuxCodeId, and this reconstructed capture therefore leaves both undefined. performRestore() subsequently falls back to Available, making the agent routable instead of restoring the captured idle state. Persist and reconstruct the minimal legacy state fields alongside the ASC snapshot.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The product requirement is to return the agent to Available (auxCodeId: 0) when a wellness break ends, regardless of the pre-break idle code. The current restore path does that. The captured prior state is used only for ownership checks during an early exit; preserving Meeting after completion would conflict with the required behavior.
| if (input.rtdStatus.state === 'disconnected') { | ||
| clearOfferTimer(); | ||
| if (phaseRef.current === 'offer-pending' || phaseRef.current === 'request-pending') { | ||
| setRequestAvailable(false); | ||
| setPhase('idle'); |
There was a problem hiding this comment.
Deactivate the persisted offer on RTD disconnect
When RTD disconnects while an offer is pending, handleAIAssistantRtdStatus in storeEventsWrapper.ts has already changed the store phase to idle before this observer rerenders, so phaseRef.current is no longer offer-pending and this branch does not invalidate the offer history entry. The assistant then renders that persisted entry with enabled Take a break/Later actions, but both silently do nothing because the lifecycle is idle; resolve the active offer whenever the disconnect invalidates notification state.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
RTD disconnect handling is out of scope for this PR. The current wellness contract does not expose an RTD connection-status event to this widget, so we are not adding a separate disconnect invalidation path here. An unanswered offer remains governed by its five-minute response timeout.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e7d993a557
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| // Wellness state is changed by the AI Assistant lifecycle. Reflect its | ||
| // SDK event (and the following restore event) without echoing either | ||
| // transition back through setAgentState. | ||
| if (isCurrentStateExternallyManaged || externallyManagedStateRef.current) { |
There was a problem hiding this comment.
Send manual selections made from the wellness state
When the User State dropdown currently shows WellbeingBreak, externallyManagedStateRef.current remains true and the dropdown is still interactive. If the agent selects another state, isCurrentStateExternallyManaged becomes false but this condition still takes the early return, so updateAgentState is never called: the widget displays the selected state while the backend remains in WellbeingBreak. Distinguish user-originated selections from SDK restoration events, or disable the selector while the wellness state is externally owned.
Useful? React with 👍 / 👎.
| .wellness-break-overlay--assistant, | ||
| .wellness-break-overlay--custom { | ||
| padding: 0; | ||
| position: absolute; | ||
| } |
There was a problem hiding this comment.
Size assistant overlays independently of closed chrome
With wellnessBreakOverlayTarget="assistant", accepting from the toast while the assistant is closed or minimized leaves the overlay absolutely positioned against an .ai-assistant root whose height is only the launcher or minimized bar. The 5/60/5 break surface is therefore squeezed into that small area instead of a usable assistant-sized canvas. Give the root a stable active-overlay size or open/target the full panel before rendering this mode.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 3c5f2dc. During an assistant-scoped starting, playing, or ending overlay, the assistant root now keeps a panel-height canvas (min-height: min(32rem, 90vh)) even when chrome is closed or minimized. The regression test covers both chrome states and removes the size class when the overlay ends. The production build includes the rule.
| fs.readdirSync(this.source) | ||
| .filter((filename) => filename.includes('lottie-web')) | ||
| .forEach((filename) => fs.copyFileSync(path.join(this.source, filename), path.join(this.destination, filename))); |
There was a problem hiding this comment.
Copy the renderer chunk in production builds
Under NODE_ENV=production, Webpack gives the unnamed import('lottie-web') chunk a deterministic ID-based filename rather than a name containing lottie-web. This filter consequently copies no renderer chunk from cc-components/dist, while the embedded component runtime still requests that file; published AI Assistant and aggregate builds then report MEDIA_UNAVAILABLE and never render the animation. Copy the referenced wellness chunks without relying on their generated names, or assign the import an explicit chunk name.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed by moving the lazy Lottie loader into @webex/cc-ai-assistant. Its import has the explicit lottie-web chunk name. The production build emits that chunk, and the aggregate Web Component build copies the AI Assistant wellness assets by directory rather than a generated-filename filter.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b39210972e
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| if (phaseRef.current === 'request-pending') { | ||
| logWellnessMetric(WELLNESS_METRIC.PROVIDE_BREAK_EVENT_RECEIVED); | ||
| void enterBreak(event, false); |
There was a problem hiding this comment.
Elect one owner for singleton wellness events
When two AIAssistant widgets are mounted, both hook instances consume the same singleton-store sequence and can observe the same request-pending snapshot before either rerenders. A PROVIDE_WELLNESS_BREAK event therefore makes each instance call enterBreak, issuing duplicate setAgentState/setAgentChannelState requests and starting independent callbacks and timelines; direct offers similarly create one response timer per instance. Claim the event centrally or designate a single lifecycle owner before invoking enterBreak.
AGENTS.md reference: AGENTS.md:L87-L88
Useful? React with 👍 / 👎.
| </div> | ||
| ); | ||
|
|
||
| return overlayScope === 'custom' ? createPortal(overlay, resolvedTarget as HTMLElement) : overlay; |
There was a problem hiding this comment.
Portal viewport overlays outside the widget root
When the default viewport mode is used inside a transformed host container, returning the overlay inline makes its position: fixed box use that ancestor as its containing block and potentially inherit its clipping/stacking context. The purported viewport overlay can consequently cover only the embedded widget area rather than the browser viewport; portal viewport mode into the overlay's ownerDocument.body, while retaining the selected element for custom mode.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 88a2cf9. The default viewport overlay portals to document.body; assistant and custom targets retain their own scope. A test renders inside a transformed host and checks the overlay parent.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 2208d08780
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const leaks = declarationRoots.flatMap((root) => | ||
| collectDeclarations(root).flatMap((file) => { |
There was a problem hiding this comment.
Restrict the API check to public declarations
This scans every emitted declaration under the store and AI Assistant packages, including intentionally internal source modules. The target tree explicitly emits names such as AgentChannelStateDetail, setAgentChannelState, UseWellnessBreakInput, and useWellnessBreak, so yarn workspace @webex/cc-widgets build always reaches a nonempty leaks array and throws; on a clean standalone build it can instead fail because the dependency dist roots do not exist. Traverse declarations reachable from each package's public entry point rather than all .d.ts files.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed. check-public-api.mjs and its build hook were removed because this check is not needed for this PR. The @webex/cc-widgets build now uses the normal TypeScript command and passes.
| } catch { | ||
| runInAction(() => { | ||
| this.wellbeingBreakIdleCode = undefined; | ||
| this.wellnessBreakState = { |
There was a problem hiding this comment.
Ignore stale wellness-code requests
If a code lookup from an earlier login/session rejects after logout, relogin, or a newer successful lookup, this unconditional catch clears the current session's valid wellbeingBreakIdleCode and replaces its lifecycle with SYSTEM_CODE_UNAVAILABLE. Since captureWellnessSession can start overlapping lookups, a transient failure from the older request can disable wellness for the newly active session; capture a request/session token and apply both success and failure only while it remains current.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed. Each wellness-code lookup has a generation token. Both success and failure paths ignore a result from an older request or inactive login. Store tests cover stale lookup results.
| const clearHistory = useCallback(() => { | ||
| setHistory([]); | ||
| setContentCleared(true); | ||
| setRequestAvailable(false); | ||
| setNotice(undefined); | ||
| setError(undefined); | ||
| activeOfferHistoryIdRef.current = undefined; |
There was a problem hiding this comment.
Keep an active offer actionable after Clear
When the agent presses Clear while a direct offer is pending, setting contentCleared hides both the in-panel offer and its toast, while the lifecycle remains offer-pending and its timeout continues. The agent can no longer choose Take a break or Later and the backend eventually receives NO_RESPONSE; clear historical entries without hiding or discarding the currently actionable offer.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 88a2cf9. Clear removes prior transcript entries but retains the current actionable offer. The regression test verifies that the agent can still respond.
| if (settleTimerRef.current === undefined) { | ||
| settleTimerRef.current = window.setTimeout(() => { | ||
| settleTimerRef.current = undefined; | ||
| beginStartingCountdown(); | ||
| }, WELLNESS_STATE_SETTLE_MS); |
There was a problem hiding this comment.
Restart the settle window after any unsafe transition
Once this timer is armed, the !safe && phase === 'waiting-for-safe-state' case does not cancel it. If a task or unconfirmed channel appears during the two-second window and becomes safe again before the timer fires, the countdown begins using the original deadline rather than requiring two continuous safe seconds; clear settleTimerRef whenever safety is lost so each safe transition receives the full settling period.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed in 88a2cf9. An unsafe transition cancels the pending settle timer. A later safe transition starts a fresh two-second window; the task-blocker regression test covers this.
…s' into codex/wxcc-12423-wellness-widgets
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 44f746414d
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| capturedRef.current = undefined; | ||
| clearRecoveryMarker(); |
There was a problem hiding this comment.
Preserve recovery marker during initial session hydration
When the component mounts before login/relogin hydration, previousSessionRef initially contains an empty ID, so the first nonempty agentSessionId is treated as a rotation and deletes the refresh-recovery marker here. Because the recovery effect is declared later, it then finds no marker and cannot restore an agent who refreshed while still in WellbeingBreak, potentially leaving the agent unroutable; retain the marker across empty-to-current hydration and let its stored session ID determine whether it is stale.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
The hook-side marker handling is addressed: empty-to-current session hydration preserves the recovery marker, with a regression test. End-to-end refresh recovery still depends on the registration session projection proposed in webex/webex-js-sdk#5251.
| await api.requestWellnessBreak(); | ||
| } catch { | ||
| setPhase('idle'); | ||
| reportError('ACTION_REQUEST_FAILED', 'request-pending', true); |
There was a problem hiding this comment.
Ignore failures from superseded manual requests
If an agent logs out or relogs while requestWellnessBreak() is pending, a later rejection from that old request still executes this catch and changes the new session's global lifecycle to error. Depending on effect timing, the new session then shows either a stale ACTION_REQUEST_FAILED message or a blank wellness-owned body after its local error was cleared; capture the request's operation/session and ignore its completion after session rotation.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Addressed. requestBreak captures the operation and session before awaiting the SDK call. A rejection from a superseded request is ignored; the session-change regression test covers it.
|
|
||
| const baseConfig = require('../../../webpack.config'); | ||
|
|
||
| // Preserve dynamic imports so the wellness renderer stays out of the initial bundle. |
There was a problem hiding this comment.
[P2] Limit code splitting to the Lottie renderer
This changes the TypeScript module mode for the entire cc-components package to preserve one dynamic import. The production build emits 4,893 JavaScript chunks totaling 30.6 MiB under assets/wellness, and the package publishes all of dist/. That broad override also creates the downstream dependency-output copy layers. Use a targeted loader, entry, or explicitly named renderer chunk so one lazy dependency does not split every dynamic Momentum import or require multi-hop artifact copying.
There was a problem hiding this comment.
The wellness-specific packaging change is in 88a2cf9: Lottie and the MP3 moved to @webex/cc-ai-assistant, and the broad TypeScript override was removed from cc-components. A clean production build emits no wellness media from cc-components. It still emits 4,894 JavaScript files (33.2 MiB), so I am not claiming the package-wide chunk count is fixed by this PR.
| if (phaseRef.current === 'offer-pending' || phaseRef.current === 'request-pending') { | ||
| store.setWellnessBreakState({phase: 'idle'}); | ||
| } | ||
| if (captured && latest.agentSessionId && ACTIVE_PHASES.has(phaseRef.current)) { |
There was a problem hiding this comment.
[P1] Serialize unmount restoration with the normal lifecycle
This cleanup submits the captured pre-break state for every active phase without checking the current agent state. If a supervisor, RONA, or other external transition occurred during playback, unmount overwrites it; if unmount happens while the initial change-to-break request is pending, this write can race the late transition and leave the agent in WellbeingBreak after the widget is gone. It also bypasses the documented retries and swallows failure. Route unmount through the same guarded, serialized restore owner as performRestore, with regression coverage for both pending acceptance and an external transition.
There was a problem hiding this comment.
Addressed in 88a2cf9. Unmount waits for an in-flight change-to-break request and uses the shared guarded Available-restore function, which checks ownership and retries. Tests cover pending acceptance and an external state during playback.
| this.wellnessBreakState = {phase: 'idle'}; | ||
| }); | ||
| } | ||
| } else if (response.isAgentLoggedIn) { |
There was a problem hiding this comment.
[P1] Hydrate the wellness session after register/silent relogin
This branch loads the idle code for an already logged-in profile, but registerCC() never populates wellnessAgentSessionId or the legacy state fields. In SDK next.126, registration performs silentRelogin(), stores agentSessionId privately, returns a Profile without it, and emits no relogin event. A fresh page load therefore leaves the ID empty, while useWellnessBreak requires it for both UI enablement and refresh recovery; repeated registration can instead retain a stale ID. Add a supported SDK projection or event for the active session and cover the already-logged-in registration path.
There was a problem hiding this comment.
The widgets registration path is prepared to read Profile.agentSessionId when present, but the pinned SDK 3.12.0-next.131 does not yet project it after silent relogin. SDK PR webex/webex-js-sdk#5251 adds that projection. The current widget unit test uses a projected mock profile, so refresh hydration remains dependent on that SDK PR being merged and published; we will validate against the published SDK before treating this as resolved.
| this.wellnessBreakState = {phase: 'idle'}; | ||
| }); | ||
| } | ||
| } else if (response.isAgentLoggedIn) { |
There was a problem hiding this comment.
[P1] Wellness session is not hydrated for already-logged-in agents after registration
When register() returns isAgentLoggedIn === true—the page-refresh path—this branch only loads the system idle code. wellnessAgentSessionId, legacyAgentState, and legacyAuxCodeId are populated only from station-login/relogin events, while the SDK Profile does not contain the session ID.
After refresh, useWellnessBreak therefore remains disabled because agentSessionId is empty. Manual requests fail with SESSION_UNAVAILABLE, active notifications cannot be accepted, and refresh recovery cannot restore the prior state. Please hydrate the current SDK session/state during registration or expose a registration snapshot, and add a page-refresh regression test.
There was a problem hiding this comment.
Agreed. registerCC() reads the session from the returned profile, and the page-refresh widget test covers that shape, but the installed SDK 3.12.0-next.131 does not return Profile.agentSessionId yet. SDK PR webex/webex-js-sdk#5251 adds the logged-in registration snapshot after silent relogin. This thread remains open until that SDK change is published, consumed here, and verified with the actual package.
| setRequestAvailable(false); | ||
| setNotice(undefined); | ||
| setError(undefined); | ||
| capturedRef.current = undefined; |
There was a problem hiding this comment.
[P2] Invalidate the actionable offer when the station session rotates
Session rotation clears timers and captured state but does not resolve or disable activeOfferHistoryIdRef. The previous offer remains rendered with enabled actions, despite the contract saying session rotation invalidates it. If a later offer arrives, clicking the old card can invoke the handler using the newer event. Mark outstanding offers non-actionable during session rotation while preserving them in the transcript.
There was a problem hiding this comment.
Addressed in 88a2cf9. Session rotation marks the previous offer non-actionable while leaving it in history. The regression test confirms its actions no longer call the SDK.
| logger, | ||
| wellbeingBreakIdleCode, | ||
| } = store; | ||
| const isWellnessBreakState = Boolean(wellbeingBreakIdleCode && currentState === wellbeingBreakIdleCode.id); |
There was a problem hiding this comment.
[P1] User State can echo an SDK-owned WellbeingBreak state back to the SDK
isCurrentStateExternallyManaged is false until the asynchronous system-code lookup completes. During login or refresh, currentState can already be WellbeingBreak, causing useUserState to execute updateAgentState(currentState) before the code is recognized as externally managed.
This can overwrite or echo a system-owned wellness state. Add a hydration/pending guard so the state-change effect cannot call setAgentState until wellness-code resolution has completed.
There was a problem hiding this comment.
Addressed. User State waits for the asynchronous wellness-code lookup before treating an observed WellbeingBreak state as a user-requested change. The regression test checks that no SDK state change is sent during hydration.
| window.clearInterval(timer); | ||
| return; | ||
| } | ||
| legacyRecoveryCountRef.current += 1; |
There was a problem hiding this comment.
[P1] Reset the background restore retry budget for each break
legacyRecoveryCountRef is never reset when a new break starts or when the station session changes. Once one restore failure consumes the five background attempts, any later break that encounters RESTORE_FAILED immediately skips its recovery attempts. Reset this counter per wellness-break lifecycle/session and add a test covering two consecutive breaks with restore failures.
There was a problem hiding this comment.
Addressed in 88a2cf9. legacyRecoveryCountRef resets when a break starts and when the station session changes. A regression test runs two breaks with restore failures and verifies a fresh retry budget.
|
Please add regression coverage for the four lifecycle cases identified above:
|
|
Regression coverage now covers asynchronous wellness-code resolution while WellbeingBreak is current, fresh restore retries across two breaks, and session rotation that keeps offer history but disables old actions. The registration-refresh test covers a profile with |
…llness-widgets # Conflicts: # packages/contact-center/store/package.json # widgets-samples/cc/samples-cc-react-app/package.json # yarn.lock
Thank you for your contribution! |
COMPLETES WXCC-12423
This pull request addresses
Adds Agent Wellness Break to the Contact Center AI Assistant widget. Agents can respond to a break offer or request a break, see the timed break experience, and return to
Availablewhen the break ends.Vidcast: https://app.vidcast.io/share/2d163aaa-6444-49a5-82fd-861d360b59c3
by making the following changes
@webex/cc-store.@webex/cc-ai-assistantand load them when needed.WellbeingBreakstate out of ordinary User State changes and requestAvailable(auxCodeId: 0) after a completed break.@webex/contact-centerto3.12.0-next.131.Change Type
The following scenarios were tested
The GAI Coding Policy And Copyright Annotation Best Practices
Checklist before merging
Make sure to have followed the contributing guidelines before submitting.