@webex/contact-center 3.12.0-llmrefactor.7 → 3.12.0-llmrefactor.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.sdd/manifest.json +3 -2
- package/ai-docs/CONTRACTS.md +3 -2
- package/dist/cc.js +108 -6
- package/dist/cc.js.map +1 -1
- package/dist/metrics/MetricsManager.js +16 -0
- package/dist/metrics/MetricsManager.js.map +1 -1
- package/dist/metrics/behavioral-events.js +91 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +16 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +34 -6
- package/dist/services/AnswerCallOnWebexService.js.map +1 -1
- package/dist/services/WebexCrossClientService.js +54 -3
- package/dist/services/WebexCrossClientService.js.map +1 -1
- package/dist/services/WxAppTelephonyMercurySync.js +21 -6
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -1
- package/dist/services/task/state-machine/TaskStateMachine.js +0 -17
- package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
- package/dist/services/task/state-machine/guards.js +7 -37
- package/dist/services/task/state-machine/guards.js.map +1 -1
- package/dist/services/task/voice/Voice.js +81 -14
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js +154 -1
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -1
- package/dist/services/wxAppDiagnosticLogging.js +122 -0
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
- package/dist/types/cc.d.ts +1 -0
- package/dist/types/metrics/MetricsManager.d.ts +6 -0
- package/dist/types/metrics/constants.d.ts +15 -0
- package/dist/types/services/AnswerCallOnWebexService.d.ts +1 -0
- package/dist/types/services/WebexCrossClientService.d.ts +4 -0
- package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +0 -42
- package/dist/types/services/task/state-machine/guards.d.ts +4 -4
- package/dist/types/services/task/voice/Voice.d.ts +5 -1
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +3 -0
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
- package/dist/webex.js +1 -1
- package/package.json +8 -8
- package/src/cc.ts +155 -7
- package/src/metrics/MetricsManager.ts +16 -0
- package/src/metrics/ai-docs/metrics-spec.md +71 -9
- package/src/metrics/behavioral-events.ts +92 -0
- package/src/metrics/constants.ts +18 -0
- package/src/services/AnswerCallOnWebexService.ts +41 -8
- package/src/services/WebexCrossClientService.ts +85 -4
- package/src/services/WxAppTelephonyMercurySync.ts +31 -4
- package/src/services/agent/ai-docs/agent-spec.md +1 -0
- package/src/services/task/ai-docs/task-spec.md +17 -16
- package/src/services/task/state-machine/TaskStateMachine.ts +0 -20
- package/src/services/task/state-machine/ai-docs/AGENTS.md +2 -4
- package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +8 -9
- package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +5 -7
- package/src/services/task/state-machine/guards.ts +7 -63
- package/src/services/task/voice/Voice.ts +115 -26
- package/src/services/task/voice/wxAppVoiceMethods.ts +211 -2
- package/src/services/wxAppDiagnosticLogging.ts +174 -0
- package/test/unit/spec/cc.ts +251 -22
- package/test/unit/spec/metrics/MetricsManager.ts +25 -0
- package/test/unit/spec/metrics/behavioral-events.ts +42 -0
- package/test/unit/spec/services/AnswerCallOnWebexService.ts +41 -0
- package/test/unit/spec/services/WebexCrossClientService.ts +207 -1
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +23 -0
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +72 -46
- package/test/unit/spec/services/task/state-machine/guards.ts +6 -6
- package/test/unit/spec/services/task/voice/Voice.ts +380 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +145 -1
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -78,7 +78,7 @@ See root `CONTRACTS.md` for the package-level state-control export.
|
|
|
78
78
|
| TASK_STATE_MACHINE-R-005 | Keep authentication and credentials outside the state-machine layer; it receives typed Task data/events and never invokes authenticated transport. | Pure transition logic remains reusable and cannot leak or mutate host authentication state. | `src/services/task/state-machine/TaskStateMachine.ts`, `src/services/task/state-machine/types.ts` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts` | None; security/auth applicability is explicitly N/A. | PRESENT |
|
|
79
79
|
| TASK_STATE_MACHINE-R-006 | Treat `UIControlConfig` values as Task-supplied capability configuration, not rollout flags evaluated or owned by the state machine. | Rollout and profile policy must be resolved before actor construction so transitions remain deterministic. | `src/services/task/state-machine/types.ts`, `src/services/task/Task.ts` | `test/unit/spec/services/task/Task.ts` | None; rollout ownership is explicit. | PRESENT |
|
|
80
80
|
| TASK_STATE_MACHINE-R-007 | Keep logging and metrics in Task/TaskManager integration; the state-machine implementation has no LoggerProxy or MetricsManager dependency. | Separating observability side effects from guards/actions preserves deterministic transition tests. | `src/services/task/state-machine/TaskStateMachine.ts`, `src/services/task/Task.ts` | `test/unit/spec/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task/Task.ts` | None; observability ownership is explicit. | PRESENT |
|
|
81
|
-
| TASK_STATE_MACHINE-R-008 | `CONTACT_OWNER_CHANGED` must synchronize task/context data and emit `task:hydrate` without transitioning state. A TaskManager-recovered promoted-Agent task first receives an internal IDLE `HYDRATE` before listener installation, then the original owner-change event produces exactly one external hydrate and no incoming event. TaskManager maps an owner-changing `ContactUpdated` for an existing task to that owner-change event, while same-owner or owner-less updates remain `CONTACT_UPDATED`; missing-task `ContactUpdated` does not create a task. It also preserves an already confirmed active owner from a late participant-left snapshot that still names the departed owner. Participant Drop must rely on the existing `ParticipantLeftConference` mapping rather than adding an initiating state event.
|
|
81
|
+
| TASK_STATE_MACHINE-R-008 | `CONTACT_OWNER_CHANGED` must synchronize task/context data and emit `task:hydrate` without transitioning state. A TaskManager-recovered promoted-Agent task first receives an internal IDLE `HYDRATE` before listener installation, then the original owner-change event produces exactly one external hydrate and no incoming event. TaskManager maps an owner-changing `ContactUpdated` for an existing task to that owner-change event, while same-owner or owner-less updates remain `CONTACT_UPDATED`; missing-task `ContactUpdated` does not create a task. It also preserves an already confirmed active owner from a late participant-left snapshot that still names the departed owner. Participant Drop must rely on the existing `ParticipantLeftConference` mapping rather than adding an initiating state event. `PARTICIPANT_LEAVE` is handled in `HELD`, `RESUME_INITIATING`, `CONSULTING`, `CONSULT_INITIATING`, and `CONFERENCING`; `CONNECTED`, `HOLD_INITIATING`, and `CONF_INITIATING` ignore the event. The current Agent is treated as departed only when the event names that Agent or the updated participants map omits that Agent (EP-DN removal). Remaining in the map with `hasLeft`, or disappearing only from `mainCall` media, is non-terminal. `CONSULT_END` does not use mainCall membership as self-departure evidence. Removed accepted Agents emit `task:consultEnd` plus `task:end`, surviving initiators recover to their main call, and an unaccepted OFFERED consultee emits only `task:consultEnd`. Starting Consult must preserve the prior task snapshot used by the guard. | Owner-sensitive consumers must rerender promptly while owner selection, participant removal, consult removal, and narrowly scoped desynchronization recovery remain backend-authoritative and compatible with existing incoming-task callback behavior. | `src/services/task/state-machine/TaskStateMachine.ts`, `src/services/task/state-machine/actions.ts`, `src/services/task/state-machine/guards.ts`, `src/services/task/TaskManager.ts` | `test/unit/spec/services/task/Task.ts`, `test/unit/spec/services/task/TaskManager.ts`, `test/unit/spec/services/task/state-machine/TaskStateMachine.ts`, `test/unit/spec/services/task/state-machine/guards.ts` | Backend ownership-successor selection and delivery of complete recovery payloads are outside this module. | PRESENT |
|
|
82
82
|
|
|
83
83
|
## Design Overview
|
|
84
84
|
TaskManager maps Contact Center notifications to `TaskEvent` values. Each Task sends those events to its XState actor built by `createTaskStateMachine()`. The configuration applies guards and named actions, updates `TaskContext`, and computes UI controls. Task supplies the integration-specific `syncTaskDataFromEvent` implementation through machine options; it is not a default action in `actions.ts`.
|
|
@@ -524,10 +524,8 @@ didCurrentAgentLeaveMainInteraction(context, event) {
|
|
|
524
524
|
const participantIdFromEvent = 'participantId' in event ? event.participantId : undefined;
|
|
525
525
|
const participantId = participantIdFromEvent ?? event.taskData?.participantId;
|
|
526
526
|
if (Boolean(participantId) && participantId === selfAgentId) return true;
|
|
527
|
-
//
|
|
528
|
-
//
|
|
529
|
-
// Only a from-conference CONSULT_END may compare mainCall membership, and only when self
|
|
530
|
-
// remains active in the participant map and on the consult leg. Partial ordinary calls are false.
|
|
527
|
+
// Self missing from the updated participants map is terminal (EP-DN removal).
|
|
528
|
+
// Remaining in the map with hasLeft, or disappearing only from mainCall media, is not.
|
|
531
529
|
}
|
|
532
530
|
|
|
533
531
|
// True when this agent initiated the conference transfer (widgets or desktop).
|
|
@@ -1791,7 +1789,7 @@ Complete mapping from backend CC_EVENTS to internal TaskEvent types.
|
|
|
1791
1789
|
| `PARTICIPANT_JOINED_CONFERENCE` | `CONFERENCE_START` | `CONSULTING` / `CONF_INITIATING` / `CONFERENCING` | `CONFERENCING` / same | Conference participant joined |
|
|
1792
1790
|
| `AGENT_CONSULT_CONFERENCE_FAILED` | `CONFERENCE_FAILED` | `CONF_INITIATING` | `CONSULTING` | Merge fail fallback |
|
|
1793
1791
|
| `AGENT_CONSULT_CONFERENCE_ENDED` | `CONFERENCE_END` | `CONFERENCING` | `WRAPPING_UP` / `CONNECTED` / `TERMINATED` | Guard-driven |
|
|
1794
|
-
| `PARTICIPANT_LEFT_CONFERENCE` | `PARTICIPANT_LEAVE` | `CONSULTING` / `CONFERENCING`
|
|
1792
|
+
| `PARTICIPANT_LEFT_CONFERENCE` | `PARTICIPANT_LEAVE` | `HELD` / `RESUME_INITIATING` / `CONSULTING` / `CONSULT_INITIATING` / `CONFERENCING` | `WRAPPING_UP` / `TERMINATED` / `CONNECTED` / same | Named self or omitted from participant map; `CONNECTED` / `HOLD_INITIATING` / `CONF_INITIATING` ignore |
|
|
1795
1793
|
| `AGENT_CONFERENCE_TRANSFERRED` | `TRANSFER_CONFERENCE_SUCCESS` | `CONSULTING` / `CONFERENCING` | `WRAPPING_UP` / `CONFERENCING` / `TERMINATED` / same | Initiator/receiver dependent |
|
|
1796
1794
|
|
|
1797
1795
|
- `AGENT_CONTACT_UNASSIGNED` -> returns `null` in mapper (`TaskManager.mapEventToTaskStateMachineEvent`)
|
|
@@ -1840,7 +1838,7 @@ Complete mapping from backend CC_EVENTS to internal TaskEvent types.
|
|
|
1840
1838
|
| API `task.dropConferenceParticipant()` | None | No direct state transition | AQM waits for backend `ParticipantLeftConference`; that event follows the existing `PARTICIPANT_LEAVE` path |
|
|
1841
1839
|
| `AgentConsultConferenced` | `CONFERENCE_START` | CONSULTING/CONF_INITIATING → CONFERENCING | `handleConferenceStarted` path |
|
|
1842
1840
|
| `ParticipantJoinedConference` | `CONFERENCE_START` | CONFERENCING → CONFERENCING | Refresh + emit conference started |
|
|
1843
|
-
| `ParticipantLeftConference` | `PARTICIPANT_LEAVE` |
|
|
1841
|
+
| `ParticipantLeftConference` | `PARTICIPANT_LEAVE` | HELD / RESUME_INITIATING / CONSULTING / CONSULT_INITIATING / CONFERENCING → WRAPPING_UP / TERMINATED / CONNECTED / stay; CONNECTED / HOLD_INITIATING / CONF_INITIATING ignore | Uses `didCurrentAgentLeaveMainInteraction` (named self or omitted from participant map), `shouldWrapUp`, `shouldDowngradeConferenceToConnected` |
|
|
1844
1842
|
| `AgentConsultConferenceEnded` | `CONFERENCE_END` | CONFERENCING → WRAPPING_UP / CONNECTED / TERMINATED | Guard-based branch |
|
|
1845
1843
|
| `AgentConsultConferenceFailed` | `CONFERENCE_FAILED` | CONF_INITIATING → CONSULTING | Merge failed fallback |
|
|
1846
1844
|
| `AgentConferenceTransferred` | `TRANSFER_CONFERENCE_SUCCESS` | CONSULTING/CONFERENCING branch logic | Initiator/receiver dependent |
|
|
@@ -364,10 +364,10 @@ export const guards = {
|
|
|
364
364
|
/**
|
|
365
365
|
* True when an updated lifecycle event shows that this agent left the main interaction.
|
|
366
366
|
*
|
|
367
|
-
*
|
|
368
|
-
*
|
|
369
|
-
*
|
|
370
|
-
*
|
|
367
|
+
* Returns true when the event names this agent, or when this agent is absent from the
|
|
368
|
+
* updated participants map. EP-DN payloads remove the leaving participant instead of
|
|
369
|
+
* setting hasLeft. Remaining in the map with hasLeft, or disappearing only from
|
|
370
|
+
* mainCall media, is not treated as departure.
|
|
371
371
|
*/
|
|
372
372
|
didCurrentAgentLeaveMainInteraction: ({context, event}: GuardParams): boolean => {
|
|
373
373
|
const taskData = getTaskDataFromEvent(event);
|
|
@@ -383,65 +383,9 @@ export const guards = {
|
|
|
383
383
|
if (Boolean(participantId) && participantId === selfAgentId) {
|
|
384
384
|
return true;
|
|
385
385
|
}
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
const currentParticipant = taskData?.interaction?.participants?.[selfAgentId];
|
|
390
|
-
if (currentParticipant?.hasLeft === true) {
|
|
391
|
-
return true;
|
|
392
|
-
}
|
|
393
|
-
|
|
394
|
-
// A participant-left event that explicitly identifies somebody else must not use a
|
|
395
|
-
// potentially partial participant or media roster to infer that self also departed.
|
|
396
|
-
if (
|
|
397
|
-
event?.type === TaskEvent.PARTICIPANT_LEAVE &&
|
|
398
|
-
Boolean(participantId) &&
|
|
399
|
-
participantId !== selfAgentId
|
|
400
|
-
) {
|
|
401
|
-
return false;
|
|
402
|
-
}
|
|
403
|
-
|
|
404
|
-
const previousParticipants = context.taskData?.interaction?.participants;
|
|
405
|
-
const updatedParticipants = taskData?.interaction?.participants;
|
|
406
|
-
const wasPreviouslyActive = Boolean(
|
|
407
|
-
previousParticipants?.[selfAgentId] && previousParticipants[selfAgentId].hasLeft !== true
|
|
408
|
-
);
|
|
409
|
-
// Some EP-DN lifecycle payloads remove the participant entry instead of setting hasLeft.
|
|
410
|
-
// Requiring a previously active entry prevents an incomplete initial snapshot from ending a task.
|
|
411
|
-
if (updatedParticipants && wasPreviouslyActive && !(selfAgentId in updatedParticipants)) {
|
|
412
|
-
return true;
|
|
413
|
-
}
|
|
414
|
-
|
|
415
|
-
const previousMainMedia = Object.values(context.taskData?.interaction?.media ?? {}).filter(
|
|
416
|
-
(media) => media?.mType === 'mainCall'
|
|
417
|
-
);
|
|
418
|
-
const updatedMainMedia = Object.values(taskData?.interaction?.media ?? {}).filter(
|
|
419
|
-
(media) => media?.mType === 'mainCall'
|
|
420
|
-
);
|
|
421
|
-
const wasPreviouslyInMainCall = previousMainMedia.some((media) =>
|
|
422
|
-
media.participants?.includes(selfAgentId)
|
|
423
|
-
);
|
|
424
|
-
const remainsInMainCall = updatedMainMedia.some((media) =>
|
|
425
|
-
media.participants?.includes(selfAgentId)
|
|
426
|
-
);
|
|
427
|
-
// A hasLeft participant returned above, so presence here means the updated map keeps self active.
|
|
428
|
-
const remainsActiveInParticipantMap = Boolean(currentParticipant);
|
|
429
|
-
const remainsOnConsultLeg = Object.values(taskData?.interaction?.media ?? {}).some(
|
|
430
|
-
(media) => media?.mType === MEDIA_TYPE_CONSULT && media.participants?.includes(selfAgentId)
|
|
431
|
-
);
|
|
432
|
-
|
|
433
|
-
// Agent Desktop uses mainCall membership for AgentConsultEnded cleanup. Restrict that fallback
|
|
434
|
-
// to a consult that originated from conference and require corroborating active-map and
|
|
435
|
-
// consult-leg membership so ordinary partial CONNECTED/HELD payloads cannot terminate a task.
|
|
436
|
-
if (
|
|
437
|
-
event?.type === TaskEvent.CONSULT_END &&
|
|
438
|
-
context.consultFromConference === true &&
|
|
439
|
-
updatedMainMedia.length > 0 &&
|
|
440
|
-
wasPreviouslyInMainCall &&
|
|
441
|
-
!remainsInMainCall &&
|
|
442
|
-
remainsActiveInParticipantMap &&
|
|
443
|
-
remainsOnConsultLeg
|
|
444
|
-
) {
|
|
386
|
+
// EP-DN payloads remove the leaving participant from the map instead of setting hasLeft.
|
|
387
|
+
const participants = taskData?.interaction?.participants;
|
|
388
|
+
if (participants && !(selfAgentId in participants)) {
|
|
445
389
|
return true;
|
|
446
390
|
}
|
|
447
391
|
|
|
@@ -41,12 +41,20 @@ import {
|
|
|
41
41
|
isWebexAppInboundCallingOffer as wxIsWebexAppInboundCallingOffer,
|
|
42
42
|
mapWxAppVoiceError,
|
|
43
43
|
runWxAppAccept,
|
|
44
|
+
runWxAppOutdialDecline,
|
|
44
45
|
runWxAppReject,
|
|
45
46
|
runWxAppToggleMute,
|
|
46
47
|
runWxAppTransmitDtmf,
|
|
47
48
|
WxAppVoiceDependencies,
|
|
48
49
|
WxAppVoiceLifecycle,
|
|
49
50
|
} from './wxAppVoiceMethods';
|
|
51
|
+
import {
|
|
52
|
+
deriveWxAppAcceptReason,
|
|
53
|
+
logWxAppMercuryMuteSync,
|
|
54
|
+
logWxAppOfferDecision,
|
|
55
|
+
callIdSuffix,
|
|
56
|
+
WxAppAcceptReason,
|
|
57
|
+
} from '../../wxAppDiagnosticLogging';
|
|
50
58
|
|
|
51
59
|
export default class Voice extends Task implements IVoice {
|
|
52
60
|
private static readonly WXAPP_MUTE_SYNC_RETRY_DELAY_MS = 50;
|
|
@@ -58,7 +66,9 @@ export default class Voice extends Task implements IVoice {
|
|
|
58
66
|
private wxAppAnswerPending = false;
|
|
59
67
|
private wxAppAcceptInFlight = false;
|
|
60
68
|
private wxAppMuteSyncInFlight?: Promise<boolean | undefined>;
|
|
61
|
-
private
|
|
69
|
+
private wxAppMuteToggleTail: Promise<void> = Promise.resolve();
|
|
70
|
+
private wxAppDtmfTail: Promise<void> = Promise.resolve();
|
|
71
|
+
private lastLoggedWxAppAcceptReason?: WxAppAcceptReason;
|
|
62
72
|
|
|
63
73
|
constructor(
|
|
64
74
|
contact: ReturnType<typeof routingContact>,
|
|
@@ -95,6 +105,7 @@ export default class Voice extends Task implements IVoice {
|
|
|
95
105
|
enableWxBetterTogether: this.enableWxBetterTogether,
|
|
96
106
|
answerCallOnWebexService: this.answerCallOnWebexService,
|
|
97
107
|
agentId: this.agentId,
|
|
108
|
+
metricsManager: this.metricsManager,
|
|
98
109
|
getTaskData: () => this.data,
|
|
99
110
|
getTaskState: () => this.stateMachineService?.getSnapshot?.()?.value as TaskState | undefined,
|
|
100
111
|
getWxAppMuted: () => this.wxAppMuted,
|
|
@@ -135,17 +146,98 @@ export default class Voice extends Task implements IVoice {
|
|
|
135
146
|
this.updateUiControls(true);
|
|
136
147
|
}
|
|
137
148
|
|
|
149
|
+
protected updateUiControls(forceEmit = false): void {
|
|
150
|
+
super.updateUiControls(forceEmit);
|
|
151
|
+
this.logWxAppOfferDecisionIfNeeded();
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
private logWxAppOfferDecisionIfNeeded(): void {
|
|
155
|
+
if (!this.enableWxBetterTogether) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const state = this.stateMachineService?.getSnapshot?.()?.value as TaskState | undefined;
|
|
160
|
+
if (state !== TaskState.OFFERED) {
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
const accept = this.currentUiControls?.main?.accept;
|
|
165
|
+
if (!accept?.isVisible) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
const deps = this.getWxAppVoiceDependencies();
|
|
170
|
+
const isOutdial = this.data?.interaction?.outboundType === 'OUTDIAL';
|
|
171
|
+
const isWxAppInboundOffer = this.isWebexAppInboundCallingOffer();
|
|
172
|
+
const isWxAppOutdialOffer = this.isWebexAppCallingOffer() && isOutdial;
|
|
173
|
+
const deviceDetails = getCallingDeviceDetails(deps);
|
|
174
|
+
const acceptReason = deriveWxAppAcceptReason({
|
|
175
|
+
isWxAppInboundOffer,
|
|
176
|
+
isWxAppOutdialOffer,
|
|
177
|
+
isWebrtc: this.uiControlConfig.voiceVariant === VOICE_VARIANT.WEBRTC,
|
|
178
|
+
isOutdial,
|
|
179
|
+
wxAppAcceptInFlight: this.wxAppAcceptInFlight,
|
|
180
|
+
wxAppAnswerPending: this.wxAppAnswerPending,
|
|
181
|
+
enableWxBetterTogether: this.enableWxBetterTogether,
|
|
182
|
+
hasDeviceCallId: Boolean(deviceDetails?.deviceCallId),
|
|
183
|
+
});
|
|
184
|
+
|
|
185
|
+
if (acceptReason === this.lastLoggedWxAppAcceptReason) {
|
|
186
|
+
return;
|
|
187
|
+
}
|
|
188
|
+
|
|
189
|
+
this.lastLoggedWxAppAcceptReason = acceptReason;
|
|
190
|
+
|
|
191
|
+
logWxAppOfferDecision({
|
|
192
|
+
interactionId: this.data.interactionId,
|
|
193
|
+
acceptVisible: accept.isVisible,
|
|
194
|
+
acceptEnabled: accept.isEnabled,
|
|
195
|
+
acceptReason,
|
|
196
|
+
wxAppParticipantDeviceType: deviceDetails?.deviceType,
|
|
197
|
+
hasDeviceCallId: Boolean(deviceDetails?.deviceCallId),
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
138
201
|
public applyWxAppMuteStateFromSync(incomingCallId: string, muted: boolean): void {
|
|
139
202
|
if (!this.enableWxBetterTogether) {
|
|
140
203
|
return;
|
|
141
204
|
}
|
|
142
205
|
|
|
143
206
|
const activeCallId = this.getWebexCallingCallId();
|
|
144
|
-
|
|
207
|
+
const incomingSuffix = callIdSuffix(incomingCallId);
|
|
208
|
+
const activeSuffix = callIdSuffix(activeCallId);
|
|
209
|
+
|
|
210
|
+
if (!activeCallId || !incomingCallId.endsWith(activeCallId)) {
|
|
211
|
+
logWxAppMercuryMuteSync({
|
|
212
|
+
phase: 'dropped',
|
|
213
|
+
muted,
|
|
214
|
+
callIdSuffix: incomingSuffix,
|
|
215
|
+
interactionId: this.data.interactionId,
|
|
216
|
+
dropReason: !activeCallId ? 'no_active_wxApp_call' : 'call_id_mismatch',
|
|
217
|
+
});
|
|
218
|
+
|
|
219
|
+
return;
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
if (this.wxAppMuted === muted) {
|
|
223
|
+
logWxAppMercuryMuteSync({
|
|
224
|
+
phase: 'dropped',
|
|
225
|
+
muted,
|
|
226
|
+
callIdSuffix: incomingSuffix,
|
|
227
|
+
interactionId: this.data.interactionId,
|
|
228
|
+
dropReason: 'mute_state_unchanged',
|
|
229
|
+
});
|
|
230
|
+
|
|
145
231
|
return;
|
|
146
232
|
}
|
|
147
233
|
|
|
148
234
|
this.wxAppMuted = muted;
|
|
235
|
+
logWxAppMercuryMuteSync({
|
|
236
|
+
phase: 'applied',
|
|
237
|
+
muted,
|
|
238
|
+
callIdSuffix: activeSuffix,
|
|
239
|
+
interactionId: this.data.interactionId,
|
|
240
|
+
});
|
|
149
241
|
this.emit(TASK_EVENTS.TASK_WXAPP_MUTE_STATE_UPDATED, {muted});
|
|
150
242
|
}
|
|
151
243
|
|
|
@@ -354,7 +446,9 @@ export default class Voice extends Task implements IVoice {
|
|
|
354
446
|
]);
|
|
355
447
|
|
|
356
448
|
this.setWxAppAnswerPending(false);
|
|
357
|
-
const response = await this.
|
|
449
|
+
const response = await runWxAppOutdialDecline(this.getWxAppVoiceDependencies(), () =>
|
|
450
|
+
this.contact.cancelTask({interactionId})
|
|
451
|
+
);
|
|
358
452
|
|
|
359
453
|
this.metricsManager.trackEvent(
|
|
360
454
|
METRIC_EVENT_NAMES.TASK_DECLINE_SUCCESS,
|
|
@@ -388,25 +482,13 @@ export default class Voice extends Task implements IVoice {
|
|
|
388
482
|
*/
|
|
389
483
|
public async toggleMute(options?: TaskToggleMuteOptions): Promise<void> {
|
|
390
484
|
if (this.enableWxBetterTogether && this.getWebexCallingCallId()) {
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
396
|
-
this.getWxAppVoiceDependencies(),
|
|
397
|
-
this.createWxAppLifecycle(),
|
|
398
|
-
options
|
|
399
|
-
);
|
|
400
|
-
|
|
401
|
-
this.wxAppMuteToggleInFlight = togglePromise;
|
|
402
|
-
togglePromise
|
|
403
|
-
.finally(() => {
|
|
404
|
-
if (this.wxAppMuteToggleInFlight === togglePromise) {
|
|
405
|
-
this.wxAppMuteToggleInFlight = undefined;
|
|
406
|
-
}
|
|
407
|
-
})
|
|
408
|
-
.catch(() => undefined);
|
|
485
|
+
const togglePromise = this.wxAppMuteToggleTail
|
|
486
|
+
.catch(() => undefined)
|
|
487
|
+
.then(() =>
|
|
488
|
+
runWxAppToggleMute(this.getWxAppVoiceDependencies(), this.createWxAppLifecycle(), options)
|
|
489
|
+
);
|
|
409
490
|
|
|
491
|
+
this.wxAppMuteToggleTail = togglePromise.catch(() => undefined);
|
|
410
492
|
await togglePromise;
|
|
411
493
|
|
|
412
494
|
return;
|
|
@@ -420,11 +502,18 @@ export default class Voice extends Task implements IVoice {
|
|
|
420
502
|
*/
|
|
421
503
|
public async transmitDtmf(options: TaskTransmitDtmfOptions): Promise<void> {
|
|
422
504
|
if (this.enableWxBetterTogether && this.getWebexCallingCallId()) {
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
505
|
+
const dtmfPromise = this.wxAppDtmfTail
|
|
506
|
+
.catch(() => undefined)
|
|
507
|
+
.then(() =>
|
|
508
|
+
runWxAppTransmitDtmf(
|
|
509
|
+
this.getWxAppVoiceDependencies(),
|
|
510
|
+
this.createWxAppLifecycle(),
|
|
511
|
+
options
|
|
512
|
+
)
|
|
513
|
+
);
|
|
514
|
+
|
|
515
|
+
this.wxAppDtmfTail = dtmfPromise.catch(() => undefined);
|
|
516
|
+
await dtmfPromise;
|
|
428
517
|
|
|
429
518
|
return;
|
|
430
519
|
}
|