@corva/ui 3.78.0-16 → 3.78.0-17
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/MCP_README.md +11 -3
- package/mcp-server/server.mjs +109 -53
- package/package.json +1 -1
package/MCP_README.md
CHANGED
|
@@ -711,9 +711,17 @@ To check whether telemetry is currently active for your install, run `/mcp__corv
|
|
|
711
711
|
|
|
712
712
|
To opt out entirely, set `CORVA_UI_MCP_TELEMETRY_DISABLED=1` in the server's environment (e.g. in the `env` block of
|
|
713
713
|
your MCP config file) — any non-empty value other than `0` or `false` works. The switch is honored at runtime before
|
|
714
|
-
any telemetry configuration is read.
|
|
715
|
-
|
|
716
|
-
|
|
714
|
+
any telemetry configuration is read.
|
|
715
|
+
|
|
716
|
+
Feedback travels over the same channel, so `submit_feedback` reports one of 2 delivery states:
|
|
717
|
+
|
|
718
|
+
- **queued** — telemetry is enabled and the message was handed to it for export. Delivery is not confirmed. Feedback is
|
|
719
|
+
exempt from the sampling rate, so it is never sampled out.
|
|
720
|
+
- **not sent** — nothing reaches the maintainers, and the response names the cause: the opt-out is set
|
|
721
|
+
(`CORVA_UI_MCP_TELEMETRY_DISABLED`, or `"enabled": false` in the local config), or telemetry has no valid
|
|
722
|
+
configuration in this session (or failed to start).
|
|
723
|
+
|
|
724
|
+
The `feedback` prompt relays that state to you, and `get_diagnostics` shows it as the feedback channel.
|
|
717
725
|
|
|
718
726
|
## FAQ
|
|
719
727
|
|
package/mcp-server/server.mjs
CHANGED
|
@@ -5,7 +5,7 @@ import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js'
|
|
|
5
5
|
import { OTLPMetricExporter, AggregationTemporalityPreference } from '@opentelemetry/exporter-metrics-otlp-http';
|
|
6
6
|
import { MeterProvider, PeriodicExportingMetricReader } from '@opentelemetry/sdk-metrics';
|
|
7
7
|
import { SpanKind, SpanStatusCode, propagation, context } from '@opentelemetry/api';
|
|
8
|
-
import {
|
|
8
|
+
import { TraceIdRatioBasedSampler, SamplingDecision, NodeTracerProvider, SimpleSpanProcessor } from '@opentelemetry/sdk-trace-node';
|
|
9
9
|
import { OTLPTraceExporter } from '@opentelemetry/exporter-trace-otlp-http';
|
|
10
10
|
import { randomUUID } from 'crypto';
|
|
11
11
|
import { defaultResource, resourceFromAttributes } from '@opentelemetry/resources';
|
|
@@ -246,9 +246,9 @@ const validateDsn = dsn => {
|
|
|
246
246
|
return url.href;
|
|
247
247
|
};
|
|
248
248
|
|
|
249
|
-
const MCP_SERVER_VERSION = '1.
|
|
249
|
+
const MCP_SERVER_VERSION = '1.9.0';
|
|
250
250
|
|
|
251
|
-
var version = "3.78.0-
|
|
251
|
+
var version = "3.78.0-17";
|
|
252
252
|
|
|
253
253
|
const CORVA_UI_VERSION = version;
|
|
254
254
|
|
|
@@ -535,11 +535,24 @@ const recordInitSpan = (tracer, attrs) => {
|
|
|
535
535
|
}
|
|
536
536
|
};
|
|
537
537
|
|
|
538
|
+
// Feedback leaves the process only as this span, so a ratio below 1 would silently lose messages.
|
|
539
|
+
// Matched by name: recordToolSpan sets gen_ai.tool.name after startSpan, too late for a sampler.
|
|
540
|
+
const FEEDBACK_SPAN_NAME = 'tools/call submit_feedback';
|
|
541
|
+
const createSampler = samplingRate => {
|
|
542
|
+
const ratioSampler = new TraceIdRatioBasedSampler(samplingRate);
|
|
543
|
+
return {
|
|
544
|
+
shouldSample: (context, traceId, spanName) => spanName === FEEDBACK_SPAN_NAME ? {
|
|
545
|
+
decision: SamplingDecision.RECORD_AND_SAMPLED
|
|
546
|
+
} : ratioSampler.shouldSample(context, traceId),
|
|
547
|
+
toString: () => `FeedbackAlwaysOn{${ratioSampler.toString()}}`
|
|
548
|
+
};
|
|
549
|
+
};
|
|
550
|
+
|
|
538
551
|
const createTracerProvider = config => {
|
|
539
552
|
const dsn = validateDsn(config.dsn);
|
|
540
553
|
return new NodeTracerProvider({
|
|
541
554
|
resource: config.resource,
|
|
542
|
-
sampler:
|
|
555
|
+
sampler: createSampler(config.samplingRate),
|
|
543
556
|
spanProcessors: [
|
|
544
557
|
// Export spans immediately at end for short-lived local MCP sessions.
|
|
545
558
|
new SimpleSpanProcessor(new OTLPTraceExporter({
|
|
@@ -659,7 +672,7 @@ const readLocalConfig = async logger => {
|
|
|
659
672
|
// Fail closed: the file's presence signals the user wants local control over telemetry,
|
|
660
673
|
// so a broken file must never silently fall back to the remote config that re-enables it.
|
|
661
674
|
logger.warn(`Local telemetry config file exists but is invalid: ${configPath}. Telemetry disabled (fix or delete the file to re-enable).`);
|
|
662
|
-
return '
|
|
675
|
+
return 'invalid';
|
|
663
676
|
} catch (error) {
|
|
664
677
|
if (error instanceof Error && 'code' in error && error.code === 'ENOENT') {
|
|
665
678
|
// File doesn't exist, which is normal. Silently skip.
|
|
@@ -668,10 +681,13 @@ const readLocalConfig = async logger => {
|
|
|
668
681
|
|
|
669
682
|
// Other errors (JSON parse errors, permissions, etc.) — same fail-closed rule as above.
|
|
670
683
|
logger.warn(`Failed to read local telemetry config from ${configPath}: ${error instanceof Error ? error.message : String(error)}. Telemetry disabled (fix or delete the file to re-enable).`);
|
|
671
|
-
return '
|
|
684
|
+
return 'invalid';
|
|
672
685
|
}
|
|
673
686
|
};
|
|
674
687
|
|
|
688
|
+
/** The user turned telemetry off, as opposed to it being unavailable. */
|
|
689
|
+
const TELEMETRY_OPT_OUT = 'opt-out';
|
|
690
|
+
|
|
675
691
|
// Read at call time, not module scope: in production bundles the rollup replace plugin bakes
|
|
676
692
|
// the URL in as a string literal either way, while in dev/tsx mode (and tests) this honors the
|
|
677
693
|
// process env at the moment of the call.
|
|
@@ -688,13 +704,18 @@ const isKillSwitchEnabled = () => {
|
|
|
688
704
|
const fetchTelemetryConfig = async logger => {
|
|
689
705
|
if (isKillSwitchEnabled()) {
|
|
690
706
|
logger.info('Telemetry disabled via CORVA_UI_MCP_TELEMETRY_DISABLED');
|
|
691
|
-
return
|
|
707
|
+
return TELEMETRY_OPT_OUT;
|
|
692
708
|
}
|
|
693
709
|
const localConfig = await readLocalConfig(logger);
|
|
694
710
|
|
|
695
711
|
// Explicit user opt-out ("enabled": false in the local file) — must stop here, not
|
|
696
712
|
// fall through to the remote config, which would override the user's choice.
|
|
697
713
|
if (localConfig === 'disabled') {
|
|
714
|
+
return TELEMETRY_OPT_OUT;
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
// A broken file fails closed too, but it is not the user's choice, so it is not opt-out.
|
|
718
|
+
if (localConfig === 'invalid') {
|
|
698
719
|
return null;
|
|
699
720
|
}
|
|
700
721
|
if (localConfig) {
|
|
@@ -743,10 +764,18 @@ const fetchTelemetryConfig = async logger => {
|
|
|
743
764
|
const initTelemetry = async logger => {
|
|
744
765
|
try {
|
|
745
766
|
const configResult = await fetchTelemetryConfig(logger);
|
|
767
|
+
if (configResult === TELEMETRY_OPT_OUT) {
|
|
768
|
+
return {
|
|
769
|
+
client: createNoopTelemetryClient(),
|
|
770
|
+
info: null,
|
|
771
|
+
disabledReason: 'opt-out'
|
|
772
|
+
};
|
|
773
|
+
}
|
|
746
774
|
if (!configResult) {
|
|
747
775
|
return {
|
|
748
776
|
client: createNoopTelemetryClient(),
|
|
749
|
-
info: null
|
|
777
|
+
info: null,
|
|
778
|
+
disabledReason: 'no-config'
|
|
750
779
|
};
|
|
751
780
|
}
|
|
752
781
|
const {
|
|
@@ -777,13 +806,15 @@ const initTelemetry = async logger => {
|
|
|
777
806
|
info: {
|
|
778
807
|
configSource: source,
|
|
779
808
|
samplingRate: config.samplingRate
|
|
780
|
-
}
|
|
809
|
+
},
|
|
810
|
+
disabledReason: null
|
|
781
811
|
};
|
|
782
812
|
} catch (error) {
|
|
783
813
|
logger.warn(`Telemetry init failed, continuing without telemetry: ${error}`);
|
|
784
814
|
return {
|
|
785
815
|
client: createNoopTelemetryClient(),
|
|
786
|
-
info: null
|
|
816
|
+
info: null,
|
|
817
|
+
disabledReason: 'no-config'
|
|
787
818
|
};
|
|
788
819
|
}
|
|
789
820
|
};
|
|
@@ -1037,7 +1068,7 @@ Do the following:
|
|
|
1037
1068
|
2. Infer a sentiment ('positive', 'negative', or 'neutral') from the feedback. If it is ambiguous, ask the user briefly or default to 'neutral'.
|
|
1038
1069
|
3. If the feedback is about a specific item, set category (component, hook, util, constant, client, theme, tool, docs, or other) and target (e.g. the component or hook name).
|
|
1039
1070
|
4. Call \`mcp__corva-ui__submit_feedback\` with the message and the fields above, and set \`source: "user"\` (this feedback is relayed from the user).
|
|
1040
|
-
5.
|
|
1071
|
+
5. Tell the user the delivery state the tool reported: queued, or not sent with its cause. Do not claim more than the tool reported.`;
|
|
1041
1072
|
return {
|
|
1042
1073
|
description: feedbackPromptDescription,
|
|
1043
1074
|
messages: [{
|
|
@@ -22471,11 +22502,54 @@ ${formatMethods(client.methods)}`;
|
|
|
22471
22502
|
};
|
|
22472
22503
|
};
|
|
22473
22504
|
|
|
22505
|
+
const FEEDBACK_CATEGORIES = [...ENTITY_TYPES, 'theme', 'tool', 'docs', 'other'];
|
|
22506
|
+
const submitFeedbackToolName = 'submit_feedback';
|
|
22507
|
+
const submitFeedbackToolTitle = 'Submit Feedback';
|
|
22508
|
+
const submitFeedbackToolDescription = `Send feedback about @corva/ui or this MCP server so the maintainers can improve docs and coverage.
|
|
22509
|
+
Use this when the user says something like "send feedback that…", or proactively when you hit a docs/coverage gap (a missing component, a wrong or incomplete prop, a hook that lacks an example, a search that should have matched but didn't).
|
|
22510
|
+
|
|
22511
|
+
Always infer sentiment and category from the feedback itself — do not leave them at the defaults. Reserve sentiment 'neutral' for genuinely factual/mixed feedback and category 'other' only when none of the specific categories fit.
|
|
22512
|
+
Set source to 'agent' when you are reporting a gap on your own initiative, or 'user' when relaying something the user said.
|
|
22513
|
+
Keep message specific and actionable; name the component/hook/util in target when the feedback is about a particular item.
|
|
22514
|
+
The response states whether the message was queued or not sent, and why; relay that state to the user as is.`;
|
|
22515
|
+
const submitFeedbackToolSchema = {
|
|
22516
|
+
message: z.string().min(1).describe('The feedback text (required). Be specific and actionable.'),
|
|
22517
|
+
sentiment: z.enum(['positive', 'negative', 'neutral']).optional().describe("Tone of the feedback — classify it from the message; don't default to neutral unless the feedback is genuinely neutral/factual (default: neutral)"),
|
|
22518
|
+
category: z.enum(FEEDBACK_CATEGORIES).optional().describe("What the feedback is about — pick the most specific category; use 'other' only when none apply"),
|
|
22519
|
+
target: z.string().optional().describe('The specific component/hook/util/tool the feedback is about (e.g. "Button")'),
|
|
22520
|
+
source: z.enum(['user', 'agent']).optional().describe("Who originated the feedback: 'user' (relayed) or 'agent' (default: user)")
|
|
22521
|
+
};
|
|
22522
|
+
const NOT_SENT_CAUSES = {
|
|
22523
|
+
'opt-out': 'telemetry is turned off by opt-out (CORVA_UI_MCP_TELEMETRY_DISABLED, or "enabled": false in the local telemetry config)',
|
|
22524
|
+
'no-config': 'telemetry has no valid configuration in this session, or it failed to start'
|
|
22525
|
+
};
|
|
22526
|
+
const describeFeedbackDelivery = delivery => delivery.state === 'queued' ? 'queued — handed to telemetry for export; delivery is not confirmed' : `not sent — ${NOT_SENT_CAUSES[delivery.cause]}`;
|
|
22527
|
+
const formatResponse = (delivery, sentiment) => delivery.state === 'queued' ? `Feedback ${describeFeedbackDelivery(delivery)}. (${sentiment})\nThe @corva/ui team uses this to improve the library and its docs.` : `Feedback ${describeFeedbackDelivery(delivery)}. Feedback travels only through telemetry, so the @corva/ui team will not receive this message. Tell the user, so they can share it with the team another way.`;
|
|
22528
|
+
const handleSubmitFeedback = (args, delivery) => {
|
|
22529
|
+
const message = args.message?.trim() ?? '';
|
|
22530
|
+
if (!message) {
|
|
22531
|
+
throw new Error('Feedback message cannot be empty.');
|
|
22532
|
+
}
|
|
22533
|
+
const sentiment = args.sentiment ?? 'neutral';
|
|
22534
|
+
const source = args.source ?? 'user';
|
|
22535
|
+
const category = args.category?.trim() || undefined;
|
|
22536
|
+
const target = args.target?.trim() || undefined;
|
|
22537
|
+
return {
|
|
22538
|
+
response: createToolResponse(formatResponse(delivery, sentiment)),
|
|
22539
|
+
delivery,
|
|
22540
|
+
message,
|
|
22541
|
+
sentiment,
|
|
22542
|
+
category,
|
|
22543
|
+
target,
|
|
22544
|
+
source
|
|
22545
|
+
};
|
|
22546
|
+
};
|
|
22547
|
+
|
|
22474
22548
|
const diagnosticsToolName = 'get_diagnostics';
|
|
22475
22549
|
const diagnosticsToolTitle = 'Get Server Diagnostics';
|
|
22476
22550
|
const diagnosticsToolDescription = `Get MCP server health metrics.
|
|
22477
22551
|
Returns server version, uptime, memory usage, request statistics, telemetry status,
|
|
22478
|
-
project identity (app key and package name), and the prompts this server registers.`;
|
|
22552
|
+
whether feedback can reach the maintainers, project identity (app key and package name), and the prompts this server registers.`;
|
|
22479
22553
|
const diagnosticsToolSchema = {};
|
|
22480
22554
|
const formatUptime = ms => {
|
|
22481
22555
|
const seconds = Math.floor(ms / 1000);
|
|
@@ -22534,6 +22608,9 @@ const handleGetDiagnostics = (stats, telemetry, identity, registeredPromptNames)
|
|
|
22534
22608
|
text += `${bold('Telemetry sampling rate')}: ${telemetry.samplingRate}\n`;
|
|
22535
22609
|
}
|
|
22536
22610
|
}
|
|
22611
|
+
if (telemetry.feedbackDelivery) {
|
|
22612
|
+
text += `${bold('Feedback channel')}: ${describeFeedbackDelivery(telemetry.feedbackDelivery)}\n`;
|
|
22613
|
+
}
|
|
22537
22614
|
} else {
|
|
22538
22615
|
text += `${bold('Status')}: Disabled\n`;
|
|
22539
22616
|
}
|
|
@@ -22550,41 +22627,6 @@ const handleGetDiagnostics = (stats, telemetry, identity, registeredPromptNames)
|
|
|
22550
22627
|
};
|
|
22551
22628
|
};
|
|
22552
22629
|
|
|
22553
|
-
const FEEDBACK_CATEGORIES = [...ENTITY_TYPES, 'theme', 'tool', 'docs', 'other'];
|
|
22554
|
-
const submitFeedbackToolName = 'submit_feedback';
|
|
22555
|
-
const submitFeedbackToolTitle = 'Submit Feedback';
|
|
22556
|
-
const submitFeedbackToolDescription = `Send feedback about @corva/ui or this MCP server so the maintainers can improve docs and coverage.
|
|
22557
|
-
Use this when the user says something like "send feedback that…", or proactively when you hit a docs/coverage gap (a missing component, a wrong or incomplete prop, a hook that lacks an example, a search that should have matched but didn't).
|
|
22558
|
-
|
|
22559
|
-
Always infer sentiment and category from the feedback itself — do not leave them at the defaults. Reserve sentiment 'neutral' for genuinely factual/mixed feedback and category 'other' only when none of the specific categories fit.
|
|
22560
|
-
Set source to 'agent' when you are reporting a gap on your own initiative, or 'user' when relaying something the user said.
|
|
22561
|
-
Keep message specific and actionable; name the component/hook/util in target when the feedback is about a particular item.`;
|
|
22562
|
-
const submitFeedbackToolSchema = {
|
|
22563
|
-
message: z.string().min(1).describe('The feedback text (required). Be specific and actionable.'),
|
|
22564
|
-
sentiment: z.enum(['positive', 'negative', 'neutral']).optional().describe("Tone of the feedback — classify it from the message; don't default to neutral unless the feedback is genuinely neutral/factual (default: neutral)"),
|
|
22565
|
-
category: z.enum(FEEDBACK_CATEGORIES).optional().describe("What the feedback is about — pick the most specific category; use 'other' only when none apply"),
|
|
22566
|
-
target: z.string().optional().describe('The specific component/hook/util/tool the feedback is about (e.g. "Button")'),
|
|
22567
|
-
source: z.enum(['user', 'agent']).optional().describe("Who originated the feedback: 'user' (relayed) or 'agent' (default: user)")
|
|
22568
|
-
};
|
|
22569
|
-
const handleSubmitFeedback = args => {
|
|
22570
|
-
const message = args.message?.trim() ?? '';
|
|
22571
|
-
if (!message) {
|
|
22572
|
-
throw new Error('Feedback message cannot be empty.');
|
|
22573
|
-
}
|
|
22574
|
-
const sentiment = args.sentiment ?? 'neutral';
|
|
22575
|
-
const source = args.source ?? 'user';
|
|
22576
|
-
const category = args.category?.trim() || undefined;
|
|
22577
|
-
const target = args.target?.trim() || undefined;
|
|
22578
|
-
return {
|
|
22579
|
-
response: createToolResponse(`Thanks — your feedback was recorded. (${sentiment})\nThe @corva/ui team uses this to improve the library and its docs.`),
|
|
22580
|
-
message,
|
|
22581
|
-
sentiment,
|
|
22582
|
-
category,
|
|
22583
|
-
target,
|
|
22584
|
-
source
|
|
22585
|
-
};
|
|
22586
|
-
};
|
|
22587
|
-
|
|
22588
22630
|
/**
|
|
22589
22631
|
* Authoring types for inline component migrations.
|
|
22590
22632
|
*
|
|
@@ -23038,7 +23080,8 @@ const registerTools = deps => {
|
|
|
23038
23080
|
getStats,
|
|
23039
23081
|
getTelemetryStatus,
|
|
23040
23082
|
getProjectIdentity,
|
|
23041
|
-
getTelemetry
|
|
23083
|
+
getTelemetry,
|
|
23084
|
+
getFeedbackDelivery
|
|
23042
23085
|
} = deps;
|
|
23043
23086
|
|
|
23044
23087
|
// TODO: every `inputSchema: …ToolSchema as any` below is unexplained — the cast predates any
|
|
@@ -23215,7 +23258,7 @@ const registerTools = deps => {
|
|
|
23215
23258
|
const parentContext = extractContextFromMeta(extra?._meta);
|
|
23216
23259
|
try {
|
|
23217
23260
|
return executeToolWithObservability(submitFeedbackToolName, args, () => {
|
|
23218
|
-
const result = handleSubmitFeedback(args);
|
|
23261
|
+
const result = handleSubmitFeedback(args, getFeedbackDelivery());
|
|
23219
23262
|
// Supplemental low-cardinality counter, in addition to the standard
|
|
23220
23263
|
// tool-call span/metrics recorded by the wrapper. No-op when disabled.
|
|
23221
23264
|
const telemetry = getTelemetry();
|
|
@@ -23226,7 +23269,7 @@ const registerTools = deps => {
|
|
|
23226
23269
|
...result,
|
|
23227
23270
|
isEmpty: false
|
|
23228
23271
|
};
|
|
23229
|
-
}, r => `feedback
|
|
23272
|
+
}, r => `feedback ${r.delivery.state} (${r.sentiment})`, {
|
|
23230
23273
|
parentContext,
|
|
23231
23274
|
getSpanAttributes: r => ({
|
|
23232
23275
|
// Indexed copy of the message so it is searchable/groupable in
|
|
@@ -23339,6 +23382,7 @@ class CorvaUiMcpServer {
|
|
|
23339
23382
|
|
|
23340
23383
|
telemetry = createNoopTelemetryClient();
|
|
23341
23384
|
telemetryInfo = null;
|
|
23385
|
+
telemetryDisabledReason = null;
|
|
23342
23386
|
telemetryInitPromise = null;
|
|
23343
23387
|
shutdownRequested = false;
|
|
23344
23388
|
pendingInitData = null;
|
|
@@ -23562,7 +23606,8 @@ class CorvaUiMcpServer {
|
|
|
23562
23606
|
getStats: () => this.getStats(),
|
|
23563
23607
|
getTelemetryStatus: () => this.getTelemetryStatus(),
|
|
23564
23608
|
getProjectIdentity: () => this.projectIdentity,
|
|
23565
|
-
getTelemetry: () => this.telemetry
|
|
23609
|
+
getTelemetry: () => this.telemetry,
|
|
23610
|
+
getFeedbackDelivery: () => this.getFeedbackDelivery()
|
|
23566
23611
|
});
|
|
23567
23612
|
}
|
|
23568
23613
|
setupPrompts() {
|
|
@@ -23580,10 +23625,12 @@ class CorvaUiMcpServer {
|
|
|
23580
23625
|
const telemetryTimer = this.mcpLogger.time('telemetry-init');
|
|
23581
23626
|
this.telemetryInitPromise = initTelemetry(this.mcpLogger).then(({
|
|
23582
23627
|
client,
|
|
23583
|
-
info
|
|
23628
|
+
info,
|
|
23629
|
+
disabledReason
|
|
23584
23630
|
}) => {
|
|
23585
23631
|
this.telemetry = client;
|
|
23586
23632
|
this.telemetryInfo = info;
|
|
23633
|
+
this.telemetryDisabledReason = disabledReason;
|
|
23587
23634
|
}).catch(() => {
|
|
23588
23635
|
/* telemetry is best-effort — it must never take the server down */
|
|
23589
23636
|
});
|
|
@@ -23635,7 +23682,16 @@ class CorvaUiMcpServer {
|
|
|
23635
23682
|
enabled: this.telemetry.isEnabled(),
|
|
23636
23683
|
sessionId: this.telemetry.isEnabled() ? this.telemetry.sessionId : undefined,
|
|
23637
23684
|
configSource: this.telemetryInfo?.configSource,
|
|
23638
|
-
samplingRate: this.telemetryInfo?.samplingRate
|
|
23685
|
+
samplingRate: this.telemetryInfo?.samplingRate,
|
|
23686
|
+
feedbackDelivery: this.getFeedbackDelivery()
|
|
23687
|
+
};
|
|
23688
|
+
}
|
|
23689
|
+
getFeedbackDelivery() {
|
|
23690
|
+
return this.telemetry.isEnabled() ? {
|
|
23691
|
+
state: 'queued'
|
|
23692
|
+
} : {
|
|
23693
|
+
state: 'not-sent',
|
|
23694
|
+
cause: this.telemetryDisabledReason ?? 'no-config'
|
|
23639
23695
|
};
|
|
23640
23696
|
}
|
|
23641
23697
|
getStats() {
|