@agent-native/core 0.77.24 → 0.78.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.
Files changed (212) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +20 -0
  3. package/corpus/core/docs/content/template-analytics.md +5 -0
  4. package/corpus/core/docs/content/tracking.md +45 -0
  5. package/corpus/core/package.json +3 -1
  6. package/corpus/core/src/agent/types.ts +22 -0
  7. package/corpus/core/src/client/AgentPanel.tsx +34 -18
  8. package/corpus/core/src/client/AssistantChat.tsx +40 -0
  9. package/corpus/core/src/client/agent-chat.ts +173 -0
  10. package/corpus/core/src/client/analytics.ts +141 -5
  11. package/corpus/core/src/client/composer/TiptapComposer.tsx +316 -60
  12. package/corpus/core/src/client/composer/extensions/MentionReference.tsx +3 -0
  13. package/corpus/core/src/client/composer/types.ts +22 -0
  14. package/corpus/core/src/client/index.ts +14 -0
  15. package/corpus/core/src/client/session-replay.ts +800 -0
  16. package/corpus/core/src/db/schema.ts +12 -0
  17. package/corpus/core/src/deploy/build.ts +75 -0
  18. package/corpus/core/src/private-blob/index.ts +18 -0
  19. package/corpus/core/src/private-blob/registry.ts +241 -0
  20. package/corpus/core/src/private-blob/types.ts +55 -0
  21. package/corpus/core/src/scripts/utils.ts +34 -6
  22. package/corpus/core/src/server/agent-chat-plugin.ts +10 -0
  23. package/corpus/core/src/server/email.ts +5 -0
  24. package/corpus/core/src/server/index.ts +1 -0
  25. package/corpus/core/src/styles/agent-native.css +24 -0
  26. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +1 -1
  27. package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +43 -0
  28. package/corpus/templates/analytics/AGENTS.md +32 -1
  29. package/corpus/templates/analytics/DEVELOPING.md +9 -1
  30. package/corpus/templates/analytics/actions/delete-dashboard-report-subscription.ts +24 -0
  31. package/corpus/templates/analytics/actions/get-session-replay-events.ts +50 -0
  32. package/corpus/templates/analytics/actions/get-session-replay-summary.ts +27 -0
  33. package/corpus/templates/analytics/actions/list-dashboard-report-subscriptions.ts +24 -0
  34. package/corpus/templates/analytics/actions/list-session-recordings.ts +59 -0
  35. package/corpus/templates/analytics/actions/navigate.ts +15 -5
  36. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +2 -2
  37. package/corpus/templates/analytics/actions/save-dashboard-report-subscription.ts +36 -0
  38. package/corpus/templates/analytics/actions/send-dashboard-report-now.ts +60 -0
  39. package/corpus/templates/analytics/actions/view-screen.ts +50 -2
  40. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +51 -2
  41. package/corpus/templates/analytics/app/components/layout/Header.tsx +4 -1
  42. package/corpus/templates/analytics/app/components/layout/Layout.tsx +14 -2
  43. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +6 -6
  44. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +35 -6
  45. package/corpus/templates/analytics/app/i18n-data.ts +1119 -0
  46. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +437 -0
  47. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +11 -12
  48. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +43 -0
  49. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +136 -43
  50. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-filters.ts +26 -0
  51. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +450 -0
  52. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +419 -0
  53. package/corpus/templates/analytics/app/routes/overview.tsx +13 -6
  54. package/corpus/templates/analytics/app/routes/sessions.$recordingId.tsx +10 -0
  55. package/corpus/templates/analytics/app/routes/sessions._index.tsx +10 -0
  56. package/corpus/templates/analytics/changelog/2026-06-25-analytics-now-opens-directly-to-ask-instead-of-the-old-overv.md +6 -0
  57. package/corpus/templates/analytics/changelog/2026-06-25-dashboard-charts-now-show-reliable-blue-placement-lines-whil.md +6 -0
  58. package/corpus/templates/analytics/changelog/2026-06-26-dashboards-can-now-send-scheduled-daily-email-reports-from-t.md +6 -0
  59. package/corpus/templates/analytics/changelog/2026-06-26-session-replay-browser-sessions-can-now-be-reviewed-from.md +6 -0
  60. package/corpus/templates/analytics/package.json +5 -2
  61. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +131 -0
  62. package/corpus/templates/analytics/scripts/seed-session-replay.ts +198 -0
  63. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +140 -27
  64. package/corpus/templates/analytics/server/db/index.ts +12 -0
  65. package/corpus/templates/analytics/server/db/schema.ts +131 -0
  66. package/corpus/templates/analytics/server/handlers/session-replay.ts +281 -0
  67. package/corpus/templates/analytics/server/handlers/sql-query.ts +8 -348
  68. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +66 -0
  69. package/corpus/templates/analytics/server/jobs/session-replay-retention.ts +29 -0
  70. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +365 -0
  71. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +473 -0
  72. package/corpus/templates/analytics/server/lib/dashboard-report.ts +405 -0
  73. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +63 -18
  74. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +98 -26
  75. package/corpus/templates/analytics/server/lib/session-replay.ts +1645 -0
  76. package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
  77. package/corpus/templates/analytics/server/plugins/dashboard-report-jobs.ts +47 -0
  78. package/corpus/templates/analytics/server/plugins/db.ts +200 -0
  79. package/corpus/templates/analytics/server/plugins/session-replay-retention-jobs.ts +41 -0
  80. package/corpus/templates/analytics/server/routes/api/analytics/replay.options.ts +1 -0
  81. package/corpus/templates/analytics/server/routes/api/analytics/replay.post.ts +1 -0
  82. package/corpus/templates/analytics/server/routes/api/dashboard-reports/run.post.ts +61 -0
  83. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/chunks/[seq].get.ts +1 -0
  84. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/events.get.ts +1 -0
  85. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId]/manifest.get.ts +1 -0
  86. package/corpus/templates/analytics/server/routes/api/session-replay/recordings/[recordingId].get.ts +1 -0
  87. package/corpus/templates/analytics/server/routes/api/session-replay/recordings.get.ts +1 -0
  88. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +10 -2
  89. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +14 -7
  90. package/corpus/templates/assets/.agents/skills/library-management/SKILL.md +12 -0
  91. package/corpus/templates/assets/AGENTS.md +18 -3
  92. package/corpus/templates/assets/actions/_image-model-default.ts +24 -0
  93. package/corpus/templates/assets/actions/dismiss-variant-slots.ts +15 -22
  94. package/corpus/templates/assets/actions/generate-asset.ts +30 -23
  95. package/corpus/templates/assets/actions/generate-image-batch.ts +76 -29
  96. package/corpus/templates/assets/actions/generate-image.ts +46 -30
  97. package/corpus/templates/assets/actions/generate-video.ts +18 -3
  98. package/corpus/templates/assets/actions/list-assets.ts +39 -8
  99. package/corpus/templates/assets/actions/list-libraries.ts +24 -2
  100. package/corpus/templates/assets/actions/navigate.ts +1 -1
  101. package/corpus/templates/assets/actions/open-asset-picker.ts +38 -1
  102. package/corpus/templates/assets/actions/refresh-generation-run.ts +10 -0
  103. package/corpus/templates/assets/actions/save-generated-image.ts +21 -19
  104. package/corpus/templates/assets/actions/variant-slots.ts +131 -28
  105. package/corpus/templates/assets/actions/view-screen.ts +9 -0
  106. package/corpus/templates/assets/app/components/generation/GenerationResults.tsx +425 -0
  107. package/corpus/templates/assets/app/components/layout/Header.tsx +3 -1
  108. package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
  109. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +4 -6
  110. package/corpus/templates/assets/app/hooks/use-navigation-state.ts +47 -21
  111. package/corpus/templates/assets/app/i18n-data.ts +1815 -0
  112. package/corpus/templates/assets/app/lib/libraries.ts +6 -0
  113. package/corpus/templates/assets/app/lib/picker-chat-handoff.ts +71 -0
  114. package/corpus/templates/assets/app/routes/_index.tsx +4 -0
  115. package/corpus/templates/assets/app/routes/asset.$id.tsx +2 -2
  116. package/corpus/templates/assets/app/routes/audit.tsx +1 -1
  117. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +1557 -2164
  118. package/corpus/templates/assets/app/routes/brand-kits._index.tsx +7 -191
  119. package/corpus/templates/assets/app/routes/brand-kits.tsx +1 -5
  120. package/corpus/templates/assets/app/routes/libraries.tsx +4 -4
  121. package/corpus/templates/assets/app/routes/library.$id.tsx +9 -7
  122. package/corpus/templates/assets/app/routes/library.tsx +1454 -175
  123. package/corpus/templates/assets/server/db/index.ts +1 -1
  124. package/corpus/templates/assets/server/lib/generation.ts +86 -15
  125. package/corpus/templates/assets/server/plugins/agent-chat.ts +196 -0
  126. package/corpus/templates/assets/server/plugins/db.ts +5 -0
  127. package/corpus/templates/assets/shared/api.ts +6 -3
  128. package/dist/agent/types.d.ts +21 -0
  129. package/dist/agent/types.d.ts.map +1 -1
  130. package/dist/agent/types.js.map +1 -1
  131. package/dist/client/AgentPanel.d.ts +6 -2
  132. package/dist/client/AgentPanel.d.ts.map +1 -1
  133. package/dist/client/AgentPanel.js +10 -4
  134. package/dist/client/AgentPanel.js.map +1 -1
  135. package/dist/client/AssistantChat.d.ts +6 -0
  136. package/dist/client/AssistantChat.d.ts.map +1 -1
  137. package/dist/client/AssistantChat.js +15 -3
  138. package/dist/client/AssistantChat.js.map +1 -1
  139. package/dist/client/agent-chat.d.ts +32 -0
  140. package/dist/client/agent-chat.d.ts.map +1 -1
  141. package/dist/client/agent-chat.js +113 -0
  142. package/dist/client/agent-chat.js.map +1 -1
  143. package/dist/client/analytics.d.ts +22 -3
  144. package/dist/client/analytics.d.ts.map +1 -1
  145. package/dist/client/analytics.js +96 -4
  146. package/dist/client/analytics.js.map +1 -1
  147. package/dist/client/composer/TiptapComposer.d.ts +2 -1
  148. package/dist/client/composer/TiptapComposer.d.ts.map +1 -1
  149. package/dist/client/composer/TiptapComposer.js +228 -56
  150. package/dist/client/composer/TiptapComposer.js.map +1 -1
  151. package/dist/client/composer/extensions/MentionReference.d.ts.map +1 -1
  152. package/dist/client/composer/extensions/MentionReference.js +3 -0
  153. package/dist/client/composer/extensions/MentionReference.js.map +1 -1
  154. package/dist/client/composer/types.d.ts +21 -0
  155. package/dist/client/composer/types.d.ts.map +1 -1
  156. package/dist/client/composer/types.js.map +1 -1
  157. package/dist/client/index.d.ts +2 -2
  158. package/dist/client/index.d.ts.map +1 -1
  159. package/dist/client/index.js +2 -2
  160. package/dist/client/index.js.map +1 -1
  161. package/dist/client/session-replay.d.ts +41 -0
  162. package/dist/client/session-replay.d.ts.map +1 -0
  163. package/dist/client/session-replay.js +595 -0
  164. package/dist/client/session-replay.js.map +1 -0
  165. package/dist/collab/awareness.d.ts +2 -2
  166. package/dist/collab/awareness.d.ts.map +1 -1
  167. package/dist/collab/routes.d.ts +2 -2
  168. package/dist/db/schema.d.ts +3 -1
  169. package/dist/db/schema.d.ts.map +1 -1
  170. package/dist/db/schema.js +6 -2
  171. package/dist/db/schema.js.map +1 -1
  172. package/dist/deploy/build.d.ts +8 -0
  173. package/dist/deploy/build.d.ts.map +1 -1
  174. package/dist/deploy/build.js +66 -0
  175. package/dist/deploy/build.js.map +1 -1
  176. package/dist/file-upload/actions/upload-image.d.ts +2 -2
  177. package/dist/notifications/routes.d.ts +3 -3
  178. package/dist/observability/routes.d.ts +8 -8
  179. package/dist/private-blob/index.d.ts +3 -0
  180. package/dist/private-blob/index.d.ts.map +1 -0
  181. package/dist/private-blob/index.js +2 -0
  182. package/dist/private-blob/index.js.map +1 -0
  183. package/dist/private-blob/registry.d.ts +10 -0
  184. package/dist/private-blob/registry.d.ts.map +1 -0
  185. package/dist/private-blob/registry.js +152 -0
  186. package/dist/private-blob/registry.js.map +1 -0
  187. package/dist/private-blob/types.d.ts +51 -0
  188. package/dist/private-blob/types.d.ts.map +1 -0
  189. package/dist/private-blob/types.js +2 -0
  190. package/dist/private-blob/types.js.map +1 -0
  191. package/dist/resources/handlers.d.ts +3 -3
  192. package/dist/scripts/utils.d.ts +4 -4
  193. package/dist/scripts/utils.d.ts.map +1 -1
  194. package/dist/scripts/utils.js +30 -6
  195. package/dist/scripts/utils.js.map +1 -1
  196. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  197. package/dist/server/agent-chat-plugin.js +5 -0
  198. package/dist/server/agent-chat-plugin.js.map +1 -1
  199. package/dist/server/agent-engine-api-key-route.d.ts +2 -2
  200. package/dist/server/email.d.ts +2 -0
  201. package/dist/server/email.d.ts.map +1 -1
  202. package/dist/server/email.js +3 -0
  203. package/dist/server/email.js.map +1 -1
  204. package/dist/server/index.d.ts +1 -1
  205. package/dist/server/index.d.ts.map +1 -1
  206. package/dist/server/index.js.map +1 -1
  207. package/dist/server/transcribe-voice.d.ts +1 -1
  208. package/dist/styles/agent-native.css +24 -0
  209. package/docs/content/template-analytics.md +5 -0
  210. package/docs/content/tracking.md +45 -0
  211. package/package.json +3 -1
  212. package/corpus/templates/analytics/app/pages/overview/OverviewPage.tsx +0 -209
@@ -19,16 +19,20 @@
19
19
  import { sql } from "drizzle-orm";
20
20
  import {
21
21
  pgTable,
22
+ index as pgIndex,
22
23
  text as pgText,
23
24
  integer as pgInteger,
24
25
  boolean as pgBoolean,
25
26
  doublePrecision as pgDoublePrecision,
27
+ uniqueIndex as pgUniqueIndex,
26
28
  } from "drizzle-orm/pg-core";
27
29
  import {
28
30
  sqliteTable,
31
+ index as sqliteIndex,
29
32
  text as sqliteText,
30
33
  integer as sqliteInteger,
31
34
  real as sqliteReal,
35
+ uniqueIndex as sqliteUniqueIndex,
32
36
  } from "drizzle-orm/sqlite-core";
33
37
 
34
38
  import { getDialect } from "./client.js";
@@ -46,6 +50,14 @@ function pg(): boolean {
46
50
  export const table: typeof sqliteTable = ((...args: any[]) =>
47
51
  pg() ? (pgTable as any)(...args) : (sqliteTable as any)(...args)) as any;
48
52
 
53
+ export const index: typeof sqliteIndex = ((...args: any[]) =>
54
+ pg() ? (pgIndex as any)(...args) : (sqliteIndex as any)(...args)) as any;
55
+
56
+ export const uniqueIndex: typeof sqliteUniqueIndex = ((...args: any[]) =>
57
+ pg()
58
+ ? (pgUniqueIndex as any)(...args)
59
+ : (sqliteUniqueIndex as any)(...args)) as any;
60
+
49
61
  /**
50
62
  * Text column. Works identically in both dialects.
51
63
  * Supports `{ enum: [...] }` config in both.
@@ -1318,6 +1318,16 @@ const SERVERLESS_FFMPEG_STATIC_ARCHES = new Set<NodeJS.Architecture>([
1318
1318
  "arm64",
1319
1319
  "x64",
1320
1320
  ]);
1321
+ const SERVERLESS_FUNCTION_PACKAGE_DENYLIST = new Set([
1322
+ "@vscode/test-electron",
1323
+ "electron",
1324
+ "electron-builder",
1325
+ "electron-updater",
1326
+ "electron-vite",
1327
+ "fsevents",
1328
+ "node-pty",
1329
+ "playwright",
1330
+ ]);
1321
1331
  type ServerlessFfmpegStaticArch = "arm64" | "x64";
1322
1332
 
1323
1333
  function serverlessFfmpegStaticTargetArchFromEnv(): ServerlessFfmpegStaticArch | null {
@@ -1748,6 +1758,70 @@ function copyInstalledFfmpegStaticPackage(serverDir: string | undefined) {
1748
1758
  );
1749
1759
  }
1750
1760
 
1761
+ /**
1762
+ * Nitro's file tracer can over-include optional desktop/dev packages that are
1763
+ * present in a monorepo install but cannot run in serverless. Netlify installs
1764
+ * the generated per-function package.json before upload; if `electron` remains
1765
+ * there, the function can exceed Netlify's 250 MB unzipped size limit even
1766
+ * though the server bundle never imports Electron at runtime.
1767
+ */
1768
+ export function sanitizeServerlessFunctionPackageManifest(
1769
+ functionDir: string | undefined,
1770
+ ): void {
1771
+ if (!functionDir || !fs.existsSync(functionDir)) return;
1772
+
1773
+ const packageJsonPath = path.join(functionDir, "package.json");
1774
+ if (!fs.existsSync(packageJsonPath)) return;
1775
+
1776
+ let packageJson: Record<string, unknown>;
1777
+ try {
1778
+ packageJson = JSON.parse(fs.readFileSync(packageJsonPath, "utf8"));
1779
+ } catch {
1780
+ return;
1781
+ }
1782
+
1783
+ let removed = 0;
1784
+ const depFields = [
1785
+ "dependencies",
1786
+ "optionalDependencies",
1787
+ "devDependencies",
1788
+ "peerDependencies",
1789
+ ];
1790
+ for (const field of depFields) {
1791
+ const deps = packageJson[field];
1792
+ if (!deps || typeof deps !== "object" || Array.isArray(deps)) continue;
1793
+ const depRecord = deps as Record<string, unknown>;
1794
+ for (const packageName of SERVERLESS_FUNCTION_PACKAGE_DENYLIST) {
1795
+ if (Object.prototype.hasOwnProperty.call(depRecord, packageName)) {
1796
+ delete depRecord[packageName];
1797
+ removed++;
1798
+ }
1799
+ }
1800
+ if (Object.keys(depRecord).length === 0) {
1801
+ delete packageJson[field];
1802
+ }
1803
+ }
1804
+
1805
+ const nodeModulesDir = path.join(functionDir, "node_modules");
1806
+ for (const packageName of SERVERLESS_FUNCTION_PACKAGE_DENYLIST) {
1807
+ const packageDir = path.join(nodeModulesDir, ...packageName.split("/"));
1808
+ if (fs.existsSync(packageDir)) {
1809
+ fs.rmSync(packageDir, { recursive: true, force: true });
1810
+ removed++;
1811
+ }
1812
+ }
1813
+
1814
+ if (removed > 0) {
1815
+ fs.writeFileSync(
1816
+ packageJsonPath,
1817
+ `${JSON.stringify(packageJson, null, 2)}\n`,
1818
+ );
1819
+ console.log(
1820
+ `[deploy] Removed ${removed} desktop-only package reference(s) from ${path.relative(cwd, functionDir)}.`,
1821
+ );
1822
+ }
1823
+ }
1824
+
1751
1825
  /**
1752
1826
  * Create stub directories for dangling platform-specific optional dependency
1753
1827
  * symlinks in the pnpm store.
@@ -2139,6 +2213,7 @@ export default bundle;
2139
2213
  copyInstalledLibsqlNativePackages(nitro.options.output.serverDir);
2140
2214
  copyInstalledResvgPackages(nitro.options.output.serverDir);
2141
2215
  copyInstalledFfmpegStaticPackage(nitro.options.output.serverDir);
2216
+ sanitizeServerlessFunctionPackageManifest(nitro.options.output.serverDir);
2142
2217
  }
2143
2218
 
2144
2219
  // Durable background agent runs (default-OFF / opt-in; enable with a truthy
@@ -0,0 +1,18 @@
1
+ export type {
2
+ PrivateBlobDeleteResult,
3
+ PrivateBlobHandle,
4
+ PrivateBlobMetadata,
5
+ PrivateBlobProvider,
6
+ PrivateBlobPutInput,
7
+ PrivateBlobReadResult,
8
+ } from "./types.js";
9
+ export {
10
+ deletePrivateBlob,
11
+ getActivePrivateBlobProvider,
12
+ listPrivateBlobProviders,
13
+ putPrivateBlob,
14
+ readPrivateBlob,
15
+ registerPrivateBlobProvider,
16
+ setPrivateBlobPublicUploadFallbackEnabled,
17
+ unregisterPrivateBlobProvider,
18
+ } from "./registry.js";
@@ -0,0 +1,241 @@
1
+ import { createCipheriv, createDecipheriv, randomBytes } from "node:crypto";
2
+
3
+ import { uploadFile } from "../file-upload/index.js";
4
+ import {
5
+ decryptSecretValue,
6
+ encryptSecretValue,
7
+ getSecretEncryptionKey,
8
+ } from "../secrets/crypto.js";
9
+ import type {
10
+ PrivateBlobDeleteResult,
11
+ PrivateBlobHandle,
12
+ PrivateBlobProvider,
13
+ PrivateBlobPutInput,
14
+ PrivateBlobReadResult,
15
+ } from "./types.js";
16
+
17
+ interface PrivateBlobGlobals {
18
+ __agentNativePrivateBlobProviders?: Map<string, PrivateBlobProvider>;
19
+ __agentNativePrivateBlobPublicUploadFallback?: { enabled: boolean };
20
+ }
21
+
22
+ interface EncryptedPayload {
23
+ iv: string;
24
+ tag: string;
25
+ ciphertext: Uint8Array;
26
+ }
27
+
28
+ interface EncryptionParams {
29
+ iv: string;
30
+ tag: string;
31
+ }
32
+
33
+ interface PublicUploadDescriptor {
34
+ kind: "agent-native.private-blob.public-upload";
35
+ version: 1;
36
+ url: string;
37
+ uploadProvider: string;
38
+ uploadId?: string;
39
+ encryption: EncryptionParams;
40
+ mimeType?: string;
41
+ metadata?: PrivateBlobHandle["metadata"];
42
+ size: number;
43
+ createdAt: string;
44
+ }
45
+
46
+ const PUBLIC_UPLOAD_HANDLE_PREFIX = "public-upload:v1:";
47
+ const globals = globalThis as typeof globalThis & PrivateBlobGlobals;
48
+ const providers: Map<string, PrivateBlobProvider> =
49
+ (globals.__agentNativePrivateBlobProviders ??= new Map());
50
+ const publicUploadFallbackRef: { enabled: boolean } =
51
+ (globals.__agentNativePrivateBlobPublicUploadFallback ??= {
52
+ enabled: true,
53
+ });
54
+
55
+ function toBytes(data: Uint8Array | Buffer): Uint8Array {
56
+ return data instanceof Uint8Array ? data : new Uint8Array(data);
57
+ }
58
+
59
+ function encryptBytes(data: Uint8Array): EncryptedPayload {
60
+ const iv = randomBytes(12);
61
+ const cipher = createCipheriv("aes-256-gcm", getSecretEncryptionKey(), iv);
62
+ const ciphertext = Buffer.concat([
63
+ cipher.update(Buffer.from(data)),
64
+ cipher.final(),
65
+ ]);
66
+ return {
67
+ iv: iv.toString("base64url"),
68
+ ciphertext: new Uint8Array(ciphertext),
69
+ tag: cipher.getAuthTag().toString("base64url"),
70
+ };
71
+ }
72
+
73
+ function decryptBytes(
74
+ params: EncryptionParams,
75
+ ciphertext: Uint8Array,
76
+ ): Uint8Array {
77
+ const decipher = createDecipheriv(
78
+ "aes-256-gcm",
79
+ getSecretEncryptionKey(),
80
+ Buffer.from(params.iv, "base64url"),
81
+ );
82
+ decipher.setAuthTag(Buffer.from(params.tag, "base64url"));
83
+ return new Uint8Array(
84
+ Buffer.concat([decipher.update(Buffer.from(ciphertext)), decipher.final()]),
85
+ );
86
+ }
87
+
88
+ function encodePublicUploadDescriptor(
89
+ descriptor: PublicUploadDescriptor,
90
+ ): string {
91
+ return `${PUBLIC_UPLOAD_HANDLE_PREFIX}${encryptSecretValue(
92
+ JSON.stringify(descriptor),
93
+ )}`;
94
+ }
95
+
96
+ function decodePublicUploadDescriptor(id: string): PublicUploadDescriptor {
97
+ if (!id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX)) {
98
+ throw new Error(
99
+ "Private blob handle is not a public-upload fallback handle",
100
+ );
101
+ }
102
+ const raw = decryptSecretValue(id.slice(PUBLIC_UPLOAD_HANDLE_PREFIX.length));
103
+ const descriptor = JSON.parse(raw) as PublicUploadDescriptor;
104
+ if (
105
+ descriptor?.kind !== "agent-native.private-blob.public-upload" ||
106
+ descriptor.version !== 1 ||
107
+ typeof descriptor.url !== "string"
108
+ ) {
109
+ throw new Error("Private blob handle descriptor is invalid");
110
+ }
111
+ return descriptor;
112
+ }
113
+
114
+ function isPublicUploadFallbackHandle(handle: PrivateBlobHandle): boolean {
115
+ return handle.id.startsWith(PUBLIC_UPLOAD_HANDLE_PREFIX);
116
+ }
117
+
118
+ async function putViaEncryptedPublicUpload(
119
+ input: PrivateBlobPutInput,
120
+ ): Promise<PrivateBlobHandle | null> {
121
+ const bytes = toBytes(input.data);
122
+ const encrypted = encryptBytes(bytes);
123
+ const uploaded = await uploadFile({
124
+ data: Buffer.from(encrypted.ciphertext),
125
+ filename: input.filename ?? input.key ?? "private-blob.bin",
126
+ mimeType: "application/octet-stream",
127
+ ownerEmail: input.ownerEmail,
128
+ });
129
+ if (!uploaded) return null;
130
+
131
+ const descriptor: PublicUploadDescriptor = {
132
+ kind: "agent-native.private-blob.public-upload",
133
+ version: 1,
134
+ url: uploaded.url,
135
+ uploadProvider: uploaded.provider,
136
+ uploadId: uploaded.id,
137
+ encryption: { iv: encrypted.iv, tag: encrypted.tag },
138
+ mimeType: input.mimeType,
139
+ metadata: input.metadata,
140
+ size: bytes.byteLength,
141
+ createdAt: new Date().toISOString(),
142
+ };
143
+
144
+ return {
145
+ id: encodePublicUploadDescriptor(descriptor),
146
+ provider: `public-upload:${uploaded.provider}`,
147
+ opaque: true,
148
+ encrypted: true,
149
+ mimeType: input.mimeType,
150
+ size: bytes.byteLength,
151
+ createdAt: descriptor.createdAt,
152
+ metadata: input.metadata,
153
+ };
154
+ }
155
+
156
+ async function readViaEncryptedPublicUpload(
157
+ handle: PrivateBlobHandle,
158
+ ): Promise<PrivateBlobReadResult> {
159
+ const descriptor = decodePublicUploadDescriptor(handle.id);
160
+ const response = await fetch(descriptor.url);
161
+ if (!response.ok) {
162
+ throw new Error(
163
+ `Private blob public-upload read failed (${response.status}): ${response.statusText}`,
164
+ );
165
+ }
166
+ // The uploaded ciphertext is intentionally opaque; the descriptor carries
167
+ // auth tag + IV separately so the backing public URL is useless by itself.
168
+ const ciphertext = new Uint8Array(await response.arrayBuffer());
169
+ return {
170
+ data: decryptBytes(descriptor.encryption, ciphertext),
171
+ mimeType: descriptor.mimeType,
172
+ metadata: descriptor.metadata,
173
+ handle,
174
+ };
175
+ }
176
+
177
+ export function registerPrivateBlobProvider(
178
+ provider: PrivateBlobProvider,
179
+ ): void {
180
+ providers.set(provider.id, provider);
181
+ }
182
+
183
+ export function unregisterPrivateBlobProvider(id: string): void {
184
+ providers.delete(id);
185
+ }
186
+
187
+ export function listPrivateBlobProviders(): PrivateBlobProvider[] {
188
+ return [...providers.values()];
189
+ }
190
+
191
+ export function getActivePrivateBlobProvider(): PrivateBlobProvider | null {
192
+ for (const provider of providers.values()) {
193
+ if (provider.isConfigured()) return provider;
194
+ }
195
+ return null;
196
+ }
197
+
198
+ export function setPrivateBlobPublicUploadFallbackEnabled(
199
+ enabled: boolean,
200
+ ): void {
201
+ publicUploadFallbackRef.enabled = enabled;
202
+ }
203
+
204
+ export async function putPrivateBlob(
205
+ input: PrivateBlobPutInput,
206
+ ): Promise<PrivateBlobHandle | null> {
207
+ const provider = getActivePrivateBlobProvider();
208
+ if (provider) return provider.put(input);
209
+ if (!publicUploadFallbackRef.enabled) return null;
210
+ if (process.env.AGENT_NATIVE_PRIVATE_BLOB_PUBLIC_UPLOAD_FALLBACK === "0") {
211
+ return null;
212
+ }
213
+ return putViaEncryptedPublicUpload(input);
214
+ }
215
+
216
+ export async function readPrivateBlob(
217
+ handle: PrivateBlobHandle,
218
+ ): Promise<PrivateBlobReadResult> {
219
+ const provider = providers.get(handle.provider);
220
+ if (provider) return provider.read(handle);
221
+ if (isPublicUploadFallbackHandle(handle)) {
222
+ return readViaEncryptedPublicUpload(handle);
223
+ }
224
+ throw new Error(`No private blob provider registered for ${handle.provider}`);
225
+ }
226
+
227
+ export async function deletePrivateBlob(
228
+ handle: PrivateBlobHandle,
229
+ ): Promise<PrivateBlobDeleteResult> {
230
+ const provider = providers.get(handle.provider);
231
+ if (provider) return provider.delete(handle);
232
+ if (isPublicUploadFallbackHandle(handle)) {
233
+ return {
234
+ deleted: false,
235
+ provider: handle.provider,
236
+ reason:
237
+ "delete is not supported by the encrypted public-upload fallback provider",
238
+ };
239
+ }
240
+ throw new Error(`No private blob provider registered for ${handle.provider}`);
241
+ }
@@ -0,0 +1,55 @@
1
+ export interface PrivateBlobMetadata {
2
+ [key: string]: string | number | boolean | null | undefined;
3
+ }
4
+
5
+ export interface PrivateBlobPutInput {
6
+ /** Private blob contents. */
7
+ data: Uint8Array | Buffer;
8
+ /** Optional caller-owned key for providers that support deterministic paths. */
9
+ key?: string;
10
+ /** Original filename or logical blob name. */
11
+ filename?: string;
12
+ /** MIME type, e.g. "application/json". */
13
+ mimeType?: string;
14
+ /** Optional owner email for provider-specific scoping. */
15
+ ownerEmail?: string;
16
+ /** Low-cardinality metadata safe to store beside the blob. */
17
+ metadata?: PrivateBlobMetadata;
18
+ }
19
+
20
+ export interface PrivateBlobHandle {
21
+ /** Opaque provider handle. Consumers must not interpret this as a URL. */
22
+ id: string;
23
+ /** Provider that owns the handle. */
24
+ provider: string;
25
+ /** True for handles that intentionally do not expose fetchable URLs. */
26
+ opaque: true;
27
+ /** True when bytes are encrypted before reaching the backing store. */
28
+ encrypted: boolean;
29
+ mimeType?: string;
30
+ size?: number;
31
+ createdAt?: string;
32
+ metadata?: PrivateBlobMetadata;
33
+ }
34
+
35
+ export interface PrivateBlobReadResult {
36
+ data: Uint8Array;
37
+ mimeType?: string;
38
+ metadata?: PrivateBlobMetadata;
39
+ handle: PrivateBlobHandle;
40
+ }
41
+
42
+ export interface PrivateBlobDeleteResult {
43
+ deleted: boolean;
44
+ provider: string;
45
+ reason?: string;
46
+ }
47
+
48
+ export interface PrivateBlobProvider {
49
+ id: string;
50
+ name: string;
51
+ isConfigured: () => boolean;
52
+ put: (input: PrivateBlobPutInput) => Promise<PrivateBlobHandle>;
53
+ read: (handle: PrivateBlobHandle) => Promise<PrivateBlobReadResult>;
54
+ delete: (handle: PrivateBlobHandle) => Promise<PrivateBlobDeleteResult>;
55
+ }
@@ -8,29 +8,57 @@ export { parseArgs, camelCaseArgs } from "./parse-args.js";
8
8
 
9
9
  /**
10
10
  * Load .env files. In an enterprise workspace (detected via
11
- * `agent-native.workspaceCore` in a parent package.json) this also loads
12
- * the workspace root's .env first, so shared keys like ANTHROPIC_API_KEY
13
- * flow to every app without duplication. Per-app .env values win on
14
- * conflict (dotenv doesn't overwrite existing process.env values).
11
+ * `agent-native.workspaceCore` in a parent package.json) this also loads the
12
+ * workspace root's .env as a fallback, so shared keys like ANTHROPIC_API_KEY
13
+ * flow to every app without duplication. Shell values win, then app
14
+ * .env.local, app .env, workspace .env.local, and workspace .env.
15
15
  */
16
16
  export function loadEnv(envPath?: string): void {
17
17
  const appEnv = envPath ?? path.join(process.cwd(), ".env");
18
+ const shellKeys = new Set(Object.keys(process.env));
18
19
  // App-level .env first. Dotenv won't clobber already-set process.env, so
19
20
  // values that are already present (e.g. set by the shell) still win.
20
21
  // `quiet: true` suppresses the dotenv tip line on every load (v17+).
21
- dotenv.config({ path: appEnv, quiet: true });
22
+ loadEnvFile(appEnv);
23
+ loadEnvLocalOverrides(localEnvPathFor(appEnv), shellKeys);
22
24
 
23
25
  // Then workspace root, if any — but only fill in keys the app didn't
24
26
  // define. Setting `override: false` is dotenv's default.
25
27
  const workspaceRoot = findWorkspaceRoot(path.dirname(appEnv));
26
28
  if (workspaceRoot) {
29
+ const beforeWorkspaceKeys = new Set(Object.keys(process.env));
27
30
  const wsEnv = path.join(workspaceRoot, ".env");
28
31
  if (fs.existsSync(wsEnv) && wsEnv !== appEnv) {
29
- dotenv.config({ path: wsEnv, quiet: true });
32
+ loadEnvFile(wsEnv);
33
+ loadEnvLocalOverrides(localEnvPathFor(wsEnv), beforeWorkspaceKeys);
30
34
  }
31
35
  }
32
36
  }
33
37
 
38
+ function loadEnvFile(filePath: string): void {
39
+ if (fs.existsSync(filePath)) {
40
+ dotenv.config({ path: filePath, quiet: true });
41
+ }
42
+ }
43
+
44
+ function localEnvPathFor(envPath: string): string {
45
+ return path.basename(envPath) === ".env"
46
+ ? path.join(path.dirname(envPath), ".env.local")
47
+ : `${envPath}.local`;
48
+ }
49
+
50
+ function loadEnvLocalOverrides(
51
+ filePath: string,
52
+ protectedKeys: Set<string>,
53
+ ): void {
54
+ if (!fs.existsSync(filePath)) return;
55
+ const values = dotenv.parse(fs.readFileSync(filePath));
56
+ for (const [key, value] of Object.entries(values)) {
57
+ if (protectedKeys.has(key)) continue;
58
+ process.env[key] = value;
59
+ }
60
+ }
61
+
34
62
  /**
35
63
  * Locate the nearest enterprise workspace root above `startDir`, identified
36
64
  * by the `agent-native.workspaceCore` field in its package.json.
@@ -6660,6 +6660,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6660
6660
  refPath?: string;
6661
6661
  refId?: string;
6662
6662
  section?: string;
6663
+ slotKey?: string;
6664
+ slotLabel?: string;
6665
+ metadata?: Record<string, unknown>;
6666
+ clearsSlots?: string[];
6667
+ relatedReferences?: unknown[];
6663
6668
  }
6664
6669
 
6665
6670
  const matchesQuery = (item: MentionItemResponse) =>
@@ -6796,6 +6801,11 @@ Non-code requests are still fine on this surface: read data, navigate the UI, su
6796
6801
  refPath: item.refPath,
6797
6802
  refId: item.refId,
6798
6803
  section: provider.label,
6804
+ slotKey: item.slotKey,
6805
+ slotLabel: item.slotLabel,
6806
+ metadata: item.metadata,
6807
+ clearsSlots: item.clearsSlots,
6808
+ relatedReferences: item.relatedReferences,
6799
6809
  })),
6800
6810
  );
6801
6811
  } catch (e) {
@@ -16,6 +16,8 @@ export interface EmailAttachment {
16
16
  filename: string;
17
17
  content: string | Buffer;
18
18
  contentType?: string;
19
+ contentId?: string;
20
+ disposition?: "attachment" | "inline";
19
21
  }
20
22
 
21
23
  export interface SendEmailArgs {
@@ -76,6 +78,7 @@ export async function sendEmail(args: SendEmailArgs): Promise<void> {
76
78
  ? a.content
77
79
  : a.content.toString("base64"),
78
80
  content_type: a.contentType,
81
+ content_id: a.contentId,
79
82
  }));
80
83
  }
81
84
  const headers: Record<string, string> = {};
@@ -129,6 +132,8 @@ export async function sendEmail(args: SendEmailArgs): Promise<void> {
129
132
  ? Buffer.from(a.content).toString("base64")
130
133
  : a.content.toString("base64"),
131
134
  type: a.contentType,
135
+ disposition: a.disposition ?? (a.contentId ? "inline" : undefined),
136
+ content_id: a.contentId,
132
137
  }));
133
138
  }
134
139
 
@@ -398,6 +398,7 @@ export {
398
398
  sendEmail,
399
399
  isEmailConfigured,
400
400
  getEmailProvider,
401
+ type EmailAttachment,
401
402
  type EmailProvider,
402
403
  type SendEmailArgs,
403
404
  } from "./email.js";
@@ -46,6 +46,7 @@
46
46
  opening animates in as reliably as closing animates out. */
47
47
  .agent-sidebar-panel[data-agent-sidebar-animation="desktop"] {
48
48
  width: 0;
49
+ overflow: clip;
49
50
  will-change: width;
50
51
  transition: width 260ms cubic-bezier(0.32, 0.72, 0, 1);
51
52
  }
@@ -54,6 +55,22 @@
54
55
  width: var(--agent-sidebar-width);
55
56
  }
56
57
 
58
+ .agent-sidebar-panel[data-agent-sidebar-animation="desktop"]
59
+ > .agent-sidebar-panel-inner {
60
+ width: var(--agent-sidebar-width);
61
+ min-width: var(--agent-sidebar-width);
62
+ max-width: var(--agent-sidebar-width);
63
+ height: 100%;
64
+ transform: var(--agent-sidebar-inner-closed-transform, translateX(100%));
65
+ will-change: transform;
66
+ transition: transform 260ms cubic-bezier(0.32, 0.72, 0, 1);
67
+ }
68
+
69
+ .agent-sidebar-panel[data-agent-sidebar-animation="desktop"][data-agent-sidebar-state="open"]
70
+ > .agent-sidebar-panel-inner {
71
+ transform: translateX(0);
72
+ }
73
+
57
74
  .agent-sidebar-panel[data-agent-sidebar-animation="mobile"] {
58
75
  transform: var(
59
76
  --agent-sidebar-closed-transform,
@@ -81,6 +98,11 @@
81
98
  width: 0;
82
99
  }
83
100
 
101
+ .agent-sidebar-panel[data-agent-sidebar-animation="desktop"][data-agent-sidebar-state="open"]
102
+ > .agent-sidebar-panel-inner {
103
+ transform: var(--agent-sidebar-inner-closed-transform, translateX(100%));
104
+ }
105
+
84
106
  .agent-sidebar-panel[data-agent-sidebar-animation="mobile"][data-agent-sidebar-state="open"] {
85
107
  transform: var(
86
108
  --agent-sidebar-closed-transform,
@@ -95,6 +117,8 @@
95
117
 
96
118
  @media (prefers-reduced-motion: reduce) {
97
119
  .agent-sidebar-panel[data-agent-sidebar-animation],
120
+ .agent-sidebar-panel[data-agent-sidebar-animation="desktop"]
121
+ > .agent-sidebar-panel-inner,
98
122
  .agent-sidebar-backdrop[data-agent-sidebar-animation] {
99
123
  transition: none;
100
124
  }
@@ -257,7 +257,7 @@ For a **first-party analytics** dashboard, prefer `compose-dashboard` over hand-
257
257
  - Each metric accepts an optional per-metric `window` of `'30d' | '90d' | 'all'` (only affects windowed virality/time metrics) and `title` / `chartType` / `width` overrides.
258
258
  - Returns `{ dashboardId, panelCount, createdMetrics, unknownMetrics, invalidMetrics, skippedExistingIds }` — report `panelCount` as proof-of-done.
259
259
 
260
- Available metric keys: `total-signups`, `signups-over-time`, `signups-by-template`, `sessions-by-app`, `sessions-over-time`, `signed-in-vs-anon`, `total-template-clicks`, `total-demo-clicks`, `total-cli-copies`, `template-interest-over-time`, `clicks-by-template`, `demo-clicks-by-template`, `cli-copies-by-template`, `cli-copies-over-time`, `pageviews-over-time`, `top-referrer-domains`, `referred-signups-30d`, `viral-signup-share-30d`, `clip-share-signups-30d`, `signups-by-referral-source`, `referred-signups-over-time`, `top-referrers`, `share-funnel-30d`, `viral-participation-rate-90d`, `viral-coefficient-90d`, `activated-referrers-90d`.
260
+ Available metric keys: `total-signups`, `signups-over-time`, `signups-by-template`, `sessions-by-app`, `sessions-over-time`, `replay-sessions`, `replay-chunks-over-time`, `recent-replay-sessions`, `signed-in-vs-anon`, `total-template-clicks`, `total-demo-clicks`, `total-cli-copies`, `template-interest-over-time`, `clicks-by-template`, `demo-clicks-by-template`, `cli-copies-by-template`, `cli-copies-over-time`, `pageviews-over-time`, `top-referrer-domains`, `referred-signups-30d`, `viral-signup-share-30d`, `clip-share-signups-30d`, `signups-by-referral-source`, `referred-signups-over-time`, `top-referrers`, `share-funnel-30d`, `viral-participation-rate-90d`, `viral-coefficient-90d`, `activated-referrers-90d`.
261
261
 
262
262
  ```bash
263
263
  # Build a large first-party dashboard in ONE call (server generates the panels)
@@ -0,0 +1,43 @@
1
+ ---
2
+ name: session-replay
3
+ description: Inspect, troubleshoot, and extend Analytics session replay recordings.
4
+ scope: dev
5
+ ---
6
+
7
+ # Session Replay
8
+
9
+ Use this skill when working on `/sessions`, replay ingest, replay storage, or
10
+ agent answers about browser recordings in the Analytics template.
11
+
12
+ ## Source Of Truth
13
+
14
+ - Replay ingest writes `session_recordings` and `session_replay_chunks`.
15
+ - The UI and agent must use `list-session-recordings`,
16
+ `get-session-replay-summary`, and `get-session-replay-events`.
17
+ - `/sessions/:recordingId` is keyed by `session_recordings.id`, not
18
+ `analytics_events.session_id`.
19
+ - Do not add actions that synthesize "sessions" from `analytics_events`.
20
+ Events can be linked beside a recording through `session_id`, but they are not
21
+ playable replay rows by themselves.
22
+
23
+ ## Storage And Access
24
+
25
+ - Never expose object-storage URLs or raw `session_replay_chunks` rows to the
26
+ browser or agent.
27
+ - Playback bytes must go through scoped server helpers that check
28
+ `session-recording` access before reading private blob refs.
29
+ - SQL inline chunks are a local/dev fallback only; production should use
30
+ private or encrypted blob storage.
31
+
32
+ ## Capture Defaults
33
+
34
+ - Replay is off unless enabled by config/env.
35
+ - Consumers can enable replay directly with
36
+ `configureTracking({ key, endpoint, sessionReplay: { enabled: true } })`.
37
+ - Agent Native templates already call `configureTracking()` in their roots;
38
+ hosted template deployments can usually enable replay with Vite/Netlify env
39
+ vars on the recorded site.
40
+ - Inputs are masked by default. Page text is visible unless marked with
41
+ `.an-mask` or `data-an-mask`.
42
+ - Use `.an-block`, `.an-ignore`, `data-an-block`, or `data-an-ignore` for
43
+ sensitive zones that should not be captured.