@agent-native/core 0.122.0 → 0.122.2

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 (187) hide show
  1. package/README.md +5 -25
  2. package/corpus/README.md +1 -1
  3. package/corpus/core/CHANGELOG.md +15 -0
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/a2a/client.ts +57 -4
  6. package/corpus/core/src/agent/production-agent.ts +65 -1
  7. package/corpus/core/src/agent/run-store.ts +61 -1
  8. package/corpus/core/src/client/settings/SettingsPanel.tsx +9 -9
  9. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +12 -2
  10. package/corpus/core/src/extensions/url-safety.ts +67 -4
  11. package/corpus/core/src/server/builder-design-systems.ts +1 -1
  12. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +0 -7
  13. package/corpus/core/src/templates/default/app/root.tsx +1 -1
  14. package/corpus/core/src/templates/default/public/manifest.json +2 -2
  15. package/corpus/core/src/vite/client.ts +64 -3
  16. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +15 -0
  17. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -23
  18. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-panel-window.ts +3 -3
  19. package/corpus/templates/analytics/app/routes/_index.tsx +1 -1
  20. package/corpus/templates/analytics/changelog/2026-07-25-analytics-dashboards-and-scheduled-reports-are-faster-and-mo.md +6 -0
  21. package/corpus/templates/analytics/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  22. package/corpus/templates/analytics/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  23. package/corpus/templates/analytics/package.json +1 -1
  24. package/corpus/templates/analytics/public/manifest.json +1 -1
  25. package/corpus/templates/analytics/server/db/schema.ts +12 -0
  26. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +8 -4
  27. package/corpus/templates/analytics/server/lib/dashboard-report.ts +136 -5
  28. package/corpus/templates/analytics/server/lib/dashboard-time-scope.ts +250 -1
  29. package/corpus/templates/analytics/server/lib/first-party-analytics-cache.ts +167 -0
  30. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +34 -9
  31. package/corpus/templates/analytics/server/lib/first-party-dashboard-repair.ts +105 -23
  32. package/corpus/templates/analytics/server/lib/first-party-unbounded-panel-repair.ts +169 -0
  33. package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
  34. package/corpus/templates/analytics/server/plugins/db.ts +41 -1
  35. package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +6 -3
  36. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +0 -2
  37. package/corpus/templates/assets/app/routes/_index.tsx +1 -1
  38. package/corpus/templates/assets/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  39. package/corpus/templates/assets/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  40. package/corpus/templates/assets/server/plugins/auth.ts +1 -1
  41. package/corpus/templates/brain/app/lib/brain.ts +0 -7
  42. package/corpus/templates/brain/app/routes/_index.tsx +1 -2
  43. package/corpus/templates/brain/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  44. package/corpus/templates/brain/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  45. package/corpus/templates/brain/package.json +1 -1
  46. package/corpus/templates/brain/public/manifest.json +1 -1
  47. package/corpus/templates/brain/server/plugins/auth.ts +1 -1
  48. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +0 -6
  49. package/corpus/templates/calendar/app/routes/_app._index.tsx +1 -1
  50. package/corpus/templates/calendar/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  51. package/corpus/templates/calendar/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  52. package/corpus/templates/calendar/package.json +1 -1
  53. package/corpus/templates/calendar/public/manifest.json +1 -1
  54. package/corpus/templates/calendar/server/plugins/auth.ts +1 -1
  55. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +0 -7
  56. package/corpus/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  57. package/corpus/templates/clips/actions/import-loom-recording.ts +53 -98
  58. package/corpus/templates/clips/actions/lib/loom-import-job.ts +260 -0
  59. package/corpus/templates/clips/app/components/library/library-layout.tsx +0 -7
  60. package/corpus/templates/clips/app/routes/_app.library._index.tsx +1 -1
  61. package/corpus/templates/clips/app/routes/_index.tsx +1 -1
  62. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +6 -0
  63. package/corpus/templates/clips/app/routes/record.tsx +5 -0
  64. package/corpus/templates/clips/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  65. package/corpus/templates/clips/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  66. package/corpus/templates/clips/server/db/schema.ts +2 -0
  67. package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +2 -1
  68. package/corpus/templates/clips/server/plugins/auth.ts +1 -1
  69. package/corpus/templates/clips/server/plugins/db.ts +8 -0
  70. package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +56 -3
  71. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +1 -39
  72. package/corpus/templates/content/app/routes/_app._index.tsx +1 -2
  73. package/corpus/templates/content/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  74. package/corpus/templates/content/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  75. package/corpus/templates/content/package.json +1 -1
  76. package/corpus/templates/content/public/manifest.json +1 -1
  77. package/corpus/templates/content/server/plugins/auth.ts +1 -1
  78. package/corpus/templates/design/actions/edit-design.ts +66 -27
  79. package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
  80. package/corpus/templates/design/app/routes/_index.tsx +1 -1
  81. package/corpus/templates/design/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  82. package/corpus/templates/design/changelog/2026-07-25-design-edits-retry-concurrent-changes.md +6 -0
  83. package/corpus/templates/design/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  84. package/corpus/templates/design/server/plugins/auth.ts +1 -1
  85. package/corpus/templates/design/server/source-workspace.ts +5 -1
  86. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -10
  87. package/corpus/templates/dispatch/app/routes/overview.tsx +1 -1
  88. package/corpus/templates/dispatch/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  89. package/corpus/templates/dispatch/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  90. package/corpus/templates/dispatch/package.json +1 -1
  91. package/corpus/templates/dispatch/public/manifest.json +1 -1
  92. package/corpus/templates/forms/.agents/skills/form-responses/SKILL.md +6 -4
  93. package/corpus/templates/forms/actions/create-form.ts +5 -1
  94. package/corpus/templates/forms/actions/export-responses.ts +22 -11
  95. package/corpus/templates/forms/actions/update-form.ts +5 -1
  96. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +0 -35
  97. package/corpus/templates/forms/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  98. package/corpus/templates/forms/changelog/2026-07-25-fields-can-be-created-without-ids.md +6 -0
  99. package/corpus/templates/forms/changelog/2026-07-25-response-exports-use-file-storage.md +6 -0
  100. package/corpus/templates/forms/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  101. package/corpus/templates/forms/package.json +1 -1
  102. package/corpus/templates/forms/public/manifest.json +1 -1
  103. package/corpus/templates/forms/server/lib/validate-fields.ts +43 -0
  104. package/corpus/templates/forms/server/plugins/auth.ts +1 -1
  105. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +0 -2
  106. package/corpus/templates/macros/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  107. package/corpus/templates/macros/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  108. package/corpus/templates/macros/package.json +1 -1
  109. package/corpus/templates/macros/server/plugins/auth.ts +2 -2
  110. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +0 -39
  111. package/corpus/templates/mail/app/routes/$view.$threadId.tsx +1 -1
  112. package/corpus/templates/mail/app/routes/$view.tsx +1 -1
  113. package/corpus/templates/mail/app/routes/_index.tsx +1 -1
  114. package/corpus/templates/mail/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  115. package/corpus/templates/mail/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  116. package/corpus/templates/mail/package.json +1 -1
  117. package/corpus/templates/mail/public/manifest.json +1 -1
  118. package/corpus/templates/mail/server/plugins/auth.ts +1 -1
  119. package/corpus/templates/plan/agent-native.app-skill.json +1 -1
  120. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +4 -9
  121. package/corpus/templates/plan/app/lib/app-config.ts +1 -1
  122. package/corpus/templates/plan/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  123. package/corpus/templates/plan/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  124. package/corpus/templates/plan/package.json +1 -1
  125. package/corpus/templates/plan/public/manifest.json +1 -1
  126. package/corpus/templates/plan/server/plugins/auth.ts +1 -1
  127. package/corpus/templates/slides/actions/extract-pdf.ts +4 -4
  128. package/corpus/templates/slides/actions/import-file.ts +3 -4
  129. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +0 -2
  130. package/corpus/templates/slides/app/routes/_index.tsx +1 -1
  131. package/corpus/templates/slides/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
  132. package/corpus/templates/slides/changelog/2026-07-25-gemini-image-models-updated.md +6 -0
  133. package/corpus/templates/slides/changelog/2026-07-25-pdf-imports-tolerate-missing-canvas.md +6 -0
  134. package/corpus/templates/slides/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  135. package/corpus/templates/slides/package.json +1 -1
  136. package/corpus/templates/slides/public/manifest.json +1 -1
  137. package/corpus/templates/slides/server/handlers/image-providers/gemini.ts +5 -2
  138. package/corpus/templates/slides/server/lib/pdf-parse-setup.ts +150 -0
  139. package/corpus/templates/slides/server/plugins/auth.ts +1 -1
  140. package/dist/a2a/client.d.ts.map +1 -1
  141. package/dist/a2a/client.js +58 -4
  142. package/dist/a2a/client.js.map +1 -1
  143. package/dist/agent/production-agent.d.ts.map +1 -1
  144. package/dist/agent/production-agent.js +63 -1
  145. package/dist/agent/production-agent.js.map +1 -1
  146. package/dist/agent/run-store.d.ts.map +1 -1
  147. package/dist/agent/run-store.js +49 -0
  148. package/dist/agent/run-store.js.map +1 -1
  149. package/dist/client/settings/SettingsPanel.js +9 -9
  150. package/dist/client/settings/SettingsPanel.js.map +1 -1
  151. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  152. package/dist/client/settings/SettingsTabsPage.js +7 -4
  153. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  154. package/dist/collab/awareness.d.ts +2 -2
  155. package/dist/collab/awareness.d.ts.map +1 -1
  156. package/dist/collab/routes.d.ts +1 -1
  157. package/dist/extensions/url-safety.d.ts +9 -16
  158. package/dist/extensions/url-safety.d.ts.map +1 -1
  159. package/dist/extensions/url-safety.js +48 -4
  160. package/dist/extensions/url-safety.js.map +1 -1
  161. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  162. package/dist/notifications/routes.d.ts +3 -3
  163. package/dist/observability/routes.d.ts +3 -3
  164. package/dist/secrets/routes.d.ts +9 -9
  165. package/dist/server/builder-design-systems.js +1 -1
  166. package/dist/server/builder-design-systems.js.map +1 -1
  167. package/dist/server/transcribe-voice.d.ts +1 -1
  168. package/dist/templates/chat/app/components/layout/Sidebar.tsx +0 -7
  169. package/dist/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  170. package/dist/templates/default/app/root.tsx +1 -1
  171. package/dist/templates/default/public/manifest.json +2 -2
  172. package/dist/vite/client.d.ts.map +1 -1
  173. package/dist/vite/client.js +64 -1
  174. package/dist/vite/client.js.map +1 -1
  175. package/package.json +3 -3
  176. package/src/a2a/client.ts +57 -4
  177. package/src/agent/production-agent.ts +65 -1
  178. package/src/agent/run-store.ts +61 -1
  179. package/src/client/settings/SettingsPanel.tsx +9 -9
  180. package/src/client/settings/SettingsTabsPage.tsx +12 -2
  181. package/src/extensions/url-safety.ts +67 -4
  182. package/src/server/builder-design-systems.ts +1 -1
  183. package/src/templates/chat/app/components/layout/Sidebar.tsx +0 -7
  184. package/src/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
  185. package/src/templates/default/app/root.tsx +1 -1
  186. package/src/templates/default/public/manifest.json +2 -2
  187. package/src/vite/client.ts +64 -3
@@ -2414,6 +2414,10 @@ type NitroModuleGraph = {
2414
2414
 
2415
2415
  const NITRO_STARTUP_SETTLE_MS = 1_000;
2416
2416
  const NITRO_STARTUP_TIMEOUT_MS = 30_000;
2417
+ const NITRO_STARTUP_RETRY_KEY = "__agent_native_nitro_startup_retry";
2418
+ const NITRO_STARTUP_RETRY_MAX = 5;
2419
+ const NITRO_STARTUP_RETRY_DELAY_MS = 1_000;
2420
+ const NITRO_STARTUP_RETRY_RESET_MS = 15_000;
2417
2421
 
2418
2422
  function nitroModuleGraphSignature(environment: unknown): string | null {
2419
2423
  const graph = (environment as { moduleGraph?: NitroModuleGraph } | undefined)
@@ -2456,9 +2460,66 @@ function sendNitroStartingResponse(
2456
2460
  res.end();
2457
2461
  return;
2458
2462
  }
2459
- res.end(
2460
- '<!doctype html><html><head><meta charset="utf-8"><meta http-equiv="refresh" content="0.25"><title>Starting…</title></head><body></body></html>',
2461
- );
2463
+ res.end(`<!doctype html>
2464
+ <html>
2465
+ <head>
2466
+ <meta charset="utf-8">
2467
+ <meta name="viewport" content="width=device-width, initial-scale=1">
2468
+ <title>Dev server restarting…</title>
2469
+ <style>
2470
+ body { margin: 0; min-height: 100vh; display: grid; place-items: center; font: 16px/1.5 system-ui, sans-serif; color: #171717; background: #fafafa; }
2471
+ main { width: min(560px, calc(100vw - 48px)); }
2472
+ h1 { margin: 0 0 8px; font-size: 1.25rem; }
2473
+ p { margin: 0; color: #737373; }
2474
+ </style>
2475
+ </head>
2476
+ <body>
2477
+ <main>
2478
+ <h1>Dev server is restarting…</h1>
2479
+ <p id="agent-native-nitro-retry-status">Checking again shortly.</p>
2480
+ </main>
2481
+ <script>
2482
+ (() => {
2483
+ const key = ${JSON.stringify(NITRO_STARTUP_RETRY_KEY)};
2484
+ const maxRetries = ${NITRO_STARTUP_RETRY_MAX};
2485
+ const resetAfterMs = ${NITRO_STARTUP_RETRY_RESET_MS};
2486
+ const retryDelayMs = ${NITRO_STARTUP_RETRY_DELAY_MS};
2487
+ const status = document.getElementById("agent-native-nitro-retry-status");
2488
+ const now = Date.now();
2489
+ let count = 0;
2490
+ let lastAttemptAt = 0;
2491
+
2492
+ try {
2493
+ const stored = JSON.parse(sessionStorage.getItem(key) || "null");
2494
+ if (stored && typeof stored === "object") {
2495
+ count = Number.isFinite(stored.count) ? stored.count : 0;
2496
+ lastAttemptAt = Number.isFinite(stored.at) ? stored.at : 0;
2497
+ }
2498
+ } catch (error) {
2499
+ // A blocked session store is handled below by showing a manual retry.
2500
+ }
2501
+
2502
+ if (now - lastAttemptAt > resetAfterMs) count = 0;
2503
+ if (count >= maxRetries) {
2504
+ if (status) status.textContent = "The server is still unavailable. Refresh when it is ready.";
2505
+ return;
2506
+ }
2507
+
2508
+ const nextState = JSON.stringify({ count: count + 1, at: now });
2509
+ try {
2510
+ sessionStorage.setItem(key, nextState);
2511
+ if (sessionStorage.getItem(key) !== nextState) throw new Error("unavailable");
2512
+ } catch (error) {
2513
+ if (status) status.textContent = "Refresh manually when the server is ready.";
2514
+ return;
2515
+ }
2516
+
2517
+ if (status) status.textContent = "Retrying in one second…";
2518
+ setTimeout(() => window.location.reload(), retryDelayMs);
2519
+ })();
2520
+ </script>
2521
+ </body>
2522
+ </html>`);
2462
2523
  }
2463
2524
 
2464
2525
  function nitroStartupGate(
@@ -84,6 +84,21 @@ Do not rely on undeclared time variables. Server validation rejects unbound
84
84
  first-party SQL, so declare the filter or choose an explicit non-dashboard
85
85
  scope before saving.
86
86
 
87
+ **A bound anywhere in the SQL is not the same as every CTE having its own
88
+ bound.** If a panel has multiple top-level CTEs (`WITH a AS (...), b AS
89
+ (...)`) and more than one of them reads `analytics_events`, EVERY one of
90
+ those CTEs needs its own `{{timeRange}}`/`{{<id>Start}}`/`{{<id>End}}`
91
+ reference or literal date bound — not just the final `SELECT` or one sibling
92
+ CTE. A CTE that computes something like "this user's first-ever active day"
93
+ by scanning `analytics_events` with no bound at all will full-table-scan on
94
+ every render even though the panel *looks* time-bound overall (root cause of
95
+ a 2026-07-25 production incident: several dashboards had exactly this shape).
96
+ Server validation checks each top-level CTE independently now, so this fails
97
+ at save time — but write it right the first time: bound every CTE, or use
98
+ `config.timeScope: "cohort-history"` only for a CTE that is genuinely
99
+ defining a cohort (e.g. a first-seen date), never as a way to skip bounding
100
+ an ordinary activity scan.
101
+
87
102
  ## Creating A Dashboard
88
103
 
89
104
  When the user asks for a dashboard:
@@ -19,14 +19,12 @@ import {
19
19
  IconLock,
20
20
  IconLink,
21
21
  IconMessageCircle,
22
- IconHierarchy2,
23
22
  IconUsersGroup,
24
23
  IconEye,
25
24
  IconEyeOff,
26
25
  IconPlayerPlay,
27
26
  IconLayoutSidebarLeftCollapse,
28
27
  IconLayoutSidebarLeftExpand,
29
- IconArrowUpRight,
30
28
  } from "@tabler/icons-react";
31
29
  import {
32
30
  useQuery,
@@ -183,12 +181,6 @@ import {
183
181
  import { CSS } from "@dnd-kit/utilities";
184
182
 
185
183
  const bottomItems = [
186
- {
187
- icon: IconHierarchy2,
188
- labelKey: "settings.agentTitle",
189
- href: "/agent",
190
- showExternalLinkIcon: true,
191
- },
192
184
  { icon: IconSettings, labelKey: "navigation.settings", href: "/settings" },
193
185
  ];
194
186
 
@@ -2098,12 +2090,6 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2098
2090
  href: "/data-dictionary",
2099
2091
  active: location.pathname.startsWith("/data-dictionary"),
2100
2092
  },
2101
- {
2102
- icon: IconHierarchy2,
2103
- label: t("settings.agentTitle"),
2104
- href: "/agent",
2105
- active: location.pathname === "/agent",
2106
- },
2107
2093
  {
2108
2094
  icon: IconSettings,
2109
2095
  label: t("navigation.settings"),
@@ -2572,15 +2558,7 @@ export function Sidebar({ mobile }: { mobile?: boolean } = {}) {
2572
2558
  )}
2573
2559
  >
2574
2560
  <Icon className="h-4 w-4" />
2575
- <span className="flex min-w-0 items-center gap-1">
2576
- <span className="truncate">{t(item.labelKey)}</span>
2577
- {item.showExternalLinkIcon && (
2578
- <IconArrowUpRight
2579
- aria-hidden="true"
2580
- className="h-3.5 w-3.5 shrink-0 text-muted-foreground/70"
2581
- />
2582
- )}
2583
- </span>
2561
+ <span className="truncate">{t(item.labelKey)}</span>
2584
2562
  </Link>
2585
2563
  );
2586
2564
  })}
@@ -1,8 +1,8 @@
1
- import { MAX_CONCURRENT_SQL_QUERIES } from "@shared/sql-query-limits";
2
-
3
1
  import type { SqlPanel } from "./types";
4
2
 
5
- export const REPORT_PANEL_CHUNK_SIZE = MAX_CONCURRENT_SQL_QUERIES;
3
+ // Keep each report window within the browser-side SQL query concurrency limit
4
+ // so a cold window does not serialize into multiple waves before readiness.
5
+ export const REPORT_PANEL_CHUNK_SIZE = 4;
6
6
 
7
7
  export function listReportablePanelIds(panels: SqlPanel[]): string[] {
8
8
  return panels
@@ -1,7 +1,7 @@
1
1
  import { redirect, type LoaderFunctionArgs } from "react-router";
2
2
 
3
3
  const SEO_TITLE =
4
- "Agent-Native Analytics - Open Source Alternative to Amplitude & FullStory";
4
+ "Analytics - Open Source Alternative to Amplitude & FullStory";
5
5
  const SEO_DESCRIPTION =
6
6
  "Open Source analytics app and alternative to Amplitude and FullStory where AI agents connect to warehouses, product analytics, and CRM data to answer questions and build dashboards.";
7
7
 
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-25
4
+ ---
5
+
6
+ Analytics dashboards and scheduled reports are faster and more reliable with large event histories.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-25
4
+ ---
5
+
6
+ App branding now uses the product name without the Agent-Native prefix.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-25
4
+ ---
5
+
6
+ Settings navigation now keeps Manage agent as a dedicated linked destination at the bottom.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "analytics",
3
- "displayName": "Agent-Native Analytics",
3
+ "displayName": "Analytics",
4
4
  "private": true,
5
5
  "type": "module",
6
6
  "scripts": {
@@ -1,5 +1,5 @@
1
1
  {
2
- "name": "Agent Native Analytics",
2
+ "name": "Analytics",
3
3
  "short_name": "Analytics",
4
4
  "description": "Analytics dashboard",
5
5
  "start_url": ".",
@@ -193,6 +193,18 @@ export const bigqueryCache = table("bigquery_cache", {
193
193
  expiresAt: text("expires_at").notNull(),
194
194
  });
195
195
 
196
+ /**
197
+ * First-party dashboard panel result cache — see
198
+ * server/lib/first-party-analytics-cache.ts.
199
+ */
200
+ export const firstPartyAnalyticsCache = table("first_party_analytics_cache", {
201
+ key: text("key").primaryKey(),
202
+ sql: text("sql").notNull(),
203
+ result: text("result").notNull(),
204
+ createdAt: text("created_at").notNull(),
205
+ expiresAt: text("expires_at").notNull(),
206
+ });
207
+
196
208
  /**
197
209
  * Public write keys for the first-party analytics ingestion endpoint.
198
210
  * The key is intentionally public/write-only: it can create events for the
@@ -446,10 +446,14 @@ export async function runDashboardPanelQuery(args: {
446
446
  }
447
447
 
448
448
  if (source === "first-party") {
449
- return await queryFirstPartyAnalytics(query, {
450
- userEmail: ctx.userEmail,
451
- orgId: ctx.orgId ?? null,
452
- });
449
+ return await queryFirstPartyAnalytics(
450
+ query,
451
+ {
452
+ userEmail: ctx.userEmail,
453
+ orgId: ctx.orgId ?? null,
454
+ },
455
+ { cache: true },
456
+ );
453
457
  }
454
458
 
455
459
  if (source === "demo") {
@@ -15,6 +15,8 @@ import {
15
15
  EMBED_TOKEN_QUERY_PARAM,
16
16
  } from "@agent-native/core/shared";
17
17
 
18
+ import { interpolate } from "../../app/pages/adhoc/sql-dashboard/interpolate";
19
+ import { serializePanelSql } from "../../app/pages/adhoc/sql-dashboard/panel-sql";
18
20
  import {
19
21
  listReportablePanelIds,
20
22
  REPORT_PANEL_CHUNK_SIZE,
@@ -32,6 +34,7 @@ import {
32
34
  type DashboardReportCaptureOutcome,
33
35
  type DashboardReportSubscription,
34
36
  } from "./dashboard-report-subscriptions";
37
+ import { queryFirstPartyAnalytics } from "./first-party-analytics.js";
35
38
 
36
39
  type ReportSnapshot = {
37
40
  dashboardId: string;
@@ -42,6 +45,8 @@ type ReportSnapshot = {
42
45
  reportSettingsUrl: string;
43
46
  generatedAt: string;
44
47
  panelIds: string[];
48
+ panels: SqlDashboardConfig["panels"];
49
+ variables?: Record<string, string>;
45
50
  };
46
51
 
47
52
  const DATE_FILTER_TYPES: ReadonlySet<FilterType> = new Set([
@@ -58,7 +63,9 @@ const LOCAL_SCREENSHOT_TIMEOUT_MS = 90_000;
58
63
  const SERVERLESS_SCREENSHOT_TIMEOUT_MS = 90_000;
59
64
  const SERVERLESS_SECOND_READY_TIMEOUT_MS = 45_000;
60
65
  // Cap browser work separately from the sweep-level delivery deadline.
61
- const SERVERLESS_CHUNKED_ATTEMPT_TIMEOUT_MS = 150_000;
66
+ // Reserve time under Netlify's 300s background-function limit for cleanup and
67
+ // email delivery (see MAX_CAPTURE_CLEANUP_RESERVE_MS / delivery reserves below).
68
+ const SERVERLESS_CHUNKED_ATTEMPT_TIMEOUT_MS = 210_000;
62
69
  const BROWSER_CLEANUP_TIMEOUT_MS = 10_000;
63
70
  const DASHBOARD_REPORT_EMAIL_TIMEOUT_MS = 10_000;
64
71
  const DASHBOARD_REPORT_EMAIL_OVERHEAD_RESERVE_MS = 5_000;
@@ -226,6 +233,8 @@ async function collectReportSnapshot(
226
233
  }),
227
234
  generatedAt: new Date().toISOString(),
228
235
  panelIds: listReportablePanelIds(config.panels),
236
+ panels: config.panels,
237
+ variables: config.variables,
229
238
  };
230
239
  }
231
240
 
@@ -375,6 +384,14 @@ function boundedStageTimeout(capMs: number, deadlineAt?: number): number {
375
384
  return Math.max(1, Math.min(capMs, remainingMs));
376
385
  }
377
386
 
387
+ function reportDeliveryReserveMs(recipientCount: number): number {
388
+ return (
389
+ MAX_CAPTURE_CLEANUP_RESERVE_MS +
390
+ recipientCount * DASHBOARD_REPORT_EMAIL_TIMEOUT_MS +
391
+ DASHBOARD_REPORT_EMAIL_OVERHEAD_RESERVE_MS
392
+ );
393
+ }
394
+
378
395
  async function runWithinCaptureDeadline<T>(
379
396
  operation: () => Promise<T>,
380
397
  deadlineAt?: number,
@@ -448,7 +465,15 @@ async function waitForDashboardReportReady(
448
465
  if (root.getAttribute("data-dashboard-report-ready") !== "true") {
449
466
  return false;
450
467
  }
451
- return !root.querySelector("[data-dashboard-report-loading='true']");
468
+ if (root.querySelector("[data-dashboard-report-loading='true']")) {
469
+ return false;
470
+ }
471
+ // A panel that errored (including its own query-call timeout) clears
472
+ // its loading marker just like a successful panel, so without this
473
+ // check the capture would treat a broken panel as "ready" and bake
474
+ // its raw error text into the delivered screenshot instead of
475
+ // failing the attempt so the retry sweep can pick it up.
476
+ return !root.querySelector("[role='alert']");
452
477
  })()`,
453
478
  undefined,
454
479
  { timeout },
@@ -505,6 +530,101 @@ async function waitForDashboardReportReady(
505
530
  }
506
531
  }
507
532
 
533
+ const PREWARM_CONCURRENCY = 3;
534
+ const PREWARM_PANEL_TIMEOUT_MS = 20_000;
535
+ const PREWARM_MAX_BUDGET_MS = 45_000;
536
+
537
+ /**
538
+ * Runs first-party panel queries once, directly, before the browser capture
539
+ * starts, so the capture's own query-dashboard-panel calls hit a warm cache
540
+ * instead of several panels racing each other cold for database time inside
541
+ * a single capture window — that contention, not any one query alone, is
542
+ * what was pushing panels past their capture timeout. Best-effort: a panel
543
+ * that fails or times out here is simply computed cold by the capture later,
544
+ * exactly as it always was, so this can never make a report worse than
545
+ * before it existed. Bounded to time left after reserving the full chunked
546
+ * capture attempt and delivery tail, so prewarming cannot starve capture.
547
+ * The per-panel timeout only bounds how long a worker waits before logging;
548
+ * it is also passed to the database/cache layers, and the original query is
549
+ * still awaited before this function returns so it cannot overlap capture.
550
+ */
551
+ async function prewarmFirstPartyPanelCache(
552
+ sub: DashboardReportSubscription,
553
+ snapshot: ReportSnapshot,
554
+ deadlineAt?: number,
555
+ recipientCount = 0,
556
+ ): Promise<void> {
557
+ const panels = (snapshot.panels ?? []).filter(
558
+ (panel) => panel.source === "first-party" && panel.sql != null,
559
+ );
560
+ if (!panels.length) return;
561
+
562
+ const remaining = deadlineAt ? deadlineAt - Date.now() : Infinity;
563
+ const budgetMs = Math.max(
564
+ 0,
565
+ Math.min(
566
+ PREWARM_MAX_BUDGET_MS,
567
+ deadlineAt
568
+ ? remaining -
569
+ SERVERLESS_CHUNKED_ATTEMPT_TIMEOUT_MS -
570
+ reportDeliveryReserveMs(recipientCount)
571
+ : PREWARM_MAX_BUDGET_MS,
572
+ ),
573
+ );
574
+ if (budgetMs <= 0) return;
575
+ const prewarmDeadline = Date.now() + budgetMs;
576
+
577
+ const vars: Record<string, string> = { ...snapshot.variables };
578
+ for (const [key, value] of Object.entries(snapshot.filters)) {
579
+ if (key.startsWith("f_")) vars[key.slice(2)] = value;
580
+ }
581
+ const scope = { userEmail: sub.ownerEmail, orgId: sub.orgId ?? null };
582
+
583
+ let index = 0;
584
+ async function worker(): Promise<void> {
585
+ while (index < panels.length && Date.now() < prewarmDeadline) {
586
+ const panel = panels[index++];
587
+ const sql = interpolate(serializePanelSql(panel.sql), vars, {
588
+ failClosedTimeVariables: true,
589
+ });
590
+ if (!sql.trim()) continue;
591
+ const panelTimeoutMs = Math.max(
592
+ 1,
593
+ Math.min(PREWARM_PANEL_TIMEOUT_MS, prewarmDeadline - Date.now()),
594
+ );
595
+ const query = queryFirstPartyAnalytics(sql, scope, {
596
+ cache: true,
597
+ timeoutMs: panelTimeoutMs,
598
+ });
599
+ let timeout: ReturnType<typeof setTimeout> | undefined;
600
+ try {
601
+ await Promise.race([
602
+ query,
603
+ new Promise<never>((_, reject) => {
604
+ timeout = setTimeout(
605
+ () => reject(new Error("prewarm panel timeout")),
606
+ panelTimeoutMs,
607
+ );
608
+ timeout.unref?.();
609
+ }),
610
+ ]);
611
+ } catch (err) {
612
+ console.warn(
613
+ `[dashboard-report] prewarm failed for panel ${panel.id}:`,
614
+ err instanceof Error ? err.message : err,
615
+ );
616
+ } finally {
617
+ if (timeout) clearTimeout(timeout);
618
+ await query.catch(() => undefined);
619
+ }
620
+ }
621
+ }
622
+
623
+ await Promise.all(
624
+ Array.from({ length: PREWARM_CONCURRENCY }, () => worker()),
625
+ );
626
+ }
627
+
508
628
  async function assertDashboardReportPanelWindow(
509
629
  page: any,
510
630
  expectedPanelIds: string[],
@@ -1358,14 +1478,25 @@ export async function sendDashboardReportSubscription(
1358
1478
  () => collectReportSnapshot(sub),
1359
1479
  options.deadlineAt,
1360
1480
  );
1481
+ try {
1482
+ await prewarmFirstPartyPanelCache(
1483
+ sub,
1484
+ snapshot,
1485
+ options.deadlineAt,
1486
+ recipients.length,
1487
+ );
1488
+ } catch (err) {
1489
+ console.warn(
1490
+ "[dashboard-report] panel cache prewarm failed (non-fatal):",
1491
+ err instanceof Error ? err.message : err,
1492
+ );
1493
+ }
1361
1494
  const captureTimeoutMs = options.deadlineAt
1362
1495
  ? Math.min(
1363
1496
  SERVERLESS_CHUNKED_ATTEMPT_TIMEOUT_MS,
1364
1497
  options.deadlineAt -
1365
1498
  Date.now() -
1366
- MAX_CAPTURE_CLEANUP_RESERVE_MS -
1367
- recipients.length * DASHBOARD_REPORT_EMAIL_TIMEOUT_MS -
1368
- DASHBOARD_REPORT_EMAIL_OVERHEAD_RESERVE_MS,
1499
+ reportDeliveryReserveMs(recipients.length),
1369
1500
  )
1370
1501
  : undefined;
1371
1502
  const capture = await captureDashboardPngWithFallback(