@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.
Files changed (69) hide show
  1. package/.sdd/manifest.json +3 -2
  2. package/ai-docs/CONTRACTS.md +3 -2
  3. package/dist/cc.js +108 -6
  4. package/dist/cc.js.map +1 -1
  5. package/dist/metrics/MetricsManager.js +16 -0
  6. package/dist/metrics/MetricsManager.js.map +1 -1
  7. package/dist/metrics/behavioral-events.js +91 -0
  8. package/dist/metrics/behavioral-events.js.map +1 -1
  9. package/dist/metrics/constants.js +16 -0
  10. package/dist/metrics/constants.js.map +1 -1
  11. package/dist/services/AnswerCallOnWebexService.js +34 -6
  12. package/dist/services/AnswerCallOnWebexService.js.map +1 -1
  13. package/dist/services/WebexCrossClientService.js +54 -3
  14. package/dist/services/WebexCrossClientService.js.map +1 -1
  15. package/dist/services/WxAppTelephonyMercurySync.js +21 -6
  16. package/dist/services/WxAppTelephonyMercurySync.js.map +1 -1
  17. package/dist/services/task/state-machine/TaskStateMachine.js +0 -17
  18. package/dist/services/task/state-machine/TaskStateMachine.js.map +1 -1
  19. package/dist/services/task/state-machine/guards.js +7 -37
  20. package/dist/services/task/state-machine/guards.js.map +1 -1
  21. package/dist/services/task/voice/Voice.js +81 -14
  22. package/dist/services/task/voice/Voice.js.map +1 -1
  23. package/dist/services/task/voice/wxAppVoiceMethods.js +154 -1
  24. package/dist/services/task/voice/wxAppVoiceMethods.js.map +1 -1
  25. package/dist/services/wxAppDiagnosticLogging.js +122 -0
  26. package/dist/services/wxAppDiagnosticLogging.js.map +1 -0
  27. package/dist/types/cc.d.ts +1 -0
  28. package/dist/types/metrics/MetricsManager.d.ts +6 -0
  29. package/dist/types/metrics/constants.d.ts +15 -0
  30. package/dist/types/services/AnswerCallOnWebexService.d.ts +1 -0
  31. package/dist/types/services/WebexCrossClientService.d.ts +4 -0
  32. package/dist/types/services/task/state-machine/TaskStateMachine.d.ts +0 -42
  33. package/dist/types/services/task/state-machine/guards.d.ts +4 -4
  34. package/dist/types/services/task/voice/Voice.d.ts +5 -1
  35. package/dist/types/services/task/voice/wxAppVoiceMethods.d.ts +3 -0
  36. package/dist/types/services/wxAppDiagnosticLogging.d.ts +51 -0
  37. package/dist/webex.js +1 -1
  38. package/package.json +8 -8
  39. package/src/cc.ts +155 -7
  40. package/src/metrics/MetricsManager.ts +16 -0
  41. package/src/metrics/ai-docs/metrics-spec.md +71 -9
  42. package/src/metrics/behavioral-events.ts +92 -0
  43. package/src/metrics/constants.ts +18 -0
  44. package/src/services/AnswerCallOnWebexService.ts +41 -8
  45. package/src/services/WebexCrossClientService.ts +85 -4
  46. package/src/services/WxAppTelephonyMercurySync.ts +31 -4
  47. package/src/services/agent/ai-docs/agent-spec.md +1 -0
  48. package/src/services/task/ai-docs/task-spec.md +17 -16
  49. package/src/services/task/state-machine/TaskStateMachine.ts +0 -20
  50. package/src/services/task/state-machine/ai-docs/AGENTS.md +2 -4
  51. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +8 -9
  52. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +5 -7
  53. package/src/services/task/state-machine/guards.ts +7 -63
  54. package/src/services/task/voice/Voice.ts +115 -26
  55. package/src/services/task/voice/wxAppVoiceMethods.ts +211 -2
  56. package/src/services/wxAppDiagnosticLogging.ts +174 -0
  57. package/test/unit/spec/cc.ts +251 -22
  58. package/test/unit/spec/metrics/MetricsManager.ts +25 -0
  59. package/test/unit/spec/metrics/behavioral-events.ts +42 -0
  60. package/test/unit/spec/services/AnswerCallOnWebexService.ts +41 -0
  61. package/test/unit/spec/services/WebexCrossClientService.ts +207 -1
  62. package/test/unit/spec/services/WxAppTelephonyMercurySync.ts +23 -0
  63. package/test/unit/spec/services/task/state-machine/TaskStateMachine.ts +72 -46
  64. package/test/unit/spec/services/task/state-machine/guards.ts +6 -6
  65. package/test/unit/spec/services/task/voice/Voice.ts +380 -0
  66. package/test/unit/spec/services/task/voice/wxAppVoiceMethods.ts +145 -1
  67. package/test/unit/spec/services/wxAppDiagnosticLogging.ts +143 -0
  68. package/umd/contact-center.min.js +2 -2
  69. package/umd/contact-center.min.js.map +1 -1
@@ -1,14 +1,24 @@
1
- import AnswerCallOnWebexService from '../../AnswerCallOnWebexService';
1
+ import {EventPayload} from '@webex/internal-plugin-metrics/src/metrics.types';
2
+
3
+ import AnswerCallOnWebexService, {WxAppTelephonyError} from '../../AnswerCallOnWebexService';
2
4
  import LoggerProxy from '../../../logger-proxy';
3
5
  import {METHODS} from '../../../constants';
4
6
  import {getErrorDetails} from '../../core/Utils';
7
+ import {Failure} from '../../core/GlobalTypes';
5
8
  import {TaskData, TaskToggleMuteOptions, TaskTransmitDtmfOptions} from '../types';
6
9
  import {TaskState} from '../state-machine';
10
+ import MetricsManager from '../../../metrics/MetricsManager';
11
+ import {METRIC_EVENT_NAMES} from '../../../metrics/constants';
7
12
  import {
8
13
  decodedLineOwnerId,
9
14
  getWebexCallingDeviceDetailsForAgent,
10
15
  WebexCallingDeviceDetails,
11
16
  } from '../WebexCallingUtils';
17
+ import {
18
+ logWxAppTelephonyAction,
19
+ logWxAppValidationFailure,
20
+ type WxAppTelephonyAction,
21
+ } from '../../wxAppDiagnosticLogging';
12
22
 
13
23
  type WxAppParticipant = {
14
24
  deviceType?: string;
@@ -20,6 +30,7 @@ export type WxAppVoiceDependencies = {
20
30
  enableWxBetterTogether: boolean;
21
31
  answerCallOnWebexService?: AnswerCallOnWebexService;
22
32
  agentId?: string;
33
+ metricsManager: MetricsManager;
23
34
  getTaskData: () => TaskData;
24
35
  getTaskState: () => TaskState | undefined;
25
36
  getWxAppMuted: () => boolean;
@@ -135,12 +146,86 @@ export function resolveWxAppLineOwnerId(
135
146
  return decodedLineOwnerId(getWxAppLineOwnerId(deps));
136
147
  }
137
148
 
149
+ function getInteractionId(deps: WxAppVoiceDependencies): string | undefined {
150
+ return deps.getTaskData()?.interactionId;
151
+ }
152
+
153
+ function logTelephonyFailure(
154
+ action: WxAppTelephonyAction,
155
+ deps: WxAppVoiceDependencies,
156
+ error: unknown
157
+ ): void {
158
+ const wxError = error as WxAppTelephonyError;
159
+ logWxAppTelephonyAction({
160
+ action,
161
+ phase: 'failed',
162
+ interactionId: getInteractionId(deps),
163
+ trackingId: wxError.trackingId,
164
+ httpStatus: wxError.status,
165
+ failureReason: wxError.message,
166
+ });
167
+ }
168
+
169
+ function getWxAppTelephonyMetricFailurePayload(
170
+ deps: WxAppVoiceDependencies,
171
+ error: unknown
172
+ ): Record<string, string> {
173
+ const wxError = error as WxAppTelephonyError;
174
+ const payload: Record<string, string> = {
175
+ taskId: getInteractionId(deps) ?? '',
176
+ error: error instanceof Error ? error.toString() : String(error),
177
+ };
178
+
179
+ if (wxError.trackingId) {
180
+ payload.trackingId = wxError.trackingId;
181
+ }
182
+
183
+ return payload;
184
+ }
185
+
186
+ type AqmWrappedError = {
187
+ details?: Failure;
188
+ };
189
+
190
+ function getWxAppOutdialDeclineFailurePayload(
191
+ deps: WxAppVoiceDependencies,
192
+ error: unknown
193
+ ): EventPayload {
194
+ const base: EventPayload = {
195
+ taskId: getInteractionId(deps) ?? '',
196
+ error: error instanceof Error ? error.toString() : String(error),
197
+ };
198
+ const aqmDetails = (error as AqmWrappedError).details;
199
+
200
+ if (aqmDetails) {
201
+ return {...base, ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(aqmDetails)};
202
+ }
203
+
204
+ return getWxAppTelephonyMetricFailurePayload(deps, error);
205
+ }
206
+
207
+ function logWxAppOutdialDeclineFailure(deps: WxAppVoiceDependencies, error: unknown): void {
208
+ const aqmDetails = (error as AqmWrappedError).details;
209
+ const wxError = error as WxAppTelephonyError;
210
+
211
+ logWxAppTelephonyAction({
212
+ action: 'decline',
213
+ phase: 'failed',
214
+ interactionId: getInteractionId(deps),
215
+ trackingId: aqmDetails?.trackingId ?? wxError.trackingId,
216
+ httpStatus: wxError.status,
217
+ failureReason: aqmDetails?.data?.reason ?? wxError.message,
218
+ });
219
+ }
220
+
138
221
  function assertWxAppEnabled(deps: WxAppVoiceDependencies): void {
139
222
  if (!deps.enableWxBetterTogether) {
223
+ logWxAppValidationFailure('enableWxBetterTogether_disabled', getInteractionId(deps));
140
224
  throw new Error('enableWxBetterTogether is disabled');
141
225
  }
142
226
 
143
227
  if (!deps.answerCallOnWebexService) {
228
+ logWxAppValidationFailure('answer_call_on_webex_service_unavailable', getInteractionId(deps));
144
229
  throw new Error('AnswerCallOnWebexService is unavailable');
145
230
  }
146
231
  }
@@ -149,14 +234,18 @@ export async function acceptOnWebex(
149
234
  deps: WxAppVoiceDependencies,
150
235
  options?: {lineOwnerId?: string}
151
236
  ): Promise<void> {
237
+ const interactionId = getInteractionId(deps);
238
+ logWxAppTelephonyAction({action: 'accept', phase: 'started', interactionId});
152
239
  assertWxAppEnabled(deps);
153
240
 
154
241
  if (!isWebexAppCallingOffer(deps)) {
242
+ logWxAppValidationFailure('not_wxApp_calling_offer', interactionId);
155
243
  throw new Error('Task is not a wxApp calling offer');
156
244
  }
157
245
 
158
246
  const details = getCallingDeviceDetails(deps);
159
247
  if (!details) {
248
+ logWxAppValidationFailure('wxApp_device_details_unavailable', interactionId);
160
249
  throw new Error('WxApp calling device details are unavailable');
161
250
  }
162
251
 
@@ -165,20 +254,25 @@ export async function acceptOnWebex(
165
254
  endpointId: details.deviceId,
166
255
  lineOwnerId: resolveWxAppLineOwnerId(deps, options?.lineOwnerId),
167
256
  });
257
+ logWxAppTelephonyAction({action: 'accept', phase: 'success', interactionId});
168
258
  }
169
259
 
170
260
  export async function rejectOnWebex(
171
261
  deps: WxAppVoiceDependencies,
172
262
  options?: {lineOwnerId?: string}
173
263
  ): Promise<void> {
264
+ const interactionId = getInteractionId(deps);
265
+ logWxAppTelephonyAction({action: 'decline', phase: 'started', interactionId});
174
266
  assertWxAppEnabled(deps);
175
267
 
176
268
  if (!isWebexAppInboundCallingOffer(deps)) {
269
+ logWxAppValidationFailure('not_wxApp_inbound_calling_offer', interactionId);
177
270
  throw new Error('Task is not a wxApp inbound calling offer');
178
271
  }
179
272
 
180
273
  const details = getCallingDeviceDetails(deps);
181
274
  if (!details) {
275
+ logWxAppValidationFailure('wxApp_device_details_unavailable', interactionId);
182
276
  throw new Error('WxApp calling device details are unavailable');
183
277
  }
184
278
 
@@ -186,16 +280,20 @@ export async function rejectOnWebex(
186
280
  callId: details.deviceCallId,
187
281
  lineOwnerId: resolveWxAppLineOwnerId(deps, options?.lineOwnerId),
188
282
  });
283
+ logWxAppTelephonyAction({action: 'decline', phase: 'success', interactionId});
189
284
  }
190
285
 
191
286
  export async function toggleMuteOnWebex(
192
287
  deps: WxAppVoiceDependencies,
193
288
  options?: {lineOwnerId?: string; muted?: boolean}
194
289
  ): Promise<void> {
290
+ const interactionId = getInteractionId(deps);
291
+ logWxAppTelephonyAction({action: 'mute', phase: 'started', interactionId});
195
292
  assertWxAppEnabled(deps);
196
293
 
197
294
  const callId = getWebexCallingCallId(deps);
198
295
  if (!callId) {
296
+ logWxAppValidationFailure('wxApp_call_id_unavailable', interactionId);
199
297
  throw new Error('WxApp call ID is unavailable');
200
298
  }
201
299
 
@@ -209,16 +307,20 @@ export async function toggleMuteOnWebex(
209
307
  lineOwnerId: resolveWxAppLineOwnerId(deps, options?.lineOwnerId),
210
308
  });
211
309
  deps.setWxAppMuted(targetMuted);
310
+ logWxAppTelephonyAction({action: 'mute', phase: 'success', interactionId});
212
311
  }
213
312
 
214
313
  export async function transmitDtmfOnWebex(
215
314
  deps: WxAppVoiceDependencies,
216
315
  options: {dtmf: string; lineOwnerId?: string}
217
316
  ): Promise<void> {
317
+ const interactionId = getInteractionId(deps);
318
+ logWxAppTelephonyAction({action: 'dtmf', phase: 'started', interactionId});
218
319
  assertWxAppEnabled(deps);
219
320
 
220
321
  const callId = getWebexCallingCallId(deps);
221
322
  if (!callId) {
323
+ logWxAppValidationFailure('wxApp_call_id_unavailable', interactionId);
222
324
  throw new Error('WxApp call ID is unavailable');
223
325
  }
224
326
 
@@ -226,7 +328,8 @@ export async function transmitDtmfOnWebex(
226
328
  module: 'wxAppVoiceMethods',
227
329
  method: METHODS.TRANSMIT_DTMF,
228
330
  data: {
229
- callId,
331
+ interactionId,
332
+ callIdSuffix: callId.length <= 8 ? callId : callId.slice(-8),
230
333
  dtmfLength: options.dtmf.length,
231
334
  hasLineOwnerId: Boolean(options.lineOwnerId),
232
335
  },
@@ -237,6 +340,7 @@ export async function transmitDtmfOnWebex(
237
340
  dtmf: options.dtmf,
238
341
  lineOwnerId: resolveWxAppLineOwnerId(deps, options.lineOwnerId),
239
342
  });
343
+ logWxAppTelephonyAction({action: 'dtmf', phase: 'success', interactionId});
240
344
  }
241
345
 
242
346
  export async function runWxAppAccept(
@@ -244,14 +348,32 @@ export async function runWxAppAccept(
244
348
  lifecycle: WxAppVoiceLifecycle,
245
349
  options?: {lineOwnerId?: string}
246
350
  ): Promise<void> {
351
+ const taskId = getInteractionId(deps);
352
+
353
+ deps.metricsManager.timeEvent([
354
+ METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_SUCCESS,
355
+ METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_FAILED,
356
+ ]);
357
+
247
358
  try {
248
359
  lifecycle.setWxAppAcceptInFlight(true);
249
360
  lifecycle.setWxAppAnswerPending(true);
250
361
  await acceptOnWebex(deps, options);
251
362
  lifecycle.resetWxAppMuted();
252
363
  await lifecycle.syncWxAppMuteFromCallDetails();
364
+
365
+ deps.metricsManager.trackEvent(METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_SUCCESS, {taskId}, [
366
+ 'operational',
367
+ 'behavioral',
368
+ ]);
253
369
  } catch (error) {
254
370
  lifecycle.setWxAppAnswerPending(false);
371
+ logTelephonyFailure('accept', deps, error);
372
+ deps.metricsManager.trackEvent(
373
+ METRIC_EVENT_NAMES.WXAPP_TASK_ACCEPT_FAILED,
374
+ getWxAppTelephonyMetricFailurePayload(deps, error),
375
+ ['operational', 'behavioral']
376
+ );
255
377
  lifecycle.mapWxAppVoiceError(error, METHODS.ACCEPT);
256
378
  } finally {
257
379
  lifecycle.setWxAppAcceptInFlight(false);
@@ -263,21 +385,89 @@ export async function runWxAppReject(
263
385
  lifecycle: WxAppVoiceLifecycle,
264
386
  options?: {lineOwnerId?: string}
265
387
  ): Promise<void> {
388
+ const taskId = getInteractionId(deps);
389
+
390
+ deps.metricsManager.timeEvent([
391
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
392
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
393
+ ]);
394
+
266
395
  try {
267
396
  await rejectOnWebex(deps, options);
397
+
398
+ deps.metricsManager.trackEvent(METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS, {taskId}, [
399
+ 'operational',
400
+ 'behavioral',
401
+ ]);
268
402
  } catch (error) {
403
+ logTelephonyFailure('decline', deps, error);
404
+ deps.metricsManager.trackEvent(
405
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
406
+ getWxAppTelephonyMetricFailurePayload(deps, error),
407
+ ['operational', 'behavioral']
408
+ );
269
409
  lifecycle.mapWxAppVoiceError(error, METHODS.REJECT);
270
410
  }
271
411
  }
272
412
 
413
+ export async function runWxAppOutdialDecline<T>(
414
+ deps: WxAppVoiceDependencies,
415
+ executeCancel: () => Promise<T>
416
+ ): Promise<T> {
417
+ const taskId = getInteractionId(deps);
418
+
419
+ deps.metricsManager.timeEvent([
420
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS,
421
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
422
+ ]);
423
+
424
+ try {
425
+ const result = await executeCancel();
426
+
427
+ deps.metricsManager.trackEvent(METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_SUCCESS, {taskId}, [
428
+ 'operational',
429
+ 'behavioral',
430
+ ]);
431
+
432
+ return result;
433
+ } catch (error) {
434
+ logWxAppOutdialDeclineFailure(deps, error);
435
+ deps.metricsManager.trackEvent(
436
+ METRIC_EVENT_NAMES.WXAPP_TASK_DECLINE_FAILED,
437
+ getWxAppOutdialDeclineFailurePayload(deps, error),
438
+ ['operational', 'behavioral']
439
+ );
440
+ throw error;
441
+ }
442
+ }
443
+
273
444
  export async function runWxAppToggleMute(
274
445
  deps: WxAppVoiceDependencies,
275
446
  lifecycle: WxAppVoiceLifecycle,
276
447
  options?: TaskToggleMuteOptions
277
448
  ): Promise<void> {
449
+ const taskId = getInteractionId(deps);
450
+
451
+ deps.metricsManager.timeEvent([
452
+ METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_SUCCESS,
453
+ METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_FAILED,
454
+ ]);
455
+
278
456
  try {
279
457
  await toggleMuteOnWebex(deps, options);
458
+
459
+ deps.metricsManager.trackEvent(
460
+ METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_SUCCESS,
461
+ {taskId, targetMuted: deps.getWxAppMuted()},
462
+ ['operational', 'behavioral']
463
+ );
280
464
  } catch (error) {
465
+ logTelephonyFailure('mute', deps, error);
466
+ deps.metricsManager.trackEvent(
467
+ METRIC_EVENT_NAMES.WXAPP_TASK_MUTE_FAILED,
468
+ getWxAppTelephonyMetricFailurePayload(deps, error),
469
+ ['operational', 'behavioral']
470
+ );
281
471
  lifecycle.mapWxAppVoiceError(error, METHODS.TOGGLE_MUTE);
282
472
  }
283
473
  }
@@ -287,9 +477,28 @@ export async function runWxAppTransmitDtmf(
287
477
  lifecycle: WxAppVoiceLifecycle,
288
478
  options: TaskTransmitDtmfOptions
289
479
  ): Promise<void> {
480
+ const taskId = getInteractionId(deps);
481
+
482
+ deps.metricsManager.timeEvent([
483
+ METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_SUCCESS,
484
+ METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_FAILED,
485
+ ]);
486
+
290
487
  try {
291
488
  await transmitDtmfOnWebex(deps, options);
489
+
490
+ deps.metricsManager.trackEvent(
491
+ METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_SUCCESS,
492
+ {taskId, dtmfLength: options.dtmf.length},
493
+ ['operational', 'behavioral']
494
+ );
292
495
  } catch (error) {
496
+ logTelephonyFailure('dtmf', deps, error);
497
+ deps.metricsManager.trackEvent(
498
+ METRIC_EVENT_NAMES.WXAPP_TASK_DTMF_FAILED,
499
+ getWxAppTelephonyMetricFailurePayload(deps, error),
500
+ ['operational', 'behavioral']
501
+ );
293
502
  lifecycle.mapWxAppVoiceError(error, METHODS.TRANSMIT_DTMF);
294
503
  }
295
504
  }
@@ -0,0 +1,174 @@
1
+ import LoggerProxy from '../logger-proxy';
2
+
3
+ export const WXAPP_LOG_FEATURE = 'wxApp';
4
+ export const WXAPP_LOG_PREFIX = '[CC wxApp]';
5
+
6
+ export type WxAppAcceptReason =
7
+ | 'wxApp_offer_ready'
8
+ | 'wxApp_answer_pending'
9
+ | 'wxApp_accept_in_flight'
10
+ | 'extension_non_wxApp_offer'
11
+ | 'browser_webrtc_offer'
12
+ | 'outdial_auto_answer';
13
+
14
+ export type WxAppSessionSkipReason =
15
+ | 'unsupported_browser_login'
16
+ | 'flag_disabled'
17
+ | 'mercury_subscribe_failed'
18
+ | 'publish_failed';
19
+
20
+ export type WxAppTelephonyAction = 'accept' | 'decline' | 'mute' | 'dtmf';
21
+ export type WxAppTelephonyPhase = 'started' | 'success' | 'failed';
22
+
23
+ type WxAppLogData = Record<string, string | boolean | number | undefined>;
24
+
25
+ /** Last 8 chars of callId for correlation without logging full id. */
26
+ export function callIdSuffix(callId: string | null | undefined): string | undefined {
27
+ if (!callId) {
28
+ return undefined;
29
+ }
30
+
31
+ return callId.length <= 8 ? callId : callId.slice(-8);
32
+ }
33
+
34
+ function logWxApp(message: string, method: string, data: WxAppLogData): void {
35
+ LoggerProxy.info(`${WXAPP_LOG_PREFIX} ${message}`, {
36
+ module: 'wxAppDiagnosticLogging',
37
+ method,
38
+ data: {feature: WXAPP_LOG_FEATURE, ...data},
39
+ });
40
+ }
41
+
42
+ function logWxAppError(message: string, method: string, data: WxAppLogData): void {
43
+ LoggerProxy.error(`${WXAPP_LOG_PREFIX} ${message}`, {
44
+ module: 'wxAppDiagnosticLogging',
45
+ method,
46
+ data: {feature: WXAPP_LOG_FEATURE, ...data},
47
+ });
48
+ }
49
+
50
+ export function logWxAppSessionReadiness(params: {
51
+ enableWxBetterTogether: boolean;
52
+ loginOption: string | undefined;
53
+ wxAppHooksApplied: boolean;
54
+ usersubPublished: boolean;
55
+ mercurySubscribed: boolean;
56
+ telephonyTaskType: 'Voice' | 'WebRTC' | 'unknown';
57
+ skipReason?: WxAppSessionSkipReason;
58
+ }): void {
59
+ logWxApp('session readiness', 'logWxAppSessionReadiness', {
60
+ event: 'session_readiness',
61
+ enableWxBetterTogether: params.enableWxBetterTogether,
62
+ loginOption: params.loginOption,
63
+ wxAppHooksApplied: params.wxAppHooksApplied,
64
+ usersubPublished: params.usersubPublished,
65
+ mercurySubscribed: params.mercurySubscribed,
66
+ telephonyTaskType: params.telephonyTaskType,
67
+ skipReason: params.skipReason,
68
+ });
69
+ }
70
+
71
+ export function deriveWxAppAcceptReason(params: {
72
+ isWxAppInboundOffer: boolean;
73
+ isWxAppOutdialOffer: boolean;
74
+ isWebrtc: boolean;
75
+ isOutdial: boolean;
76
+ wxAppAcceptInFlight: boolean;
77
+ wxAppAnswerPending: boolean;
78
+ enableWxBetterTogether: boolean;
79
+ hasDeviceCallId: boolean;
80
+ }): WxAppAcceptReason {
81
+ if (params.isWxAppInboundOffer || params.isWxAppOutdialOffer) {
82
+ if (params.wxAppAcceptInFlight) {
83
+ return 'wxApp_accept_in_flight';
84
+ }
85
+ if (params.wxAppAnswerPending) {
86
+ return 'wxApp_answer_pending';
87
+ }
88
+
89
+ return 'wxApp_offer_ready';
90
+ }
91
+
92
+ if (params.isWebrtc && !params.isOutdial) {
93
+ return 'browser_webrtc_offer';
94
+ }
95
+
96
+ if (params.isOutdial) {
97
+ return 'outdial_auto_answer';
98
+ }
99
+
100
+ if (params.enableWxBetterTogether && !params.hasDeviceCallId) {
101
+ return 'extension_non_wxApp_offer';
102
+ }
103
+
104
+ return 'extension_non_wxApp_offer';
105
+ }
106
+
107
+ export function logWxAppOfferDecision(params: {
108
+ interactionId: string;
109
+ acceptVisible: boolean;
110
+ acceptEnabled: boolean;
111
+ acceptReason: WxAppAcceptReason;
112
+ wxAppParticipantDeviceType?: string;
113
+ hasDeviceCallId: boolean;
114
+ }): void {
115
+ logWxApp('offer decision', 'logWxAppOfferDecision', {
116
+ event: 'offer_decision',
117
+ interactionId: params.interactionId,
118
+ acceptVisible: params.acceptVisible,
119
+ acceptEnabled: params.acceptEnabled,
120
+ acceptReason: params.acceptReason,
121
+ wxAppParticipantDeviceType: params.wxAppParticipantDeviceType,
122
+ hasDeviceCallId: params.hasDeviceCallId,
123
+ });
124
+ }
125
+
126
+ export function logWxAppTelephonyAction(params: {
127
+ action: WxAppTelephonyAction;
128
+ phase: WxAppTelephonyPhase;
129
+ interactionId?: string;
130
+ trackingId?: string;
131
+ httpStatus?: number | string;
132
+ failureReason?: string;
133
+ }): void {
134
+ const payload: WxAppLogData = {
135
+ event: 'telephony_action',
136
+ action: params.action,
137
+ phase: params.phase,
138
+ interactionId: params.interactionId,
139
+ trackingId: params.trackingId,
140
+ httpStatus: params.httpStatus,
141
+ failureReason: params.failureReason,
142
+ };
143
+
144
+ if (params.phase === 'failed') {
145
+ logWxAppError(`telephony ${params.action} ${params.phase}`, 'logWxAppTelephonyAction', payload);
146
+ } else {
147
+ logWxApp(`telephony ${params.action} ${params.phase}`, 'logWxAppTelephonyAction', payload);
148
+ }
149
+ }
150
+
151
+ export function logWxAppValidationFailure(reason: string, interactionId?: string): void {
152
+ logWxAppError('validation failed', 'logWxAppValidationFailure', {
153
+ event: 'validation_failed',
154
+ reason,
155
+ interactionId,
156
+ });
157
+ }
158
+
159
+ export function logWxAppMercuryMuteSync(params: {
160
+ phase: 'received' | 'applied' | 'dropped';
161
+ muted?: boolean;
162
+ callIdSuffix?: string;
163
+ interactionId?: string;
164
+ dropReason?: string;
165
+ }): void {
166
+ logWxApp(`mercury mute sync ${params.phase}`, 'logWxAppMercuryMuteSync', {
167
+ event: 'mercury_mute_sync',
168
+ phase: params.phase,
169
+ muted: params.muted,
170
+ callIdSuffix: params.callIdSuffix,
171
+ interactionId: params.interactionId,
172
+ dropReason: params.dropReason,
173
+ });
174
+ }