@copilotkit/web-inspector 1.70.0 → 1.70.1

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.
@@ -1,13 +1,26 @@
1
1
 
2
2
  //#region src/lib/home-briefing.ts
3
+ /**
4
+ * A self-contained handoff for a coding agent. It answers the onboarding
5
+ * workflow's session and feedback consent gates up front, while keeping the
6
+ * feature guide—not a generic product-onboarding branch—in control of the
7
+ * implementation plan.
8
+ */
9
+ function homeFeatureImplementationPrompt(service, context) {
10
+ return `Identify your coding-agent slug (for example, \`codex\` or \`claude-code\`). From the target project root, run \`npx copilotkit@latest onboard start --run ${context.onboardingRunId} --coding-agent <coding-agent-slug>\` and use its output as onboarding context. If it requires a CopilotKit CLI session check, you have permission to run it; never reveal credentials or send optional diagnostic feedback reports.
11
+
12
+ This task is specifically to enable ${service.label}, not to re-onboard the application. First inspect the existing CopilotKit runtime, provider, agent, and UI wiring, and confirm that ${service.label} is not already enabled. Then read the ${service.label} guide (${service.docsUrl}) and make a short plan before editing. Preserve the project's framework, package manager, installed CopilotKit version, existing agent IDs, routes, provider layout, and working behavior. Do not create, select, or alter a CopilotKit Intelligence project—or add Intelligence configuration—unless this feature's official guide explicitly requires it or the user asks.
13
+
14
+ Implement the smallest complete integration: wire every feature-required client and runtime configuration into the chat-to-agent path people already use, reuse local patterns, and do not invent environment values or hardcode secrets. Add or update focused tests and run the relevant project checks. Finish only after local validation proves ${service.label} works—not merely that the code compiles. Use a feature-specific runtime or Inspector capability check and, when the feature supports one, a representative UI interaction that proves the user-facing result. If the project overrides default rendering (for example, with a wildcard tool renderer), make that override compatible with this feature; a capability flag alone is not success. Summarize the changed files, validation, and any manual setup still required.`;
15
+ }
3
16
  const SERVICE_DOCS_URL = {
4
17
  threads: "https://docs.copilotkit.ai/threads",
5
- memory: "https://docs.copilotkit.ai/premium/intelligence-platform",
18
+ memory: "https://docs.copilotkit.ai/intelligence/intelligence-platform",
6
19
  a2ui: "https://docs.copilotkit.ai/generative-ui/a2ui",
7
20
  "open-gen-ui": "https://docs.copilotkit.ai/generative-ui/open-generative-ui",
8
- suggestions: "https://docs.copilotkit.ai/agentic-chat-ui",
21
+ suggestions: "https://docs.copilotkit.ai/reference/hooks/useConfigureSuggestions",
9
22
  audio: "https://docs.copilotkit.ai/voice",
10
- websocket: "https://docs.copilotkit.ai/premium/intelligence-platform"
23
+ websocket: "https://docs.copilotkit.ai/intelligence/intelligence-platform"
11
24
  };
12
25
  /** Return the Home hero button for a trusted metadata action. */
13
26
  function homeHeroActionFromMetadata(action) {
@@ -240,7 +253,7 @@ function buildHomeModel(input) {
240
253
  },
241
254
  {
242
255
  id: "memory",
243
- label: "Memory",
256
+ label: "Learning",
244
257
  enabled: intelligenceConnected && input.memoriesOn,
245
258
  docsUrl: SERVICE_DOCS_URL.memory
246
259
  },
@@ -264,7 +277,7 @@ function buildHomeModel(input) {
264
277
  },
265
278
  {
266
279
  id: "audio",
267
- label: "Audio",
280
+ label: "Voice",
268
281
  enabled: input.audioOn,
269
282
  docsUrl: SERVICE_DOCS_URL.audio
270
283
  },
@@ -286,5 +299,6 @@ function buildHomeModel(input) {
286
299
 
287
300
  //#endregion
288
301
  exports.buildHomeModel = buildHomeModel;
302
+ exports.homeFeatureImplementationPrompt = homeFeatureImplementationPrompt;
289
303
  exports.runtimeConnectionNeedsAttention = runtimeConnectionNeedsAttention;
290
304
  //# sourceMappingURL=home-briefing.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"home-briefing.cjs","names":[],"sources":["../../src/lib/home-briefing.ts"],"sourcesContent":["import type { InspectorMetadataProjection } from \"./inspector-metadata.js\";\n\nexport type HomeHeroActionKind =\n | \"enable_intelligence\"\n | \"manage_plan\"\n | \"renew\";\n\nexport type HomeHeroAction = {\n kind: HomeHeroActionKind;\n url: string;\n label: string;\n};\n\nexport type HomeConnection = \"connected\" | \"disconnected\";\n\nexport type HomeRuntimeConnectionState =\n | \"connected\"\n | \"connecting\"\n | \"disconnected\"\n | \"error\"\n | \"unavailable\";\n\nexport type HomeRuntimeHealthTone = \"success\" | \"active\" | \"error\" | \"muted\";\n\nexport type HomeServiceId =\n | \"threads\"\n | \"memory\"\n | \"a2ui\"\n | \"open-gen-ui\"\n | \"suggestions\"\n | \"audio\"\n | \"websocket\";\n\nexport type HomeServiceTile = {\n id: HomeServiceId;\n label: string;\n enabled: boolean;\n url?: string;\n docsUrl: string;\n};\n\nexport type HomeModel = {\n hero: {\n connection: HomeConnection;\n title: string;\n body: string;\n action?: HomeHeroAction;\n };\n project?: {\n organizationName: string;\n projectName: string;\n planLabel?: string;\n license: string;\n usage?: {\n used: number;\n limitLabel: string;\n ratio?: number;\n };\n };\n projectLinked: boolean;\n runtime: {\n url?: string;\n health: {\n state: \"healthy\" | \"checking\" | \"offline\" | \"error\" | \"unavailable\";\n label: string;\n runtime: {\n label: string;\n tone: HomeRuntimeHealthTone;\n };\n liveUpdates: {\n label: string;\n tone: HomeRuntimeHealthTone;\n };\n lastEvent: {\n label: string;\n tone: HomeRuntimeHealthTone;\n id?: string;\n agentId?: string;\n type?: string;\n timestamp?: number;\n };\n };\n };\n services: HomeServiceTile[];\n news: {\n title: string;\n previewText: string;\n documentHtml?: string;\n empty: boolean;\n };\n};\n\nexport type HomeBriefingInput = {\n intelligenceConnected: boolean;\n threadsAvailable: boolean;\n metadata: InspectorMetadataProjection;\n runtimeUrl?: string;\n runtimeConnectionState: HomeRuntimeConnectionState;\n lastRuntimeEvent?: {\n id: string;\n agentId: string;\n type: string;\n timestamp: number;\n };\n memoriesOn: boolean;\n a2uiOn: boolean;\n openGenUiOn: boolean;\n suggestionsOn: boolean;\n audioOn: boolean;\n websocketUrl?: string;\n announcementPreviewText?: string;\n announcementMarkdown?: string;\n announcementHtml?: string;\n intelligenceSignupUrl?: string;\n};\n\nconst SERVICE_DOCS_URL: Record<HomeServiceId, string> = {\n threads: \"https://docs.copilotkit.ai/threads\",\n memory: \"https://docs.copilotkit.ai/premium/intelligence-platform\",\n a2ui: \"https://docs.copilotkit.ai/generative-ui/a2ui\",\n \"open-gen-ui\": \"https://docs.copilotkit.ai/generative-ui/open-generative-ui\",\n suggestions: \"https://docs.copilotkit.ai/agentic-chat-ui\",\n audio: \"https://docs.copilotkit.ai/voice\",\n websocket: \"https://docs.copilotkit.ai/premium/intelligence-platform\",\n};\n\n/** Return the Home hero button for a trusted metadata action. */\nexport function homeHeroActionFromMetadata(action: {\n kind: HomeHeroActionKind;\n url: string;\n}): HomeHeroAction {\n if (action.kind === \"manage_plan\") {\n return { kind: action.kind, url: action.url, label: \"Manage plan\" };\n }\n\n if (action.kind === \"renew\") {\n return { kind: action.kind, url: action.url, label: \"Renew plan\" };\n }\n\n return {\n kind: action.kind,\n url: action.url,\n label: \"Setup Intelligence\",\n };\n}\n\n/** Return a short preview from markdown, without links or headings. */\nexport function announcementPreview(markdown: string, maxLength = 140): string {\n const plain = markdown\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n .replace(/[#*_`]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n if (plain.length <= maxLength) {\n return plain;\n }\n\n return `${plain.slice(0, maxLength).trimEnd()}…`;\n}\n\nfunction connectIntelligenceAction(\n action: HomeHeroAction | undefined,\n connectUrl?: string,\n licenseState?: InspectorMetadataProjection[\"licenseState\"],\n): HomeHeroAction | undefined {\n if (action) {\n return action;\n }\n\n if (connectUrl && (licenseState === \"none\" || licenseState === \"unknown\")) {\n return homeHeroActionFromMetadata({\n kind: \"enable_intelligence\",\n url: connectUrl,\n });\n }\n\n return undefined;\n}\n\nfunction heroForState(args: {\n connected: boolean;\n action?: HomeHeroAction;\n connectUrl?: string;\n licenseState: InspectorMetadataProjection[\"licenseState\"];\n}): HomeModel[\"hero\"] {\n if (!args.connected) {\n const renewing = args.action?.kind === \"renew\";\n return {\n connection: \"disconnected\",\n // Not \"Intelligence is not setup\" — that reads as a defect in the tool,\n // and a defect gets dismissed. The heading names the product; the\n // argument for it is made by the rotating copy on Home, which changes\n // with the picture beside it.\n title: renewing ? \"Renew Intelligence\" : \"CopilotKit Intelligence\",\n // In install mode this is NOT the visible paragraph. The visible copy\n // rotates every few seconds, which would make a screen reader announce a\n // new sentence four times a loop, so the rotating text is hidden from\n // assistive tech and this one stable sentence is exposed instead. It has\n // to carry the whole chain on its own.\n body: renewing\n ? \"Renew Intelligence to restore persistent Threads and Memory.\"\n : \"Intelligence keeps every thread your users have, finds evidence-backed patterns in them, and proposes skills you approve before your agent uses them.\",\n action: connectIntelligenceAction(\n args.action,\n args.connectUrl,\n args.licenseState,\n ),\n };\n }\n\n return {\n connection: \"connected\",\n title: \"Connected to Intelligence\",\n body: \"Use Workbench to inspect threads and memory.\",\n action:\n args.action?.kind === \"enable_intelligence\" ? undefined : args.action,\n };\n}\n\nfunction usageFromMetadata(metadata: InspectorMetadataProjection):\n | {\n used: number;\n limitLabel: string;\n ratio?: number;\n }\n | undefined {\n const usage = metadata.usage;\n if (!usage) {\n return undefined;\n }\n\n if (usage.limit.kind === \"finite\") {\n return {\n used: usage.used,\n limitLabel: `${usage.used} / ${usage.limit.value}`,\n ratio: usage.limit.value === 0 ? 1 : usage.used / usage.limit.value,\n };\n }\n\n if (usage.limit.kind === \"unlimited\") {\n return {\n used: usage.used,\n limitLabel: `${usage.used} / unlimited`,\n };\n }\n\n return {\n used: usage.used,\n limitLabel: `${usage.used} used`,\n };\n}\n\nfunction newsFromAnnouncement(args: {\n previewText?: string;\n markdown?: string;\n html?: string;\n}): HomeModel[\"news\"] {\n const markdown = args.markdown?.trim();\n if (markdown && args.html) {\n const heading = markdown.match(/^#{1,3}\\s+(.+)$/m)?.[1]?.trim();\n return {\n title: heading || \"The latest from CopilotKit\",\n previewText:\n args.previewText?.trim() || announcementPreview(markdown, 160),\n documentHtml: args.html,\n empty: false,\n };\n }\n\n return {\n title: \"You're all caught up\",\n previewText: \"Latest CopilotKit updates will appear here.\",\n empty: true,\n };\n}\n\nfunction runtimeEventSignal(\n event: HomeBriefingInput[\"lastRuntimeEvent\"],\n): HomeModel[\"runtime\"][\"health\"][\"lastEvent\"] {\n if (!event) {\n return { label: \"No events yet\", tone: \"muted\" };\n }\n\n const eventDetails = {\n id: event.id,\n agentId: event.agentId,\n type: event.type,\n timestamp: event.timestamp,\n };\n\n if (event.type === \"RUN_ERROR\" || event.type === \"ERROR\") {\n return {\n label: event.type === \"RUN_ERROR\" ? \"Run error\" : \"Error\",\n tone: \"error\",\n ...eventDetails,\n };\n }\n\n if (event.type === \"RUN_FINISHED\") {\n return {\n label: \"Run completed\",\n tone: \"success\",\n ...eventDetails,\n };\n }\n\n if (\n event.type.endsWith(\"_START\") ||\n event.type.endsWith(\"_STARTED\") ||\n event.type.endsWith(\"_CONTENT\") ||\n event.type.endsWith(\"_ARGS\") ||\n event.type.endsWith(\"_DELTA\")\n ) {\n return {\n label: \"Last event is in progress\",\n tone: \"active\",\n ...eventDetails,\n };\n }\n\n return {\n label: \"Last event received\",\n tone: \"success\",\n ...eventDetails,\n };\n}\n\n/**\n * Whether the runtime *connection* needs attention — the single condition\n * shared by System Health and the launcher's error signal, so the two can\n * never disagree about whether the wiring is broken.\n *\n * Exactly one state counts. `disconnected` is also the initial value, so\n * counting it would raise the signal on every page load; `connecting` is a\n * normal startup step; `unavailable` means no Core is attached, which is not\n * a defect of the developer's wiring.\n *\n * Note the deliberate asymmetry with `health.state`: a failed *run* also\n * drives System Health to \"Needs attention\" while the connection is fine.\n * That is an event rather than a state, and it is excluded from the launcher\n * on purpose — see the launcher-signal comments in index.ts.\n */\nexport function runtimeConnectionNeedsAttention(\n state: HomeRuntimeConnectionState,\n): boolean {\n return state === \"error\";\n}\n\nfunction runtimeHealthFromInput(\n input: HomeBriefingInput,\n): HomeModel[\"runtime\"][\"health\"] {\n const lastEvent = runtimeEventSignal(input.lastRuntimeEvent);\n\n if (input.runtimeConnectionState === \"connected\") {\n return {\n state: lastEvent.tone === \"error\" ? \"error\" : \"healthy\",\n label: lastEvent.tone === \"error\" ? \"Needs attention\" : \"Healthy\",\n runtime: { label: \"Available\", tone: \"success\" },\n liveUpdates: { label: \"Ready\", tone: \"success\" },\n lastEvent,\n };\n }\n\n if (input.runtimeConnectionState === \"connecting\") {\n return {\n state: \"checking\",\n label: \"Checking\",\n runtime: { label: \"Checking\", tone: \"active\" },\n liveUpdates: { label: \"Connecting\", tone: \"active\" },\n lastEvent,\n };\n }\n\n if (input.runtimeConnectionState === \"unavailable\") {\n return {\n state: \"unavailable\",\n label: \"Unavailable\",\n runtime: { label: \"Unavailable\", tone: \"muted\" },\n liveUpdates: { label: \"Not attached\", tone: \"muted\" },\n lastEvent,\n };\n }\n\n const needsAttention = runtimeConnectionNeedsAttention(\n input.runtimeConnectionState,\n );\n return {\n state: needsAttention ? \"error\" : \"offline\",\n label: needsAttention ? \"Runtime error\" : \"Offline\",\n runtime: { label: \"Offline\", tone: \"error\" },\n liveUpdates: {\n label: needsAttention ? \"Error\" : \"Disconnected\",\n tone: \"error\",\n },\n lastEvent,\n };\n}\n\n/** Build the Home briefing from data the Inspector already has. */\nexport function buildHomeModel(input: HomeBriefingInput): HomeModel {\n const metadataAction =\n input.metadata.threadsFooterAction ?? input.metadata.lockedAction;\n const action = metadataAction\n ? homeHeroActionFromMetadata({\n kind: metadataAction.kind,\n url: metadataAction.url,\n })\n : undefined;\n\n const identity = input.metadata.identity;\n const projectLinked = Boolean(identity);\n const intelligenceConnected =\n input.metadata.licenseState === \"valid\" ||\n (input.metadata.licenseState === \"unknown\" && input.intelligenceConnected);\n const usage = usageFromMetadata(input.metadata);\n\n return {\n hero: heroForState({\n connected: intelligenceConnected,\n action,\n connectUrl: input.intelligenceSignupUrl,\n licenseState: input.metadata.licenseState,\n }),\n projectLinked,\n project: identity\n ? {\n organizationName: identity.organizationName,\n projectName: identity.projectName,\n planLabel: input.metadata.plan?.label,\n license: input.metadata.licenseState,\n usage,\n }\n : input.metadata.plan || usage\n ? {\n organizationName: \"Not linked\",\n projectName: \"This runtime is not linked to a project\",\n planLabel: input.metadata.plan?.label,\n license: input.metadata.licenseState,\n usage,\n }\n : undefined,\n runtime: {\n url: input.runtimeUrl,\n health: runtimeHealthFromInput(input),\n },\n services: [\n {\n id: \"threads\",\n label: \"Threads\",\n enabled: intelligenceConnected && input.threadsAvailable,\n url: input.runtimeUrl,\n docsUrl: SERVICE_DOCS_URL.threads,\n },\n {\n id: \"memory\",\n label: \"Memory\",\n enabled: intelligenceConnected && input.memoriesOn,\n docsUrl: SERVICE_DOCS_URL.memory,\n },\n {\n id: \"a2ui\",\n label: \"A2UI\",\n enabled: input.a2uiOn,\n docsUrl: SERVICE_DOCS_URL.a2ui,\n },\n {\n id: \"open-gen-ui\",\n label: \"Open Gen UI\",\n enabled: input.openGenUiOn,\n docsUrl: SERVICE_DOCS_URL[\"open-gen-ui\"],\n },\n {\n id: \"suggestions\",\n label: \"Suggestions\",\n enabled: input.suggestionsOn,\n docsUrl: SERVICE_DOCS_URL.suggestions,\n },\n {\n id: \"audio\",\n label: \"Audio\",\n enabled: input.audioOn,\n docsUrl: SERVICE_DOCS_URL.audio,\n },\n {\n id: \"websocket\",\n label: \"Websocket\",\n enabled: intelligenceConnected && Boolean(input.websocketUrl),\n url: input.websocketUrl,\n docsUrl: SERVICE_DOCS_URL.websocket,\n },\n ],\n news: newsFromAnnouncement({\n previewText: input.announcementPreviewText,\n markdown: input.announcementMarkdown,\n html: input.announcementHtml ?? undefined,\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"home-briefing.cjs","names":[],"sources":["../../src/lib/home-briefing.ts"],"sourcesContent":["import type { InspectorMetadataProjection } from \"./inspector-metadata.js\";\n\nexport type HomeHeroActionKind =\n | \"enable_intelligence\"\n | \"manage_plan\"\n | \"renew\";\n\nexport type HomeHeroAction = {\n kind: HomeHeroActionKind;\n url: string;\n label: string;\n};\n\nexport type HomeConnection = \"connected\" | \"disconnected\";\n\nexport type HomeRuntimeConnectionState =\n | \"connected\"\n | \"connecting\"\n | \"disconnected\"\n | \"error\"\n | \"unavailable\";\n\nexport type HomeRuntimeHealthTone = \"success\" | \"active\" | \"error\" | \"muted\";\n\nexport type HomeServiceId =\n | \"threads\"\n | \"memory\"\n | \"a2ui\"\n | \"open-gen-ui\"\n | \"suggestions\"\n | \"audio\"\n | \"websocket\";\n\nexport type HomeServiceTile = {\n id: HomeServiceId;\n label: string;\n enabled: boolean;\n url?: string;\n docsUrl: string;\n};\n\nexport type HomeFeatureImplementationPromptContext = {\n onboardingRunId: string;\n};\n\n/**\n * A self-contained handoff for a coding agent. It answers the onboarding\n * workflow's session and feedback consent gates up front, while keeping the\n * feature guide—not a generic product-onboarding branch—in control of the\n * implementation plan.\n */\nexport function homeFeatureImplementationPrompt(\n service: Pick<HomeServiceTile, \"label\" | \"docsUrl\">,\n context: HomeFeatureImplementationPromptContext,\n): string {\n return `Identify your coding-agent slug (for example, \\`codex\\` or \\`claude-code\\`). From the target project root, run \\`npx copilotkit@latest onboard start --run ${context.onboardingRunId} --coding-agent <coding-agent-slug>\\` and use its output as onboarding context. If it requires a CopilotKit CLI session check, you have permission to run it; never reveal credentials or send optional diagnostic feedback reports.\n\nThis task is specifically to enable ${service.label}, not to re-onboard the application. First inspect the existing CopilotKit runtime, provider, agent, and UI wiring, and confirm that ${service.label} is not already enabled. Then read the ${service.label} guide (${service.docsUrl}) and make a short plan before editing. Preserve the project's framework, package manager, installed CopilotKit version, existing agent IDs, routes, provider layout, and working behavior. Do not create, select, or alter a CopilotKit Intelligence project—or add Intelligence configuration—unless this feature's official guide explicitly requires it or the user asks.\n\nImplement the smallest complete integration: wire every feature-required client and runtime configuration into the chat-to-agent path people already use, reuse local patterns, and do not invent environment values or hardcode secrets. Add or update focused tests and run the relevant project checks. Finish only after local validation proves ${service.label} works—not merely that the code compiles. Use a feature-specific runtime or Inspector capability check and, when the feature supports one, a representative UI interaction that proves the user-facing result. If the project overrides default rendering (for example, with a wildcard tool renderer), make that override compatible with this feature; a capability flag alone is not success. Summarize the changed files, validation, and any manual setup still required.`;\n}\n\nexport type HomeModel = {\n hero: {\n connection: HomeConnection;\n title: string;\n body: string;\n action?: HomeHeroAction;\n };\n project?: {\n organizationName: string;\n projectName: string;\n planLabel?: string;\n license: string;\n usage?: {\n used: number;\n limitLabel: string;\n ratio?: number;\n };\n };\n projectLinked: boolean;\n runtime: {\n url?: string;\n health: {\n state: \"healthy\" | \"checking\" | \"offline\" | \"error\" | \"unavailable\";\n label: string;\n runtime: {\n label: string;\n tone: HomeRuntimeHealthTone;\n };\n liveUpdates: {\n label: string;\n tone: HomeRuntimeHealthTone;\n };\n lastEvent: {\n label: string;\n tone: HomeRuntimeHealthTone;\n id?: string;\n agentId?: string;\n type?: string;\n timestamp?: number;\n };\n };\n };\n services: HomeServiceTile[];\n news: {\n title: string;\n previewText: string;\n documentHtml?: string;\n empty: boolean;\n };\n};\n\nexport type HomeBriefingInput = {\n intelligenceConnected: boolean;\n threadsAvailable: boolean;\n metadata: InspectorMetadataProjection;\n runtimeUrl?: string;\n runtimeConnectionState: HomeRuntimeConnectionState;\n lastRuntimeEvent?: {\n id: string;\n agentId: string;\n type: string;\n timestamp: number;\n };\n memoriesOn: boolean;\n a2uiOn: boolean;\n openGenUiOn: boolean;\n suggestionsOn: boolean;\n audioOn: boolean;\n websocketUrl?: string;\n announcementPreviewText?: string;\n announcementMarkdown?: string;\n announcementHtml?: string;\n intelligenceSignupUrl?: string;\n};\n\nconst SERVICE_DOCS_URL: Record<HomeServiceId, string> = {\n threads: \"https://docs.copilotkit.ai/threads\",\n memory: \"https://docs.copilotkit.ai/intelligence/intelligence-platform\",\n a2ui: \"https://docs.copilotkit.ai/generative-ui/a2ui\",\n \"open-gen-ui\": \"https://docs.copilotkit.ai/generative-ui/open-generative-ui\",\n suggestions:\n \"https://docs.copilotkit.ai/reference/hooks/useConfigureSuggestions\",\n audio: \"https://docs.copilotkit.ai/voice\",\n websocket: \"https://docs.copilotkit.ai/intelligence/intelligence-platform\",\n};\n\n/** Return the Home hero button for a trusted metadata action. */\nexport function homeHeroActionFromMetadata(action: {\n kind: HomeHeroActionKind;\n url: string;\n}): HomeHeroAction {\n if (action.kind === \"manage_plan\") {\n return { kind: action.kind, url: action.url, label: \"Manage plan\" };\n }\n\n if (action.kind === \"renew\") {\n return { kind: action.kind, url: action.url, label: \"Renew plan\" };\n }\n\n return {\n kind: action.kind,\n url: action.url,\n label: \"Setup Intelligence\",\n };\n}\n\n/** Return a short preview from markdown, without links or headings. */\nexport function announcementPreview(markdown: string, maxLength = 140): string {\n const plain = markdown\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n .replace(/[#*_`]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n if (plain.length <= maxLength) {\n return plain;\n }\n\n return `${plain.slice(0, maxLength).trimEnd()}…`;\n}\n\nfunction connectIntelligenceAction(\n action: HomeHeroAction | undefined,\n connectUrl?: string,\n licenseState?: InspectorMetadataProjection[\"licenseState\"],\n): HomeHeroAction | undefined {\n if (action) {\n return action;\n }\n\n if (connectUrl && (licenseState === \"none\" || licenseState === \"unknown\")) {\n return homeHeroActionFromMetadata({\n kind: \"enable_intelligence\",\n url: connectUrl,\n });\n }\n\n return undefined;\n}\n\nfunction heroForState(args: {\n connected: boolean;\n action?: HomeHeroAction;\n connectUrl?: string;\n licenseState: InspectorMetadataProjection[\"licenseState\"];\n}): HomeModel[\"hero\"] {\n if (!args.connected) {\n const renewing = args.action?.kind === \"renew\";\n return {\n connection: \"disconnected\",\n // Not \"Intelligence is not setup\" — that reads as a defect in the tool,\n // and a defect gets dismissed. The heading names the product; the\n // argument for it is made by the rotating copy on Home, which changes\n // with the picture beside it.\n title: renewing ? \"Renew Intelligence\" : \"CopilotKit Intelligence\",\n // In install mode this is NOT the visible paragraph. The visible copy\n // rotates every few seconds, which would make a screen reader announce a\n // new sentence four times a loop, so the rotating text is hidden from\n // assistive tech and this one stable sentence is exposed instead. It has\n // to carry the whole chain on its own.\n body: renewing\n ? \"Renew Intelligence to restore persistent Threads and Memory.\"\n : \"Intelligence keeps every thread your users have, finds evidence-backed patterns in them, and proposes skills you approve before your agent uses them.\",\n action: connectIntelligenceAction(\n args.action,\n args.connectUrl,\n args.licenseState,\n ),\n };\n }\n\n return {\n connection: \"connected\",\n title: \"Connected to Intelligence\",\n body: \"Use Workbench to inspect threads and memory.\",\n action:\n args.action?.kind === \"enable_intelligence\" ? undefined : args.action,\n };\n}\n\nfunction usageFromMetadata(metadata: InspectorMetadataProjection):\n | {\n used: number;\n limitLabel: string;\n ratio?: number;\n }\n | undefined {\n const usage = metadata.usage;\n if (!usage) {\n return undefined;\n }\n\n if (usage.limit.kind === \"finite\") {\n return {\n used: usage.used,\n limitLabel: `${usage.used} / ${usage.limit.value}`,\n ratio: usage.limit.value === 0 ? 1 : usage.used / usage.limit.value,\n };\n }\n\n if (usage.limit.kind === \"unlimited\") {\n return {\n used: usage.used,\n limitLabel: `${usage.used} / unlimited`,\n };\n }\n\n return {\n used: usage.used,\n limitLabel: `${usage.used} used`,\n };\n}\n\nfunction newsFromAnnouncement(args: {\n previewText?: string;\n markdown?: string;\n html?: string;\n}): HomeModel[\"news\"] {\n const markdown = args.markdown?.trim();\n if (markdown && args.html) {\n const heading = markdown.match(/^#{1,3}\\s+(.+)$/m)?.[1]?.trim();\n return {\n title: heading || \"The latest from CopilotKit\",\n previewText:\n args.previewText?.trim() || announcementPreview(markdown, 160),\n documentHtml: args.html,\n empty: false,\n };\n }\n\n return {\n title: \"You're all caught up\",\n previewText: \"Latest CopilotKit updates will appear here.\",\n empty: true,\n };\n}\n\nfunction runtimeEventSignal(\n event: HomeBriefingInput[\"lastRuntimeEvent\"],\n): HomeModel[\"runtime\"][\"health\"][\"lastEvent\"] {\n if (!event) {\n return { label: \"No events yet\", tone: \"muted\" };\n }\n\n const eventDetails = {\n id: event.id,\n agentId: event.agentId,\n type: event.type,\n timestamp: event.timestamp,\n };\n\n if (event.type === \"RUN_ERROR\" || event.type === \"ERROR\") {\n return {\n label: event.type === \"RUN_ERROR\" ? \"Run error\" : \"Error\",\n tone: \"error\",\n ...eventDetails,\n };\n }\n\n if (event.type === \"RUN_FINISHED\") {\n return {\n label: \"Run completed\",\n tone: \"success\",\n ...eventDetails,\n };\n }\n\n if (\n event.type.endsWith(\"_START\") ||\n event.type.endsWith(\"_STARTED\") ||\n event.type.endsWith(\"_CONTENT\") ||\n event.type.endsWith(\"_ARGS\") ||\n event.type.endsWith(\"_DELTA\")\n ) {\n return {\n label: \"Last event is in progress\",\n tone: \"active\",\n ...eventDetails,\n };\n }\n\n return {\n label: \"Last event received\",\n tone: \"success\",\n ...eventDetails,\n };\n}\n\n/**\n * Whether the runtime *connection* needs attention — the single condition\n * shared by System Health and the launcher's error signal, so the two can\n * never disagree about whether the wiring is broken.\n *\n * Exactly one state counts. `disconnected` is also the initial value, so\n * counting it would raise the signal on every page load; `connecting` is a\n * normal startup step; `unavailable` means no Core is attached, which is not\n * a defect of the developer's wiring.\n *\n * Note the deliberate asymmetry with `health.state`: a failed *run* also\n * drives System Health to \"Needs attention\" while the connection is fine.\n * That is an event rather than a state, and it is excluded from the launcher\n * on purpose — see the launcher-signal comments in index.ts.\n */\nexport function runtimeConnectionNeedsAttention(\n state: HomeRuntimeConnectionState,\n): boolean {\n return state === \"error\";\n}\n\nfunction runtimeHealthFromInput(\n input: HomeBriefingInput,\n): HomeModel[\"runtime\"][\"health\"] {\n const lastEvent = runtimeEventSignal(input.lastRuntimeEvent);\n\n if (input.runtimeConnectionState === \"connected\") {\n return {\n state: lastEvent.tone === \"error\" ? \"error\" : \"healthy\",\n label: lastEvent.tone === \"error\" ? \"Needs attention\" : \"Healthy\",\n runtime: { label: \"Available\", tone: \"success\" },\n liveUpdates: { label: \"Ready\", tone: \"success\" },\n lastEvent,\n };\n }\n\n if (input.runtimeConnectionState === \"connecting\") {\n return {\n state: \"checking\",\n label: \"Checking\",\n runtime: { label: \"Checking\", tone: \"active\" },\n liveUpdates: { label: \"Connecting\", tone: \"active\" },\n lastEvent,\n };\n }\n\n if (input.runtimeConnectionState === \"unavailable\") {\n return {\n state: \"unavailable\",\n label: \"Unavailable\",\n runtime: { label: \"Unavailable\", tone: \"muted\" },\n liveUpdates: { label: \"Not attached\", tone: \"muted\" },\n lastEvent,\n };\n }\n\n const needsAttention = runtimeConnectionNeedsAttention(\n input.runtimeConnectionState,\n );\n return {\n state: needsAttention ? \"error\" : \"offline\",\n label: needsAttention ? \"Runtime error\" : \"Offline\",\n runtime: { label: \"Offline\", tone: \"error\" },\n liveUpdates: {\n label: needsAttention ? \"Error\" : \"Disconnected\",\n tone: \"error\",\n },\n lastEvent,\n };\n}\n\n/** Build the Home briefing from data the Inspector already has. */\nexport function buildHomeModel(input: HomeBriefingInput): HomeModel {\n const metadataAction =\n input.metadata.threadsFooterAction ?? input.metadata.lockedAction;\n const action = metadataAction\n ? homeHeroActionFromMetadata({\n kind: metadataAction.kind,\n url: metadataAction.url,\n })\n : undefined;\n\n const identity = input.metadata.identity;\n const projectLinked = Boolean(identity);\n const intelligenceConnected =\n input.metadata.licenseState === \"valid\" ||\n (input.metadata.licenseState === \"unknown\" && input.intelligenceConnected);\n const usage = usageFromMetadata(input.metadata);\n\n return {\n hero: heroForState({\n connected: intelligenceConnected,\n action,\n connectUrl: input.intelligenceSignupUrl,\n licenseState: input.metadata.licenseState,\n }),\n projectLinked,\n project: identity\n ? {\n organizationName: identity.organizationName,\n projectName: identity.projectName,\n planLabel: input.metadata.plan?.label,\n license: input.metadata.licenseState,\n usage,\n }\n : input.metadata.plan || usage\n ? {\n organizationName: \"Not linked\",\n projectName: \"This runtime is not linked to a project\",\n planLabel: input.metadata.plan?.label,\n license: input.metadata.licenseState,\n usage,\n }\n : undefined,\n runtime: {\n url: input.runtimeUrl,\n health: runtimeHealthFromInput(input),\n },\n services: [\n {\n id: \"threads\",\n label: \"Threads\",\n enabled: intelligenceConnected && input.threadsAvailable,\n url: input.runtimeUrl,\n docsUrl: SERVICE_DOCS_URL.threads,\n },\n {\n id: \"memory\",\n label: \"Learning\",\n enabled: intelligenceConnected && input.memoriesOn,\n docsUrl: SERVICE_DOCS_URL.memory,\n },\n {\n id: \"a2ui\",\n label: \"A2UI\",\n enabled: input.a2uiOn,\n docsUrl: SERVICE_DOCS_URL.a2ui,\n },\n {\n id: \"open-gen-ui\",\n label: \"Open Gen UI\",\n enabled: input.openGenUiOn,\n docsUrl: SERVICE_DOCS_URL[\"open-gen-ui\"],\n },\n {\n id: \"suggestions\",\n label: \"Suggestions\",\n enabled: input.suggestionsOn,\n docsUrl: SERVICE_DOCS_URL.suggestions,\n },\n {\n id: \"audio\",\n label: \"Voice\",\n enabled: input.audioOn,\n docsUrl: SERVICE_DOCS_URL.audio,\n },\n {\n id: \"websocket\",\n label: \"Websocket\",\n enabled: intelligenceConnected && Boolean(input.websocketUrl),\n url: input.websocketUrl,\n docsUrl: SERVICE_DOCS_URL.websocket,\n },\n ],\n news: newsFromAnnouncement({\n previewText: input.announcementPreviewText,\n markdown: input.announcementMarkdown,\n html: input.announcementHtml ?? undefined,\n }),\n };\n}\n"],"mappings":"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"}
@@ -1,12 +1,25 @@
1
1
  //#region src/lib/home-briefing.ts
2
+ /**
3
+ * A self-contained handoff for a coding agent. It answers the onboarding
4
+ * workflow's session and feedback consent gates up front, while keeping the
5
+ * feature guide—not a generic product-onboarding branch—in control of the
6
+ * implementation plan.
7
+ */
8
+ function homeFeatureImplementationPrompt(service, context) {
9
+ return `Identify your coding-agent slug (for example, \`codex\` or \`claude-code\`). From the target project root, run \`npx copilotkit@latest onboard start --run ${context.onboardingRunId} --coding-agent <coding-agent-slug>\` and use its output as onboarding context. If it requires a CopilotKit CLI session check, you have permission to run it; never reveal credentials or send optional diagnostic feedback reports.
10
+
11
+ This task is specifically to enable ${service.label}, not to re-onboard the application. First inspect the existing CopilotKit runtime, provider, agent, and UI wiring, and confirm that ${service.label} is not already enabled. Then read the ${service.label} guide (${service.docsUrl}) and make a short plan before editing. Preserve the project's framework, package manager, installed CopilotKit version, existing agent IDs, routes, provider layout, and working behavior. Do not create, select, or alter a CopilotKit Intelligence project—or add Intelligence configuration—unless this feature's official guide explicitly requires it or the user asks.
12
+
13
+ Implement the smallest complete integration: wire every feature-required client and runtime configuration into the chat-to-agent path people already use, reuse local patterns, and do not invent environment values or hardcode secrets. Add or update focused tests and run the relevant project checks. Finish only after local validation proves ${service.label} works—not merely that the code compiles. Use a feature-specific runtime or Inspector capability check and, when the feature supports one, a representative UI interaction that proves the user-facing result. If the project overrides default rendering (for example, with a wildcard tool renderer), make that override compatible with this feature; a capability flag alone is not success. Summarize the changed files, validation, and any manual setup still required.`;
14
+ }
2
15
  const SERVICE_DOCS_URL = {
3
16
  threads: "https://docs.copilotkit.ai/threads",
4
- memory: "https://docs.copilotkit.ai/premium/intelligence-platform",
17
+ memory: "https://docs.copilotkit.ai/intelligence/intelligence-platform",
5
18
  a2ui: "https://docs.copilotkit.ai/generative-ui/a2ui",
6
19
  "open-gen-ui": "https://docs.copilotkit.ai/generative-ui/open-generative-ui",
7
- suggestions: "https://docs.copilotkit.ai/agentic-chat-ui",
20
+ suggestions: "https://docs.copilotkit.ai/reference/hooks/useConfigureSuggestions",
8
21
  audio: "https://docs.copilotkit.ai/voice",
9
- websocket: "https://docs.copilotkit.ai/premium/intelligence-platform"
22
+ websocket: "https://docs.copilotkit.ai/intelligence/intelligence-platform"
10
23
  };
11
24
  /** Return the Home hero button for a trusted metadata action. */
12
25
  function homeHeroActionFromMetadata(action) {
@@ -239,7 +252,7 @@ function buildHomeModel(input) {
239
252
  },
240
253
  {
241
254
  id: "memory",
242
- label: "Memory",
255
+ label: "Learning",
243
256
  enabled: intelligenceConnected && input.memoriesOn,
244
257
  docsUrl: SERVICE_DOCS_URL.memory
245
258
  },
@@ -263,7 +276,7 @@ function buildHomeModel(input) {
263
276
  },
264
277
  {
265
278
  id: "audio",
266
- label: "Audio",
279
+ label: "Voice",
267
280
  enabled: input.audioOn,
268
281
  docsUrl: SERVICE_DOCS_URL.audio
269
282
  },
@@ -284,5 +297,5 @@ function buildHomeModel(input) {
284
297
  }
285
298
 
286
299
  //#endregion
287
- export { buildHomeModel, runtimeConnectionNeedsAttention };
300
+ export { buildHomeModel, homeFeatureImplementationPrompt, runtimeConnectionNeedsAttention };
288
301
  //# sourceMappingURL=home-briefing.mjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"home-briefing.mjs","names":[],"sources":["../../src/lib/home-briefing.ts"],"sourcesContent":["import type { InspectorMetadataProjection } from \"./inspector-metadata.js\";\n\nexport type HomeHeroActionKind =\n | \"enable_intelligence\"\n | \"manage_plan\"\n | \"renew\";\n\nexport type HomeHeroAction = {\n kind: HomeHeroActionKind;\n url: string;\n label: string;\n};\n\nexport type HomeConnection = \"connected\" | \"disconnected\";\n\nexport type HomeRuntimeConnectionState =\n | \"connected\"\n | \"connecting\"\n | \"disconnected\"\n | \"error\"\n | \"unavailable\";\n\nexport type HomeRuntimeHealthTone = \"success\" | \"active\" | \"error\" | \"muted\";\n\nexport type HomeServiceId =\n | \"threads\"\n | \"memory\"\n | \"a2ui\"\n | \"open-gen-ui\"\n | \"suggestions\"\n | \"audio\"\n | \"websocket\";\n\nexport type HomeServiceTile = {\n id: HomeServiceId;\n label: string;\n enabled: boolean;\n url?: string;\n docsUrl: string;\n};\n\nexport type HomeModel = {\n hero: {\n connection: HomeConnection;\n title: string;\n body: string;\n action?: HomeHeroAction;\n };\n project?: {\n organizationName: string;\n projectName: string;\n planLabel?: string;\n license: string;\n usage?: {\n used: number;\n limitLabel: string;\n ratio?: number;\n };\n };\n projectLinked: boolean;\n runtime: {\n url?: string;\n health: {\n state: \"healthy\" | \"checking\" | \"offline\" | \"error\" | \"unavailable\";\n label: string;\n runtime: {\n label: string;\n tone: HomeRuntimeHealthTone;\n };\n liveUpdates: {\n label: string;\n tone: HomeRuntimeHealthTone;\n };\n lastEvent: {\n label: string;\n tone: HomeRuntimeHealthTone;\n id?: string;\n agentId?: string;\n type?: string;\n timestamp?: number;\n };\n };\n };\n services: HomeServiceTile[];\n news: {\n title: string;\n previewText: string;\n documentHtml?: string;\n empty: boolean;\n };\n};\n\nexport type HomeBriefingInput = {\n intelligenceConnected: boolean;\n threadsAvailable: boolean;\n metadata: InspectorMetadataProjection;\n runtimeUrl?: string;\n runtimeConnectionState: HomeRuntimeConnectionState;\n lastRuntimeEvent?: {\n id: string;\n agentId: string;\n type: string;\n timestamp: number;\n };\n memoriesOn: boolean;\n a2uiOn: boolean;\n openGenUiOn: boolean;\n suggestionsOn: boolean;\n audioOn: boolean;\n websocketUrl?: string;\n announcementPreviewText?: string;\n announcementMarkdown?: string;\n announcementHtml?: string;\n intelligenceSignupUrl?: string;\n};\n\nconst SERVICE_DOCS_URL: Record<HomeServiceId, string> = {\n threads: \"https://docs.copilotkit.ai/threads\",\n memory: \"https://docs.copilotkit.ai/premium/intelligence-platform\",\n a2ui: \"https://docs.copilotkit.ai/generative-ui/a2ui\",\n \"open-gen-ui\": \"https://docs.copilotkit.ai/generative-ui/open-generative-ui\",\n suggestions: \"https://docs.copilotkit.ai/agentic-chat-ui\",\n audio: \"https://docs.copilotkit.ai/voice\",\n websocket: \"https://docs.copilotkit.ai/premium/intelligence-platform\",\n};\n\n/** Return the Home hero button for a trusted metadata action. */\nexport function homeHeroActionFromMetadata(action: {\n kind: HomeHeroActionKind;\n url: string;\n}): HomeHeroAction {\n if (action.kind === \"manage_plan\") {\n return { kind: action.kind, url: action.url, label: \"Manage plan\" };\n }\n\n if (action.kind === \"renew\") {\n return { kind: action.kind, url: action.url, label: \"Renew plan\" };\n }\n\n return {\n kind: action.kind,\n url: action.url,\n label: \"Setup Intelligence\",\n };\n}\n\n/** Return a short preview from markdown, without links or headings. */\nexport function announcementPreview(markdown: string, maxLength = 140): string {\n const plain = markdown\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n .replace(/[#*_`]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n if (plain.length <= maxLength) {\n return plain;\n }\n\n return `${plain.slice(0, maxLength).trimEnd()}…`;\n}\n\nfunction connectIntelligenceAction(\n action: HomeHeroAction | undefined,\n connectUrl?: string,\n licenseState?: InspectorMetadataProjection[\"licenseState\"],\n): HomeHeroAction | undefined {\n if (action) {\n return action;\n }\n\n if (connectUrl && (licenseState === \"none\" || licenseState === \"unknown\")) {\n return homeHeroActionFromMetadata({\n kind: \"enable_intelligence\",\n url: connectUrl,\n });\n }\n\n return undefined;\n}\n\nfunction heroForState(args: {\n connected: boolean;\n action?: HomeHeroAction;\n connectUrl?: string;\n licenseState: InspectorMetadataProjection[\"licenseState\"];\n}): HomeModel[\"hero\"] {\n if (!args.connected) {\n const renewing = args.action?.kind === \"renew\";\n return {\n connection: \"disconnected\",\n // Not \"Intelligence is not setup\" — that reads as a defect in the tool,\n // and a defect gets dismissed. The heading names the product; the\n // argument for it is made by the rotating copy on Home, which changes\n // with the picture beside it.\n title: renewing ? \"Renew Intelligence\" : \"CopilotKit Intelligence\",\n // In install mode this is NOT the visible paragraph. The visible copy\n // rotates every few seconds, which would make a screen reader announce a\n // new sentence four times a loop, so the rotating text is hidden from\n // assistive tech and this one stable sentence is exposed instead. It has\n // to carry the whole chain on its own.\n body: renewing\n ? \"Renew Intelligence to restore persistent Threads and Memory.\"\n : \"Intelligence keeps every thread your users have, finds evidence-backed patterns in them, and proposes skills you approve before your agent uses them.\",\n action: connectIntelligenceAction(\n args.action,\n args.connectUrl,\n args.licenseState,\n ),\n };\n }\n\n return {\n connection: \"connected\",\n title: \"Connected to Intelligence\",\n body: \"Use Workbench to inspect threads and memory.\",\n action:\n args.action?.kind === \"enable_intelligence\" ? undefined : args.action,\n };\n}\n\nfunction usageFromMetadata(metadata: InspectorMetadataProjection):\n | {\n used: number;\n limitLabel: string;\n ratio?: number;\n }\n | undefined {\n const usage = metadata.usage;\n if (!usage) {\n return undefined;\n }\n\n if (usage.limit.kind === \"finite\") {\n return {\n used: usage.used,\n limitLabel: `${usage.used} / ${usage.limit.value}`,\n ratio: usage.limit.value === 0 ? 1 : usage.used / usage.limit.value,\n };\n }\n\n if (usage.limit.kind === \"unlimited\") {\n return {\n used: usage.used,\n limitLabel: `${usage.used} / unlimited`,\n };\n }\n\n return {\n used: usage.used,\n limitLabel: `${usage.used} used`,\n };\n}\n\nfunction newsFromAnnouncement(args: {\n previewText?: string;\n markdown?: string;\n html?: string;\n}): HomeModel[\"news\"] {\n const markdown = args.markdown?.trim();\n if (markdown && args.html) {\n const heading = markdown.match(/^#{1,3}\\s+(.+)$/m)?.[1]?.trim();\n return {\n title: heading || \"The latest from CopilotKit\",\n previewText:\n args.previewText?.trim() || announcementPreview(markdown, 160),\n documentHtml: args.html,\n empty: false,\n };\n }\n\n return {\n title: \"You're all caught up\",\n previewText: \"Latest CopilotKit updates will appear here.\",\n empty: true,\n };\n}\n\nfunction runtimeEventSignal(\n event: HomeBriefingInput[\"lastRuntimeEvent\"],\n): HomeModel[\"runtime\"][\"health\"][\"lastEvent\"] {\n if (!event) {\n return { label: \"No events yet\", tone: \"muted\" };\n }\n\n const eventDetails = {\n id: event.id,\n agentId: event.agentId,\n type: event.type,\n timestamp: event.timestamp,\n };\n\n if (event.type === \"RUN_ERROR\" || event.type === \"ERROR\") {\n return {\n label: event.type === \"RUN_ERROR\" ? \"Run error\" : \"Error\",\n tone: \"error\",\n ...eventDetails,\n };\n }\n\n if (event.type === \"RUN_FINISHED\") {\n return {\n label: \"Run completed\",\n tone: \"success\",\n ...eventDetails,\n };\n }\n\n if (\n event.type.endsWith(\"_START\") ||\n event.type.endsWith(\"_STARTED\") ||\n event.type.endsWith(\"_CONTENT\") ||\n event.type.endsWith(\"_ARGS\") ||\n event.type.endsWith(\"_DELTA\")\n ) {\n return {\n label: \"Last event is in progress\",\n tone: \"active\",\n ...eventDetails,\n };\n }\n\n return {\n label: \"Last event received\",\n tone: \"success\",\n ...eventDetails,\n };\n}\n\n/**\n * Whether the runtime *connection* needs attention — the single condition\n * shared by System Health and the launcher's error signal, so the two can\n * never disagree about whether the wiring is broken.\n *\n * Exactly one state counts. `disconnected` is also the initial value, so\n * counting it would raise the signal on every page load; `connecting` is a\n * normal startup step; `unavailable` means no Core is attached, which is not\n * a defect of the developer's wiring.\n *\n * Note the deliberate asymmetry with `health.state`: a failed *run* also\n * drives System Health to \"Needs attention\" while the connection is fine.\n * That is an event rather than a state, and it is excluded from the launcher\n * on purpose — see the launcher-signal comments in index.ts.\n */\nexport function runtimeConnectionNeedsAttention(\n state: HomeRuntimeConnectionState,\n): boolean {\n return state === \"error\";\n}\n\nfunction runtimeHealthFromInput(\n input: HomeBriefingInput,\n): HomeModel[\"runtime\"][\"health\"] {\n const lastEvent = runtimeEventSignal(input.lastRuntimeEvent);\n\n if (input.runtimeConnectionState === \"connected\") {\n return {\n state: lastEvent.tone === \"error\" ? \"error\" : \"healthy\",\n label: lastEvent.tone === \"error\" ? \"Needs attention\" : \"Healthy\",\n runtime: { label: \"Available\", tone: \"success\" },\n liveUpdates: { label: \"Ready\", tone: \"success\" },\n lastEvent,\n };\n }\n\n if (input.runtimeConnectionState === \"connecting\") {\n return {\n state: \"checking\",\n label: \"Checking\",\n runtime: { label: \"Checking\", tone: \"active\" },\n liveUpdates: { label: \"Connecting\", tone: \"active\" },\n lastEvent,\n };\n }\n\n if (input.runtimeConnectionState === \"unavailable\") {\n return {\n state: \"unavailable\",\n label: \"Unavailable\",\n runtime: { label: \"Unavailable\", tone: \"muted\" },\n liveUpdates: { label: \"Not attached\", tone: \"muted\" },\n lastEvent,\n };\n }\n\n const needsAttention = runtimeConnectionNeedsAttention(\n input.runtimeConnectionState,\n );\n return {\n state: needsAttention ? \"error\" : \"offline\",\n label: needsAttention ? \"Runtime error\" : \"Offline\",\n runtime: { label: \"Offline\", tone: \"error\" },\n liveUpdates: {\n label: needsAttention ? \"Error\" : \"Disconnected\",\n tone: \"error\",\n },\n lastEvent,\n };\n}\n\n/** Build the Home briefing from data the Inspector already has. */\nexport function buildHomeModel(input: HomeBriefingInput): HomeModel {\n const metadataAction =\n input.metadata.threadsFooterAction ?? input.metadata.lockedAction;\n const action = metadataAction\n ? homeHeroActionFromMetadata({\n kind: metadataAction.kind,\n url: metadataAction.url,\n })\n : undefined;\n\n const identity = input.metadata.identity;\n const projectLinked = Boolean(identity);\n const intelligenceConnected =\n input.metadata.licenseState === \"valid\" ||\n (input.metadata.licenseState === \"unknown\" && input.intelligenceConnected);\n const usage = usageFromMetadata(input.metadata);\n\n return {\n hero: heroForState({\n connected: intelligenceConnected,\n action,\n connectUrl: input.intelligenceSignupUrl,\n licenseState: input.metadata.licenseState,\n }),\n projectLinked,\n project: identity\n ? {\n organizationName: identity.organizationName,\n projectName: identity.projectName,\n planLabel: input.metadata.plan?.label,\n license: input.metadata.licenseState,\n usage,\n }\n : input.metadata.plan || usage\n ? {\n organizationName: \"Not linked\",\n projectName: \"This runtime is not linked to a project\",\n planLabel: input.metadata.plan?.label,\n license: input.metadata.licenseState,\n usage,\n }\n : undefined,\n runtime: {\n url: input.runtimeUrl,\n health: runtimeHealthFromInput(input),\n },\n services: [\n {\n id: \"threads\",\n label: \"Threads\",\n enabled: intelligenceConnected && input.threadsAvailable,\n url: input.runtimeUrl,\n docsUrl: SERVICE_DOCS_URL.threads,\n },\n {\n id: \"memory\",\n label: \"Memory\",\n enabled: intelligenceConnected && input.memoriesOn,\n docsUrl: SERVICE_DOCS_URL.memory,\n },\n {\n id: \"a2ui\",\n label: \"A2UI\",\n enabled: input.a2uiOn,\n docsUrl: SERVICE_DOCS_URL.a2ui,\n },\n {\n id: \"open-gen-ui\",\n label: \"Open Gen UI\",\n enabled: input.openGenUiOn,\n docsUrl: SERVICE_DOCS_URL[\"open-gen-ui\"],\n },\n {\n id: \"suggestions\",\n label: \"Suggestions\",\n enabled: input.suggestionsOn,\n docsUrl: SERVICE_DOCS_URL.suggestions,\n },\n {\n id: \"audio\",\n label: \"Audio\",\n enabled: input.audioOn,\n docsUrl: SERVICE_DOCS_URL.audio,\n },\n {\n id: \"websocket\",\n label: \"Websocket\",\n enabled: intelligenceConnected && Boolean(input.websocketUrl),\n url: input.websocketUrl,\n docsUrl: SERVICE_DOCS_URL.websocket,\n },\n ],\n news: newsFromAnnouncement({\n previewText: input.announcementPreviewText,\n markdown: input.announcementMarkdown,\n html: input.announcementHtml ?? undefined,\n }),\n };\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"home-briefing.mjs","names":[],"sources":["../../src/lib/home-briefing.ts"],"sourcesContent":["import type { InspectorMetadataProjection } from \"./inspector-metadata.js\";\n\nexport type HomeHeroActionKind =\n | \"enable_intelligence\"\n | \"manage_plan\"\n | \"renew\";\n\nexport type HomeHeroAction = {\n kind: HomeHeroActionKind;\n url: string;\n label: string;\n};\n\nexport type HomeConnection = \"connected\" | \"disconnected\";\n\nexport type HomeRuntimeConnectionState =\n | \"connected\"\n | \"connecting\"\n | \"disconnected\"\n | \"error\"\n | \"unavailable\";\n\nexport type HomeRuntimeHealthTone = \"success\" | \"active\" | \"error\" | \"muted\";\n\nexport type HomeServiceId =\n | \"threads\"\n | \"memory\"\n | \"a2ui\"\n | \"open-gen-ui\"\n | \"suggestions\"\n | \"audio\"\n | \"websocket\";\n\nexport type HomeServiceTile = {\n id: HomeServiceId;\n label: string;\n enabled: boolean;\n url?: string;\n docsUrl: string;\n};\n\nexport type HomeFeatureImplementationPromptContext = {\n onboardingRunId: string;\n};\n\n/**\n * A self-contained handoff for a coding agent. It answers the onboarding\n * workflow's session and feedback consent gates up front, while keeping the\n * feature guide—not a generic product-onboarding branch—in control of the\n * implementation plan.\n */\nexport function homeFeatureImplementationPrompt(\n service: Pick<HomeServiceTile, \"label\" | \"docsUrl\">,\n context: HomeFeatureImplementationPromptContext,\n): string {\n return `Identify your coding-agent slug (for example, \\`codex\\` or \\`claude-code\\`). From the target project root, run \\`npx copilotkit@latest onboard start --run ${context.onboardingRunId} --coding-agent <coding-agent-slug>\\` and use its output as onboarding context. If it requires a CopilotKit CLI session check, you have permission to run it; never reveal credentials or send optional diagnostic feedback reports.\n\nThis task is specifically to enable ${service.label}, not to re-onboard the application. First inspect the existing CopilotKit runtime, provider, agent, and UI wiring, and confirm that ${service.label} is not already enabled. Then read the ${service.label} guide (${service.docsUrl}) and make a short plan before editing. Preserve the project's framework, package manager, installed CopilotKit version, existing agent IDs, routes, provider layout, and working behavior. Do not create, select, or alter a CopilotKit Intelligence project—or add Intelligence configuration—unless this feature's official guide explicitly requires it or the user asks.\n\nImplement the smallest complete integration: wire every feature-required client and runtime configuration into the chat-to-agent path people already use, reuse local patterns, and do not invent environment values or hardcode secrets. Add or update focused tests and run the relevant project checks. Finish only after local validation proves ${service.label} works—not merely that the code compiles. Use a feature-specific runtime or Inspector capability check and, when the feature supports one, a representative UI interaction that proves the user-facing result. If the project overrides default rendering (for example, with a wildcard tool renderer), make that override compatible with this feature; a capability flag alone is not success. Summarize the changed files, validation, and any manual setup still required.`;\n}\n\nexport type HomeModel = {\n hero: {\n connection: HomeConnection;\n title: string;\n body: string;\n action?: HomeHeroAction;\n };\n project?: {\n organizationName: string;\n projectName: string;\n planLabel?: string;\n license: string;\n usage?: {\n used: number;\n limitLabel: string;\n ratio?: number;\n };\n };\n projectLinked: boolean;\n runtime: {\n url?: string;\n health: {\n state: \"healthy\" | \"checking\" | \"offline\" | \"error\" | \"unavailable\";\n label: string;\n runtime: {\n label: string;\n tone: HomeRuntimeHealthTone;\n };\n liveUpdates: {\n label: string;\n tone: HomeRuntimeHealthTone;\n };\n lastEvent: {\n label: string;\n tone: HomeRuntimeHealthTone;\n id?: string;\n agentId?: string;\n type?: string;\n timestamp?: number;\n };\n };\n };\n services: HomeServiceTile[];\n news: {\n title: string;\n previewText: string;\n documentHtml?: string;\n empty: boolean;\n };\n};\n\nexport type HomeBriefingInput = {\n intelligenceConnected: boolean;\n threadsAvailable: boolean;\n metadata: InspectorMetadataProjection;\n runtimeUrl?: string;\n runtimeConnectionState: HomeRuntimeConnectionState;\n lastRuntimeEvent?: {\n id: string;\n agentId: string;\n type: string;\n timestamp: number;\n };\n memoriesOn: boolean;\n a2uiOn: boolean;\n openGenUiOn: boolean;\n suggestionsOn: boolean;\n audioOn: boolean;\n websocketUrl?: string;\n announcementPreviewText?: string;\n announcementMarkdown?: string;\n announcementHtml?: string;\n intelligenceSignupUrl?: string;\n};\n\nconst SERVICE_DOCS_URL: Record<HomeServiceId, string> = {\n threads: \"https://docs.copilotkit.ai/threads\",\n memory: \"https://docs.copilotkit.ai/intelligence/intelligence-platform\",\n a2ui: \"https://docs.copilotkit.ai/generative-ui/a2ui\",\n \"open-gen-ui\": \"https://docs.copilotkit.ai/generative-ui/open-generative-ui\",\n suggestions:\n \"https://docs.copilotkit.ai/reference/hooks/useConfigureSuggestions\",\n audio: \"https://docs.copilotkit.ai/voice\",\n websocket: \"https://docs.copilotkit.ai/intelligence/intelligence-platform\",\n};\n\n/** Return the Home hero button for a trusted metadata action. */\nexport function homeHeroActionFromMetadata(action: {\n kind: HomeHeroActionKind;\n url: string;\n}): HomeHeroAction {\n if (action.kind === \"manage_plan\") {\n return { kind: action.kind, url: action.url, label: \"Manage plan\" };\n }\n\n if (action.kind === \"renew\") {\n return { kind: action.kind, url: action.url, label: \"Renew plan\" };\n }\n\n return {\n kind: action.kind,\n url: action.url,\n label: \"Setup Intelligence\",\n };\n}\n\n/** Return a short preview from markdown, without links or headings. */\nexport function announcementPreview(markdown: string, maxLength = 140): string {\n const plain = markdown\n .replace(/\\[([^\\]]+)\\]\\([^)]+\\)/g, \"$1\")\n .replace(/[#*_`]/g, \"\")\n .replace(/\\s+/g, \" \")\n .trim();\n if (plain.length <= maxLength) {\n return plain;\n }\n\n return `${plain.slice(0, maxLength).trimEnd()}…`;\n}\n\nfunction connectIntelligenceAction(\n action: HomeHeroAction | undefined,\n connectUrl?: string,\n licenseState?: InspectorMetadataProjection[\"licenseState\"],\n): HomeHeroAction | undefined {\n if (action) {\n return action;\n }\n\n if (connectUrl && (licenseState === \"none\" || licenseState === \"unknown\")) {\n return homeHeroActionFromMetadata({\n kind: \"enable_intelligence\",\n url: connectUrl,\n });\n }\n\n return undefined;\n}\n\nfunction heroForState(args: {\n connected: boolean;\n action?: HomeHeroAction;\n connectUrl?: string;\n licenseState: InspectorMetadataProjection[\"licenseState\"];\n}): HomeModel[\"hero\"] {\n if (!args.connected) {\n const renewing = args.action?.kind === \"renew\";\n return {\n connection: \"disconnected\",\n // Not \"Intelligence is not setup\" — that reads as a defect in the tool,\n // and a defect gets dismissed. The heading names the product; the\n // argument for it is made by the rotating copy on Home, which changes\n // with the picture beside it.\n title: renewing ? \"Renew Intelligence\" : \"CopilotKit Intelligence\",\n // In install mode this is NOT the visible paragraph. The visible copy\n // rotates every few seconds, which would make a screen reader announce a\n // new sentence four times a loop, so the rotating text is hidden from\n // assistive tech and this one stable sentence is exposed instead. It has\n // to carry the whole chain on its own.\n body: renewing\n ? \"Renew Intelligence to restore persistent Threads and Memory.\"\n : \"Intelligence keeps every thread your users have, finds evidence-backed patterns in them, and proposes skills you approve before your agent uses them.\",\n action: connectIntelligenceAction(\n args.action,\n args.connectUrl,\n args.licenseState,\n ),\n };\n }\n\n return {\n connection: \"connected\",\n title: \"Connected to Intelligence\",\n body: \"Use Workbench to inspect threads and memory.\",\n action:\n args.action?.kind === \"enable_intelligence\" ? undefined : args.action,\n };\n}\n\nfunction usageFromMetadata(metadata: InspectorMetadataProjection):\n | {\n used: number;\n limitLabel: string;\n ratio?: number;\n }\n | undefined {\n const usage = metadata.usage;\n if (!usage) {\n return undefined;\n }\n\n if (usage.limit.kind === \"finite\") {\n return {\n used: usage.used,\n limitLabel: `${usage.used} / ${usage.limit.value}`,\n ratio: usage.limit.value === 0 ? 1 : usage.used / usage.limit.value,\n };\n }\n\n if (usage.limit.kind === \"unlimited\") {\n return {\n used: usage.used,\n limitLabel: `${usage.used} / unlimited`,\n };\n }\n\n return {\n used: usage.used,\n limitLabel: `${usage.used} used`,\n };\n}\n\nfunction newsFromAnnouncement(args: {\n previewText?: string;\n markdown?: string;\n html?: string;\n}): HomeModel[\"news\"] {\n const markdown = args.markdown?.trim();\n if (markdown && args.html) {\n const heading = markdown.match(/^#{1,3}\\s+(.+)$/m)?.[1]?.trim();\n return {\n title: heading || \"The latest from CopilotKit\",\n previewText:\n args.previewText?.trim() || announcementPreview(markdown, 160),\n documentHtml: args.html,\n empty: false,\n };\n }\n\n return {\n title: \"You're all caught up\",\n previewText: \"Latest CopilotKit updates will appear here.\",\n empty: true,\n };\n}\n\nfunction runtimeEventSignal(\n event: HomeBriefingInput[\"lastRuntimeEvent\"],\n): HomeModel[\"runtime\"][\"health\"][\"lastEvent\"] {\n if (!event) {\n return { label: \"No events yet\", tone: \"muted\" };\n }\n\n const eventDetails = {\n id: event.id,\n agentId: event.agentId,\n type: event.type,\n timestamp: event.timestamp,\n };\n\n if (event.type === \"RUN_ERROR\" || event.type === \"ERROR\") {\n return {\n label: event.type === \"RUN_ERROR\" ? \"Run error\" : \"Error\",\n tone: \"error\",\n ...eventDetails,\n };\n }\n\n if (event.type === \"RUN_FINISHED\") {\n return {\n label: \"Run completed\",\n tone: \"success\",\n ...eventDetails,\n };\n }\n\n if (\n event.type.endsWith(\"_START\") ||\n event.type.endsWith(\"_STARTED\") ||\n event.type.endsWith(\"_CONTENT\") ||\n event.type.endsWith(\"_ARGS\") ||\n event.type.endsWith(\"_DELTA\")\n ) {\n return {\n label: \"Last event is in progress\",\n tone: \"active\",\n ...eventDetails,\n };\n }\n\n return {\n label: \"Last event received\",\n tone: \"success\",\n ...eventDetails,\n };\n}\n\n/**\n * Whether the runtime *connection* needs attention — the single condition\n * shared by System Health and the launcher's error signal, so the two can\n * never disagree about whether the wiring is broken.\n *\n * Exactly one state counts. `disconnected` is also the initial value, so\n * counting it would raise the signal on every page load; `connecting` is a\n * normal startup step; `unavailable` means no Core is attached, which is not\n * a defect of the developer's wiring.\n *\n * Note the deliberate asymmetry with `health.state`: a failed *run* also\n * drives System Health to \"Needs attention\" while the connection is fine.\n * That is an event rather than a state, and it is excluded from the launcher\n * on purpose — see the launcher-signal comments in index.ts.\n */\nexport function runtimeConnectionNeedsAttention(\n state: HomeRuntimeConnectionState,\n): boolean {\n return state === \"error\";\n}\n\nfunction runtimeHealthFromInput(\n input: HomeBriefingInput,\n): HomeModel[\"runtime\"][\"health\"] {\n const lastEvent = runtimeEventSignal(input.lastRuntimeEvent);\n\n if (input.runtimeConnectionState === \"connected\") {\n return {\n state: lastEvent.tone === \"error\" ? \"error\" : \"healthy\",\n label: lastEvent.tone === \"error\" ? \"Needs attention\" : \"Healthy\",\n runtime: { label: \"Available\", tone: \"success\" },\n liveUpdates: { label: \"Ready\", tone: \"success\" },\n lastEvent,\n };\n }\n\n if (input.runtimeConnectionState === \"connecting\") {\n return {\n state: \"checking\",\n label: \"Checking\",\n runtime: { label: \"Checking\", tone: \"active\" },\n liveUpdates: { label: \"Connecting\", tone: \"active\" },\n lastEvent,\n };\n }\n\n if (input.runtimeConnectionState === \"unavailable\") {\n return {\n state: \"unavailable\",\n label: \"Unavailable\",\n runtime: { label: \"Unavailable\", tone: \"muted\" },\n liveUpdates: { label: \"Not attached\", tone: \"muted\" },\n lastEvent,\n };\n }\n\n const needsAttention = runtimeConnectionNeedsAttention(\n input.runtimeConnectionState,\n );\n return {\n state: needsAttention ? \"error\" : \"offline\",\n label: needsAttention ? \"Runtime error\" : \"Offline\",\n runtime: { label: \"Offline\", tone: \"error\" },\n liveUpdates: {\n label: needsAttention ? \"Error\" : \"Disconnected\",\n tone: \"error\",\n },\n lastEvent,\n };\n}\n\n/** Build the Home briefing from data the Inspector already has. */\nexport function buildHomeModel(input: HomeBriefingInput): HomeModel {\n const metadataAction =\n input.metadata.threadsFooterAction ?? input.metadata.lockedAction;\n const action = metadataAction\n ? homeHeroActionFromMetadata({\n kind: metadataAction.kind,\n url: metadataAction.url,\n })\n : undefined;\n\n const identity = input.metadata.identity;\n const projectLinked = Boolean(identity);\n const intelligenceConnected =\n input.metadata.licenseState === \"valid\" ||\n (input.metadata.licenseState === \"unknown\" && input.intelligenceConnected);\n const usage = usageFromMetadata(input.metadata);\n\n return {\n hero: heroForState({\n connected: intelligenceConnected,\n action,\n connectUrl: input.intelligenceSignupUrl,\n licenseState: input.metadata.licenseState,\n }),\n projectLinked,\n project: identity\n ? {\n organizationName: identity.organizationName,\n projectName: identity.projectName,\n planLabel: input.metadata.plan?.label,\n license: input.metadata.licenseState,\n usage,\n }\n : input.metadata.plan || usage\n ? {\n organizationName: \"Not linked\",\n projectName: \"This runtime is not linked to a project\",\n planLabel: input.metadata.plan?.label,\n license: input.metadata.licenseState,\n usage,\n }\n : undefined,\n runtime: {\n url: input.runtimeUrl,\n health: runtimeHealthFromInput(input),\n },\n services: [\n {\n id: \"threads\",\n label: \"Threads\",\n enabled: intelligenceConnected && input.threadsAvailable,\n url: input.runtimeUrl,\n docsUrl: SERVICE_DOCS_URL.threads,\n },\n {\n id: \"memory\",\n label: \"Learning\",\n enabled: intelligenceConnected && input.memoriesOn,\n docsUrl: SERVICE_DOCS_URL.memory,\n },\n {\n id: \"a2ui\",\n label: \"A2UI\",\n enabled: input.a2uiOn,\n docsUrl: SERVICE_DOCS_URL.a2ui,\n },\n {\n id: \"open-gen-ui\",\n label: \"Open Gen UI\",\n enabled: input.openGenUiOn,\n docsUrl: SERVICE_DOCS_URL[\"open-gen-ui\"],\n },\n {\n id: \"suggestions\",\n label: \"Suggestions\",\n enabled: input.suggestionsOn,\n docsUrl: SERVICE_DOCS_URL.suggestions,\n },\n {\n id: \"audio\",\n label: \"Voice\",\n enabled: input.audioOn,\n docsUrl: SERVICE_DOCS_URL.audio,\n },\n {\n id: \"websocket\",\n label: \"Websocket\",\n enabled: intelligenceConnected && Boolean(input.websocketUrl),\n url: input.websocketUrl,\n docsUrl: SERVICE_DOCS_URL.websocket,\n },\n ],\n news: newsFromAnnouncement({\n previewText: input.announcementPreviewText,\n markdown: input.announcementMarkdown,\n html: input.announcementHtml ?? undefined,\n }),\n };\n}\n"],"mappings":"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"}
@@ -51,6 +51,50 @@ function isFiniteNumber(value) {
51
51
  function isValidDockMode(value) {
52
52
  return value === "floating" || value === "docked-left";
53
53
  }
54
+ const INSPECTOR_DISMISSAL_MIRROR_KEY = "cpk:inspector:dismissed_until";
55
+ const INSPECTOR_DISMISSAL_COOKIE_NAME = "cpk_inspector_dismissed_until";
56
+ const INSPECTOR_DISMISSAL_MAX_DURATION_MS = 10080 * 60 * 1e3;
57
+ /** Return the active host-scoped Inspector dismissal deadline, if any. */
58
+ function loadInspectorDismissedUntil(now = Date.now()) {
59
+ const until = parseInspectorDismissalPayload(readCookie(INSPECTOR_DISMISSAL_COOKIE_NAME)) ?? parseInspectorDismissalPayload(readLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY));
60
+ if (until === null) return null;
61
+ if (until <= now) {
62
+ clearInspectorDismissal();
63
+ return null;
64
+ }
65
+ const maximumUntil = now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;
66
+ if (until > maximumUntil) {
67
+ saveInspectorDismissedUntil(maximumUntil, now);
68
+ return maximumUntil;
69
+ }
70
+ return until;
71
+ }
72
+ /** Persist a dismissal across browser sessions and localhost ports. */
73
+ function saveInspectorDismissedUntil(until, now = Date.now()) {
74
+ if (!Number.isFinite(until) || until <= now) {
75
+ clearInspectorDismissal();
76
+ return;
77
+ }
78
+ const boundedUntil = Math.min(until, now + INSPECTOR_DISMISSAL_MAX_DURATION_MS);
79
+ const payload = JSON.stringify({ until: boundedUntil });
80
+ writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, payload, `Max-Age=${Math.max(1, Math.ceil((boundedUntil - now) / 1e3))}`);
81
+ writeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY, payload);
82
+ }
83
+ /** Clear both persistence layers, including an expired host cookie. */
84
+ function clearInspectorDismissal() {
85
+ writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, "", "Max-Age=0");
86
+ removeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY);
87
+ }
88
+ /** Parse a finite deadline from either persistence layer. */
89
+ function parseInspectorDismissalPayload(raw) {
90
+ if (!raw) return null;
91
+ try {
92
+ const parsed = JSON.parse(raw);
93
+ return typeof parsed.until === "number" && Number.isFinite(parsed.until) ? parsed.until : null;
94
+ } catch {
95
+ return null;
96
+ }
97
+ }
54
98
  const ANNOUNCEMENT_READ_COOKIE_NAME = "cpk_inspector_announcements";
55
99
  const ANNOUNCEMENT_READ_MIRROR_KEY = "cpk:inspector:announcement_read";
56
100
  const LEGACY_ANNOUNCEMENT_READ_KEY = "cpk:inspector:announcements";
@@ -111,21 +155,27 @@ function parseTimestampPayload(raw) {
111
155
  }
112
156
  }
113
157
  function readAnnouncementCookie() {
158
+ return readCookie(ANNOUNCEMENT_READ_COOKIE_NAME);
159
+ }
160
+ function writeAnnouncementCookie(value) {
161
+ writeCookie(ANNOUNCEMENT_READ_COOKIE_NAME, value, `Max-Age=${ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS}`);
162
+ }
163
+ function readCookie(name) {
114
164
  if (typeof document === "undefined") return null;
115
165
  try {
116
166
  for (const entry of document.cookie.split(";")) {
117
167
  const separator = entry.indexOf("=");
118
168
  if (separator === -1) continue;
119
- if (entry.slice(0, separator).trim() !== ANNOUNCEMENT_READ_COOKIE_NAME) continue;
169
+ if (entry.slice(0, separator).trim() !== name) continue;
120
170
  return decodeURIComponent(entry.slice(separator + 1).trim());
121
171
  }
122
172
  } catch {}
123
173
  return null;
124
174
  }
125
- function writeAnnouncementCookie(value) {
175
+ function writeCookie(name, value, lifetime) {
126
176
  if (typeof document === "undefined") return;
127
177
  try {
128
- document.cookie = `${ANNOUNCEMENT_READ_COOKIE_NAME}=${encodeURIComponent(value)}; Path=/; Max-Age=${ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;
178
+ document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; ${lifetime}; SameSite=Lax`;
129
179
  } catch {}
130
180
  }
131
181
  function readLocalStorageItem(key) {
@@ -195,6 +245,7 @@ function generateUuidV4() {
195
245
  }
196
246
 
197
247
  //#endregion
248
+ exports.INSPECTOR_DISMISSAL_MAX_DURATION_MS = INSPECTOR_DISMISSAL_MAX_DURATION_MS;
198
249
  exports.clearLegacyAnnouncementReadState = clearLegacyAnnouncementReadState;
199
250
  exports.getOrCreateTelemetryDistinctId = getOrCreateTelemetryDistinctId;
200
251
  exports.hasTelemetryDisclosureBeenShown = hasTelemetryDisclosureBeenShown;
@@ -205,9 +256,11 @@ exports.isValidPosition = isValidPosition;
205
256
  exports.isValidSize = isValidSize;
206
257
  exports.loadAnnouncementPulsedTimestamp = loadAnnouncementPulsedTimestamp;
207
258
  exports.loadAnnouncementReadTimestamp = loadAnnouncementReadTimestamp;
259
+ exports.loadInspectorDismissedUntil = loadInspectorDismissedUntil;
208
260
  exports.loadInspectorState = loadInspectorState;
209
261
  exports.markTelemetryDisclosureShown = markTelemetryDisclosureShown;
210
262
  exports.saveAnnouncementPulsedTimestamp = saveAnnouncementPulsedTimestamp;
211
263
  exports.saveAnnouncementReadTimestamp = saveAnnouncementReadTimestamp;
264
+ exports.saveInspectorDismissedUntil = saveInspectorDismissedUntil;
212
265
  exports.saveInspectorState = saveInspectorState;
213
266
  //# sourceMappingURL=persistence.cjs.map
@@ -1 +1 @@
1
- {"version":3,"file":"persistence.cjs","names":[],"sources":["../../src/lib/persistence.ts"],"sourcesContent":["import type { Anchor, DockMode, Position, Size } from \"./types.js\";\n\nexport type PersistedContextState = {\n anchor?: Anchor;\n anchorOffset?: Position;\n size?: Size;\n hasCustomPosition?: boolean;\n};\n\nexport type PersistedState = {\n button?: Omit<PersistedContextState, \"size\">;\n window?: PersistedContextState;\n isOpen?: boolean;\n dockMode?: DockMode;\n selectedMenu?: string;\n selectedContext?: string;\n hasOpenedInspector?: boolean;\n sidebarCollapsed?: boolean;\n /** @deprecated Replaced by colorSchemePreference to distinguish a user choice from the old light default. */\n colorScheme?: \"light\" | \"dark\";\n colorSchemePreference?: \"light\" | \"dark\";\n};\n\nexport const HOME_NEWS_READ_STORAGE_KEY = \"cpk:inspector:home-news-read\";\n\n/** Return story ids the user has already opened or hovered on Home. */\nexport function loadHomeNewsReadIds(storageKey: string): string[] {\n if (typeof window === \"undefined\") {\n return [];\n }\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) {\n return [];\n }\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter((id): id is string => typeof id === \"string\");\n } catch {\n return [];\n }\n}\n\n/** Persist Home news story ids that the user has already seen. */\nexport function saveHomeNewsReadIds(storageKey: string, ids: string[]): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(ids));\n } catch (error) {\n console.warn(\"Failed to persist Home news read state\", error);\n }\n}\n\nexport function loadInspectorState(storageKey: string): PersistedState | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n const raw = window.localStorage.getItem(storageKey);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n // Fall through to cookie migration path\n }\n }\n\n // Backwards compatibility: try to read the legacy cookie and migrate it\n if (typeof document !== \"undefined\") {\n const prefix = `${storageKey}=`;\n const entry = document.cookie\n .split(\"; \")\n .find((cookie) => cookie.startsWith(prefix));\n if (entry) {\n const legacyRaw = entry.substring(prefix.length);\n try {\n const parsed = JSON.parse(decodeURIComponent(legacyRaw));\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n return null;\n }\n }\n }\n\n return null;\n}\n\nexport function saveInspectorState(\n storageKey: string,\n state: PersistedState,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to persist inspector state\", error);\n }\n}\n\nexport function isValidAnchor(value: unknown): value is Anchor {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Anchor;\n return (\n (candidate.horizontal === \"left\" || candidate.horizontal === \"right\") &&\n (candidate.vertical === \"top\" || candidate.vertical === \"bottom\")\n );\n}\n\nexport function isValidPosition(value: unknown): value is Position {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Position;\n return isFiniteNumber(candidate.x) && isFiniteNumber(candidate.y);\n}\n\nexport function isValidSize(value: unknown): value is Size {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Size;\n return isFiniteNumber(candidate.width) && isFiniteNumber(candidate.height);\n}\n\nexport function isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nexport function isValidDockMode(value: unknown): value is DockMode {\n return value === \"floating\" || value === \"docked-left\";\n}\n\n// Announcement read state — \"have I read this announcement\" is a property of\n// the person, not of the project, so it must survive a change of localhost\n// port. localStorage cannot express that: it is partitioned by origin and\n// origin includes the port, so :3000 and :5173 are separate stores. Cookies\n// are partitioned by host, so a cookie set on `localhost` without a `domain`\n// attribute is shared by every port on that host.\n//\n// Underscores, not colons: `:` is a separator in RFC 6265 and is not valid in\n// a cookie name. Browsers are lenient about it; we don't rely on that.\nconst ANNOUNCEMENT_READ_COOKIE_NAME = \"cpk_inspector_announcements\";\n\n// localStorage mirror of the cookie, so a browser that blocks cookies\n// degrades to per-port behaviour instead of losing the read state entirely.\n// A NEW key on purpose — the legacy one is abandoned, not migrated.\nconst ANNOUNCEMENT_READ_MIRROR_KEY = \"cpk:inspector:announcement_read\";\n\n// The superseded key. Every existing user is re-armed exactly once so they\n// discover the surface that replaced the announcement bubble, and the key is\n// deleted rather than left in place so nothing can fall back to it later.\nconst LEGACY_ANNOUNCEMENT_READ_KEY = \"cpk:inspector:announcements\";\n\n// Pulse suppression is per browser tab, and stores the announcement timestamp\n// rather than a boolean: a boolean would swallow a newly published\n// announcement for the rest of that tab's life, and the feed is fetched once\n// per mount with no polling.\nconst ANNOUNCEMENT_PULSED_SESSION_KEY = \"cpk:inspector:pulsed\";\n\n// Roughly one year. No `Secure` — local development is served over plain\n// HTTP — and no `HttpOnly`, because the component reads the value from\n// script. `Path=/` and `SameSite=Lax` keep it host-wide and same-site only.\nconst ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;\n\n/**\n * The announcement timestamp the user has already read, or `null` when\n * nothing has been read yet. Prefers the host-scoped cookie and falls back to\n * the origin-scoped mirror, so cookie-blocking browsers still remember the\n * read within the port they are on.\n */\nexport function loadAnnouncementReadTimestamp(): string | null {\n return (\n parseTimestampPayload(readAnnouncementCookie()) ??\n parseTimestampPayload(readLocalStorageItem(ANNOUNCEMENT_READ_MIRROR_KEY))\n );\n}\n\n/**\n * Records an announcement as read in both the host-scoped cookie and the\n * origin-scoped mirror. The stored value is `{\"timestamp\":\"…\"}` — the same\n * shape the announcement state has always used.\n */\nexport function saveAnnouncementReadTimestamp(timestamp: string): void {\n const payload = JSON.stringify({ timestamp });\n writeAnnouncementCookie(payload);\n writeLocalStorageItem(ANNOUNCEMENT_READ_MIRROR_KEY, payload);\n}\n\n/**\n * Deletes the superseded origin-scoped read state. Safe to call on every\n * startup: nothing writes that key any more, so the value cannot come back.\n */\nexport function clearLegacyAnnouncementReadState(): void {\n removeLocalStorageItem(LEGACY_ANNOUNCEMENT_READ_KEY);\n}\n\n/**\n * The announcement timestamp this browser tab has already pulsed for, or\n * `null` when it hasn't pulsed yet.\n */\nexport function loadAnnouncementPulsedTimestamp(): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.sessionStorage.getItem(ANNOUNCEMENT_PULSED_SESSION_KEY);\n } catch {\n return null;\n }\n}\n\n/** Suppresses further pulses for this announcement in this browser tab. */\nexport function saveAnnouncementPulsedTimestamp(timestamp: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.sessionStorage.setItem(ANNOUNCEMENT_PULSED_SESSION_KEY, timestamp);\n } catch {\n // No-op — a lost suppression costs one extra pulse, never correctness.\n }\n}\n\nfunction parseTimestampPayload(raw: string | null): string | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as { timestamp?: unknown };\n return typeof parsed?.timestamp === \"string\" ? parsed.timestamp : null;\n } catch {\n return null;\n }\n}\n\nfunction readAnnouncementCookie(): string | null {\n if (typeof document === \"undefined\") return null;\n try {\n for (const entry of document.cookie.split(\";\")) {\n const separator = entry.indexOf(\"=\");\n if (separator === -1) continue;\n if (entry.slice(0, separator).trim() !== ANNOUNCEMENT_READ_COOKIE_NAME) {\n continue;\n }\n return decodeURIComponent(entry.slice(separator + 1).trim());\n }\n } catch {\n // Cookie access throws in sandboxed documents — fall through to the\n // mirror rather than breaking the host app.\n }\n return null;\n}\n\nfunction writeAnnouncementCookie(value: string): void {\n if (typeof document === \"undefined\") return;\n try {\n document.cookie = `${ANNOUNCEMENT_READ_COOKIE_NAME}=${encodeURIComponent(\n value,\n )}; Path=/; Max-Age=${ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;\n } catch {\n // No-op — the mirror below is what a cookie-blocking browser falls back\n // to, and neither may break the host app.\n }\n}\n\nfunction readLocalStorageItem(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorageItem(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nfunction removeLocalStorageItem(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n // No-op.\n }\n}\n\n// Telemetry persistence — flat per-key localStorage rather than the\n// JSON-blob shape used for window/dock state, because each value is\n// independent and we want to read/write them without round-tripping\n// the whole inspector state object.\nconst TELEMETRY_DISTINCT_ID_KEY = \"cpk:inspector:telemetry:distinct_id\";\nconst TELEMETRY_OPT_OUT_KEY = \"cpk:inspector:telemetry:opt_out\";\nconst TELEMETRY_DISCLOSURE_SHOWN_KEY =\n \"cpk:inspector:telemetry:disclosure_shown\";\n\n// Module-level fallback for when localStorage is unavailable (private mode,\n// quota exceeded, etc.). Cached so that whats_new_viewed and\n// whats_new_clicked from the same page-load share one distinct_id even\n// without persistent storage —\n// funnel coherence within a session is preserved even when storage fails.\nlet inMemoryFallbackId: string | null = null;\n\nexport function getOrCreateTelemetryDistinctId(): string {\n if (typeof window === \"undefined\") {\n // SSR / test fallback. A non-persistent ID is preferable to throwing\n // because telemetry must never break the host application.\n return generateUuidV4();\n }\n\n try {\n const existing = window.localStorage.getItem(TELEMETRY_DISTINCT_ID_KEY);\n if (existing && existing.length > 0) return existing;\n const fresh = generateUuidV4();\n window.localStorage.setItem(TELEMETRY_DISTINCT_ID_KEY, fresh);\n return fresh;\n } catch {\n return (inMemoryFallbackId ??= generateUuidV4());\n }\n}\n\n// Test-only reset so the in-memory fallback doesn't leak between test cases.\nexport function _resetTelemetryPersistenceForTesting(): void {\n inMemoryFallbackId = null;\n}\n\nexport function isTelemetryOptedOut(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\nexport function setTelemetryOptOut(optedOut: boolean): void {\n if (typeof window === \"undefined\") return;\n try {\n if (optedOut) {\n window.localStorage.setItem(TELEMETRY_OPT_OUT_KEY, \"true\");\n } else {\n window.localStorage.removeItem(TELEMETRY_OPT_OUT_KEY);\n }\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nexport function hasTelemetryDisclosureBeenShown(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return (\n window.localStorage.getItem(TELEMETRY_DISCLOSURE_SHOWN_KEY) === \"true\"\n );\n } catch {\n return false;\n }\n}\n\nexport function markTelemetryDisclosureShown(): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(TELEMETRY_DISCLOSURE_SHOWN_KEY, \"true\");\n } catch {\n // No-op.\n }\n}\n\nfunction generateUuidV4(): string {\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n typeof globalThis.crypto.randomUUID === \"function\"\n ) {\n return globalThis.crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID (older browsers,\n // some test runners). Not cryptographically strong; acceptable because\n // the value is just an anonymous correlation ID.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"persistence.cjs","names":[],"sources":["../../src/lib/persistence.ts"],"sourcesContent":["import type { Anchor, DockMode, Position, Size } from \"./types.js\";\n\nexport type PersistedContextState = {\n anchor?: Anchor;\n anchorOffset?: Position;\n size?: Size;\n hasCustomPosition?: boolean;\n};\n\nexport type PersistedState = {\n button?: Omit<PersistedContextState, \"size\">;\n window?: PersistedContextState;\n isOpen?: boolean;\n dockMode?: DockMode;\n selectedMenu?: string;\n selectedContext?: string;\n hasOpenedInspector?: boolean;\n sidebarCollapsed?: boolean;\n /** @deprecated Replaced by colorSchemePreference to distinguish a user choice from the old light default. */\n colorScheme?: \"light\" | \"dark\";\n colorSchemePreference?: \"light\" | \"dark\";\n};\n\nexport const HOME_NEWS_READ_STORAGE_KEY = \"cpk:inspector:home-news-read\";\n\n/** Return story ids the user has already opened or hovered on Home. */\nexport function loadHomeNewsReadIds(storageKey: string): string[] {\n if (typeof window === \"undefined\") {\n return [];\n }\n\n try {\n const raw = window.localStorage.getItem(storageKey);\n if (!raw) {\n return [];\n }\n const parsed = JSON.parse(raw);\n if (!Array.isArray(parsed)) {\n return [];\n }\n return parsed.filter((id): id is string => typeof id === \"string\");\n } catch {\n return [];\n }\n}\n\n/** Persist Home news story ids that the user has already seen. */\nexport function saveHomeNewsReadIds(storageKey: string, ids: string[]): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(ids));\n } catch (error) {\n console.warn(\"Failed to persist Home news read state\", error);\n }\n}\n\nexport function loadInspectorState(storageKey: string): PersistedState | null {\n if (typeof window === \"undefined\") {\n return null;\n }\n\n const raw = window.localStorage.getItem(storageKey);\n if (raw) {\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n // Fall through to cookie migration path\n }\n }\n\n // Backwards compatibility: try to read the legacy cookie and migrate it\n if (typeof document !== \"undefined\") {\n const prefix = `${storageKey}=`;\n const entry = document.cookie\n .split(\"; \")\n .find((cookie) => cookie.startsWith(prefix));\n if (entry) {\n const legacyRaw = entry.substring(prefix.length);\n try {\n const parsed = JSON.parse(decodeURIComponent(legacyRaw));\n if (parsed && typeof parsed === \"object\") {\n return parsed as PersistedState;\n }\n } catch {\n return null;\n }\n }\n }\n\n return null;\n}\n\nexport function saveInspectorState(\n storageKey: string,\n state: PersistedState,\n): void {\n if (typeof window === \"undefined\") {\n return;\n }\n\n try {\n window.localStorage.setItem(storageKey, JSON.stringify(state));\n } catch (error) {\n console.warn(\"Failed to persist inspector state\", error);\n }\n}\n\nexport function isValidAnchor(value: unknown): value is Anchor {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Anchor;\n return (\n (candidate.horizontal === \"left\" || candidate.horizontal === \"right\") &&\n (candidate.vertical === \"top\" || candidate.vertical === \"bottom\")\n );\n}\n\nexport function isValidPosition(value: unknown): value is Position {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Position;\n return isFiniteNumber(candidate.x) && isFiniteNumber(candidate.y);\n}\n\nexport function isValidSize(value: unknown): value is Size {\n if (!value || typeof value !== \"object\") {\n return false;\n }\n\n const candidate = value as Size;\n return isFiniteNumber(candidate.width) && isFiniteNumber(candidate.height);\n}\n\nexport function isFiniteNumber(value: unknown): value is number {\n return typeof value === \"number\" && Number.isFinite(value);\n}\n\nexport function isValidDockMode(value: unknown): value is DockMode {\n return value === \"floating\" || value === \"docked-left\";\n}\n\n// Inspector dismissal is intentionally host-scoped rather than origin-scoped.\n// A developer who closes the Inspector on localhost:3000 should not see it\n// again on localhost:5173 during the same dismissal window. Cookies are shared\n// across ports on one host; localStorage is not.\nexport const INSPECTOR_DISMISSAL_MIRROR_KEY = \"cpk:inspector:dismissed_until\";\nexport const INSPECTOR_DISMISSAL_COOKIE_NAME = \"cpk_inspector_dismissed_until\";\nexport const INSPECTOR_DISMISSAL_MAX_DURATION_MS = 7 * 24 * 60 * 60 * 1000;\n\ntype InspectorDismissalPayload = Readonly<{ until: number }>;\n\n/** Return the active host-scoped Inspector dismissal deadline, if any. */\nexport function loadInspectorDismissedUntil(\n now: number = Date.now(),\n): number | null {\n const until =\n parseInspectorDismissalPayload(\n readCookie(INSPECTOR_DISMISSAL_COOKIE_NAME),\n ) ??\n parseInspectorDismissalPayload(\n readLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY),\n );\n\n if (until === null) return null;\n if (until <= now) {\n clearInspectorDismissal();\n return null;\n }\n const maximumUntil = now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;\n if (until > maximumUntil) {\n saveInspectorDismissedUntil(maximumUntil, now);\n return maximumUntil;\n }\n return until;\n}\n\n/** Persist a dismissal across browser sessions and localhost ports. */\nexport function saveInspectorDismissedUntil(\n until: number,\n now: number = Date.now(),\n): void {\n if (!Number.isFinite(until) || until <= now) {\n clearInspectorDismissal();\n return;\n }\n\n const boundedUntil = Math.min(\n until,\n now + INSPECTOR_DISMISSAL_MAX_DURATION_MS,\n );\n const payload = JSON.stringify({\n until: boundedUntil,\n } satisfies InspectorDismissalPayload);\n const maxAgeSeconds = Math.max(1, Math.ceil((boundedUntil - now) / 1000));\n writeCookie(\n INSPECTOR_DISMISSAL_COOKIE_NAME,\n payload,\n `Max-Age=${maxAgeSeconds}`,\n );\n writeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY, payload);\n}\n\n/** Clear both persistence layers, including an expired host cookie. */\nexport function clearInspectorDismissal(): void {\n writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, \"\", \"Max-Age=0\");\n removeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY);\n}\n\n/** Parse a finite deadline from either persistence layer. */\nfunction parseInspectorDismissalPayload(raw: string | null): number | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as Partial<InspectorDismissalPayload>;\n return typeof parsed.until === \"number\" && Number.isFinite(parsed.until)\n ? parsed.until\n : null;\n } catch {\n return null;\n }\n}\n\n// Announcement read state — \"have I read this announcement\" is a property of\n// the person, not of the project, so it must survive a change of localhost\n// port. localStorage cannot express that: it is partitioned by origin and\n// origin includes the port, so :3000 and :5173 are separate stores. Cookies\n// are partitioned by host, so a cookie set on `localhost` without a `domain`\n// attribute is shared by every port on that host.\n//\n// Underscores, not colons: `:` is a separator in RFC 6265 and is not valid in\n// a cookie name. Browsers are lenient about it; we don't rely on that.\nconst ANNOUNCEMENT_READ_COOKIE_NAME = \"cpk_inspector_announcements\";\n\n// localStorage mirror of the cookie, so a browser that blocks cookies\n// degrades to per-port behaviour instead of losing the read state entirely.\n// A NEW key on purpose — the legacy one is abandoned, not migrated.\nconst ANNOUNCEMENT_READ_MIRROR_KEY = \"cpk:inspector:announcement_read\";\n\n// The superseded key. Every existing user is re-armed exactly once so they\n// discover the surface that replaced the announcement bubble, and the key is\n// deleted rather than left in place so nothing can fall back to it later.\nconst LEGACY_ANNOUNCEMENT_READ_KEY = \"cpk:inspector:announcements\";\n\n// Pulse suppression is per browser tab, and stores the announcement timestamp\n// rather than a boolean: a boolean would swallow a newly published\n// announcement for the rest of that tab's life, and the feed is fetched once\n// per mount with no polling.\nconst ANNOUNCEMENT_PULSED_SESSION_KEY = \"cpk:inspector:pulsed\";\n\n// Roughly one year. No `Secure` — local development is served over plain\n// HTTP — and no `HttpOnly`, because the component reads the value from\n// script. `Path=/` and `SameSite=Lax` keep it host-wide and same-site only.\nconst ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS = 60 * 60 * 24 * 365;\n\n/**\n * The announcement timestamp the user has already read, or `null` when\n * nothing has been read yet. Prefers the host-scoped cookie and falls back to\n * the origin-scoped mirror, so cookie-blocking browsers still remember the\n * read within the port they are on.\n */\nexport function loadAnnouncementReadTimestamp(): string | null {\n return (\n parseTimestampPayload(readAnnouncementCookie()) ??\n parseTimestampPayload(readLocalStorageItem(ANNOUNCEMENT_READ_MIRROR_KEY))\n );\n}\n\n/**\n * Records an announcement as read in both the host-scoped cookie and the\n * origin-scoped mirror. The stored value is `{\"timestamp\":\"…\"}` — the same\n * shape the announcement state has always used.\n */\nexport function saveAnnouncementReadTimestamp(timestamp: string): void {\n const payload = JSON.stringify({ timestamp });\n writeAnnouncementCookie(payload);\n writeLocalStorageItem(ANNOUNCEMENT_READ_MIRROR_KEY, payload);\n}\n\n/**\n * Deletes the superseded origin-scoped read state. Safe to call on every\n * startup: nothing writes that key any more, so the value cannot come back.\n */\nexport function clearLegacyAnnouncementReadState(): void {\n removeLocalStorageItem(LEGACY_ANNOUNCEMENT_READ_KEY);\n}\n\n/**\n * The announcement timestamp this browser tab has already pulsed for, or\n * `null` when it hasn't pulsed yet.\n */\nexport function loadAnnouncementPulsedTimestamp(): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.sessionStorage.getItem(ANNOUNCEMENT_PULSED_SESSION_KEY);\n } catch {\n return null;\n }\n}\n\n/** Suppresses further pulses for this announcement in this browser tab. */\nexport function saveAnnouncementPulsedTimestamp(timestamp: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.sessionStorage.setItem(ANNOUNCEMENT_PULSED_SESSION_KEY, timestamp);\n } catch {\n // No-op — a lost suppression costs one extra pulse, never correctness.\n }\n}\n\nfunction parseTimestampPayload(raw: string | null): string | null {\n if (!raw) return null;\n try {\n const parsed = JSON.parse(raw) as { timestamp?: unknown };\n return typeof parsed?.timestamp === \"string\" ? parsed.timestamp : null;\n } catch {\n return null;\n }\n}\n\nfunction readAnnouncementCookie(): string | null {\n return readCookie(ANNOUNCEMENT_READ_COOKIE_NAME);\n}\n\nfunction writeAnnouncementCookie(value: string): void {\n writeCookie(\n ANNOUNCEMENT_READ_COOKIE_NAME,\n value,\n `Max-Age=${ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS}`,\n );\n}\n\nfunction readCookie(name: string): string | null {\n if (typeof document === \"undefined\") return null;\n try {\n for (const entry of document.cookie.split(\";\")) {\n const separator = entry.indexOf(\"=\");\n if (separator === -1) continue;\n if (entry.slice(0, separator).trim() !== name) continue;\n return decodeURIComponent(entry.slice(separator + 1).trim());\n }\n } catch {\n // Cookie access throws in sandboxed documents. Callers fall back to their\n // origin-scoped mirror rather than breaking the host app.\n }\n return null;\n}\n\nfunction writeCookie(name: string, value: string, lifetime: string): void {\n if (typeof document === \"undefined\") return;\n try {\n // No Domain attribute: the cookie belongs to exactly this host, while\n // remaining shared by every port on that host.\n document.cookie = `${name}=${encodeURIComponent(\n value,\n )}; Path=/; ${lifetime}; SameSite=Lax`;\n } catch {\n // The localStorage mirror gives cookie-blocking browsers per-origin\n // persistence, and neither storage layer may break the host app.\n }\n}\n\nfunction readLocalStorageItem(key: string): string | null {\n if (typeof window === \"undefined\") return null;\n try {\n return window.localStorage.getItem(key);\n } catch {\n return null;\n }\n}\n\nfunction writeLocalStorageItem(key: string, value: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(key, value);\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nfunction removeLocalStorageItem(key: string): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.removeItem(key);\n } catch {\n // No-op.\n }\n}\n\n// Telemetry persistence — flat per-key localStorage rather than the\n// JSON-blob shape used for window/dock state, because each value is\n// independent and we want to read/write them without round-tripping\n// the whole inspector state object.\nconst TELEMETRY_DISTINCT_ID_KEY = \"cpk:inspector:telemetry:distinct_id\";\nconst TELEMETRY_OPT_OUT_KEY = \"cpk:inspector:telemetry:opt_out\";\nconst TELEMETRY_DISCLOSURE_SHOWN_KEY =\n \"cpk:inspector:telemetry:disclosure_shown\";\n\n// Module-level fallback for when localStorage is unavailable (private mode,\n// quota exceeded, etc.). Cached so that whats_new_viewed and\n// whats_new_clicked from the same page-load share one distinct_id even\n// without persistent storage —\n// funnel coherence within a session is preserved even when storage fails.\nlet inMemoryFallbackId: string | null = null;\n\nexport function getOrCreateTelemetryDistinctId(): string {\n if (typeof window === \"undefined\") {\n // SSR / test fallback. A non-persistent ID is preferable to throwing\n // because telemetry must never break the host application.\n return generateUuidV4();\n }\n\n try {\n const existing = window.localStorage.getItem(TELEMETRY_DISTINCT_ID_KEY);\n if (existing && existing.length > 0) return existing;\n const fresh = generateUuidV4();\n window.localStorage.setItem(TELEMETRY_DISTINCT_ID_KEY, fresh);\n return fresh;\n } catch {\n return (inMemoryFallbackId ??= generateUuidV4());\n }\n}\n\n// Test-only reset so the in-memory fallback doesn't leak between test cases.\nexport function _resetTelemetryPersistenceForTesting(): void {\n inMemoryFallbackId = null;\n}\n\nexport function isTelemetryOptedOut(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return window.localStorage.getItem(TELEMETRY_OPT_OUT_KEY) === \"true\";\n } catch {\n return false;\n }\n}\n\nexport function setTelemetryOptOut(optedOut: boolean): void {\n if (typeof window === \"undefined\") return;\n try {\n if (optedOut) {\n window.localStorage.setItem(TELEMETRY_OPT_OUT_KEY, \"true\");\n } else {\n window.localStorage.removeItem(TELEMETRY_OPT_OUT_KEY);\n }\n } catch {\n // No-op — see getOrCreateTelemetryDistinctId.\n }\n}\n\nexport function hasTelemetryDisclosureBeenShown(): boolean {\n if (typeof window === \"undefined\") return false;\n try {\n return (\n window.localStorage.getItem(TELEMETRY_DISCLOSURE_SHOWN_KEY) === \"true\"\n );\n } catch {\n return false;\n }\n}\n\nexport function markTelemetryDisclosureShown(): void {\n if (typeof window === \"undefined\") return;\n try {\n window.localStorage.setItem(TELEMETRY_DISCLOSURE_SHOWN_KEY, \"true\");\n } catch {\n // No-op.\n }\n}\n\nfunction generateUuidV4(): string {\n if (\n typeof globalThis.crypto !== \"undefined\" &&\n typeof globalThis.crypto.randomUUID === \"function\"\n ) {\n return globalThis.crypto.randomUUID();\n }\n // Fallback for environments without crypto.randomUUID (older browsers,\n // some test runners). Not cryptographically strong; acceptable because\n // the value is just an anonymous correlation ID.\n return \"xxxxxxxx-xxxx-4xxx-yxxx-xxxxxxxxxxxx\".replace(/[xy]/g, (c) => {\n const r = (Math.random() * 16) | 0;\n const v = c === \"x\" ? r : (r & 0x3) | 0x8;\n return v.toString(16);\n });\n}\n"],"mappings":"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"}
@@ -50,6 +50,50 @@ function isFiniteNumber(value) {
50
50
  function isValidDockMode(value) {
51
51
  return value === "floating" || value === "docked-left";
52
52
  }
53
+ const INSPECTOR_DISMISSAL_MIRROR_KEY = "cpk:inspector:dismissed_until";
54
+ const INSPECTOR_DISMISSAL_COOKIE_NAME = "cpk_inspector_dismissed_until";
55
+ const INSPECTOR_DISMISSAL_MAX_DURATION_MS = 10080 * 60 * 1e3;
56
+ /** Return the active host-scoped Inspector dismissal deadline, if any. */
57
+ function loadInspectorDismissedUntil(now = Date.now()) {
58
+ const until = parseInspectorDismissalPayload(readCookie(INSPECTOR_DISMISSAL_COOKIE_NAME)) ?? parseInspectorDismissalPayload(readLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY));
59
+ if (until === null) return null;
60
+ if (until <= now) {
61
+ clearInspectorDismissal();
62
+ return null;
63
+ }
64
+ const maximumUntil = now + INSPECTOR_DISMISSAL_MAX_DURATION_MS;
65
+ if (until > maximumUntil) {
66
+ saveInspectorDismissedUntil(maximumUntil, now);
67
+ return maximumUntil;
68
+ }
69
+ return until;
70
+ }
71
+ /** Persist a dismissal across browser sessions and localhost ports. */
72
+ function saveInspectorDismissedUntil(until, now = Date.now()) {
73
+ if (!Number.isFinite(until) || until <= now) {
74
+ clearInspectorDismissal();
75
+ return;
76
+ }
77
+ const boundedUntil = Math.min(until, now + INSPECTOR_DISMISSAL_MAX_DURATION_MS);
78
+ const payload = JSON.stringify({ until: boundedUntil });
79
+ writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, payload, `Max-Age=${Math.max(1, Math.ceil((boundedUntil - now) / 1e3))}`);
80
+ writeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY, payload);
81
+ }
82
+ /** Clear both persistence layers, including an expired host cookie. */
83
+ function clearInspectorDismissal() {
84
+ writeCookie(INSPECTOR_DISMISSAL_COOKIE_NAME, "", "Max-Age=0");
85
+ removeLocalStorageItem(INSPECTOR_DISMISSAL_MIRROR_KEY);
86
+ }
87
+ /** Parse a finite deadline from either persistence layer. */
88
+ function parseInspectorDismissalPayload(raw) {
89
+ if (!raw) return null;
90
+ try {
91
+ const parsed = JSON.parse(raw);
92
+ return typeof parsed.until === "number" && Number.isFinite(parsed.until) ? parsed.until : null;
93
+ } catch {
94
+ return null;
95
+ }
96
+ }
53
97
  const ANNOUNCEMENT_READ_COOKIE_NAME = "cpk_inspector_announcements";
54
98
  const ANNOUNCEMENT_READ_MIRROR_KEY = "cpk:inspector:announcement_read";
55
99
  const LEGACY_ANNOUNCEMENT_READ_KEY = "cpk:inspector:announcements";
@@ -110,21 +154,27 @@ function parseTimestampPayload(raw) {
110
154
  }
111
155
  }
112
156
  function readAnnouncementCookie() {
157
+ return readCookie(ANNOUNCEMENT_READ_COOKIE_NAME);
158
+ }
159
+ function writeAnnouncementCookie(value) {
160
+ writeCookie(ANNOUNCEMENT_READ_COOKIE_NAME, value, `Max-Age=${ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS}`);
161
+ }
162
+ function readCookie(name) {
113
163
  if (typeof document === "undefined") return null;
114
164
  try {
115
165
  for (const entry of document.cookie.split(";")) {
116
166
  const separator = entry.indexOf("=");
117
167
  if (separator === -1) continue;
118
- if (entry.slice(0, separator).trim() !== ANNOUNCEMENT_READ_COOKIE_NAME) continue;
168
+ if (entry.slice(0, separator).trim() !== name) continue;
119
169
  return decodeURIComponent(entry.slice(separator + 1).trim());
120
170
  }
121
171
  } catch {}
122
172
  return null;
123
173
  }
124
- function writeAnnouncementCookie(value) {
174
+ function writeCookie(name, value, lifetime) {
125
175
  if (typeof document === "undefined") return;
126
176
  try {
127
- document.cookie = `${ANNOUNCEMENT_READ_COOKIE_NAME}=${encodeURIComponent(value)}; Path=/; Max-Age=${ANNOUNCEMENT_READ_COOKIE_MAX_AGE_SECONDS}; SameSite=Lax`;
177
+ document.cookie = `${name}=${encodeURIComponent(value)}; Path=/; ${lifetime}; SameSite=Lax`;
128
178
  } catch {}
129
179
  }
130
180
  function readLocalStorageItem(key) {
@@ -194,5 +244,5 @@ function generateUuidV4() {
194
244
  }
195
245
 
196
246
  //#endregion
197
- export { clearLegacyAnnouncementReadState, getOrCreateTelemetryDistinctId, hasTelemetryDisclosureBeenShown, isTelemetryOptedOut, isValidAnchor, isValidDockMode, isValidPosition, isValidSize, loadAnnouncementPulsedTimestamp, loadAnnouncementReadTimestamp, loadInspectorState, markTelemetryDisclosureShown, saveAnnouncementPulsedTimestamp, saveAnnouncementReadTimestamp, saveInspectorState };
247
+ export { INSPECTOR_DISMISSAL_MAX_DURATION_MS, clearLegacyAnnouncementReadState, getOrCreateTelemetryDistinctId, hasTelemetryDisclosureBeenShown, isTelemetryOptedOut, isValidAnchor, isValidDockMode, isValidPosition, isValidSize, loadAnnouncementPulsedTimestamp, loadAnnouncementReadTimestamp, loadInspectorDismissedUntil, loadInspectorState, markTelemetryDisclosureShown, saveAnnouncementPulsedTimestamp, saveAnnouncementReadTimestamp, saveInspectorDismissedUntil, saveInspectorState };
198
248
  //# sourceMappingURL=persistence.mjs.map