@copilotkit/web-inspector 1.73.2 → 1.74.0
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 +363 -271
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +29 -6
- package/dist/index.d.cts.map +1 -1
- package/dist/index.d.mts +29 -6
- package/dist/index.d.mts.map +1 -1
- package/dist/index.mjs +364 -272
- package/dist/index.mjs.map +1 -1
- package/dist/index.umd.js +1274 -628
- package/dist/index.umd.js.map +1 -1
- package/dist/lib/notification-loader.cjs +38 -0
- package/dist/lib/notification-loader.cjs.map +1 -0
- package/dist/lib/notification-loader.mjs +38 -0
- package/dist/lib/notification-loader.mjs.map +1 -0
- package/dist/lib/notifications.cjs +173 -0
- package/dist/lib/notifications.cjs.map +1 -0
- package/dist/lib/notifications.d.cts +17 -0
- package/dist/lib/notifications.d.cts.map +1 -0
- package/dist/lib/notifications.d.mts +17 -0
- package/dist/lib/notifications.d.mts.map +1 -0
- package/dist/lib/notifications.mjs +168 -0
- package/dist/lib/notifications.mjs.map +1 -0
- package/dist/lib/onboarding-prompt.cjs +11 -17
- package/dist/lib/onboarding-prompt.cjs.map +1 -1
- package/dist/lib/onboarding-prompt.mjs +11 -17
- package/dist/lib/onboarding-prompt.mjs.map +1 -1
- package/dist/lib/persistence.cjs +124 -48
- package/dist/lib/persistence.cjs.map +1 -1
- package/dist/lib/persistence.mjs +121 -45
- package/dist/lib/persistence.mjs.map +1 -1
- package/dist/lib/telemetry.cjs +42 -0
- package/dist/lib/telemetry.cjs.map +1 -1
- package/dist/lib/telemetry.d.cts.map +1 -1
- package/dist/lib/telemetry.d.mts.map +1 -1
- package/dist/lib/telemetry.mjs +35 -1
- package/dist/lib/telemetry.mjs.map +1 -1
- package/dist/package.cjs +1 -1
- package/dist/package.mjs +1 -1
- package/dist/styles/generated.cjs +1 -1
- package/dist/styles/generated.cjs.map +1 -1
- package/dist/styles/generated.mjs +1 -1
- package/dist/styles/generated.mjs.map +1 -1
- package/dist/styles/notification-article.cjs +244 -0
- package/dist/styles/notification-article.cjs.map +1 -0
- package/dist/styles/notification-article.mjs +244 -0
- package/dist/styles/notification-article.mjs.map +1 -0
- package/package.json +4 -3
package/dist/lib/persistence.mjs
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { emptyNotificationState, parseNotificationState } from "./notifications.mjs";
|
|
2
|
+
|
|
1
3
|
//#region src/lib/persistence.ts
|
|
2
4
|
function loadInspectorState(storageKey) {
|
|
3
5
|
if (typeof window === "undefined") return null;
|
|
@@ -94,30 +96,8 @@ function parseInspectorDismissalPayload(raw) {
|
|
|
94
96
|
return null;
|
|
95
97
|
}
|
|
96
98
|
}
|
|
97
|
-
const ANNOUNCEMENT_READ_COOKIE_NAME = "cpk_inspector_announcements";
|
|
98
|
-
const ANNOUNCEMENT_READ_MIRROR_KEY = "cpk:inspector:announcement_read";
|
|
99
99
|
const LEGACY_ANNOUNCEMENT_READ_KEY = "cpk:inspector:announcements";
|
|
100
100
|
const ANNOUNCEMENT_PULSED_SESSION_KEY = "cpk:inspector:pulsed";
|
|
101
|
-
const ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS = 3600 * 24 * 365;
|
|
102
|
-
/**
|
|
103
|
-
* The announcement timestamp the user has already read, or `null` when
|
|
104
|
-
* nothing has been read yet. Prefers the host-scoped cookie and falls back to
|
|
105
|
-
* the origin-scoped mirror, so cookie-blocking browsers still remember the
|
|
106
|
-
* read within the port they are on.
|
|
107
|
-
*/
|
|
108
|
-
function loadAnnouncementReadTimestamp() {
|
|
109
|
-
return parseTimestampPayload(readAnnouncementCookie()) ?? parseTimestampPayload(readLocalStorageItem(ANNOUNCEMENT_READ_MIRROR_KEY));
|
|
110
|
-
}
|
|
111
|
-
/**
|
|
112
|
-
* Records an announcement as read in both the host-scoped cookie and the
|
|
113
|
-
* origin-scoped mirror. The stored value is `{"timestamp":"…"}` — the same
|
|
114
|
-
* shape the announcement state has always used.
|
|
115
|
-
*/
|
|
116
|
-
function saveAnnouncementReadTimestamp(timestamp) {
|
|
117
|
-
const payload = JSON.stringify({ timestamp });
|
|
118
|
-
writeAnnouncementCookie(payload);
|
|
119
|
-
writeLocalStorageItem(ANNOUNCEMENT_READ_MIRROR_KEY, payload);
|
|
120
|
-
}
|
|
121
101
|
/**
|
|
122
102
|
* Deletes the superseded origin-scoped read state. Safe to call on every
|
|
123
103
|
* startup: nothing writes that key any more, so the value cannot come back.
|
|
@@ -137,28 +117,6 @@ function loadAnnouncementPulsedTimestamp() {
|
|
|
137
117
|
return null;
|
|
138
118
|
}
|
|
139
119
|
}
|
|
140
|
-
/** Suppresses further pulses for this announcement in this browser tab. */
|
|
141
|
-
function saveAnnouncementPulsedTimestamp(timestamp) {
|
|
142
|
-
if (typeof window === "undefined") return;
|
|
143
|
-
try {
|
|
144
|
-
window.sessionStorage.setItem(ANNOUNCEMENT_PULSED_SESSION_KEY, timestamp);
|
|
145
|
-
} catch {}
|
|
146
|
-
}
|
|
147
|
-
function parseTimestampPayload(raw) {
|
|
148
|
-
if (!raw) return null;
|
|
149
|
-
try {
|
|
150
|
-
const parsed = JSON.parse(raw);
|
|
151
|
-
return typeof parsed?.timestamp === "string" ? parsed.timestamp : null;
|
|
152
|
-
} catch {
|
|
153
|
-
return null;
|
|
154
|
-
}
|
|
155
|
-
}
|
|
156
|
-
function readAnnouncementCookie() {
|
|
157
|
-
return readCookie(ANNOUNCEMENT_READ_COOKIE_NAME);
|
|
158
|
-
}
|
|
159
|
-
function writeAnnouncementCookie(value) {
|
|
160
|
-
writeCookie(ANNOUNCEMENT_READ_COOKIE_NAME, value, `Max-Age=${ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS}`);
|
|
161
|
-
}
|
|
162
120
|
function readCookie(name) {
|
|
163
121
|
if (typeof document === "undefined") return null;
|
|
164
122
|
try {
|
|
@@ -242,7 +200,125 @@ function generateUuidV4() {
|
|
|
242
200
|
return (c === "x" ? r : r & 3 | 8).toString(16);
|
|
243
201
|
});
|
|
244
202
|
}
|
|
203
|
+
const NOTIFICATION_COOKIE = "cpk_inspector_notifications_v1";
|
|
204
|
+
const NOTIFICATION_STORAGE = "cpk:inspector:notifications:v1";
|
|
205
|
+
const NOTIFICATION_COOKIE_MAX_LENGTH = 1024;
|
|
206
|
+
const LEGACY_ANNOUNCEMENT_ID = "16f7d877-49e3-41c3-9ca6-f951d3d8ba80";
|
|
207
|
+
/** Read host-wide acknowledgements from either the new compact cookie or an old full-state cookie. */
|
|
208
|
+
function readNotificationAcknowledgements() {
|
|
209
|
+
try {
|
|
210
|
+
const raw = readCookie(NOTIFICATION_COOKIE);
|
|
211
|
+
if (raw) {
|
|
212
|
+
const value = JSON.parse(raw);
|
|
213
|
+
if (value && typeof value === "object") {
|
|
214
|
+
const state = parseNotificationState({
|
|
215
|
+
...emptyNotificationState(),
|
|
216
|
+
...value
|
|
217
|
+
});
|
|
218
|
+
if (state) return {
|
|
219
|
+
readIds: state.readIds,
|
|
220
|
+
suppressedIds: state.suppressedIds
|
|
221
|
+
};
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
} catch {}
|
|
225
|
+
return {
|
|
226
|
+
readIds: [],
|
|
227
|
+
suppressedIds: []
|
|
228
|
+
};
|
|
229
|
+
}
|
|
230
|
+
/** Load per-origin selection and merge host-wide read and suppressed notices. */
|
|
231
|
+
function loadNotificationState() {
|
|
232
|
+
const raw = readLocalStorageItem(NOTIFICATION_STORAGE);
|
|
233
|
+
let localState = emptyNotificationState();
|
|
234
|
+
if (raw) try {
|
|
235
|
+
localState = parseNotificationState(JSON.parse(raw)) ?? localState;
|
|
236
|
+
} catch {}
|
|
237
|
+
const host = readNotificationAcknowledgements();
|
|
238
|
+
return {
|
|
239
|
+
...localState,
|
|
240
|
+
readIds: [...new Set([...localState.readIds, ...host.readIds])],
|
|
241
|
+
suppressedIds: [...new Set([...localState.suppressedIds, ...host.suppressedIds])]
|
|
242
|
+
};
|
|
243
|
+
}
|
|
244
|
+
/** Preserve a legacy acknowledgement only for the known announcement entering the new feed. */
|
|
245
|
+
function migrateAnnouncementReadState(state, feed) {
|
|
246
|
+
for (const raw of [readCookie("cpk_inspector_announcements"), readLocalStorageItem("cpk:inspector:announcement_read")]) {
|
|
247
|
+
if (!raw) continue;
|
|
248
|
+
try {
|
|
249
|
+
const value = JSON.parse(raw);
|
|
250
|
+
if (!value || typeof value !== "object" || !("timestamp" in value)) continue;
|
|
251
|
+
const legacyNotice = feed.notifications.find((notice) => notice.id === LEGACY_ANNOUNCEMENT_ID && notice.publishedAt === value.timestamp);
|
|
252
|
+
if (!legacyNotice) continue;
|
|
253
|
+
const migrated = {
|
|
254
|
+
...state,
|
|
255
|
+
readIds: [...new Set([...state.readIds, legacyNotice.id])],
|
|
256
|
+
suppressedIds: [...new Set([...state.suppressedIds, legacyNotice.id])]
|
|
257
|
+
};
|
|
258
|
+
saveNotificationState(migrated);
|
|
259
|
+
writeCookie("cpk_inspector_announcements", "", "Max-Age=0");
|
|
260
|
+
removeLocalStorageItem("cpk:inspector:announcement_read");
|
|
261
|
+
return migrated;
|
|
262
|
+
} catch {}
|
|
263
|
+
}
|
|
264
|
+
return state;
|
|
265
|
+
}
|
|
266
|
+
/** Save full state per origin and a bounded host-wide acknowledgement cookie. */
|
|
267
|
+
function saveNotificationState(state) {
|
|
268
|
+
const host = readNotificationAcknowledgements();
|
|
269
|
+
const merged = {
|
|
270
|
+
...state,
|
|
271
|
+
readIds: [...new Set([...state.readIds, ...host.readIds])],
|
|
272
|
+
suppressedIds: [...new Set([...state.suppressedIds, ...host.suppressedIds])]
|
|
273
|
+
};
|
|
274
|
+
writeLocalStorageItem(NOTIFICATION_STORAGE, JSON.stringify(merged));
|
|
275
|
+
const cookieState = {
|
|
276
|
+
schemaVersion: 1,
|
|
277
|
+
readIds: [...merged.readIds],
|
|
278
|
+
suppressedIds: [...merged.suppressedIds]
|
|
279
|
+
};
|
|
280
|
+
let raw = JSON.stringify(cookieState);
|
|
281
|
+
while (encodeURIComponent(raw).length >= NOTIFICATION_COOKIE_MAX_LENGTH) {
|
|
282
|
+
if (cookieState.readIds.length >= cookieState.suppressedIds.length) cookieState.readIds.shift();
|
|
283
|
+
else cookieState.suppressedIds.shift();
|
|
284
|
+
raw = JSON.stringify(cookieState);
|
|
285
|
+
}
|
|
286
|
+
writeCookie(NOTIFICATION_COOKIE, raw, "Max-Age=31536000");
|
|
287
|
+
}
|
|
288
|
+
/** ID-based pulse state is separate from the legacy announcement timestamp. */
|
|
289
|
+
const NOTIFICATION_PULSED_SESSION_KEY = "cpk:inspector:notification-pulsed-id";
|
|
290
|
+
const MAX_PULSED_NOTIFICATION_IDS = 100;
|
|
291
|
+
function pulsedNotificationIds(raw) {
|
|
292
|
+
if (!raw) return [];
|
|
293
|
+
if (!raw.startsWith("[")) return [raw];
|
|
294
|
+
try {
|
|
295
|
+
const ids = JSON.parse(raw);
|
|
296
|
+
return Array.isArray(ids) ? ids.filter((id) => typeof id === "string") : [];
|
|
297
|
+
} catch {
|
|
298
|
+
return [];
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
function saveNotificationPulsedId(id) {
|
|
302
|
+
if (typeof window === "undefined") return;
|
|
303
|
+
try {
|
|
304
|
+
const ids = pulsedNotificationIds(window.sessionStorage.getItem(NOTIFICATION_PULSED_SESSION_KEY));
|
|
305
|
+
if (!ids.includes(id)) ids.push(id);
|
|
306
|
+
window.sessionStorage.setItem(NOTIFICATION_PULSED_SESSION_KEY, JSON.stringify(ids.slice(-MAX_PULSED_NOTIFICATION_IDS)));
|
|
307
|
+
} catch {}
|
|
308
|
+
}
|
|
309
|
+
function hasNotificationPulsed(id, publishedAt) {
|
|
310
|
+
if (typeof window === "undefined") return false;
|
|
311
|
+
try {
|
|
312
|
+
const current = window.sessionStorage.getItem(NOTIFICATION_PULSED_SESSION_KEY);
|
|
313
|
+
if (current !== null) return pulsedNotificationIds(current).includes(id);
|
|
314
|
+
if (loadAnnouncementPulsedTimestamp() === publishedAt) {
|
|
315
|
+
saveNotificationPulsedId(id);
|
|
316
|
+
return true;
|
|
317
|
+
}
|
|
318
|
+
} catch {}
|
|
319
|
+
return false;
|
|
320
|
+
}
|
|
245
321
|
|
|
246
322
|
//#endregion
|
|
247
|
-
export { INSPECTOR_DISMISSAL_MAX_DURATION_MS, clearLegacyAnnouncementReadState, getOrCreateTelemetryDistinctId, hasTelemetryDisclosureBeenShown, isTelemetryOptedOut, isValidAnchor, isValidDockMode, isValidPosition, isValidSize,
|
|
323
|
+
export { INSPECTOR_DISMISSAL_MAX_DURATION_MS, clearLegacyAnnouncementReadState, getOrCreateTelemetryDistinctId, hasNotificationPulsed, hasTelemetryDisclosureBeenShown, isTelemetryOptedOut, isValidAnchor, isValidDockMode, isValidPosition, isValidSize, loadInspectorDismissedUntil, loadInspectorState, loadNotificationState, markTelemetryDisclosureShown, migrateAnnouncementReadState, saveInspectorDismissedUntil, saveInspectorState, saveNotificationPulsedId, saveNotificationState };
|
|
248
324
|
//# sourceMappingURL=persistence.mjs.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"persistence.mjs","names":[],"sources":["../../src/lib/persistence.ts"],"sourcesContent":["import type { Anchor, DockMode, Position, Size } from \"./types.js\";\n\nexport type PersistedContextState = {\n anchor?: Anchor;\n anchorOffset?: Position;\n size?: Size;\n hasCustomPosition?: boolean;\n};\n\nexport type PersistedState = {\n button?: Omit<PersistedContextState, \"size\">;\n window?: PersistedContextState;\n isOpen?: boolean;\n dockMode?: DockMode;\n selectedMenu?: string;\n selectedContext?: string;\n hasOpenedInspector?: boolean;\n sidebarCollapsed?: boolean;\n /** @deprecated Replaced by colorSchemePreference to distinguish a user choice from the old light default. */\n colorScheme?: \"light\" | \"dark\";\n colorSchemePreference?: \"light\" | \"dark\";\n};\n\nexport const HOME_NEWS_READ_STORAGE_KEY = \"cpk:inspector:home-news-read\";\n\n/** Return story ids the user has already opened or hovered on Home. */\nexport function loadHomeNewsReadIds(storageKey: string): string[] {\n if (typeof window === \"undefined\") {\n return [];\n }\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) {\n return [];\n }\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter((id): id is string => typeof id === \"string\");\n } catch {\n return [];\n }\n}\n\n/** Persist Home news story ids that the user has already seen. */\nexport function saveHomeNewsReadIds(storageKey: string, ids: string[]): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(ids));\n } catch (error) {\n console.warn(\"Failed to persist Home news read state\", error);\n }\n}\n\nexport function loadInspectorState(storageKey: string): PersistedState | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n const raw = window.localStorage.getItem(storageKey);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n // Fall through to cookie migration path\n }\n }\n\n // Backwards compatibility: try to read the legacy cookie and migrate it\n if (typeof document !== \"undefined\") {\n const prefix = `${storageKey}=`;\n const entry = document.cookie\n .split(\"; \")\n .find((cookie) => cookie.startsWith(prefix));\n if (entry) {\n const legacyRaw = entry.substring(prefix.length);\n try {\n const parsed = JSON.parse(decodeURIComponent(legacyRaw));\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n return null;\n }\n }\n }\n\n return null;\n}\n\nexport function saveInspectorState(\n storageKey: string,\n state: PersistedState,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to persist inspector state\", error);\n }\n}\n\nexport function isValidAnchor(value: unknown): value is Anchor {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Anchor;\n return (\n (candidate.horizontal === \"left\" || candidate.horizontal === \"right\") &&\n (candidate.vertical === \"top\" || candidate.vertical === \"bottom\")\n );\n}\n\nexport function isValidPosition(value: unknown): value is Position {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Position;\n return isFiniteNumber(candidate.x) && isFiniteNumber(candidate.y);\n}\n\nexport function isValidSize(value: unknown): value is Size {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Size;\n return isFiniteNumber(candidate.width) && isFiniteNumber(candidate.height);\n}\n\nexport function isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nexport function isValidDockMode(value: unknown): value is DockMode {\n return value === \"floating\" || value === \"docked-left\";\n}\n\n// Inspector dismissal is intentionally host-scoped rather than origin-scoped.\n// A developer who closes the Inspector on localhost:3000 should not see it\n// again on localhost:5173 during the same dismissal window. Cookies are shared\n// across ports on one host; localStorage is not.\nexport const INSPECTOR_DISMISSAL_MIRROR_KEY = \"cpk:inspector:dismissed_until\";\nexport const INSPECTOR_DISMISSAL_COOKIE_NAME = \"cpk_inspector_dismissed_until\";\nexport const INSPECTOR_DISMISSAL_MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1000;\n\ntype InspectorDismissalPayload = Readonly<{ until: number }>;\n\n/** Return the active host-scoped Inspector dismissal deadline, if any. */\nexport function loadInspectorDismissedUntil(\n now: number = Date.now(),\n): number | null {\n const until =\n parseInspectorDismissalPayload(\n readCookie(INSPECTOR_DISMISSAL_COOKIE_NAME),\n ) ??\n parseInspectorDismissalPayload(\n readLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY),\n );\n\n if (until === null) return null;\n if (until <= now) {\n clearInspectorDismissal();\n return null;\n }\n const maximumUntil = now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;\n if (until > maximumUntil) {\n saveInspectorDismissedUntil(maximumUntil, now);\n return maximumUntil;\n }\n return until;\n}\n\n/** Persist a dismissal across browser sessions and localhost ports. */\nexport function saveInspectorDismissedUntil(\n until: number,\n now: number = Date.now(),\n): void {\n if (!Number.isFinite(until) || until <= now) {\n clearInspectorDismissal();\n return;\n }\n\n const boundedUntil = Math.min(\n until,\n now + INSPECTOR_DISMISSAL_MAX_DURATION_MS,\n );\n const payload = JSON.stringify({\n until: boundedUntil,\n } satisfies InspectorDismissalPayload);\n const maxAgeSeconds = Math.max(1, Math.ceil((boundedUntil - now) / 1000));\n writeCookie(\n INSPECTOR_DISMISSAL_COOKIE_NAME,\n payload,\n `Max-Age=${maxAgeSeconds}`,\n );\n writeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY, payload);\n}\n\n/** Clear both persistence layers, including an expired host cookie. */\nexport function clearInspectorDismissal(): void {\n writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, \"\", \"Max-Age=0\");\n removeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY);\n}\n\n/** Parse a finite deadline from either persistence layer. */\nfunction parseInspectorDismissalPayload(raw: string | null): number | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<InspectorDismissalPayload>;\n return typeof parsed.until === \"number\" && Number.isFinite(parsed.until)\n ? parsed.until\n : null;\n } catch {\n return null;\n }\n}\n\n// Announcement read state — \"have I read this announcement\" is a property of\n// the person, not of the project, so it must survive a change of localhost\n// port. localStorage cannot express that: it is partitioned by origin and\n// origin includes the port, so :3000 and :5173 are separate stores. Cookies\n// are partitioned by host, so a cookie set on `localhost` without a `domain`\n// attribute is shared by every port on that host.\n//\n// Underscores, not colons: `:` is a separator in RFC 6265 and is not valid in\n// a cookie name. Browsers are lenient about it; we don't rely on that.\nconst ANNOUNCEMENT_READ_COOKIE_NAME = \"cpk_inspector_announcements\";\n\n// localStorage mirror of the cookie, so a browser that blocks cookies\n// degrades to per-port behaviour instead of losing the read state entirely.\n// A NEW key on purpose — the legacy one is abandoned, not migrated.\nconst ANNOUNCEMENT_READ_MIRROR_KEY = \"cpk:inspector:announcement_read\";\n\n// The superseded key. Every existing user is re-armed exactly once so they\n// discover the surface that replaced the announcement bubble, and the key is\n// deleted rather than left in place so nothing can fall back to it later.\nconst LEGACY_ANNOUNCEMENT_READ_KEY = \"cpk:inspector:announcements\";\n\n// Pulse suppression is per browser tab, and stores the announcement timestamp\n// rather than a boolean: a boolean would swallow a newly published\n// announcement for the rest of that tab's life, and the feed is fetched once\n// per mount with no polling.\nconst ANNOUNCEMENT_PULSED_SESSION_KEY = \"cpk:inspector:pulsed\";\n\n// Roughly one year. No `Secure` — local development is served over plain\n// HTTP — and no `HttpOnly`, because the component reads the value from\n// script. `Path=/` and `SameSite=Lax` keep it host-wide and same-site only.\nconst ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;\n\n/**\n * The announcement timestamp the user has already read, or `null` when\n * nothing has been read yet. Prefers the host-scoped cookie and falls back to\n * the origin-scoped mirror, so cookie-blocking browsers still remember the\n * read within the port they are on.\n */\nexport function loadAnnouncementReadTimestamp(): string | null {\n return (\n parseTimestampPayload(readAnnouncementCookie()) ??\n parseTimestampPayload(readLocalStorageItem(ANNOUNCEMENT_READ_MIRROR_KEY))\n );\n}\n\n/**\n * Records an announcement as read in both the host-scoped cookie and the\n * origin-scoped mirror. The stored value is `{\"timestamp\":\"…\"}` — the same\n * shape the announcement state has always used.\n */\nexport function saveAnnouncementReadTimestamp(timestamp: string): void {\n const payload = JSON.stringify({ timestamp });\n writeAnnouncementCookie(payload);\n writeLocalStorageItem(ANNOUNCEMENT_READ_MIRROR_KEY, payload);\n}\n\n/**\n * Deletes the superseded origin-scoped read state. Safe to call on every\n * startup: nothing writes that key any more, so the value cannot come back.\n */\nexport function clearLegacyAnnouncementReadState(): void {\n removeLocalStorageItem(LEGACY_ANNOUNCEMENT_READ_KEY);\n}\n\n/**\n * The announcement timestamp this browser tab has already pulsed for, or\n * `null` when it hasn't pulsed yet.\n */\nexport function loadAnnouncementPulsedTimestamp(): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.sessionStorage.getItem(ANNOUNCEMENT_PULSED_SESSION_KEY);\n } catch {\n return null;\n }\n}\n\n/** Suppresses further pulses for this announcement in this browser tab. */\nexport function saveAnnouncementPulsedTimestamp(timestamp: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.sessionStorage.setItem(ANNOUNCEMENT_PULSED_SESSION_KEY, timestamp);\n } catch {\n // No-op — a lost suppression costs one extra pulse, never correctness.\n }\n}\n\nfunction parseTimestampPayload(raw: string | null): string | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as { timestamp?: unknown };\n return typeof parsed?.timestamp === \"string\" ? parsed.timestamp : null;\n } catch {\n return null;\n }\n}\n\nfunction readAnnouncementCookie(): string | null {\n return readCookie(ANNOUNCEMENT_READ_COOKIE_NAME);\n}\n\nfunction writeAnnouncementCookie(value: string): void {\n writeCookie(\n ANNOUNCEMENT_READ_COOKIE_NAME,\n value,\n `Max-Age=${ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS}`,\n );\n}\n\nfunction readCookie(name: string): string | null {\n if (typeof document === \"undefined\") return null;\n try {\n for (const entry of document.cookie.split(\";\")) {\n const separator = entry.indexOf(\"=\");\n if (separator === -1) continue;\n if (entry.slice(0, separator).trim() !== name) continue;\n return decodeURIComponent(entry.slice(separator + 1).trim());\n }\n } catch {\n // Cookie access throws in sandboxed documents. Callers fall back to their\n // origin-scoped mirror rather than breaking the host app.\n }\n return null;\n}\n\nfunction writeCookie(name: string, value: string, lifetime: string): void {\n if (typeof document === \"undefined\") return;\n try {\n // No Domain attribute: the cookie belongs to exactly this host, while\n // remaining shared by every port on that host.\n document.cookie = `${name}=${encodeURIComponent(\n value,\n )}; Path=/; ${lifetime}; SameSite=Lax`;\n } catch {\n // The localStorage mirror gives cookie-blocking browsers per-origin\n // persistence, and neither storage layer may break the host app.\n }\n}\n\nfunction readLocalStorageItem(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorageItem(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nfunction removeLocalStorageItem(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n // No-op.\n }\n}\n\n// Telemetry persistence — flat per-key localStorage rather than the\n// JSON-blob shape used for window/dock state, because each value is\n// independent and we want to read/write them without round-tripping\n// the whole inspector state object.\nconst TELEMETRY_DISTINCT_ID_KEY = \"cpk:inspector:telemetry:distinct_id\";\nconst TELEMETRY_OPT_OUT_KEY = \"cpk:inspector:telemetry:opt_out\";\nconst TELEMETRY_DISCLOSURE_SHOWN_KEY =\n \"cpk:inspector:telemetry:disclosure_shown\";\n\n// Module-level fallback for when localStorage is unavailable (private mode,\n// quota exceeded, etc.). Cached so that whats_new_viewed and\n// whats_new_clicked from the same page-load share one distinct_id even\n// without persistent storage —\n// funnel coherence within a session is preserved even when storage fails.\nlet inMemoryFallbackId: string | null = null;\n\nexport function getOrCreateTelemetryDistinctId(): string {\n if (typeof window === \"undefined\") {\n // SSR / test fallback. A non-persistent ID is preferable to throwing\n // because telemetry must never break the host application.\n return generateUuidV4();\n }\n\n try {\n const existing = window.localStorage.getItem(TELEMETRY_DISTINCT_ID_KEY);\n if (existing && existing.length > 0) return existing;\n const fresh = generateUuidV4();\n window.localStorage.setItem(TELEMETRY_DISTINCT_ID_KEY, fresh);\n return fresh;\n } catch {\n return (inMemoryFallbackId ??= generateUuidV4());\n }\n}\n\n// Test-only reset so the in-memory fallback doesn't leak between test cases.\nexport function _resetTelemetryPersistenceForTesting(): void {\n inMemoryFallbackId = null;\n}\n\nexport function isTelemetryOptedOut(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\nexport function setTelemetryOptOut(optedOut: boolean): void {\n if (typeof window === \"undefined\") return;\n try {\n if (optedOut) {\n window.localStorage.setItem(TELEMETRY_OPT_OUT_KEY, \"true\");\n } else {\n window.localStorage.removeItem(TELEMETRY_OPT_OUT_KEY);\n }\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nexport function hasTelemetryDisclosureBeenShown(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return (\n window.localStorage.getItem(TELEMETRY_DISCLOSURE_SHOWN_KEY) === \"true\"\n );\n } catch {\n return false;\n }\n}\n\nexport function markTelemetryDisclosureShown(): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(TELEMETRY_DISCLOSURE_SHOWN_KEY, \"true\");\n } catch {\n // No-op.\n }\n}\n\nfunction generateUuidV4(): string {\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n typeof globalThis.crypto.randomUUID === \"function\"\n ) {\n return globalThis.crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID (older browsers,\n // some test runners). Not cryptographically strong; acceptable because\n // the value is just an anonymous correlation ID.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"persistence.mjs","names":[],"sources":["../../src/lib/persistence.ts"],"sourcesContent":["import {\n emptyNotificationState,\n parseNotificationState,\n} from \"./notifications.js\";\nimport type { NotificationState, NotificationFeed } from \"./notifications.js\";\nimport type { Anchor, DockMode, Position, Size } from \"./types.js\";\n\nexport type PersistedContextState = {\n anchor?: Anchor;\n anchorOffset?: Position;\n size?: Size;\n hasCustomPosition?: boolean;\n};\n\nexport type PersistedState = {\n button?: Omit<PersistedContextState, \"size\">;\n window?: PersistedContextState;\n isOpen?: boolean;\n dockMode?: DockMode;\n selectedMenu?: string;\n selectedContext?: string;\n hasOpenedInspector?: boolean;\n sidebarCollapsed?: boolean;\n /** @deprecated Replaced by colorSchemePreference to distinguish a user choice from the old light default. */\n colorScheme?: \"light\" | \"dark\";\n colorSchemePreference?: \"light\" | \"dark\";\n};\n\nexport const HOME_NEWS_READ_STORAGE_KEY = \"cpk:inspector:home-news-read\";\n\n/** Return story ids the user has already opened or hovered on Home. */\nexport function loadHomeNewsReadIds(storageKey: string): string[] {\n if (typeof window === \"undefined\") {\n return [];\n }\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) {\n return [];\n }\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter((id): id is string => typeof id === \"string\");\n } catch {\n return [];\n }\n}\n\n/** Persist Home news story ids that the user has already seen. */\nexport function saveHomeNewsReadIds(storageKey: string, ids: string[]): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(ids));\n } catch (error) {\n console.warn(\"Failed to persist Home news read state\", error);\n }\n}\n\nexport function loadInspectorState(storageKey: string): PersistedState | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n const raw = window.localStorage.getItem(storageKey);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n // Fall through to cookie migration path\n }\n }\n\n // Backwards compatibility: try to read the legacy cookie and migrate it\n if (typeof document !== \"undefined\") {\n const prefix = `${storageKey}=`;\n const entry = document.cookie\n .split(\"; \")\n .find((cookie) => cookie.startsWith(prefix));\n if (entry) {\n const legacyRaw = entry.substring(prefix.length);\n try {\n const parsed = JSON.parse(decodeURIComponent(legacyRaw));\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n return null;\n }\n }\n }\n\n return null;\n}\n\nexport function saveInspectorState(\n storageKey: string,\n state: PersistedState,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to persist inspector state\", error);\n }\n}\n\nexport function isValidAnchor(value: unknown): value is Anchor {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Anchor;\n return (\n (candidate.horizontal === \"left\" || candidate.horizontal === \"right\") &&\n (candidate.vertical === \"top\" || candidate.vertical === \"bottom\")\n );\n}\n\nexport function isValidPosition(value: unknown): value is Position {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Position;\n return isFiniteNumber(candidate.x) && isFiniteNumber(candidate.y);\n}\n\nexport function isValidSize(value: unknown): value is Size {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Size;\n return isFiniteNumber(candidate.width) && isFiniteNumber(candidate.height);\n}\n\nexport function isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nexport function isValidDockMode(value: unknown): value is DockMode {\n return value === \"floating\" || value === \"docked-left\";\n}\n\n// Inspector dismissal is intentionally host-scoped rather than origin-scoped.\n// A developer who closes the Inspector on localhost:3000 should not see it\n// again on localhost:5173 during the same dismissal window. Cookies are shared\n// across ports on one host; localStorage is not.\nexport const INSPECTOR_DISMISSAL_MIRROR_KEY = \"cpk:inspector:dismissed_until\";\nexport const INSPECTOR_DISMISSAL_COOKIE_NAME = \"cpk_inspector_dismissed_until\";\nexport const INSPECTOR_DISMISSAL_MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1000;\n\ntype InspectorDismissalPayload = Readonly<{ until: number }>;\n\n/** Return the active host-scoped Inspector dismissal deadline, if any. */\nexport function loadInspectorDismissedUntil(\n now: number = Date.now(),\n): number | null {\n const until =\n parseInspectorDismissalPayload(\n readCookie(INSPECTOR_DISMISSAL_COOKIE_NAME),\n ) ??\n parseInspectorDismissalPayload(\n readLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY),\n );\n\n if (until === null) return null;\n if (until <= now) {\n clearInspectorDismissal();\n return null;\n }\n const maximumUntil = now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;\n if (until > maximumUntil) {\n saveInspectorDismissedUntil(maximumUntil, now);\n return maximumUntil;\n }\n return until;\n}\n\n/** Persist a dismissal across browser sessions and localhost ports. */\nexport function saveInspectorDismissedUntil(\n until: number,\n now: number = Date.now(),\n): void {\n if (!Number.isFinite(until) || until <= now) {\n clearInspectorDismissal();\n return;\n }\n\n const boundedUntil = Math.min(\n until,\n now + INSPECTOR_DISMISSAL_MAX_DURATION_MS,\n );\n const payload = JSON.stringify({\n until: boundedUntil,\n } satisfies InspectorDismissalPayload);\n const maxAgeSeconds = Math.max(1, Math.ceil((boundedUntil - now) / 1000));\n writeCookie(\n INSPECTOR_DISMISSAL_COOKIE_NAME,\n payload,\n `Max-Age=${maxAgeSeconds}`,\n );\n writeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY, payload);\n}\n\n/** Clear both persistence layers, including an expired host cookie. */\nexport function clearInspectorDismissal(): void {\n writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, \"\", \"Max-Age=0\");\n removeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY);\n}\n\n/** Parse a finite deadline from either persistence layer. */\nfunction parseInspectorDismissalPayload(raw: string | null): number | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<InspectorDismissalPayload>;\n return typeof parsed.until === \"number\" && Number.isFinite(parsed.until)\n ? parsed.until\n : null;\n } catch {\n return null;\n }\n}\n\n// Obsolete pre-cookie state; the cookie and its mirror are migrated below.\nconst LEGACY_ANNOUNCEMENT_READ_KEY = \"cpk:inspector:announcements\";\n\n// Pulse suppression is per browser tab, and stores the announcement timestamp\n// rather than a boolean: a boolean would swallow a newly published\n// announcement for the rest of that tab's life, and the feed is fetched once\n// per mount with no polling.\nconst ANNOUNCEMENT_PULSED_SESSION_KEY = \"cpk:inspector:pulsed\";\n\n/**\n * Deletes the superseded origin-scoped read state. Safe to call on every\n * startup: nothing writes that key any more, so the value cannot come back.\n */\nexport function clearLegacyAnnouncementReadState(): void {\n removeLocalStorageItem(LEGACY_ANNOUNCEMENT_READ_KEY);\n}\n\n/**\n * The announcement timestamp this browser tab has already pulsed for, or\n * `null` when it hasn't pulsed yet.\n */\nexport function loadAnnouncementPulsedTimestamp(): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.sessionStorage.getItem(ANNOUNCEMENT_PULSED_SESSION_KEY);\n } catch {\n return null;\n }\n}\n\nfunction readCookie(name: string): string | null {\n if (typeof document === \"undefined\") return null;\n try {\n for (const entry of document.cookie.split(\";\")) {\n const separator = entry.indexOf(\"=\");\n if (separator === -1) continue;\n if (entry.slice(0, separator).trim() !== name) continue;\n return decodeURIComponent(entry.slice(separator + 1).trim());\n }\n } catch {\n // Cookie access throws in sandboxed documents. Callers fall back to their\n // origin-scoped mirror rather than breaking the host app.\n }\n return null;\n}\n\nfunction writeCookie(name: string, value: string, lifetime: string): void {\n if (typeof document === \"undefined\") return;\n try {\n // No Domain attribute: the cookie belongs to exactly this host, while\n // remaining shared by every port on that host.\n document.cookie = `${name}=${encodeURIComponent(\n value,\n )}; Path=/; ${lifetime}; SameSite=Lax`;\n } catch {\n // The localStorage mirror gives cookie-blocking browsers per-origin\n // persistence, and neither storage layer may break the host app.\n }\n}\n\nfunction readLocalStorageItem(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorageItem(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nfunction removeLocalStorageItem(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n // No-op.\n }\n}\n\n// Telemetry persistence — flat per-key localStorage rather than the\n// JSON-blob shape used for window/dock state, because each value is\n// independent and we want to read/write them without round-tripping\n// the whole inspector state object.\nconst TELEMETRY_DISTINCT_ID_KEY = \"cpk:inspector:telemetry:distinct_id\";\nconst TELEMETRY_OPT_OUT_KEY = \"cpk:inspector:telemetry:opt_out\";\nconst TELEMETRY_DISCLOSURE_SHOWN_KEY =\n \"cpk:inspector:telemetry:disclosure_shown\";\n\n// Module-level fallback for when localStorage is unavailable (private mode,\n// quota exceeded, etc.). Cached so that whats_new_viewed and\n// whats_new_clicked from the same page-load share one distinct_id even\n// without persistent storage —\n// funnel coherence within a session is preserved even when storage fails.\nlet inMemoryFallbackId: string | null = null;\n\nexport function getOrCreateTelemetryDistinctId(): string {\n if (typeof window === \"undefined\") {\n // SSR / test fallback. A non-persistent ID is preferable to throwing\n // because telemetry must never break the host application.\n return generateUuidV4();\n }\n\n try {\n const existing = window.localStorage.getItem(TELEMETRY_DISTINCT_ID_KEY);\n if (existing && existing.length > 0) return existing;\n const fresh = generateUuidV4();\n window.localStorage.setItem(TELEMETRY_DISTINCT_ID_KEY, fresh);\n return fresh;\n } catch {\n return (inMemoryFallbackId ??= generateUuidV4());\n }\n}\n\n// Test-only reset so the in-memory fallback doesn't leak between test cases.\nexport function _resetTelemetryPersistenceForTesting(): void {\n inMemoryFallbackId = null;\n}\n\nexport function isTelemetryOptedOut(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\nexport function setTelemetryOptOut(optedOut: boolean): void {\n if (typeof window === \"undefined\") return;\n try {\n if (optedOut) {\n window.localStorage.setItem(TELEMETRY_OPT_OUT_KEY, \"true\");\n } else {\n window.localStorage.removeItem(TELEMETRY_OPT_OUT_KEY);\n }\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nexport function hasTelemetryDisclosureBeenShown(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return (\n window.localStorage.getItem(TELEMETRY_DISCLOSURE_SHOWN_KEY) === \"true\"\n );\n } catch {\n return false;\n }\n}\n\nexport function markTelemetryDisclosureShown(): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(TELEMETRY_DISCLOSURE_SHOWN_KEY, \"true\");\n } catch {\n // No-op.\n }\n}\n\nfunction generateUuidV4(): string {\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n typeof globalThis.crypto.randomUUID === \"function\"\n ) {\n return globalThis.crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID (older browsers,\n // some test runners). Not cryptographically strong; acceptable because\n // the value is just an anonymous correlation ID.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n\nconst NOTIFICATION_COOKIE = \"cpk_inspector_notifications_v1\";\nconst NOTIFICATION_STORAGE = \"cpk:inspector:notifications:v1\";\nconst NOTIFICATION_COOKIE_MAX_LENGTH = 1024;\nconst LEGACY_ANNOUNCEMENT_ID = \"16f7d877-49e3-41c3-9ca6-f951d3d8ba80\";\n\n/** Read host-wide acknowledgements from either the new compact cookie or an old full-state cookie. */\nfunction readNotificationAcknowledgements(): Pick<\n NotificationState,\n \"readIds\" | \"suppressedIds\"\n> {\n try {\n const raw = readCookie(NOTIFICATION_COOKIE);\n if (raw) {\n const value: unknown = JSON.parse(raw);\n if (value && typeof value === \"object\") {\n const state = parseNotificationState({\n ...emptyNotificationState(),\n ...value,\n });\n if (state)\n return { readIds: state.readIds, suppressedIds: state.suppressedIds };\n }\n }\n } catch {\n // A blocked or malformed cookie must not disrupt the Inspector.\n }\n return { readIds: [], suppressedIds: [] };\n}\n\n/** Load per-origin selection and merge host-wide read and suppressed notices. */\nexport function loadNotificationState(): NotificationState {\n const raw = readLocalStorageItem(NOTIFICATION_STORAGE);\n let localState = emptyNotificationState();\n if (raw) {\n try {\n localState = parseNotificationState(JSON.parse(raw)) ?? localState;\n } catch {\n // Keep the host acknowledgements when local storage is malformed.\n }\n }\n const host = readNotificationAcknowledgements();\n return {\n ...localState,\n readIds: [...new Set([...localState.readIds, ...host.readIds])],\n suppressedIds: [\n ...new Set([...localState.suppressedIds, ...host.suppressedIds]),\n ],\n };\n}\n\n/** Preserve a legacy acknowledgement only for the known announcement entering the new feed. */\nexport function migrateAnnouncementReadState(\n state: NotificationState,\n feed: NotificationFeed,\n): NotificationState {\n for (const raw of [\n readCookie(\"cpk_inspector_announcements\"),\n readLocalStorageItem(\"cpk:inspector:announcement_read\"),\n ]) {\n if (!raw) continue;\n try {\n const value: unknown = JSON.parse(raw);\n if (!value || typeof value !== \"object\" || !(\"timestamp\" in value))\n continue;\n const legacyNotice = feed.notifications.find(\n (notice) =>\n notice.id === LEGACY_ANNOUNCEMENT_ID &&\n notice.publishedAt === value.timestamp,\n );\n if (!legacyNotice) continue;\n const migrated = {\n ...state,\n readIds: [...new Set([...state.readIds, legacyNotice.id])],\n suppressedIds: [...new Set([...state.suppressedIds, legacyNotice.id])],\n };\n saveNotificationState(migrated);\n writeCookie(\"cpk_inspector_announcements\", \"\", \"Max-Age=0\");\n removeLocalStorageItem(\"cpk:inspector:announcement_read\");\n return migrated;\n } catch {\n // Malformed legacy data must not disrupt the host app.\n }\n }\n return state;\n}\n\n/** Save full state per origin and a bounded host-wide acknowledgement cookie. */\nexport function saveNotificationState(state: NotificationState): void {\n const host = readNotificationAcknowledgements();\n const merged = {\n ...state,\n readIds: [...new Set([...state.readIds, ...host.readIds])],\n suppressedIds: [\n ...new Set([...state.suppressedIds, ...host.suppressedIds]),\n ],\n };\n writeLocalStorageItem(NOTIFICATION_STORAGE, JSON.stringify(merged));\n const cookieState = {\n schemaVersion: 1,\n readIds: [...merged.readIds],\n suppressedIds: [...merged.suppressedIds],\n };\n let raw = JSON.stringify(cookieState);\n // Limit bytes sent with every localhost request. Full history stays per\n // origin when the host cookie keeps only the newest acknowledgements.\n while (encodeURIComponent(raw).length >= NOTIFICATION_COOKIE_MAX_LENGTH) {\n if (cookieState.readIds.length >= cookieState.suppressedIds.length)\n cookieState.readIds.shift();\n else cookieState.suppressedIds.shift();\n raw = JSON.stringify(cookieState);\n }\n writeCookie(NOTIFICATION_COOKIE, raw, \"Max-Age=31536000\");\n}\n\n/** ID-based pulse state is separate from the legacy announcement timestamp. */\nconst NOTIFICATION_PULSED_SESSION_KEY = \"cpk:inspector:notification-pulsed-id\";\nconst MAX_PULSED_NOTIFICATION_IDS = 100;\n\nfunction pulsedNotificationIds(raw: string | null): string[] {\n if (!raw) return [];\n // Earlier previews stored one ID directly under this key.\n if (!raw.startsWith(\"[\")) return [raw];\n try {\n const ids: unknown = JSON.parse(raw);\n return Array.isArray(ids)\n ? ids.filter((id): id is string => typeof id === \"string\")\n : [];\n } catch {\n return [];\n }\n}\n\nexport function saveNotificationPulsedId(id: string): void {\n if (typeof window === \"undefined\") return;\n try {\n const ids = pulsedNotificationIds(\n window.sessionStorage.getItem(NOTIFICATION_PULSED_SESSION_KEY),\n );\n if (!ids.includes(id)) ids.push(id);\n window.sessionStorage.setItem(\n NOTIFICATION_PULSED_SESSION_KEY,\n JSON.stringify(ids.slice(-MAX_PULSED_NOTIFICATION_IDS)),\n );\n } catch {\n /* A lost suppression must not disrupt the host. */\n }\n}\nexport function hasNotificationPulsed(\n id: string,\n publishedAt: string,\n): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n const current = window.sessionStorage.getItem(\n NOTIFICATION_PULSED_SESSION_KEY,\n );\n if (current !== null) return pulsedNotificationIds(current).includes(id);\n if (loadAnnouncementPulsedTimestamp() === publishedAt) {\n saveNotificationPulsedId(id);\n return true;\n }\n } catch {\n /* Treat unavailable storage as a fresh tab. */\n }\n return false;\n}\n"],"mappings":"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"}
|
package/dist/lib/telemetry.cjs
CHANGED
|
@@ -8,6 +8,14 @@ const TELEMETRY_EVENTS = {
|
|
|
8
8
|
whatsNewSignalViewed: "oss.inspector.whats_new_signal_viewed",
|
|
9
9
|
errorSignalViewed: "oss.inspector.error_signal_viewed",
|
|
10
10
|
whatsNewClicked: "oss.inspector.whats_new_clicked",
|
|
11
|
+
hudViewed: "oss.inspector.hud_viewed",
|
|
12
|
+
hudNotificationViewed: "oss.inspector.hud_notification_viewed",
|
|
13
|
+
hudNotificationClicked: "oss.inspector.hud_notification_clicked",
|
|
14
|
+
hudFeatureToggleViewed: "oss.inspector.hud_feature_toggle_viewed",
|
|
15
|
+
hudFeatureToggleClicked: "oss.inspector.hud_feature_toggle_clicked",
|
|
16
|
+
hudFeatureClicked: "oss.inspector.hud_feature_clicked",
|
|
17
|
+
hudHideViewed: "oss.inspector.hud_hide_viewed",
|
|
18
|
+
hudHideClicked: "oss.inspector.hud_hide_clicked",
|
|
11
19
|
threadsTabClicked: "oss.inspector.threads_tab_clicked",
|
|
12
20
|
threadsTryFromHereClicked: "oss.inspector.threads_try_from_here_clicked",
|
|
13
21
|
threadsLockedViewed: "oss.inspector.threads_locked_viewed",
|
|
@@ -99,6 +107,32 @@ function trackWhatsNewViewed(props) {
|
|
|
99
107
|
function trackWhatsNewSignalViewed(props) {
|
|
100
108
|
track(TELEMETRY_EVENTS.whatsNewSignalViewed, props);
|
|
101
109
|
}
|
|
110
|
+
/** One impression per rendered HUD presentation in a visible tab. */
|
|
111
|
+
function trackHudViewed(props) {
|
|
112
|
+
track(TELEMETRY_EVENTS.hudViewed, props);
|
|
113
|
+
}
|
|
114
|
+
/** The UUID identifies the notification consistently across HUD and article events. */
|
|
115
|
+
function trackHudNotificationViewed(props) {
|
|
116
|
+
track(TELEMETRY_EVENTS.hudNotificationViewed, props);
|
|
117
|
+
}
|
|
118
|
+
function trackHudNotificationClicked(props) {
|
|
119
|
+
track(TELEMETRY_EVENTS.hudNotificationClicked, props);
|
|
120
|
+
}
|
|
121
|
+
function trackHudFeatureToggleViewed(props) {
|
|
122
|
+
track(TELEMETRY_EVENTS.hudFeatureToggleViewed, props);
|
|
123
|
+
}
|
|
124
|
+
function trackHudFeatureToggleClicked(props) {
|
|
125
|
+
track(TELEMETRY_EVENTS.hudFeatureToggleClicked, props);
|
|
126
|
+
}
|
|
127
|
+
function trackHudFeatureClicked(props) {
|
|
128
|
+
track(TELEMETRY_EVENTS.hudFeatureClicked, props);
|
|
129
|
+
}
|
|
130
|
+
function trackHudHideViewed(props) {
|
|
131
|
+
track(TELEMETRY_EVENTS.hudHideViewed, props);
|
|
132
|
+
}
|
|
133
|
+
function trackHudHideClicked(props) {
|
|
134
|
+
track(TELEMETRY_EVENTS.hudHideClicked, props);
|
|
135
|
+
}
|
|
102
136
|
/**
|
|
103
137
|
* Fires when the launcher's error signal becomes *visible* — not when it arms.
|
|
104
138
|
* Arming can happen with the panel open or the tab hidden, where there is no
|
|
@@ -442,6 +476,14 @@ exports.trackHomeFeaturePromptClicked = trackHomeFeaturePromptClicked;
|
|
|
442
476
|
exports.trackHomePromptCopied = trackHomePromptCopied;
|
|
443
477
|
exports.trackHomeStoryBeatSelected = trackHomeStoryBeatSelected;
|
|
444
478
|
exports.trackHomeViewed = trackHomeViewed;
|
|
479
|
+
exports.trackHudFeatureClicked = trackHudFeatureClicked;
|
|
480
|
+
exports.trackHudFeatureToggleClicked = trackHudFeatureToggleClicked;
|
|
481
|
+
exports.trackHudFeatureToggleViewed = trackHudFeatureToggleViewed;
|
|
482
|
+
exports.trackHudHideClicked = trackHudHideClicked;
|
|
483
|
+
exports.trackHudHideViewed = trackHudHideViewed;
|
|
484
|
+
exports.trackHudNotificationClicked = trackHudNotificationClicked;
|
|
485
|
+
exports.trackHudNotificationViewed = trackHudNotificationViewed;
|
|
486
|
+
exports.trackHudViewed = trackHudViewed;
|
|
445
487
|
exports.trackInspectorOpened = trackInspectorOpened;
|
|
446
488
|
exports.trackLearningEvidenceOpened = trackLearningEvidenceOpened;
|
|
447
489
|
exports.trackLearningPageChanged = trackLearningPageChanged;
|
|
@@ -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// What's new and thread-inspection interactions. POSTs directly from the\n// browser to the CopilotKit telemetry sink at\n// `telemetry.copilotkit.ai/ingest`, where a Lambda fan-out forwards events to\n// 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 announcement markdown. Feature-specific properties are scoped to\n// event metadata only (banner_id/timestamp, cta location). Reviewers\n// should grep call 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 whatsNewViewed: \"oss.inspector.whats_new_viewed\",\n whatsNewSignalViewed: \"oss.inspector.whats_new_signal_viewed\",\n errorSignalViewed: \"oss.inspector.error_signal_viewed\",\n whatsNewClicked: \"oss.inspector.whats_new_clicked\",\n threadsTabClicked: \"oss.inspector.threads_tab_clicked\",\n threadsTryFromHereClicked: \"oss.inspector.threads_try_from_here_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 learningPaneViewed: \"oss.inspector.learning_pane_viewed\",\n learningSetupPromptClicked: \"oss.inspector.learning_setup_prompt_clicked\",\n learningSnapshotLoaded: \"oss.inspector.learning_snapshot_loaded\",\n learningSkillToggled: \"oss.inspector.learning_skill_toggled\",\n learningEvidenceOpened: \"oss.inspector.learning_evidence_opened\",\n learningPageChanged: \"oss.inspector.learning_page_changed\",\n learningWebAppOpened: \"oss.inspector.learning_web_app_opened\",\n homeViewed: \"oss.inspector.home_viewed\",\n homeCtaClicked: \"oss.inspector.home_cta_clicked\",\n homeFeaturePromptClicked: \"oss.inspector.home_feature_prompt_clicked\",\n // Carries the CLI's own `onboarding_run_id`, which is the whole point: it is\n // the first event that can be joined to `cli.onboarding.completed` on the\n // Intelligence side. `home_cta_clicked` only ever proved someone clicked a\n // link, never that an install followed.\n homePromptCopied: \"oss.inspector.home_prompt_copied\",\n // Which step of the Intelligence story a developer opened by hand. Carries\n // the beat as a property rather than splitting into one event per label,\n // because the labels are expected to change as the story is iterated and a\n // per-label event would retire with them.\n homeStoryBeatSelected: \"oss.inspector.home_story_beat_selected\",\n metadataModuleViewed: \"oss.inspector.metadata_module_viewed\",\n metadataActionClicked: \"oss.inspector.metadata_action_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 live shell-docs telemetry page\n// (`showcase/shell-docs/src/content/docs/integrations/built-in-agent/telemetry.mdx`).\n// Mirror constant: packages/runtime/src/v1-deprecated/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\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 try {\n const distinctId = getOrCreateTelemetryDistinctId();\n // Every event carries package identity and the inspector's anonymous\n // distinct-ID, so any of them can be segmented by inspector version.\n const body = JSON.stringify({\n event,\n properties: {\n ...properties,\n package_name: PACKAGE_NAME,\n package_version: PACKAGE_VERSION,\n inspector_distinct_id: distinctId,\n distinct_id: distinctId,\n },\n package: {\n name: PACKAGE_NAME,\n version: PACKAGE_VERSION,\n },\n ts: Math.floor(Date.now() / 1000),\n });\n void postBestEffort(TELEMETRY_INGEST_URL, body, distinctId);\n } catch {\n // Identity and serialization failures are best-effort too.\n }\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. What's new is the\n * only surface that carries one, so the value is currently a constant — it\n * stays a stamped property rather than an inferred one so a second surface\n * can be added without changing the event's shape.\n */\nexport type WhatsNewSurface = \"whats_new\";\n\n/**\n * Whether a launcher signal was animated, or held static because the reader\n * asked for reduced motion. Shared by every launcher signal so an\n * accessibility setting is never mistaken for disinterest.\n */\nexport type LauncherSignalPresentation = \"animated\" | \"reduced_motion\";\n\nexport type WhatsNewSignalPresentation = LauncherSignalPresentation;\n\n/**\n * Which failure class raised the launcher's error signal. A closed enum: the\n * failure *message* is never transmitted, so prompts, URLs and identifiers\n * embedded in an error cannot leave the browser.\n *\n * `connection` and `threads` are wiring *state*. `run`, `tool` and `memory`\n * are unread *events* that clear when their landing view is read.\n */\nexport type InspectorWiringErrorSource = \"connection\" | \"threads\";\nexport type InspectorEventErrorSource = \"run\" | \"tool\" | \"memory\";\nexport type InspectorErrorSignalSource =\n | InspectorWiringErrorSource\n | InspectorEventErrorSource;\n\n/**\n * Fires when What's new has rendered *with content*. A loading state is not\n * an impression, so the metric cannot inflate itself by counting readers who\n * arrived before the feed resolved.\n */\nexport function trackWhatsNewViewed(props: {\n banner_id: string;\n surface: WhatsNewSurface;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.whatsNewViewed, props);\n}\n\n/** Fires when the unread launcher signal is presented in a visible tab. */\nexport function trackWhatsNewSignalViewed(props: {\n banner_id: string;\n surface: \"launcher\";\n presentation: WhatsNewSignalPresentation;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.whatsNewSignalViewed, props);\n}\n\n/**\n * Whether the launcher opened its pill for this outage, or suppressed it.\n *\n * `suppressed` means no pill was shown. In practice that is the no-room\n * fallback — neither side of the launcher had space for the label — because\n * every other path to a visible error signal opens one.\n */\nexport type InspectorErrorSignalLabel = \"shown\" | \"suppressed\";\n\n/**\n * Fires when the launcher's error signal becomes *visible* — not when it arms.\n * Arming can happen with the panel open or the tab hidden, where there is no\n * launcher to look at, so counting arms would inflate the denominator this\n * event exists to provide.\n *\n * Deliberately carries three fixed enum values and nothing else. This is the\n * one place a later change could casually attach a free-text field, and the\n * failure message must never be transmitted.\n *\n * `label` is deliberately not a new event: the catalogue's size is asserted\n * and spelled out in a test title, and a property answers the one open\n * question — how often the no-room fallback fires — without touching either.\n *\n * The pill's own marginal effect cannot be measured here and must not be\n * reverse-engineered from this data: it ships together with the dot and the\n * beat, so there is no period with one and not the other.\n */\nexport function trackErrorSignalViewed(props: {\n source: InspectorErrorSignalSource;\n presentation: LauncherSignalPresentation;\n label: InspectorErrorSignalLabel;\n}): void {\n track(TELEMETRY_EVENTS.errorSignalViewed, {\n source: props.source,\n presentation: props.presentation,\n label: props.label,\n });\n}\n\n/**\n * Fires for links followed inside the announcement body. `body` is the only\n * call-to-action left: dismissal no longer exists, so there is nothing else a\n * click on this surface can mean.\n */\nexport function trackWhatsNewClicked(props: {\n banner_id: string;\n cta: \"body\";\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.whatsNewClicked, 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\" | \"message_toolbar\";\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 /** True when an error signal was on the launcher at open time. */\n has_error_signal?: boolean;\n /** Which failure class was signalling at open time, when one was. */\n error_signal_source?: InspectorErrorSignalSource;\n /** True when this is the first Inspector open after install or upgrade. */\n first_open?: 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 the announcement's own\n * click event (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 ExampleKind = \"realtime_sync\" | \"manage_history\" | \"inspect_runs\";\nexport type ThreadsUsageBucket =\n | \"absent\"\n | \"empty\"\n | \"within_limit\"\n | \"at_or_over_limit\"\n | \"unlimited\"\n | \"unknown_limit\";\nexport type ThreadsExpiryBucket = \"unavailable\" | \"zero\" | \"positive\";\nexport type InspectorGroupKey = \"home\" | \"workbench\" | \"inspect\";\nexport type InspectorLeafKey =\n | \"home\"\n | \"whats-new\"\n | \"playground\"\n | \"threads\"\n | \"ag-ui-events\"\n | \"agents\"\n | \"frontend-tools\"\n | \"capabilities\"\n | \"agent-context\"\n | \"memories\";\nexport type MetadataActionPlacement = \"threads_footer\" | \"threads_locked\";\nexport type ExampleTourStep = 1 | 2 | 3;\nexport type ExampleTourTab = \"timeline\" | \"raw-events\" | \"state\";\n\nexport type InspectorThreadTelemetryProps = Readonly<{\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 | \"sidebar_footer\";\n cta?: \"signup\" | \"talk_to_engineer\";\n telemetry_disabled?: boolean;\n has_threads?: boolean;\n usage_bucket?: ThreadsUsageBucket;\n expiry_bucket?: ThreadsExpiryBucket;\n group_key?: InspectorGroupKey;\n leaf_key?: InspectorLeafKey;\n example_kind?: ExampleKind;\n tour_step?: ExampleTourStep;\n tour_tab?: ExampleTourTab;\n dismiss_method?: \"skip\" | \"done\";\n outcome?: \"success\" | \"failure\";\n}>;\n\n/** Rebuild the common Thread payload from its closed coarse allowlist. */\nfunction threadCommonProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...(props.intelligence_status === undefined\n ? {}\n : { intelligence_status: props.intelligence_status }),\n ...(props.thread_service_status === undefined\n ? {}\n : { thread_service_status: props.thread_service_status }),\n ...(props.license_status === undefined\n ? {}\n : { license_status: props.license_status }),\n ...(props.runtime_mode === undefined\n ? {}\n : { runtime_mode: props.runtime_mode }),\n ...(props.runtime_url_type === undefined\n ? {}\n : { runtime_url_type: props.runtime_url_type }),\n ...(props.telemetry_disabled === undefined\n ? {}\n : { telemetry_disabled: props.telemetry_disabled }),\n ...(props.has_threads === undefined\n ? {}\n : { has_threads: props.has_threads }),\n ...(props.usage_bucket === undefined\n ? {}\n : { usage_bucket: props.usage_bucket }),\n ...(props.expiry_bucket === undefined\n ? {}\n : { expiry_bucket: props.expiry_bucket }),\n ...(props.group_key === undefined ? {} : { group_key: props.group_key }),\n ...(props.leaf_key === undefined ? {} : { leaf_key: props.leaf_key }),\n };\n}\n\n/** Add only the fields used by the three Thread CTA funnels. */\nfunction threadCtaProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...threadCommonProperties(props),\n ...(props.cta === undefined ? {} : { cta: props.cta }),\n ...(props.cta_surface === undefined\n ? {}\n : { cta_surface: props.cta_surface }),\n ...(props.posthog_distinct_id === undefined\n ? {}\n : { posthog_distinct_id: props.posthog_distinct_id }),\n };\n}\n\n/** Add only the closed example kind used by example impressions/selections. */\nfunction threadExampleProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...threadCommonProperties(props),\n ...(props.example_kind === undefined\n ? {}\n : { example_kind: props.example_kind }),\n };\n}\n\n/** Add only a bounded example kind and tour step/tab pair. */\nfunction threadTourProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...threadExampleProperties(props),\n ...(props.tour_step === undefined ? {} : { tour_step: props.tour_step }),\n ...(props.tour_tab === undefined ? {} : { tour_tab: props.tour_tab }),\n };\n}\n\n/** Add the bounded dismissal leaf used only by terminal tour events. */\nfunction threadTourTerminalProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...threadTourProperties(props),\n ...(props.dismiss_method === undefined\n ? {}\n : { dismiss_method: props.dismiss_method }),\n };\n}\n\nexport function trackThreadsTabClicked(\n props: InspectorThreadTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.threadsTabClicked, threadCommonProperties(props));\n}\n\nexport function trackThreadsTryFromHereClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsTryFromHereClicked, {\n ...threadCommonProperties(props),\n ...(props.outcome === undefined ? {} : { outcome: props.outcome }),\n });\n}\n\nexport function trackThreadsLockedViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsLockedViewed, threadCommonProperties(props));\n}\n\nexport function trackThreadsIntelligenceSignupClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsIntelligenceSignupClicked,\n threadCtaProperties(props),\n );\n}\n\nexport function trackThreadsTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsTalkToEngineerClicked,\n threadCtaProperties(props),\n );\n}\n\nexport function trackTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.talkToEngineerClicked, threadCtaProperties(props));\n}\n\nexport function trackThreadsEmptyEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsEmptyEnabledViewed,\n threadCommonProperties(props),\n );\n}\n\nexport function trackThreadsEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEnabledViewed, threadCommonProperties(props));\n}\n\nexport function trackThreadsExampleViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleViewed, threadExampleProperties(props));\n}\n\nexport function trackThreadsExampleSelected(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleSelected,\n threadExampleProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourStarted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourStarted,\n threadTourProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourStepViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourStepViewed,\n threadTourProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourDismissed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourDismissed,\n threadTourTerminalProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourCompleted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourCompleted,\n threadTourTerminalProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourReopened(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourReopened,\n threadTourProperties(props),\n );\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\nexport type InspectorLearningViewState =\n | \"loading\"\n | \"error\"\n | \"selection_required\"\n | \"invalid\"\n | \"results\"\n | \"first_run\"\n | \"ready\"\n | \"empty\"\n | \"setup\"\n | \"landing\";\nexport type InspectorLearningCountBucket =\n | \"zero\"\n | \"one\"\n | \"two_to_five\"\n | \"six_to_twenty\"\n | \"twenty_one_plus\";\nexport type InspectorLearningDurationBucket =\n | \"under_250ms\"\n | \"250ms_to_1s\"\n | \"1s_to_3s\"\n | \"3s_plus\";\n\nexport function learningCountBucket(\n value: number,\n): InspectorLearningCountBucket {\n if (value <= 0) return \"zero\";\n if (value === 1) return \"one\";\n if (value <= 5) return \"two_to_five\";\n if (value <= 20) return \"six_to_twenty\";\n return \"twenty_one_plus\";\n}\n\nexport function learningDurationBucket(\n durationMs: number,\n): InspectorLearningDurationBucket {\n if (durationMs < 250) return \"under_250ms\";\n if (durationMs < 1_000) return \"250ms_to_1s\";\n if (durationMs < 3_000) return \"1s_to_3s\";\n return \"3s_plus\";\n}\n\nexport function trackLearningPaneViewed(props: {\n state: InspectorLearningViewState;\n}): void {\n track(TELEMETRY_EVENTS.learningPaneViewed, props);\n}\n\nexport function trackLearningSetupPromptClicked(props: {\n outcome: \"success\" | \"failure\";\n}): void {\n track(TELEMETRY_EVENTS.learningSetupPromptClicked, props);\n}\n\nexport function trackLearningSnapshotLoaded(props: {\n outcome: \"success\" | \"unsupported\" | \"failure\";\n duration_bucket: InspectorLearningDurationBucket;\n skills_bucket: InspectorLearningCountBucket;\n insights_bucket: InspectorLearningCountBucket;\n pending_threads_bucket: InspectorLearningCountBucket;\n}): void {\n track(TELEMETRY_EVENTS.learningSnapshotLoaded, props);\n}\n\nexport function trackLearningSkillToggled(props: {\n action: \"expanded\" | \"collapsed\";\n}): void {\n track(TELEMETRY_EVENTS.learningSkillToggled, props);\n}\n\nexport function trackLearningEvidenceOpened(): void {\n track(TELEMETRY_EVENTS.learningEvidenceOpened);\n}\n\nexport function trackLearningPageChanged(props: {\n section: \"skills\" | \"insights\";\n direction: \"previous\" | \"next\";\n}): void {\n track(TELEMETRY_EVENTS.learningPageChanged, props);\n}\n\nexport function trackLearningWebAppOpened(props: {\n category: \"learning\" | \"runs\" | \"candidates\";\n}): void {\n track(TELEMETRY_EVENTS.learningWebAppOpened, props);\n}\n\nexport type InspectorMetadataTelemetryModule = \"identity\" | \"plan\" | \"action\";\nexport type InspectorMetadataLicenseBucket =\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"unknown\";\nexport type InspectorMetadataActionKind =\n | \"manage_plan\"\n | \"renew\"\n | \"enable_intelligence\";\n\nexport type InspectorHomeTelemetryProps = Readonly<{\n action_kind?: InspectorMetadataActionKind;\n group_key?: InspectorGroupKey;\n leaf_key?: InspectorLeafKey;\n}>;\n\nexport function trackHomeViewed(props: InspectorHomeTelemetryProps = {}): void {\n track(TELEMETRY_EVENTS.homeViewed, {\n group_key: props.group_key ?? \"home\",\n leaf_key: props.leaf_key ?? \"home\",\n });\n}\n\nexport function trackHomeCtaClicked(props: InspectorHomeTelemetryProps): void {\n track(TELEMETRY_EVENTS.homeCtaClicked, {\n action_kind: props.action_kind,\n group_key: props.group_key ?? \"home\",\n leaf_key: props.leaf_key ?? \"home\",\n });\n}\n\n/**\n * Correlates a copied feature setup prompt with its onboarding run. The run ID\n * is generated locally for this click, not derived from account or runtime\n * data, so it can be safely joined with the onboarding flow downstream.\n */\nexport function trackHomeFeaturePromptClicked(props: {\n feature_id: string;\n onboarding_run_id: string;\n}): void {\n track(TELEMETRY_EVENTS.homeFeaturePromptClicked, {\n feature_id: props.feature_id,\n onboarding_run_id: props.onboarding_run_id,\n group_key: \"home\",\n leaf_key: \"home\",\n });\n}\n\nexport type InspectorHomePromptCopiedTelemetryProps = Readonly<{\n /** The id minted for this session and substituted into the copied prompt. */\n onboarding_run_id: string;\n /** Whether the clipboard write actually landed. */\n outcome: \"copied\" | \"failed\";\n}>;\n\n/**\n * Report a copy of the Intelligence install prompt.\n *\n * `outcome` is reported rather than only emitting on success, because a\n * clipboard that refuses is indistinguishable from a developer who never\n * pressed the button — and the two call for opposite fixes.\n */\nexport function trackHomePromptCopied(\n props: InspectorHomePromptCopiedTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.homePromptCopied, {\n onboarding_run_id: props.onboarding_run_id,\n outcome: props.outcome,\n group_key: \"home\",\n leaf_key: \"home\",\n });\n}\n\nexport type InspectorHomeStoryBeatTelemetryProps = Readonly<{\n /** Stable id of the step, e.g. \"threads\". Survives a label rename. */\n beat: string;\n /** Its position in the rail, so a reorder can be evaluated against clicks. */\n beat_index: number;\n}>;\n\n/**\n * Report a step of the Intelligence story that a developer opened themselves.\n *\n * Only a press reports. The story also advances on its own every few seconds,\n * and reporting that would bury the handful of real interactions under a\n * metronome — one event per idle developer per six seconds, none of it intent.\n */\nexport function trackHomeStoryBeatSelected(\n props: InspectorHomeStoryBeatTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.homeStoryBeatSelected, {\n beat: props.beat,\n beat_index: props.beat_index,\n group_key: \"home\",\n leaf_key: \"home\",\n });\n}\n\nexport type InspectorMetadataModuleViewedTelemetryProps = Readonly<{\n module: InspectorMetadataTelemetryModule;\n license_bucket: InspectorMetadataLicenseBucket;\n action_kind?: InspectorMetadataActionKind;\n usage_bucket: ThreadsUsageBucket;\n expiry_bucket: ThreadsExpiryBucket;\n group_key?: InspectorGroupKey;\n leaf_key?: InspectorLeafKey;\n action_placement?: MetadataActionPlacement;\n}>;\n\nexport type InspectorMetadataActionClickedTelemetryProps = Readonly<{\n action_kind: Exclude<InspectorMetadataActionKind, \"enable_intelligence\">;\n license_bucket: InspectorMetadataLicenseBucket;\n usage_bucket: ThreadsUsageBucket;\n expiry_bucket: ThreadsExpiryBucket;\n group_key?: InspectorGroupKey;\n leaf_key?: InspectorLeafKey;\n action_placement?: MetadataActionPlacement;\n}>;\n\nfunction metadataCoarseProperties(\n props:\n | InspectorMetadataModuleViewedTelemetryProps\n | InspectorMetadataActionClickedTelemetryProps,\n) {\n const navigation =\n props.group_key === undefined || props.leaf_key === undefined\n ? {}\n : { group_key: props.group_key, leaf_key: props.leaf_key };\n return {\n license_bucket: props.license_bucket,\n usage_bucket: props.usage_bucket,\n expiry_bucket: props.expiry_bucket,\n ...navigation,\n ...(props.action_placement === undefined\n ? {}\n : { action_placement: props.action_placement }),\n };\n}\n\n/**\n * Tracks one visible Inspector metadata module using coarse fields only.\n * Rebuilding the payload here prevents local labels, IDs, URLs, and usage\n * values from crossing the telemetry boundary through extra object fields.\n */\nexport function trackMetadataModuleViewed(\n props: InspectorMetadataModuleViewedTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.metadataModuleViewed, {\n module: props.module,\n ...(props.action_kind === undefined\n ? {}\n : { action_kind: props.action_kind }),\n ...metadataCoarseProperties(props),\n });\n}\n\n/**\n * Tracks clicks for managed plan and renewal actions. The existing\n * Intelligence-enable funnel keeps its original event and never calls this\n * helper, which prevents a single click from producing two wire events.\n */\nexport function trackMetadataActionClicked(\n props: InspectorMetadataActionClickedTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.metadataActionClicked, {\n module: \"action\",\n action_kind: props.action_kind,\n ...metadataCoarseProperties(props),\n });\n}\n\n/**\n * Returns the inspector's anonymous distinct-ID for cross-domain\n * propagation onto outbound announcement-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 * `whats_new_viewed → whats_new_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 announcement-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 let timeoutId: ReturnType<typeof setTimeout> | undefined;\n try {\n const controller = new AbortController();\n timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\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 if (timeoutId !== undefined) 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// the launcher HUD, What's new, and thread-inspection interactions. POSTs directly from the\n// browser to the CopilotKit telemetry sink at\n// `telemetry.copilotkit.ai/ingest`, where a Lambda fan-out forwards events to\n// 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 announcement markdown. Feature-specific properties are scoped to\n// event metadata only (notification UUID, cta location). Reviewers\n// should grep call 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 whatsNewViewed: \"oss.inspector.whats_new_viewed\",\n whatsNewSignalViewed: \"oss.inspector.whats_new_signal_viewed\",\n errorSignalViewed: \"oss.inspector.error_signal_viewed\",\n whatsNewClicked: \"oss.inspector.whats_new_clicked\",\n hudViewed: \"oss.inspector.hud_viewed\",\n hudNotificationViewed: \"oss.inspector.hud_notification_viewed\",\n hudNotificationClicked: \"oss.inspector.hud_notification_clicked\",\n hudFeatureToggleViewed: \"oss.inspector.hud_feature_toggle_viewed\",\n hudFeatureToggleClicked: \"oss.inspector.hud_feature_toggle_clicked\",\n hudFeatureClicked: \"oss.inspector.hud_feature_clicked\",\n hudHideViewed: \"oss.inspector.hud_hide_viewed\",\n hudHideClicked: \"oss.inspector.hud_hide_clicked\",\n threadsTabClicked: \"oss.inspector.threads_tab_clicked\",\n threadsTryFromHereClicked: \"oss.inspector.threads_try_from_here_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 learningPaneViewed: \"oss.inspector.learning_pane_viewed\",\n learningSetupPromptClicked: \"oss.inspector.learning_setup_prompt_clicked\",\n learningSnapshotLoaded: \"oss.inspector.learning_snapshot_loaded\",\n learningSkillToggled: \"oss.inspector.learning_skill_toggled\",\n learningEvidenceOpened: \"oss.inspector.learning_evidence_opened\",\n learningPageChanged: \"oss.inspector.learning_page_changed\",\n learningWebAppOpened: \"oss.inspector.learning_web_app_opened\",\n homeViewed: \"oss.inspector.home_viewed\",\n homeCtaClicked: \"oss.inspector.home_cta_clicked\",\n homeFeaturePromptClicked: \"oss.inspector.home_feature_prompt_clicked\",\n // Carries the CLI's own `onboarding_run_id`, which is the whole point: it is\n // the first event that can be joined to `cli.onboarding.completed` on the\n // Intelligence side. `home_cta_clicked` only ever proved someone clicked a\n // link, never that an install followed.\n homePromptCopied: \"oss.inspector.home_prompt_copied\",\n // Which step of the Intelligence story a developer opened by hand. Carries\n // the beat as a property rather than splitting into one event per label,\n // because the labels are expected to change as the story is iterated and a\n // per-label event would retire with them.\n homeStoryBeatSelected: \"oss.inspector.home_story_beat_selected\",\n metadataModuleViewed: \"oss.inspector.metadata_module_viewed\",\n metadataActionClicked: \"oss.inspector.metadata_action_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 live shell-docs telemetry page\n// (`showcase/shell-docs/src/content/docs/integrations/built-in-agent/telemetry.mdx`).\n// Mirror constant: packages/runtime/src/v1-deprecated/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\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 try {\n const distinctId = getOrCreateTelemetryDistinctId();\n // Every event carries package identity and the inspector's anonymous\n // distinct-ID, so any of them can be segmented by inspector version.\n const body = JSON.stringify({\n event,\n properties: {\n ...properties,\n package_name: PACKAGE_NAME,\n package_version: PACKAGE_VERSION,\n inspector_distinct_id: distinctId,\n distinct_id: distinctId,\n },\n package: {\n name: PACKAGE_NAME,\n version: PACKAGE_VERSION,\n },\n ts: Math.floor(Date.now() / 1000),\n });\n void postBestEffort(TELEMETRY_INGEST_URL, body, distinctId);\n } catch {\n // Identity and serialization failures are best-effort too.\n }\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. What's new is the\n * only surface of `whats_new_viewed`. The HUD has its own notification event,\n * so its impressions do not change the meaning of this event.\n */\nexport type WhatsNewSurface = \"whats_new\";\n\n/**\n * Whether a launcher signal was animated, or held static because the reader\n * asked for reduced motion. Shared by every launcher signal so an\n * accessibility setting is never mistaken for disinterest.\n */\nexport type LauncherSignalPresentation = \"animated\" | \"reduced_motion\";\n\nexport type WhatsNewSignalPresentation = LauncherSignalPresentation;\n\n/**\n * Which failure class raised the launcher's error signal. A closed enum: the\n * failure *message* is never transmitted, so prompts, URLs and identifiers\n * embedded in an error cannot leave the browser.\n *\n * `connection` and `threads` are wiring *state*. `run`, `tool` and `memory`\n * are unread *events* that clear when their landing view is read.\n */\nexport type InspectorWiringErrorSource = \"connection\" | \"threads\";\nexport type InspectorEventErrorSource = \"run\" | \"tool\" | \"memory\";\nexport type InspectorErrorSignalSource =\n | InspectorWiringErrorSource\n | InspectorEventErrorSource;\n\n/**\n * Fires when What's new has rendered *with content*. A loading state is not\n * an impression, so the metric cannot inflate itself by counting readers who\n * arrived before the feed resolved.\n */\nexport function trackWhatsNewViewed(props: {\n banner_id: string;\n notification_id?: string;\n surface: WhatsNewSurface;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.whatsNewViewed, props);\n}\n\n/** Fires when the unread launcher signal is presented in a visible tab. */\nexport function trackWhatsNewSignalViewed(props: {\n banner_id: string;\n notification_id?: string;\n surface: \"launcher\";\n presentation: WhatsNewSignalPresentation;\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.whatsNewSignalViewed, props);\n}\n\nexport type HudFeature = \"threads\" | \"learning\";\n\n/**\n * What opened the HUD presentation. `intro` is the automatic preview that\n * plays after every mount, `user` is a pointer or keyboard open. A user who\n * takes over a playing intro keeps `intro`, because the presentation itself\n * was not requested.\n */\nexport type HudTrigger = \"intro\" | \"user\";\n\n/** One impression per rendered HUD presentation in a visible tab. */\nexport function trackHudViewed(props: { trigger: HudTrigger }): void {\n track(TELEMETRY_EVENTS.hudViewed, props);\n}\n\n/** The UUID identifies the notification consistently across HUD and article events. */\nexport function trackHudNotificationViewed(props: {\n banner_id: string;\n notification_id: string;\n trigger: HudTrigger;\n}): void {\n track(TELEMETRY_EVENTS.hudNotificationViewed, props);\n}\n\nexport function trackHudNotificationClicked(props: {\n banner_id: string;\n notification_id: string;\n action: \"open\" | \"dismiss\";\n trigger: HudTrigger;\n}): void {\n track(TELEMETRY_EVENTS.hudNotificationClicked, props);\n}\n\nexport function trackHudFeatureToggleViewed(props: {\n feature: HudFeature;\n trigger: HudTrigger;\n}): void {\n track(TELEMETRY_EVENTS.hudFeatureToggleViewed, props);\n}\n\nexport function trackHudFeatureToggleClicked(props: {\n feature: HudFeature;\n trigger: HudTrigger;\n}): void {\n track(TELEMETRY_EVENTS.hudFeatureToggleClicked, props);\n}\n\nexport function trackHudFeatureClicked(props: {\n feature: HudFeature;\n control: \"row\" | \"action\" | \"learn_more\";\n trigger: HudTrigger;\n}): void {\n track(TELEMETRY_EVENTS.hudFeatureClicked, props);\n}\n\nexport function trackHudHideViewed(props: { trigger: HudTrigger }): void {\n track(TELEMETRY_EVENTS.hudHideViewed, props);\n}\n\nexport function trackHudHideClicked(props: { trigger: HudTrigger }): void {\n track(TELEMETRY_EVENTS.hudHideClicked, props);\n}\n\n/**\n * Whether the launcher opened its pill for this outage, or suppressed it.\n *\n * `suppressed` means no pill was shown. In practice that is the no-room\n * fallback — neither side of the launcher had space for the label — because\n * every other path to a visible error signal opens one.\n */\nexport type InspectorErrorSignalLabel = \"shown\" | \"suppressed\";\n\n/**\n * Fires when the launcher's error signal becomes *visible* — not when it arms.\n * Arming can happen with the panel open or the tab hidden, where there is no\n * launcher to look at, so counting arms would inflate the denominator this\n * event exists to provide.\n *\n * Deliberately carries three fixed enum values and nothing else. This is the\n * one place a later change could casually attach a free-text field, and the\n * failure message must never be transmitted.\n *\n * `label` is deliberately not a new event: the catalogue's size is asserted\n * and spelled out in a test title, and a property answers the one open\n * question — how often the no-room fallback fires — without touching either.\n *\n * The pill's own marginal effect cannot be measured here and must not be\n * reverse-engineered from this data: it ships together with the dot and the\n * beat, so there is no period with one and not the other.\n */\nexport function trackErrorSignalViewed(props: {\n source: InspectorErrorSignalSource;\n presentation: LauncherSignalPresentation;\n label: InspectorErrorSignalLabel;\n}): void {\n track(TELEMETRY_EVENTS.errorSignalViewed, {\n source: props.source,\n presentation: props.presentation,\n label: props.label,\n });\n}\n\n/**\n * Fires for links followed inside the announcement body. `body` is the only\n * call-to-action left: dismissal no longer exists, so there is nothing else a\n * click on this surface can mean.\n */\nexport function trackWhatsNewClicked(props: {\n banner_id: string;\n notification_id?: string;\n cta: \"body\";\n cta_label?: string;\n}): void {\n track(TELEMETRY_EVENTS.whatsNewClicked, 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\" | \"message_toolbar\";\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 /** True when an error signal was on the launcher at open time. */\n has_error_signal?: boolean;\n /** Which failure class was signalling at open time, when one was. */\n error_signal_source?: InspectorErrorSignalSource;\n /** True when this is the first Inspector open after install or upgrade. */\n first_open?: 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 the announcement's own\n * click event (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 ExampleKind = \"realtime_sync\" | \"manage_history\" | \"inspect_runs\";\nexport type ThreadsUsageBucket =\n | \"absent\"\n | \"empty\"\n | \"within_limit\"\n | \"at_or_over_limit\"\n | \"unlimited\"\n | \"unknown_limit\";\nexport type ThreadsExpiryBucket = \"unavailable\" | \"zero\" | \"positive\";\nexport type InspectorGroupKey = \"home\" | \"workbench\" | \"inspect\";\nexport type InspectorLeafKey =\n | \"home\"\n | \"whats-new\"\n | \"playground\"\n | \"threads\"\n | \"ag-ui-events\"\n | \"agents\"\n | \"frontend-tools\"\n | \"capabilities\"\n | \"agent-context\"\n | \"memories\";\nexport type MetadataActionPlacement = \"threads_footer\" | \"threads_locked\";\nexport type ExampleTourStep = 1 | 2 | 3;\nexport type ExampleTourTab = \"timeline\" | \"raw-events\" | \"state\";\n\nexport type InspectorThreadTelemetryProps = Readonly<{\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 | \"sidebar_footer\";\n cta?: \"signup\" | \"talk_to_engineer\";\n telemetry_disabled?: boolean;\n has_threads?: boolean;\n usage_bucket?: ThreadsUsageBucket;\n expiry_bucket?: ThreadsExpiryBucket;\n group_key?: InspectorGroupKey;\n leaf_key?: InspectorLeafKey;\n example_kind?: ExampleKind;\n tour_step?: ExampleTourStep;\n tour_tab?: ExampleTourTab;\n dismiss_method?: \"skip\" | \"done\";\n outcome?: \"success\" | \"failure\";\n}>;\n\n/** Rebuild the common Thread payload from its closed coarse allowlist. */\nfunction threadCommonProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...(props.intelligence_status === undefined\n ? {}\n : { intelligence_status: props.intelligence_status }),\n ...(props.thread_service_status === undefined\n ? {}\n : { thread_service_status: props.thread_service_status }),\n ...(props.license_status === undefined\n ? {}\n : { license_status: props.license_status }),\n ...(props.runtime_mode === undefined\n ? {}\n : { runtime_mode: props.runtime_mode }),\n ...(props.runtime_url_type === undefined\n ? {}\n : { runtime_url_type: props.runtime_url_type }),\n ...(props.telemetry_disabled === undefined\n ? {}\n : { telemetry_disabled: props.telemetry_disabled }),\n ...(props.has_threads === undefined\n ? {}\n : { has_threads: props.has_threads }),\n ...(props.usage_bucket === undefined\n ? {}\n : { usage_bucket: props.usage_bucket }),\n ...(props.expiry_bucket === undefined\n ? {}\n : { expiry_bucket: props.expiry_bucket }),\n ...(props.group_key === undefined ? {} : { group_key: props.group_key }),\n ...(props.leaf_key === undefined ? {} : { leaf_key: props.leaf_key }),\n };\n}\n\n/** Add only the fields used by the three Thread CTA funnels. */\nfunction threadCtaProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...threadCommonProperties(props),\n ...(props.cta === undefined ? {} : { cta: props.cta }),\n ...(props.cta_surface === undefined\n ? {}\n : { cta_surface: props.cta_surface }),\n ...(props.posthog_distinct_id === undefined\n ? {}\n : { posthog_distinct_id: props.posthog_distinct_id }),\n };\n}\n\n/** Add only the closed example kind used by example impressions/selections. */\nfunction threadExampleProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...threadCommonProperties(props),\n ...(props.example_kind === undefined\n ? {}\n : { example_kind: props.example_kind }),\n };\n}\n\n/** Add only a bounded example kind and tour step/tab pair. */\nfunction threadTourProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...threadExampleProperties(props),\n ...(props.tour_step === undefined ? {} : { tour_step: props.tour_step }),\n ...(props.tour_tab === undefined ? {} : { tour_tab: props.tour_tab }),\n };\n}\n\n/** Add the bounded dismissal leaf used only by terminal tour events. */\nfunction threadTourTerminalProperties(props: InspectorThreadTelemetryProps) {\n return {\n ...threadTourProperties(props),\n ...(props.dismiss_method === undefined\n ? {}\n : { dismiss_method: props.dismiss_method }),\n };\n}\n\nexport function trackThreadsTabClicked(\n props: InspectorThreadTelemetryProps = {},\n): void {\n track(TELEMETRY_EVENTS.threadsTabClicked, threadCommonProperties(props));\n}\n\nexport function trackThreadsTryFromHereClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsTryFromHereClicked, {\n ...threadCommonProperties(props),\n ...(props.outcome === undefined ? {} : { outcome: props.outcome }),\n });\n}\n\nexport function trackThreadsLockedViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsLockedViewed, threadCommonProperties(props));\n}\n\nexport function trackThreadsIntelligenceSignupClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsIntelligenceSignupClicked,\n threadCtaProperties(props),\n );\n}\n\nexport function trackThreadsTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsTalkToEngineerClicked,\n threadCtaProperties(props),\n );\n}\n\nexport function trackTalkToEngineerClicked(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.talkToEngineerClicked, threadCtaProperties(props));\n}\n\nexport function trackThreadsEmptyEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsEmptyEnabledViewed,\n threadCommonProperties(props),\n );\n}\n\nexport function trackThreadsEnabledViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsEnabledViewed, threadCommonProperties(props));\n}\n\nexport function trackThreadsExampleViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.threadsExampleViewed, threadExampleProperties(props));\n}\n\nexport function trackThreadsExampleSelected(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleSelected,\n threadExampleProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourStarted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourStarted,\n threadTourProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourStepViewed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourStepViewed,\n threadTourProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourDismissed(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourDismissed,\n threadTourTerminalProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourCompleted(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourCompleted,\n threadTourTerminalProperties(props),\n );\n}\n\nexport function trackThreadsExampleTourReopened(\n props: InspectorThreadTelemetryProps,\n): void {\n track(\n TELEMETRY_EVENTS.threadsExampleTourReopened,\n threadTourProperties(props),\n );\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\nexport type InspectorLearningViewState =\n | \"loading\"\n | \"error\"\n | \"selection_required\"\n | \"invalid\"\n | \"results\"\n | \"first_run\"\n | \"ready\"\n | \"empty\"\n | \"setup\"\n | \"landing\";\nexport type InspectorLearningCountBucket =\n | \"zero\"\n | \"one\"\n | \"two_to_five\"\n | \"six_to_twenty\"\n | \"twenty_one_plus\";\nexport type InspectorLearningDurationBucket =\n | \"under_250ms\"\n | \"250ms_to_1s\"\n | \"1s_to_3s\"\n | \"3s_plus\";\n\nexport function learningCountBucket(\n value: number,\n): InspectorLearningCountBucket {\n if (value <= 0) return \"zero\";\n if (value === 1) return \"one\";\n if (value <= 5) return \"two_to_five\";\n if (value <= 20) return \"six_to_twenty\";\n return \"twenty_one_plus\";\n}\n\nexport function learningDurationBucket(\n durationMs: number,\n): InspectorLearningDurationBucket {\n if (durationMs < 250) return \"under_250ms\";\n if (durationMs < 1_000) return \"250ms_to_1s\";\n if (durationMs < 3_000) return \"1s_to_3s\";\n return \"3s_plus\";\n}\n\nexport function trackLearningPaneViewed(props: {\n state: InspectorLearningViewState;\n}): void {\n track(TELEMETRY_EVENTS.learningPaneViewed, props);\n}\n\nexport function trackLearningSetupPromptClicked(props: {\n outcome: \"success\" | \"failure\";\n}): void {\n track(TELEMETRY_EVENTS.learningSetupPromptClicked, props);\n}\n\nexport function trackLearningSnapshotLoaded(props: {\n outcome: \"success\" | \"unsupported\" | \"failure\";\n duration_bucket: InspectorLearningDurationBucket;\n skills_bucket: InspectorLearningCountBucket;\n insights_bucket: InspectorLearningCountBucket;\n pending_threads_bucket: InspectorLearningCountBucket;\n}): void {\n track(TELEMETRY_EVENTS.learningSnapshotLoaded, props);\n}\n\nexport function trackLearningSkillToggled(props: {\n action: \"expanded\" | \"collapsed\";\n}): void {\n track(TELEMETRY_EVENTS.learningSkillToggled, props);\n}\n\nexport function trackLearningEvidenceOpened(): void {\n track(TELEMETRY_EVENTS.learningEvidenceOpened);\n}\n\nexport function trackLearningPageChanged(props: {\n section: \"skills\" | \"insights\";\n direction: \"previous\" | \"next\";\n}): void {\n track(TELEMETRY_EVENTS.learningPageChanged, props);\n}\n\nexport function trackLearningWebAppOpened(props: {\n category: \"learning\" | \"runs\" | \"candidates\";\n}): void {\n track(TELEMETRY_EVENTS.learningWebAppOpened, props);\n}\n\nexport type InspectorMetadataTelemetryModule = \"identity\" | \"plan\" | \"action\";\nexport type InspectorMetadataLicenseBucket =\n | \"valid\"\n | \"none\"\n | \"expired\"\n | \"unknown\";\nexport type InspectorMetadataActionKind =\n | \"manage_plan\"\n | \"renew\"\n | \"enable_intelligence\";\n\nexport type InspectorHomeTelemetryProps = Readonly<{\n action_kind?: InspectorMetadataActionKind;\n group_key?: InspectorGroupKey;\n leaf_key?: InspectorLeafKey;\n}>;\n\nexport function trackHomeViewed(props: InspectorHomeTelemetryProps = {}): void {\n track(TELEMETRY_EVENTS.homeViewed, {\n group_key: props.group_key ?? \"home\",\n leaf_key: props.leaf_key ?? \"home\",\n });\n}\n\nexport function trackHomeCtaClicked(props: InspectorHomeTelemetryProps): void {\n track(TELEMETRY_EVENTS.homeCtaClicked, {\n action_kind: props.action_kind,\n group_key: props.group_key ?? \"home\",\n leaf_key: props.leaf_key ?? \"home\",\n });\n}\n\n/**\n * Correlates a copied feature setup prompt with its onboarding run. The run ID\n * is generated locally for this click, not derived from account or runtime\n * data, so it can be safely joined with the onboarding flow downstream.\n */\nexport function trackHomeFeaturePromptClicked(props: {\n feature_id: string;\n onboarding_run_id: string;\n}): void {\n track(TELEMETRY_EVENTS.homeFeaturePromptClicked, {\n feature_id: props.feature_id,\n onboarding_run_id: props.onboarding_run_id,\n group_key: \"home\",\n leaf_key: \"home\",\n });\n}\n\nexport type InspectorHomePromptCopiedTelemetryProps = Readonly<{\n /** The id minted for this session and substituted into the copied prompt. */\n onboarding_run_id: string;\n /** Whether the clipboard write actually landed. */\n outcome: \"copied\" | \"failed\";\n}>;\n\n/**\n * Report a copy of the Intelligence install prompt.\n *\n * `outcome` is reported rather than only emitting on success, because a\n * clipboard that refuses is indistinguishable from a developer who never\n * pressed the button — and the two call for opposite fixes.\n */\nexport function trackHomePromptCopied(\n props: InspectorHomePromptCopiedTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.homePromptCopied, {\n onboarding_run_id: props.onboarding_run_id,\n outcome: props.outcome,\n group_key: \"home\",\n leaf_key: \"home\",\n });\n}\n\nexport type InspectorHomeStoryBeatTelemetryProps = Readonly<{\n /** Stable id of the step, e.g. \"threads\". Survives a label rename. */\n beat: string;\n /** Its position in the rail, so a reorder can be evaluated against clicks. */\n beat_index: number;\n}>;\n\n/**\n * Report a step of the Intelligence story that a developer opened themselves.\n *\n * Only a press reports. The story also advances on its own every few seconds,\n * and reporting that would bury the handful of real interactions under a\n * metronome — one event per idle developer per six seconds, none of it intent.\n */\nexport function trackHomeStoryBeatSelected(\n props: InspectorHomeStoryBeatTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.homeStoryBeatSelected, {\n beat: props.beat,\n beat_index: props.beat_index,\n group_key: \"home\",\n leaf_key: \"home\",\n });\n}\n\nexport type InspectorMetadataModuleViewedTelemetryProps = Readonly<{\n module: InspectorMetadataTelemetryModule;\n license_bucket: InspectorMetadataLicenseBucket;\n action_kind?: InspectorMetadataActionKind;\n usage_bucket: ThreadsUsageBucket;\n expiry_bucket: ThreadsExpiryBucket;\n group_key?: InspectorGroupKey;\n leaf_key?: InspectorLeafKey;\n action_placement?: MetadataActionPlacement;\n}>;\n\nexport type InspectorMetadataActionClickedTelemetryProps = Readonly<{\n action_kind: Exclude<InspectorMetadataActionKind, \"enable_intelligence\">;\n license_bucket: InspectorMetadataLicenseBucket;\n usage_bucket: ThreadsUsageBucket;\n expiry_bucket: ThreadsExpiryBucket;\n group_key?: InspectorGroupKey;\n leaf_key?: InspectorLeafKey;\n action_placement?: MetadataActionPlacement;\n}>;\n\nfunction metadataCoarseProperties(\n props:\n | InspectorMetadataModuleViewedTelemetryProps\n | InspectorMetadataActionClickedTelemetryProps,\n) {\n const navigation =\n props.group_key === undefined || props.leaf_key === undefined\n ? {}\n : { group_key: props.group_key, leaf_key: props.leaf_key };\n return {\n license_bucket: props.license_bucket,\n usage_bucket: props.usage_bucket,\n expiry_bucket: props.expiry_bucket,\n ...navigation,\n ...(props.action_placement === undefined\n ? {}\n : { action_placement: props.action_placement }),\n };\n}\n\n/**\n * Tracks one visible Inspector metadata module using coarse fields only.\n * Rebuilding the payload here prevents local labels, IDs, URLs, and usage\n * values from crossing the telemetry boundary through extra object fields.\n */\nexport function trackMetadataModuleViewed(\n props: InspectorMetadataModuleViewedTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.metadataModuleViewed, {\n module: props.module,\n ...(props.action_kind === undefined\n ? {}\n : { action_kind: props.action_kind }),\n ...metadataCoarseProperties(props),\n });\n}\n\n/**\n * Tracks clicks for managed plan and renewal actions. The existing\n * Intelligence-enable funnel keeps its original event and never calls this\n * helper, which prevents a single click from producing two wire events.\n */\nexport function trackMetadataActionClicked(\n props: InspectorMetadataActionClickedTelemetryProps,\n): void {\n track(TELEMETRY_EVENTS.metadataActionClicked, {\n module: \"action\",\n action_kind: props.action_kind,\n ...metadataCoarseProperties(props),\n });\n}\n\n/**\n * Returns the inspector's anonymous distinct-ID for cross-domain\n * propagation onto outbound announcement-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 * `whats_new_viewed → whats_new_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 announcement-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 let timeoutId: ReturnType<typeof setTimeout> | undefined;\n try {\n const controller = new AbortController();\n timeoutId = setTimeout(() => controller.abort(), FETCH_TIMEOUT_MS);\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 if (timeoutId !== undefined) clearTimeout(timeoutId);\n }\n}\n"],"mappings":"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"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.d.cts","names":[],"sources":["../../src/lib/telemetry.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"telemetry.d.cts","names":[],"sources":["../../src/lib/telemetry.ts"],"mappings":";;;;;;KA4WY,mBAAA"}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"telemetry.d.mts","names":[],"sources":["../../src/lib/telemetry.ts"],"mappings":";;;;;;
|
|
1
|
+
{"version":3,"file":"telemetry.d.mts","names":[],"sources":["../../src/lib/telemetry.ts"],"mappings":";;;;;;KA4WY,mBAAA"}
|