@webex/internal-plugin-metrics 3.12.0-next.55 → 3.12.0-next.57

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/AGENTS.md ADDED
@@ -0,0 +1,208 @@
1
+ <!-- sdd-generated-metadata
2
+ doc_kind: agent-entry
3
+ generated_from: agents@0.3.0
4
+ generated_by: claude-code
5
+ approved_by: rarajes2@cisco.com
6
+ updated_at: 2026-10-07T04:50:31Z
7
+ validation_status: pass
8
+ -->
9
+
10
+ # AI Agent Instructions
11
+
12
+ Agent entry for the `@webex/internal-plugin-metrics` package inside the `webex-js-sdk` monorepo.
13
+ Commands in this file are run from the monorepo root unless stated otherwise.
14
+
15
+ ## Working Method
16
+
17
+ - Read the relevant repository files before editing. Do not edit blind.
18
+ - Understand the task and affected surfaces before writing code.
19
+ - Prefer focused edits over rewriting whole files.
20
+ - Do not invent commands, paths, exports, or behavior. Verify them from repository-local source.
21
+ - Test or validate before declaring done.
22
+ - Keep output concise. If something is uncertain, say so instead of guessing.
23
+
24
+ ## Canonical Repository Documentation
25
+
26
+ Before changing code or documentation, start with:
27
+
28
+ - `docs/index.md` for repository documentation navigation;
29
+ - `docs/architecture.md` for repository boundaries and interactions;
30
+ - `docs/specs/README.md` for the manifest-backed module and contract registry;
31
+ - the affected module's manifest-routed `<module-path>/docs/README.md`; and
32
+ - `docs/adr/index.md` plus applicable concrete ADRs for durable decisions.
33
+
34
+ Follow `.sdd/manifest.json` for each contract's publication state and canonical source. Published
35
+ repository-owned HTTP APIs use their linked OpenAPI document; internal HTTP surfaces may link route
36
+ code, and published SDKs use their ecosystem-native API artifact. The manifest remains authoritative
37
+ for canonical paths and artifact decisions.
38
+
39
+ This package owns no HTTP surface. Every HTTP contract it touches belongs to the upstream Webex
40
+ metrics or unified telemetry service, and its own published surface is the SDK export declared in
41
+ `package.json` and `src/index.ts`.
42
+
43
+ ## 1. Commands (Run First)
44
+
45
+ Use only documented commands. Do not invent alternatives when a command is missing.
46
+
47
+ | Task | Command |
48
+ | ---------------------- | --------------------------------------------------------------------------------- |
49
+ | Install dependencies | `yarn install` (monorepo root) |
50
+ | Start dev environment | Not applicable — this package is a library plugin with no runnable dev server |
51
+ | Run tests | `yarn workspace @webex/internal-plugin-metrics test:unit` |
52
+ | Run linters | `yarn workspace @webex/internal-plugin-metrics test:style` |
53
+ | Build/release artifact | `yarn workspace @webex/internal-plugin-metrics build:src` |
54
+ | Format code | No dedicated command; `test:style` runs `eslint ./src/**/*.*` without `--fix` |
55
+
56
+ Run a single unit spec with `--targets` relative to `test/unit/spec/`, for example
57
+ `yarn workspace @webex/internal-plugin-metrics test:unit --targets network-telemetry.ts`.
58
+
59
+ There is no runnable full build-with-tests command. The `test` script in `package.json` chains
60
+ `test:integration` and `test:browser`, which this package does not define, and no `test/integration`
61
+ directory exists. `.sdd/manifest.json` records `commands.build` as an unresolved owner decision for that
62
+ reason. Do not treat `test` as a working gate and do not substitute another command for it.
63
+
64
+ If a required command is unknown, stop and ask for the exact command.
65
+
66
+ ## 2. Agent Persona and Scope
67
+
68
+ You are the Webex JS SDK telemetry engineering assistant for `@webex/internal-plugin-metrics`.
69
+
70
+ Primary outcomes:
71
+
72
+ - Deliver production-safe changes with passing tests.
73
+ - Preserve existing behavior unless a change request explicitly requires a behavior change.
74
+ - Keep docs and configuration aligned with code changes.
75
+
76
+ Definition of done:
77
+
78
+ 1. Requested change is implemented.
79
+ 2. Relevant tests/lint/build checks pass locally.
80
+ 3. Updated docs/config cover new behavior.
81
+ 4. PR summary includes risks and rollback notes.
82
+
83
+ ## 3. Repository Knowledge
84
+
85
+ ### Tech Stack
86
+
87
+ Document concrete versions so assistants choose compatible APIs.
88
+
89
+ | Area | Tooling | Version |
90
+ | ---------------- | ------------------------------------------------------------ | ------------------------------ |
91
+ | Language runtime | Node.js; `engines` in `package.json` sets the consumer floor, and the owner-declared manifest `toolchain` sets the build and test version | `22.14` to build and test; `>=18` floor |
92
+ | Build tool | `@webex/legacy-tools` plus `tsc`, per `package.json` scripts | workspace package |
93
+ | Test framework | Mocha, selected by `webex-legacy-tools test --runner mocha` | workspace runner |
94
+ | Lint/format | ESLint with `@webex/eslint-config-legacy`, per `.eslintrc.js` | `eslint ^8.24.0`, `prettier ^2.7.1` |
95
+
96
+ ### Project Map
97
+
98
+ - `src/index.ts`: package entry point; registers the `metrics` and `newMetrics` internal plugins and defines the export surface.
99
+ - `src/metrics.js`: the legacy `webex.internal.metrics` plugin, its three Batcher children and the network-telemetry wiring.
100
+ - `src/new-metrics.ts`: the `webex.internal.newMetrics` façade over every current event family.
101
+ - `src/network-telemetry.ts`: the SDK request-outcome collector and its summary payload.
102
+ - `src/call-diagnostic/`: Call Analyzer client, feature and media-quality events, the latency ledger and error-code mapping.
103
+ - `src/privacy-and-security-permission-enricher/`: permission-change enrichment policy for client events.
104
+ - `src/rtcMetrics/`: WebRTC stats reporting to the unified telemetry service.
105
+ - `src/unhandled-exception-telemetry/`: browser uncaught-error and rejection reporter.
106
+ - `test/unit/spec/`: the unit suite; its subtree mirrors `src/`.
107
+
108
+ ### Critical Constraints
109
+
110
+ - The Call Analyzer event schema is owned externally by `@webex/event-dictionary-ts`, pinned in `package.json`. Do not hand-declare event names or payload fields in `src/metrics.types.ts`; derive them from that package.
111
+ - Telemetry must never affect the requests it observes. Requests to the `metrics` and `unifiedTelemetry` services are excluded centrally in `src/network-telemetry.ts`, and every submission failure is caught and logged rather than propagated.
112
+ - Personal data must not reach telemetry. IP addresses are anonymized in `src/call-diagnostic/call-diagnostic-metrics.util.ts`, URL credentials and query data are stripped in `src/unhandled-exception-telemetry/utils.ts`, and endpoint path segments are redacted in `src/network-telemetry.ts`.
113
+ - `webex.internal.metrics` and `webex.internal.newMetrics` are consumed by sibling workspace packages, including `packages/@webex/plugin-meetings` and `packages/@webex/internal-plugin-device`. Treat both surfaces as stable and additive.
114
+ - `src/metrics.js` must tolerate being constructed standalone and before `webex.config` is populated. Its deferred `change:config` initialization in `initialize` exists for that reason; do not collapse it into a direct call.
115
+
116
+ ## 4. Testing Workflow
117
+
118
+ Run checks in this order:
119
+
120
+ 1. `yarn workspace @webex/internal-plugin-metrics test:unit`
121
+ 2. `yarn workspace @webex/internal-plugin-metrics test:style`
122
+ 3. Not applicable — this package defines no integration or end-to-end tier.
123
+
124
+ Testing rules:
125
+
126
+ - Add or update tests for every behavior change.
127
+ - Cover failure paths and edge cases, not only happy paths.
128
+ - Do not remove tests to make CI pass.
129
+ - Use `sinon` for stubs and `assert` from `@webex/test-helper-chai` for assertions, matching the existing specs.
130
+ - Control time with `sinon.useFakeTimers()`; the telemetry collectors are interval and timeout driven.
131
+
132
+ ## 5. Code Style and Patterns
133
+
134
+ Coding expectations:
135
+
136
+ - Follow repository style rules and static analysis output.
137
+ - Prefer small, composable functions over large procedural blocks.
138
+ - Make error handling explicit at system boundaries.
139
+ - Keep public interfaces stable unless a breaking change is requested.
140
+ - Before adding a literal path, filename, command, workflow identifier, status,
141
+ or other policy value, search the repository for exact and semantically
142
+ equivalent uses. When repeated production values represent one shared
143
+ contract, move them to the narrowest owning shared module or catalog as one
144
+ canonical named constant, enum, or type and update consumers. Keep incidental
145
+ similarities, one-off implementation details, and independent test fixtures
146
+ local.
147
+ - Do not import a default, named, object, or namespace binding and immediately
148
+ re-export it. Consumers should import from the owning module; package barrels
149
+ should use direct named re-exports.
150
+ - Do not write inline runtime `typeof` checks outside descriptively named guard
151
+ implementations. Import and reuse the owning guard instead; TypeScript
152
+ type-position `typeof` queries remain allowed.
153
+
154
+ Pattern example:
155
+
156
+ ```text
157
+ Preferred:
158
+ - Validate external input close to the boundary.
159
+ - Return typed/structured results.
160
+ - Include actionable error messages.
161
+
162
+ Avoid:
163
+ - Passing unvalidated payloads deep into the system.
164
+ - Swallowing exceptions or returning ambiguous null values.
165
+ ```
166
+
167
+ New code in this package is TypeScript. `src/metrics.js`, `src/batcher.js`,
168
+ `src/client-metrics-batcher.js` and `src/config.js` remain JavaScript because they extend the
169
+ Ampersand-based `WebexPlugin` and `Batcher` classes from `@webex/webex-core`; keep edits there
170
+ in-style rather than converting the file as a side effect.
171
+
172
+ ## 6. Git Workflow
173
+
174
+ - Branch naming: `[feature|fix|chore]/[short-description]`
175
+ - Commit format: conventional commits (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`)
176
+ - Keep commits focused and reversible.
177
+ - Rebase on the target branch before opening PR.
178
+
179
+ PR checklist:
180
+
181
+ - What changed and why.
182
+ - Tests run and results.
183
+ - Risk/impact areas.
184
+ - Rollback approach.
185
+
186
+ ## 7. Boundaries and Escalation
187
+
188
+ ### Always
189
+
190
+ - Prefer existing patterns over introducing new architecture.
191
+ - Keep changes minimal for the requested scope.
192
+ - Call out assumptions and unknowns explicitly.
193
+
194
+ ### Ask First
195
+
196
+ - Adding new dependencies or external services.
197
+ - Large refactors or schema migrations.
198
+ - Security-sensitive changes (auth, encryption, secrets handling).
199
+ - Any destructive data or infrastructure operation.
200
+ - Changing a submitted payload shape, metric name or event name, because downstream ingestion and dashboards depend on them.
201
+
202
+ ### Never
203
+
204
+ - Commit secrets, keys, or credentials.
205
+ - Bypass required checks by disabling tests or linters.
206
+ - Rewrite history on shared branches.
207
+ - Change unrelated files "while here" without clear need.
208
+ - Add personally identifiable information, credentials or raw IP addresses to any telemetry payload.
@@ -382,14 +382,15 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
382
382
  }, {
383
383
  key: "getIdentifiers",
384
384
  value: function getIdentifiers(options) {
385
- var _this$device, _meeting$locusInfo, _meeting$meetingInfo2, _meeting$meetingInfo3, _meeting$meetingInfo6, _meeting$meetingInfo8;
385
+ var _this$device, _meeting$locusInfo, _meeting$meetingInfo2, _meeting$meetingInfo3, _meeting$meetingInfo6, _meeting$meetingInfo8, _meeting$meetingInfo0;
386
386
  var meeting = options.meeting,
387
387
  mediaConnections = options.mediaConnections,
388
388
  correlationId = options.correlationId,
389
389
  webexConferenceIdStr = options.webexConferenceIdStr,
390
390
  globalMeetingId = options.globalMeetingId,
391
391
  preLoginId = options.preLoginId,
392
- sessionCorrelationId = options.sessionCorrelationId;
392
+ sessionCorrelationId = options.sessionCorrelationId,
393
+ locusCluster = options.locusCluster;
393
394
  var identifiers = {
394
395
  correlationId: 'unknown' // concerned with setting this to unknown. This will fail diagnostic events parsing because it's not a uuid pattern
395
396
  };
@@ -445,6 +446,9 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
445
446
  var _meeting$meetingInfo9;
446
447
  identifiers.webexSiteName = (_meeting$meetingInfo9 = meeting.meetingInfo) === null || _meeting$meetingInfo9 === void 0 ? void 0 : _meeting$meetingInfo9.siteName;
447
448
  }
449
+ if (meeting !== null && meeting !== void 0 && (_meeting$meetingInfo0 = meeting.meetingInfo) !== null && _meeting$meetingInfo0 !== void 0 && _meeting$meetingInfo0.locusClusterUrl) {
450
+ identifiers.locusCluster = meeting.meetingInfo.locusClusterUrl;
451
+ }
448
452
  if (mediaConnections) {
449
453
  var _mediaConnections$, _mediaConnections$2;
450
454
  identifiers.mediaAgentAlias = mediaConnections === null || mediaConnections === void 0 ? void 0 : (_mediaConnections$ = mediaConnections[0]) === null || _mediaConnections$ === void 0 ? void 0 : _mediaConnections$.mediaAgentAlias;
@@ -456,6 +460,9 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
456
460
  if (!(identifiers !== null && identifiers !== void 0 && identifiers.globalMeetingId) && globalMeetingId) {
457
461
  identifiers.globalMeetingId = globalMeetingId;
458
462
  }
463
+ if (!(identifiers !== null && identifiers !== void 0 && identifiers.locusCluster) && locusCluster) {
464
+ identifiers.locusCluster = locusCluster;
465
+ }
459
466
  if (identifiers.correlationId === undefined) {
460
467
  throw new Error('Identifiers initialization failed.');
461
468
  }
@@ -971,7 +978,7 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
971
978
  }, {
972
979
  key: "createCommonEventObjectInMeeting",
973
980
  value: function createCommonEventObjectInMeeting(_ref9) {
974
- var _this$webex$meetings20, _this$webex$meetings21, _this$webex$meetings22, _meeting$meetingInfo0, _options$joinFlowVers, _meeting$callStateFor;
981
+ var _this$webex$meetings20, _this$webex$meetings21, _this$webex$meetings22, _meeting$meetingInfo1, _options$joinFlowVers, _meeting$callStateFor, _options$meetingJoinP, _meeting$callStateFor2;
975
982
  var name = _ref9.name,
976
983
  options = _ref9.options,
977
984
  _ref9$eventType = _ref9.eventType,
@@ -980,7 +987,8 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
980
987
  mediaConnections = options.mediaConnections,
981
988
  globalMeetingId = options.globalMeetingId,
982
989
  webexConferenceIdStr = options.webexConferenceIdStr,
983
- sessionCorrelationId = options.sessionCorrelationId;
990
+ sessionCorrelationId = options.sessionCorrelationId,
991
+ locusCluster = options.locusCluster;
984
992
 
985
993
  // @ts-ignore
986
994
  var meeting = this.webex.meetings.getBasicMeetingInformation(meetingId);
@@ -1002,7 +1010,8 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
1002
1010
  mediaConnections: (meeting === null || meeting === void 0 ? void 0 : meeting.mediaConnections) || mediaConnections,
1003
1011
  webexConferenceIdStr: webexConferenceIdStr,
1004
1012
  globalMeetingId: globalMeetingId,
1005
- sessionCorrelationId: sessionCorrelationId
1013
+ sessionCorrelationId: sessionCorrelationId,
1014
+ locusCluster: locusCluster
1006
1015
  });
1007
1016
 
1008
1017
  // create common event object structure
@@ -1027,7 +1036,7 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
1027
1036
  webexSubServiceType: this.getSubServiceType(meeting),
1028
1037
  // @ts-ignore
1029
1038
  webClientPreload: (_this$webex$meetings20 = this.webex.meetings) === null || _this$webex$meetings20 === void 0 ? void 0 : (_this$webex$meetings21 = _this$webex$meetings20.config) === null || _this$webex$meetings21 === void 0 ? void 0 : (_this$webex$meetings22 = _this$webex$meetings21.metrics) === null || _this$webex$meetings22 === void 0 ? void 0 : _this$webex$meetings22.webClientPreload,
1030
- isVipMeeting: (meeting === null || meeting === void 0 ? void 0 : (_meeting$meetingInfo0 = meeting.meetingInfo) === null || _meeting$meetingInfo0 === void 0 ? void 0 : _meeting$meetingInfo0.vipmeeting) || false,
1039
+ isVipMeeting: (meeting === null || meeting === void 0 ? void 0 : (_meeting$meetingInfo1 = meeting.meetingInfo) === null || _meeting$meetingInfo1 === void 0 ? void 0 : _meeting$meetingInfo1.vipmeeting) || false,
1031
1040
  isAutomatedUser: AutomatedUserUtils.isAutomatedUser(),
1032
1041
  userActivation: this.getUserActivation()
1033
1042
  });
@@ -1041,9 +1050,10 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
1041
1050
  // @ts-ignore
1042
1051
  commonEventObject.meetingJoinedTime = meetingJoinedTime;
1043
1052
  }
1044
- if (options.meetingJoinPhase) {
1053
+ var meetingJoinPhase = (_options$meetingJoinP = options.meetingJoinPhase) !== null && _options$meetingJoinP !== void 0 ? _options$meetingJoinP : (_meeting$callStateFor2 = meeting.callStateForMetrics) === null || _meeting$callStateFor2 === void 0 ? void 0 : _meeting$callStateFor2.meetingJoinPhase;
1054
+ if (meetingJoinPhase) {
1045
1055
  // @ts-ignore
1046
- commonEventObject.meetingJoinPhase = options.meetingJoinPhase;
1056
+ commonEventObject.meetingJoinPhase = meetingJoinPhase;
1047
1057
  }
1048
1058
  return commonEventObject;
1049
1059
  }
@@ -1107,7 +1117,7 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
1107
1117
  }, {
1108
1118
  key: "createClientEventObjectPreMeeting",
1109
1119
  value: function createClientEventObjectPreMeeting(_ref10) {
1110
- var _this$webex$meetings23, _this$webex$meetings24, _this$webex$meetings25;
1120
+ var _this$webex$meetings23, _this$webex$meetings24, _this$webex$meetings25, _options$meetingJoinP2;
1111
1121
  var name = _ref10.name,
1112
1122
  options = _ref10.options,
1113
1123
  errors = _ref10.errors;
@@ -1115,7 +1125,8 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
1115
1125
  globalMeetingId = options.globalMeetingId,
1116
1126
  webexConferenceIdStr = options.webexConferenceIdStr,
1117
1127
  preLoginId = options.preLoginId,
1118
- sessionCorrelationId = options.sessionCorrelationId;
1128
+ sessionCorrelationId = options.sessionCorrelationId,
1129
+ locusCluster = options.locusCluster;
1119
1130
 
1120
1131
  // grab identifiers
1121
1132
  var identifiers = this.getIdentifiers({
@@ -1123,7 +1134,8 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
1123
1134
  sessionCorrelationId: sessionCorrelationId,
1124
1135
  preLoginId: preLoginId,
1125
1136
  globalMeetingId: globalMeetingId,
1126
- webexConferenceIdStr: webexConferenceIdStr
1137
+ webexConferenceIdStr: webexConferenceIdStr,
1138
+ locusCluster: locusCluster
1127
1139
  });
1128
1140
 
1129
1141
  // create client event object
@@ -1146,9 +1158,9 @@ var CallDiagnosticMetrics = exports.default = /*#__PURE__*/function (_StatelessW
1146
1158
  if (options.joinFlowVersion) {
1147
1159
  clientEventObject.joinFlowVersion = options.joinFlowVersion;
1148
1160
  }
1149
- if (options.meetingJoinPhase) {
1150
- clientEventObject.meetingJoinPhase = options.meetingJoinPhase;
1151
- }
1161
+
1162
+ // No meeting exists yet for pre-meeting events, so the join phase is pre-join by construction
1163
+ clientEventObject.meetingJoinPhase = (_options$meetingJoinP2 = options.meetingJoinPhase) !== null && _options$meetingJoinP2 !== void 0 ? _options$meetingJoinP2 : 'pre-join';
1152
1164
  if (options.userNameInput) {
1153
1165
  clientEventObject.userNameInput = options.userNameInput;
1154
1166
  }