@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.
Files changed (47) hide show
  1. package/dist/index.cjs +363 -271
  2. package/dist/index.cjs.map +1 -1
  3. package/dist/index.d.cts +29 -6
  4. package/dist/index.d.cts.map +1 -1
  5. package/dist/index.d.mts +29 -6
  6. package/dist/index.d.mts.map +1 -1
  7. package/dist/index.mjs +364 -272
  8. package/dist/index.mjs.map +1 -1
  9. package/dist/index.umd.js +1274 -628
  10. package/dist/index.umd.js.map +1 -1
  11. package/dist/lib/notification-loader.cjs +38 -0
  12. package/dist/lib/notification-loader.cjs.map +1 -0
  13. package/dist/lib/notification-loader.mjs +38 -0
  14. package/dist/lib/notification-loader.mjs.map +1 -0
  15. package/dist/lib/notifications.cjs +173 -0
  16. package/dist/lib/notifications.cjs.map +1 -0
  17. package/dist/lib/notifications.d.cts +17 -0
  18. package/dist/lib/notifications.d.cts.map +1 -0
  19. package/dist/lib/notifications.d.mts +17 -0
  20. package/dist/lib/notifications.d.mts.map +1 -0
  21. package/dist/lib/notifications.mjs +168 -0
  22. package/dist/lib/notifications.mjs.map +1 -0
  23. package/dist/lib/onboarding-prompt.cjs +11 -17
  24. package/dist/lib/onboarding-prompt.cjs.map +1 -1
  25. package/dist/lib/onboarding-prompt.mjs +11 -17
  26. package/dist/lib/onboarding-prompt.mjs.map +1 -1
  27. package/dist/lib/persistence.cjs +124 -48
  28. package/dist/lib/persistence.cjs.map +1 -1
  29. package/dist/lib/persistence.mjs +121 -45
  30. package/dist/lib/persistence.mjs.map +1 -1
  31. package/dist/lib/telemetry.cjs +42 -0
  32. package/dist/lib/telemetry.cjs.map +1 -1
  33. package/dist/lib/telemetry.d.cts.map +1 -1
  34. package/dist/lib/telemetry.d.mts.map +1 -1
  35. package/dist/lib/telemetry.mjs +35 -1
  36. package/dist/lib/telemetry.mjs.map +1 -1
  37. package/dist/package.cjs +1 -1
  38. package/dist/package.mjs +1 -1
  39. package/dist/styles/generated.cjs +1 -1
  40. package/dist/styles/generated.cjs.map +1 -1
  41. package/dist/styles/generated.mjs +1 -1
  42. package/dist/styles/generated.mjs.map +1 -1
  43. package/dist/styles/notification-article.cjs +244 -0
  44. package/dist/styles/notification-article.cjs.map +1 -0
  45. package/dist/styles/notification-article.mjs +244 -0
  46. package/dist/styles/notification-article.mjs.map +1 -0
  47. package/package.json +4 -3
@@ -0,0 +1,38 @@
1
+ const require_notifications = require('./notifications.cjs');
2
+
3
+ //#region src/lib/notification-loader.ts
4
+ const NOTIFICATION_FEED_URL = "https://cdn.copilotkit.ai/notifications/v1.json";
5
+ const requests = /* @__PURE__ */ new Map();
6
+ /** New SDKs read only cohorts; announcements.json remains the older SDK channel.
7
+ * Share one request per framework/version per page. Missing context gets the full
8
+ * feed so future servers cannot exclude notices the client may still match.
9
+ */
10
+ function loadNotificationFeed(context) {
11
+ if (typeof window === "undefined" || typeof fetch === "undefined") return Promise.resolve(null);
12
+ const url = new URL(NOTIFICATION_FEED_URL);
13
+ if (context.framework && context.sdkVersion) {
14
+ url.searchParams.set("framework", context.framework);
15
+ url.searchParams.set("sdkVersion", context.sdkVersion);
16
+ }
17
+ const key = url.toString();
18
+ const existing = requests.get(key);
19
+ if (existing) return existing;
20
+ const request = fetch(key, {
21
+ cache: "no-cache",
22
+ credentials: "omit"
23
+ }).then(async (response) => {
24
+ if (!response.ok) throw new Error(`Feed request failed (${response.status})`);
25
+ const feed = require_notifications.parseNotificationFeed(await response.json());
26
+ if (!feed) throw new Error("Feed failed validation");
27
+ return feed;
28
+ }).catch((error) => {
29
+ console.warn("[CopilotKit Inspector] Failed to load notifications", error);
30
+ return null;
31
+ });
32
+ requests.set(key, request);
33
+ return request;
34
+ }
35
+
36
+ //#endregion
37
+ exports.loadNotificationFeed = loadNotificationFeed;
38
+ //# sourceMappingURL=notification-loader.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-loader.cjs","names":["parseNotificationFeed"],"sources":["../../src/lib/notification-loader.ts"],"sourcesContent":["import { parseNotificationFeed } from \"./notifications.js\";\nimport type { NotificationContext, NotificationFeed } from \"./notifications.js\";\n\nexport const NOTIFICATION_FEED_URL =\n \"https://cdn.copilotkit.ai/notifications/v1.json\";\nconst requests = new Map<string, Promise<NotificationFeed | null>>();\n/** New SDKs read only cohorts; announcements.json remains the older SDK channel.\n * Share one request per framework/version per page. Missing context gets the full\n * feed so future servers cannot exclude notices the client may still match.\n */\nexport function loadNotificationFeed(\n context: Pick<NotificationContext, \"framework\" | \"sdkVersion\">,\n): Promise<NotificationFeed | null> {\n if (typeof window === \"undefined\" || typeof fetch === \"undefined\")\n return Promise.resolve(null);\n const url = new URL(NOTIFICATION_FEED_URL);\n if (context.framework && context.sdkVersion) {\n url.searchParams.set(\"framework\", context.framework);\n url.searchParams.set(\"sdkVersion\", context.sdkVersion);\n }\n const key = url.toString();\n const existing = requests.get(key);\n if (existing) return existing;\n const request = fetch(key, {\n cache: \"no-cache\",\n credentials: \"omit\",\n })\n .then(async (response) => {\n if (!response.ok)\n throw new Error(`Feed request failed (${response.status})`);\n const feed = parseNotificationFeed(await response.json());\n if (!feed) throw new Error(\"Feed failed validation\");\n return feed;\n })\n .catch((error: unknown) => {\n // Keep the host running, but make an empty What's New debuggable.\n console.warn(\n \"[CopilotKit Inspector] Failed to load notifications\",\n error,\n );\n return null;\n });\n requests.set(key, request);\n return request;\n}\n"],"mappings":";;;AAGA,MAAa,wBACX;AACF,MAAM,2BAAW,IAAI,KAA+C;;;;;AAKpE,SAAgB,qBACd,SACkC;AAClC,KAAI,OAAO,WAAW,eAAe,OAAO,UAAU,YACpD,QAAO,QAAQ,QAAQ,KAAK;CAC9B,MAAM,MAAM,IAAI,IAAI,sBAAsB;AAC1C,KAAI,QAAQ,aAAa,QAAQ,YAAY;AAC3C,MAAI,aAAa,IAAI,aAAa,QAAQ,UAAU;AACpD,MAAI,aAAa,IAAI,cAAc,QAAQ,WAAW;;CAExD,MAAM,MAAM,IAAI,UAAU;CAC1B,MAAM,WAAW,SAAS,IAAI,IAAI;AAClC,KAAI,SAAU,QAAO;CACrB,MAAM,UAAU,MAAM,KAAK;EACzB,OAAO;EACP,aAAa;EACd,CAAC,CACC,KAAK,OAAO,aAAa;AACxB,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG;EAC7D,MAAM,OAAOA,4CAAsB,MAAM,SAAS,MAAM,CAAC;AACzD,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,yBAAyB;AACpD,SAAO;GACP,CACD,OAAO,UAAmB;AAEzB,UAAQ,KACN,uDACA,MACD;AACD,SAAO;GACP;AACJ,UAAS,IAAI,KAAK,QAAQ;AAC1B,QAAO"}
@@ -0,0 +1,38 @@
1
+ import { parseNotificationFeed } from "./notifications.mjs";
2
+
3
+ //#region src/lib/notification-loader.ts
4
+ const NOTIFICATION_FEED_URL = "https://cdn.copilotkit.ai/notifications/v1.json";
5
+ const requests = /* @__PURE__ */ new Map();
6
+ /** New SDKs read only cohorts; announcements.json remains the older SDK channel.
7
+ * Share one request per framework/version per page. Missing context gets the full
8
+ * feed so future servers cannot exclude notices the client may still match.
9
+ */
10
+ function loadNotificationFeed(context) {
11
+ if (typeof window === "undefined" || typeof fetch === "undefined") return Promise.resolve(null);
12
+ const url = new URL(NOTIFICATION_FEED_URL);
13
+ if (context.framework && context.sdkVersion) {
14
+ url.searchParams.set("framework", context.framework);
15
+ url.searchParams.set("sdkVersion", context.sdkVersion);
16
+ }
17
+ const key = url.toString();
18
+ const existing = requests.get(key);
19
+ if (existing) return existing;
20
+ const request = fetch(key, {
21
+ cache: "no-cache",
22
+ credentials: "omit"
23
+ }).then(async (response) => {
24
+ if (!response.ok) throw new Error(`Feed request failed (${response.status})`);
25
+ const feed = parseNotificationFeed(await response.json());
26
+ if (!feed) throw new Error("Feed failed validation");
27
+ return feed;
28
+ }).catch((error) => {
29
+ console.warn("[CopilotKit Inspector] Failed to load notifications", error);
30
+ return null;
31
+ });
32
+ requests.set(key, request);
33
+ return request;
34
+ }
35
+
36
+ //#endregion
37
+ export { loadNotificationFeed };
38
+ //# sourceMappingURL=notification-loader.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notification-loader.mjs","names":[],"sources":["../../src/lib/notification-loader.ts"],"sourcesContent":["import { parseNotificationFeed } from \"./notifications.js\";\nimport type { NotificationContext, NotificationFeed } from \"./notifications.js\";\n\nexport const NOTIFICATION_FEED_URL =\n \"https://cdn.copilotkit.ai/notifications/v1.json\";\nconst requests = new Map<string, Promise<NotificationFeed | null>>();\n/** New SDKs read only cohorts; announcements.json remains the older SDK channel.\n * Share one request per framework/version per page. Missing context gets the full\n * feed so future servers cannot exclude notices the client may still match.\n */\nexport function loadNotificationFeed(\n context: Pick<NotificationContext, \"framework\" | \"sdkVersion\">,\n): Promise<NotificationFeed | null> {\n if (typeof window === \"undefined\" || typeof fetch === \"undefined\")\n return Promise.resolve(null);\n const url = new URL(NOTIFICATION_FEED_URL);\n if (context.framework && context.sdkVersion) {\n url.searchParams.set(\"framework\", context.framework);\n url.searchParams.set(\"sdkVersion\", context.sdkVersion);\n }\n const key = url.toString();\n const existing = requests.get(key);\n if (existing) return existing;\n const request = fetch(key, {\n cache: \"no-cache\",\n credentials: \"omit\",\n })\n .then(async (response) => {\n if (!response.ok)\n throw new Error(`Feed request failed (${response.status})`);\n const feed = parseNotificationFeed(await response.json());\n if (!feed) throw new Error(\"Feed failed validation\");\n return feed;\n })\n .catch((error: unknown) => {\n // Keep the host running, but make an empty What's New debuggable.\n console.warn(\n \"[CopilotKit Inspector] Failed to load notifications\",\n error,\n );\n return null;\n });\n requests.set(key, request);\n return request;\n}\n"],"mappings":";;;AAGA,MAAa,wBACX;AACF,MAAM,2BAAW,IAAI,KAA+C;;;;;AAKpE,SAAgB,qBACd,SACkC;AAClC,KAAI,OAAO,WAAW,eAAe,OAAO,UAAU,YACpD,QAAO,QAAQ,QAAQ,KAAK;CAC9B,MAAM,MAAM,IAAI,IAAI,sBAAsB;AAC1C,KAAI,QAAQ,aAAa,QAAQ,YAAY;AAC3C,MAAI,aAAa,IAAI,aAAa,QAAQ,UAAU;AACpD,MAAI,aAAa,IAAI,cAAc,QAAQ,WAAW;;CAExD,MAAM,MAAM,IAAI,UAAU;CAC1B,MAAM,WAAW,SAAS,IAAI,IAAI;AAClC,KAAI,SAAU,QAAO;CACrB,MAAM,UAAU,MAAM,KAAK;EACzB,OAAO;EACP,aAAa;EACd,CAAC,CACC,KAAK,OAAO,aAAa;AACxB,MAAI,CAAC,SAAS,GACZ,OAAM,IAAI,MAAM,wBAAwB,SAAS,OAAO,GAAG;EAC7D,MAAM,OAAO,sBAAsB,MAAM,SAAS,MAAM,CAAC;AACzD,MAAI,CAAC,KAAM,OAAM,IAAI,MAAM,yBAAyB;AACpD,SAAO;GACP,CACD,OAAO,UAAmB;AAEzB,UAAQ,KACN,uDACA,MACD;AACD,SAAO;GACP;AACJ,UAAS,IAAI,KAAK,QAAQ;AAC1B,QAAO"}
@@ -0,0 +1,173 @@
1
+ let semver = require("semver");
2
+
3
+ //#region src/lib/notifications.ts
4
+ function emptyNotificationState() {
5
+ return {
6
+ schemaVersion: 1,
7
+ eligibleIds: [],
8
+ readIds: [],
9
+ suppressedIds: [],
10
+ activeId: null
11
+ };
12
+ }
13
+ const priorities = {
14
+ Low: 10,
15
+ Normal: 20,
16
+ High: 30,
17
+ Urgent: 40
18
+ };
19
+ const conditionValues = {
20
+ framework: [
21
+ "react",
22
+ "vue",
23
+ "angular"
24
+ ],
25
+ intelligence: ["enabled", "disabled"],
26
+ deployment: ["managed", "self-hosted"]
27
+ };
28
+ function record(value) {
29
+ return value !== null && typeof value === "object" && !Array.isArray(value);
30
+ }
31
+ function onlyKeys(value, keys) {
32
+ return Object.keys(value).every((key) => keys.includes(key));
33
+ }
34
+ function text(value, max = 256) {
35
+ return typeof value === "string" && value.trim().length > 0 && value.length <= max;
36
+ }
37
+ function isNotificationId(value) {
38
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
39
+ }
40
+ function ids(value) {
41
+ return Array.isArray(value) && value.every(isNotificationId) && new Set(value).size === value.length;
42
+ }
43
+ /** Validate authored conditions; unsupported predicates cannot broaden the audience. */
44
+ function validNotificationConditions(value) {
45
+ if (!record(value)) return false;
46
+ if (value.sdkVersion !== void 0 && value.framework === void 0) return false;
47
+ return Object.entries(value).every(([key, expected]) => {
48
+ if (!text(expected)) return false;
49
+ if (key === "sdkVersion") return (0, semver.validRange)(expected) !== null;
50
+ if (key === "plan") return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(expected);
51
+ return Object.prototype.hasOwnProperty.call(conditionValues, key) && (conditionValues[key]?.includes(expected) ?? false);
52
+ });
53
+ }
54
+ /** Parse the public envelope. Any invalid entry rejects the whole feed. */
55
+ function parseNotificationFeed(value) {
56
+ if (!record(value) || !onlyKeys(value, ["schemaVersion", "notifications"]) || value.schemaVersion !== 1) return null;
57
+ if (!Array.isArray(value.notifications) || value.notifications.length > 100) return null;
58
+ const notifications = [];
59
+ for (const notice of value.notifications) {
60
+ if (!record(notice) || !onlyKeys(notice, [
61
+ "id",
62
+ "title",
63
+ "body",
64
+ "publishedAt",
65
+ "audiences",
66
+ "priority",
67
+ "priorityOverride"
68
+ ])) return null;
69
+ if (!isNotificationId(notice.id) || !text(notice.title) || !text(notice.body, 1e5) || !text(notice.publishedAt)) return null;
70
+ if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(notice.publishedAt) || !Number.isFinite(Date.parse(notice.publishedAt)) || new Date(notice.publishedAt).toISOString() !== notice.publishedAt) return null;
71
+ if (!Array.isArray(notice.audiences) || !notice.audiences.length || notice.audiences.length > 100 || !notice.audiences.every(validNotificationConditions)) return null;
72
+ if (!text(notice.priority) || !Object.prototype.hasOwnProperty.call(priorities, notice.priority)) return null;
73
+ if (notice.priorityOverride !== void 0 && (typeof notice.priorityOverride !== "number" || !Number.isSafeInteger(notice.priorityOverride) || notice.priorityOverride < 0)) return null;
74
+ if (notifications.some((n) => n.id === notice.id)) return null;
75
+ notifications.push({
76
+ id: notice.id,
77
+ title: notice.title,
78
+ body: notice.body,
79
+ publishedAt: notice.publishedAt,
80
+ audiences: notice.audiences,
81
+ priority: notice.priority,
82
+ ...notice.priorityOverride === void 0 ? {} : { priorityOverride: notice.priorityOverride }
83
+ });
84
+ }
85
+ return {
86
+ schemaVersion: 1,
87
+ notifications
88
+ };
89
+ }
90
+ /** Ignore malformed or incompatible browser state. */
91
+ function parseNotificationState(value) {
92
+ if (!record(value) || value.schemaVersion !== 1 || !ids(value.eligibleIds) || !ids(value.readIds) || !ids(value.suppressedIds) || value.activeId !== null && !isNotificationId(value.activeId)) return null;
93
+ return {
94
+ schemaVersion: 1,
95
+ eligibleIds: value.eligibleIds,
96
+ readIds: value.readIds,
97
+ suppressedIds: value.suppressedIds,
98
+ activeId: value.activeId
99
+ };
100
+ }
101
+ /** Internal priority never becomes a developer-facing badge. */
102
+ function notificationPriority(notice) {
103
+ return notice.priorityOverride ?? priorities[notice.priority];
104
+ }
105
+ /** Stable ordering used by authoring reports and first-time selection. */
106
+ function compareNotifications(a, b) {
107
+ return notificationPriority(b) - notificationPriority(a) || Date.parse(b.publishedAt) - Date.parse(a.publishedAt) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
108
+ }
109
+ /** Refresh eligibility without promoting the previously delivered backlog. */
110
+ function reconcileNotifications(state, feed, context) {
111
+ const eligible = feed.notifications.filter((n) => matchNotification(n, context).matches);
112
+ const quiet = (n) => state.readIds.includes(n.id) || state.suppressedIds.includes(n.id);
113
+ const active = eligible.find((n) => n.id === state.activeId && !quiet(n)) ?? feed.notifications.find((n) => n.id === state.activeId && !quiet(n) && n.audiences.some((conditions) => {
114
+ const entries = Object.entries(conditions);
115
+ if (!entries.some(([key]) => context[key] === void 0)) return false;
116
+ const known = Object.fromEntries(entries.filter(([key]) => context[key] !== void 0));
117
+ return matchNotification({
118
+ ...n,
119
+ audiences: [known]
120
+ }, context).matches;
121
+ }));
122
+ const newcomer = eligible.filter((n) => !quiet(n) && !state.eligibleIds.includes(n.id)).sort(compareNotifications)[0];
123
+ const winner = active && (!newcomer || notificationPriority(active) >= notificationPriority(newcomer)) ? active : newcomer;
124
+ return {
125
+ ...state,
126
+ eligibleIds: eligible.map((n) => n.id),
127
+ activeId: winner?.id ?? null
128
+ };
129
+ }
130
+ /** Reading or closing the highlight quiets its delivered backlog, not future IDs. */
131
+ function acknowledgeNotification(state, noticeId) {
132
+ if (!state.eligibleIds.includes(noticeId)) return state;
133
+ const highlighted = state.activeId === noticeId;
134
+ return {
135
+ ...state,
136
+ readIds: [...new Set([...state.readIds, noticeId])],
137
+ suppressedIds: [...new Set([...state.suppressedIds, ...highlighted ? state.eligibleIds : [noticeId]])],
138
+ activeId: highlighted ? null : state.activeId
139
+ };
140
+ }
141
+ /** Explain targeting using the same rules in CI and the Inspector. */
142
+ function matchNotification(notice, context) {
143
+ if (!context.development) return {
144
+ matches: false,
145
+ reasons: ["Development only"]
146
+ };
147
+ if (!context.sdkVersion || !(0, semver.valid)(context.sdkVersion) || (0, semver.prerelease)(context.sdkVersion) !== null) return {
148
+ matches: false,
149
+ reasons: ["Known stable SDK required"]
150
+ };
151
+ const reasons = [];
152
+ const matches = notice.audiences.some((conditions) => {
153
+ return Object.entries(conditions).every(([field, expected]) => {
154
+ const actual = context[field];
155
+ const matches = field === "sdkVersion" ? actual !== void 0 && (0, semver.valid)(actual) !== null && (0, semver.prerelease)(actual) === null && (0, semver.satisfies)(actual, expected) : actual === expected;
156
+ if (!matches) reasons.push(`${field} ${actual === void 0 ? "is unknown" : `requires ${expected} (got ${actual})`}`);
157
+ return matches;
158
+ });
159
+ });
160
+ return {
161
+ matches,
162
+ reasons: matches ? [] : reasons
163
+ };
164
+ }
165
+
166
+ //#endregion
167
+ exports.acknowledgeNotification = acknowledgeNotification;
168
+ exports.compareNotifications = compareNotifications;
169
+ exports.emptyNotificationState = emptyNotificationState;
170
+ exports.parseNotificationFeed = parseNotificationFeed;
171
+ exports.parseNotificationState = parseNotificationState;
172
+ exports.reconcileNotifications = reconcileNotifications;
173
+ //# sourceMappingURL=notifications.cjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.cjs","names":[],"sources":["../../src/lib/notifications.ts"],"sourcesContent":["import { satisfies, valid, validRange, prerelease } from \"semver\";\n\n/** Versioned CDN notification contract. Keep the Intelligence copy and fixtures aligned. */\nexport type NotificationFramework = \"react\" | \"vue\" | \"angular\";\nexport type NotificationPriority = \"Low\" | \"Normal\" | \"High\" | \"Urgent\";\nexport interface NotificationConditions {\n framework?: NotificationFramework;\n sdkVersion?: string;\n intelligence?: \"enabled\" | \"disabled\";\n deployment?: \"managed\" | \"self-hosted\";\n plan?: string;\n}\nexport interface CohortNotification {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n audiences: NotificationConditions[];\n priority: NotificationPriority;\n priorityOverride?: number;\n}\nexport interface NotificationFeed {\n schemaVersion: 1;\n notifications: CohortNotification[];\n}\nexport interface NotificationContext extends Omit<\n NotificationConditions,\n \"sdkVersion\"\n> {\n development: boolean;\n sdkVersion?: string;\n}\nexport interface NotificationState {\n schemaVersion: 1;\n eligibleIds: string[];\n readIds: string[];\n suppressedIds: string[];\n activeId: string | null;\n}\nexport function emptyNotificationState(): NotificationState {\n return {\n schemaVersion: 1,\n eligibleIds: [],\n readIds: [],\n suppressedIds: [],\n activeId: null,\n };\n}\nconst priorities: Record<NotificationPriority, number> = {\n Low: 10,\n Normal: 20,\n High: 30,\n Urgent: 40,\n};\nconst conditionValues: Record<string, readonly string[]> = {\n framework: [\"react\", \"vue\", \"angular\"],\n intelligence: [\"enabled\", \"disabled\"],\n deployment: [\"managed\", \"self-hosted\"],\n};\nfunction record(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction onlyKeys(value: Record<string, unknown>, keys: string[]): boolean {\n return Object.keys(value).every((key) => keys.includes(key));\n}\nfunction text(value: unknown, max = 256): value is string {\n return (\n typeof value === \"string\" && value.trim().length > 0 && value.length <= max\n );\n}\nexport function isNotificationId(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(\n value,\n )\n );\n}\nfunction ids(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.every(isNotificationId) &&\n new Set(value).size === value.length\n );\n}\n/** Validate authored conditions; unsupported predicates cannot broaden the audience. */\nexport function validNotificationConditions(\n value: unknown,\n): value is NotificationConditions {\n if (!record(value)) return false;\n if (value.sdkVersion !== undefined && value.framework === undefined)\n return false;\n return Object.entries(value).every(([key, expected]) => {\n if (!text(expected)) return false;\n if (key === \"sdkVersion\") return validRange(expected) !== null;\n if (key === \"plan\") return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(expected);\n return (\n Object.prototype.hasOwnProperty.call(conditionValues, key) &&\n (conditionValues[key]?.includes(expected) ?? false)\n );\n });\n}\n/** Parse the public envelope. Any invalid entry rejects the whole feed. */\nexport function parseNotificationFeed(value: unknown): NotificationFeed | null {\n if (\n !record(value) ||\n !onlyKeys(value, [\"schemaVersion\", \"notifications\"]) ||\n value.schemaVersion !== 1\n )\n return null;\n if (!Array.isArray(value.notifications) || value.notifications.length > 100)\n return null;\n const notifications: CohortNotification[] = [];\n for (const notice of value.notifications) {\n if (\n !record(notice) ||\n !onlyKeys(notice, [\n \"id\",\n \"title\",\n \"body\",\n \"publishedAt\",\n \"audiences\",\n \"priority\",\n \"priorityOverride\",\n ])\n )\n return null;\n if (\n !isNotificationId(notice.id) ||\n !text(notice.title) ||\n !text(notice.body, 100_000) ||\n !text(notice.publishedAt)\n )\n return null;\n if (\n !/^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\.\\d{3}Z$/.test(notice.publishedAt) ||\n !Number.isFinite(Date.parse(notice.publishedAt)) ||\n new Date(notice.publishedAt).toISOString() !== notice.publishedAt\n )\n return null;\n if (\n !Array.isArray(notice.audiences) ||\n !notice.audiences.length ||\n notice.audiences.length > 100 ||\n !notice.audiences.every(validNotificationConditions)\n )\n return null;\n if (\n !text(notice.priority) ||\n !Object.prototype.hasOwnProperty.call(priorities, notice.priority)\n )\n return null;\n if (\n notice.priorityOverride !== undefined &&\n (typeof notice.priorityOverride !== \"number\" ||\n !Number.isSafeInteger(notice.priorityOverride) ||\n notice.priorityOverride < 0)\n )\n return null;\n if (notifications.some((n) => n.id === notice.id)) return null;\n notifications.push({\n id: notice.id,\n title: notice.title,\n body: notice.body,\n publishedAt: notice.publishedAt,\n audiences: notice.audiences,\n priority: notice.priority as NotificationPriority,\n ...(notice.priorityOverride === undefined\n ? {}\n : { priorityOverride: notice.priorityOverride }),\n });\n }\n return { schemaVersion: 1, notifications };\n}\n/** Ignore malformed or incompatible browser state. */\nexport function parseNotificationState(\n value: unknown,\n): NotificationState | null {\n if (\n !record(value) ||\n value.schemaVersion !== 1 ||\n !ids(value.eligibleIds) ||\n !ids(value.readIds) ||\n !ids(value.suppressedIds) ||\n (value.activeId !== null && !isNotificationId(value.activeId))\n )\n return null;\n return {\n schemaVersion: 1,\n eligibleIds: value.eligibleIds,\n readIds: value.readIds,\n suppressedIds: value.suppressedIds,\n activeId: value.activeId,\n };\n}\n/** Internal priority never becomes a developer-facing badge. */\nexport function notificationPriority(notice: CohortNotification): number {\n return notice.priorityOverride ?? priorities[notice.priority];\n}\n/** Stable ordering used by authoring reports and first-time selection. */\nexport function compareNotifications(\n a: CohortNotification,\n b: CohortNotification,\n): number {\n return (\n notificationPriority(b) - notificationPriority(a) ||\n Date.parse(b.publishedAt) - Date.parse(a.publishedAt) ||\n (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)\n );\n}\n/** Refresh eligibility without promoting the previously delivered backlog. */\nexport function reconcileNotifications(\n state: NotificationState,\n feed: NotificationFeed,\n context: NotificationContext,\n): NotificationState {\n const eligible = feed.notifications.filter(\n (n) => matchNotification(n, context).matches,\n );\n const quiet = (n: CohortNotification) =>\n state.readIds.includes(n.id) || state.suppressedIds.includes(n.id);\n const active =\n eligible.find((n) => n.id === state.activeId && !quiet(n)) ??\n feed.notifications.find(\n (n) =>\n n.id === state.activeId &&\n !quiet(n) &&\n n.audiences.some((conditions) => {\n const entries = Object.entries(conditions);\n if (\n !entries.some(\n ([key]) =>\n context[key as keyof NotificationConditions] === undefined,\n )\n )\n return false;\n const known = Object.fromEntries(\n entries.filter(\n ([key]) =>\n context[key as keyof NotificationConditions] !== undefined,\n ),\n );\n return matchNotification({ ...n, audiences: [known] }, context)\n .matches;\n }),\n );\n const newcomer = eligible\n .filter((n) => !quiet(n) && !state.eligibleIds.includes(n.id))\n .sort(compareNotifications)[0];\n const winner =\n active &&\n (!newcomer ||\n notificationPriority(active) >= notificationPriority(newcomer))\n ? active\n : newcomer;\n return {\n ...state,\n eligibleIds: eligible.map((n) => n.id),\n activeId: winner?.id ?? null,\n };\n}\n/** Reading or closing the highlight quiets its delivered backlog, not future IDs. */\nexport function acknowledgeNotification(\n state: NotificationState,\n noticeId: string,\n): NotificationState {\n if (!state.eligibleIds.includes(noticeId)) return state;\n const highlighted = state.activeId === noticeId;\n return {\n ...state,\n readIds: [...new Set([...state.readIds, noticeId])],\n suppressedIds: [\n ...new Set([\n ...state.suppressedIds,\n ...(highlighted ? state.eligibleIds : [noticeId]),\n ]),\n ],\n activeId: highlighted ? null : state.activeId,\n };\n}\n/** Explain targeting using the same rules in CI and the Inspector. */\nexport function matchNotification(\n notice: CohortNotification,\n context: NotificationContext,\n): { matches: boolean; reasons: string[] } {\n if (!context.development)\n return { matches: false, reasons: [\"Development only\"] };\n if (\n !context.sdkVersion ||\n !valid(context.sdkVersion) ||\n prerelease(context.sdkVersion) !== null\n )\n return {\n matches: false,\n reasons: [\"Known stable SDK required\"],\n };\n const reasons: string[] = [];\n const matches = notice.audiences.some((conditions) => {\n return Object.entries(conditions).every(([field, expected]) => {\n const actual = context[field as keyof NotificationConditions];\n const matches =\n field === \"sdkVersion\"\n ? actual !== undefined &&\n valid(actual) !== null &&\n prerelease(actual) === null &&\n satisfies(actual, expected)\n : actual === expected;\n if (!matches)\n reasons.push(\n `${field} ${actual === undefined ? \"is unknown\" : `requires ${expected} (got ${actual})`}`,\n );\n return matches;\n });\n });\n return {\n matches,\n reasons: matches ? [] : reasons,\n };\n}\n"],"mappings":"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"}
@@ -0,0 +1,17 @@
1
+ //#region src/lib/notifications.d.ts
2
+ /** Versioned CDN notification contract. Keep the Intelligence copy and fixtures aligned. */
3
+ type NotificationFramework = "react" | "vue" | "angular";
4
+ interface NotificationConditions {
5
+ framework?: NotificationFramework;
6
+ sdkVersion?: string;
7
+ intelligence?: "enabled" | "disabled";
8
+ deployment?: "managed" | "self-hosted";
9
+ plan?: string;
10
+ }
11
+ interface NotificationContext extends Omit<NotificationConditions, "sdkVersion"> {
12
+ development: boolean;
13
+ sdkVersion?: string;
14
+ }
15
+ //#endregion
16
+ export { NotificationContext };
17
+ //# sourceMappingURL=notifications.d.cts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.d.cts","names":[],"sources":["../../src/lib/notifications.ts"],"mappings":";;KAGY,qBAAA;AAAA,UAEK,sBAAA;EACf,SAAA,GAAY,qBAAA;EACZ,UAAA;EACA,YAAA;EACA,UAAA;EACA,IAAA;AAAA;AAAA,UAee,mBAAA,SAA4B,IAAA,CAC3C,sBAAA;EAGA,WAAA;EACA,UAAA;AAAA"}
@@ -0,0 +1,17 @@
1
+ //#region src/lib/notifications.d.ts
2
+ /** Versioned CDN notification contract. Keep the Intelligence copy and fixtures aligned. */
3
+ type NotificationFramework = "react" | "vue" | "angular";
4
+ interface NotificationConditions {
5
+ framework?: NotificationFramework;
6
+ sdkVersion?: string;
7
+ intelligence?: "enabled" | "disabled";
8
+ deployment?: "managed" | "self-hosted";
9
+ plan?: string;
10
+ }
11
+ interface NotificationContext extends Omit<NotificationConditions, "sdkVersion"> {
12
+ development: boolean;
13
+ sdkVersion?: string;
14
+ }
15
+ //#endregion
16
+ export { NotificationContext };
17
+ //# sourceMappingURL=notifications.d.mts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.d.mts","names":[],"sources":["../../src/lib/notifications.ts"],"mappings":";;KAGY,qBAAA;AAAA,UAEK,sBAAA;EACf,SAAA,GAAY,qBAAA;EACZ,UAAA;EACA,YAAA;EACA,UAAA;EACA,IAAA;AAAA;AAAA,UAee,mBAAA,SAA4B,IAAA,CAC3C,sBAAA;EAGA,WAAA;EACA,UAAA;AAAA"}
@@ -0,0 +1,168 @@
1
+ import { prerelease, satisfies, valid, validRange } from "semver";
2
+
3
+ //#region src/lib/notifications.ts
4
+ function emptyNotificationState() {
5
+ return {
6
+ schemaVersion: 1,
7
+ eligibleIds: [],
8
+ readIds: [],
9
+ suppressedIds: [],
10
+ activeId: null
11
+ };
12
+ }
13
+ const priorities = {
14
+ Low: 10,
15
+ Normal: 20,
16
+ High: 30,
17
+ Urgent: 40
18
+ };
19
+ const conditionValues = {
20
+ framework: [
21
+ "react",
22
+ "vue",
23
+ "angular"
24
+ ],
25
+ intelligence: ["enabled", "disabled"],
26
+ deployment: ["managed", "self-hosted"]
27
+ };
28
+ function record(value) {
29
+ return value !== null && typeof value === "object" && !Array.isArray(value);
30
+ }
31
+ function onlyKeys(value, keys) {
32
+ return Object.keys(value).every((key) => keys.includes(key));
33
+ }
34
+ function text(value, max = 256) {
35
+ return typeof value === "string" && value.trim().length > 0 && value.length <= max;
36
+ }
37
+ function isNotificationId(value) {
38
+ return typeof value === "string" && /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(value);
39
+ }
40
+ function ids(value) {
41
+ return Array.isArray(value) && value.every(isNotificationId) && new Set(value).size === value.length;
42
+ }
43
+ /** Validate authored conditions; unsupported predicates cannot broaden the audience. */
44
+ function validNotificationConditions(value) {
45
+ if (!record(value)) return false;
46
+ if (value.sdkVersion !== void 0 && value.framework === void 0) return false;
47
+ return Object.entries(value).every(([key, expected]) => {
48
+ if (!text(expected)) return false;
49
+ if (key === "sdkVersion") return validRange(expected) !== null;
50
+ if (key === "plan") return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(expected);
51
+ return Object.prototype.hasOwnProperty.call(conditionValues, key) && (conditionValues[key]?.includes(expected) ?? false);
52
+ });
53
+ }
54
+ /** Parse the public envelope. Any invalid entry rejects the whole feed. */
55
+ function parseNotificationFeed(value) {
56
+ if (!record(value) || !onlyKeys(value, ["schemaVersion", "notifications"]) || value.schemaVersion !== 1) return null;
57
+ if (!Array.isArray(value.notifications) || value.notifications.length > 100) return null;
58
+ const notifications = [];
59
+ for (const notice of value.notifications) {
60
+ if (!record(notice) || !onlyKeys(notice, [
61
+ "id",
62
+ "title",
63
+ "body",
64
+ "publishedAt",
65
+ "audiences",
66
+ "priority",
67
+ "priorityOverride"
68
+ ])) return null;
69
+ if (!isNotificationId(notice.id) || !text(notice.title) || !text(notice.body, 1e5) || !text(notice.publishedAt)) return null;
70
+ if (!/^\d{4}-\d\d-\d\dT\d\d:\d\d:\d\d\.\d{3}Z$/.test(notice.publishedAt) || !Number.isFinite(Date.parse(notice.publishedAt)) || new Date(notice.publishedAt).toISOString() !== notice.publishedAt) return null;
71
+ if (!Array.isArray(notice.audiences) || !notice.audiences.length || notice.audiences.length > 100 || !notice.audiences.every(validNotificationConditions)) return null;
72
+ if (!text(notice.priority) || !Object.prototype.hasOwnProperty.call(priorities, notice.priority)) return null;
73
+ if (notice.priorityOverride !== void 0 && (typeof notice.priorityOverride !== "number" || !Number.isSafeInteger(notice.priorityOverride) || notice.priorityOverride < 0)) return null;
74
+ if (notifications.some((n) => n.id === notice.id)) return null;
75
+ notifications.push({
76
+ id: notice.id,
77
+ title: notice.title,
78
+ body: notice.body,
79
+ publishedAt: notice.publishedAt,
80
+ audiences: notice.audiences,
81
+ priority: notice.priority,
82
+ ...notice.priorityOverride === void 0 ? {} : { priorityOverride: notice.priorityOverride }
83
+ });
84
+ }
85
+ return {
86
+ schemaVersion: 1,
87
+ notifications
88
+ };
89
+ }
90
+ /** Ignore malformed or incompatible browser state. */
91
+ function parseNotificationState(value) {
92
+ if (!record(value) || value.schemaVersion !== 1 || !ids(value.eligibleIds) || !ids(value.readIds) || !ids(value.suppressedIds) || value.activeId !== null && !isNotificationId(value.activeId)) return null;
93
+ return {
94
+ schemaVersion: 1,
95
+ eligibleIds: value.eligibleIds,
96
+ readIds: value.readIds,
97
+ suppressedIds: value.suppressedIds,
98
+ activeId: value.activeId
99
+ };
100
+ }
101
+ /** Internal priority never becomes a developer-facing badge. */
102
+ function notificationPriority(notice) {
103
+ return notice.priorityOverride ?? priorities[notice.priority];
104
+ }
105
+ /** Stable ordering used by authoring reports and first-time selection. */
106
+ function compareNotifications(a, b) {
107
+ return notificationPriority(b) - notificationPriority(a) || Date.parse(b.publishedAt) - Date.parse(a.publishedAt) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0);
108
+ }
109
+ /** Refresh eligibility without promoting the previously delivered backlog. */
110
+ function reconcileNotifications(state, feed, context) {
111
+ const eligible = feed.notifications.filter((n) => matchNotification(n, context).matches);
112
+ const quiet = (n) => state.readIds.includes(n.id) || state.suppressedIds.includes(n.id);
113
+ const active = eligible.find((n) => n.id === state.activeId && !quiet(n)) ?? feed.notifications.find((n) => n.id === state.activeId && !quiet(n) && n.audiences.some((conditions) => {
114
+ const entries = Object.entries(conditions);
115
+ if (!entries.some(([key]) => context[key] === void 0)) return false;
116
+ const known = Object.fromEntries(entries.filter(([key]) => context[key] !== void 0));
117
+ return matchNotification({
118
+ ...n,
119
+ audiences: [known]
120
+ }, context).matches;
121
+ }));
122
+ const newcomer = eligible.filter((n) => !quiet(n) && !state.eligibleIds.includes(n.id)).sort(compareNotifications)[0];
123
+ const winner = active && (!newcomer || notificationPriority(active) >= notificationPriority(newcomer)) ? active : newcomer;
124
+ return {
125
+ ...state,
126
+ eligibleIds: eligible.map((n) => n.id),
127
+ activeId: winner?.id ?? null
128
+ };
129
+ }
130
+ /** Reading or closing the highlight quiets its delivered backlog, not future IDs. */
131
+ function acknowledgeNotification(state, noticeId) {
132
+ if (!state.eligibleIds.includes(noticeId)) return state;
133
+ const highlighted = state.activeId === noticeId;
134
+ return {
135
+ ...state,
136
+ readIds: [...new Set([...state.readIds, noticeId])],
137
+ suppressedIds: [...new Set([...state.suppressedIds, ...highlighted ? state.eligibleIds : [noticeId]])],
138
+ activeId: highlighted ? null : state.activeId
139
+ };
140
+ }
141
+ /** Explain targeting using the same rules in CI and the Inspector. */
142
+ function matchNotification(notice, context) {
143
+ if (!context.development) return {
144
+ matches: false,
145
+ reasons: ["Development only"]
146
+ };
147
+ if (!context.sdkVersion || !valid(context.sdkVersion) || prerelease(context.sdkVersion) !== null) return {
148
+ matches: false,
149
+ reasons: ["Known stable SDK required"]
150
+ };
151
+ const reasons = [];
152
+ const matches = notice.audiences.some((conditions) => {
153
+ return Object.entries(conditions).every(([field, expected]) => {
154
+ const actual = context[field];
155
+ const matches = field === "sdkVersion" ? actual !== void 0 && valid(actual) !== null && prerelease(actual) === null && satisfies(actual, expected) : actual === expected;
156
+ if (!matches) reasons.push(`${field} ${actual === void 0 ? "is unknown" : `requires ${expected} (got ${actual})`}`);
157
+ return matches;
158
+ });
159
+ });
160
+ return {
161
+ matches,
162
+ reasons: matches ? [] : reasons
163
+ };
164
+ }
165
+
166
+ //#endregion
167
+ export { acknowledgeNotification, compareNotifications, emptyNotificationState, parseNotificationFeed, parseNotificationState, reconcileNotifications };
168
+ //# sourceMappingURL=notifications.mjs.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"notifications.mjs","names":[],"sources":["../../src/lib/notifications.ts"],"sourcesContent":["import { satisfies, valid, validRange, prerelease } from \"semver\";\n\n/** Versioned CDN notification contract. Keep the Intelligence copy and fixtures aligned. */\nexport type NotificationFramework = \"react\" | \"vue\" | \"angular\";\nexport type NotificationPriority = \"Low\" | \"Normal\" | \"High\" | \"Urgent\";\nexport interface NotificationConditions {\n framework?: NotificationFramework;\n sdkVersion?: string;\n intelligence?: \"enabled\" | \"disabled\";\n deployment?: \"managed\" | \"self-hosted\";\n plan?: string;\n}\nexport interface CohortNotification {\n id: string;\n title: string;\n body: string;\n publishedAt: string;\n audiences: NotificationConditions[];\n priority: NotificationPriority;\n priorityOverride?: number;\n}\nexport interface NotificationFeed {\n schemaVersion: 1;\n notifications: CohortNotification[];\n}\nexport interface NotificationContext extends Omit<\n NotificationConditions,\n \"sdkVersion\"\n> {\n development: boolean;\n sdkVersion?: string;\n}\nexport interface NotificationState {\n schemaVersion: 1;\n eligibleIds: string[];\n readIds: string[];\n suppressedIds: string[];\n activeId: string | null;\n}\nexport function emptyNotificationState(): NotificationState {\n return {\n schemaVersion: 1,\n eligibleIds: [],\n readIds: [],\n suppressedIds: [],\n activeId: null,\n };\n}\nconst priorities: Record<NotificationPriority, number> = {\n Low: 10,\n Normal: 20,\n High: 30,\n Urgent: 40,\n};\nconst conditionValues: Record<string, readonly string[]> = {\n framework: [\"react\", \"vue\", \"angular\"],\n intelligence: [\"enabled\", \"disabled\"],\n deployment: [\"managed\", \"self-hosted\"],\n};\nfunction record(value: unknown): value is Record<string, unknown> {\n return value !== null && typeof value === \"object\" && !Array.isArray(value);\n}\nfunction onlyKeys(value: Record<string, unknown>, keys: string[]): boolean {\n return Object.keys(value).every((key) => keys.includes(key));\n}\nfunction text(value: unknown, max = 256): value is string {\n return (\n typeof value === \"string\" && value.trim().length > 0 && value.length <= max\n );\n}\nexport function isNotificationId(value: unknown): value is string {\n return (\n typeof value === \"string\" &&\n /^[0-9a-f]{8}-[0-9a-f]{4}-[1-8][0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/i.test(\n value,\n )\n );\n}\nfunction ids(value: unknown): value is string[] {\n return (\n Array.isArray(value) &&\n value.every(isNotificationId) &&\n new Set(value).size === value.length\n );\n}\n/** Validate authored conditions; unsupported predicates cannot broaden the audience. */\nexport function validNotificationConditions(\n value: unknown,\n): value is NotificationConditions {\n if (!record(value)) return false;\n if (value.sdkVersion !== undefined && value.framework === undefined)\n return false;\n return Object.entries(value).every(([key, expected]) => {\n if (!text(expected)) return false;\n if (key === \"sdkVersion\") return validRange(expected) !== null;\n if (key === \"plan\") return /^[a-z0-9]+(?:-[a-z0-9]+)*$/.test(expected);\n return (\n Object.prototype.hasOwnProperty.call(conditionValues, key) &&\n (conditionValues[key]?.includes(expected) ?? false)\n );\n });\n}\n/** Parse the public envelope. Any invalid entry rejects the whole feed. */\nexport function parseNotificationFeed(value: unknown): NotificationFeed | null {\n if (\n !record(value) ||\n !onlyKeys(value, [\"schemaVersion\", \"notifications\"]) ||\n value.schemaVersion !== 1\n )\n return null;\n if (!Array.isArray(value.notifications) || value.notifications.length > 100)\n return null;\n const notifications: CohortNotification[] = [];\n for (const notice of value.notifications) {\n if (\n !record(notice) ||\n !onlyKeys(notice, [\n \"id\",\n \"title\",\n \"body\",\n \"publishedAt\",\n \"audiences\",\n \"priority\",\n \"priorityOverride\",\n ])\n )\n return null;\n if (\n !isNotificationId(notice.id) ||\n !text(notice.title) ||\n !text(notice.body, 100_000) ||\n !text(notice.publishedAt)\n )\n return null;\n if (\n !/^\\d{4}-\\d\\d-\\d\\dT\\d\\d:\\d\\d:\\d\\d\\.\\d{3}Z$/.test(notice.publishedAt) ||\n !Number.isFinite(Date.parse(notice.publishedAt)) ||\n new Date(notice.publishedAt).toISOString() !== notice.publishedAt\n )\n return null;\n if (\n !Array.isArray(notice.audiences) ||\n !notice.audiences.length ||\n notice.audiences.length > 100 ||\n !notice.audiences.every(validNotificationConditions)\n )\n return null;\n if (\n !text(notice.priority) ||\n !Object.prototype.hasOwnProperty.call(priorities, notice.priority)\n )\n return null;\n if (\n notice.priorityOverride !== undefined &&\n (typeof notice.priorityOverride !== \"number\" ||\n !Number.isSafeInteger(notice.priorityOverride) ||\n notice.priorityOverride < 0)\n )\n return null;\n if (notifications.some((n) => n.id === notice.id)) return null;\n notifications.push({\n id: notice.id,\n title: notice.title,\n body: notice.body,\n publishedAt: notice.publishedAt,\n audiences: notice.audiences,\n priority: notice.priority as NotificationPriority,\n ...(notice.priorityOverride === undefined\n ? {}\n : { priorityOverride: notice.priorityOverride }),\n });\n }\n return { schemaVersion: 1, notifications };\n}\n/** Ignore malformed or incompatible browser state. */\nexport function parseNotificationState(\n value: unknown,\n): NotificationState | null {\n if (\n !record(value) ||\n value.schemaVersion !== 1 ||\n !ids(value.eligibleIds) ||\n !ids(value.readIds) ||\n !ids(value.suppressedIds) ||\n (value.activeId !== null && !isNotificationId(value.activeId))\n )\n return null;\n return {\n schemaVersion: 1,\n eligibleIds: value.eligibleIds,\n readIds: value.readIds,\n suppressedIds: value.suppressedIds,\n activeId: value.activeId,\n };\n}\n/** Internal priority never becomes a developer-facing badge. */\nexport function notificationPriority(notice: CohortNotification): number {\n return notice.priorityOverride ?? priorities[notice.priority];\n}\n/** Stable ordering used by authoring reports and first-time selection. */\nexport function compareNotifications(\n a: CohortNotification,\n b: CohortNotification,\n): number {\n return (\n notificationPriority(b) - notificationPriority(a) ||\n Date.parse(b.publishedAt) - Date.parse(a.publishedAt) ||\n (a.id < b.id ? -1 : a.id > b.id ? 1 : 0)\n );\n}\n/** Refresh eligibility without promoting the previously delivered backlog. */\nexport function reconcileNotifications(\n state: NotificationState,\n feed: NotificationFeed,\n context: NotificationContext,\n): NotificationState {\n const eligible = feed.notifications.filter(\n (n) => matchNotification(n, context).matches,\n );\n const quiet = (n: CohortNotification) =>\n state.readIds.includes(n.id) || state.suppressedIds.includes(n.id);\n const active =\n eligible.find((n) => n.id === state.activeId && !quiet(n)) ??\n feed.notifications.find(\n (n) =>\n n.id === state.activeId &&\n !quiet(n) &&\n n.audiences.some((conditions) => {\n const entries = Object.entries(conditions);\n if (\n !entries.some(\n ([key]) =>\n context[key as keyof NotificationConditions] === undefined,\n )\n )\n return false;\n const known = Object.fromEntries(\n entries.filter(\n ([key]) =>\n context[key as keyof NotificationConditions] !== undefined,\n ),\n );\n return matchNotification({ ...n, audiences: [known] }, context)\n .matches;\n }),\n );\n const newcomer = eligible\n .filter((n) => !quiet(n) && !state.eligibleIds.includes(n.id))\n .sort(compareNotifications)[0];\n const winner =\n active &&\n (!newcomer ||\n notificationPriority(active) >= notificationPriority(newcomer))\n ? active\n : newcomer;\n return {\n ...state,\n eligibleIds: eligible.map((n) => n.id),\n activeId: winner?.id ?? null,\n };\n}\n/** Reading or closing the highlight quiets its delivered backlog, not future IDs. */\nexport function acknowledgeNotification(\n state: NotificationState,\n noticeId: string,\n): NotificationState {\n if (!state.eligibleIds.includes(noticeId)) return state;\n const highlighted = state.activeId === noticeId;\n return {\n ...state,\n readIds: [...new Set([...state.readIds, noticeId])],\n suppressedIds: [\n ...new Set([\n ...state.suppressedIds,\n ...(highlighted ? state.eligibleIds : [noticeId]),\n ]),\n ],\n activeId: highlighted ? null : state.activeId,\n };\n}\n/** Explain targeting using the same rules in CI and the Inspector. */\nexport function matchNotification(\n notice: CohortNotification,\n context: NotificationContext,\n): { matches: boolean; reasons: string[] } {\n if (!context.development)\n return { matches: false, reasons: [\"Development only\"] };\n if (\n !context.sdkVersion ||\n !valid(context.sdkVersion) ||\n prerelease(context.sdkVersion) !== null\n )\n return {\n matches: false,\n reasons: [\"Known stable SDK required\"],\n };\n const reasons: string[] = [];\n const matches = notice.audiences.some((conditions) => {\n return Object.entries(conditions).every(([field, expected]) => {\n const actual = context[field as keyof NotificationConditions];\n const matches =\n field === \"sdkVersion\"\n ? actual !== undefined &&\n valid(actual) !== null &&\n prerelease(actual) === null &&\n satisfies(actual, expected)\n : actual === expected;\n if (!matches)\n reasons.push(\n `${field} ${actual === undefined ? \"is unknown\" : `requires ${expected} (got ${actual})`}`,\n );\n return matches;\n });\n });\n return {\n matches,\n reasons: matches ? [] : reasons,\n };\n}\n"],"mappings":"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"}
@@ -13,12 +13,11 @@
13
13
  * The CLI resolves `onboard start` against a prompt graph, so the two surfaces
14
14
  * must not drift — if the CLI's entry point changes, both change together.
15
15
  *
16
- * Two lines, and deliberately nothing else. It used to open by telling the
17
- * agent to identify itself and pass the slug as a flag -- two sentences of
18
- * instruction to a machine, in the one piece of text a human reads, decides on
19
- * and pastes. The graph asks for the slug itself now, with `onboard identify`,
20
- * which is the surface that talks to the agent for the rest of the run
21
- * (Intelligence OSS-1157).
16
+ * One sentence, and deliberately nothing else. It used to carry the `npx`
17
+ * command itself, which put a command line and a telemetry id in the one piece
18
+ * of text a human reads, decides on and pastes. The command now lives in the
19
+ * document the URL serves, with the run id already written into it
20
+ * (PE-216/PE-217).
22
21
  *
23
22
  * This one stays generic, and so do the docs and Intelligence web-app copies
24
23
  * of it (OSS-1150). Those three are entry points for a developer with no
@@ -29,9 +28,8 @@
29
28
  * `FEATURE_ONBOARDING_PROMPT_TEMPLATE` below.
30
29
  */
31
30
  const RUN_ID_PLACEHOLDER = "<run-id>";
32
- const ONBOARDING_PROMPT_TEMPLATE = `Help me get started with CopilotKit. Run this command and follow the instructions:
33
-
34
- npx --yes copilotkit@latest onboard start --run ${RUN_ID_PLACEHOLDER}`;
31
+ const ONBOARDING_PROMPT_ORIGIN = "https://copilotkit.ai";
32
+ const ONBOARDING_PROMPT_TEMPLATE = `Read ${ONBOARDING_PROMPT_ORIGIN}/onboarding-prompts/${RUN_ID_PLACEHOLDER} and help me get set up.`;
35
33
  /** Length and alphabet are the CLI's, so a run id copied here resolves there. */
36
34
  const RUN_ID_LENGTH = 12;
37
35
  /**
@@ -92,15 +90,11 @@ const INTENT_PLACEHOLDER = "<intent>";
92
90
  * here would duplicate all of that and then drift from it the next time the
93
91
  * underlying API changes.
94
92
  *
95
- * What is left is the caller's own business: identify yourself, run this
96
- * command, and the standing permission for the session check that the
97
- * developer granted by copying the prompt.
93
+ * The session-check permission the developer granted by copying this, and the
94
+ * credentials warning beside it, moved with the command into the document the
95
+ * URL serves. They are not dropped.
98
96
  */
99
- const FEATURE_ONBOARDING_PROMPT_TEMPLATE = `Help me set this up in my CopilotKit app. Run this command and follow the instructions:
100
-
101
- npx --yes copilotkit@latest onboard start --run ${RUN_ID_PLACEHOLDER} --intent ${INTENT_PLACEHOLDER}
102
-
103
- If it requires a CopilotKit CLI session check, you have permission to run it. Never reveal credentials.`;
97
+ const FEATURE_ONBOARDING_PROMPT_TEMPLATE = `Read ${ONBOARDING_PROMPT_ORIGIN}/onboarding-prompts/${RUN_ID_PLACEHOLDER}?intent=${INTENT_PLACEHOLDER} and help me set this up.`;
104
98
  /** Bind one run id and one tile's feature outcome into the copied prompt. */
105
99
  function createFeatureOnboardingPrompt(serviceId, runId) {
106
100
  return FEATURE_ONBOARDING_PROMPT_TEMPLATE.replace(RUN_ID_PLACEHOLDER, runId).replace(INTENT_PLACEHOLDER, FEATURE_ONBOARDING_INTENT[serviceId]);