@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
|
@@ -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":";;;AAuCA,SAAgB,yBAA4C;AAC1D,QAAO;EACL,eAAe;EACf,aAAa,EAAE;EACf,SAAS,EAAE;EACX,eAAe,EAAE;EACjB,UAAU;EACX;;AAEH,MAAM,aAAmD;CACvD,KAAK;CACL,QAAQ;CACR,MAAM;CACN,QAAQ;CACT;AACD,MAAM,kBAAqD;CACzD,WAAW;EAAC;EAAS;EAAO;EAAU;CACtC,cAAc,CAAC,WAAW,WAAW;CACrC,YAAY,CAAC,WAAW,cAAc;CACvC;AACD,SAAS,OAAO,OAAkD;AAChE,QAAO,UAAU,QAAQ,OAAO,UAAU,YAAY,CAAC,MAAM,QAAQ,MAAM;;AAE7E,SAAS,SAAS,OAAgC,MAAyB;AACzE,QAAO,OAAO,KAAK,MAAM,CAAC,OAAO,QAAQ,KAAK,SAAS,IAAI,CAAC;;AAE9D,SAAS,KAAK,OAAgB,MAAM,KAAsB;AACxD,QACE,OAAO,UAAU,YAAY,MAAM,MAAM,CAAC,SAAS,KAAK,MAAM,UAAU;;AAG5E,SAAgB,iBAAiB,OAAiC;AAChE,QACE,OAAO,UAAU,YACjB,6EAA6E,KAC3E,MACD;;AAGL,SAAS,IAAI,OAAmC;AAC9C,QACE,MAAM,QAAQ,MAAM,IACpB,MAAM,MAAM,iBAAiB,IAC7B,IAAI,IAAI,MAAM,CAAC,SAAS,MAAM;;;AAIlC,SAAgB,4BACd,OACiC;AACjC,KAAI,CAAC,OAAO,MAAM,CAAE,QAAO;AAC3B,KAAI,MAAM,eAAe,UAAa,MAAM,cAAc,OACxD,QAAO;AACT,QAAO,OAAO,QAAQ,MAAM,CAAC,OAAO,CAAC,KAAK,cAAc;AACtD,MAAI,CAAC,KAAK,SAAS,CAAE,QAAO;AAC5B,MAAI,QAAQ,aAAc,+BAAkB,SAAS,KAAK;AAC1D,MAAI,QAAQ,OAAQ,QAAO,6BAA6B,KAAK,SAAS;AACtE,SACE,OAAO,UAAU,eAAe,KAAK,iBAAiB,IAAI,KACzD,gBAAgB,MAAM,SAAS,SAAS,IAAI;GAE/C;;;AAGJ,SAAgB,sBAAsB,OAAyC;AAC7E,KACE,CAAC,OAAO,MAAM,IACd,CAAC,SAAS,OAAO,CAAC,iBAAiB,gBAAgB,CAAC,IACpD,MAAM,kBAAkB,EAExB,QAAO;AACT,KAAI,CAAC,MAAM,QAAQ,MAAM,cAAc,IAAI,MAAM,cAAc,SAAS,IACtE,QAAO;CACT,MAAM,gBAAsC,EAAE;AAC9C,MAAK,MAAM,UAAU,MAAM,eAAe;AACxC,MACE,CAAC,OAAO,OAAO,IACf,CAAC,SAAS,QAAQ;GAChB;GACA;GACA;GACA;GACA;GACA;GACA;GACD,CAAC,CAEF,QAAO;AACT,MACE,CAAC,iBAAiB,OAAO,GAAG,IAC5B,CAAC,KAAK,OAAO,MAAM,IACnB,CAAC,KAAK,OAAO,MAAM,IAAQ,IAC3B,CAAC,KAAK,OAAO,YAAY,CAEzB,QAAO;AACT,MACE,CAAC,2CAA2C,KAAK,OAAO,YAAY,IACpE,CAAC,OAAO,SAAS,KAAK,MAAM,OAAO,YAAY,CAAC,IAChD,IAAI,KAAK,OAAO,YAAY,CAAC,aAAa,KAAK,OAAO,YAEtD,QAAO;AACT,MACE,CAAC,MAAM,QAAQ,OAAO,UAAU,IAChC,CAAC,OAAO,UAAU,UAClB,OAAO,UAAU,SAAS,OAC1B,CAAC,OAAO,UAAU,MAAM,4BAA4B,CAEpD,QAAO;AACT,MACE,CAAC,KAAK,OAAO,SAAS,IACtB,CAAC,OAAO,UAAU,eAAe,KAAK,YAAY,OAAO,SAAS,CAElE,QAAO;AACT,MACE,OAAO,qBAAqB,WAC3B,OAAO,OAAO,qBAAqB,YAClC,CAAC,OAAO,cAAc,OAAO,iBAAiB,IAC9C,OAAO,mBAAmB,GAE5B,QAAO;AACT,MAAI,cAAc,MAAM,MAAM,EAAE,OAAO,OAAO,GAAG,CAAE,QAAO;AAC1D,gBAAc,KAAK;GACjB,IAAI,OAAO;GACX,OAAO,OAAO;GACd,MAAM,OAAO;GACb,aAAa,OAAO;GACpB,WAAW,OAAO;GAClB,UAAU,OAAO;GACjB,GAAI,OAAO,qBAAqB,SAC5B,EAAE,GACF,EAAE,kBAAkB,OAAO,kBAAkB;GAClD,CAAC;;AAEJ,QAAO;EAAE,eAAe;EAAG;EAAe;;;AAG5C,SAAgB,uBACd,OAC0B;AAC1B,KACE,CAAC,OAAO,MAAM,IACd,MAAM,kBAAkB,KACxB,CAAC,IAAI,MAAM,YAAY,IACvB,CAAC,IAAI,MAAM,QAAQ,IACnB,CAAC,IAAI,MAAM,cAAc,IACxB,MAAM,aAAa,QAAQ,CAAC,iBAAiB,MAAM,SAAS,CAE7D,QAAO;AACT,QAAO;EACL,eAAe;EACf,aAAa,MAAM;EACnB,SAAS,MAAM;EACf,eAAe,MAAM;EACrB,UAAU,MAAM;EACjB;;;AAGH,SAAgB,qBAAqB,QAAoC;AACvE,QAAO,OAAO,oBAAoB,WAAW,OAAO;;;AAGtD,SAAgB,qBACd,GACA,GACQ;AACR,QACE,qBAAqB,EAAE,GAAG,qBAAqB,EAAE,IACjD,KAAK,MAAM,EAAE,YAAY,GAAG,KAAK,MAAM,EAAE,YAAY,KACpD,EAAE,KAAK,EAAE,KAAK,KAAK,EAAE,KAAK,EAAE,KAAK,IAAI;;;AAI1C,SAAgB,uBACd,OACA,MACA,SACmB;CACnB,MAAM,WAAW,KAAK,cAAc,QACjC,MAAM,kBAAkB,GAAG,QAAQ,CAAC,QACtC;CACD,MAAM,SAAS,MACb,MAAM,QAAQ,SAAS,EAAE,GAAG,IAAI,MAAM,cAAc,SAAS,EAAE,GAAG;CACpE,MAAM,SACJ,SAAS,MAAM,MAAM,EAAE,OAAO,MAAM,YAAY,CAAC,MAAM,EAAE,CAAC,IAC1D,KAAK,cAAc,MAChB,MACC,EAAE,OAAO,MAAM,YACf,CAAC,MAAM,EAAE,IACT,EAAE,UAAU,MAAM,eAAe;EAC/B,MAAM,UAAU,OAAO,QAAQ,WAAW;AAC1C,MACE,CAAC,QAAQ,MACN,CAAC,SACA,QAAQ,SAAyC,OACpD,CAED,QAAO;EACT,MAAM,QAAQ,OAAO,YACnB,QAAQ,QACL,CAAC,SACA,QAAQ,SAAyC,OACpD,CACF;AACD,SAAO,kBAAkB;GAAE,GAAG;GAAG,WAAW,CAAC,MAAM;GAAE,EAAE,QAAQ,CAC5D;GACH,CACL;CACH,MAAM,WAAW,SACd,QAAQ,MAAM,CAAC,MAAM,EAAE,IAAI,CAAC,MAAM,YAAY,SAAS,EAAE,GAAG,CAAC,CAC7D,KAAK,qBAAqB,CAAC;CAC9B,MAAM,SACJ,WACC,CAAC,YACA,qBAAqB,OAAO,IAAI,qBAAqB,SAAS,IAC5D,SACA;AACN,QAAO;EACL,GAAG;EACH,aAAa,SAAS,KAAK,MAAM,EAAE,GAAG;EACtC,UAAU,QAAQ,MAAM;EACzB;;;AAGH,SAAgB,wBACd,OACA,UACmB;AACnB,KAAI,CAAC,MAAM,YAAY,SAAS,SAAS,CAAE,QAAO;CAClD,MAAM,cAAc,MAAM,aAAa;AACvC,QAAO;EACL,GAAG;EACH,SAAS,CAAC,GAAG,IAAI,IAAI,CAAC,GAAG,MAAM,SAAS,SAAS,CAAC,CAAC;EACnD,eAAe,CACb,GAAG,IAAI,IAAI,CACT,GAAG,MAAM,eACT,GAAI,cAAc,MAAM,cAAc,CAAC,SAAS,CACjD,CAAC,CACH;EACD,UAAU,cAAc,OAAO,MAAM;EACtC;;;AAGH,SAAgB,kBACd,QACA,SACyC;AACzC,KAAI,CAAC,QAAQ,YACX,QAAO;EAAE,SAAS;EAAO,SAAS,CAAC,mBAAmB;EAAE;AAC1D,KACE,CAAC,QAAQ,cACT,mBAAO,QAAQ,WAAW,2BACf,QAAQ,WAAW,KAAK,KAEnC,QAAO;EACL,SAAS;EACT,SAAS,CAAC,4BAA4B;EACvC;CACH,MAAM,UAAoB,EAAE;CAC5B,MAAM,UAAU,OAAO,UAAU,MAAM,eAAe;AACpD,SAAO,OAAO,QAAQ,WAAW,CAAC,OAAO,CAAC,OAAO,cAAc;GAC7D,MAAM,SAAS,QAAQ;GACvB,MAAM,UACJ,UAAU,eACN,WAAW,4BACL,OAAO,KAAK,+BACP,OAAO,KAAK,8BACb,QAAQ,SAAS,GAC3B,WAAW;AACjB,OAAI,CAAC,QACH,SAAQ,KACN,GAAG,MAAM,GAAG,WAAW,SAAY,eAAe,YAAY,SAAS,QAAQ,OAAO,KACvF;AACH,UAAO;IACP;GACF;AACF,QAAO;EACL;EACA,SAAS,UAAU,EAAE,GAAG;EACzB"}
|
|
@@ -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
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
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
|
|
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
|
-
*
|
|
96
|
-
*
|
|
97
|
-
*
|
|
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 = `
|
|
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]);
|