@webex/contact-center 3.12.0-llmrefactor.12 → 3.12.0-llmrefactor.14
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.sdd/manifest.json +6 -5
- package/ai-docs/CONTRACTS.md +8 -0
- package/ai-docs/SERVICE_STATE.md +4 -0
- package/ai-docs/contact-center-spec.md +26 -3
- package/ai-docs/features/contact-owner-change-refactor.md +78 -0
- package/dist/cc.js +202 -9
- package/dist/cc.js.map +1 -1
- package/dist/constants.js +5 -1
- package/dist/constants.js.map +1 -1
- package/dist/index.js +25 -10
- package/dist/index.js.map +1 -1
- package/dist/metrics/behavioral-events.js +6 -0
- package/dist/metrics/behavioral-events.js.map +1 -1
- package/dist/metrics/constants.js +6 -0
- package/dist/metrics/constants.js.map +1 -1
- package/dist/services/AnswerCallOnWebexService.js +4 -4
- package/dist/services/AnswerCallOnWebexService.js.map +1 -1
- package/dist/services/ApiAiAssistant.js +116 -1
- package/dist/services/ApiAiAssistant.js.map +1 -1
- package/dist/services/WebexCrossClientService.js +5 -2
- package/dist/services/WebexCrossClientService.js.map +1 -1
- package/dist/services/WxAppTelephonyMercurySync.js +6 -3
- package/dist/services/WxAppTelephonyMercurySync.js.map +1 -1
- package/dist/services/config/Util.js +5 -1
- package/dist/services/config/Util.js.map +1 -1
- package/dist/services/config/constants.js +10 -1
- package/dist/services/config/constants.js.map +1 -1
- package/dist/services/config/index.js +51 -0
- package/dist/services/config/index.js.map +1 -1
- package/dist/services/config/types.js +4 -3
- package/dist/services/config/types.js.map +1 -1
- package/dist/services/core/Utils.js +5 -1
- package/dist/services/core/Utils.js.map +1 -1
- package/dist/services/task/TaskFactory.js +1 -0
- package/dist/services/task/TaskFactory.js.map +1 -1
- package/dist/services/task/TaskManager.js +50 -58
- package/dist/services/task/TaskManager.js.map +1 -1
- package/dist/services/task/types.js.map +1 -1
- package/dist/services/task/voice/Voice.js +63 -44
- package/dist/services/task/voice/Voice.js.map +1 -1
- package/dist/services/task/voice/wxAppOfferObservability.js +145 -0
- package/dist/services/task/voice/wxAppOfferObservability.js.map +1 -0
- package/dist/services/task/voice/wxAppVoiceMethods.js +52 -11
- package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -1
- package/dist/services/wxAppDiagnosticLogging.js +50 -13
- package/dist/services/wxAppDiagnosticLogging.js.map +1 -1
- package/dist/types/cc.d.ts +21 -1
- package/dist/types/constants.d.ts +4 -0
- package/dist/types/index.d.ts +11 -1
- package/dist/types/metrics/constants.d.ts +6 -0
- package/dist/types/services/ApiAiAssistant.d.ts +35 -2
- package/dist/types/services/config/constants.d.ts +5 -0
- package/dist/types/services/config/types.d.ts +15 -2
- package/dist/types/services/core/Utils.d.ts +2 -0
- package/dist/types/services/task/types.d.ts +2 -0
- package/dist/types/services/task/voice/Voice.d.ts +15 -2
- package/dist/types/services/task/voice/wxAppOfferObservability.d.ts +39 -0
- package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +19 -6
- package/dist/types/services/wxAppDiagnosticLogging.d.ts +12 -0
- package/dist/types/types.d.ts +56 -1
- package/dist/types.js +39 -2
- package/dist/types.js.map +1 -1
- package/dist/webex.js +9 -1
- package/dist/webex.js.map +1 -1
- package/package.json +9 -9
- package/src/cc.ts +294 -25
- package/src/constants.ts +4 -0
- package/src/index.ts +11 -0
- package/src/metrics/ai-docs/metrics-spec.md +61 -20
- package/src/metrics/behavioral-events.ts +6 -0
- package/src/metrics/constants.ts +6 -0
- package/src/services/AnswerCallOnWebexService.ts +4 -4
- package/src/services/ApiAiAssistant.ts +153 -1
- package/src/services/WebexCrossClientService.ts +7 -2
- package/src/services/WxAppTelephonyMercurySync.ts +3 -3
- package/src/services/ai-docs/services-spec.md +11 -3
- package/src/services/config/Util.ts +6 -0
- package/src/services/config/ai-docs/config-spec.md +14 -3
- package/src/services/config/constants.ts +12 -0
- package/src/services/config/index.ts +65 -0
- package/src/services/config/types.ts +13 -2
- package/src/services/core/Utils.ts +4 -0
- package/src/services/task/TaskFactory.ts +1 -0
- package/src/services/task/TaskManager.ts +85 -87
- package/src/services/task/ai-docs/task-spec.md +7 -3
- package/src/services/task/types.ts +16 -0
- package/src/services/task/voice/Voice.ts +74 -52
- package/src/services/task/voice/wxAppOfferObservability.ts +204 -0
- package/src/services/task/voice/wxAppVoiceMethods.ts +83 -24
- package/src/services/wxAppDiagnosticLogging.ts +135 -23
- package/src/types.ts +65 -0
- package/src/webex.js +8 -0
- package/test/unit/spec/cc.ts +469 -19
- package/test/unit/spec/services/ApiAiAssistant.ts +134 -2
- package/test/unit/spec/services/WebexCrossClientService.ts +45 -3
- package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +1 -1
- package/test/unit/spec/services/config/Util.ts +44 -0
- package/test/unit/spec/services/config/index.ts +68 -0
- package/test/unit/spec/services/task/TaskManager.ts +214 -3
- package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +33 -0
- package/test/unit/spec/services/task/voice/Voice.ts +10 -51
- package/test/unit/spec/services/task/voice/wxAppOfferObservability.ts +361 -0
- package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +121 -5
- package/test/unit/spec/services/wxAppDiagnosticLogging.ts +44 -8
- package/test/unit/spec/wellnessSampleUtils.ts +288 -0
- package/umd/contact-center.min.js +2 -2
- package/umd/contact-center.min.js.map +1 -1
|
@@ -50,12 +50,11 @@ import {
|
|
|
50
50
|
WxAppVoiceLifecycle,
|
|
51
51
|
} from './wxAppVoiceMethods';
|
|
52
52
|
import {
|
|
53
|
-
deriveWxAppAcceptReason,
|
|
54
53
|
logWxAppMercuryMuteSync,
|
|
55
|
-
logWxAppOfferDecision,
|
|
56
54
|
callIdSuffix,
|
|
57
|
-
WxAppAcceptReason,
|
|
55
|
+
type WxAppAcceptReason,
|
|
58
56
|
} from '../../wxAppDiagnosticLogging';
|
|
57
|
+
import {WxAppOfferObservability, WxAppOfferObservabilityContext} from './wxAppOfferObservability';
|
|
59
58
|
|
|
60
59
|
const hasNonemptyPendingWrapUp = (taskData?: TaskData): boolean =>
|
|
61
60
|
Array.isArray(taskData?.agentsPendingWrapUp) && taskData.agentsPendingWrapUp.length > 0;
|
|
@@ -87,7 +86,9 @@ export default class Voice extends Task implements IVoice {
|
|
|
87
86
|
private wxAppMuteSyncInFlight?: Promise<boolean | undefined>;
|
|
88
87
|
private wxAppMuteToggleTail: Promise<void> = Promise.resolve();
|
|
89
88
|
private wxAppDtmfTail: Promise<void> = Promise.resolve();
|
|
90
|
-
|
|
89
|
+
/** Lazy — `super()` may call `updateUiControls` before field initializers run. */
|
|
90
|
+
private wxAppOfferObservability: WxAppOfferObservability | null = null;
|
|
91
|
+
private getUsersubPublished?: () => boolean;
|
|
91
92
|
|
|
92
93
|
constructor(
|
|
93
94
|
contact: ReturnType<typeof routingContact>,
|
|
@@ -117,6 +118,7 @@ export default class Voice extends Task implements IVoice {
|
|
|
117
118
|
|
|
118
119
|
this.enableWxBetterTogether = resolvedOptions.enableWxBetterTogether;
|
|
119
120
|
this.answerCallOnWebexService = callOptions?.answerCallOnWebexService;
|
|
121
|
+
this.getUsersubPublished = callOptions?.getUsersubPublished;
|
|
120
122
|
}
|
|
121
123
|
|
|
122
124
|
private getWxAppVoiceDependencies(): WxAppVoiceDependencies {
|
|
@@ -131,6 +133,7 @@ export default class Voice extends Task implements IVoice {
|
|
|
131
133
|
setWxAppMuted: (muted: boolean) => {
|
|
132
134
|
this.wxAppMuted = muted;
|
|
133
135
|
},
|
|
136
|
+
getUsersubPublished: this.getUsersubPublished,
|
|
134
137
|
};
|
|
135
138
|
}
|
|
136
139
|
|
|
@@ -165,56 +168,54 @@ export default class Voice extends Task implements IVoice {
|
|
|
165
168
|
this.updateUiControls(true);
|
|
166
169
|
}
|
|
167
170
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
private logWxAppOfferDecisionIfNeeded(): void {
|
|
174
|
-
if (!this.enableWxBetterTogether) {
|
|
175
|
-
return;
|
|
171
|
+
private getOrCreateWxAppOfferObservability(): WxAppOfferObservability {
|
|
172
|
+
if (!this.wxAppOfferObservability) {
|
|
173
|
+
this.wxAppOfferObservability = new WxAppOfferObservability();
|
|
176
174
|
}
|
|
177
175
|
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
return;
|
|
181
|
-
}
|
|
182
|
-
|
|
183
|
-
const accept = this.currentUiControls?.main?.accept;
|
|
184
|
-
if (!accept?.isVisible) {
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
176
|
+
return this.wxAppOfferObservability;
|
|
177
|
+
}
|
|
187
178
|
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
isWxAppInboundOffer,
|
|
195
|
-
isWxAppOutdialOffer,
|
|
196
|
-
isWebrtc: this.uiControlConfig.voiceVariant === VOICE_VARIANT.WEBRTC,
|
|
197
|
-
isOutdial,
|
|
198
|
-
wxAppAcceptInFlight: this.wxAppAcceptInFlight,
|
|
199
|
-
wxAppAnswerPending: this.wxAppAnswerPending,
|
|
200
|
-
enableWxBetterTogether: this.enableWxBetterTogether,
|
|
201
|
-
hasDeviceCallId: Boolean(deviceDetails?.deviceCallId),
|
|
202
|
-
});
|
|
179
|
+
protected updateUiControls(forceEmit = false): void {
|
|
180
|
+
super.updateUiControls(forceEmit);
|
|
181
|
+
this.getOrCreateWxAppOfferObservability().handleUiControlsUpdate(
|
|
182
|
+
this.getWxAppOfferObservabilityContext()
|
|
183
|
+
);
|
|
184
|
+
}
|
|
203
185
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
186
|
+
/**
|
|
187
|
+
* Re-run OFFERED wxApp diagnostics without emitting UI controls.
|
|
188
|
+
* Used after usersub publish so a pre-publish offer can schedule mismatch grace.
|
|
189
|
+
*/
|
|
190
|
+
public refreshWxAppOfferObservability(): void {
|
|
191
|
+
this.getOrCreateWxAppOfferObservability().handleUiControlsUpdate(
|
|
192
|
+
this.getWxAppOfferObservabilityContext()
|
|
193
|
+
);
|
|
194
|
+
}
|
|
207
195
|
|
|
208
|
-
|
|
196
|
+
/**
|
|
197
|
+
* Cancel pending participant-mismatch grace timer. Observability only.
|
|
198
|
+
*/
|
|
199
|
+
public disposeWxAppOfferObservability(): void {
|
|
200
|
+
this.wxAppOfferObservability?.dispose();
|
|
201
|
+
}
|
|
209
202
|
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
203
|
+
private getWxAppOfferObservabilityContext(): WxAppOfferObservabilityContext {
|
|
204
|
+
return {
|
|
205
|
+
getInteractionId: () => this.data.interactionId,
|
|
206
|
+
getTaskState: () => this.stateMachineService?.getSnapshot?.()?.value as TaskState | undefined,
|
|
207
|
+
getAcceptControl: () => this.currentUiControls?.main?.accept,
|
|
208
|
+
getVoiceVariant: () => this.uiControlConfig.voiceVariant ?? VOICE_VARIANT.PSTN,
|
|
209
|
+
isOutdial: () => this.data?.interaction?.outboundType === 'OUTDIAL',
|
|
210
|
+
isWxAppInboundOffer: () => this.isWebexAppInboundCallingOffer(),
|
|
211
|
+
isWxAppCallingOffer: () => this.isWebexAppCallingOffer(),
|
|
212
|
+
getWxAppAcceptInFlight: () => this.wxAppAcceptInFlight,
|
|
213
|
+
getWxAppAnswerPending: () => this.wxAppAnswerPending,
|
|
214
|
+
getEnableWxBetterTogether: () => this.enableWxBetterTogether,
|
|
215
|
+
getUsersubPublished: () => this.getUsersubPublished?.() ?? false,
|
|
216
|
+
getWxAppVoiceDependencies: () => this.getWxAppVoiceDependencies(),
|
|
217
|
+
getMetricsManager: () => this.metricsManager,
|
|
218
|
+
};
|
|
218
219
|
}
|
|
219
220
|
|
|
220
221
|
public applyWxAppMuteStateFromSync(incomingCallId: string, muted: boolean): void {
|
|
@@ -423,12 +424,26 @@ export default class Voice extends Task implements IVoice {
|
|
|
423
424
|
return this.stateMachineService?.getSnapshot?.();
|
|
424
425
|
}
|
|
425
426
|
|
|
427
|
+
private getWxAppAcceptReasonForMetrics(): WxAppAcceptReason {
|
|
428
|
+
if (this.wxAppAcceptInFlight) {
|
|
429
|
+
return 'wxApp_accept_in_flight';
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
if (this.wxAppAnswerPending) {
|
|
433
|
+
return 'wxApp_answer_pending';
|
|
434
|
+
}
|
|
435
|
+
|
|
436
|
+
return 'wxApp_offer_ready';
|
|
437
|
+
}
|
|
438
|
+
|
|
426
439
|
/**
|
|
427
440
|
* Accepts the task. Routes to wxApp telephony answer when `enableWxBetterTogether` and wxApp offer.
|
|
428
441
|
*/
|
|
429
442
|
public async accept(): Promise<TaskResponse> {
|
|
430
443
|
if (this.enableWxBetterTogether && this.isWebexAppCallingOffer()) {
|
|
431
|
-
await runWxAppAccept(this.getWxAppVoiceDependencies(), this.createWxAppLifecycle()
|
|
444
|
+
await runWxAppAccept(this.getWxAppVoiceDependencies(), this.createWxAppLifecycle(), {
|
|
445
|
+
acceptReason: this.getWxAppAcceptReasonForMetrics(),
|
|
446
|
+
});
|
|
432
447
|
|
|
433
448
|
return Promise.resolve();
|
|
434
449
|
}
|
|
@@ -441,7 +456,9 @@ export default class Voice extends Task implements IVoice {
|
|
|
441
456
|
*/
|
|
442
457
|
public async decline(): Promise<TaskResponse> {
|
|
443
458
|
if (this.enableWxBetterTogether && this.isWebexAppInboundCallingOffer()) {
|
|
444
|
-
await runWxAppReject(this.getWxAppVoiceDependencies(), this.createWxAppLifecycle()
|
|
459
|
+
await runWxAppReject(this.getWxAppVoiceDependencies(), this.createWxAppLifecycle(), {
|
|
460
|
+
acceptReason: this.getWxAppAcceptReasonForMetrics(),
|
|
461
|
+
});
|
|
445
462
|
|
|
446
463
|
return Promise.resolve();
|
|
447
464
|
}
|
|
@@ -464,9 +481,14 @@ export default class Voice extends Task implements IVoice {
|
|
|
464
481
|
METRIC_EVENT_NAMES.TASK_DECLINE_FAILED,
|
|
465
482
|
]);
|
|
466
483
|
|
|
484
|
+
const declineAcceptReason = this.wxAppAnswerPending
|
|
485
|
+
? 'wxApp_answer_pending'
|
|
486
|
+
: 'wxApp_offer_ready';
|
|
467
487
|
this.setWxAppAnswerPending(false);
|
|
468
|
-
const response = await runWxAppOutdialDecline(
|
|
469
|
-
this.
|
|
488
|
+
const response = await runWxAppOutdialDecline(
|
|
489
|
+
this.getWxAppVoiceDependencies(),
|
|
490
|
+
() => this.contact.cancelTask({interactionId}),
|
|
491
|
+
{acceptReason: declineAcceptReason}
|
|
470
492
|
);
|
|
471
493
|
|
|
472
494
|
this.metricsManager.trackEvent(
|
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import MetricsManager from '../../../metrics/MetricsManager';
|
|
2
|
+
import {METRIC_EVENT_NAMES} from '../../../metrics/constants';
|
|
3
|
+
import {
|
|
4
|
+
deriveWxAppAcceptReason,
|
|
5
|
+
logWxAppOfferDecision,
|
|
6
|
+
logWxAppOfferParticipantMismatch,
|
|
7
|
+
WxAppAcceptReason,
|
|
8
|
+
} from '../../wxAppDiagnosticLogging';
|
|
9
|
+
import {TaskState} from '../state-machine';
|
|
10
|
+
import {VoiceVariant, VOICE_VARIANT} from '../types';
|
|
11
|
+
import {
|
|
12
|
+
getCallingDeviceDetails,
|
|
13
|
+
getWxAppParticipantDiagnostics,
|
|
14
|
+
WxAppVoiceDependencies,
|
|
15
|
+
} from './wxAppVoiceMethods';
|
|
16
|
+
|
|
17
|
+
/** Defer mismatch WARN/metric so transient WS ordering gaps (~200–300 ms) do not false-alarm. */
|
|
18
|
+
export const WXAPP_PARTICIPANT_MISMATCH_GRACE_MS = 500;
|
|
19
|
+
|
|
20
|
+
export type WxAppOfferObservabilityContext = {
|
|
21
|
+
getInteractionId: () => string;
|
|
22
|
+
getTaskState: () => TaskState | undefined;
|
|
23
|
+
getAcceptControl: () => {isVisible: boolean; isEnabled: boolean} | undefined;
|
|
24
|
+
getVoiceVariant: () => VoiceVariant;
|
|
25
|
+
isOutdial: () => boolean;
|
|
26
|
+
isWxAppInboundOffer: () => boolean;
|
|
27
|
+
isWxAppCallingOffer: () => boolean;
|
|
28
|
+
getWxAppAcceptInFlight: () => boolean;
|
|
29
|
+
getWxAppAnswerPending: () => boolean;
|
|
30
|
+
getEnableWxBetterTogether: () => boolean;
|
|
31
|
+
getUsersubPublished: () => boolean;
|
|
32
|
+
getWxAppVoiceDependencies: () => WxAppVoiceDependencies;
|
|
33
|
+
getMetricsManager: () => MetricsManager;
|
|
34
|
+
};
|
|
35
|
+
|
|
36
|
+
/**
|
|
37
|
+
* OFFERED-state wxApp offer diagnostics: acceptReason logging and deferred participant mismatch.
|
|
38
|
+
* Invoked from Voice.updateUiControls — observability only.
|
|
39
|
+
*/
|
|
40
|
+
export class WxAppOfferObservability {
|
|
41
|
+
private lastLoggedWxAppAcceptReason?: WxAppAcceptReason;
|
|
42
|
+
private lastLoggedWxAppParticipantMismatch = false;
|
|
43
|
+
private wxAppParticipantMismatchGraceTimer?: ReturnType<typeof setTimeout>;
|
|
44
|
+
private disposed = false;
|
|
45
|
+
|
|
46
|
+
public handleUiControlsUpdate(ctx: WxAppOfferObservabilityContext): void {
|
|
47
|
+
if (this.disposed) {
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (!ctx.getEnableWxBetterTogether()) {
|
|
52
|
+
this.cancelGraceTimer();
|
|
53
|
+
|
|
54
|
+
return;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
const state = ctx.getTaskState();
|
|
58
|
+
if (state !== TaskState.OFFERED) {
|
|
59
|
+
this.cancelGraceTimer();
|
|
60
|
+
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
const accept = ctx.getAcceptControl();
|
|
65
|
+
if (!accept?.isVisible) {
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const deps = ctx.getWxAppVoiceDependencies();
|
|
70
|
+
const isOutdial = ctx.isOutdial();
|
|
71
|
+
const isWxAppInboundOffer = ctx.isWxAppInboundOffer();
|
|
72
|
+
const isWxAppOutdialOffer = ctx.isWxAppCallingOffer() && isOutdial;
|
|
73
|
+
const deviceDetails = getCallingDeviceDetails(deps);
|
|
74
|
+
const participantDiagnostics = getWxAppParticipantDiagnostics(deps);
|
|
75
|
+
const usersubPublished = ctx.getUsersubPublished();
|
|
76
|
+
const acceptReason = deriveWxAppAcceptReason({
|
|
77
|
+
isWxAppInboundOffer,
|
|
78
|
+
isWxAppOutdialOffer,
|
|
79
|
+
isWebrtc: ctx.getVoiceVariant() === VOICE_VARIANT.WEBRTC,
|
|
80
|
+
isOutdial,
|
|
81
|
+
wxAppAcceptInFlight: ctx.getWxAppAcceptInFlight(),
|
|
82
|
+
wxAppAnswerPending: ctx.getWxAppAnswerPending(),
|
|
83
|
+
enableWxBetterTogether: ctx.getEnableWxBetterTogether(),
|
|
84
|
+
hasDeviceCallId: participantDiagnostics.hasDeviceCallId,
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
if (acceptReason !== this.lastLoggedWxAppAcceptReason) {
|
|
88
|
+
this.lastLoggedWxAppAcceptReason = acceptReason;
|
|
89
|
+
|
|
90
|
+
logWxAppOfferDecision({
|
|
91
|
+
interactionId: ctx.getInteractionId(),
|
|
92
|
+
acceptVisible: accept.isVisible,
|
|
93
|
+
acceptEnabled: accept.isEnabled,
|
|
94
|
+
acceptReason,
|
|
95
|
+
wxAppParticipantDeviceType:
|
|
96
|
+
deviceDetails?.deviceType ?? participantDiagnostics.participantDeviceType,
|
|
97
|
+
hasDeviceCallId: participantDiagnostics.hasDeviceCallId,
|
|
98
|
+
hasDeviceId: participantDiagnostics.hasDeviceId,
|
|
99
|
+
usersubPublished,
|
|
100
|
+
});
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (participantDiagnostics.hasValidWxAppParticipant || !usersubPublished) {
|
|
104
|
+
this.cancelGraceTimer();
|
|
105
|
+
} else if (!ctx.isOutdial() && !this.lastLoggedWxAppParticipantMismatch) {
|
|
106
|
+
this.scheduleGraceTimer(ctx);
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
public dispose(): void {
|
|
111
|
+
this.disposed = true;
|
|
112
|
+
this.cancelGraceTimer();
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
private cancelGraceTimer(): void {
|
|
116
|
+
if (this.wxAppParticipantMismatchGraceTimer !== undefined) {
|
|
117
|
+
clearTimeout(this.wxAppParticipantMismatchGraceTimer);
|
|
118
|
+
this.wxAppParticipantMismatchGraceTimer = undefined;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
private scheduleGraceTimer(ctx: WxAppOfferObservabilityContext): void {
|
|
123
|
+
if (
|
|
124
|
+
this.disposed ||
|
|
125
|
+
this.lastLoggedWxAppParticipantMismatch ||
|
|
126
|
+
this.wxAppParticipantMismatchGraceTimer !== undefined
|
|
127
|
+
) {
|
|
128
|
+
return;
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
this.wxAppParticipantMismatchGraceTimer = setTimeout(() => {
|
|
132
|
+
this.wxAppParticipantMismatchGraceTimer = undefined;
|
|
133
|
+
this.emitParticipantMismatchIfStillNeeded(ctx);
|
|
134
|
+
}, WXAPP_PARTICIPANT_MISMATCH_GRACE_MS);
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
private emitParticipantMismatchIfStillNeeded(ctx: WxAppOfferObservabilityContext): void {
|
|
138
|
+
if (
|
|
139
|
+
this.disposed ||
|
|
140
|
+
!ctx.getEnableWxBetterTogether() ||
|
|
141
|
+
this.lastLoggedWxAppParticipantMismatch
|
|
142
|
+
) {
|
|
143
|
+
return;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
if (ctx.isOutdial()) {
|
|
147
|
+
return;
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
if (ctx.getTaskState() !== TaskState.OFFERED) {
|
|
151
|
+
return;
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const accept = ctx.getAcceptControl();
|
|
155
|
+
if (!accept?.isVisible) {
|
|
156
|
+
return;
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const deps = ctx.getWxAppVoiceDependencies();
|
|
160
|
+
const participantDiagnostics = getWxAppParticipantDiagnostics(deps);
|
|
161
|
+
const usersubPublished = ctx.getUsersubPublished();
|
|
162
|
+
|
|
163
|
+
if (!usersubPublished || participantDiagnostics.hasValidWxAppParticipant) {
|
|
164
|
+
return;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
const isOutdial = ctx.isOutdial();
|
|
168
|
+
const acceptReason = deriveWxAppAcceptReason({
|
|
169
|
+
isWxAppInboundOffer: ctx.isWxAppInboundOffer(),
|
|
170
|
+
isWxAppOutdialOffer: ctx.isWxAppCallingOffer() && isOutdial,
|
|
171
|
+
isWebrtc: ctx.getVoiceVariant() === VOICE_VARIANT.WEBRTC,
|
|
172
|
+
isOutdial,
|
|
173
|
+
wxAppAcceptInFlight: ctx.getWxAppAcceptInFlight(),
|
|
174
|
+
wxAppAnswerPending: ctx.getWxAppAnswerPending(),
|
|
175
|
+
enableWxBetterTogether: ctx.getEnableWxBetterTogether(),
|
|
176
|
+
hasDeviceCallId: participantDiagnostics.hasDeviceCallId,
|
|
177
|
+
});
|
|
178
|
+
|
|
179
|
+
this.lastLoggedWxAppParticipantMismatch = true;
|
|
180
|
+
|
|
181
|
+
logWxAppOfferParticipantMismatch({
|
|
182
|
+
interactionId: ctx.getInteractionId(),
|
|
183
|
+
usersubPublished,
|
|
184
|
+
hasDeviceCallId: participantDiagnostics.hasDeviceCallId,
|
|
185
|
+
hasDeviceId: participantDiagnostics.hasDeviceId,
|
|
186
|
+
participantDeviceType: participantDiagnostics.participantDeviceType,
|
|
187
|
+
acceptVisible: accept.isVisible,
|
|
188
|
+
acceptEnabled: accept.isEnabled,
|
|
189
|
+
acceptReason,
|
|
190
|
+
});
|
|
191
|
+
|
|
192
|
+
ctx.getMetricsManager().trackEvent(
|
|
193
|
+
METRIC_EVENT_NAMES.WXAPP_OFFER_PARTICIPANT_FIELDS_MISSING,
|
|
194
|
+
{
|
|
195
|
+
taskId: ctx.getInteractionId(),
|
|
196
|
+
usersubPublished,
|
|
197
|
+
hasDeviceCallId: participantDiagnostics.hasDeviceCallId,
|
|
198
|
+
hasDeviceId: participantDiagnostics.hasDeviceId,
|
|
199
|
+
acceptReason,
|
|
200
|
+
},
|
|
201
|
+
['operational', 'behavioral']
|
|
202
|
+
);
|
|
203
|
+
}
|
|
204
|
+
}
|
|
@@ -17,6 +17,7 @@ import {
|
|
|
17
17
|
import {
|
|
18
18
|
logWxAppTelephonyAction,
|
|
19
19
|
logWxAppValidationFailure,
|
|
20
|
+
type WxAppAcceptReason,
|
|
20
21
|
type WxAppTelephonyAction,
|
|
21
22
|
} from '../../wxAppDiagnosticLogging';
|
|
22
23
|
|
|
@@ -35,6 +36,15 @@ export type WxAppVoiceDependencies = {
|
|
|
35
36
|
getTaskState: () => TaskState | undefined;
|
|
36
37
|
getWxAppMuted: () => boolean;
|
|
37
38
|
setWxAppMuted: (muted: boolean) => void;
|
|
39
|
+
/** Read-only observability: whether usersub answer-calls-on-wxcc is active this session. */
|
|
40
|
+
getUsersubPublished?: () => boolean;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
export type WxAppParticipantDiagnostics = {
|
|
44
|
+
participantDeviceType?: string;
|
|
45
|
+
hasDeviceCallId: boolean;
|
|
46
|
+
hasDeviceId: boolean;
|
|
47
|
+
hasValidWxAppParticipant: boolean;
|
|
38
48
|
};
|
|
39
49
|
|
|
40
50
|
/** @deprecated Use {@link WxAppVoiceDependencies} */
|
|
@@ -76,6 +86,34 @@ function getWxAppAgentParticipant(deps: WxAppVoiceDependencies): unknown {
|
|
|
76
86
|
);
|
|
77
87
|
}
|
|
78
88
|
|
|
89
|
+
export function getWxAppParticipantDiagnostics(
|
|
90
|
+
deps: WxAppVoiceDependencies
|
|
91
|
+
): WxAppParticipantDiagnostics {
|
|
92
|
+
const participant = getWxAppAgentParticipant(deps) as WxAppParticipant | undefined;
|
|
93
|
+
const hasDeviceCallId =
|
|
94
|
+
typeof participant?.deviceCallId === 'string' && participant.deviceCallId.trim() !== '';
|
|
95
|
+
const hasDeviceId =
|
|
96
|
+
typeof participant?.deviceId === 'string' && participant.deviceId.trim() !== '';
|
|
97
|
+
|
|
98
|
+
return {
|
|
99
|
+
participantDeviceType: participant?.deviceType,
|
|
100
|
+
hasDeviceCallId,
|
|
101
|
+
hasDeviceId,
|
|
102
|
+
hasValidWxAppParticipant: isWxAppParticipant(participant),
|
|
103
|
+
};
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
function getWxAppTelephonyMetricContext(
|
|
107
|
+
deps: WxAppVoiceDependencies
|
|
108
|
+
): Record<string, boolean | string> {
|
|
109
|
+
const diagnostics = getWxAppParticipantDiagnostics(deps);
|
|
110
|
+
|
|
111
|
+
return {
|
|
112
|
+
hasDeviceCallId: diagnostics.hasDeviceCallId,
|
|
113
|
+
hasDeviceId: diagnostics.hasDeviceId,
|
|
114
|
+
};
|
|
115
|
+
}
|
|
116
|
+
|
|
79
117
|
export function getCallingDeviceDetails(
|
|
80
118
|
deps: WxAppVoiceDependencies
|
|
81
119
|
): WebexCallingDeviceDetails | undefined {
|
|
@@ -169,11 +207,12 @@ function logTelephonyFailure(
|
|
|
169
207
|
function getWxAppTelephonyMetricFailurePayload(
|
|
170
208
|
deps: WxAppVoiceDependencies,
|
|
171
209
|
error: unknown
|
|
172
|
-
): Record<string, string> {
|
|
210
|
+
): Record<string, string | boolean> {
|
|
173
211
|
const wxError = error as WxAppTelephonyError;
|
|
174
|
-
const payload: Record<string, string> = {
|
|
212
|
+
const payload: Record<string, string | boolean> = {
|
|
175
213
|
taskId: getInteractionId(deps) ?? '',
|
|
176
214
|
error: error instanceof Error ? error.toString() : String(error),
|
|
215
|
+
...getWxAppTelephonyMetricContext(deps),
|
|
177
216
|
};
|
|
178
217
|
|
|
179
218
|
if (wxError.trackingId) {
|
|
@@ -198,7 +237,11 @@ function getWxAppOutdialDeclineFailurePayload(
|
|
|
198
237
|
const aqmDetails = (error as AqmWrappedError).details;
|
|
199
238
|
|
|
200
239
|
if (aqmDetails) {
|
|
201
|
-
return {
|
|
240
|
+
return {
|
|
241
|
+
...base,
|
|
242
|
+
...getWxAppTelephonyMetricContext(deps),
|
|
243
|
+
...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(aqmDetails),
|
|
244
|
+
};
|
|
202
245
|
}
|
|
203
246
|
|
|
204
247
|
return getWxAppTelephonyMetricFailurePayload(deps, error);
|
|
@@ -324,7 +367,7 @@ export async function transmitDtmfOnWebex(
|
|
|
324
367
|
throw new Error('WxApp call ID is unavailable');
|
|
325
368
|
}
|
|
326
369
|
|
|
327
|
-
LoggerProxy.
|
|
370
|
+
LoggerProxy.log('transmitDtmf', {
|
|
328
371
|
module: 'wxAppVoiceMethods',
|
|
329
372
|
method: METHODS.TRANSMIT_DTMF,
|
|
330
373
|
data: {
|
|
@@ -343,12 +386,18 @@ export async function transmitDtmfOnWebex(
|
|
|
343
386
|
logWxAppTelephonyAction({action: 'dtmf', phase: 'success', interactionId});
|
|
344
387
|
}
|
|
345
388
|
|
|
389
|
+
export type WxAppAcceptRejectOptions = {
|
|
390
|
+
lineOwnerId?: string;
|
|
391
|
+
acceptReason?: WxAppAcceptReason;
|
|
392
|
+
};
|
|
393
|
+
|
|
346
394
|
export async function runWxAppAccept(
|
|
347
395
|
deps: WxAppVoiceDependencies,
|
|
348
396
|
lifecycle: WxAppVoiceLifecycle,
|
|
349
|
-
options?:
|
|
397
|
+
options?: WxAppAcceptRejectOptions
|
|
350
398
|
): Promise<void> {
|
|
351
399
|
const taskId = getInteractionId(deps);
|
|
400
|
+
const acceptReason = options?.acceptReason ?? 'wxApp_offer_ready';
|
|
352
401
|
|
|
353
402
|
deps.metricsManager.timeEvent([
|
|
354
403
|
METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_SUCCESS,
|
|
@@ -362,16 +411,17 @@ export async function runWxAppAccept(
|
|
|
362
411
|
lifecycle.resetWxAppMuted();
|
|
363
412
|
await lifecycle.syncWxAppMuteFromCallDetails();
|
|
364
413
|
|
|
365
|
-
deps.metricsManager.trackEvent(
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
414
|
+
deps.metricsManager.trackEvent(
|
|
415
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_SUCCESS,
|
|
416
|
+
{taskId, acceptReason, ...getWxAppTelephonyMetricContext(deps)},
|
|
417
|
+
['operational', 'behavioral']
|
|
418
|
+
);
|
|
369
419
|
} catch (error) {
|
|
370
420
|
lifecycle.setWxAppAnswerPending(false);
|
|
371
421
|
logTelephonyFailure('accept', deps, error);
|
|
372
422
|
deps.metricsManager.trackEvent(
|
|
373
423
|
METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_FAILED,
|
|
374
|
-
getWxAppTelephonyMetricFailurePayload(deps, error),
|
|
424
|
+
{acceptReason, ...getWxAppTelephonyMetricFailurePayload(deps, error)},
|
|
375
425
|
['operational', 'behavioral']
|
|
376
426
|
);
|
|
377
427
|
lifecycle.mapWxAppVoiceError(error, METHODS.ACCEPT);
|
|
@@ -383,9 +433,10 @@ export async function runWxAppAccept(
|
|
|
383
433
|
export async function runWxAppReject(
|
|
384
434
|
deps: WxAppVoiceDependencies,
|
|
385
435
|
lifecycle: WxAppVoiceLifecycle,
|
|
386
|
-
options?:
|
|
436
|
+
options?: WxAppAcceptRejectOptions
|
|
387
437
|
): Promise<void> {
|
|
388
438
|
const taskId = getInteractionId(deps);
|
|
439
|
+
const acceptReason = options?.acceptReason ?? 'wxApp_offer_ready';
|
|
389
440
|
|
|
390
441
|
deps.metricsManager.timeEvent([
|
|
391
442
|
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
|
|
@@ -395,26 +446,33 @@ export async function runWxAppReject(
|
|
|
395
446
|
try {
|
|
396
447
|
await rejectOnWebex(deps, options);
|
|
397
448
|
|
|
398
|
-
deps.metricsManager.trackEvent(
|
|
399
|
-
|
|
400
|
-
|
|
401
|
-
|
|
449
|
+
deps.metricsManager.trackEvent(
|
|
450
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
|
|
451
|
+
{taskId, acceptReason, ...getWxAppTelephonyMetricContext(deps)},
|
|
452
|
+
['operational', 'behavioral']
|
|
453
|
+
);
|
|
402
454
|
} catch (error) {
|
|
403
455
|
logTelephonyFailure('decline', deps, error);
|
|
404
456
|
deps.metricsManager.trackEvent(
|
|
405
457
|
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
|
|
406
|
-
getWxAppTelephonyMetricFailurePayload(deps, error),
|
|
458
|
+
{acceptReason, ...getWxAppTelephonyMetricFailurePayload(deps, error)},
|
|
407
459
|
['operational', 'behavioral']
|
|
408
460
|
);
|
|
409
461
|
lifecycle.mapWxAppVoiceError(error, METHODS.REJECT);
|
|
410
462
|
}
|
|
411
463
|
}
|
|
412
464
|
|
|
465
|
+
export type WxAppOutdialDeclineOptions = {
|
|
466
|
+
acceptReason?: WxAppAcceptReason;
|
|
467
|
+
};
|
|
468
|
+
|
|
413
469
|
export async function runWxAppOutdialDecline<T>(
|
|
414
470
|
deps: WxAppVoiceDependencies,
|
|
415
|
-
executeCancel: () => Promise<T
|
|
471
|
+
executeCancel: () => Promise<T>,
|
|
472
|
+
options?: WxAppOutdialDeclineOptions
|
|
416
473
|
): Promise<T> {
|
|
417
474
|
const taskId = getInteractionId(deps);
|
|
475
|
+
const acceptReason = options?.acceptReason ?? 'wxApp_offer_ready';
|
|
418
476
|
|
|
419
477
|
deps.metricsManager.timeEvent([
|
|
420
478
|
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
|
|
@@ -424,17 +482,18 @@ export async function runWxAppOutdialDecline<T>(
|
|
|
424
482
|
try {
|
|
425
483
|
const result = await executeCancel();
|
|
426
484
|
|
|
427
|
-
deps.metricsManager.trackEvent(
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
485
|
+
deps.metricsManager.trackEvent(
|
|
486
|
+
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
|
|
487
|
+
{taskId, acceptReason, ...getWxAppTelephonyMetricContext(deps)},
|
|
488
|
+
['operational', 'behavioral']
|
|
489
|
+
);
|
|
431
490
|
|
|
432
491
|
return result;
|
|
433
492
|
} catch (error) {
|
|
434
493
|
logWxAppOutdialDeclineFailure(deps, error);
|
|
435
494
|
deps.metricsManager.trackEvent(
|
|
436
495
|
METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
|
|
437
|
-
getWxAppOutdialDeclineFailurePayload(deps, error),
|
|
496
|
+
{acceptReason, ...getWxAppOutdialDeclineFailurePayload(deps, error)},
|
|
438
497
|
['operational', 'behavioral']
|
|
439
498
|
);
|
|
440
499
|
throw error;
|
|
@@ -458,7 +517,7 @@ export async function runWxAppToggleMute(
|
|
|
458
517
|
|
|
459
518
|
deps.metricsManager.trackEvent(
|
|
460
519
|
METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_SUCCESS,
|
|
461
|
-
{taskId, targetMuted: deps.getWxAppMuted()},
|
|
520
|
+
{taskId, targetMuted: deps.getWxAppMuted(), ...getWxAppTelephonyMetricContext(deps)},
|
|
462
521
|
['operational', 'behavioral']
|
|
463
522
|
);
|
|
464
523
|
} catch (error) {
|
|
@@ -489,7 +548,7 @@ export async function runWxAppTransmitDtmf(
|
|
|
489
548
|
|
|
490
549
|
deps.metricsManager.trackEvent(
|
|
491
550
|
METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_SUCCESS,
|
|
492
|
-
{taskId, dtmfLength: options.dtmf.length},
|
|
551
|
+
{taskId, dtmfLength: options.dtmf.length, ...getWxAppTelephonyMetricContext(deps)},
|
|
493
552
|
['operational', 'behavioral']
|
|
494
553
|
);
|
|
495
554
|
} catch (error) {
|