feat(contact-center): wxcc-6026 wxapp answer, usersub publish, mercury mute sync - #5167
Conversation
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: ba7574076f
ℹ️ 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 this.ensureWxAppMercuryAndSubscribe(); |
There was a problem hiding this comment.
Publish the enabled state during station login
When enableAnswerOnWebex is supplied in the initial SDK configuration—the path used by the updated sample—a successful station login only starts the Mercury subscription here. The only usersub publication of true is inside the runtime setter, so callers that configure the feature before initialization never publish answer-calls-on-wxcc and Webex App continues showing its competing call notification.
Useful? React with 👍 / 👎.
| if (mercury && this.boundMuteHandler) { | ||
| mercury.off(TELEPHONY_CALL_MUTED); | ||
| } | ||
| if (mercury && this.boundUnmuteHandler) { | ||
| mercury.off(TELEPHONY_CALL_UNMUTED); |
There was a problem hiding this comment.
Pass the stored handlers when unsubscribing
When this feature is disabled, station logout runs, or subscribe() refreshes its registration, these off(event) calls remove every Mercury listener for the event rather than only this service's callbacks. Since Mercury is shared by SDK plugins and host code, another consumer of the telephony mute events will silently stop receiving them; pass boundMuteHandler and boundUnmuteHandler back to off.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L75-L76
Useful? React with 👍 / 👎.
| const refreshTime = ttl * 1000 - EXPIRATION_OFFSET_MS; | ||
| this.refreshTimer = setTimeout(() => { | ||
| this.setManageWebexCallingInWxcc(true, {userId, ttl, appName: this.appName}).catch( |
There was a problem hiding this comment.
Invalidate an in-flight refresh during teardown
If disable/logout occurs after this timer fires but while its setManageWebexCallingInWxcc(true) request is still pending, teardown() sets the state false but the stale request can subsequently set it back to true and schedule another refresh. The logged-out instance can therefore resume publishing the enabled state indefinitely; use a generation/cancellation check before applying a refresh result or scheduling its successor.
Useful? React with 👍 / 👎.
| // WXCC-6026: wxApp mute toggle handler | ||
| async function toggleWxAppMute() { | ||
| if (!currentTask || !isAnswerOnWebexEnabled) return; | ||
| try { | ||
| await currentTask.toggleMuteOnWebex(); |
There was a problem hiding this comment.
Wire the wxApp mute handler into the sample UI
For an enabled wxApp Voice task, the SDK now exposes the mute control, but this newly added handler has no callers: both the existing mute button and task-control action map still invoke muteUnmute(), which calls the unsupported currentTask.toggleMute() on a non-WebRTC Voice. Consequently the sample's visible wxApp mute button fails instead of invoking toggleMuteOnWebex().
Useful? React with 👍 / 👎.
| * Updates config, publishes usersub state, and refreshes uiControls on active tasks. | ||
| * @public | ||
| */ | ||
| public async setManageWebexCallingInWxcc(enabled: boolean): Promise<void> { |
There was a problem hiding this comment.
Update the canonical contracts for the new public API
This public runtime toggle is added alongside a new configuration flag, task methods, event, and UI-control field, but the commit does not update the owning module specification, ai-docs/CONTRACTS.md, or .sdd/manifest.json. That leaves the package's authoritative contract metadata inconsistent with the exported behavior and violates the package requirement to update those artifacts in the same change.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L52-L53
Useful? React with 👍 / 👎.
| @@ -0,0 +1,113 @@ | |||
| import {expect} from '@jest/globals'; | |||
There was a problem hiding this comment.
Use the mandated test helpers in the new suites
The new test suites use Jest expect, spies, and jest.fn() throughout instead of the repository-required Sinon mocks/stubs and assert from @webex/test-helper-chai; call assertions also omit the prescribed assert.calledOnceWithExactly. Convert these additions to the package's mandated test helpers so they follow the repository's test contract.
AGENTS.md reference: AGENTS.md:L43-L47
Useful? React with 👍 / 👎.
| it('returns undefined when agentId is undefined', () => { | ||
| expect(getWebexCallingDeviceDetailsForAgent(undefined, participantsById)).toBeUndefined(); |
There was a problem hiding this comment.
Parameterize the repeated device-detail cases
This describe block adds more than three same-shaped validation cases separately—for undefined or empty agent IDs, null or array participants, and missing device fields—despite the repository rule requiring parameterized tests once there are more than three similar cases. Consolidate these inputs into a table-driven test to avoid duplicated setup and assertions.
AGENTS.md reference: AGENTS.md:L49-L50
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c85bbce8e9
ℹ️ 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".
| protected onTaskAssigned(): void { | ||
| this.syncWxAppMuteFromCallDetails().catch(() => undefined); | ||
| } |
There was a problem hiding this comment.
Seed mute state when hydrating an active task
When an already-connected wxApp task is reconstructed from AgentContact after a reconnect or hydration, TaskManager sends HYDRATE, not ASSIGN, so this hook never runs and wxAppMuted remains its default false. If the actual call is muted, the next no-argument toggleMuteOnWebex() therefore sends another mute request rather than unmuting; invoke the call-details sync for hydrated engaged tasks as well.
Useful? React with 👍 / 👎.
| Object.values(interaction.participants).some( | ||
| (participant: {deviceType?: string}) => participant?.deviceType === 'wxApp' | ||
| ) |
There was a problem hiding this comment.
Require the current agent to be the wxApp participant
When another participant is using wxApp but the current agent is using a different device type, this repo-wide some() check combines that other participant with the current agent's generic device details and marks the task as a wxApp offer/engaged call. The resulting accept, mute, and keypad controls are enabled even though getCallingDeviceDetails() requires the current agent's own participant to have deviceType === 'wxApp', so the exposed actions subsequently reject; derive this predicate from the current agent's participant/device details instead.
Useful? React with 👍 / 👎.
| export function mapWxAppVoiceError(error: unknown, method: string, module: string): never { | ||
| const {error: detailedError} = getErrorDetails(error, method, module); | ||
| throw detailedError; |
There was a problem hiding this comment.
Preserve structured REST failures through the voice wrapper
When a telephony request rejects with backend details, AnswerCallOnWebexService.telephonyRequest() has already passed it through getErrorDetails() and thrown a plain Error containing the backend reason. Calling getErrorDetails() again here sees no .details and replaces that useful reason with Error while performing <method> for all four public wxApp actions, preventing callers from distinguishing actionable REST failures; avoid remapping an error that the service has already normalized.
Useful? React with 👍 / 👎.
| } catch (error) { | ||
| LoggerProxy.error(`Failed to publish answer-calls-on-wxcc cross-client state: ${error}`, { | ||
| module: CC_FILE, | ||
| method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC, | ||
| }); |
There was a problem hiding this comment.
Propagate failures from the runtime usersub publish
When the usersub request fails during the public runtime toggle, this catch logs and swallows the rejection, so setManageWebexCallingInWxcc(true) resolves after enabling task controls even though Webex App toast suppression was not published. Because the failed service call also never schedules its refresh timer, the state remains unpublished indefinitely and the caller has no signal to retry; propagate the failure for the runtime setter or roll back the local enablement.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 73694e0e23
ℹ️ 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 this.publishAnswerOnWebexCrossClientState(false); | ||
| this.webexCrossClientService.teardown(); | ||
| this.wxAppTelephonyMercurySync.unsubscribe(); |
There was a problem hiding this comment.
Always finish teardown when the usersub publish fails
When the agent logout has already succeeded but this usersub request rejects, control jumps to the catch before webexCrossClientService.teardown() and wxAppTelephonyMercurySync.unsubscribe(). The caller is told logout failed even though the agent is logged out remotely, while the existing refresh timer and Mercury listener remain active and can continue publishing the enabled state; run the local teardown unconditionally while surfacing the publish failure separately.
Useful? React with 👍 / 👎.
| await this.publishCrossClientState([userId], ttl, composition, 'setManageWebexCallingInWxcc'); | ||
|
|
||
| if (operationGeneration !== this.refreshGeneration) { | ||
| return; |
There was a problem hiding this comment.
Compensate for stale enabled-state publishes
If disable, logout, or teardown occurs while an enable publish is in flight, this generation check runs only after the remote request has completed, so it suppresses local state changes but cannot undo the already-sent answer-calls-on-wxcc: true composition. A late request can therefore leave Webex App notifications suppressed for the 900-second TTL after the feature was disabled; fresh evidence beyond the prior teardown concern is that the current cancellation check remains after await publishCrossClientState() without issuing a compensating false publish.
Useful? React with 👍 / 👎.
| private assertWxAppStationLoginSupportedForEnable(): void { | ||
| if (this.agentConfig?.deviceType === LoginOption.BROWSER) { | ||
| throw new Error( | ||
| 'setManageWebexCallingInWxcc is not supported for BROWSER (Desktop) login. Use EXTENSION or AGENT_DN.' | ||
| ); |
There was a problem hiding this comment.
Validate the current station login type
For a normal station login, the success path calls webCallingService.setLoginOption() but never writes the selected login option to agentConfig.deviceType (that assignment only occurs in the silent-relogin handleDeviceType() path). This check can consequently read an absent or stale Extension value after the agent logs in with BROWSER and allow an explicitly unsupported enable operation, publishing toast suppression even though wxApp controls cannot operate on the browser participant; validate against the actual station-login selection or persist it on success.
Useful? React with 👍 / 👎.
| // WXCC-6026: wxApp DTMF transmit handler | ||
| async function transmitWxAppDtmf(digit) { | ||
| if (!currentTask || !isAnswerOnWebexEnabled) return; |
There was a problem hiding this comment.
Connect the wxApp DTMF handler to a sample control
This newly added function has no callers: the existing keypad invokes pressKey(), which only appends to the outdial number, while the task-control renderer omits keypad from both actionOrder and actionMap. Thus an engaged wxApp task can expose uiControls.keypad as enabled, but the sample provides no action that calls transmitDtmfOnWebex(), leaving the demonstrated DTMF feature unusable.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: b7675e2897
ℹ️ 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.wxAppMuteSyncInFlight = syncPromise.finally(() => { | ||
| if (this.wxAppMuteSyncInFlight === syncPromise) { | ||
| this.wxAppMuteSyncInFlight = undefined; |
There was a problem hiding this comment.
Clear the completed mute-sync promise
After the first call-details sync settles, this comparison can never succeed because wxAppMuteSyncInFlight holds the promise returned by .finally(), not syncPromise. Every later sync therefore returns the permanently cached result without issuing another GET, so re-enabling the feature or attempting a reconnect backfill cannot recover mute changes missed by Mercury; compare against the wrapped promise or otherwise clear the field when that operation completes.
Useful? React with 👍 / 👎.
| if (isAnswerOnWebexEnabled && currentTask.isWebexAppCallingOffer && currentTask.isWebexAppCallingOffer()) { | ||
| await currentTask.rejectOnWebex(); |
There was a problem hiding this comment.
Route wxApp outdial declines through cancelTask
For a wxApp OUTDIAL offer, isWebexAppCallingOffer() is true and the new UI controls explicitly enable decline, but this branch calls rejectOnWebex(), whose inbound-only guard rejects that task. The sample consequently logs an error and leaves the outgoing offer active; distinguish inbound from outdial here and invoke currentTask.decline() for the outdial cancellation path.
Useful? React with 👍 / 👎.
| await deps.answerCallOnWebexService.answerCall({ | ||
| callId: details.deviceCallId, | ||
| endpointId: details.deviceId, | ||
| lineOwnerId: options?.lineOwnerId, |
There was a problem hiding this comment.
Default shared-line requests to the task's owner
When the wxApp participant carries a base64 lineOwnerId for a shared-line call, zero-argument calls such as the sample's acceptOnWebex() send undefined here, and the other action wrappers do the same; the call-details path instead forwards the encoded participant value unchanged. Thus the newly added decodedLineOwnerId() helper is never used and shared-line answer/reject/mute/DTMF requests lack the owner identifier expected by the telephony API. Derive and decode the participant owner when the caller does not explicitly override it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: c66156e217
ℹ️ 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".
| // AgentOutboundFailed. Normalize to OUTBOUND_FAILED so state machine | ||
| // enters WRAPPING_UP and emits TASK_OUTDIAL_FAILED (AGENT_ENDS). | ||
| // Pre-accept cancelTask decline stays CONTACT_ENDED → TERMINATED (no wrapup). | ||
| if (TaskManager.isAgentTerminatedOutdialWrapup(payload, wrapUpRequired, task)) { |
There was a problem hiding this comment.
Restrict the failure remap to wxApp outdial tasks
For any ordinary Browser or extension OUTDIAL call that the agent ends, a ContactEnded payload with terminatingParty: 'Agent' and pending wrap-up now satisfies this predicate even when the task is not a wxApp call. The event is consequently remapped to OUTBOUND_FAILED, causing consumers to receive task:outdialFailed with AGENT_ENDS and potentially show a failure popup after a successfully connected call; require the task's current-agent participant/config to identify a wxApp-managed call before applying this special mapping.
Useful? React with 👍 / 👎.
| if (isWxAppInboundOffer) return VISIBLE_ENABLED; | ||
| if (isWxAppOutdialOffer) return VISIBLE_ENABLED; |
There was a problem hiding this comment.
Disable decline while a wxApp answer is pending
After acceptOnWebex() sets wxAppAnswerPending, the task remains OFFERED until the backend assignment arrives, but both inbound and outdial branches still expose an enabled decline control. A user can therefore click decline during the answer request and issue a telephony reject or cancelTask concurrently with the in-flight answer, leaving the Webex call and Contact Center task with conflicting outcomes; return a disabled decline control while wxAppAnswerPending is true.
Useful? React with 👍 / 👎.
| return false; | ||
| } | ||
|
|
||
| if (!state || state === TaskState.OFFERED || state === TaskState.IDLE) { |
There was a problem hiding this comment.
Exclude terminal states from wxApp engagement
When a wxApp task reaches TERMINATED or COMPLETED while its participant details are still present, this predicate continues to classify it as engaged because it excludes only IDLE and OFFERED. The terminal state update then publishes enabled mute and keypad controls, and the matching call-ID helper likewise lets retained task objects send telephony actions against the ended call; limit engagement to the active call states rather than treating every non-offer state as active.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7ab610eecf
ℹ️ 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".
| ? {isVisible: true, isEnabled: isWebrtc && !isOutdial} | ||
| : DISABLED, | ||
| accept: (() => { | ||
| if (isWxAppInboundOffer) return VISIBLE_ENABLED; |
There was a problem hiding this comment.
Disable inbound accept while an answer is pending
For an inbound wxApp offer, this branch always keeps Accept enabled even after acceptOnWebex() sets wxAppAcceptInFlight and wxAppAnswerPending. Until the backend assignment changes the task from OFFERED, a double-click can therefore send multiple concurrent /answer requests; apply the same pending/in-flight check already used for outdial offers.
Useful? React with 👍 / 👎.
| await this.publishAnswerOnWebexCrossClientState(false); | ||
| this.webexCrossClientService.teardown(); | ||
| this.wxAppTelephonyMercurySync.unsubscribe(); |
There was a problem hiding this comment.
Always tear down wxApp state during deregistration
When deregistering while answer-call suppression is active, a usersub failure at this await exits before webexCrossClientService.teardown(), Mercury unsubscription, and the runtime flag reset. The SDK has already removed its other listeners and sockets, but the refresh timer and mute listeners can remain active and keep publishing suppression after deregistration; run the local cleanup unconditionally while surfacing the publish failure separately.
Useful? React with 👍 / 👎.
| const isOutdial = this.data?.interaction?.outboundType === 'OUTDIAL'; | ||
| if (!this.enableAnswerOnWebex || !isOutdial) { | ||
| super.unsupportedMethodError(METHODS.REJECT); |
There was a problem hiding this comment.
Restrict outdial decline to an offered wxApp task
Whenever the global flag is enabled, this allows decline() for every outdial Voice task regardless of its current state or participant device type. A caller can consequently invoke it on a normal extension outdial or an already connected wxApp outdial and send cancelTask, although this API was added only for the offered wxApp cancellation path; require isWebexAppCallingOffer() before issuing the request.
Useful? React with 👍 / 👎.
|
As per discussion, Can we do below implementation: Have a simplistic API's expose a public method in cc.ts to enable answer on webex (which we are already doing) now based on this in Voice class we should updayte ui controls accordingly to show or hide accept decline buttons. now based on this flag we call answer API(webex api) etc on Voice class accept method. in this way the consumer(sample app or widgets) dont need to worry about all these flags and conditions, they just need to enable the feature using cc.enableAnwerOnWebex..... |
| public async decline(): Promise<TaskResponse> { | ||
| super.unsupportedMethodError(METHODS.REJECT); | ||
| const isOutdial = this.data?.interaction?.outboundType === 'OUTDIAL'; | ||
| if (!this.enableAnswerOnWebex || !isOutdial) { |
There was a problem hiding this comment.
can you verify this in webrtc flow, as per this condition normal decline might break
There was a problem hiding this comment.
Verified — normal WebRTC decline is unaffected.
WebRTC overrides decline() and calls webCallingService.declineCall() directly; it never enters Voice.decline() wxApp routing.
The wxApp outdial cancelTask path in Voice.decline() is gated by isWebexAppCallingOffer(), which requires TaskState.OFFERED plus the current agent's wxApp participant. Connected or non-wxApp outdial tasks therefore hit unsupportedMethodError, not cancelTask.
Unit coverage: WebRTC.ts spec (decline() calls declineCall) and Voice.ts unified API routing tests for wxApp inbound/outdial gates.
| } | ||
|
|
||
| export async function acceptOnWebex( | ||
| deps: WxAppVoiceDeps, |
There was a problem hiding this comment.
what is deps and WxAppVoiceDeps can we have a full form of this?
There was a problem hiding this comment.
WxAppVoiceDependencies is the full name (dependencies injected into wxApp voice helpers). WxAppVoiceDeps remains as a deprecated alias for backward compatibility in internal imports.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 612eb2fda0
ℹ️ 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".
| * not Browser/Desktop (WebRTC) login. | ||
| * @private | ||
| */ | ||
| private assertWxAppStationLoginSupportedForEnable(): void { |
There was a problem hiding this comment.
Reject enablement until a supported station is selected
The fresh gap in the station-type validation is the pre-station-login case: after Webex device authentication but before stationLogin(), getCurrentStationLoginOption() is undefined, so this negative check allows the public setter to publish answer-calls-on-wxcc: true and start its refresh timer. If the agent subsequently selects BROWSER, ensureWxAppPostStationLogin() returns immediately without disabling or publishing false, leaving Webex App notifications suppressed indefinitely for an unsupported login mode; require the option to be explicitly EXTENSION or AGENT_DN before enabling.
Useful? React with 👍 / 👎.
| if (mercury && !mercury.connected) { | ||
| await mercury.connect(); |
There was a problem hiding this comment.
Release Mercury resources opened for wxApp sync
When an Extension/AGENT_DN profile has WebRTC disabled or does not offer BROWSER login, this path can register the device and connect Mercury solely for wxApp mute sync, but deregister() disconnects/unregisters only under its existing webRtcEnabled && loginVoiceOptions.includes(BROWSER) guard. Unsubscribing the two mute handlers does not close that feature-created socket or device registration, so deregistering such an SDK instance leaves host resources active; track ownership and release resources opened here during teardown.
Useful? React with 👍 / 👎.
| this.syncWxAppMuteFromCallDetails().catch(() => undefined); | ||
| } | ||
|
|
||
| protected onTaskHydrated(): void { |
There was a problem hiding this comment.
Preserve answer-pending state across offered-task hydration
When an AgentContact HYDRATE arrives after wxApp answer succeeds but before ASSIGN moves the task out of OFFERED, this hook unconditionally clears wxAppAnswerPending. That immediately re-enables Accept and also removes the marker TaskManager.isAgentTerminatedOutdialWrapup() uses to distinguish an answered outdial from a pre-accept cancellation, so a reconnect/update during this window can permit another answer request or misclassify the subsequent ContactEnded; clear the flag only after assignment or when hydrated data proves the offer is no longer pending.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1e0b7d720c
ℹ️ 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".
| cc: { | ||
| allowMultiLogin: isMultiLoginEnabled, | ||
| disableWebRTCRegistration: isWebRTCRegistrationDisabled, | ||
| enableAnswerOnWebex: false, |
There was a problem hiding this comment.
Pass the persisted answer flag into SDK initialization
When the checkbox is persisted as enabled and the page reloads into the silent-relogin flow, this hardcoded false causes the SDK to publish the disabled cross-client state. The sample only calls the runtime setter from agent:stationLoginSuccess, while silent relogin emits agent:reloginSuccess, whose handler does not enable the feature, so Answer on Webex remains disabled despite the checked setting. Fresh evidence beyond the earlier initialization concern is that this revision explicitly ignores isAnswerOnWebexEnabled when generating the SDK configuration.
Useful? React with 👍 / 👎.
| const currentlyMuted = muteElm.innerText === 'Unmute'; | ||
| const intendedMuted = !currentlyMuted; |
There was a problem hiding this comment.
Derive the mute action from the SDK mute state
When Mercury or the call-details backfill changes the wxApp mute state, the SDK emits task:wxapp-mute-state-updated, but the sample never handles that event, so the button text used here can be stale. For example, after muting in Webex App while the button still says “Mute,” clicking it sends {muted: true} again instead of unmuting; listen for the new event/update the label, or let the SDK's no-argument toggle derive the target from getWxAppMuted().
Useful? React with 👍 / 👎.
| | `contact-center.user-preference` | Services/Config | `UserPreference`, `cc.userPreference`, `UserPreferenceData`, request/response types | `getUserPreference(params?)`, `createUserPreference(data)`, `updateUserPreference(userId, data)`, `deleteUserPreference(userId)` | semver public | `src/services/ai-docs/services-spec.md` | `src/index.ts`, `src/cc.ts`, `src/services/UserPreference.ts`, `src/services/config/types.ts` | | ||
| | `contact-center.preview-campaign` | Contact Center/Task | `acceptPreviewContact`, `skipPreviewContact`, `removePreviewContact` | `(payload: PreviewContactPayload) => Promise<TaskResponse>` | semver public | `ai-docs/contact-center-spec.md`, `src/services/task/ai-docs/task-spec.md` | `src/cc.ts`, `src/services/task/dialer.ts`, `src/services/task/types.ts` | | ||
| | `contact-center.state-controls` | Task state machine | `getDefaultUIControls`, task state/control types | function is exported directly from `uiControlsComputer.ts` by package root | semver public | `src/services/task/state-machine/ai-docs/task-state-machine-spec.md` | `src/index.ts`, `src/services/task/state-machine/uiControlsComputer.ts` | | ||
| | `contact-center.wxapp-answer` | Contact Center / Task | `enableAnswerOnWebex`, `setManageWebexCallingInWxcc`, `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); runtime toggle via setter; `setManageWebexCallingInWxcc(true)` rejects `BROWSER` login; mute backfill via GET call details + `getWxAppMuted()` on hydrate/refresh; SDK `Voice` routes wxApp telephony when flag active | semver public | `src/services/task/ai-docs/task-spec.md`, `ai-docs/contact-center-spec.md`, `ai-docs/WXCC-6026-wxapp-answer-flow.md` | `src/cc.ts`, `src/services/task/voice/Voice.ts`, `src/services/task/voice/wxAppVoiceMethods.ts` | |
There was a problem hiding this comment.
Point the wxApp contract at an existing specification
The new contract row links to ai-docs/WXCC-6026-wxapp-answer-flow.md, but that file does not exist anywhere in the package, and the manifest-designated owning ai-docs/contact-center-spec.md was not updated by this commit. Fresh evidence beyond the previous contract comment is that the attempted catalog remediation itself introduces a dead detail link, leaving the public configuration and runtime method without an owning canonical specification; update that spec and reference an existing document.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L66-L70
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a37df0fc44
ℹ️ 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.webCallingService.deregisterWebCallingLine(); | ||
| } | ||
|
|
||
| await this.teardownWxAppLocalState(); |
There was a problem hiding this comment.
Preserve the init flag across profile updates
When enableWxBetterTogether is initialized as true, updateAgentProfile() calls stationLogout() and then stationLogin() on the same SDK instance. This teardown resets $config.enableWxBetterTogether to false, so the subsequent login force-publishes false and no longer initializes wxApp answering; switching from BROWSER to EXTENSION—the documented profile-update example—therefore silently loses the feature. Preserve the init setting across this internal logout/login sequence while still clearing runtime resources for a genuine logout.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L83-L85
Useful? React with 👍 / 👎.
| method: METHODS.SET_MANAGE_WEBEX_CALLING_IN_WXCC, | ||
| } | ||
| ); | ||
| this.scheduleForcedFalsePublishRetry(); |
There was a problem hiding this comment.
Schedule rollback retries after disabling the flag
If the usersub true publish succeeds, Mercury initialization fails, and the compensating false publish also fails, this retry request is made while enableWxBetterTogether is still true. scheduleForcedFalsePublishRetry() consequently returns immediately because of its enabled-state guard, and the flag is reset only after this method returns, leaving the already-published suppression active until its TTL expires with no retry. Reset the flag before scheduling or allow rollback retries independently of the enabled guard.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L83-L85
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 1dc4aec77f
ℹ️ 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".
| } catch (error) { | ||
| publishError = error; |
There was a problem hiding this comment.
Retry failed teardown publishes
When stationLogout() calls this helper without rethrowPublishError, a transient usersub failure is only recorded before webexCrossClientService.teardown() clears the active-state marker and refresh timers. No forced-false retry is scheduled, so the last successful answer-calls-on-wxcc: true composition can continue suppressing Webex App call notifications for up to its 900-second TTL after the agent has logged out; schedule the existing bounded false-publish retry for this teardown path.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L83-L85
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 6a941e29a3
ℹ️ 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".
| // Clear any cached agent configuration | ||
| this.agentConfig = null; | ||
|
|
||
| await this.teardownWxAppLocalState({rethrowPublishError: true, clearInitFlag: true}); |
There was a problem hiding this comment.
Publish the disabled state before unregistering the device
When deregister() is called directly for a WebRTC-capable profile while Better Together is active, the preceding branch successfully calls device.unregister() before reaching this teardown; the device implementation then clears its state (packages/@webex/internal-plugin-device/src/device.js:721), including the URL that WebexCrossClientService.setManageWebexCallingInWxcc() requires. The false publish therefore fails, and its retries cannot recover without a device URL, leaving Webex App notifications suppressed until the prior 900-second composition expires. Move this teardown ahead of the generic device unregister or preserve the device identity needed for the publish.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L83-L85
Useful? React with 👍 / 👎.
| await this.publishAnswerOnWebexCrossClientState(false, {force: true}); | ||
| this.webexCrossClientService.teardown(); | ||
| this.wxAppTelephonyMercurySync.unsubscribe(); | ||
| await this.releaseWxAppMercuryResources(); |
There was a problem hiding this comment.
Cancel stale compensating retries after a new login
If the false publish during stationLogout() fails, an unconditional compensating retry is scheduled while the init flag remains enabled. When the agent logs back in within the 30-second retry delay, the successful true publish does not clear this ContactCenter-level timer, so this callback later publishes false and tears down Mercury for the newly active session. Cancel or generation-guard compensating retries when a subsequent enable/login succeeds.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L83-L85
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: e261e6f6ee
ℹ️ 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 this.publishAnswerOnWebexCrossClientState(true); | ||
| publishedEnable = true; | ||
| await this.ensureWxAppMercuryAndSubscribe(); |
There was a problem hiding this comment.
Register the device before publishing the enabled state
When an EXTENSION/AGENT_DN profile has not already connected Mercury—for example, webRtcEnabled is false or BROWSER login is unavailable—the WDM device can still be unregistered here. publishAnswerOnWebexCrossClientState(true) then either returns without publishing because device.userId is absent or fails because device.url is absent; the registration in ensureWxAppMercuryAndSubscribe() happens only afterward. Register/connect first and then publish, otherwise fresh extension-only sessions never suppress Webex App notifications.
Useful? React with 👍 / 👎.
| this.services.rtdWebSocketManager.off('message', this.handleRTDWebsocketMessage); | ||
| this.services.connectionService.off('connectionLost', this.handleConnectionLost); | ||
|
|
||
| await this.teardownWxAppLocalState({rethrowPublishError: true, clearInitFlag: true}); |
There was a problem hiding this comment.
Continue deregistration after a usersub publish failure
When the false usersub publish fails, this awaited helper performs the new local wxApp cleanup but then rethrows, so deregister() skips the Mercury/device cleanup, both WebSocket close() calls, and agentConfig reset below. Fresh evidence beyond the earlier teardown report is that wxApp-local cleanup now runs, but the broader SDK deregistration still aborts and leaves backend sockets active with their message handlers already removed; retain the publish error and throw it only after the remaining cleanup finishes.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L83-L85
Useful? React with 👍 / 👎.
| [TaskEvent.TASK_WRAPUP]: [ | ||
| { | ||
| guard: guards.shouldWrapUp, | ||
| target: TaskState.WRAPPING_UP, | ||
| actions: ['updateTaskData', 'markEnded', 'emitTaskWrapup'], |
There was a problem hiding this comment.
Document the new OFFERED wrap-up branch
This changes AGENT_WRAPUP while OFFERED to enter WRAPPING_UP whenever shouldWrapUp succeeds, and the mapper always supplies wrapUpRequired: true, but the owning canonical spec still states OFFERED TASK_WRAPUP unconditionally reaches TERMINATED at task-state-machine-spec.md lines 862-866 and again in the event table at line 1749. Update those state descriptions and diagrams so validators and future lifecycle changes use the implemented branch.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L52-L52
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 96088a9453
ℹ️ 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".
| publishedEnable = true; | ||
| this.taskManager.syncWxAppMuteFromCallDetailsForAllTasks(); | ||
| } else { | ||
| await this.publishAnswerOnWebexCrossClientState(false, {force: true}); |
There was a problem hiding this comment.
Register the device before the forced disabled publish
When a page reload starts with the flag disabled on an EXTENSION/AGENT_DN profile that did not connect Mercury during register() (for example, WebRTC is disabled or BROWSER login is unavailable), the WDM device is still unregistered. This forced publish does not register it: publishAnswerOnWebexCrossClientState() silently returns when device.userId is absent, or WebexCrossClientService rejects when the device URL is absent, and retries encounter the same state. Consequently, a prior session's true composition is not cleared and Webex App notifications remain suppressed until its 900-second TTL expires; establish the device identity before attempting this forced false publish.
Useful? React with 👍 / 👎.
| if (!enable && !options?.force && !this.webexCrossClientService.isAnswerCallsStateActive()) { | ||
| return; |
There was a problem hiding this comment.
Do not skip teardown while an enable publish is pending
If stationLogout() or deregister() overlaps an enable publish—for example, when a reconnect-triggered silent relogin races with logout—answerCallsState is still false until that request resolves, so this guard skips the compensating false publish. Teardown then changes the generation, but the outstanding true request can still be applied by usersub; the generation check only discards its local completion, leaving call-notification suppression active for the full TTL after teardown. Track pending enable operations or serialize the publish sequence so teardown always follows an in-flight true with false.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 7877419fab
ℹ️ 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".
| })) as IHttpResponse; | ||
|
|
||
| return (response.body ?? {}) as CallDetailsResponse; | ||
| } catch (error) { |
There was a problem hiding this comment.
Suppress expected call-detail failures before logging
When the post-accept mute backfill races call creation and Hydra returns the expected 400/“Call not found”/101002 response, this service logs the rejection as an error before Voice.syncWxAppMuteFromCallDetails() can classify and suppress it. The canonical task spec explicitly says these expected responses are not logged, but the existing Voice test mocks this service and therefore misses the lower-layer log; classify expected call-not-found responses here before calling LoggerProxy.error.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L46-L53
Useful? React with 👍 / 👎.
| if (this.wxAppMuteToggleInFlight) { | ||
| await this.wxAppMuteToggleInFlight; |
There was a problem hiding this comment.
Continue queued mute operations after an earlier failure
When two toggleMute() calls overlap and the first telephony request rejects, the second call rethrows that first rejection at this await and exits without issuing its own requested mute/unmute operation. This is especially visible for rapid no-argument retries: the second toggle never gets a chance to recompute its target from the unchanged mute state. Wait for settlement while swallowing the predecessor's error only within the queue, leaving the first caller's promise rejected independently.
Useful? React with 👍 / 👎.
| this.webCallingService.deregisterWebCallingLine(); | ||
| } | ||
|
|
||
| await this.teardownWxAppLocalState(); |
There was a problem hiding this comment.
Clear the Better Together flag on genuine station logout
When an application performs a genuine stationLogout() and later logs in again on the same SDK instance, this call tears down usersub and Mercury but does not pass clearInitFlag, so $config.enableWxBetterTogether and all task-manager flags remain enabled and the next station login republishes true. This contradicts the canonical teardown contract that logout resets runtime config; preserve the flag only for the internal updateAgentProfile() logout/login sequence while clearing it for the public logout path.
AGENTS.md reference: packages/@webex/contact-center/AGENTS.md:L46-L53
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 77a11a880a
ℹ️ 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 (wxAppAnswerPending && isWxAppInboundOffer) return VISIBLE_DISABLED; | ||
| if (isWxAppInboundOffer) return VISIBLE_ENABLED; | ||
| if (isWxAppOutdialOffer) return VISIBLE_ENABLED; |
There was a problem hiding this comment.
Disable outdial decline while answer assignment is pending
After a wxApp outdial answer request succeeds, runWxAppAccept() leaves wxAppAnswerPending true until ASSIGN arrives, but this branch immediately re-enables Decline once the REST request is no longer in flight. Clicking it during that window calls cancelTask() and clears the pending marker, canceling an already-answered outdial and causing the ensuing terminal event to be treated as a pre-accept cancellation. Keep Decline disabled while wxAppAnswerPending is true for outdial offers as well, and guard the public decline() path similarly.
Useful? React with 👍 / 👎.
| rethrowPublishError?: boolean; | ||
| clearInitFlag?: boolean; | ||
| }): Promise<{publishError?: unknown}> { | ||
| this.clearWxAppFalsePublishRetryTimer(); |
There was a problem hiding this comment.
Preserve pending forced-false retries during teardown
When the forced false publish at a disabled EXTENSION/DN login fails, ensureWxAppPostStationLogin() schedules a retry, but a station logout or deregistration within the 30-second delay cancels it here. Because the feature flag and service active marker are both false, the teardown's subsequent publish is skipped and no replacement retry is scheduled, so a prior session's true composition can continue suppressing Webex App notifications until its TTL expires. Do not clear the pending cleanup retry unless teardown confirms a false publish or schedules an equivalent retry.
Useful? React with 👍 / 👎.
mkesavan13
left a comment
There was a problem hiding this comment.
Just a few nitpicks. Rest looks great. Please go ahead and merge.
Thanks a lot @akulakum, @rsarika and @vivekv1504 for working on this one 💪🏻
| if (options) { | ||
| // parameter intentionally unused — WebRTC toggles from local stream state | ||
| } |
There was a problem hiding this comment.
nitpick: Do we really need this block?
There was a problem hiding this comment.
nitpick: Could have named wxAppVoiceUtils instead of methods
| if (options) { | ||
| // parameter intentionally unused | ||
| } |
There was a problem hiding this comment.
nitpick: Do we really need this and wherever similar blocks are empty?
| /** | ||
| * Update wxApp thick-client answer flag at runtime (Voice overrides). | ||
| */ | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| public setEnableWxBetterTogether(_enabled: boolean): void { | ||
| // no-op for non-voice tasks | ||
| } | ||
|
|
||
| /** | ||
| * Apply wxApp mute state from external sync (Voice overrides). | ||
| */ | ||
| // eslint-disable-next-line @typescript-eslint/no-unused-vars | ||
| public applyWxAppMuteStateFromSync(_incomingCallId: string, _muted: boolean): void { | ||
| // no-op for non-voice tasks | ||
| } | ||
|
|
||
| /** | ||
| * Hook for post-assign wxApp sync (Voice overrides). | ||
| */ | ||
| protected onTaskAssigned(): void { | ||
| // no-op by default | ||
| } | ||
|
|
||
| /** | ||
| * Hook for post-hydrate wxApp sync (Voice overrides). | ||
| */ | ||
| protected onTaskHydrated(): void { | ||
| // no-op by default | ||
| } |
There was a problem hiding this comment.
Once again, isn't it enough we declare it in types but not write an empty one here to override in Voice?
COMPLETES WXCC-6026
This pull request addresses
Third-party CRM embeds using the Contact Center SDK need Answer on Webex (“Better together”): agents answer, decline, mute, and send DTMF from the embed while Webex App (thick client) handles telephony on the same machine and user.
Without this change, the SDK had no wxApp telephony surface, no cross-client toast suppression, and no bidirectional mute sync with Webex App.
by making the following changes
Unified wxApp task API (Voice tasks — rsarika review)
Hosts and widgets use the standard
ITaskcontract only; SDKVoiceroutes wxApp telephony internally whenenableWxBetterTogetheris active:*OnWebexmethods onIVoice(acceptOnWebex,rejectOnWebex,toggleMuteOnWebex,transmitDtmfOnWebexremoved). Greenfield feature — no backward-compat aliases.wxAppVoiceMethods.ts):runWxAppAccept,runWxAppReject,runWxAppToggleMute,runWxAppTransmitDtmfwired viaVoice.createWxAppLifecycle()— not exposed on the task prototype (avoids DevTools leakingexecuteWxApp*helpers).WebexCallingUtils,AnswerCallOnWebexService.task.uiControls.main.*when backend sends wxApp participant + device details on the offer.InteractionUIControls.keypadpromoted to a required field.decline()on inbound wxApp offers uses telephony reject; outdial pre-accept decline uses CC routing (cancelTask). Track outdial offer state viawxAppAnswerPendingonuiControlConfig.wxAppAcceptInFlight/ pending wxApp answer to prevent double-accept.Cross-client toast suppression (usersub — P0)
WebexCrossClientServicepublishingPOST usersub/api/v1/publishwithcross-client-stateandanswer-calls-on-wxcc(appName: wxcc,ttl: 900, refresh ~14 min while ON).enableWxBetterTogether: trueinwebexConfig.ccbeforewebex.init()/cc.register(). To change after init, re-init the SDK with updated config.cc.isWxBetterTogetherEnabled().setManageWebexCallingInWxccis private (@internal) — not part of the Phase 1 host contract. Production lifecycle usesensureWxAppPostStationLogin()andteardownWxAppLocalState()(usersub + Mercury on station login / silent relogin / logout). Runtime toggle deferred to Phase 2.falseon sign-out / deregister; resetenableWxBetterTogetheron logout so stale flags do not carry into relogin.ensureWxAppPostStationLogin()runs after station login and silent relogin (Extension / Agent DN only). When init flag is ON, auto-publishes usersub, subscribes Mercury, and backfills mute from call details. Skips Browser (WebRTC) login.Bidirectional mute sync (Mercury + telephony GET)
WxAppTelephonyMercurySyncforevent:telephony_calls.muted/.unmuted.TASK_WXAPP_MUTE_STATE_UPDATEDso embed UI stays in sync when the agent mutes/unmutes in Webex App.task.toggleMute({ muted }).syncWxAppMuteFromCallDetailsbackfill: re-seed mute fromGET telephony/calls/{callId}on post-login, and after accept; coalesce in-flight syncs; retry whencallIdis not yet available; passlineOwnerIdwhen present.wxAppAnswerPendingfalse); expected 400 / “Call not found” /101002responses are not logged as errors.wxApp outdial QA fixes
ContactEndedmapping: post-accept agent-terminated outdial normalizes toOUTBOUND_FAILED/AGENT_ENDS(wrapup +TASK_OUTDIAL_FAILEDparity with legacyAgentOutboundFailed). Pre-accept decline staysCONTACT_ENDED→TERMINATED(no wrapup), even when backend sends misleadinginteraction.state: wrapUp.isWxAppEngagedForControls); hide mute during wrapup.Codex review fixes
OUTBOUND_FAILEDfrom HELD, CONSULTING, and CONFERENCING states inTaskStateMachine.ensureWxAppMercuryAndSubscribe()rethrows after cleanup on partial failure; release CC-owned device/Mercury resources.getWebexCallingCallId()andisWxAppEngagedForControls()exclude WRAPPING_UP / terminated states.Codex review fixes (follow-up)
OUTBOUND_FAILEDinHOLD_INITIATING,RESUME_INITIATING,CONSULT_INITIATING, andCONF_INITIATINGtransitional states (same wrapup/terminate transitions as stable states).toggleMute()calls viawxAppMuteToggleInFlightinVoice.tsso third-party/sample no-arg toggles read updated mute state; widgets already pass{ muted }.refreshGenerationon teardown.toggleMute({ muted: nextMuted })best practice.Breaking change (Phase 1)
webex.cc.setManageWebexCallingInWxcc()removed from the public API (nowprivate/@internal). Hosts must useenableWxBetterTogetherat init; re-init to change mid-session.privateis compile-time only — the method may still appear on the runtime object in DevTools; it is not part of the supported public contract.Other
docs/samples/contact-center) — init-only wxApp toggle; unified task methods.CONTRACTS.md,WXCC-6026-wxapp-answer-flow.md, and task spec for init-only public API.runWxApp*orchestration, TaskManager (outdial ContactEnded mapping, mute backfill), uiControls,ccpost-login init / guards, privatesetManageWebexCallingInWxccimpl, and Codex review fixes.Change Type
The following scenarios were tested
Automated (unit):
WebexCrossClientService— usersub publish body (appName: wxcc, ttl 900, true/false); bounded refresh retry on publish failure; no retry after teardownWxAppTelephonyMercurySync— Mercury muted/unmuted filtering and callbackAnswerCallOnWebexService,WebexCallingUtils,wxAppVoiceMethods(inbound vs outdial offer detection, accept/toggleMute internals)runWxAppAccept,runWxAppReject,runWxAppToggleMute,runWxAppTransmitDtmf— lifecycle wiring and error propagationsetManageWebexCallingInWxcc— config update, usersub publish, Mercury subscribe/unsubscribe, rollback on publish failure (tests via internal access)cc.ensureWxAppPostStationLogin— auto usersub + Mercury + mute backfill when init flag ON; skip Browser loginuiControlsComputer— wxApp offer/engaged accept/decline/mute visibility; accept disabled during wxApp accept in-flight; main-leg mute/keypad; wrapup mute hiddenVoice— unified API routing (accept/decline/toggleMute/transmitDtmf→ wxApp runners when flag active), mute sync skip/retry/coalesce, serialized no-arg mute toggles, call-not-found silent handling,wxAppAnswerPendingTaskManager— outdialContactEnded→OUTBOUND_FAILEDvs pre-acceptTERMINATED; mute backfill helpersTaskStateMachine— wxApp outdial wrapup / termination paths; outbound failure from HELD/CONSULTING/CONFERENCING and transitional states (HOLD/RESUME/CONSULT/CONF initiating)ensureWxAppMercuryAndSubscribegetWebexCallingCallId/isWxAppEngagedForControlsManual (end-to-end):
enableWxBetterTogether: true→ Extension/DN station login → usersub + Mercury + mute backfill without runtime toggletask.accept()/task.decline()(telephony REST under the hood)task.toggleMute(); mute/unmute Webex App → embed (Mercury sync); mute icon refresh after reloadtask.transmitDtmf()when keypad control is enabledanswer-calls-on-wxcc: false; config reset on logoutexecuteWxApp*orchestration methodsThe GAI Coding Policy And Copyright Annotation Best Practices
I certified that
Make sure to have followed the contributing guidelines before submitting.