@copilotkit/web-inspector 1.64.2-canary.perfall1 → 1.64.2
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/dist/index.cjs +81 -24
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +17 -2
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +17 -2
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +82 -25
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +137 -56
- package/dist/index.umd.js.map +1 -1
- package/dist/lib/telemetry.cjs +28 -5
- package/dist/lib/telemetry.cjs.map +1 -1
- package/dist/lib/telemetry.mjs +27 -6
- package/dist/lib/telemetry.mjs.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/package.json +2 -2
- package/src/__tests__/telemetry-egress-guard.spec.ts +41 -0
- package/src/__tests__/web-inspector.spec.ts +343 -3
- package/src/index.ts +137 -35
- package/src/lib/__tests__/telemetry.test.ts +97 -3
- package/src/lib/telemetry.ts +77 -4
- package/src/lib/testing/telemetry-egress-guard.ts +33 -0
- package/vitest.setup.ts +8 -0
package/dist/lib/telemetry.cjs
CHANGED
|
@@ -3,8 +3,10 @@ const require_package = require('../package.cjs');
|
|
|
3
3
|
|
|
4
4
|
//#region src/lib/telemetry.ts
|
|
5
5
|
const TELEMETRY_EVENTS = {
|
|
6
|
+
opened: "oss.inspector.opened",
|
|
6
7
|
bannerViewed: "oss.inspector.banner_viewed",
|
|
7
8
|
bannerClicked: "oss.inspector.banner_clicked",
|
|
9
|
+
bannerDismissed: "oss.inspector.banner_dismissed",
|
|
8
10
|
threadsTabClicked: "oss.inspector.threads_tab_clicked",
|
|
9
11
|
threadsLockedViewed: "oss.inspector.threads_locked_viewed",
|
|
10
12
|
threadsIntelligenceSignupClicked: "oss.inspector.threads_intelligence_signup_clicked",
|
|
@@ -26,8 +28,8 @@ const TELEMETRY_DOCS_URL = "https://docs.copilotkit.ai/telemetry";
|
|
|
26
28
|
const PACKAGE_NAME = "@copilotkit/web-inspector";
|
|
27
29
|
const PACKAGE_VERSION = require_package.version;
|
|
28
30
|
const FETCH_TIMEOUT_MS = 3e3;
|
|
29
|
-
function
|
|
30
|
-
return event === TELEMETRY_EVENTS.threadsTabClicked || event === TELEMETRY_EVENTS.threadsLockedViewed || event === TELEMETRY_EVENTS.threadsIntelligenceSignupClicked || event === TELEMETRY_EVENTS.threadsTalkToEngineerClicked || event === TELEMETRY_EVENTS.talkToEngineerClicked || event === TELEMETRY_EVENTS.threadsEmptyEnabledViewed || event === TELEMETRY_EVENTS.threadsEnabledViewed || event === TELEMETRY_EVENTS.threadsExampleViewed || event === TELEMETRY_EVENTS.threadsExampleSelected || event === TELEMETRY_EVENTS.threadsExampleTourStarted || event === TELEMETRY_EVENTS.threadsExampleTourStepViewed || event === TELEMETRY_EVENTS.threadsExampleTourDismissed || event === TELEMETRY_EVENTS.threadsExampleTourCompleted || event === TELEMETRY_EVENTS.threadsExampleTourReopened || event === TELEMETRY_EVENTS.memoriesTabClicked;
|
|
31
|
+
function isEnrichedTelemetryEvent(event) {
|
|
32
|
+
return event === TELEMETRY_EVENTS.opened || event === TELEMETRY_EVENTS.threadsTabClicked || event === TELEMETRY_EVENTS.threadsLockedViewed || event === TELEMETRY_EVENTS.threadsIntelligenceSignupClicked || event === TELEMETRY_EVENTS.threadsTalkToEngineerClicked || event === TELEMETRY_EVENTS.talkToEngineerClicked || event === TELEMETRY_EVENTS.threadsEmptyEnabledViewed || event === TELEMETRY_EVENTS.threadsEnabledViewed || event === TELEMETRY_EVENTS.threadsExampleViewed || event === TELEMETRY_EVENTS.threadsExampleSelected || event === TELEMETRY_EVENTS.threadsExampleTourStarted || event === TELEMETRY_EVENTS.threadsExampleTourStepViewed || event === TELEMETRY_EVENTS.threadsExampleTourDismissed || event === TELEMETRY_EVENTS.threadsExampleTourCompleted || event === TELEMETRY_EVENTS.threadsExampleTourReopened || event === TELEMETRY_EVENTS.memoriesTabClicked;
|
|
31
33
|
}
|
|
32
34
|
function getRuntimeUrlType(runtimeUrl) {
|
|
33
35
|
if (!runtimeUrl) return "missing";
|
|
@@ -54,7 +56,7 @@ function getRuntimeUrlType(runtimeUrl) {
|
|
|
54
56
|
function track(event, properties = {}) {
|
|
55
57
|
if (require_persistence.isTelemetryOptedOut()) return;
|
|
56
58
|
const distinctId = require_persistence.getOrCreateTelemetryDistinctId();
|
|
57
|
-
const
|
|
59
|
+
const enrichedProperties = isEnrichedTelemetryEvent(event) ? {
|
|
58
60
|
package_name: PACKAGE_NAME,
|
|
59
61
|
package_version: PACKAGE_VERSION,
|
|
60
62
|
inspector_distinct_id: distinctId
|
|
@@ -65,12 +67,12 @@ function track(event, properties = {}) {
|
|
|
65
67
|
event,
|
|
66
68
|
properties: {
|
|
67
69
|
...properties,
|
|
68
|
-
...
|
|
70
|
+
...enrichedProperties,
|
|
69
71
|
distinct_id: distinctId
|
|
70
72
|
},
|
|
71
73
|
package: {
|
|
72
74
|
name: PACKAGE_NAME,
|
|
73
|
-
...
|
|
75
|
+
...isEnrichedTelemetryEvent(event) ? { version: PACKAGE_VERSION } : {}
|
|
74
76
|
},
|
|
75
77
|
ts: Math.floor(Date.now() / 1e3)
|
|
76
78
|
});
|
|
@@ -85,6 +87,25 @@ function trackBannerViewed(props) {
|
|
|
85
87
|
function trackBannerClicked(props) {
|
|
86
88
|
track(TELEMETRY_EVENTS.bannerClicked, props);
|
|
87
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* First-class dismissal signal. Emitted *in addition to*
|
|
92
|
+
* `banner_clicked { cta: "dismiss" }` rather than replacing it — the
|
|
93
|
+
* existing dashboards read the `cta` value, so removing it would silently
|
|
94
|
+
* zero them out. New reporting should prefer this event, whose `surface`
|
|
95
|
+
* says whether the user swatted the bubble away or dismissed the card
|
|
96
|
+
* after opening the panel.
|
|
97
|
+
*/
|
|
98
|
+
function trackBannerDismissed(props) {
|
|
99
|
+
track(TELEMETRY_EVENTS.bannerDismissed, props);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Panel-open signal (OSS-566). Before this event, opens could only be
|
|
103
|
+
* inferred from in-panel activity (a floor) or from `banner_clicked`
|
|
104
|
+
* cta=`body` (which misses the floating-button path entirely).
|
|
105
|
+
*/
|
|
106
|
+
function trackInspectorOpened(props) {
|
|
107
|
+
track(TELEMETRY_EVENTS.opened, props);
|
|
108
|
+
}
|
|
88
109
|
function trackThreadsTabClicked(props = {}) {
|
|
89
110
|
track(TELEMETRY_EVENTS.threadsTabClicked, props);
|
|
90
111
|
}
|
|
@@ -199,7 +220,9 @@ exports.getRuntimeUrlType = getRuntimeUrlType;
|
|
|
199
220
|
exports.getTelemetryDistinctIdForUrl = getTelemetryDistinctIdForUrl;
|
|
200
221
|
exports.maybeShowDisclosure = maybeShowDisclosure;
|
|
201
222
|
exports.trackBannerClicked = trackBannerClicked;
|
|
223
|
+
exports.trackBannerDismissed = trackBannerDismissed;
|
|
202
224
|
exports.trackBannerViewed = trackBannerViewed;
|
|
225
|
+
exports.trackInspectorOpened = trackInspectorOpened;
|
|
203
226
|
exports.trackMemoriesTabClicked = trackMemoriesTabClicked;
|
|
204
227
|
exports.trackTalkToEngineerClicked = trackTalkToEngineerClicked;
|
|
205
228
|
exports.trackThreadsEmptyEnabledViewed = trackThreadsEmptyEnabledViewed;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.cjs","names":["isTelemetryOptedOut","getOrCreateTelemetryDistinctId","hasTelemetryDisclosureBeenShown"],"sources":["../../src/lib/telemetry.ts"],"sourcesContent":["// Inspector-side anonymous telemetry. V1 events fire from index.ts for\n// banner and thread-inspection interactions. POSTs directly from the browser\n// to the CopilotKit telemetry sink at `telemetry.copilotkit.ai/ingest`,\n// where a Lambda fan-out forwards events to PostHog / Reo / Scarf.\n//\n// The endpoint URL is intentionally clearly named so it's obvious in\n// DevTools / Network tab — transparency for opt-in users.\n//\n// Privacy invariants enforced here:\n// - We never send message content, agent state, prompts, completions,\n// or banner markdown. Properties are scoped to event metadata only\n// (banner_id/timestamp, cta location). Reviewers should grep call\n// sites for any unintended payload.\n// - The opt-out short-circuits before any network call. There is no\n// buffer, no retry queue.\n// - All errors are swallowed; telemetry must never break the host app.\n\nimport {\n getOrCreateTelemetryDistinctId,\n hasTelemetryDisclosureBeenShown,\n isTelemetryOptedOut,\n markTelemetryDisclosureShown,\n} from \"./persistence.js\";\nimport packageJson from \"../../package.json\" with { type: \"json\" };\n\n// V1 funnel events. Namespaced `oss.inspector.*` so the lambda's\n// owned-prefix gate (oss-path-to-production) can accept them server-side\n// without a per-event sink deploy.\nexport const TELEMETRY_EVENTS = {\n bannerViewed: \"oss.inspector.banner_viewed\",\n bannerClicked: \"oss.inspector.banner_clicked\",\n threadsTabClicked: \"oss.inspector.threads_tab_clicked\",\n threadsLockedViewed: \"oss.inspector.threads_locked_viewed\",\n threadsIntelligenceSignupClicked:\n \"oss.inspector.threads_intelligence_signup_clicked\",\n threadsTalkToEngineerClicked:\n \"oss.inspector.threads_talk_to_engineer_clicked\",\n talkToEngineerClicked: \"oss.inspector.talk_to_engineer_clicked\",\n threadsEmptyEnabledViewed: \"oss.inspector.threads_empty_enabled_viewed\",\n threadsEnabledViewed: \"oss.inspector.threads_enabled_viewed\",\n threadsExampleViewed: \"oss.inspector.threads_example_viewed\",\n threadsExampleSelected: \"oss.inspector.threads_example_selected\",\n threadsExampleTourStarted: \"oss.inspector.threads_example_tour_started\",\n threadsExampleTourStepViewed:\n \"oss.inspector.threads_example_tour_step_viewed\",\n threadsExampleTourDismissed: \"oss.inspector.threads_example_tour_dismissed\",\n threadsExampleTourCompleted: \"oss.inspector.threads_example_tour_completed\",\n threadsExampleTourReopened: \"oss.inspector.threads_example_tour_reopened\",\n memoriesTabClicked: \"oss.inspector.memories_tab_clicked\",\n} as const;\n\nexport type TelemetryEvent =\n (typeof TELEMETRY_EVENTS)[keyof typeof TELEMETRY_EVENTS];\n\n// Per the OSS-96 ticket — the URL is intentionally clearly named for\n// transparency in the network tab.\nexport const TELEMETRY_INGEST_URL = \"https://telemetry.copilotkit.ai/ingest\";\n\n// Surfaced in console disclosure and the in-product opt-out panel.\n// Keep in sync with the canonical telemetry docs page on main\n// (`docs/content/docs/(root)/(other)/telemetry/index.mdx`).\n// Mirror constant: packages/runtime/src/lib/telemetry-disclosure.ts\nexport const TELEMETRY_DOCS_URL = \"https://docs.copilotkit.ai/telemetry\";\n\nconst PACKAGE_NAME = \"@copilotkit/web-inspector\";\nconst PACKAGE_VERSION = packageJson.version;\n\n// 3-second cap so a slow gateway can't hang the host app. Matches the\n// runtime's existing scarf-client convention.\nconst FETCH_TIMEOUT_MS = 3000;\n\nfunction isThreadsTelemetryEvent(event: TelemetryEvent): boolean {\n return (\n event === TELEMETRY_EVENTS.threadsTabClicked ||\n event === TELEMETRY_EVENTS.threadsLockedViewed ||\n event === TELEMETRY_EVENTS.threadsIntelligenceSignupClicked ||\n event === TELEMETRY_EVENTS.threadsTalkToEngineerClicked ||\n event === TELEMETRY_EVENTS.talkToEngineerClicked ||\n event === TELEMETRY_EVENTS.threadsEmptyEnabledViewed ||\n event === TELEMETRY_EVENTS.threadsEnabledViewed ||\n event === TELEMETRY_EVENTS.threadsExampleViewed ||\n event === TELEMETRY_EVENTS.threadsExampleSelected ||\n event === TELEMETRY_EVENTS.threadsExampleTourStarted ||\n event === TELEMETRY_EVENTS.threadsExampleTourStepViewed ||\n event === TELEMETRY_EVENTS.threadsExampleTourDismissed ||\n event === TELEMETRY_EVENTS.threadsExampleTourCompleted ||\n event === TELEMETRY_EVENTS.threadsExampleTourReopened ||\n event === TELEMETRY_EVENTS.memoriesTabClicked\n );\n}\n\nexport type RuntimeUrlType =\n | \"missing\"\n | \"relative\"\n | \"localhost\"\n | \"same_origin\"\n | \"remote\"\n | \"invalid\";\n\nexport function getRuntimeUrlType(\n runtimeUrl: string | undefined,\n): RuntimeUrlType {\n if (!runtimeUrl) return \"missing\";\n if (runtimeUrl.startsWith(\"/\") && !runtimeUrl.startsWith(\"//\")) {\n return \"relative\";\n }\n\n try {\n const baseHref =\n typeof window !== \"undefined\"\n ? window.location.href\n : \"https://copilotkit.ai\";\n const url = new URL(runtimeUrl, baseHref);\n const baseUrl = new URL(baseHref);\n const hostname = url.hostname.toLowerCase();\n\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"[::1]\"\n ) {\n return \"localhost\";\n }\n\n return url.origin === baseUrl.origin ? \"same_origin\" : \"remote\";\n } catch {\n return \"invalid\";\n }\n}\n\n/**\n * Fire-and-forget telemetry send. Returns synchronously; the network\n * call is dispatched in the background and any failure is swallowed.\n *\n * Short-circuits when the user has opted out. Does NOT itself trigger\n * the first-run disclosure — call `maybeShowDisclosure()` from the\n * inspector's mount lifecycle instead.\n */\nexport function track(\n event: TelemetryEvent,\n properties: Record<string, unknown> = {},\n): void {\n if (isTelemetryOptedOut()) return;\n\n const distinctId = getOrCreateTelemetryDistinctId();\n const threadsProperties = isThreadsTelemetryEvent(event)\n ? {\n package_name: PACKAGE_NAME,\n package_version: PACKAGE_VERSION,\n inspector_distinct_id: distinctId,\n }\n : {};\n let body: string;\n try {\n body = JSON.stringify({\n event,\n properties: {\n ...properties,\n ...threadsProperties,\n distinct_id: distinctId,\n },\n package: {\n name: PACKAGE_NAME,\n ...(isThreadsTelemetryEvent(event) ? { version: PACKAGE_VERSION } : {}),\n },\n ts: Math.floor(Date.now() / 1000),\n });\n } catch {\n return;\n }\n\n void postBestEffort(TELEMETRY_INGEST_URL, body, distinctId);\n}\n\n// --- Typed per-event helpers ---\n// These enforce the known property shape for each V1 event at the call\n// site, so callers can't accidentally include PII under a wrong key.\n\nexport function trackBannerViewed(props: {\n banner_id: string;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerViewed, props);\n}\n\nexport function trackBannerClicked(props: {\n banner_id: string;\n cta: \"body\" | \"dismiss\";\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerClicked, props);\n}\n\nexport type InspectorThreadTelemetryProps = {\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n posthog_distinct_id?: string;\n intelligence_status?:\n | \"intelligence_not_enabled\"\n | \"intelligence_enabled\"\n | \"unknown\";\n thread_service_status?: \"unavailable\" | \"available\" | \"unknown\" | \"error\";\n license_status?:\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n runtime_mode?: \"sse\" | \"intelligence\";\n runtime_url_type?: RuntimeUrlType;\n cta_surface?:\n | \"threads_locked\"\n | \"threads_header\"\n | \"threads_empty\"\n | \"threads_populated\";\n cta?: \"signup\" | \"talk_to_engineer\";\n telemetry_disabled?: boolean;\n thread_count?: number;\n example_thread_id?: string;\n tour_step?: number;\n tour_tab?: \"timeline\" | \"raw-events\" | \"state\";\n dismiss_method?: \"skip\" | \"done\";\n};\n\nexport function trackThreadsTabClicked(\n props: InspectorThreadTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.threadsTabClicked, props);\n}\n\nexport function trackThreadsLockedViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsLockedViewed, props);\n}\n\nexport function trackThreadsIntelligenceSignupClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsIntelligenceSignupClicked, props);\n}\n\nexport function trackThreadsTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsTalkToEngineerClicked, props);\n}\n\nexport function trackTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.talkToEngineerClicked, props);\n}\n\nexport function trackThreadsEmptyEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEmptyEnabledViewed, props);\n}\n\nexport function trackThreadsEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEnabledViewed, props);\n}\n\nexport function trackThreadsExampleViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleViewed, props);\n}\n\nexport function trackThreadsExampleSelected(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleSelected, props);\n}\n\nexport function trackThreadsExampleTourStarted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourStarted, props);\n}\n\nexport function trackThreadsExampleTourStepViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourStepViewed, props);\n}\n\nexport function trackThreadsExampleTourDismissed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourDismissed, props);\n}\n\nexport function trackThreadsExampleTourCompleted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourCompleted, props);\n}\n\nexport function trackThreadsExampleTourReopened(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourReopened, props);\n}\n\nexport type InspectorMemoryTelemetryProps = {\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n posthog_distinct_id?: string;\n memory_count?: number;\n available?: boolean;\n};\n\nexport function trackMemoriesTabClicked(\n props: InspectorMemoryTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.memoriesTabClicked, props);\n}\n\n/**\n * Returns the inspector's anonymous distinct-ID for cross-domain\n * propagation onto outbound banner-CTA links, or `null` when the user\n * is opted out.\n *\n * The website / Ops API reads this query param on signup-flow landing\n * pages and calls `posthog.alias(...)` to merge the inspector's anon\n * ID with the website's anon ID, enabling the\n * `banner_viewed → banner_clicked → signup_attributed` funnel.\n * `identify()` itself is out of scope here (it happens on signup, in\n * the website / Ops API).\n *\n * Opt-out short-circuits this too: if the user has opted out, we do\n * NOT leak an anon ID across domains.\n */\nexport function getTelemetryDistinctIdForUrl(): string | null {\n if (isTelemetryOptedOut()) return null;\n return getOrCreateTelemetryDistinctId();\n}\n\n/**\n * Seeds the anonymous distinct-ID into localStorage on inspector mount\n * so it is ready for cross-domain propagation onto banner-CTA links\n * even before the first event fires. No-op when the user has opted out.\n */\nexport function ensureTelemetryDistinctId(): void {\n if (isTelemetryOptedOut()) return;\n getOrCreateTelemetryDistinctId();\n}\n\n/**\n * Fires the one-time console disclosure on inspector mount, when the\n * user is not opted out and hasn't seen it before. Idempotent across\n * calls within a single session because `markTelemetryDisclosureShown`\n * persists to localStorage.\n *\n * If the user is opted out, we deliberately do nothing and do NOT mark\n * the flag — so a future opt-in flips back to \"first run\" behavior.\n */\nexport function maybeShowDisclosure(): void {\n if (isTelemetryOptedOut()) return;\n if (hasTelemetryDisclosureBeenShown()) return;\n // eslint-disable-next-line no-console\n console.info(\n `[CopilotKit Inspector] anonymous interaction telemetry enabled — see ${TELEMETRY_DOCS_URL} to opt out.`,\n );\n markTelemetryDisclosureShown();\n}\n\nexport { isTelemetryOptedOut };\n\nasync function postBestEffort(\n url: string,\n body: string,\n distinctId: string,\n): Promise<void> {\n if (typeof fetch === \"undefined\") return;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-CopilotKit-Telemetry-Id\": distinctId,\n },\n body,\n signal: controller.signal,\n // No credentials / no Authorization header — anonymous endpoint.\n });\n } catch {\n // Silent failure — telemetry must not break the application.\n } finally {\n clearTimeout(timeoutId);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"telemetry.cjs","names":["isTelemetryOptedOut","getOrCreateTelemetryDistinctId","hasTelemetryDisclosureBeenShown"],"sources":["../../src/lib/telemetry.ts"],"sourcesContent":["// Inspector-side anonymous telemetry. V1 events fire from index.ts for\n// banner and thread-inspection interactions. POSTs directly from the browser\n// to the CopilotKit telemetry sink at `telemetry.copilotkit.ai/ingest`,\n// where a Lambda fan-out forwards events to PostHog / Reo / Scarf.\n//\n// The endpoint URL is intentionally clearly named so it's obvious in\n// DevTools / Network tab — transparency for opt-in users.\n//\n// Privacy invariants enforced here:\n// - We never send message content, agent state, prompts, completions,\n// or banner markdown. Properties are scoped to event metadata only\n// (banner_id/timestamp, cta location). Reviewers should grep call\n// sites for any unintended payload.\n// - The opt-out short-circuits before any network call. There is no\n// buffer, no retry queue.\n// - All errors are swallowed; telemetry must never break the host app.\n\nimport {\n getOrCreateTelemetryDistinctId,\n hasTelemetryDisclosureBeenShown,\n isTelemetryOptedOut,\n markTelemetryDisclosureShown,\n} from \"./persistence.js\";\nimport packageJson from \"../../package.json\" with { type: \"json\" };\n\n// V1 funnel events. Namespaced `oss.inspector.*` so the lambda's\n// owned-prefix gate (oss-path-to-production) can accept them server-side\n// without a per-event sink deploy.\nexport const TELEMETRY_EVENTS = {\n opened: \"oss.inspector.opened\",\n bannerViewed: \"oss.inspector.banner_viewed\",\n bannerClicked: \"oss.inspector.banner_clicked\",\n bannerDismissed: \"oss.inspector.banner_dismissed\",\n threadsTabClicked: \"oss.inspector.threads_tab_clicked\",\n threadsLockedViewed: \"oss.inspector.threads_locked_viewed\",\n threadsIntelligenceSignupClicked:\n \"oss.inspector.threads_intelligence_signup_clicked\",\n threadsTalkToEngineerClicked:\n \"oss.inspector.threads_talk_to_engineer_clicked\",\n talkToEngineerClicked: \"oss.inspector.talk_to_engineer_clicked\",\n threadsEmptyEnabledViewed: \"oss.inspector.threads_empty_enabled_viewed\",\n threadsEnabledViewed: \"oss.inspector.threads_enabled_viewed\",\n threadsExampleViewed: \"oss.inspector.threads_example_viewed\",\n threadsExampleSelected: \"oss.inspector.threads_example_selected\",\n threadsExampleTourStarted: \"oss.inspector.threads_example_tour_started\",\n threadsExampleTourStepViewed:\n \"oss.inspector.threads_example_tour_step_viewed\",\n threadsExampleTourDismissed: \"oss.inspector.threads_example_tour_dismissed\",\n threadsExampleTourCompleted: \"oss.inspector.threads_example_tour_completed\",\n threadsExampleTourReopened: \"oss.inspector.threads_example_tour_reopened\",\n memoriesTabClicked: \"oss.inspector.memories_tab_clicked\",\n} as const;\n\nexport type TelemetryEvent =\n (typeof TELEMETRY_EVENTS)[keyof typeof TELEMETRY_EVENTS];\n\n// Per the OSS-96 ticket — the URL is intentionally clearly named for\n// transparency in the network tab.\nexport const TELEMETRY_INGEST_URL = \"https://telemetry.copilotkit.ai/ingest\";\n\n// Surfaced in console disclosure and the in-product opt-out panel.\n// Keep in sync with the canonical telemetry docs page on main\n// (`docs/content/docs/(root)/(other)/telemetry/index.mdx`).\n// Mirror constant: packages/runtime/src/lib/telemetry-disclosure.ts\nexport const TELEMETRY_DOCS_URL = \"https://docs.copilotkit.ai/telemetry\";\n\nconst PACKAGE_NAME = \"@copilotkit/web-inspector\";\nconst PACKAGE_VERSION = packageJson.version;\n\n// 3-second cap so a slow gateway can't hang the host app. Matches the\n// runtime's existing scarf-client convention.\nconst FETCH_TIMEOUT_MS = 3000;\n\n// Events that carry package identity (`package_name` / `package_version`) and\n// the inspector's anonymous distinct-ID alongside their own properties.\n// Threads / memories events have always been enriched this way; `opened`\n// joins them so panel-open volume can be segmented by inspector version.\n// Banner events deliberately keep the flat shape their existing dashboards\n// read.\nfunction isEnrichedTelemetryEvent(event: TelemetryEvent): boolean {\n return (\n event === TELEMETRY_EVENTS.opened ||\n event === TELEMETRY_EVENTS.threadsTabClicked ||\n event === TELEMETRY_EVENTS.threadsLockedViewed ||\n event === TELEMETRY_EVENTS.threadsIntelligenceSignupClicked ||\n event === TELEMETRY_EVENTS.threadsTalkToEngineerClicked ||\n event === TELEMETRY_EVENTS.talkToEngineerClicked ||\n event === TELEMETRY_EVENTS.threadsEmptyEnabledViewed ||\n event === TELEMETRY_EVENTS.threadsEnabledViewed ||\n event === TELEMETRY_EVENTS.threadsExampleViewed ||\n event === TELEMETRY_EVENTS.threadsExampleSelected ||\n event === TELEMETRY_EVENTS.threadsExampleTourStarted ||\n event === TELEMETRY_EVENTS.threadsExampleTourStepViewed ||\n event === TELEMETRY_EVENTS.threadsExampleTourDismissed ||\n event === TELEMETRY_EVENTS.threadsExampleTourCompleted ||\n event === TELEMETRY_EVENTS.threadsExampleTourReopened ||\n event === TELEMETRY_EVENTS.memoriesTabClicked\n );\n}\n\nexport type RuntimeUrlType =\n | \"missing\"\n | \"relative\"\n | \"localhost\"\n | \"same_origin\"\n | \"remote\"\n | \"invalid\";\n\nexport function getRuntimeUrlType(\n runtimeUrl: string | undefined,\n): RuntimeUrlType {\n if (!runtimeUrl) return \"missing\";\n if (runtimeUrl.startsWith(\"/\") && !runtimeUrl.startsWith(\"//\")) {\n return \"relative\";\n }\n\n try {\n const baseHref =\n typeof window !== \"undefined\"\n ? window.location.href\n : \"https://copilotkit.ai\";\n const url = new URL(runtimeUrl, baseHref);\n const baseUrl = new URL(baseHref);\n const hostname = url.hostname.toLowerCase();\n\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"[::1]\"\n ) {\n return \"localhost\";\n }\n\n return url.origin === baseUrl.origin ? \"same_origin\" : \"remote\";\n } catch {\n return \"invalid\";\n }\n}\n\n/**\n * Fire-and-forget telemetry send. Returns synchronously; the network\n * call is dispatched in the background and any failure is swallowed.\n *\n * Short-circuits when the user has opted out. Does NOT itself trigger\n * the first-run disclosure — call `maybeShowDisclosure()` from the\n * inspector's mount lifecycle instead.\n */\nexport function track(\n event: TelemetryEvent,\n properties: Record<string, unknown> = {},\n): void {\n if (isTelemetryOptedOut()) return;\n\n const distinctId = getOrCreateTelemetryDistinctId();\n const enrichedProperties = isEnrichedTelemetryEvent(event)\n ? {\n package_name: PACKAGE_NAME,\n package_version: PACKAGE_VERSION,\n inspector_distinct_id: distinctId,\n }\n : {};\n let body: string;\n try {\n body = JSON.stringify({\n event,\n properties: {\n ...properties,\n ...enrichedProperties,\n distinct_id: distinctId,\n },\n package: {\n name: PACKAGE_NAME,\n ...(isEnrichedTelemetryEvent(event)\n ? { version: PACKAGE_VERSION }\n : {}),\n },\n ts: Math.floor(Date.now() / 1000),\n });\n } catch {\n return;\n }\n\n void postBestEffort(TELEMETRY_INGEST_URL, body, distinctId);\n}\n\n// --- Typed per-event helpers ---\n// These enforce the known property shape for each V1 event at the call\n// site, so callers can't accidentally include PII under a wrong key.\n\n/**\n * Where an announcement was rendered when the event fired. The announcement\n * has two surfaces — the preview bubble beside the *collapsed* floating\n * button, and the card *inside* the opened panel — and reach on one says\n * nothing about attention on the other. Always stamped at fire time, never\n * inferred at fetch time.\n */\nexport type BannerSurface = \"collapsed_preview\" | \"expanded_card\";\n\nexport function trackBannerViewed(props: {\n banner_id: string;\n surface: BannerSurface;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerViewed, props);\n}\n\nexport function trackBannerClicked(props: {\n banner_id: string;\n cta: \"body\" | \"dismiss\";\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerClicked, props);\n}\n\n/**\n * First-class dismissal signal. Emitted *in addition to*\n * `banner_clicked { cta: \"dismiss\" }` rather than replacing it — the\n * existing dashboards read the `cta` value, so removing it would silently\n * zero them out. New reporting should prefer this event, whose `surface`\n * says whether the user swatted the bubble away or dismissed the card\n * after opening the panel.\n */\nexport function trackBannerDismissed(props: {\n banner_id: string;\n surface: BannerSurface;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerDismissed, props);\n}\n\n/**\n * How the panel was opened. Restoring a persisted-open panel on mount is\n * deliberately NOT a source: it is not an open *intent*, and counting it\n * would turn every dev-server hot reload into an \"open\".\n */\nexport type InspectorOpenSource = \"floating_button\" | \"announcement_preview\";\n\nexport type InspectorOpenedTelemetryProps = {\n open_source: InspectorOpenSource;\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n license_status?:\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n runtime_mode?: \"sse\" | \"intelligence\";\n runtime_url_type?: RuntimeUrlType;\n /** True when an unseen announcement was on screen at open time. */\n has_unseen_announcement?: boolean;\n};\n\n/**\n * Panel-open signal (OSS-566). Before this event, opens could only be\n * inferred from in-panel activity (a floor) or from `banner_clicked`\n * cta=`body` (which misses the floating-button path entirely).\n */\nexport function trackInspectorOpened(\n props: InspectorOpenedTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.opened, props);\n}\n\nexport type InspectorThreadTelemetryProps = {\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n posthog_distinct_id?: string;\n intelligence_status?:\n | \"intelligence_not_enabled\"\n | \"intelligence_enabled\"\n | \"unknown\";\n thread_service_status?: \"unavailable\" | \"available\" | \"unknown\" | \"error\";\n license_status?:\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n runtime_mode?: \"sse\" | \"intelligence\";\n runtime_url_type?: RuntimeUrlType;\n cta_surface?:\n | \"threads_locked\"\n | \"threads_header\"\n | \"threads_empty\"\n | \"threads_populated\";\n cta?: \"signup\" | \"talk_to_engineer\";\n telemetry_disabled?: boolean;\n thread_count?: number;\n example_thread_id?: string;\n tour_step?: number;\n tour_tab?: \"timeline\" | \"raw-events\" | \"state\";\n dismiss_method?: \"skip\" | \"done\";\n};\n\nexport function trackThreadsTabClicked(\n props: InspectorThreadTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.threadsTabClicked, props);\n}\n\nexport function trackThreadsLockedViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsLockedViewed, props);\n}\n\nexport function trackThreadsIntelligenceSignupClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsIntelligenceSignupClicked, props);\n}\n\nexport function trackThreadsTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsTalkToEngineerClicked, props);\n}\n\nexport function trackTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.talkToEngineerClicked, props);\n}\n\nexport function trackThreadsEmptyEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEmptyEnabledViewed, props);\n}\n\nexport function trackThreadsEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEnabledViewed, props);\n}\n\nexport function trackThreadsExampleViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleViewed, props);\n}\n\nexport function trackThreadsExampleSelected(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleSelected, props);\n}\n\nexport function trackThreadsExampleTourStarted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourStarted, props);\n}\n\nexport function trackThreadsExampleTourStepViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourStepViewed, props);\n}\n\nexport function trackThreadsExampleTourDismissed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourDismissed, props);\n}\n\nexport function trackThreadsExampleTourCompleted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourCompleted, props);\n}\n\nexport function trackThreadsExampleTourReopened(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourReopened, props);\n}\n\nexport type InspectorMemoryTelemetryProps = {\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n posthog_distinct_id?: string;\n memory_count?: number;\n available?: boolean;\n};\n\nexport function trackMemoriesTabClicked(\n props: InspectorMemoryTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.memoriesTabClicked, props);\n}\n\n/**\n * Returns the inspector's anonymous distinct-ID for cross-domain\n * propagation onto outbound banner-CTA links, or `null` when the user\n * is opted out.\n *\n * The website / Ops API reads this query param on signup-flow landing\n * pages and calls `posthog.alias(...)` to merge the inspector's anon\n * ID with the website's anon ID, enabling the\n * `banner_viewed → banner_clicked → signup_attributed` funnel.\n * `identify()` itself is out of scope here (it happens on signup, in\n * the website / Ops API).\n *\n * Opt-out short-circuits this too: if the user has opted out, we do\n * NOT leak an anon ID across domains.\n */\nexport function getTelemetryDistinctIdForUrl(): string | null {\n if (isTelemetryOptedOut()) return null;\n return getOrCreateTelemetryDistinctId();\n}\n\n/**\n * Seeds the anonymous distinct-ID into localStorage on inspector mount\n * so it is ready for cross-domain propagation onto banner-CTA links\n * even before the first event fires. No-op when the user has opted out.\n */\nexport function ensureTelemetryDistinctId(): void {\n if (isTelemetryOptedOut()) return;\n getOrCreateTelemetryDistinctId();\n}\n\n/**\n * Fires the one-time console disclosure on inspector mount, when the\n * user is not opted out and hasn't seen it before. Idempotent across\n * calls within a single session because `markTelemetryDisclosureShown`\n * persists to localStorage.\n *\n * If the user is opted out, we deliberately do nothing and do NOT mark\n * the flag — so a future opt-in flips back to \"first run\" behavior.\n */\nexport function maybeShowDisclosure(): void {\n if (isTelemetryOptedOut()) return;\n if (hasTelemetryDisclosureBeenShown()) return;\n // eslint-disable-next-line no-console\n console.info(\n `[CopilotKit Inspector] anonymous interaction telemetry enabled — see ${TELEMETRY_DOCS_URL} to opt out.`,\n );\n markTelemetryDisclosureShown();\n}\n\nexport { isTelemetryOptedOut };\n\nasync function postBestEffort(\n url: string,\n body: string,\n distinctId: string,\n): Promise<void> {\n if (typeof fetch === \"undefined\") return;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-CopilotKit-Telemetry-Id\": distinctId,\n },\n body,\n signal: controller.signal,\n // No credentials / no Authorization header — anonymous endpoint.\n });\n } catch {\n // Silent failure — telemetry must not break the application.\n } finally {\n clearTimeout(timeoutId);\n }\n}\n"],"mappings":"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"}
|
package/dist/lib/telemetry.mjs
CHANGED
|
@@ -3,8 +3,10 @@ import { version } from "../package.mjs";
|
|
|
3
3
|
|
|
4
4
|
//#region src/lib/telemetry.ts
|
|
5
5
|
const TELEMETRY_EVENTS = {
|
|
6
|
+
opened: "oss.inspector.opened",
|
|
6
7
|
bannerViewed: "oss.inspector.banner_viewed",
|
|
7
8
|
bannerClicked: "oss.inspector.banner_clicked",
|
|
9
|
+
bannerDismissed: "oss.inspector.banner_dismissed",
|
|
8
10
|
threadsTabClicked: "oss.inspector.threads_tab_clicked",
|
|
9
11
|
threadsLockedViewed: "oss.inspector.threads_locked_viewed",
|
|
10
12
|
threadsIntelligenceSignupClicked: "oss.inspector.threads_intelligence_signup_clicked",
|
|
@@ -26,8 +28,8 @@ const TELEMETRY_DOCS_URL = "https://docs.copilotkit.ai/telemetry";
|
|
|
26
28
|
const PACKAGE_NAME = "@copilotkit/web-inspector";
|
|
27
29
|
const PACKAGE_VERSION = version;
|
|
28
30
|
const FETCH_TIMEOUT_MS = 3e3;
|
|
29
|
-
function
|
|
30
|
-
return event === TELEMETRY_EVENTS.threadsTabClicked || event === TELEMETRY_EVENTS.threadsLockedViewed || event === TELEMETRY_EVENTS.threadsIntelligenceSignupClicked || event === TELEMETRY_EVENTS.threadsTalkToEngineerClicked || event === TELEMETRY_EVENTS.talkToEngineerClicked || event === TELEMETRY_EVENTS.threadsEmptyEnabledViewed || event === TELEMETRY_EVENTS.threadsEnabledViewed || event === TELEMETRY_EVENTS.threadsExampleViewed || event === TELEMETRY_EVENTS.threadsExampleSelected || event === TELEMETRY_EVENTS.threadsExampleTourStarted || event === TELEMETRY_EVENTS.threadsExampleTourStepViewed || event === TELEMETRY_EVENTS.threadsExampleTourDismissed || event === TELEMETRY_EVENTS.threadsExampleTourCompleted || event === TELEMETRY_EVENTS.threadsExampleTourReopened || event === TELEMETRY_EVENTS.memoriesTabClicked;
|
|
31
|
+
function isEnrichedTelemetryEvent(event) {
|
|
32
|
+
return event === TELEMETRY_EVENTS.opened || event === TELEMETRY_EVENTS.threadsTabClicked || event === TELEMETRY_EVENTS.threadsLockedViewed || event === TELEMETRY_EVENTS.threadsIntelligenceSignupClicked || event === TELEMETRY_EVENTS.threadsTalkToEngineerClicked || event === TELEMETRY_EVENTS.talkToEngineerClicked || event === TELEMETRY_EVENTS.threadsEmptyEnabledViewed || event === TELEMETRY_EVENTS.threadsEnabledViewed || event === TELEMETRY_EVENTS.threadsExampleViewed || event === TELEMETRY_EVENTS.threadsExampleSelected || event === TELEMETRY_EVENTS.threadsExampleTourStarted || event === TELEMETRY_EVENTS.threadsExampleTourStepViewed || event === TELEMETRY_EVENTS.threadsExampleTourDismissed || event === TELEMETRY_EVENTS.threadsExampleTourCompleted || event === TELEMETRY_EVENTS.threadsExampleTourReopened || event === TELEMETRY_EVENTS.memoriesTabClicked;
|
|
31
33
|
}
|
|
32
34
|
function getRuntimeUrlType(runtimeUrl) {
|
|
33
35
|
if (!runtimeUrl) return "missing";
|
|
@@ -54,7 +56,7 @@ function getRuntimeUrlType(runtimeUrl) {
|
|
|
54
56
|
function track(event, properties = {}) {
|
|
55
57
|
if (isTelemetryOptedOut()) return;
|
|
56
58
|
const distinctId = getOrCreateTelemetryDistinctId();
|
|
57
|
-
const
|
|
59
|
+
const enrichedProperties = isEnrichedTelemetryEvent(event) ? {
|
|
58
60
|
package_name: PACKAGE_NAME,
|
|
59
61
|
package_version: PACKAGE_VERSION,
|
|
60
62
|
inspector_distinct_id: distinctId
|
|
@@ -65,12 +67,12 @@ function track(event, properties = {}) {
|
|
|
65
67
|
event,
|
|
66
68
|
properties: {
|
|
67
69
|
...properties,
|
|
68
|
-
...
|
|
70
|
+
...enrichedProperties,
|
|
69
71
|
distinct_id: distinctId
|
|
70
72
|
},
|
|
71
73
|
package: {
|
|
72
74
|
name: PACKAGE_NAME,
|
|
73
|
-
...
|
|
75
|
+
...isEnrichedTelemetryEvent(event) ? { version: PACKAGE_VERSION } : {}
|
|
74
76
|
},
|
|
75
77
|
ts: Math.floor(Date.now() / 1e3)
|
|
76
78
|
});
|
|
@@ -85,6 +87,25 @@ function trackBannerViewed(props) {
|
|
|
85
87
|
function trackBannerClicked(props) {
|
|
86
88
|
track(TELEMETRY_EVENTS.bannerClicked, props);
|
|
87
89
|
}
|
|
90
|
+
/**
|
|
91
|
+
* First-class dismissal signal. Emitted *in addition to*
|
|
92
|
+
* `banner_clicked { cta: "dismiss" }` rather than replacing it — the
|
|
93
|
+
* existing dashboards read the `cta` value, so removing it would silently
|
|
94
|
+
* zero them out. New reporting should prefer this event, whose `surface`
|
|
95
|
+
* says whether the user swatted the bubble away or dismissed the card
|
|
96
|
+
* after opening the panel.
|
|
97
|
+
*/
|
|
98
|
+
function trackBannerDismissed(props) {
|
|
99
|
+
track(TELEMETRY_EVENTS.bannerDismissed, props);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Panel-open signal (OSS-566). Before this event, opens could only be
|
|
103
|
+
* inferred from in-panel activity (a floor) or from `banner_clicked`
|
|
104
|
+
* cta=`body` (which misses the floating-button path entirely).
|
|
105
|
+
*/
|
|
106
|
+
function trackInspectorOpened(props) {
|
|
107
|
+
track(TELEMETRY_EVENTS.opened, props);
|
|
108
|
+
}
|
|
88
109
|
function trackThreadsTabClicked(props = {}) {
|
|
89
110
|
track(TELEMETRY_EVENTS.threadsTabClicked, props);
|
|
90
111
|
}
|
|
@@ -193,5 +214,5 @@ async function postBestEffort(url, body, distinctId) {
|
|
|
193
214
|
}
|
|
194
215
|
|
|
195
216
|
//#endregion
|
|
196
|
-
export { TELEMETRY_DOCS_URL, ensureTelemetryDistinctId, getRuntimeUrlType, getTelemetryDistinctIdForUrl, maybeShowDisclosure, trackBannerClicked, trackBannerViewed, trackMemoriesTabClicked, trackTalkToEngineerClicked, trackThreadsEmptyEnabledViewed, trackThreadsEnabledViewed, trackThreadsExampleSelected, trackThreadsExampleTourCompleted, trackThreadsExampleTourDismissed, trackThreadsExampleTourReopened, trackThreadsExampleTourStarted, trackThreadsExampleTourStepViewed, trackThreadsExampleViewed, trackThreadsIntelligenceSignupClicked, trackThreadsLockedViewed, trackThreadsTabClicked, trackThreadsTalkToEngineerClicked };
|
|
217
|
+
export { TELEMETRY_DOCS_URL, ensureTelemetryDistinctId, getRuntimeUrlType, getTelemetryDistinctIdForUrl, maybeShowDisclosure, trackBannerClicked, trackBannerDismissed, trackBannerViewed, trackInspectorOpened, trackMemoriesTabClicked, trackTalkToEngineerClicked, trackThreadsEmptyEnabledViewed, trackThreadsEnabledViewed, trackThreadsExampleSelected, trackThreadsExampleTourCompleted, trackThreadsExampleTourDismissed, trackThreadsExampleTourReopened, trackThreadsExampleTourStarted, trackThreadsExampleTourStepViewed, trackThreadsExampleViewed, trackThreadsIntelligenceSignupClicked, trackThreadsLockedViewed, trackThreadsTabClicked, trackThreadsTalkToEngineerClicked };
|
|
197
218
|
//# sourceMappingURL=telemetry.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.mjs","names":["packageJson.version"],"sources":["../../src/lib/telemetry.ts"],"sourcesContent":["// Inspector-side anonymous telemetry. V1 events fire from index.ts for\n// banner and thread-inspection interactions. POSTs directly from the browser\n// to the CopilotKit telemetry sink at `telemetry.copilotkit.ai/ingest`,\n// where a Lambda fan-out forwards events to PostHog / Reo / Scarf.\n//\n// The endpoint URL is intentionally clearly named so it's obvious in\n// DevTools / Network tab — transparency for opt-in users.\n//\n// Privacy invariants enforced here:\n// - We never send message content, agent state, prompts, completions,\n// or banner markdown. Properties are scoped to event metadata only\n// (banner_id/timestamp, cta location). Reviewers should grep call\n// sites for any unintended payload.\n// - The opt-out short-circuits before any network call. There is no\n// buffer, no retry queue.\n// - All errors are swallowed; telemetry must never break the host app.\n\nimport {\n getOrCreateTelemetryDistinctId,\n hasTelemetryDisclosureBeenShown,\n isTelemetryOptedOut,\n markTelemetryDisclosureShown,\n} from \"./persistence.js\";\nimport packageJson from \"../../package.json\" with { type: \"json\" };\n\n// V1 funnel events. Namespaced `oss.inspector.*` so the lambda's\n// owned-prefix gate (oss-path-to-production) can accept them server-side\n// without a per-event sink deploy.\nexport const TELEMETRY_EVENTS = {\n bannerViewed: \"oss.inspector.banner_viewed\",\n bannerClicked: \"oss.inspector.banner_clicked\",\n threadsTabClicked: \"oss.inspector.threads_tab_clicked\",\n threadsLockedViewed: \"oss.inspector.threads_locked_viewed\",\n threadsIntelligenceSignupClicked:\n \"oss.inspector.threads_intelligence_signup_clicked\",\n threadsTalkToEngineerClicked:\n \"oss.inspector.threads_talk_to_engineer_clicked\",\n talkToEngineerClicked: \"oss.inspector.talk_to_engineer_clicked\",\n threadsEmptyEnabledViewed: \"oss.inspector.threads_empty_enabled_viewed\",\n threadsEnabledViewed: \"oss.inspector.threads_enabled_viewed\",\n threadsExampleViewed: \"oss.inspector.threads_example_viewed\",\n threadsExampleSelected: \"oss.inspector.threads_example_selected\",\n threadsExampleTourStarted: \"oss.inspector.threads_example_tour_started\",\n threadsExampleTourStepViewed:\n \"oss.inspector.threads_example_tour_step_viewed\",\n threadsExampleTourDismissed: \"oss.inspector.threads_example_tour_dismissed\",\n threadsExampleTourCompleted: \"oss.inspector.threads_example_tour_completed\",\n threadsExampleTourReopened: \"oss.inspector.threads_example_tour_reopened\",\n memoriesTabClicked: \"oss.inspector.memories_tab_clicked\",\n} as const;\n\nexport type TelemetryEvent =\n (typeof TELEMETRY_EVENTS)[keyof typeof TELEMETRY_EVENTS];\n\n// Per the OSS-96 ticket — the URL is intentionally clearly named for\n// transparency in the network tab.\nexport const TELEMETRY_INGEST_URL = \"https://telemetry.copilotkit.ai/ingest\";\n\n// Surfaced in console disclosure and the in-product opt-out panel.\n// Keep in sync with the canonical telemetry docs page on main\n// (`docs/content/docs/(root)/(other)/telemetry/index.mdx`).\n// Mirror constant: packages/runtime/src/lib/telemetry-disclosure.ts\nexport const TELEMETRY_DOCS_URL = \"https://docs.copilotkit.ai/telemetry\";\n\nconst PACKAGE_NAME = \"@copilotkit/web-inspector\";\nconst PACKAGE_VERSION = packageJson.version;\n\n// 3-second cap so a slow gateway can't hang the host app. Matches the\n// runtime's existing scarf-client convention.\nconst FETCH_TIMEOUT_MS = 3000;\n\nfunction isThreadsTelemetryEvent(event: TelemetryEvent): boolean {\n return (\n event === TELEMETRY_EVENTS.threadsTabClicked ||\n event === TELEMETRY_EVENTS.threadsLockedViewed ||\n event === TELEMETRY_EVENTS.threadsIntelligenceSignupClicked ||\n event === TELEMETRY_EVENTS.threadsTalkToEngineerClicked ||\n event === TELEMETRY_EVENTS.talkToEngineerClicked ||\n event === TELEMETRY_EVENTS.threadsEmptyEnabledViewed ||\n event === TELEMETRY_EVENTS.threadsEnabledViewed ||\n event === TELEMETRY_EVENTS.threadsExampleViewed ||\n event === TELEMETRY_EVENTS.threadsExampleSelected ||\n event === TELEMETRY_EVENTS.threadsExampleTourStarted ||\n event === TELEMETRY_EVENTS.threadsExampleTourStepViewed ||\n event === TELEMETRY_EVENTS.threadsExampleTourDismissed ||\n event === TELEMETRY_EVENTS.threadsExampleTourCompleted ||\n event === TELEMETRY_EVENTS.threadsExampleTourReopened ||\n event === TELEMETRY_EVENTS.memoriesTabClicked\n );\n}\n\nexport type RuntimeUrlType =\n | \"missing\"\n | \"relative\"\n | \"localhost\"\n | \"same_origin\"\n | \"remote\"\n | \"invalid\";\n\nexport function getRuntimeUrlType(\n runtimeUrl: string | undefined,\n): RuntimeUrlType {\n if (!runtimeUrl) return \"missing\";\n if (runtimeUrl.startsWith(\"/\") && !runtimeUrl.startsWith(\"//\")) {\n return \"relative\";\n }\n\n try {\n const baseHref =\n typeof window !== \"undefined\"\n ? window.location.href\n : \"https://copilotkit.ai\";\n const url = new URL(runtimeUrl, baseHref);\n const baseUrl = new URL(baseHref);\n const hostname = url.hostname.toLowerCase();\n\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"[::1]\"\n ) {\n return \"localhost\";\n }\n\n return url.origin === baseUrl.origin ? \"same_origin\" : \"remote\";\n } catch {\n return \"invalid\";\n }\n}\n\n/**\n * Fire-and-forget telemetry send. Returns synchronously; the network\n * call is dispatched in the background and any failure is swallowed.\n *\n * Short-circuits when the user has opted out. Does NOT itself trigger\n * the first-run disclosure — call `maybeShowDisclosure()` from the\n * inspector's mount lifecycle instead.\n */\nexport function track(\n event: TelemetryEvent,\n properties: Record<string, unknown> = {},\n): void {\n if (isTelemetryOptedOut()) return;\n\n const distinctId = getOrCreateTelemetryDistinctId();\n const threadsProperties = isThreadsTelemetryEvent(event)\n ? {\n package_name: PACKAGE_NAME,\n package_version: PACKAGE_VERSION,\n inspector_distinct_id: distinctId,\n }\n : {};\n let body: string;\n try {\n body = JSON.stringify({\n event,\n properties: {\n ...properties,\n ...threadsProperties,\n distinct_id: distinctId,\n },\n package: {\n name: PACKAGE_NAME,\n ...(isThreadsTelemetryEvent(event) ? { version: PACKAGE_VERSION } : {}),\n },\n ts: Math.floor(Date.now() / 1000),\n });\n } catch {\n return;\n }\n\n void postBestEffort(TELEMETRY_INGEST_URL, body, distinctId);\n}\n\n// --- Typed per-event helpers ---\n// These enforce the known property shape for each V1 event at the call\n// site, so callers can't accidentally include PII under a wrong key.\n\nexport function trackBannerViewed(props: {\n banner_id: string;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerViewed, props);\n}\n\nexport function trackBannerClicked(props: {\n banner_id: string;\n cta: \"body\" | \"dismiss\";\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerClicked, props);\n}\n\nexport type InspectorThreadTelemetryProps = {\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n posthog_distinct_id?: string;\n intelligence_status?:\n | \"intelligence_not_enabled\"\n | \"intelligence_enabled\"\n | \"unknown\";\n thread_service_status?: \"unavailable\" | \"available\" | \"unknown\" | \"error\";\n license_status?:\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n runtime_mode?: \"sse\" | \"intelligence\";\n runtime_url_type?: RuntimeUrlType;\n cta_surface?:\n | \"threads_locked\"\n | \"threads_header\"\n | \"threads_empty\"\n | \"threads_populated\";\n cta?: \"signup\" | \"talk_to_engineer\";\n telemetry_disabled?: boolean;\n thread_count?: number;\n example_thread_id?: string;\n tour_step?: number;\n tour_tab?: \"timeline\" | \"raw-events\" | \"state\";\n dismiss_method?: \"skip\" | \"done\";\n};\n\nexport function trackThreadsTabClicked(\n props: InspectorThreadTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.threadsTabClicked, props);\n}\n\nexport function trackThreadsLockedViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsLockedViewed, props);\n}\n\nexport function trackThreadsIntelligenceSignupClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsIntelligenceSignupClicked, props);\n}\n\nexport function trackThreadsTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsTalkToEngineerClicked, props);\n}\n\nexport function trackTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.talkToEngineerClicked, props);\n}\n\nexport function trackThreadsEmptyEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEmptyEnabledViewed, props);\n}\n\nexport function trackThreadsEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEnabledViewed, props);\n}\n\nexport function trackThreadsExampleViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleViewed, props);\n}\n\nexport function trackThreadsExampleSelected(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleSelected, props);\n}\n\nexport function trackThreadsExampleTourStarted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourStarted, props);\n}\n\nexport function trackThreadsExampleTourStepViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourStepViewed, props);\n}\n\nexport function trackThreadsExampleTourDismissed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourDismissed, props);\n}\n\nexport function trackThreadsExampleTourCompleted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourCompleted, props);\n}\n\nexport function trackThreadsExampleTourReopened(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourReopened, props);\n}\n\nexport type InspectorMemoryTelemetryProps = {\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n posthog_distinct_id?: string;\n memory_count?: number;\n available?: boolean;\n};\n\nexport function trackMemoriesTabClicked(\n props: InspectorMemoryTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.memoriesTabClicked, props);\n}\n\n/**\n * Returns the inspector's anonymous distinct-ID for cross-domain\n * propagation onto outbound banner-CTA links, or `null` when the user\n * is opted out.\n *\n * The website / Ops API reads this query param on signup-flow landing\n * pages and calls `posthog.alias(...)` to merge the inspector's anon\n * ID with the website's anon ID, enabling the\n * `banner_viewed → banner_clicked → signup_attributed` funnel.\n * `identify()` itself is out of scope here (it happens on signup, in\n * the website / Ops API).\n *\n * Opt-out short-circuits this too: if the user has opted out, we do\n * NOT leak an anon ID across domains.\n */\nexport function getTelemetryDistinctIdForUrl(): string | null {\n if (isTelemetryOptedOut()) return null;\n return getOrCreateTelemetryDistinctId();\n}\n\n/**\n * Seeds the anonymous distinct-ID into localStorage on inspector mount\n * so it is ready for cross-domain propagation onto banner-CTA links\n * even before the first event fires. No-op when the user has opted out.\n */\nexport function ensureTelemetryDistinctId(): void {\n if (isTelemetryOptedOut()) return;\n getOrCreateTelemetryDistinctId();\n}\n\n/**\n * Fires the one-time console disclosure on inspector mount, when the\n * user is not opted out and hasn't seen it before. Idempotent across\n * calls within a single session because `markTelemetryDisclosureShown`\n * persists to localStorage.\n *\n * If the user is opted out, we deliberately do nothing and do NOT mark\n * the flag — so a future opt-in flips back to \"first run\" behavior.\n */\nexport function maybeShowDisclosure(): void {\n if (isTelemetryOptedOut()) return;\n if (hasTelemetryDisclosureBeenShown()) return;\n // eslint-disable-next-line no-console\n console.info(\n `[CopilotKit Inspector] anonymous interaction telemetry enabled — see ${TELEMETRY_DOCS_URL} to opt out.`,\n );\n markTelemetryDisclosureShown();\n}\n\nexport { isTelemetryOptedOut };\n\nasync function postBestEffort(\n url: string,\n body: string,\n distinctId: string,\n): Promise<void> {\n if (typeof fetch === \"undefined\") return;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-CopilotKit-Telemetry-Id\": distinctId,\n },\n body,\n signal: controller.signal,\n // No credentials / no Authorization header — anonymous endpoint.\n });\n } catch {\n // Silent failure — telemetry must not break the application.\n } finally {\n clearTimeout(timeoutId);\n }\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"telemetry.mjs","names":["packageJson.version"],"sources":["../../src/lib/telemetry.ts"],"sourcesContent":["// Inspector-side anonymous telemetry. V1 events fire from index.ts for\n// banner and thread-inspection interactions. POSTs directly from the browser\n// to the CopilotKit telemetry sink at `telemetry.copilotkit.ai/ingest`,\n// where a Lambda fan-out forwards events to PostHog / Reo / Scarf.\n//\n// The endpoint URL is intentionally clearly named so it's obvious in\n// DevTools / Network tab — transparency for opt-in users.\n//\n// Privacy invariants enforced here:\n// - We never send message content, agent state, prompts, completions,\n// or banner markdown. Properties are scoped to event metadata only\n// (banner_id/timestamp, cta location). Reviewers should grep call\n// sites for any unintended payload.\n// - The opt-out short-circuits before any network call. There is no\n// buffer, no retry queue.\n// - All errors are swallowed; telemetry must never break the host app.\n\nimport {\n getOrCreateTelemetryDistinctId,\n hasTelemetryDisclosureBeenShown,\n isTelemetryOptedOut,\n markTelemetryDisclosureShown,\n} from \"./persistence.js\";\nimport packageJson from \"../../package.json\" with { type: \"json\" };\n\n// V1 funnel events. Namespaced `oss.inspector.*` so the lambda's\n// owned-prefix gate (oss-path-to-production) can accept them server-side\n// without a per-event sink deploy.\nexport const TELEMETRY_EVENTS = {\n opened: \"oss.inspector.opened\",\n bannerViewed: \"oss.inspector.banner_viewed\",\n bannerClicked: \"oss.inspector.banner_clicked\",\n bannerDismissed: \"oss.inspector.banner_dismissed\",\n threadsTabClicked: \"oss.inspector.threads_tab_clicked\",\n threadsLockedViewed: \"oss.inspector.threads_locked_viewed\",\n threadsIntelligenceSignupClicked:\n \"oss.inspector.threads_intelligence_signup_clicked\",\n threadsTalkToEngineerClicked:\n \"oss.inspector.threads_talk_to_engineer_clicked\",\n talkToEngineerClicked: \"oss.inspector.talk_to_engineer_clicked\",\n threadsEmptyEnabledViewed: \"oss.inspector.threads_empty_enabled_viewed\",\n threadsEnabledViewed: \"oss.inspector.threads_enabled_viewed\",\n threadsExampleViewed: \"oss.inspector.threads_example_viewed\",\n threadsExampleSelected: \"oss.inspector.threads_example_selected\",\n threadsExampleTourStarted: \"oss.inspector.threads_example_tour_started\",\n threadsExampleTourStepViewed:\n \"oss.inspector.threads_example_tour_step_viewed\",\n threadsExampleTourDismissed: \"oss.inspector.threads_example_tour_dismissed\",\n threadsExampleTourCompleted: \"oss.inspector.threads_example_tour_completed\",\n threadsExampleTourReopened: \"oss.inspector.threads_example_tour_reopened\",\n memoriesTabClicked: \"oss.inspector.memories_tab_clicked\",\n} as const;\n\nexport type TelemetryEvent =\n (typeof TELEMETRY_EVENTS)[keyof typeof TELEMETRY_EVENTS];\n\n// Per the OSS-96 ticket — the URL is intentionally clearly named for\n// transparency in the network tab.\nexport const TELEMETRY_INGEST_URL = \"https://telemetry.copilotkit.ai/ingest\";\n\n// Surfaced in console disclosure and the in-product opt-out panel.\n// Keep in sync with the canonical telemetry docs page on main\n// (`docs/content/docs/(root)/(other)/telemetry/index.mdx`).\n// Mirror constant: packages/runtime/src/lib/telemetry-disclosure.ts\nexport const TELEMETRY_DOCS_URL = \"https://docs.copilotkit.ai/telemetry\";\n\nconst PACKAGE_NAME = \"@copilotkit/web-inspector\";\nconst PACKAGE_VERSION = packageJson.version;\n\n// 3-second cap so a slow gateway can't hang the host app. Matches the\n// runtime's existing scarf-client convention.\nconst FETCH_TIMEOUT_MS = 3000;\n\n// Events that carry package identity (`package_name` / `package_version`) and\n// the inspector's anonymous distinct-ID alongside their own properties.\n// Threads / memories events have always been enriched this way; `opened`\n// joins them so panel-open volume can be segmented by inspector version.\n// Banner events deliberately keep the flat shape their existing dashboards\n// read.\nfunction isEnrichedTelemetryEvent(event: TelemetryEvent): boolean {\n return (\n event === TELEMETRY_EVENTS.opened ||\n event === TELEMETRY_EVENTS.threadsTabClicked ||\n event === TELEMETRY_EVENTS.threadsLockedViewed ||\n event === TELEMETRY_EVENTS.threadsIntelligenceSignupClicked ||\n event === TELEMETRY_EVENTS.threadsTalkToEngineerClicked ||\n event === TELEMETRY_EVENTS.talkToEngineerClicked ||\n event === TELEMETRY_EVENTS.threadsEmptyEnabledViewed ||\n event === TELEMETRY_EVENTS.threadsEnabledViewed ||\n event === TELEMETRY_EVENTS.threadsExampleViewed ||\n event === TELEMETRY_EVENTS.threadsExampleSelected ||\n event === TELEMETRY_EVENTS.threadsExampleTourStarted ||\n event === TELEMETRY_EVENTS.threadsExampleTourStepViewed ||\n event === TELEMETRY_EVENTS.threadsExampleTourDismissed ||\n event === TELEMETRY_EVENTS.threadsExampleTourCompleted ||\n event === TELEMETRY_EVENTS.threadsExampleTourReopened ||\n event === TELEMETRY_EVENTS.memoriesTabClicked\n );\n}\n\nexport type RuntimeUrlType =\n | \"missing\"\n | \"relative\"\n | \"localhost\"\n | \"same_origin\"\n | \"remote\"\n | \"invalid\";\n\nexport function getRuntimeUrlType(\n runtimeUrl: string | undefined,\n): RuntimeUrlType {\n if (!runtimeUrl) return \"missing\";\n if (runtimeUrl.startsWith(\"/\") && !runtimeUrl.startsWith(\"//\")) {\n return \"relative\";\n }\n\n try {\n const baseHref =\n typeof window !== \"undefined\"\n ? window.location.href\n : \"https://copilotkit.ai\";\n const url = new URL(runtimeUrl, baseHref);\n const baseUrl = new URL(baseHref);\n const hostname = url.hostname.toLowerCase();\n\n if (\n hostname === \"localhost\" ||\n hostname === \"127.0.0.1\" ||\n hostname === \"[::1]\"\n ) {\n return \"localhost\";\n }\n\n return url.origin === baseUrl.origin ? \"same_origin\" : \"remote\";\n } catch {\n return \"invalid\";\n }\n}\n\n/**\n * Fire-and-forget telemetry send. Returns synchronously; the network\n * call is dispatched in the background and any failure is swallowed.\n *\n * Short-circuits when the user has opted out. Does NOT itself trigger\n * the first-run disclosure — call `maybeShowDisclosure()` from the\n * inspector's mount lifecycle instead.\n */\nexport function track(\n event: TelemetryEvent,\n properties: Record<string, unknown> = {},\n): void {\n if (isTelemetryOptedOut()) return;\n\n const distinctId = getOrCreateTelemetryDistinctId();\n const enrichedProperties = isEnrichedTelemetryEvent(event)\n ? {\n package_name: PACKAGE_NAME,\n package_version: PACKAGE_VERSION,\n inspector_distinct_id: distinctId,\n }\n : {};\n let body: string;\n try {\n body = JSON.stringify({\n event,\n properties: {\n ...properties,\n ...enrichedProperties,\n distinct_id: distinctId,\n },\n package: {\n name: PACKAGE_NAME,\n ...(isEnrichedTelemetryEvent(event)\n ? { version: PACKAGE_VERSION }\n : {}),\n },\n ts: Math.floor(Date.now() / 1000),\n });\n } catch {\n return;\n }\n\n void postBestEffort(TELEMETRY_INGEST_URL, body, distinctId);\n}\n\n// --- Typed per-event helpers ---\n// These enforce the known property shape for each V1 event at the call\n// site, so callers can't accidentally include PII under a wrong key.\n\n/**\n * Where an announcement was rendered when the event fired. The announcement\n * has two surfaces — the preview bubble beside the *collapsed* floating\n * button, and the card *inside* the opened panel — and reach on one says\n * nothing about attention on the other. Always stamped at fire time, never\n * inferred at fetch time.\n */\nexport type BannerSurface = \"collapsed_preview\" | \"expanded_card\";\n\nexport function trackBannerViewed(props: {\n banner_id: string;\n surface: BannerSurface;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerViewed, props);\n}\n\nexport function trackBannerClicked(props: {\n banner_id: string;\n cta: \"body\" | \"dismiss\";\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerClicked, props);\n}\n\n/**\n * First-class dismissal signal. Emitted *in addition to*\n * `banner_clicked { cta: \"dismiss\" }` rather than replacing it — the\n * existing dashboards read the `cta` value, so removing it would silently\n * zero them out. New reporting should prefer this event, whose `surface`\n * says whether the user swatted the bubble away or dismissed the card\n * after opening the panel.\n */\nexport function trackBannerDismissed(props: {\n banner_id: string;\n surface: BannerSurface;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.bannerDismissed, props);\n}\n\n/**\n * How the panel was opened. Restoring a persisted-open panel on mount is\n * deliberately NOT a source: it is not an open *intent*, and counting it\n * would turn every dev-server hot reload into an \"open\".\n */\nexport type InspectorOpenSource = \"floating_button\" | \"announcement_preview\";\n\nexport type InspectorOpenedTelemetryProps = {\n open_source: InspectorOpenSource;\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n license_status?:\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n runtime_mode?: \"sse\" | \"intelligence\";\n runtime_url_type?: RuntimeUrlType;\n /** True when an unseen announcement was on screen at open time. */\n has_unseen_announcement?: boolean;\n};\n\n/**\n * Panel-open signal (OSS-566). Before this event, opens could only be\n * inferred from in-panel activity (a floor) or from `banner_clicked`\n * cta=`body` (which misses the floating-button path entirely).\n */\nexport function trackInspectorOpened(\n props: InspectorOpenedTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.opened, props);\n}\n\nexport type InspectorThreadTelemetryProps = {\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n posthog_distinct_id?: string;\n intelligence_status?:\n | \"intelligence_not_enabled\"\n | \"intelligence_enabled\"\n | \"unknown\";\n thread_service_status?: \"unavailable\" | \"available\" | \"unknown\" | \"error\";\n license_status?:\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"expiring\"\n | \"invalid\"\n | \"unknown\";\n runtime_mode?: \"sse\" | \"intelligence\";\n runtime_url_type?: RuntimeUrlType;\n cta_surface?:\n | \"threads_locked\"\n | \"threads_header\"\n | \"threads_empty\"\n | \"threads_populated\";\n cta?: \"signup\" | \"talk_to_engineer\";\n telemetry_disabled?: boolean;\n thread_count?: number;\n example_thread_id?: string;\n tour_step?: number;\n tour_tab?: \"timeline\" | \"raw-events\" | \"state\";\n dismiss_method?: \"skip\" | \"done\";\n};\n\nexport function trackThreadsTabClicked(\n props: InspectorThreadTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.threadsTabClicked, props);\n}\n\nexport function trackThreadsLockedViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsLockedViewed, props);\n}\n\nexport function trackThreadsIntelligenceSignupClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsIntelligenceSignupClicked, props);\n}\n\nexport function trackThreadsTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsTalkToEngineerClicked, props);\n}\n\nexport function trackTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.talkToEngineerClicked, props);\n}\n\nexport function trackThreadsEmptyEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEmptyEnabledViewed, props);\n}\n\nexport function trackThreadsEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEnabledViewed, props);\n}\n\nexport function trackThreadsExampleViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleViewed, props);\n}\n\nexport function trackThreadsExampleSelected(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleSelected, props);\n}\n\nexport function trackThreadsExampleTourStarted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourStarted, props);\n}\n\nexport function trackThreadsExampleTourStepViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourStepViewed, props);\n}\n\nexport function trackThreadsExampleTourDismissed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourDismissed, props);\n}\n\nexport function trackThreadsExampleTourCompleted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourCompleted, props);\n}\n\nexport function trackThreadsExampleTourReopened(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleTourReopened, props);\n}\n\nexport type InspectorMemoryTelemetryProps = {\n package_name?: typeof PACKAGE_NAME;\n package_version?: string;\n inspector_distinct_id?: string;\n posthog_distinct_id?: string;\n memory_count?: number;\n available?: boolean;\n};\n\nexport function trackMemoriesTabClicked(\n props: InspectorMemoryTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.memoriesTabClicked, props);\n}\n\n/**\n * Returns the inspector's anonymous distinct-ID for cross-domain\n * propagation onto outbound banner-CTA links, or `null` when the user\n * is opted out.\n *\n * The website / Ops API reads this query param on signup-flow landing\n * pages and calls `posthog.alias(...)` to merge the inspector's anon\n * ID with the website's anon ID, enabling the\n * `banner_viewed → banner_clicked → signup_attributed` funnel.\n * `identify()` itself is out of scope here (it happens on signup, in\n * the website / Ops API).\n *\n * Opt-out short-circuits this too: if the user has opted out, we do\n * NOT leak an anon ID across domains.\n */\nexport function getTelemetryDistinctIdForUrl(): string | null {\n if (isTelemetryOptedOut()) return null;\n return getOrCreateTelemetryDistinctId();\n}\n\n/**\n * Seeds the anonymous distinct-ID into localStorage on inspector mount\n * so it is ready for cross-domain propagation onto banner-CTA links\n * even before the first event fires. No-op when the user has opted out.\n */\nexport function ensureTelemetryDistinctId(): void {\n if (isTelemetryOptedOut()) return;\n getOrCreateTelemetryDistinctId();\n}\n\n/**\n * Fires the one-time console disclosure on inspector mount, when the\n * user is not opted out and hasn't seen it before. Idempotent across\n * calls within a single session because `markTelemetryDisclosureShown`\n * persists to localStorage.\n *\n * If the user is opted out, we deliberately do nothing and do NOT mark\n * the flag — so a future opt-in flips back to \"first run\" behavior.\n */\nexport function maybeShowDisclosure(): void {\n if (isTelemetryOptedOut()) return;\n if (hasTelemetryDisclosureBeenShown()) return;\n // eslint-disable-next-line no-console\n console.info(\n `[CopilotKit Inspector] anonymous interaction telemetry enabled — see ${TELEMETRY_DOCS_URL} to opt out.`,\n );\n markTelemetryDisclosureShown();\n}\n\nexport { isTelemetryOptedOut };\n\nasync function postBestEffort(\n url: string,\n body: string,\n distinctId: string,\n): Promise<void> {\n if (typeof fetch === \"undefined\") return;\n const controller = new AbortController();\n const timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\n try {\n await fetch(url, {\n method: \"POST\",\n headers: {\n \"Content-Type\": \"application/json\",\n \"X-CopilotKit-Telemetry-Id\": distinctId,\n },\n body,\n signal: controller.signal,\n // No credentials / no Authorization header — anonymous endpoint.\n });\n } catch {\n // Silent failure — telemetry must not break the application.\n } finally {\n clearTimeout(timeoutId);\n }\n}\n"],"mappings":"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"}
|
package/dist/package.cjs
CHANGED
package/dist/package.mjs
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@copilotkit/web-inspector",
|
|
3
|
-
"version": "1.64.2
|
|
3
|
+
"version": "1.64.2",
|
|
4
4
|
"description": "Lit-based web component for the CopilotKit web inspector",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -27,7 +27,7 @@
|
|
|
27
27
|
"lit": "^3.2.0",
|
|
28
28
|
"lucide": "^0.525.0",
|
|
29
29
|
"marked": "^12.0.2",
|
|
30
|
-
"@copilotkit/core": "1.64.2
|
|
30
|
+
"@copilotkit/core": "1.64.2"
|
|
31
31
|
},
|
|
32
32
|
"devDependencies": {
|
|
33
33
|
"@tailwindcss/cli": "^4.1.11",
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
import { createTelemetryEgressGuard } from "../lib/testing/telemetry-egress-guard.js";
|
|
4
|
+
|
|
5
|
+
// The inspector's telemetry is browser-side and fire-and-forget, and jsdom has a
|
|
6
|
+
// real `fetch`. The setup-file guard is what keeps unit runs — local and CI —
|
|
7
|
+
// from POSTing real `oss.inspector.*` events to the live sink.
|
|
8
|
+
|
|
9
|
+
describe("telemetry egress guard", () => {
|
|
10
|
+
it("swallows requests to the telemetry sink without touching the network", async () => {
|
|
11
|
+
const realFetch = vi.fn(() =>
|
|
12
|
+
Promise.resolve(new Response(null, { status: 200 })),
|
|
13
|
+
) as unknown as typeof fetch;
|
|
14
|
+
|
|
15
|
+
const guarded = createTelemetryEgressGuard(realFetch);
|
|
16
|
+
const response = await guarded("https://telemetry.copilotkit.ai/ingest", {
|
|
17
|
+
method: "POST",
|
|
18
|
+
body: "{}",
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
expect(response.status).toBe(204);
|
|
22
|
+
expect(realFetch).not.toHaveBeenCalled();
|
|
23
|
+
});
|
|
24
|
+
|
|
25
|
+
it("passes every other request through untouched", async () => {
|
|
26
|
+
const realFetch = vi.fn(() =>
|
|
27
|
+
Promise.resolve(new Response(null, { status: 200 })),
|
|
28
|
+
) as unknown as typeof fetch;
|
|
29
|
+
|
|
30
|
+
const guarded = createTelemetryEgressGuard(realFetch);
|
|
31
|
+
await guarded("https://cdn.copilotkit.ai/announcements.json");
|
|
32
|
+
await guarded(new URL("http://localhost/api/threads"));
|
|
33
|
+
|
|
34
|
+
expect(realFetch).toHaveBeenCalledTimes(2);
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
it("is installed on the global fetch for every test in this package", () => {
|
|
38
|
+
// A raw platform fetch would stringify as native code; the guard doesn't.
|
|
39
|
+
expect(String(globalThis.fetch)).not.toContain("[native code]");
|
|
40
|
+
});
|
|
41
|
+
});
|