@webex/contact-center 3.12.0-next.84 → 3.12.0-next.85

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 (47) hide show
  1. package/.sdd/manifest.json +876 -0
  2. package/AGENTS.md +65 -409
  3. package/ai-docs/ARCHITECTURE.md +168 -0
  4. package/ai-docs/CONTRACTS.md +46 -0
  5. package/ai-docs/GETTING_STARTED.md +168 -0
  6. package/ai-docs/GLOSSARY.md +43 -0
  7. package/ai-docs/README.md +25 -18
  8. package/ai-docs/REVIEW_CHECKLIST.md +41 -0
  9. package/ai-docs/RULES.md +187 -198
  10. package/ai-docs/SECURITY.md +52 -0
  11. package/ai-docs/SERVICE_STATE.md +48 -0
  12. package/ai-docs/SPEC_INDEX.md +65 -0
  13. package/ai-docs/adr/0001-spec-source-policy.md +55 -0
  14. package/ai-docs/adr/README.md +8 -0
  15. package/ai-docs/adr/_adr-template.md +31 -0
  16. package/ai-docs/contact-center-spec.md +341 -0
  17. package/ai-docs/features/generated-spec-conformance-fidelity-remediation/spec/feature-spec.md +117 -0
  18. package/ai-docs/features/residual-warning-coverage-completion/spec/feature-spec.md +203 -0
  19. package/ai-docs/features/validator-code-fidelity-drift-fix/spec/feature-spec.md +315 -0
  20. package/ai-docs/templates/README.md +1 -1
  21. package/ai-docs/templates/new-method/04-validation.md +1 -1
  22. package/ai-docs/templates/new-service/05-validation.md +1 -1
  23. package/dist/webex.js +1 -1
  24. package/package.json +1 -1
  25. package/src/metrics/ai-docs/AGENTS.md +2 -0
  26. package/src/metrics/ai-docs/ARCHITECTURE.md +2 -0
  27. package/src/metrics/ai-docs/metrics-spec.md +854 -0
  28. package/src/services/agent/ai-docs/AGENTS.md +3 -1
  29. package/src/services/agent/ai-docs/ARCHITECTURE.md +2 -0
  30. package/src/services/agent/ai-docs/agent-spec.md +504 -0
  31. package/src/services/ai-docs/AGENTS.md +4 -2
  32. package/src/services/ai-docs/services-spec.md +492 -0
  33. package/src/services/config/ai-docs/AGENTS.md +3 -1
  34. package/src/services/config/ai-docs/ARCHITECTURE.md +2 -0
  35. package/src/services/config/ai-docs/config-spec.md +669 -0
  36. package/src/services/core/ai-docs/AGENTS.md +4 -2
  37. package/src/services/core/ai-docs/ARCHITECTURE.md +2 -0
  38. package/src/services/core/ai-docs/core-spec.md +783 -0
  39. package/src/services/task/ai-docs/AGENTS.md +2 -0
  40. package/src/services/task/ai-docs/ARCHITECTURE.md +11 -2
  41. package/src/services/task/ai-docs/task-spec.md +1319 -0
  42. package/src/services/task/state-machine/ai-docs/AGENTS.md +38 -75
  43. package/src/services/task/state-machine/ai-docs/ARCHITECTURE.md +6 -4
  44. package/src/services/task/state-machine/ai-docs/task-state-machine-spec.md +2115 -0
  45. package/src/utils/AGENTS.md +3 -1
  46. package/src/utils/ai-docs/utils-spec.md +381 -0
  47. package/umd/contact-center.min.js +2 -2
@@ -0,0 +1,854 @@
1
+ # Metrics — SPEC
2
+
3
+ > Start here → root [`AGENTS.md`](../../../AGENTS.md) · router [`SPEC_INDEX.md`](../../../ai-docs/SPEC_INDEX.md) · system [`ARCHITECTURE.md`](../../../ai-docs/ARCHITECTURE.md). This is the module's canonical specification.
4
+
5
+ ## Metadata
6
+
7
+ | Field | Value |
8
+ |---|---|
9
+ | Module id | `metrics` |
10
+ | Source path(s) | `src/metrics` |
11
+ | Doc kind | Module spec |
12
+ | Coverage score | Partial (manifest-authoritative); 15/15 required document fields present |
13
+ | Generated from | `module-spec` @ SDLC template library `0.2.1` |
14
+ | generated_by / approved_by / updated_at | Codex generator / developer-approved follow-up review remediation / 2026-07-21 |
15
+ | Validation status | Follow-up validation passed (independent Claude fallback, 2026-07-21); coverage remains Partial |
16
+
17
+ ## Evidence Rules
18
+ Every requirement cites stable source and test file paths. Code/tests are the behavioral referee; routed source text supplies explicit intent and rationale. Missing or contradictory evidence blocks promotion.
19
+
20
+ ## Source Material Register
21
+ | Source material | Scope | Decision | Detail location or disposition |
22
+ |---|---|---|---|
23
+ | Reviewed prior module guides and architecture material | overview / architecture / API / tests | used and code-checked | Content is placed by meaning throughout this specification; exact routing remains in the manifest. |
24
+
25
+ ## Overview
26
+ Metrics is one of nine confirmed Contact Center SDK modules. Own timing, taxonomy, queuing, payload preparation, and submission for Contact Center behavioral, operational, and business telemetry. Existing reviewed documentation is migrated by meaning and code/tests remain the behavioral referee.
27
+
28
+ - **Singleton Pattern**: Single `MetricsManager` instance shared across the entire SDK
29
+
30
+ - **Three Metric Types**: Behavioral (user actions), operational (system events), business (business-level analytics)
31
+
32
+ - **Event Timing**: `timeEvent` + `trackEvent` pattern automatically calculates `duration_ms`
33
+
34
+ - **Queued Submission**: Events are queued until the Webex SDK is ready, then submitted in order
35
+
36
+ - **Behavioral Taxonomy**: Structured `product.agent.target.verb` naming convention for behavioral events
37
+
38
+ - **Payload Preparation**: Automatic cleanup of empty fields, space-to-underscore conversion, and `tabHidden` metadata
39
+
40
+ - **AQM Response Helpers**: Static methods to extract common tracking fields from AQM responses
41
+
42
+ ## Purpose / Responsibility
43
+ Own timing, taxonomy, queuing, payload preparation, and submission for Contact Center behavioral, operational, and business telemetry.
44
+
45
+ ## Stack
46
+ TypeScript 5.4 singleton service, Webex internal metrics APIs, LoggerProxy, Jest 27.
47
+
48
+ ## Folder / Package Structure
49
+ ```text
50
+ src/metrics/
51
+ ├── MetricsManager.ts
52
+ ├── behavioral-events.ts
53
+ ├── constants.ts
54
+ ```
55
+
56
+ ```text
57
+ src/metrics/
58
+ ├── MetricsManager.ts # Singleton metrics manager
59
+ ├── behavioral-events.ts # Behavioral event taxonomy mapping
60
+ ├── constants.ts # METRIC_EVENT_NAMES constants
61
+ └── ai-docs/
62
+ ├── AGENTS.md # Usage documentation (see PR #4762)
63
+ └── ARCHITECTURE.md # Preserved legacy, noncanonical architecture guide
64
+ ```
65
+
66
+ ## Key Files (source of truth)
67
+ | File | Holds |
68
+ |---|---|
69
+ | `src/metrics/MetricsManager.ts` | Authoritative Metrics implementation or contract source. |
70
+ | `src/metrics/behavioral-events.ts` | Authoritative Metrics implementation or contract source. |
71
+ | `src/metrics/constants.ts` | Authoritative Metrics implementation or contract source. |
72
+
73
+ ## Public Surface
74
+ | Contract ID | Type | Surface | Purpose | Compatibility / deprecation | Schema / detail link | Root index |
75
+ |---|---|---|---|---|---|---|
76
+ | `metrics.surface` | SDK / event / internal API | Internal `MetricsManager` singleton, metric event constants, taxonomy lookup, timing, and tracking helpers. | Stable module consumption boundary. | Additive changes by default; breaking package exports require a major-version transition. | `src/metrics/MetricsManager.ts` | `../../../ai-docs/CONTRACTS.md` |
77
+
78
+ Compatibility notes:
79
+ - Do not remove or reinterpret exported symbols/events without a documented consumer migration.
80
+
81
+ Returns the singleton instance. On first call with `{webex}`, binds to the Webex SDK and begins listening for the `ready` event.
82
+
83
+ **Parameters**:
84
+
85
+ - `options` (object, optional): `{webex: WebexSDK}` - The Webex SDK instance
86
+
87
+ **Returns**: `MetricsManager`
88
+
89
+ **Example**:
90
+
91
+ ```typescript
92
+ // During initialization (called internally by cc.register())
93
+ const metrics = MetricsManager.getInstance({webex});
94
+
95
+ // Subsequent calls (no webex needed)
96
+ const metrics = MetricsManager.getInstance();
97
+ ```
98
+
99
+ Starts a timer for one or more event keys. When a matching `trackEvent` / `trackBehavioralEvent` / `trackOperationalEvent` / `trackBusinessEvent` is called, `duration_ms` is automatically added to the payload.
100
+
101
+ **Parameters**:
102
+
103
+ - `keys` (string | string[]): One or more `METRIC_EVENT_NAMES` values. The first key is the tracking key; all keys in the array will resolve the same timer.
104
+
105
+ **Returns**: `void`
106
+
107
+ **Example**:
108
+
109
+ ```typescript
110
+ // Single key
111
+ metrics.timeEvent(METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS);
112
+
113
+ // Multiple keys (success/failure share one timer)
114
+ metrics.timeEvent([
115
+ METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
116
+ METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
117
+ ]);
118
+ ```
119
+
120
+ Tracks an event across one or more metric services.
121
+
122
+ **Parameters**:
123
+
124
+ - `name` (METRIC_EVENT_NAMES): The event name constant
125
+
126
+ - `payload` (EventPayload, optional): Key-value pairs of event data
127
+
128
+ - `metricServices` (MetricsType[], optional): Array of `'behavioral'` | `'operational'` | `'business'` (default: `['behavioral']`)
129
+
130
+ **Returns**: `void`
131
+
132
+ **Example**:
133
+
134
+ ```typescript
135
+ // Behavioral only (default)
136
+ metrics.trackEvent(METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS, {agentId: '123'});
137
+
138
+ // Multiple services
139
+ metrics.trackEvent(
140
+ METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS,
141
+ {interactionId: 'abc'},
142
+ ['behavioral', 'operational']
143
+ );
144
+ ```
145
+
146
+ Tracks a single behavioral event. Looks up the event taxonomy from `behavioral-events.ts` and submits via `webex.internal.newMetrics.submitBehavioralEvent`.
147
+
148
+ **Parameters**:
149
+
150
+ - `name` (METRIC_EVENT_NAMES): The event name
151
+
152
+ - `options` (EventPayload, optional): Additional payload data
153
+
154
+ **Returns**: `void`
155
+
156
+ Tracks a single operational event. Prefixes the event name with `WXCC_SDK_` and submits via `webex.internal.newMetrics.submitOperationalEvent`.
157
+
158
+ **Parameters**:
159
+
160
+ - `name` (METRIC_EVENT_NAMES): The event name
161
+
162
+ - `options` (EventPayload, optional): Additional payload data
163
+
164
+ **Returns**: `void`
165
+
166
+ Tracks a single business event. Prefixes the event name with `WXCC_SDK_` and submits via `webex.internal.newMetrics.submitBusinessEvent` with `appType: 'wxcc_sdk'`.
167
+
168
+ **Parameters**:
169
+
170
+ - `name` (METRIC_EVENT_NAMES): The event name
171
+
172
+ - `options` (EventPayload, optional): Additional payload data
173
+
174
+ **Returns**: `void`
175
+
176
+ Enables or disables metrics collection. When disabled, all pending events are cleared and new events are dropped.
177
+
178
+ **Parameters**:
179
+
180
+ - `disabled` (boolean): `true` to disable, `false` to enable
181
+
182
+ **Returns**: `void`
183
+
184
+ Static helper that extracts common tracking fields from an AQM success response.
185
+
186
+ **Parameters**:
187
+
188
+ - `response` (any): The AQM response object
189
+
190
+ **Returns**: `Record<string, any>` with fields: `agentId`, `agentSessionId`, `teamId`, `siteId`, `orgId`, `eventType`, `trackingId`, `notifTrackingId`
191
+
192
+ **Example**:
193
+
194
+ ```typescript
195
+ const fields = MetricsManager.getCommonTrackingFieldForAQMResponse(aqmResponse);
196
+ metrics.trackEvent(METRIC_EVENT_NAMES.TASK_ACCEPT_SUCCESS, {
197
+ ...fields,
198
+ interactionId: task.interactionId,
199
+ });
200
+ ```
201
+
202
+ Resets the singleton instance. Used for testing only.
203
+
204
+ **Returns**: `void`
205
+
206
+ All event names are defined in `METRIC_EVENT_NAMES` (`constants.ts`). Events follow a `<Category> <Action> <Result>` pattern.
207
+
208
+ | Constant | Value | Description |
209
+ |---|---|---|
210
+ | `STATION_LOGIN_SUCCESS` | `'Station Login Success'` | Agent station login succeeded |
211
+ | `STATION_LOGIN_FAILED` | `'Station Login Failed'` | Agent station login failed |
212
+ | `STATION_LOGOUT_SUCCESS` | `'Station Logout Success'` | Agent station logout succeeded |
213
+ | `STATION_LOGOUT_FAILED` | `'Station Logout Failed'` | Agent station logout failed |
214
+ | `STATION_RELOGIN_SUCCESS` | `'Station Relogin Success'` | Silent relogin succeeded |
215
+ | `STATION_RELOGIN_FAILED` | `'Station Relogin Failed'` | Silent relogin failed |
216
+ | `AGENT_STATE_CHANGE_SUCCESS` | `'Agent State Change Success'` | State change succeeded |
217
+ | `AGENT_STATE_CHANGE_FAILED` | `'Agent State Change Failed'` | State change failed |
218
+ | `FETCH_BUDDY_AGENTS_SUCCESS` | `'Fetch Buddy Agents Success'` | Buddy agents fetch succeeded |
219
+ | `FETCH_BUDDY_AGENTS_FAILED` | `'Fetch Buddy Agents Failed'` | Buddy agents fetch failed |
220
+ | `AGENT_RONA` | `'Agent RONA'` | Agent Ring-On-No-Answer triggered |
221
+ | `AGENT_CONTACT_ASSIGN_FAILED` | `'Agent Contact Assign Failed'` | Contact assignment failed |
222
+ | `AGENT_INVITE_FAILED` | `'Agent Invite Failed'` | Agent invite failed |
223
+ | `AGENT_DEVICE_TYPE_UPDATE_SUCCESS` | `'Agent Device Type Update Success'` | Device type update succeeded |
224
+ | `AGENT_DEVICE_TYPE_UPDATE_FAILED` | `'Agent Device Type Update Failed'` | Device type update failed |
225
+ | `TASK_ACCEPT_SUCCESS` / `FAILED` | `'Task Accept ...'` | Task accept result |
226
+ | `TASK_DECLINE_SUCCESS` / `FAILED` | `'Task Decline ...'` | Task decline result |
227
+ | `TASK_END_SUCCESS` / `FAILED` | `'Task End ...'` | Task end result |
228
+ | `TASK_WRAPUP_SUCCESS` / `FAILED` | `'Task Wrapup ...'` | Task wrapup result |
229
+ | `TASK_HOLD_SUCCESS` / `FAILED` | `'Task Hold ...'` | Task hold result |
230
+ | `TASK_RESUME_SUCCESS` / `FAILED` | `'Task Resume ...'` | Task resume result |
231
+ | `TASK_CONSULT_START_SUCCESS` / `FAILED` | `'Task Consult Start ...'` | Consult start result |
232
+ | `TASK_CONSULT_END_SUCCESS` / `FAILED` | `'Task Consult End ...'` | Consult end result |
233
+ | `TASK_TRANSFER_SUCCESS` / `FAILED` | `'Task Transfer ...'` | Transfer result |
234
+ | `TASK_PAUSE_RECORDING_SUCCESS` / `FAILED` | `'Task Pause Recording ...'` | Pause recording result |
235
+ | `TASK_RESUME_RECORDING_SUCCESS` / `FAILED` | `'Task Resume Recording ...'` | Resume recording result |
236
+ | `TASK_ACCEPT_CONSULT_SUCCESS` / `FAILED` | `'Task Accept Consult ...'` | Accept consult result |
237
+ | `TASK_AUTO_ANSWER_SUCCESS` / `FAILED` | `'Task Auto Answer ...'` | Auto-answer result |
238
+ | `TASK_OUTDIAL_SUCCESS` / `FAILED` | `'Task Outdial ...'` | Outdial result |
239
+ | `TASK_CONFERENCE_START_SUCCESS` / `FAILED` | `'Task Conference Start ...'` | Conference start result |
240
+ | `TASK_CONFERENCE_END_SUCCESS` / `FAILED` | `'Task Conference End ...'` | Conference end result |
241
+ | `TASK_CONFERENCE_TRANSFER_SUCCESS` / `FAILED` | `'Task Conference Transfer ...'` | Conference transfer result |
242
+ | `TASK_CONFERENCE_EXIT_SUCCESS` / `FAILED` | `'Task Conference Exit ...'` | Conference exit result |
243
+ | `TASK_SWITCH_CALL_SUCCESS` / `FAILED` | `'Task Switch Call ...'` | Switch call result |
244
+ | `WEBSOCKET_REGISTER_SUCCESS` / `FAILED` | `'Websocket Register ...'` | WebSocket registration result |
245
+ | `WEBSOCKET_DEREGISTER_SUCCESS` / `FAIL` | `'Websocket Deregister ...'` | WebSocket deregistration result |
246
+ | `WEBSOCKET_EVENT_RECEIVED` | `'Websocket Event Received'` | WebSocket event received |
247
+ | `UPLOAD_LOGS_SUCCESS` / `FAILED` | `'Upload Logs ...'` | Log upload result |
248
+ | `ENTRYPOINT_FETCH_SUCCESS` / `FAILED` | `'Entrypoint Fetch ...'` | Entry point fetch result |
249
+ | `ADDRESSBOOK_FETCH_SUCCESS` / `FAILED` | `'AddressBook Fetch ...'` | Address book fetch result |
250
+ | `QUEUE_FETCH_SUCCESS` / `FAILED` | `'Queue Fetch ...'` | Queue fetch result |
251
+ | `OUTDIAL_ANI_EP_FETCH_SUCCESS` / `FAILED` | `'Outdial ANI Entries Fetch ...'` | Outdial ANI entries fetch result |
252
+
253
+ All event names are defined in `constants.ts` as `METRIC_EVENT_NAMES`. Events follow a `{Domain} {Action} {Success|Failed}` naming convention:
254
+
255
+ | Category | Success Event | Failure Event |
256
+ |---|---|---|
257
+ | Station Login | `STATION_LOGIN_SUCCESS` | `STATION_LOGIN_FAILED` |
258
+ | Station Logout | `STATION_LOGOUT_SUCCESS` | `STATION_LOGOUT_FAILED` |
259
+ | Station Relogin | `STATION_RELOGIN_SUCCESS` | `STATION_RELOGIN_FAILED` |
260
+ | State Change | `AGENT_STATE_CHANGE_SUCCESS` | `AGENT_STATE_CHANGE_FAILED` |
261
+ | Buddy Agents | `FETCH_BUDDY_AGENTS_SUCCESS` | `FETCH_BUDDY_AGENTS_FAILED` |
262
+ | WebSocket Register | `WEBSOCKET_REGISTER_SUCCESS` | `WEBSOCKET_REGISTER_FAILED` |
263
+ | Task Accept | `TASK_ACCEPT_SUCCESS` | `TASK_ACCEPT_FAILED` |
264
+ | Task Decline | `TASK_DECLINE_SUCCESS` | `TASK_DECLINE_FAILED` |
265
+ | Task End | `TASK_END_SUCCESS` | `TASK_END_FAILED` |
266
+ | Task Wrapup | `TASK_WRAPUP_SUCCESS` | `TASK_WRAPUP_FAILED` |
267
+ | Task Hold | `TASK_HOLD_SUCCESS` | `TASK_HOLD_FAILED` |
268
+ | Task Resume | `TASK_RESUME_SUCCESS` | `TASK_RESUME_FAILED` |
269
+ | Task Consult Start | `TASK_CONSULT_START_SUCCESS` | `TASK_CONSULT_START_FAILED` |
270
+ | Task Consult End | `TASK_CONSULT_END_SUCCESS` | `TASK_CONSULT_END_FAILED` |
271
+ | Task Transfer | `TASK_TRANSFER_SUCCESS` | `TASK_TRANSFER_FAILED` |
272
+ | Task Resume Recording | `TASK_RESUME_RECORDING_SUCCESS` | `TASK_RESUME_RECORDING_FAILED` |
273
+ | Task Pause Recording | `TASK_PAUSE_RECORDING_SUCCESS` | `TASK_PAUSE_RECORDING_FAILED` |
274
+ | Task Accept Consult | `TASK_ACCEPT_CONSULT_SUCCESS` | `TASK_ACCEPT_CONSULT_FAILED` |
275
+ | Task Auto Answer | `TASK_AUTO_ANSWER_SUCCESS` | `TASK_AUTO_ANSWER_FAILED` |
276
+ | Conference Start | `TASK_CONFERENCE_START_SUCCESS` | `TASK_CONFERENCE_START_FAILED` |
277
+ | Conference End | `TASK_CONFERENCE_END_SUCCESS` | `TASK_CONFERENCE_END_FAILED` |
278
+ | Conference Transfer | `TASK_CONFERENCE_TRANSFER_SUCCESS` | `TASK_CONFERENCE_TRANSFER_FAILED` |
279
+ | Conference Exit | `TASK_CONFERENCE_EXIT_SUCCESS` | `TASK_CONFERENCE_EXIT_FAILED` |
280
+ | Switch Call | `TASK_SWITCH_CALL_SUCCESS` | `TASK_SWITCH_CALL_FAILED` |
281
+ | Outdial | `TASK_OUTDIAL_SUCCESS` | `TASK_OUTDIAL_FAILED` |
282
+ | Upload Logs | `UPLOAD_LOGS_SUCCESS` | `UPLOAD_LOGS_FAILED` |
283
+ | WebSocket Deregister | `WEBSOCKET_DEREGISTER_SUCCESS` | `WEBSOCKET_DEREGISTER_FAIL` |
284
+ | Device Type Update | `AGENT_DEVICE_TYPE_UPDATE_SUCCESS` | `AGENT_DEVICE_TYPE_UPDATE_FAILED` |
285
+ | EntryPoint | `ENTRYPOINT_FETCH_SUCCESS` | `ENTRYPOINT_FETCH_FAILED` |
286
+ | AddressBook | `ADDRESSBOOK_FETCH_SUCCESS` | `ADDRESSBOOK_FETCH_FAILED` |
287
+ | Queue | `QUEUE_FETCH_SUCCESS` | `QUEUE_FETCH_FAILED` |
288
+ | Outdial ANI Entries | `OUTDIAL_ANI_EP_FETCH_SUCCESS` | `OUTDIAL_ANI_EP_FETCH_FAILED` |
289
+
290
+ Special events (no success/failure pair):
291
+
292
+ - `AGENT_RONA` — has behavioral taxonomy (`service.agent_rona.set`)
293
+
294
+ - `AGENT_CONTACT_ASSIGN_FAILED` — has behavioral taxonomy (`service.agent_contact_assign.fail`)
295
+
296
+ - `AGENT_INVITE_FAILED` — has behavioral taxonomy (`service.agent_invite.fail`)
297
+
298
+ - `WEBSOCKET_EVENT_RECEIVED` — **no** behavioral taxonomy (not in `eventTaxonomyMap`)
299
+
300
+ Of the 80 defined metric names, 71 have behavioral taxonomy and 9 do not. Events **without** an `eventTaxonomyMap` entry are the six `AI_ASSISTANT_*` names plus `WEBSOCKET_DEREGISTER_SUCCESS`, `WEBSOCKET_DEREGISTER_FAIL`, and `WEBSOCKET_EVENT_RECEIVED`.
301
+
302
+ ### Complete METRIC_EVENT_NAMES catalog
303
+
304
+ This table contains all 80 names from `src/metrics/constants.ts`; taxonomy presence is checked against `src/metrics/behavioral-events.ts`: 71 mapped and 9 unmapped.
305
+
306
+ | Constant | Emitted name | Behavioral taxonomy? |
307
+ |---|---|---|
308
+ | `STATION_LOGIN_SUCCESS` | `Station Login Success` | yes |
309
+ | `STATION_LOGIN_FAILED` | `Station Login Failed` | yes |
310
+ | `STATION_LOGOUT_SUCCESS` | `Station Logout Success` | yes |
311
+ | `STATION_LOGOUT_FAILED` | `Station Logout Failed` | yes |
312
+ | `STATION_RELOGIN_SUCCESS` | `Station Relogin Success` | yes |
313
+ | `STATION_RELOGIN_FAILED` | `Station Relogin Failed` | yes |
314
+ | `AGENT_STATE_CHANGE_SUCCESS` | `Agent State Change Success` | yes |
315
+ | `AGENT_STATE_CHANGE_FAILED` | `Agent State Change Failed` | yes |
316
+ | `FETCH_BUDDY_AGENTS_SUCCESS` | `Fetch Buddy Agents Success` | yes |
317
+ | `FETCH_BUDDY_AGENTS_FAILED` | `Fetch Buddy Agents Failed` | yes |
318
+ | `WEBSOCKET_REGISTER_SUCCESS` | `Websocket Register Success` | yes |
319
+ | `WEBSOCKET_REGISTER_FAILED` | `Websocket Register Failed` | yes |
320
+ | `AGENT_RONA` | `Agent RONA` | yes |
321
+ | `AGENT_CONTACT_ASSIGN_FAILED` | `Agent Contact Assign Failed` | yes |
322
+ | `AGENT_INVITE_FAILED` | `Agent Invite Failed` | yes |
323
+ | `TASK_ACCEPT_SUCCESS` | `Task Accept Success` | yes |
324
+ | `TASK_ACCEPT_FAILED` | `Task Accept Failed` | yes |
325
+ | `TASK_DECLINE_SUCCESS` | `Task Decline Success` | yes |
326
+ | `TASK_DECLINE_FAILED` | `Task Decline Failed` | yes |
327
+ | `TASK_END_SUCCESS` | `Task End Success` | yes |
328
+ | `TASK_END_FAILED` | `Task End Failed` | yes |
329
+ | `TASK_WRAPUP_SUCCESS` | `Task Wrapup Success` | yes |
330
+ | `TASK_WRAPUP_FAILED` | `Task Wrapup Failed` | yes |
331
+ | `TASK_HOLD_SUCCESS` | `Task Hold Success` | yes |
332
+ | `TASK_HOLD_FAILED` | `Task Hold Failed` | yes |
333
+ | `TASK_RESUME_SUCCESS` | `Task Resume Success` | yes |
334
+ | `TASK_RESUME_FAILED` | `Task Resume Failed` | yes |
335
+ | `TASK_CONSULT_START_SUCCESS` | `Task Consult Start Success` | yes |
336
+ | `TASK_CONSULT_START_FAILED` | `Task Consult Start Failed` | yes |
337
+ | `TASK_CONSULT_END_SUCCESS` | `Task Consult End Success` | yes |
338
+ | `TASK_CONSULT_END_FAILED` | `Task Consult End Failed` | yes |
339
+ | `TASK_TRANSFER_SUCCESS` | `Task Transfer Success` | yes |
340
+ | `TASK_TRANSFER_FAILED` | `Task Transfer Failed` | yes |
341
+ | `TASK_RESUME_RECORDING_SUCCESS` | `Task Resume Recording Success` | yes |
342
+ | `TASK_RESUME_RECORDING_FAILED` | `Task Resume Recording Failed` | yes |
343
+ | `TASK_PAUSE_RECORDING_SUCCESS` | `Task Pause Recording Success` | yes |
344
+ | `TASK_PAUSE_RECORDING_FAILED` | `Task Pause Recording Failed` | yes |
345
+ | `TASK_ACCEPT_CONSULT_SUCCESS` | `Task Accept Consult Success` | yes |
346
+ | `TASK_ACCEPT_CONSULT_FAILED` | `Task Accept Consult Failed` | yes |
347
+ | `TASK_AUTO_ANSWER_SUCCESS` | `Task Auto Answer Success` | yes |
348
+ | `TASK_AUTO_ANSWER_FAILED` | `Task Auto Answer Failed` | yes |
349
+ | `TASK_CONFERENCE_START_SUCCESS` | `Task Conference Start Success` | yes |
350
+ | `TASK_CONFERENCE_START_FAILED` | `Task Conference Start Failed` | yes |
351
+ | `TASK_CONFERENCE_END_SUCCESS` | `Task Conference End Success` | yes |
352
+ | `TASK_CONFERENCE_END_FAILED` | `Task Conference End Failed` | yes |
353
+ | `TASK_CONFERENCE_TRANSFER_SUCCESS` | `Task Conference Transfer Success` | yes |
354
+ | `TASK_CONFERENCE_TRANSFER_FAILED` | `Task Conference Transfer Failed` | yes |
355
+ | `TASK_CONFERENCE_EXIT_SUCCESS` | `Task Conference Exit Success` | yes |
356
+ | `TASK_CONFERENCE_EXIT_FAILED` | `Task Conference Exit Failed` | yes |
357
+ | `TASK_SWITCH_CALL_SUCCESS` | `Task Switch Call Success` | yes |
358
+ | `TASK_SWITCH_CALL_FAILED` | `Task Switch Call Failed` | yes |
359
+ | `TASK_OUTDIAL_SUCCESS` | `Task Outdial Success` | yes |
360
+ | `TASK_OUTDIAL_FAILED` | `Task Outdial Failed` | yes |
361
+ | `UPLOAD_LOGS_SUCCESS` | `Upload Logs Success` | yes |
362
+ | `UPLOAD_LOGS_FAILED` | `Upload Logs Failed` | yes |
363
+ | `WEBSOCKET_DEREGISTER_SUCCESS` | `Websocket Deregister Success` | no |
364
+ | `WEBSOCKET_DEREGISTER_FAIL` | `Websocket Deregister Failed` | no |
365
+ | `AGENT_DEVICE_TYPE_UPDATE_SUCCESS` | `Agent Device Type Update Success` | yes |
366
+ | `AGENT_DEVICE_TYPE_UPDATE_FAILED` | `Agent Device Type Update Failed` | yes |
367
+ | `WEBSOCKET_EVENT_RECEIVED` | `Websocket Event Received` | no |
368
+ | `ENTRYPOINT_FETCH_SUCCESS` | `Entrypoint Fetch Success` | yes |
369
+ | `ENTRYPOINT_FETCH_FAILED` | `Entrypoint Fetch Failed` | yes |
370
+ | `ADDRESSBOOK_FETCH_SUCCESS` | `AddressBook Fetch Success` | yes |
371
+ | `ADDRESSBOOK_FETCH_FAILED` | `AddressBook Fetch Failed` | yes |
372
+ | `QUEUE_FETCH_SUCCESS` | `Queue Fetch Success` | yes |
373
+ | `QUEUE_FETCH_FAILED` | `Queue Fetch Failed` | yes |
374
+ | `OUTDIAL_ANI_EP_FETCH_SUCCESS` | `Outdial ANI Entries Fetch Success` | yes |
375
+ | `OUTDIAL_ANI_EP_FETCH_FAILED` | `Outdial ANI Entries Fetch Failed` | yes |
376
+ | `CAMPAIGN_PREVIEW_ACCEPT_SUCCESS` | `Campaign Preview Accept Success` | yes |
377
+ | `CAMPAIGN_PREVIEW_ACCEPT_FAILED` | `Campaign Preview Accept Failed` | yes |
378
+ | `CAMPAIGN_PREVIEW_SKIP_SUCCESS` | `Campaign Preview Skip Success` | yes |
379
+ | `CAMPAIGN_PREVIEW_SKIP_FAILED` | `Campaign Preview Skip Failed` | yes |
380
+ | `CAMPAIGN_PREVIEW_REMOVE_SUCCESS` | `Campaign Preview Remove Success` | yes |
381
+ | `CAMPAIGN_PREVIEW_REMOVE_FAILED` | `Campaign Preview Remove Failed` | yes |
382
+ | `AI_ASSISTANT_SEND_EVENT_SUCCESS` | `AI Assistant Send Event Success` | no |
383
+ | `AI_ASSISTANT_SEND_EVENT_FAILED` | `AI Assistant Send Event Failed` | no |
384
+ | `AI_ASSISTANT_GET_SUGGESTED_RESPONSE_SUCCESS` | `AI Assistant Get Suggested Response Success` | no |
385
+ | `AI_ASSISTANT_GET_SUGGESTED_RESPONSE_FAILED` | `AI Assistant Get Suggested Response Failed` | no |
386
+ | `AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_SUCCESS` | `AI Assistant Fetch Historic Transcripts Success` | no |
387
+ | `AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_FAILED` | `AI Assistant Fetch Historic Transcripts Failed` | no |
388
+
389
+ Defined names without an `eventTaxonomyMap` entry: `AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_FAILED`, `AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_SUCCESS`, `AI_ASSISTANT_GET_SUGGESTED_RESPONSE_FAILED`, `AI_ASSISTANT_GET_SUGGESTED_RESPONSE_SUCCESS`, `AI_ASSISTANT_SEND_EVENT_FAILED`, `AI_ASSISTANT_SEND_EVENT_SUCCESS`, `WEBSOCKET_DEREGISTER_FAIL`, `WEBSOCKET_DEREGISTER_SUCCESS`, `WEBSOCKET_EVENT_RECEIVED`.
390
+
391
+ ## Requires (dependencies)
392
+ - `webex.internal.newMetrics` submission APIs
393
+ - Webex ready lifecycle
394
+ - LoggerProxy and browser visibility metadata
395
+
396
+ - **`@webex/internal-plugin-metrics`**: Provides `webex.internal.newMetrics` for actual metric submission (`submitBehavioralEvent`, `submitOperationalEvent`, `submitBusinessEvent`)
397
+
398
+ - **`LoggerProxy`**: Used for error logging within the metrics module
399
+
400
+ - **`Failure` type** (from `services/core/GlobalTypes`): Used in `getCommonTrackingFieldForAQMResponseFailed`
401
+
402
+ - **`PRODUCT_NAME`** (from `constants.ts`): Set to `'wxcc_sdk'`, used as the product identifier in behavioral taxonomy and as prefix for operational/business event names
403
+
404
+ ## Requirements
405
+ | ID | WHAT | WHY | Source Evidence | Test / Example Evidence | Assumptions / Gaps | Confidence |
406
+ |---|---|---|---|---|---|---|
407
+ | METRICS-R-001 | Track only names defined by METRIC_EVENT_NAMES and keep the canonical catalog synchronized with the const object. | Telemetry queries and dashboards depend on exact stable event names. | `src/metrics/constants.ts` | `test/unit/spec/metrics/MetricsManager.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
408
+ | METRICS-R-002 | Map behavioral events through eventTaxonomyMap and explicitly identify defined names without taxonomy. | Undefined taxonomy must not be mistaken for an emitted or classified behavioral event. | `src/metrics/behavioral-events.ts` | `test/unit/spec/metrics/behavioral-events.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
409
+ | METRICS-R-003 | When metrics are disabled, clear pending events and make `timeEvent` plus all tracking methods return without recording/submitting. | Telemetry must never block or alter product behavior and disablement must be comprehensive. | `src/metrics/MetricsManager.ts` | `test/unit/spec/metrics/MetricsManager.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
410
+ | METRICS-R-004 | Queue submissions until the host SDK is ready and flush through the correct behavioral/operational/business service. | Early lifecycle telemetry must not be lost solely because the host is not ready. | `src/metrics/MetricsManager.ts` | `test/unit/spec/metrics/MetricsManager.ts` | None; source and test evidence rechecked during the 2026-07-09 remediation; independent document revalidation pending. | PRESENT |
411
+ | METRICS-R-005 | Submit through the host SDK's `webex.internal.newMetrics` client without storing credentials or implementing authorization policy in MetricsManager. | Host-owned authentication keeps telemetry credential handling outside the Contact Center metrics module. | `src/metrics/MetricsManager.ts` | `test/unit/spec/metrics/MetricsManager.ts` | None; authentication is inherited and credential ownership is explicitly N/A. | PRESENT |
412
+
413
+ ## Design Overview
414
+ Metrics separates its stable consumption boundary from collaborators so ownership and failure behavior stay explicit. Telemetry is deliberately non-blocking and queue-backed so product behavior never waits for metrics; failures are logged rather than propagated.
415
+
416
+ > **Purpose**: Track behavioral, operational, and business metrics for Contact Center SDK operations using a singleton `MetricsManager`. Provides event timing, payload preparation, batching, and submission to the Webex metrics backend.
417
+
418
+ Each behavioral event maps to a structured taxonomy in `behavioral-events.ts`:
419
+
420
+ ```text
421
+ {product}.{agent}.{target}.{verb}
422
+ ```
423
+
424
+ - **product**: Always `'wxcc_sdk'` (from `PRODUCT_NAME`)
425
+
426
+ - **agent**: `'user'` for user-initiated actions, `'service'` for system-generated events
427
+
428
+ - **target**: Snake_case description of the action (e.g., `'station_login'`, `'task_accept'`)
429
+
430
+ - **verb**: `'complete'` for success, `'fail'` for failure, `'set'` for RONA events
431
+
432
+ **Example**: `STATION_LOGIN_SUCCESS` maps to `wxcc_sdk.user.station_login.complete`
433
+
434
+ > **Note**: The following events do **not** have behavioral taxonomy mappings in `behavioral-events.ts`:
435
+ > - `AI_ASSISTANT_SEND_EVENT_SUCCESS`
436
+ > - `AI_ASSISTANT_SEND_EVENT_FAILED`
437
+ > - `AI_ASSISTANT_GET_SUGGESTED_RESPONSE_SUCCESS`
438
+ > - `AI_ASSISTANT_GET_SUGGESTED_RESPONSE_FAILED`
439
+ > - `AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_SUCCESS`
440
+ > - `AI_ASSISTANT_FETCH_HISTORIC_TRANSCRIPTS_FAILED`
441
+ > - `WEBSOCKET_DEREGISTER_SUCCESS`
442
+ > - `WEBSOCKET_DEREGISTER_FAIL`
443
+ > - `WEBSOCKET_EVENT_RECEIVED`
444
+ >
445
+ > Calling `trackBehavioralEvent` with these event names will push an event with an `undefined` taxonomy.
446
+
447
+ > **Purpose**: Technical documentation for the metrics collection, batching, and submission system within the Contact Center SDK.
448
+
449
+ Each metric event name maps to a `BehavioralEventTaxonomy` with four fields:
450
+
451
+ ```typescript
452
+ type BehavioralEventTaxonomy = {
453
+ product: MetricEventProduct; // Always PRODUCT_NAME ('wxcc_sdk')
454
+ agent: MetricEventAgent; // 'user' or 'service'
455
+ target: string; // e.g., 'station_login', 'task_accept'
456
+ verb: MetricEventVerb; // 'complete' for success, 'fail' for failure
457
+ };
458
+ ```
459
+
460
+ The final behavioral event name is constructed as: `{product}.{agent}.{target}.{verb}`
461
+
462
+ Example: `wxcc_sdk.user.station_login.complete`
463
+
464
+ The mapping is defined in `behavioral-events.ts` via `eventTaxonomyMap` and accessed through `getEventTaxonomy(name)`.
465
+
466
+ ```mermaid
467
+ flowchart LR
468
+ A[timeEvent keys] --> B[runningEvents stores startTime + key Set]
469
+ B --> C[trackEvent called with one of the keys]
470
+ C --> D[addDurationIfTimed matches key]
471
+ D --> E[Calculates duration_ms = now - startTime]
472
+ E --> F[Removes all keys for that operation]
473
+ F --> G[Attaches duration_ms to payload]
474
+ ```
475
+
476
+ Usage pattern from `cc.ts`:
477
+
478
+ ```typescript
479
+ // Before operation
480
+ this.metricsManager.timeEvent([
481
+ METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
482
+ METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
483
+ ]);
484
+
485
+ // On success
486
+ this.metricsManager.trackEvent(
487
+ METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
488
+ { ...MetricsManager.getCommonTrackingFieldForAQMResponse(resp) },
489
+ ['behavioral', 'operational', 'business']
490
+ );
491
+
492
+ // On failure
493
+ this.metricsManager.trackEvent(
494
+ METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
495
+ { ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(failure) },
496
+ ['behavioral', 'operational', 'business']
497
+ );
498
+ ```
499
+
500
+ Two static helpers extract standardized fields from AQM responses for metric payloads:
501
+
502
+ Extracts: `agentId`, `agentSessionId`, `teamId`, `siteId`, `orgId`, `eventType`, `trackingId`, `notifTrackingId`
503
+
504
+ ## Data Flow
505
+ ```mermaid
506
+ flowchart TD
507
+ A[cc.ts calls metricsManager.timeEvent] --> B[Store startTime + keys in runningEvents]
508
+ B --> C[Operation executes]
509
+ C --> D{Success or Failure?}
510
+ D -->|Success| E[cc.ts calls metricsManager.trackEvent with success name]
511
+ D -->|Failure| F[cc.ts calls metricsManager.trackEvent with failure name]
512
+ E --> G[addDurationIfTimed attaches duration_ms]
513
+ F --> G
514
+ G --> H[preparePayload cleans and enriches]
515
+ H --> I{Metric type?}
516
+ I -->|behavioral| J[Push to pendingBehavioralEvents]
517
+ I -->|operational| K[Push to pendingOperationalEvents]
518
+ I -->|business| L[Push to pendingBusinessEvents]
519
+ J --> M[submitPendingBehavioralEvents]
520
+ K --> N[submitPendingOperationalEvents]
521
+ L --> O[submitPendingBusinessEvents]
522
+ M --> P[webex.internal.newMetrics.submitBehavioralEvent]
523
+ N --> Q[webex.internal.newMetrics.submitOperationalEvent]
524
+ O --> R[webex.internal.newMetrics.submitBusinessEvent]
525
+ ```
526
+
527
+ ## Sequence Diagram(s)
528
+ Sequence coverage:
529
+
530
+ | Operation group | Diagram | Failure / recovery coverage |
531
+ |---|---|---|
532
+ | Time and track an event | Timing and tracking | Success/failure keys share one timer; missing timing data leaves the payload untimed. |
533
+ | Queue until SDK readiness | SDK readiness | A not-ready SDK retains events; READY drains them through the configured metrics service. |
534
+ | Submit behavioral/operational/business events | Category submission | The module does not retry or requeue after handing an event to `newMetrics`. |
535
+ | Disable metrics | Disablement | Pending queues are cleared and future timing/tracking calls return immediately. |
536
+
537
+ ### Timing and tracking
538
+
539
+ ```mermaid
540
+ sequenceDiagram
541
+ participant CC as ContactCenter (cc.ts)
542
+ participant MM as MetricsManager
543
+ participant NM as webex.internal.newMetrics
544
+
545
+ CC->>MM: timeEvent([SUCCESS_KEY, FAILURE_KEY])
546
+ Note over MM: Store startTime + key set in runningEvents
547
+ CC->>CC: Execute operation (e.g., stationLogin)
548
+ Note over MM: trackEvent defaults to ['behavioral'] only if no metricServices specified
549
+ alt Success
550
+ CC->>MM: trackEvent(SUCCESS_KEY, payload, ['behavioral', 'operational', 'business'])
551
+ else Failure
552
+ CC->>MM: trackEvent(FAILURE_KEY, payload, ['behavioral', 'operational', 'business'])
553
+ end
554
+ MM->>MM: addDurationIfTimed → attach duration_ms
555
+ MM->>MM: preparePayload → clean empty fields, add tabHidden
556
+ loop For each metric type
557
+ MM->>MM: Push to pending queue
558
+ alt readyToSubmitEvents
559
+ MM->>NM: submit[Behavioral|Operational|Business]Event
560
+ else not ready
561
+ Note over MM: Events stay queued until SDK ready
562
+ end
563
+ end
564
+ ```
565
+
566
+ ### SDK readiness
567
+
568
+ ```mermaid
569
+ sequenceDiagram
570
+ participant CC as ContactCenter
571
+ participant MM as MetricsManager
572
+ participant Webex as WebexSDK
573
+
574
+ CC->>MM: getInstance({webex})
575
+ MM->>MM: Create singleton (if needed)
576
+ MM->>MM: setWebex(webex)
577
+ opt webex.ready === true
578
+ MM->>MM: setReadyToSubmitEvents()
579
+ MM->>MM: submitPendingEvents()
580
+ end
581
+ MM->>Webex: webex.once('ready', callback)
582
+ Note over MM: 'ready' listener is always registered
583
+ Webex-->>MM: 'ready' event fires
584
+ MM->>MM: setReadyToSubmitEvents()
585
+ MM->>MM: submitPendingEvents()
586
+ ```
587
+
588
+ ### Category submission
589
+
590
+ ```mermaid
591
+ sequenceDiagram
592
+ participant Caller
593
+ participant MM as MetricsManager
594
+ participant NM as webex.internal.newMetrics
595
+ Caller->>MM: trackEvent(name, payload, metricServices)
596
+ loop each requested metric service
597
+ alt behavioral
598
+ MM->>NM: submitBehavioralEvent(taxonomy + payload)
599
+ else operational
600
+ MM->>NM: submitOperationalEvent(WXCC_SDK_* + payload)
601
+ else business
602
+ MM->>NM: submitBusinessEvent(WXCC_SDK_* + appType)
603
+ else invalid metric service
604
+ MM->>MM: LoggerProxy.error(invalid type)
605
+ end
606
+ end
607
+ Note over MM,NM: No module-level retry or requeue after submission handoff
608
+ ```
609
+
610
+ ### Disablement
611
+
612
+ ```mermaid
613
+ sequenceDiagram
614
+ participant Caller
615
+ participant MM as MetricsManager
616
+ Caller->>MM: setMetricsDisabled(true)
617
+ MM->>MM: clear pending behavioral/operational/business queues
618
+ Caller->>MM: timeEvent(...) / trackEvent(...)
619
+ alt metricsDisabled
620
+ MM-->>Caller: return without timing, queueing, or submission
621
+ else re-enabled
622
+ MM->>MM: use normal timing/queue flow
623
+ end
624
+ ```
625
+
626
+ ## Class / Component Relationships
627
+ ```mermaid
628
+ classDiagram
629
+ class ContactCenter
630
+ class MetricsManager
631
+ class METRIC_EVENT_NAMES
632
+ class eventTaxonomyMap
633
+ class NewMetrics
634
+ ContactCenter --> MetricsManager : timeEvent / trackEvent
635
+ MetricsManager --> METRIC_EVENT_NAMES : canonical names
636
+ MetricsManager --> eventTaxonomyMap : behavioral taxonomy lookup
637
+ MetricsManager --> NewMetrics : submit queued event types
638
+ ```
639
+
640
+ | Component | File | Responsibility |
641
+ |---|---|---|
642
+ | `MetricsManager` | `MetricsManager.ts` | Singleton that manages event queuing, timing, payload preparation, and submission |
643
+ | `BehavioralEventTaxonomy`| `behavioral-events.ts` | Maps metric event names to structured taxonomy for behavioral analytics |
644
+ | `METRIC_EVENT_NAMES` | `constants.ts` | Canonical constant object of all tracked metric event names |
645
+
646
+ `MetricsManager` uses a private constructor with a static `getInstance` factory:
647
+
648
+ ```typescript
649
+ // MetricsManager.ts
650
+ export default class MetricsManager {
651
+ private static instance: MetricsManager;
652
+ private constructor() {}
653
+
654
+ public static getInstance(options?: {webex: WebexSDK}): MetricsManager {
655
+ if (!MetricsManager.instance) {
656
+ MetricsManager.instance = new MetricsManager();
657
+ }
658
+ if (!MetricsManager.instance.webex && options?.webex) {
659
+ MetricsManager.instance.setWebex(options.webex);
660
+ }
661
+ return MetricsManager.instance;
662
+ }
663
+
664
+ public static resetInstance() {
665
+ MetricsManager.instance = undefined;
666
+ }
667
+ }
668
+ ```
669
+
670
+ - The Webex SDK instance is set once via `setWebex()`, which listens for the `ready` event before flushing pending queues.
671
+
672
+ - `resetInstance()` sets the singleton to `undefined`, allowing a fresh instance to be created. Primarily used in tests.
673
+
674
+ ## Use Cases
675
+ - **UC-1 Time and track an event:** `timeEvent` records the start against success/failure keys and `trackEvent` attaches `duration_ms` before preparing and queuing the payload. Evidence: `src/metrics/MetricsManager.ts`, `test/unit/spec/metrics/MetricsManager.ts`.
676
+ - **UC-2 Queue until SDK readiness:** events remain in the behavioral/operational/business pending queues until the host is ready, then the ready callback drains them. Evidence: `src/metrics/MetricsManager.ts`, `test/unit/spec/metrics/MetricsManager.ts`.
677
+ - **UC-3 Submit event categories:** each requested metric service uses its matching `webex.internal.newMetrics` submission method; taxonomy is applied only where `eventTaxonomyMap` contains the name. Evidence: `src/metrics/MetricsManager.ts`, `src/metrics/behavioral-events.ts`, `test/unit/spec/metrics/MetricsManager.ts`.
678
+
679
+ ```typescript
680
+ import MetricsManager from '../metrics/MetricsManager';
681
+ import {METRIC_EVENT_NAMES} from '../metrics/constants';
682
+
683
+ // Get the singleton instance (webex is set during the ContactCenter READY callback)
684
+ const metrics = MetricsManager.getInstance();
685
+
686
+ // Time an operation, then track its result
687
+ metrics.timeEvent(METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS);
688
+ // ... perform the operation ...
689
+ metrics.trackEvent(METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS, {agentId: '123'});
690
+ ```
691
+
692
+ The standard pattern used throughout the Contact Center SDK:
693
+
694
+ ```typescript
695
+ const metrics = MetricsManager.getInstance();
696
+
697
+ // 1. Start timing before the operation
698
+ metrics.timeEvent([
699
+ METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS,
700
+ METRIC_EVENT_NAMES.STATION_LOGIN_FAILED,
701
+ ]);
702
+
703
+ try {
704
+ const response = await performLogin(params);
705
+
706
+ // 2a. Track success (duration_ms auto-added)
707
+ metrics.trackEvent(METRIC_EVENT_NAMES.STATION_LOGIN_SUCCESS, {
708
+ ...MetricsManager.getCommonTrackingFieldForAQMResponse(response),
709
+ });
710
+ } catch (error) {
711
+ // 2b. Track failure (duration_ms auto-added)
712
+ metrics.trackEvent(METRIC_EVENT_NAMES.STATION_LOGIN_FAILED, {
713
+ ...MetricsManager.getCommonTrackingFieldForAQMResponseFailed(error),
714
+ });
715
+ }
716
+ ```
717
+
718
+ ## State Model
719
+ MetricsManager holds three pending queues, running timing entries, a readiness flag, a submission lock, and an independent disabled flag. Host READY enables draining; disabling metrics prevents both timing and tracking and clears pending events through the configured disable path.
720
+
721
+ `preparePayload()` processes every event payload before submission:
722
+
723
+ 1. **Removes empty/null/undefined fields** — strips keys with `undefined`, `null`, `''`, any arrays, or empty objects
724
+
725
+ 2. **Converts spaces to underscores** — `spacesToUnderscore()` applied to all key names
726
+
727
+ 3. **Adds common metadata** — appends `tabHidden: document.hidden` in browser environments
728
+
729
+ ## Business Rules & Invariants
730
+ - `timeEvent` and every tracking method return immediately while metrics are disabled.
731
+ - Only names present in `eventTaxonomyMap` receive behavioral taxonomy; 71 of 80 names are mapped and the six AI Assistant plus three WebSocket names are intentionally catalogued as unmapped.
732
+ - `submittingEvents` prevents overlapping queue-drain executions.
733
+ - Authentication is inherited from the host Webex SDK's metrics client; MetricsManager owns no credentials, tokens, or authorization policy.
734
+
735
+ ## Concurrency & Reactive Flow
736
+ - Queue insertion is synchronous. Submission is gated by host readiness and the submission lock; each metric type drains through its own host method without propagating telemetry failures to product callers.
737
+
738
+ MetricsManager maintains three independent pending event queues:
739
+
740
+ | Queue | Type | Submitted Via | Name Transform | Extra Metadata |
741
+ |---|---|---|---|---|
742
+ | `pendingBehavioralEvents` | behavioral | `webex.internal.newMetrics.submitBehavioralEvent` | Taxonomy-based (`{product}.{agent}.{target}.{verb}`) | None |
743
+ | `pendingOperationalEvents` | operational | `webex.internal.newMetrics.submitOperationalEvent` | `WXCC_SDK_` prefix + uppercase (e.g. `WXCC_SDK_STATION_LOGIN_SUCCESS`) | None |
744
+ | `pendingBusinessEvents` | business | `webex.internal.newMetrics.submitBusinessEvent` | `WXCC_SDK_` prefix + uppercase (same as operational) | `metadata: {appType: 'wxcc_sdk'}` |
745
+
746
+ ## State Machine
747
+ ```mermaid
748
+ stateDiagram-v2
749
+ [*] --> Buffering
750
+ Buffering --> ReadyToSubmit: host already ready / ready event
751
+ ReadyToSubmit --> Submitting: pending events and lock acquired
752
+ Submitting --> ReadyToSubmit: queues drained and lock released
753
+ Buffering --> Disabled: setMetricsDisabled(true)
754
+ ReadyToSubmit --> Disabled: setMetricsDisabled(true)
755
+ Disabled --> Buffering: enabled before host ready
756
+ Disabled --> ReadyToSubmit: enabled after host ready
757
+ ```
758
+
759
+ - **readyToSubmitEvents**: Set to `true` only after `webex.once('ready')` fires. Events queue until then.
760
+
761
+ - **submittingEvents**: Lock flag to prevent concurrent `submitPendingEvents()` calls.
762
+
763
+ - **metricsDisabled**: When `true`, `timeEvent` and all `track*` methods return early, and `clearPendingEvents()` empties all queues.
764
+
765
+ ## Pitfalls
766
+ - `METRIC_EVENT_NAMES` and `eventTaxonomyMap` are different inventories: nine defined names intentionally have no behavioral taxonomy.
767
+ - `setMetricsDisabled(true)` clears pending queues but does not create a delivery receipt; callers must not infer that previously submitted events were accepted.
768
+ - Submission helpers hand events to `webex.internal.newMetrics` without a module-level retry/requeue policy, so telemetry must remain non-blocking and non-authoritative.
769
+
770
+ Static helper that extracts common tracking fields from an AQM failure response.
771
+
772
+ **Parameters**:
773
+
774
+ - `failureResponse` (Failure): The AQM failure response object
775
+
776
+ **Returns**: `Record<string, any>` with fields: `agentId`, `trackingId`, `notifTrackingId`, `orgId`, `failureType`, `failureReason`, `reasonCode`
777
+
778
+ MetricsManager is designed to be non-blocking. Metric failures do not propagate to callers:
779
+
780
+ - If `webex` is not yet ready, events are queued in `pendingBehavioralEvents`, `pendingOperationalEvents`, or `pendingBusinessEvents`
781
+
782
+ - Once `webex.ready` fires, all pending events are flushed
783
+
784
+ - If metrics are disabled via `setMetricsDisabled(true)`, all track methods silently return
785
+
786
+ - Invalid metric types log an error via `LoggerProxy` but do not throw
787
+
788
+ MetricsManager does not throw errors to callers. Instead:
789
+
790
+ - Invalid metric types are logged via `LoggerProxy.error`
791
+
792
+ - Empty `timeEvent` key arrays are logged and ignored
793
+
794
+ - Disabled state (`metricsDisabled`) prevents both timing and tracking and silently drops new events
795
+
796
+ - The `submittingEvents` lock prevents race conditions during concurrent submissions
797
+
798
+ Extracts: `agentId`, `trackingId`, `notifTrackingId`, `orgId`, `failureType`, `failureReason`, `reasonCode`
799
+
800
+ **Cause**: Webex SDK not yet ready when `trackEvent` is called
801
+
802
+ **Solution**: Events are automatically queued in `pending*Events` arrays and flushed once `webex.once('ready')` fires. Verify the SDK is initializing correctly.
803
+
804
+ **Cause**: `timeEvent` was not called before `trackEvent`, or the event name does not match any key in `runningEvents`
805
+
806
+ **Solution**: Ensure `timeEvent([SUCCESS_KEY, FAILURE_KEY])` is called before the operation, and that the exact `METRIC_EVENT_NAMES` constant is used in both calls.
807
+
808
+ **Cause**: `metricsDisabled` is set to `true`
809
+
810
+ **Solution**: Check if `setMetricsDisabled(true)` was called. This clears all pending queues and causes all `track*` methods to return early.
811
+
812
+ ## Module Do's / Don'ts
813
+ - DO use `METRIC_EVENT_NAMES` and the matching category submitter instead of constructing event names manually.
814
+ - DO keep timing success/failure keys together so either outcome clears the same running timer.
815
+ - DON'T add a behavioral taxonomy entry unless the backend taxonomy contract exists.
816
+ - DON'T make product behavior depend on metrics delivery.
817
+
818
+ ## Key Design Trade-off
819
+ - Telemetry is deliberately non-blocking and queue-backed so product behavior never waits for metrics; failures are logged rather than propagated.
820
+
821
+ ## Test-Case Strategy (module)
822
+ Use `test/unit/spec/metrics/MetricsManager.ts` for readiness queues, timing/tracking, disabled behavior, submission categories, and failures. Use `test/unit/spec/metrics/behavioral-events.ts` to reconcile every taxonomy-backed name. Mechanically compare the complete constant catalog with taxonomy keys so newly defined events cannot disappear from the spec.
823
+
824
+ | Behavior / Requirement | Existing test evidence | Gap |
825
+ |---|---|---|
826
+ | `METRICS-R-001` | `test/unit/spec/metrics/MetricsManager.ts` | Add a catalog parity assertion if constants change. |
827
+ | `METRICS-R-002` | `test/unit/spec/metrics/behavioral-events.ts` | Keep explicit coverage for all nine unmapped names. |
828
+ | `METRICS-R-003` | `test/unit/spec/metrics/MetricsManager.ts` | None. |
829
+ | `METRICS-R-004` | `test/unit/spec/metrics/MetricsManager.ts` | None. |
830
+ | `METRICS-R-005` | `test/unit/spec/metrics/MetricsManager.ts` | Authentication ownership is verified indirectly through the host metrics client. |
831
+
832
+ ## Traceability
833
+ - Repo architecture: `../../../ai-docs/ARCHITECTURE.md` · Registry: `../../../ai-docs/SPEC_INDEX.md`
834
+ - Coverage state and contracts baseline: `../../../.sdd/manifest.json`
835
+
836
+ - [`MetricsManager.ts`](../MetricsManager.ts) - Singleton metrics manager implementation
837
+
838
+ - [`behavioral-events.ts`](../behavioral-events.ts) - Event taxonomy mapping
839
+
840
+ - [`constants.ts`](../constants.ts) - `METRIC_EVENT_NAMES` definitions
841
+
842
+ - [`../../constants.ts`](../../constants.ts) - `PRODUCT_NAME` constant
843
+
844
+ - [`services/core/GlobalTypes.ts`](../../services/core/GlobalTypes.ts) - `Failure` type definition
845
+
846
+ - [MetricsManager.ts](../MetricsManager.ts) — Singleton metrics manager
847
+
848
+ - [behavioral-events.ts](../behavioral-events.ts) — Event taxonomy mapping
849
+
850
+ - [constants.ts](../constants.ts) — METRIC_EVENT_NAMES definitions
851
+
852
+ - [cc.ts](../../cc.ts) — Main plugin class (primary consumer)
853
+
854
+ - [constants.ts](../../constants.ts) — PRODUCT_NAME used in event prefixing