Skip to content

feat(contact-center): add agent wellness breaks (WXCC-12423) - #741

Open
bhabalan wants to merge 5 commits into
webex:nextfrom
bhabalan:codex/wxcc-12423-wellness-widgets
Open

feat(contact-center): add agent wellness breaks (WXCC-12423)#741
bhabalan wants to merge 5 commits into
webex:nextfrom
bhabalan:codex/wxcc-12423-wellness-widgets

Conversation

@bhabalan

@bhabalan bhabalan commented Sep 7, 2026

Copy link
Copy Markdown
Contributor

COMPLETES WXCC-12423

This pull request addresses

Adds Agent Wellness Break support to the React Contact Center widgets, aligned with the existing WXCC Desktop flows and presentation.

VIDCAST: https://app.vidcast.io/share/2d163aaa-6444-49a5-82fd-861d360b59c3

by making the following changes

  • bridges wellness feature enablement, agent-scoped notifications, user actions, and agent-state restoration through the MobX store; State Control V2 compatibility stays behind an internal first-party bridge
  • accepts valid wellness nudges for the current agent even when the notification agentSessionId differs, while outbound actions use the active local session
  • adds Desktop-parity suggestion, direct-offer, denial, countdown, timed break, completion, toast, tooltip, audio, and metrics flows
  • preserves chronological wellness messages across close, minimize, reopen, and later events until the assistant Clear action is used
  • matches the Desktop assistant landing copy/icons and shows WellbeingBreak status with an elapsed timer during the break
  • restores the exact pre-break state, including legacy Idle auxiliary states such as Meeting, without echoing SDK-owned state transitions or exposing State Control V2 methods, events, store fields, hook inputs, or types to hosts
  • exposes wellnessBreakOverlayTarget for viewport, assistant-container, or custom-element rendering in React and Web Components
  • documents the feature contracts, adds generated-declaration guards for the store, AI Assistant, and aggregate widget packages, and retains internal legacy/V2 unit coverage

The local SDK link and sample-development webpack/package changes used during development are intentionally excluded. Runtime integration depends on the matching @webex/contact-center Wellness Break API/event surface.

Change Type

  • Bug fix (non-breaking change which fixes an issue)
  • New feature (non-breaking change which adds functionality)
  • Breaking change (fix or feature that would cause existing functionality to change)
  • Documentation update
  • Tooling change
  • Internal code refactor

The following scenarios were tested

  • The testing is done with the amplify link
  • Full repository pre-commit unit suite passed
  • @webex/cc-components: 57 suites, 941 passing tests, 4 skipped tests, 234 snapshots
  • @webex/cc-user-state: 2 suites, 26 passing tests
  • Store wellness, AI Assistant wellness, and touched shared-component tests passed
  • Store, shared components, user state, AI Assistant, and CC Widgets builds passed; emitted declarations contain no State Control V2 public symbols
  • Store, shared components, user state, and AI Assistant lint passed

The repository-wide style check completed with zero errors and six existing sample-app warnings.

The GAI Coding Policy And Copyright Annotation Best Practices

  • GAI was not used (or, no additional notation is required)
  • Code was generated entirely by GAI
  • GAI was used to create a draft that was subsequently customized or modified
  • Coder created a draft manually that was non-substantively modified by GAI (e.g., refactoring was performed by GAI on manually written code)
  • Tool used for AI assistance (GitHub Copilot / Other - specify)
    • Github Copilot
    • Other - OpenAI Codex
  • This PR is related to
    • Feature
    • Defect fix
    • Tech Debt
    • Automation

Checklist before merging

  • I have not skipped any automated checks
  • All existing and new tests passed
  • I have updated the testing document
  • I have tested the functionality with amplify link

Make sure to have followed the contributing guidelines before submitting.

@bhabalan
bhabalan requested a review from a team as a code owner September 7, 2026 05:01
@bhabalan bhabalan added the validated Indicates that the PR is ready for actions label Sep 7, 2026
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Sep 7, 2026

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review Completed 2026-09-09T07:02:32.676822Z b392109 New commits
ℹ️ 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" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +328 to +329
? await import('./assets/WellnessBreakAnimationDark.json')
: await import('./assets/WellnessBreakAnimationLight.json');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +72 to +75
/** 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>;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +1893 to +1896
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);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +89 to +90
const legacyRecoveryCountRef = useRef(0);
const ascReconnectAttemptedRef = useRef(false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@aws-amplify-us-east-2

Copy link
Copy Markdown

This pull request is automatically being deployed by Amplify Hosting (learn more).

Access this pull request here: https://pr-741.d1b38q61t1z947.amplifyapp.com

@github-actions github-actions Bot removed the validated Indicates that the PR is ready for actions label Sep 9, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +521 to +524
!this.store.isWellnessBreakEnabled ||
!payload?.agentSessionId ||
payload.agentId !== this.store.agentId ||
!WELLNESS_NOTIFICATION_ACTIONS.has(payload.actionEvent)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +583 to +587
await api.respondToWellnessBreak({
agentId: latest.agentId,
agentSessionId: latest.agentSessionId,
action: 'ACCEPTED',
});

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +1020 to +1023
void groups.reduce(
(sequence, group) =>
sequence.then(() =>
store.cc

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +942 to +946
capturedRef.current = {
stateModel: marker.stateModel,
theme: input.theme,
channelTypes: marker.channelTypes || [],
preBreakChannelStates: marker.preBreakChannelStates || {},

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +851 to +855
if (input.rtdStatus.state === 'disconnected') {
clearOfferTimer();
if (phaseRef.current === 'offer-pending' || phaseRef.current === 'request-pending') {
setRequestAvailable(false);
setPhase('idle');

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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) {

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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 👍 / 👎.

Comment on lines +203 to +207
.wellness-break-overlay--assistant,
.wellness-break-overlay--custom {
padding: 0;
position: absolute;
}

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Comment on lines +25 to +27
fs.readdirSync(this.source)
.filter((filename) => filename.includes('lottie-web'))
.forEach((filename) => fs.copyFileSync(path.join(this.source, filename), path.join(this.destination, filename)));

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 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".

Comment on lines +724 to +726
if (phaseRef.current === 'request-pending') {
logWellnessMetric(WELLNESS_METRIC.PROVIDE_BREAK_EVENT_RECEIVED);
void enterBreak(event, false);

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge 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;

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Badge 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 👍 / 👎.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant