@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
@@ -1,7 +1,7 @@
1
1
  import { createAuthPlugin } from "@agent-native/core/server";
2
2
 
3
3
  export default createAuthPlugin({
4
- publicPaths: ["/track", "/api/analytics/track"],
4
+ publicPaths: ["/track", "/api/analytics/track", "/api/analytics/replay"],
5
5
  marketing: {
6
6
  appName: "Agent-Native Analytics",
7
7
  tagline:
@@ -0,0 +1,47 @@
1
+ import { runDashboardReportsOnce } from "../jobs/dashboard-report";
2
+
3
+ const INTERVAL_MS = 60_000;
4
+ let skippingLogged = false;
5
+
6
+ declare global {
7
+ var __AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__: boolean | undefined;
8
+ }
9
+
10
+ function platformSchedulerOwnsReports(): boolean {
11
+ return (
12
+ process.env.NETLIFY === "true" ||
13
+ globalThis.__AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__ === true
14
+ );
15
+ }
16
+
17
+ export default function registerDashboardReportJobs(): void {
18
+ const isProd = process.env.NODE_ENV === "production";
19
+ const flag =
20
+ process.env.ANALYTICS_DASHBOARD_REPORT_JOBS ??
21
+ process.env.RUN_BACKGROUND_JOBS;
22
+ const enabled =
23
+ !platformSchedulerOwnsReports() &&
24
+ (flag === "1" || (isProd && flag !== "0"));
25
+
26
+ if (!enabled) {
27
+ if (!skippingLogged) {
28
+ console.log(
29
+ platformSchedulerOwnsReports()
30
+ ? "[dashboard-report] Skipping in-process cron because the platform scheduler owns dashboard reports."
31
+ : "[dashboard-report] Skipping background cron (set ANALYTICS_DASHBOARD_REPORT_JOBS=1 or RUN_BACKGROUND_JOBS=1 to enable in dev; on by default in production)",
32
+ );
33
+ skippingLogged = true;
34
+ }
35
+ return;
36
+ }
37
+
38
+ setInterval(() => {
39
+ runDashboardReportsOnce().catch((err) =>
40
+ console.error("[dashboard-report] interval failed:", err),
41
+ );
42
+ }, INTERVAL_MS);
43
+
44
+ console.log(
45
+ `[dashboard-report] Recurring dashboard report sweep every ${INTERVAL_MS / 1000}s.`,
46
+ );
47
+ }
@@ -114,6 +114,9 @@ export default runMigrations(
114
114
  created_at TEXT NOT NULL DEFAULT (datetime('now')),
115
115
  last_used_at TEXT,
116
116
  revoked_at TEXT,
117
+ replay_allowed_origins TEXT NOT NULL DEFAULT '[]',
118
+ replay_max_bytes_per_day INTEGER NOT NULL DEFAULT 104857600,
119
+ replay_max_requests_per_minute INTEGER NOT NULL DEFAULT 120,
117
120
  owner_email TEXT NOT NULL DEFAULT 'local@localhost',
118
121
  org_id TEXT
119
122
  )`,
@@ -292,6 +295,203 @@ export default runMigrations(
292
295
  sqlite: `UPDATE analytics_events SET event_date = COALESCE(NULLIF(event_date, ''), substr(timestamp, 1, 10)), user_key = COALESCE(NULLIF(user_key, ''), NULLIF(user_id, ''), NULLIF(anonymous_id, '')) WHERE (event_date IS NULL OR event_date = '' OR user_key IS NULL OR user_key = '') AND timestamp >= date('now', '-400 days')`,
293
296
  },
294
297
  },
298
+ {
299
+ version: 50,
300
+ sql: {
301
+ postgres: `CREATE TABLE IF NOT EXISTS dashboard_report_subscriptions (
302
+ id TEXT PRIMARY KEY,
303
+ dashboard_id TEXT NOT NULL,
304
+ name TEXT NOT NULL,
305
+ recipients TEXT NOT NULL DEFAULT '[]',
306
+ filters TEXT NOT NULL DEFAULT '{}',
307
+ frequency TEXT NOT NULL DEFAULT 'daily',
308
+ time_of_day TEXT NOT NULL DEFAULT '09:00',
309
+ timezone TEXT NOT NULL DEFAULT 'UTC',
310
+ enabled BOOLEAN NOT NULL DEFAULT true,
311
+ next_run_at TEXT,
312
+ last_run_at TEXT,
313
+ last_status TEXT,
314
+ last_error TEXT,
315
+ created_at TEXT NOT NULL DEFAULT (now()::text),
316
+ updated_at TEXT NOT NULL DEFAULT (now()::text),
317
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
318
+ org_id TEXT
319
+ )`,
320
+ sqlite: `CREATE TABLE IF NOT EXISTS dashboard_report_subscriptions (
321
+ id TEXT PRIMARY KEY,
322
+ dashboard_id TEXT NOT NULL,
323
+ name TEXT NOT NULL,
324
+ recipients TEXT NOT NULL DEFAULT '[]',
325
+ filters TEXT NOT NULL DEFAULT '{}',
326
+ frequency TEXT NOT NULL DEFAULT 'daily',
327
+ time_of_day TEXT NOT NULL DEFAULT '09:00',
328
+ timezone TEXT NOT NULL DEFAULT 'UTC',
329
+ enabled INTEGER NOT NULL DEFAULT 1,
330
+ next_run_at TEXT,
331
+ last_run_at TEXT,
332
+ last_status TEXT,
333
+ last_error TEXT,
334
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
335
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
336
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
337
+ org_id TEXT
338
+ )`,
339
+ },
340
+ },
341
+ {
342
+ version: 51,
343
+ sql: `CREATE INDEX IF NOT EXISTS dashboard_report_subscriptions_due_idx ON dashboard_report_subscriptions (enabled, next_run_at)`,
344
+ },
345
+ {
346
+ version: 52,
347
+ sql: `CREATE INDEX IF NOT EXISTS dashboard_report_subscriptions_owner_dashboard_idx ON dashboard_report_subscriptions (owner_email, org_id, dashboard_id)`,
348
+ },
349
+ {
350
+ version: 53,
351
+ sql: `CREATE TABLE IF NOT EXISTS session_recordings (
352
+ id TEXT PRIMARY KEY,
353
+ public_key_id TEXT NOT NULL,
354
+ client_recording_id TEXT NOT NULL,
355
+ session_id TEXT NOT NULL,
356
+ user_id TEXT,
357
+ anonymous_id TEXT,
358
+ user_key TEXT,
359
+ started_at TEXT NOT NULL,
360
+ ended_at TEXT,
361
+ duration_ms INTEGER,
362
+ chunk_count INTEGER NOT NULL DEFAULT 0,
363
+ event_count INTEGER NOT NULL DEFAULT 0,
364
+ total_bytes INTEGER NOT NULL DEFAULT 0,
365
+ page_count INTEGER NOT NULL DEFAULT 0,
366
+ error_count INTEGER NOT NULL DEFAULT 0,
367
+ rage_click_count INTEGER NOT NULL DEFAULT 0,
368
+ privacy_mode TEXT NOT NULL DEFAULT 'unknown',
369
+ first_url TEXT,
370
+ last_url TEXT,
371
+ path TEXT,
372
+ hostname TEXT,
373
+ referrer TEXT,
374
+ app TEXT,
375
+ template TEXT,
376
+ status TEXT NOT NULL DEFAULT 'active',
377
+ metadata TEXT NOT NULL DEFAULT '{}',
378
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
379
+ updated_at TEXT NOT NULL DEFAULT (datetime('now')),
380
+ last_ingested_at TEXT,
381
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
382
+ org_id TEXT,
383
+ visibility TEXT NOT NULL DEFAULT 'private'
384
+ )`,
385
+ },
386
+ {
387
+ version: 54,
388
+ sql: `CREATE TABLE IF NOT EXISTS session_replay_chunks (
389
+ id TEXT PRIMARY KEY,
390
+ recording_id TEXT NOT NULL,
391
+ seq INTEGER NOT NULL,
392
+ checksum TEXT NOT NULL,
393
+ byte_length INTEGER NOT NULL DEFAULT 0,
394
+ event_count INTEGER NOT NULL DEFAULT 0,
395
+ started_at TEXT,
396
+ ended_at TEXT,
397
+ storage_kind TEXT NOT NULL,
398
+ storage_ref TEXT,
399
+ inline_data TEXT,
400
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
401
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
402
+ org_id TEXT
403
+ )`,
404
+ },
405
+ {
406
+ version: 55,
407
+ sql: `CREATE TABLE IF NOT EXISTS session_recording_shares (
408
+ id TEXT PRIMARY KEY,
409
+ resource_id TEXT NOT NULL,
410
+ principal_type TEXT NOT NULL,
411
+ principal_id TEXT NOT NULL,
412
+ role TEXT NOT NULL DEFAULT 'viewer',
413
+ created_by TEXT NOT NULL,
414
+ created_at TEXT NOT NULL DEFAULT (datetime('now'))
415
+ )`,
416
+ },
417
+ {
418
+ version: 56,
419
+ sql: `CREATE UNIQUE INDEX IF NOT EXISTS session_recordings_key_client_idx ON session_recordings (public_key_id, client_recording_id)`,
420
+ },
421
+ {
422
+ version: 57,
423
+ sql: `CREATE UNIQUE INDEX IF NOT EXISTS session_replay_chunks_recording_seq_idx ON session_replay_chunks (recording_id, seq)`,
424
+ },
425
+ {
426
+ version: 58,
427
+ sql: `CREATE INDEX IF NOT EXISTS session_recordings_scope_started_idx ON session_recordings (org_id, owner_email, started_at)`,
428
+ },
429
+ {
430
+ version: 59,
431
+ sql: `CREATE INDEX IF NOT EXISTS session_recordings_session_idx ON session_recordings (session_id)`,
432
+ },
433
+ {
434
+ version: 60,
435
+ sql: `CREATE INDEX IF NOT EXISTS session_recording_shares_resource_idx ON session_recording_shares (resource_id)`,
436
+ },
437
+ {
438
+ version: 61,
439
+ sql: `ALTER TABLE session_recordings ADD COLUMN IF NOT EXISTS page_count INTEGER NOT NULL DEFAULT 0`,
440
+ },
441
+ {
442
+ version: 62,
443
+ sql: `ALTER TABLE session_recordings ADD COLUMN IF NOT EXISTS error_count INTEGER NOT NULL DEFAULT 0`,
444
+ },
445
+ {
446
+ version: 63,
447
+ sql: `ALTER TABLE session_recordings ADD COLUMN IF NOT EXISTS rage_click_count INTEGER NOT NULL DEFAULT 0`,
448
+ },
449
+ {
450
+ version: 64,
451
+ sql: `ALTER TABLE session_recordings ADD COLUMN IF NOT EXISTS privacy_mode TEXT NOT NULL DEFAULT 'unknown'`,
452
+ },
453
+ {
454
+ version: 65,
455
+ sql: `ALTER TABLE analytics_public_keys ADD COLUMN IF NOT EXISTS replay_allowed_origins TEXT NOT NULL DEFAULT '[]'`,
456
+ },
457
+ {
458
+ version: 66,
459
+ sql: `ALTER TABLE analytics_public_keys ADD COLUMN IF NOT EXISTS replay_max_bytes_per_day INTEGER NOT NULL DEFAULT 104857600`,
460
+ },
461
+ {
462
+ version: 67,
463
+ sql: `ALTER TABLE analytics_public_keys ADD COLUMN IF NOT EXISTS replay_max_requests_per_minute INTEGER NOT NULL DEFAULT 120`,
464
+ },
465
+ {
466
+ version: 68,
467
+ sql: {
468
+ postgres: `
469
+ ALTER TABLE dashboard_report_subscriptions ALTER COLUMN enabled DROP DEFAULT;
470
+ ALTER TABLE dashboard_report_subscriptions ALTER COLUMN enabled TYPE boolean USING (enabled::text IN ('true', 't', '1'));
471
+ ALTER TABLE dashboard_report_subscriptions ALTER COLUMN enabled SET DEFAULT true;
472
+ `,
473
+ },
474
+ },
475
+ {
476
+ version: 69,
477
+ sql: `CREATE TABLE IF NOT EXISTS session_replay_ingests (
478
+ id TEXT PRIMARY KEY,
479
+ public_key_id TEXT NOT NULL,
480
+ recording_id TEXT NOT NULL,
481
+ byte_length INTEGER NOT NULL DEFAULT 0,
482
+ created_at TEXT NOT NULL DEFAULT (datetime('now')),
483
+ owner_email TEXT NOT NULL DEFAULT 'local@localhost',
484
+ org_id TEXT
485
+ )`,
486
+ },
487
+ {
488
+ version: 70,
489
+ sql: `CREATE INDEX IF NOT EXISTS session_replay_ingests_public_key_created_at_idx ON session_replay_ingests (public_key_id, created_at)`,
490
+ },
491
+ {
492
+ version: 71,
493
+ sql: `CREATE INDEX IF NOT EXISTS session_replay_ingests_recording_idx ON session_replay_ingests (recording_id)`,
494
+ },
295
495
  ],
296
496
  { table: "analytics_migrations" },
297
497
  );
@@ -0,0 +1,41 @@
1
+ import { runSessionReplayRetentionOnce } from "../jobs/session-replay-retention";
2
+
3
+ const ONE_DAY_MS = 24 * 60 * 60 * 1000;
4
+ let skippingLogged = false;
5
+
6
+ export default function registerSessionReplayRetentionJobs(): void {
7
+ const isProd = process.env.NODE_ENV === "production";
8
+ const flag =
9
+ process.env.ANALYTICS_SESSION_REPLAY_RETENTION_JOBS ??
10
+ process.env.RUN_BACKGROUND_JOBS;
11
+ const enabled = flag === "1" || (isProd && flag !== "0");
12
+
13
+ if (!enabled) {
14
+ if (!skippingLogged) {
15
+ console.log(
16
+ "[session-replay] Skipping retention job (set ANALYTICS_SESSION_REPLAY_RETENTION_JOBS=1 or RUN_BACKGROUND_JOBS=1 to enable in dev; on by default in production)",
17
+ );
18
+ skippingLogged = true;
19
+ }
20
+ return;
21
+ }
22
+
23
+ setInterval(() => {
24
+ runSessionReplayRetentionOnce()
25
+ .then((result) => {
26
+ if (
27
+ result.finalized ||
28
+ result.expired ||
29
+ result.chunks ||
30
+ result.blobDeleteFailures
31
+ ) {
32
+ console.log("[session-replay] Retention sweep completed", result);
33
+ }
34
+ })
35
+ .catch((err) =>
36
+ console.error("[session-replay] retention interval failed:", err),
37
+ );
38
+ }, ONE_DAY_MS);
39
+
40
+ console.log("[session-replay] Retention sweep scheduled daily.");
41
+ }
@@ -0,0 +1 @@
1
+ export { handleSessionReplayOptions as default } from "../../../handlers/session-replay";
@@ -0,0 +1 @@
1
+ export { handleSessionReplayIngest as default } from "../../../handlers/session-replay";
@@ -0,0 +1,61 @@
1
+ import { timingSafeEqual } from "node:crypto";
2
+
3
+ import { createError, defineEventHandler, getHeader } from "h3";
4
+
5
+ import { runDashboardReportsOnce } from "../../../jobs/dashboard-report";
6
+
7
+ declare global {
8
+ var __AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__: boolean | undefined;
9
+ }
10
+
11
+ function productionLike(): boolean {
12
+ return (
13
+ process.env.NODE_ENV === "production" || process.env.NETLIFY === "true"
14
+ );
15
+ }
16
+
17
+ function scheduledFunctionRuntime(): boolean {
18
+ return (
19
+ globalThis.__AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__ === true
20
+ );
21
+ }
22
+
23
+ function cronSecret(): string | null {
24
+ const secret = process.env.DASHBOARD_REPORTS_CRON_SECRET?.trim();
25
+ return secret ? secret : null;
26
+ }
27
+
28
+ function headerMatchesSecret(
29
+ header: string | undefined,
30
+ secret: string,
31
+ ): boolean {
32
+ const expected = `Bearer ${secret}`;
33
+ const value = header?.trim() ?? "";
34
+ if (value.length !== expected.length) return false;
35
+ return timingSafeEqual(Buffer.from(value), Buffer.from(expected));
36
+ }
37
+
38
+ export default defineEventHandler(async (event) => {
39
+ const secret = cronSecret();
40
+ const scheduledRuntime = scheduledFunctionRuntime();
41
+ if (!secret && productionLike() && !scheduledRuntime) {
42
+ throw createError({
43
+ statusCode: 503,
44
+ statusMessage: "DASHBOARD_REPORTS_CRON_SECRET is required",
45
+ });
46
+ }
47
+
48
+ if (
49
+ secret &&
50
+ !scheduledRuntime &&
51
+ !headerMatchesSecret(getHeader(event, "authorization"), secret)
52
+ ) {
53
+ throw createError({
54
+ statusCode: 401,
55
+ statusMessage: "Unauthorized",
56
+ });
57
+ }
58
+
59
+ const result = await runDashboardReportsOnce();
60
+ return { ok: true, ...result };
61
+ });
@@ -0,0 +1 @@
1
+ export { handleSessionReplayChunkBytes as default } from "../../../../../../handlers/session-replay";
@@ -0,0 +1 @@
1
+ export { handleSessionReplayEvents as default } from "../../../../../handlers/session-replay";
@@ -0,0 +1 @@
1
+ export { handleSessionReplayManifest as default } from "../../../../../handlers/session-replay";
@@ -0,0 +1 @@
1
+ export { handleSessionReplaySummary as default } from "../../../../handlers/session-replay";
@@ -0,0 +1 @@
1
+ export { handleSessionReplayList as default } from "../../../handlers/session-replay";
@@ -21,12 +21,19 @@ or generated image/video assets that another app can reference by ID and URL.
21
21
  when `libraryId` is omitted, generates candidates, returns the picker filtered
22
22
  to those run IDs, and works in in-app chat plus external MCP hosts.
23
23
  - Use `open-asset-picker` when a person should browse, search, or select an
24
- existing asset in UI, or when you want the picker to handle generation itself.
24
+ existing asset inside an embedded picker, or when you want the picker to
25
+ handle generation itself. It still opens `/library` with the iframe/bridge
26
+ contract. The normal human Library workspace is `/library` and `/library/:id`.
25
27
  Pass `mediaType: "image"` by default, or `mediaType: "video"` for video
26
28
  libraries.
27
29
  - Use unattended actions when the agent already knows what to do:
28
30
  `search-assets`, `list-assets`, `generate-image`, `generate-image-batch`,
29
31
  `generate-video`, `refresh-generation-run`, and `export-asset`.
32
+ - In chat, consume composer `@` references as structured generation inputs:
33
+ `brand-kit` maps to `libraryId`, `preset` maps to `presetId`, and
34
+ `media-type` chooses image generation versus video generation. If no mention
35
+ is available, use `view-screen`, `list-libraries`, and
36
+ `list-generation-presets` to choose explicit args.
30
37
  - Use generation presets when the user asks for a repeatable output format
31
38
  like social image, blog hero, or diagram. Call `list-generation-presets` for
32
39
  the library and pass `presetId` through generation/refinement actions.
@@ -58,7 +65,8 @@ or generated image/video assets that another app can reference by ID and URL.
58
65
  `generate-image-batch` returns, use its returned `images` / asset fields
59
66
  directly; do not call `get-generation-run`, `refresh-generation-run`, or
60
67
  regenerate just to verify image runs.
61
- 4. For preset-backed work, pass `presetId`; for handoff work, pass `sessionId`.
68
+ 4. For preset-backed work, pass a mentioned or selected `presetId`; for handoff
69
+ work, pass `sessionId`.
62
70
  5. Let the server choose a small deterministic reference set unless the user
63
71
  named exact assets. Canonical style anchors come from
64
72
  `assetLibraries.settings.canonicalStyleAssetIds` and
@@ -10,8 +10,13 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
10
10
 
11
11
  ## Rules
12
12
 
13
- - Start from the current library. Call `view-screen` when the user says "this
14
- library" or "this image" and you need fresh IDs.
13
+ - Start from composer `@` mentions when the user tags generation inputs.
14
+ `brand-kit` references map to `libraryId`, `preset` references map to
15
+ `presetId`, and `media-type` references choose image generation versus video
16
+ generation. Call `view-screen` when the user says "this library" or "this
17
+ image" and you need fresh IDs. The image model may default from the composer
18
+ image-model picker; all other generation settings should be explicit action
19
+ args, preset values, or action schema defaults.
15
20
  - Use category-tagged references. Blog heroes should prefer `hero`; diagrams
16
21
  should prefer `diagram`; product imagery should include `product` and `logo`
17
22
  references.
@@ -20,16 +25,18 @@ Use this skill before calling `generate-image`, `generate-image-batch`, or
20
25
  `assets.metadata.isStyleAnchor` before sampling other relevant references.
21
26
  - Honor library custom instructions. They are persistent prompt guidance and
22
27
  should be updated when the user wants durable generation behavior.
23
- - Generate 2-4 candidates for open-ended requests. Use `generate-image-batch`
24
- with stable `slotId`s so the UI can show live slots.
28
+ - Generate the selected candidate count for open-ended requests, usually 2-4.
29
+ Use `generate-image-batch` with stable `slotId`s so the shared generation tray
30
+ can show live slots.
25
31
  - `generate-image` and `generate-image-batch` are synchronous for images. One
26
32
  batch call should produce the requested candidates and return their asset
27
33
  IDs/URLs; do not follow it with `get-generation-run`,
28
34
  `refresh-generation-run`, or more generation unless the user asks for another
29
35
  direction or the returned slot has `ok: false`.
30
- - For repeatable deliverables, call `list-generation-presets` and pass the
31
- selected `presetId` to `generate-image`, `generate-image-batch`,
32
- `refine-image`, or `rerun-generation-run`.
36
+ - For repeatable deliverables, honor a `preset` @mention as `presetId` or call
37
+ `list-generation-presets` when choosing one. Pass the preset through
38
+ `generate-image`, `generate-image-batch`, `refine-image`, or
39
+ `rerun-generation-run`.
33
40
  - For designer handoff, preserve `sessionId` and call
34
41
  `update-generation-session` after each new candidate so the active asset,
35
42
  feedback, and run lineage stay resumable.
@@ -7,6 +7,14 @@ description: Schema, CRUD, sharing, and cascade-delete patterns for asset librar
7
7
 
8
8
  Use this skill before adding fields, changing access checks, or modifying delete behavior.
9
9
 
10
+ ## User surface
11
+
12
+ The human Library workspace is canonical at `/library`. The root selects
13
+ "All assets" and browses assets across every accessible brand kit; `/library/:id`
14
+ opens one kit's management detail. Legacy `/brand-kits` URLs redirect here.
15
+ Embedded picker hosts still load `/library` in an iframe and keep the existing
16
+ bridge contract.
17
+
10
18
  ## Schema overview
11
19
 
12
20
  ```
@@ -24,6 +32,8 @@ image_libraries — top-level library, has ownableColumns + shares
24
32
  Every action that touches an ownable resource must scope its queries:
25
33
 
26
34
  - **List queries**: `accessFilter(schema.assetLibraries, schema.assetLibraryShares)` in WHERE.
35
+ Cross-kit asset lists must first resolve the accessible library IDs, then
36
+ query `image_assets` by those IDs and include the parent kit title for UI chips.
27
37
  - **Read by id**: `await resolveAccess("asset-library", libraryId)`. The `requireLibrary(id)` helper in `_helpers.ts` wraps this.
28
38
  - **Write**: `await assertAccess("asset-library", libraryId, "editor")` for updates / inserts; `"admin"` for deletes.
29
39
 
@@ -52,6 +62,7 @@ Example: adding `image_libraries.icon`:
52
62
  ## Sharing
53
63
 
54
64
  Libraries follow the standard framework sharing model:
65
+
55
66
  - `visibility: "private" | "org" | "public"`
56
67
  - Per-user / per-org grants in `image_library_shares` with `viewer | editor | admin` roles.
57
68
  - Use the framework actions `share-resource`, `unshare-resource`, `set-resource-visibility` with `--resourceType=asset-library`. The legacy `image-library` alias remains registered for existing grants.
@@ -61,6 +72,7 @@ Generated assets and references inherit the parent library's visibility. v1 does
61
72
  ## Cascade delete
62
73
 
63
74
  `delete-library` deletes in order:
75
+
64
76
  1. `image_assets WHERE library_id = ?`
65
77
  2. `image_generation_runs WHERE library_id = ?`
66
78
  3. `image_collections WHERE library_id = ?`
@@ -21,7 +21,9 @@ Detailed library, generation, image, embed, and engine rules live in
21
21
  ad hoc provider calls when the app has an action/engine abstraction.
22
22
  - Preserve provenance and metadata for generated or imported assets.
23
23
  - Use `view-screen` when the active library, selected asset, picker, generation,
24
- or embed target is unclear. The picker is also available from the left nav.
24
+ or embed target is unclear. The human Library surface is `/library` for
25
+ cross-kit browsing and `/library/:libraryId` for a single brand kit; embedded
26
+ picker hosts still use `/library` with their iframe/auth bridge params.
25
27
  - The Create tab (`/`) is the full-page Assets chat surface. Use the shared
26
28
  `assets` chat thread storage there, keep past chats in the left sidebar, and
27
29
  use the right agent sidebar only on non-Create routes with view-transition
@@ -33,8 +35,21 @@ Detailed library, generation, image, embed, and engine rules live in
33
35
  ## Application State
34
36
 
35
37
  - `navigation` exposes library, asset, generation, picker, embed, and selection
36
- context. Picker state includes media type, selected library, query, prompt, and
37
- aspect ratio when available.
38
+ context. Human Library state uses
39
+ `{ view: "library", selection: "all" | libraryId, tab, scope, folderId, search }`.
40
+ Embedded picker state keeps `{ view: "picker", mediaType, libraryId, query,
41
+ prompt, aspectRatio }`.
42
+ - Composer `@` mentions are the source of generation inputs. Map
43
+ `brand-kit` references to `libraryId`, `preset` references to `presetId`, and
44
+ `media-type` references to choosing image (`generate-image` /
45
+ `generate-image-batch`) or video (`generate-video`) generation. The current
46
+ library view auto-tags its brand kit as a visible removable chip when the
47
+ composer is empty. The image model is the only remaining composer-side
48
+ default; the image-model picker writes `imageGenerationModel`, which image
49
+ generation actions may use when `model` is omitted.
50
+ - `asset-variants` is the shared live generation tray state. New image
51
+ candidates should appear there through `generate-image` or
52
+ `generate-image-batch`; do not invent page-local progress surfaces.
38
53
  - `navigate` moves the UI to picker, library, generation, asset, and settings
39
54
  surfaces.
40
55
 
@@ -0,0 +1,24 @@
1
+ import { readAppState } from "@agent-native/core/application-state";
2
+
3
+ import { IMAGE_MODELS, type ImageModel } from "../shared/api.js";
4
+
5
+ export const IMAGE_MODEL_STATE_KEY = "imageGenerationModel";
6
+
7
+ function imageModelValue(value: unknown): ImageModel | undefined {
8
+ return typeof value === "string" &&
9
+ (IMAGE_MODELS as readonly string[]).includes(value)
10
+ ? (value as ImageModel)
11
+ : undefined;
12
+ }
13
+
14
+ function recordValue(value: unknown): Record<string, unknown> {
15
+ return value && typeof value === "object" && !Array.isArray(value)
16
+ ? (value as Record<string, unknown>)
17
+ : {};
18
+ }
19
+
20
+ export async function readImageModelDefault(): Promise<ImageModel | undefined> {
21
+ const raw = await readAppState(IMAGE_MODEL_STATE_KEY).catch(() => null);
22
+ const state = recordValue(raw);
23
+ return imageModelValue(state.model) ?? imageModelValue(raw);
24
+ }
@@ -1,38 +1,36 @@
1
1
  import { defineAction } from "@agent-native/core";
2
- import {
3
- deleteAppState,
4
- readAppState,
5
- writeAppState,
6
- } from "@agent-native/core/application-state";
2
+ import type { ActionRunContext } from "@agent-native/core/action";
7
3
  import { assertAccess } from "@agent-native/core/sharing";
8
4
  import { eq } from "drizzle-orm";
9
5
  import { z } from "zod";
10
6
 
11
7
  import { getDb, schema } from "../server/db/index.js";
12
- import type { AssetVariantState } from "../shared/api.js";
8
+ import {
9
+ deleteVariantState,
10
+ readVariantState,
11
+ writeVariantState,
12
+ } from "./variant-slots.js";
13
13
 
14
14
  export default defineAction({
15
15
  description:
16
- "Clear one or more live candidate slots from application_state.asset-variants. Use slotId for a single slot, scope='failed' to drop every failed slot, or scope='all' to clear the panel. Any underlying asset rows for cleared slots are deleted (requires editor access on the library).",
16
+ "Clear one or more live candidate slots from application_state.asset-variants. Uses the current chat thread when available. Use slotId for a single slot, scope='failed' to drop every failed slot, or scope='all' to clear the panel. Any underlying asset rows for cleared slots are deleted (requires editor access on the library).",
17
17
  schema: z
18
18
  .object({
19
19
  slotId: z.string().optional(),
20
20
  scope: z.enum(["failed", "all"]).optional(),
21
+ threadId: z.string().nullable().optional(),
21
22
  })
22
23
  .refine((v) => Boolean(v.slotId) !== Boolean(v.scope), {
23
24
  message: "Provide exactly one of `slotId` or `scope`.",
24
25
  }),
25
- run: async ({ slotId, scope }) => {
26
- const raw = (await readAppState("asset-variants")) as unknown | null;
27
- const legacyRaw =
28
- raw ??
29
- ((await readAppState("image-variants").catch(() => null)) as
30
- | unknown
31
- | null);
32
- const state = (legacyRaw ?? null) as AssetVariantState | null;
26
+ run: async ({ slotId, scope, threadId }, context?: ActionRunContext) => {
27
+ const effectiveThreadId = threadId ?? context?.threadId ?? null;
28
+ const state = await readVariantState(effectiveThreadId);
33
29
  if (!state || !Array.isArray(state.slots) || state.slots.length === 0) {
34
30
  return { dismissed: 0, assetsDeleted: 0, cleared: true };
35
31
  }
32
+ const stateThreadId =
33
+ effectiveThreadId ?? state.variantScopeId ?? state.threadId ?? null;
36
34
 
37
35
  await assertAccess("asset-library", state.libraryId, "editor");
38
36
 
@@ -64,8 +62,7 @@ export default defineAction({
64
62
  const remaining = state.slots.filter((s) => !removed.has(s.slotId));
65
63
 
66
64
  if (remaining.length === 0) {
67
- await deleteAppState("asset-variants");
68
- await deleteAppState("image-variants").catch(() => {});
65
+ await deleteVariantState(stateThreadId);
69
66
  return {
70
67
  dismissed: toRemove.length,
71
68
  assetsDeleted,
@@ -75,11 +72,7 @@ export default defineAction({
75
72
 
76
73
  state.slots = remaining;
77
74
  state.updatedAt = new Date().toISOString();
78
- await writeAppState(
79
- "asset-variants",
80
- state as unknown as Record<string, unknown>,
81
- );
82
- await deleteAppState("image-variants").catch(() => {});
75
+ await writeVariantState(state, stateThreadId);
83
76
  return {
84
77
  dismissed: toRemove.length,
85
78
  assetsDeleted,