@agent-native/core 0.120.1 → 0.120.3

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 (396) hide show
  1. package/corpus/README.md +3 -3
  2. package/corpus/core/CHANGELOG.md +29 -0
  3. package/corpus/core/docs/content/extensions.mdx +23 -4
  4. package/corpus/core/package.json +1 -1
  5. package/corpus/core/src/agent/production-agent.ts +4 -0
  6. package/corpus/core/src/agent/run-manager.ts +36 -10
  7. package/corpus/core/src/agent/types.ts +2 -0
  8. package/corpus/core/src/cli/context-xray-local.ts +1 -1
  9. package/corpus/core/src/cli/index.ts +63 -18
  10. package/corpus/core/src/cli/skills.ts +5 -0
  11. package/corpus/core/src/cli/telemetry.ts +118 -1
  12. package/corpus/core/src/client/AgentPanel.tsx +10 -0
  13. package/corpus/core/src/client/CommandMenu.tsx +13 -1
  14. package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +39 -4
  15. package/corpus/core/src/client/analytics.ts +13 -0
  16. package/corpus/core/src/client/error-capture.ts +6 -1
  17. package/corpus/core/src/client/extensions/ExtensionsListPage.tsx +28 -8
  18. package/corpus/core/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  19. package/corpus/core/src/client/navigation/index.ts +2 -0
  20. package/corpus/core/src/client/org/OrgSwitcher.tsx +14 -32
  21. package/corpus/core/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  22. package/corpus/core/src/client/settings/SettingsPanel.tsx +10 -0
  23. package/corpus/core/src/deploy/build.ts +3 -0
  24. package/corpus/core/src/extensions/theme.ts +1 -1
  25. package/corpus/core/src/localization/default-messages.ts +2 -0
  26. package/corpus/core/src/server/analytics.ts +154 -25
  27. package/corpus/core/src/server/core-routes-plugin.ts +10 -1
  28. package/corpus/core/src/server/framework-request-handler.ts +17 -26
  29. package/corpus/core/src/styles/agent-native.css +13 -0
  30. package/corpus/core/src/templates/chat/app/components/layout/Layout.tsx +3 -0
  31. package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +113 -51
  32. package/corpus/core/src/templates/chat/app/global.css +1 -1
  33. package/corpus/core/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  34. package/corpus/core/src/templates/chat/netlify.toml +1 -0
  35. package/corpus/core/src/templates/default/app/global.css +1 -1
  36. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  37. package/corpus/core/src/tracking/error-capture.ts +151 -0
  38. package/corpus/core/src/tracking/index.ts +5 -0
  39. package/corpus/core/src/vite/client.ts +6 -0
  40. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +27 -43
  41. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +19 -8
  42. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +2 -2
  43. package/corpus/templates/analytics/AGENTS.md +20 -8
  44. package/corpus/templates/analytics/actions/get-explorer-dashboard.ts +3 -0
  45. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +140 -46
  46. package/corpus/templates/analytics/actions/migrate-analytics-artifacts.ts +48 -0
  47. package/corpus/templates/analytics/actions/save-analysis.ts +2 -3
  48. package/corpus/templates/analytics/actions/view-screen.ts +1 -1
  49. package/corpus/templates/analytics/app/components/dashboard/CumulativeNetChart.tsx +1 -1
  50. package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +1 -4
  51. package/corpus/templates/analytics/app/components/dashboard/DashboardMetadata.tsx +77 -0
  52. package/corpus/templates/analytics/app/components/dashboard/DashboardPanelSkeleton.tsx +7 -1
  53. package/corpus/templates/analytics/app/components/dashboard/DataTable.tsx +1 -1
  54. package/corpus/templates/analytics/app/components/dashboard/DateRangePicker.tsx +2 -2
  55. package/corpus/templates/analytics/app/components/dashboard/MetricCard.tsx +1 -1
  56. package/corpus/templates/analytics/app/components/dashboard/RecentActivity.tsx +1 -1
  57. package/corpus/templates/analytics/app/components/dashboard/RevenueChart.tsx +1 -1
  58. package/corpus/templates/analytics/app/components/dashboard/RevenueComparisonChart.tsx +1 -1
  59. package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +41 -9
  60. package/corpus/templates/analytics/app/components/dashboard/StatsCard.tsx +1 -1
  61. package/corpus/templates/analytics/app/components/dashboard/TimeSeriesChart.tsx +1 -1
  62. package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +2 -0
  63. package/corpus/templates/analytics/app/components/layout/Layout.tsx +28 -14
  64. package/corpus/templates/analytics/app/components/layout/NewDashboardDialog.tsx +1 -0
  65. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +255 -626
  66. package/corpus/templates/analytics/app/global.css +31 -1
  67. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -2
  68. package/corpus/templates/analytics/app/i18n-data.ts +80 -0
  69. package/corpus/templates/analytics/app/lib/dashboard-report-capture.ts +42 -0
  70. package/corpus/templates/analytics/app/pages/Agents.tsx +49 -2
  71. package/corpus/templates/analytics/app/pages/adhoc/_shared/KpiChart.tsx +1 -1
  72. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/ChartTypePicker.tsx +1 -1
  73. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/DateRangePicker.tsx +1 -1
  74. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/EventRow.tsx +1 -1
  75. package/corpus/templates/analytics/app/pages/adhoc/explorer/components/SqlPreview.tsx +2 -2
  76. package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +54 -11
  77. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -3
  78. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/EmailReportDialog.tsx +2 -2
  79. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +1 -1
  80. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +4 -54
  81. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +178 -69
  82. package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +2 -2
  83. package/corpus/templates/analytics/app/root.tsx +28 -0
  84. package/corpus/templates/analytics/app/routes/analyses._index.tsx +3 -2
  85. package/corpus/templates/analytics/app/routes/extensions._index.tsx +2 -2
  86. package/corpus/templates/analytics/changelog/2026-07-23-analytics-now-groups-saved-analyses-and-embedded-extensions-.md +6 -0
  87. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-menus-show-who-created-and-last-updated-each-dashb.md +6 -0
  88. package/corpus/templates/analytics/changelog/2026-07-23-dashboard-usage-now-loads-reliably-for-organizations-with-ma.md +6 -0
  89. package/corpus/templates/analytics/changelog/2026-07-23-long-dashboard-building-requests-now-continue-without-stoppi.md +6 -0
  90. package/corpus/templates/analytics/changelog/2026-07-24-analytics-charts-now-explain-slow-loads-and-offer-a-direct-r.md +6 -0
  91. package/corpus/templates/analytics/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  92. package/corpus/templates/analytics/changelog/2026-07-24-daily-dashboard-emails-now-load-signed-reports-more-reliably.md +6 -0
  93. package/corpus/templates/analytics/changelog/2026-07-24-manage-agent-is-clearly-marked-as-a-link-to-its-dedicated-pa.md +6 -0
  94. package/corpus/templates/analytics/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  95. package/corpus/templates/analytics/netlify.toml +4 -0
  96. package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +1 -1
  97. package/corpus/templates/analytics/server/db/schema.ts +9 -0
  98. package/corpus/templates/analytics/server/jobs/dashboard-report.ts +21 -0
  99. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +0 -1
  100. package/corpus/templates/analytics/server/lib/agent-readable-resource-context.ts +1 -0
  101. package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +11 -0
  102. package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +54 -0
  103. package/corpus/templates/analytics/server/lib/dashboard-report.ts +235 -26
  104. package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +4 -1
  105. package/corpus/templates/analytics/server/lib/migrate-analytics-artifacts.ts +1062 -0
  106. package/corpus/templates/analytics/server/plugins/agent-chat.ts +6 -52
  107. package/corpus/templates/analytics/server/plugins/core-routes.ts +1 -1
  108. package/corpus/templates/analytics/server/plugins/db.ts +19 -0
  109. package/corpus/templates/analytics/shared/dashboard-report-timeouts.ts +1 -1
  110. package/corpus/templates/assets/actions/generate-image-batch.ts +36 -1
  111. package/corpus/templates/assets/actions/generate-image.ts +28 -1
  112. package/corpus/templates/assets/app/components/asset/AssetPreviewDialog.tsx +376 -0
  113. package/corpus/templates/assets/app/components/create/RecentDraftsSection.tsx +14 -4
  114. package/corpus/templates/assets/app/components/layout/Layout.tsx +5 -0
  115. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +164 -82
  116. package/corpus/templates/assets/app/global.css +1 -1
  117. package/corpus/templates/assets/app/i18n-data.ts +10 -0
  118. package/corpus/templates/assets/app/routes/brand-kits.$id.tsx +99 -10
  119. package/corpus/templates/assets/app/routes/extensions._index.tsx +2 -2
  120. package/corpus/templates/assets/app/routes/library.tsx +21 -198
  121. package/corpus/templates/assets/changelog/2026-07-21-unified-asset-preview-click-any-asset-to-open-one-preview-wi.md +6 -0
  122. package/corpus/templates/assets/changelog/2026-07-23-assets-image-generation-now-recovers-after-a-completed-tool-.md +6 -0
  123. package/corpus/templates/assets/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  124. package/corpus/templates/assets/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  125. package/corpus/templates/assets/netlify.toml +1 -0
  126. package/corpus/templates/brain/app/components/layout/Layout.tsx +3 -0
  127. package/corpus/templates/brain/app/components/layout/Sidebar.tsx +114 -51
  128. package/corpus/templates/brain/app/global.css +1 -1
  129. package/corpus/templates/brain/app/routes/extensions._index.tsx +2 -2
  130. package/corpus/templates/brain/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  131. package/corpus/templates/brain/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  132. package/corpus/templates/brain/netlify.toml +1 -0
  133. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +110 -57
  134. package/corpus/templates/calendar/app/global.css +1 -1
  135. package/corpus/templates/calendar/app/routes/_app.extensions._index.tsx +2 -16
  136. package/corpus/templates/calendar/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  137. package/corpus/templates/calendar/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  138. package/corpus/templates/calendar/netlify.toml +1 -0
  139. package/corpus/templates/chat/app/components/layout/Layout.tsx +3 -0
  140. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +113 -51
  141. package/corpus/templates/chat/app/global.css +1 -1
  142. package/corpus/templates/chat/app/routes/extensions._index.tsx +2 -2
  143. package/corpus/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  144. package/corpus/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  145. package/corpus/templates/chat/netlify.toml +1 -0
  146. package/corpus/templates/clips/app/components/library/library-layout.tsx +117 -37
  147. package/corpus/templates/clips/app/components/library/organization-switcher.tsx +2 -2
  148. package/corpus/templates/clips/app/components/player/video-player.tsx +16 -1
  149. package/corpus/templates/clips/app/global.css +1 -1
  150. package/corpus/templates/clips/app/hooks/use-view-tracking.ts +55 -12
  151. package/corpus/templates/clips/app/routes/_app.extensions._index.tsx +2 -16
  152. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +6 -5
  153. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -7
  154. package/corpus/templates/clips/app/routes/share.$shareId.tsx +6 -5
  155. package/corpus/templates/clips/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  156. package/corpus/templates/clips/changelog/2026-07-23-meeting-recording-meters-and-dismissed-prompts.md +6 -0
  157. package/corpus/templates/clips/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  158. package/corpus/templates/clips/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  159. package/corpus/templates/clips/desktop/src/hooks/useMicMeter.ts +1 -1
  160. package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +2 -0
  161. package/corpus/templates/clips/desktop/src/lib/voice-dictation.ts +1 -3
  162. package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +86 -0
  163. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +24 -0
  164. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +5 -69
  165. package/corpus/templates/clips/desktop/src/styles.css +35 -3
  166. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +13 -44
  167. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +119 -17
  168. package/corpus/templates/clips/netlify.toml +1 -0
  169. package/corpus/templates/clips/server/plugins/db.ts +113 -68
  170. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +69 -49
  171. package/corpus/templates/content/app/global.css +1 -1
  172. package/corpus/templates/content/app/routes/_app.extensions._index.tsx +2 -2
  173. package/corpus/templates/content/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  174. package/corpus/templates/content/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  175. package/corpus/templates/content/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  176. package/corpus/templates/content/netlify.toml +1 -0
  177. package/corpus/templates/design/app/components/layout/Sidebar.tsx +115 -42
  178. package/corpus/templates/design/app/entry.client.tsx +14 -1
  179. package/corpus/templates/design/app/global.css +1 -1
  180. package/corpus/templates/design/app/routes/extensions._index.tsx +2 -2
  181. package/corpus/templates/design/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  182. package/corpus/templates/design/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  183. package/corpus/templates/design/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  184. package/corpus/templates/design/netlify.toml +1 -0
  185. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -1
  186. package/corpus/templates/dispatch/app/global.css +1 -1
  187. package/corpus/templates/dispatch/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  188. package/corpus/templates/dispatch/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  189. package/corpus/templates/dispatch/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  190. package/corpus/templates/dispatch/netlify.toml +1 -0
  191. package/corpus/templates/forms/app/components/layout/Layout.tsx +3 -0
  192. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +113 -82
  193. package/corpus/templates/forms/app/global.css +1 -1
  194. package/corpus/templates/forms/app/routes/_app.extensions._index.tsx +2 -2
  195. package/corpus/templates/forms/changelog/2026-07-23-agent-settings-are-clearly-labeled-manage-agent-in-the-sideb.md +6 -0
  196. package/corpus/templates/forms/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  197. package/corpus/templates/forms/changelog/2026-07-24-forms-sidebar-actions-now-keep-search-language-feedback-and-.md +6 -0
  198. package/corpus/templates/forms/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  199. package/corpus/templates/forms/netlify.toml +1 -0
  200. package/corpus/templates/macros/app/components/DailyProgress.tsx +5 -5
  201. package/corpus/templates/macros/app/components/ExerciseCard.tsx +2 -2
  202. package/corpus/templates/macros/app/components/MealCard.tsx +2 -2
  203. package/corpus/templates/macros/app/components/WeightCard.tsx +2 -2
  204. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +92 -34
  205. package/corpus/templates/macros/app/components/ui/button.tsx +1 -2
  206. package/corpus/templates/macros/app/components/ui/card.tsx +1 -1
  207. package/corpus/templates/macros/app/global.css +1 -1
  208. package/corpus/templates/macros/app/routes/_index.tsx +1 -1
  209. package/corpus/templates/macros/app/routes/analytics.tsx +4 -7
  210. package/corpus/templates/macros/app/routes/extensions._index.tsx +2 -2
  211. package/corpus/templates/macros/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  212. package/corpus/templates/macros/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  213. package/corpus/templates/macros/netlify.toml +1 -0
  214. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +125 -46
  215. package/corpus/templates/mail/app/global.css +1 -1
  216. package/corpus/templates/mail/app/routes/extensions._index.tsx +2 -2
  217. package/corpus/templates/mail/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  218. package/corpus/templates/mail/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  219. package/corpus/templates/mail/netlify.toml +1 -0
  220. package/corpus/templates/plan/app/components/layout/Layout.tsx +3 -0
  221. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +128 -65
  222. package/corpus/templates/plan/app/global.css +1 -1
  223. package/corpus/templates/plan/app/routes/extensions._index.tsx +2 -2
  224. package/corpus/templates/plan/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  225. package/corpus/templates/plan/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  226. package/corpus/templates/plan/netlify.toml +1 -0
  227. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +116 -36
  228. package/corpus/templates/slides/app/global.css +1 -1
  229. package/corpus/templates/slides/app/routes/extensions._index.tsx +2 -2
  230. package/corpus/templates/slides/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  231. package/corpus/templates/slides/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  232. package/corpus/templates/slides/netlify.toml +1 -0
  233. package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +37 -6
  234. package/corpus/templates/tasks/app/global.css +1 -1
  235. package/corpus/templates/tasks/app/routes/extensions._index.tsx +2 -2
  236. package/corpus/templates/tasks/app/routes/settings.tsx +31 -0
  237. package/corpus/templates/tasks/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  238. package/corpus/templates/tasks/netlify.toml +1 -0
  239. package/corpus/toolkit/CHANGELOG.md +8 -0
  240. package/corpus/toolkit/README.md +8 -1
  241. package/corpus/toolkit/agent-native.eject.json +1 -1
  242. package/corpus/toolkit/package.json +1 -1
  243. package/corpus/toolkit/src/app-shell/index.ts +1 -0
  244. package/corpus/toolkit/src/app-shell/sidebar-footer-actions.tsx +48 -0
  245. package/corpus/toolkit/src/chat-history.css +52 -1
  246. package/corpus/toolkit/src/design-system/default-adapter.tsx +1 -1
  247. package/corpus/toolkit/src/ui/button.tsx +1 -2
  248. package/corpus/toolkit/src/ui/card.tsx +1 -1
  249. package/corpus/toolkit/src/ui/table.tsx +4 -11
  250. package/dist/agent/production-agent.d.ts.map +1 -1
  251. package/dist/agent/production-agent.js +4 -0
  252. package/dist/agent/production-agent.js.map +1 -1
  253. package/dist/agent/run-manager.d.ts +9 -0
  254. package/dist/agent/run-manager.d.ts.map +1 -1
  255. package/dist/agent/run-manager.js +25 -10
  256. package/dist/agent/run-manager.js.map +1 -1
  257. package/dist/agent/types.d.ts +2 -2
  258. package/dist/agent/types.d.ts.map +1 -1
  259. package/dist/agent/types.js.map +1 -1
  260. package/dist/cli/context-xray-local.js +1 -1
  261. package/dist/cli/context-xray-local.js.map +1 -1
  262. package/dist/cli/index.js +56 -17
  263. package/dist/cli/index.js.map +1 -1
  264. package/dist/cli/skills.d.ts.map +1 -1
  265. package/dist/cli/skills.js +5 -0
  266. package/dist/cli/skills.js.map +1 -1
  267. package/dist/cli/telemetry.d.ts +7 -0
  268. package/dist/cli/telemetry.d.ts.map +1 -1
  269. package/dist/cli/telemetry.js +91 -1
  270. package/dist/cli/telemetry.js.map +1 -1
  271. package/dist/client/AgentPanel.d.ts +7 -1
  272. package/dist/client/AgentPanel.d.ts.map +1 -1
  273. package/dist/client/AgentPanel.js +2 -2
  274. package/dist/client/AgentPanel.js.map +1 -1
  275. package/dist/client/CommandMenu.d.ts +2 -0
  276. package/dist/client/CommandMenu.d.ts.map +1 -1
  277. package/dist/client/CommandMenu.js +12 -1
  278. package/dist/client/CommandMenu.js.map +1 -1
  279. package/dist/client/agent-page/AgentTabsPage.d.ts +4 -0
  280. package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
  281. package/dist/client/agent-page/AgentTabsPage.js +8 -4
  282. package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
  283. package/dist/client/analytics.d.ts.map +1 -1
  284. package/dist/client/analytics.js +7 -0
  285. package/dist/client/analytics.js.map +1 -1
  286. package/dist/client/error-capture.d.ts.map +1 -1
  287. package/dist/client/error-capture.js +8 -1
  288. package/dist/client/error-capture.js.map +1 -1
  289. package/dist/client/extensions/ExtensionsListPage.d.ts +5 -1
  290. package/dist/client/extensions/ExtensionsListPage.d.ts.map +1 -1
  291. package/dist/client/extensions/ExtensionsListPage.js +6 -4
  292. package/dist/client/extensions/ExtensionsListPage.js.map +1 -1
  293. package/dist/client/extensions/agent-native-extension-runtime.js +1 -1
  294. package/dist/client/extensions/agent-native-extension-runtime.js.map +1 -1
  295. package/dist/client/navigation/index.d.ts +1 -1
  296. package/dist/client/navigation/index.d.ts.map +1 -1
  297. package/dist/client/navigation/index.js +1 -1
  298. package/dist/client/navigation/index.js.map +1 -1
  299. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  300. package/dist/client/org/OrgSwitcher.js +9 -13
  301. package/dist/client/org/OrgSwitcher.js.map +1 -1
  302. package/dist/client/settings/ExtensionsSettingsContent.d.ts +2 -0
  303. package/dist/client/settings/ExtensionsSettingsContent.d.ts.map +1 -0
  304. package/dist/client/settings/ExtensionsSettingsContent.js +10 -0
  305. package/dist/client/settings/ExtensionsSettingsContent.js.map +1 -0
  306. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  307. package/dist/client/settings/SettingsPanel.js +10 -1
  308. package/dist/client/settings/SettingsPanel.js.map +1 -1
  309. package/dist/deploy/build.js +1 -0
  310. package/dist/deploy/build.js.map +1 -1
  311. package/dist/extensions/theme.js +1 -1
  312. package/dist/extensions/theme.js.map +1 -1
  313. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  314. package/dist/localization/default-messages.d.ts +2 -0
  315. package/dist/localization/default-messages.d.ts.map +1 -1
  316. package/dist/localization/default-messages.js +2 -0
  317. package/dist/localization/default-messages.js.map +1 -1
  318. package/dist/observability/routes.d.ts +3 -3
  319. package/dist/progress/routes.d.ts +1 -1
  320. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  321. package/dist/provider-api/actions/provider-api.d.ts +9 -9
  322. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  323. package/dist/resources/handlers.d.ts +1 -1
  324. package/dist/server/analytics.d.ts +9 -4
  325. package/dist/server/analytics.d.ts.map +1 -1
  326. package/dist/server/analytics.js +111 -22
  327. package/dist/server/analytics.js.map +1 -1
  328. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  329. package/dist/server/core-routes-plugin.js +8 -1
  330. package/dist/server/core-routes-plugin.js.map +1 -1
  331. package/dist/server/framework-request-handler.d.ts.map +1 -1
  332. package/dist/server/framework-request-handler.js +17 -27
  333. package/dist/server/framework-request-handler.js.map +1 -1
  334. package/dist/server/realtime-token.d.ts +1 -1
  335. package/dist/styles/agent-native.css +13 -0
  336. package/dist/templates/chat/app/components/layout/Layout.tsx +3 -0
  337. package/dist/templates/chat/app/components/layout/Sidebar.tsx +113 -51
  338. package/dist/templates/chat/app/global.css +1 -1
  339. package/dist/templates/chat/app/routes/extensions._index.tsx +2 -2
  340. package/dist/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  341. package/dist/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  342. package/dist/templates/chat/netlify.toml +1 -0
  343. package/dist/templates/default/app/global.css +1 -1
  344. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  345. package/dist/tracking/error-capture.d.ts +14 -0
  346. package/dist/tracking/error-capture.d.ts.map +1 -0
  347. package/dist/tracking/error-capture.js +113 -0
  348. package/dist/tracking/error-capture.js.map +1 -0
  349. package/dist/tracking/index.d.ts +1 -0
  350. package/dist/tracking/index.d.ts.map +1 -1
  351. package/dist/tracking/index.js +1 -0
  352. package/dist/tracking/index.js.map +1 -1
  353. package/dist/vite/client.d.ts.map +1 -1
  354. package/dist/vite/client.js +2 -0
  355. package/dist/vite/client.js.map +1 -1
  356. package/docs/content/extensions.mdx +23 -4
  357. package/package.json +2 -2
  358. package/src/agent/production-agent.ts +4 -0
  359. package/src/agent/run-manager.ts +36 -10
  360. package/src/agent/types.ts +2 -0
  361. package/src/cli/context-xray-local.ts +1 -1
  362. package/src/cli/index.ts +63 -18
  363. package/src/cli/skills.ts +5 -0
  364. package/src/cli/telemetry.ts +118 -1
  365. package/src/client/AgentPanel.tsx +10 -0
  366. package/src/client/CommandMenu.tsx +13 -1
  367. package/src/client/agent-page/AgentTabsPage.tsx +39 -4
  368. package/src/client/analytics.ts +13 -0
  369. package/src/client/error-capture.ts +6 -1
  370. package/src/client/extensions/ExtensionsListPage.tsx +28 -8
  371. package/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  372. package/src/client/navigation/index.ts +2 -0
  373. package/src/client/org/OrgSwitcher.tsx +14 -32
  374. package/src/client/settings/ExtensionsSettingsContent.tsx +24 -0
  375. package/src/client/settings/SettingsPanel.tsx +10 -0
  376. package/src/deploy/build.ts +3 -0
  377. package/src/extensions/theme.ts +1 -1
  378. package/src/localization/default-messages.ts +2 -0
  379. package/src/server/analytics.ts +154 -25
  380. package/src/server/core-routes-plugin.ts +10 -1
  381. package/src/server/framework-request-handler.ts +17 -26
  382. package/src/styles/agent-native.css +13 -0
  383. package/src/templates/chat/app/components/layout/Layout.tsx +3 -0
  384. package/src/templates/chat/app/components/layout/Sidebar.tsx +113 -51
  385. package/src/templates/chat/app/global.css +1 -1
  386. package/src/templates/chat/app/routes/extensions._index.tsx +2 -2
  387. package/src/templates/chat/changelog/2026-07-24-borderless-secondary-surfaces.md +6 -0
  388. package/src/templates/chat/changelog/2026-07-24-sidebar-footer-controls-follow-a-consistent-order.md +6 -0
  389. package/src/templates/chat/netlify.toml +1 -0
  390. package/src/templates/default/app/global.css +1 -1
  391. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +16 -0
  392. package/src/tracking/error-capture.ts +151 -0
  393. package/src/tracking/index.ts +5 -0
  394. package/src/vite/client.ts +6 -0
  395. package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +0 -165
  396. package/corpus/templates/analytics/app/pages/analyses/AnalysesList.tsx +0 -175
@@ -0,0 +1,1062 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ import {
4
+ createSharesTable,
5
+ ownableColumns,
6
+ table,
7
+ text,
8
+ } from "@agent-native/core/db/schema";
9
+ import { recordChange } from "@agent-native/core/server";
10
+ import { listOrgSettings } from "@agent-native/core/settings";
11
+ import { eq, inArray } from "drizzle-orm";
12
+
13
+ import { getDb, schema } from "../db/index.js";
14
+
15
+ const migrationExtensions = table("tools", {
16
+ id: text("id").primaryKey(),
17
+ name: text("name").notNull(),
18
+ description: text("description").notNull(),
19
+ content: text("content").notNull(),
20
+ icon: text("icon"),
21
+ createdAt: text("created_at").notNull(),
22
+ updatedAt: text("updated_at").notNull(),
23
+ archivedAt: text("archived_at"),
24
+ hiddenAt: text("hidden_at"),
25
+ hiddenBy: text("hidden_by"),
26
+ ...ownableColumns(),
27
+ });
28
+
29
+ const migrationExtensionData = table("tool_data", {
30
+ id: text("id").primaryKey(),
31
+ extensionId: text("tool_id").notNull(),
32
+ orgId: text("org_id"),
33
+ });
34
+
35
+ const migrationExtensionShares = createSharesTable("tool_shares");
36
+ const migrationSettings = table("settings", {
37
+ key: text("key").primaryKey(),
38
+ value: text("value").notNull(),
39
+ });
40
+
41
+ type Visibility = "private" | "org" | "public";
42
+ type SourceType = "analysis" | "extension";
43
+
44
+ export interface AnalyticsArtifactMigrationContext {
45
+ userEmail: string;
46
+ orgId: string;
47
+ }
48
+
49
+ export interface AnalyticsArtifactMigrationSummary {
50
+ dryRun: boolean;
51
+ orgId: string;
52
+ legacyDashboards: number;
53
+ legacyAnalyses: number;
54
+ dashboardsMaterialized: number;
55
+ analysesMaterialized: number;
56
+ dashboardsCreated: number;
57
+ analysisDashboardsCreated: number;
58
+ extensionDashboardsCreated: number;
59
+ duplicateDashboardsArchived: number;
60
+ duplicateAnalysesHidden: number;
61
+ duplicateExtensionsArchived: number;
62
+ analysesHidden: number;
63
+ extensionsHidden: number;
64
+ legacySettingsDeleted: number;
65
+ dashboardReferencesRewritten: number;
66
+ skipped: Array<{ type: string; id: string; reason: string }>;
67
+ }
68
+
69
+ interface LegacyDashboard {
70
+ id: string;
71
+ kind: "explorer" | "sql";
72
+ data: Record<string, unknown>;
73
+ }
74
+
75
+ interface LegacyAnalysis {
76
+ id: string;
77
+ data: Record<string, unknown>;
78
+ }
79
+
80
+ interface DashboardSource {
81
+ id: string;
82
+ kind: "explorer" | "sql";
83
+ title: string;
84
+ config: Record<string, unknown>;
85
+ ownerEmail: string;
86
+ orgId: string;
87
+ visibility: Visibility;
88
+ createdAt: string;
89
+ updatedAt: string;
90
+ archivedAt: string | null;
91
+ hiddenAt: string | null;
92
+ }
93
+
94
+ interface AnalysisSource {
95
+ id: string;
96
+ name: string;
97
+ description: string;
98
+ question: string;
99
+ instructions: string;
100
+ dataSources: string[];
101
+ resultMarkdown: string;
102
+ resultData: Record<string, unknown> | null;
103
+ author: string | null;
104
+ ownerEmail: string;
105
+ orgId: string;
106
+ visibility: Visibility;
107
+ createdAt: string;
108
+ updatedAt: string;
109
+ hiddenAt: string | null;
110
+ }
111
+
112
+ interface ExtensionSource {
113
+ id: string;
114
+ name: string;
115
+ description: string;
116
+ content: string;
117
+ icon: string | null;
118
+ createdAt: string;
119
+ updatedAt: string;
120
+ archivedAt: string | null;
121
+ hiddenAt: string | null;
122
+ ownerEmail: string;
123
+ orgId: string;
124
+ visibility: Visibility;
125
+ }
126
+
127
+ interface MigrationState {
128
+ dashboards: DashboardSource[];
129
+ analyses: AnalysisSource[];
130
+ extensions: ExtensionSource[];
131
+ legacyDashboardKeys: string[];
132
+ legacyAnalysisKeys: string[];
133
+ extensionDataIds: Set<string>;
134
+ }
135
+
136
+ function nowIso(): string {
137
+ return new Date().toISOString();
138
+ }
139
+
140
+ function parseJson(value: unknown): Record<string, unknown> {
141
+ if (value && typeof value === "object" && !Array.isArray(value)) {
142
+ return value as Record<string, unknown>;
143
+ }
144
+ if (typeof value !== "string") return {};
145
+ try {
146
+ const parsed = JSON.parse(value);
147
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed)
148
+ ? (parsed as Record<string, unknown>)
149
+ : {};
150
+ } catch {
151
+ return {};
152
+ }
153
+ }
154
+
155
+ function parseArray<T>(value: unknown): T[] {
156
+ if (Array.isArray(value)) return value as T[];
157
+ if (typeof value !== "string") return [];
158
+ try {
159
+ const parsed = JSON.parse(value);
160
+ return Array.isArray(parsed) ? (parsed as T[]) : [];
161
+ } catch {
162
+ return [];
163
+ }
164
+ }
165
+
166
+ function stableValue(value: unknown): unknown {
167
+ if (Array.isArray(value)) return value.map(stableValue);
168
+ if (value && typeof value === "object") {
169
+ return Object.fromEntries(
170
+ Object.entries(value as Record<string, unknown>)
171
+ .sort(([a], [b]) => a.localeCompare(b))
172
+ .map(([key, nested]) => [key, stableValue(nested)]),
173
+ );
174
+ }
175
+ return value;
176
+ }
177
+
178
+ function stableStringify(value: unknown): string {
179
+ return JSON.stringify(stableValue(value));
180
+ }
181
+
182
+ function scopedFingerprint(
183
+ visibility: Visibility,
184
+ ownerEmail: string,
185
+ value: unknown,
186
+ ): string {
187
+ return stableStringify({
188
+ scope: visibility === "org" ? "org" : ownerEmail.toLowerCase(),
189
+ visibility,
190
+ value,
191
+ });
192
+ }
193
+
194
+ function dashboardTitle(data: Record<string, unknown>): string {
195
+ const title = data.title ?? data.name;
196
+ return typeof title === "string" && title.trim() ? title.trim() : "Untitled";
197
+ }
198
+
199
+ function timestamp(data: Record<string, unknown>, fallback: string): string {
200
+ return typeof data.updatedAt === "string" && data.updatedAt
201
+ ? data.updatedAt
202
+ : typeof data.createdAt === "string" && data.createdAt
203
+ ? data.createdAt
204
+ : fallback;
205
+ }
206
+
207
+ function legacyDashboardRows(
208
+ settings: Record<string, Record<string, unknown>>,
209
+ ): { rows: LegacyDashboard[]; keys: string[] } {
210
+ const rows: LegacyDashboard[] = [];
211
+ const keys: string[] = [];
212
+ for (const [key, data] of Object.entries(settings)) {
213
+ if (key.startsWith("sql-dashboard-")) {
214
+ rows.push({
215
+ id: key.slice("sql-dashboard-".length),
216
+ kind: "sql",
217
+ data,
218
+ });
219
+ keys.push(key);
220
+ } else if (key.startsWith("dashboard-")) {
221
+ rows.push({
222
+ id: key.slice("dashboard-".length),
223
+ kind: "explorer",
224
+ data,
225
+ });
226
+ keys.push(key);
227
+ }
228
+ }
229
+ return { rows, keys };
230
+ }
231
+
232
+ function legacyAnalysisRows(
233
+ settings: Record<string, Record<string, unknown>>,
234
+ ): { rows: LegacyAnalysis[]; keys: string[] } {
235
+ const rows: LegacyAnalysis[] = [];
236
+ const keys: string[] = [];
237
+ for (const [key, data] of Object.entries(settings)) {
238
+ if (!key.startsWith("adhoc-analysis-")) continue;
239
+ rows.push({ id: key.slice("adhoc-analysis-".length), data });
240
+ keys.push(key);
241
+ }
242
+ return { rows, keys };
243
+ }
244
+
245
+ async function readMigrationState(
246
+ ctx: AnalyticsArtifactMigrationContext,
247
+ ): Promise<MigrationState> {
248
+ const db = getDb() as any;
249
+ const orgSettings = await listOrgSettings(ctx.orgId);
250
+ const legacyDashboards = legacyDashboardRows(orgSettings);
251
+ const legacyAnalyses = legacyAnalysisRows(orgSettings);
252
+
253
+ const [dashboardRows, analysisRows, extensionRows] = await Promise.all([
254
+ db
255
+ .select()
256
+ .from(schema.dashboards)
257
+ .where(eq(schema.dashboards.orgId, ctx.orgId)),
258
+ db
259
+ .select()
260
+ .from(schema.analyses)
261
+ .where(eq(schema.analyses.orgId, ctx.orgId)),
262
+ db
263
+ .select()
264
+ .from(migrationExtensions)
265
+ .where(eq(migrationExtensions.orgId, ctx.orgId)),
266
+ ]);
267
+ const extensionIds = extensionRows.map((row: { id: string }) => row.id);
268
+ const extensionDataRows =
269
+ extensionIds.length === 0
270
+ ? []
271
+ : await db
272
+ .select({ extensionId: migrationExtensionData.extensionId })
273
+ .from(migrationExtensionData)
274
+ .where(inArray(migrationExtensionData.extensionId, extensionIds));
275
+
276
+ const now = nowIso();
277
+ const materializedDashboardIds = new Set(
278
+ dashboardRows.map((row: { id: string }) => row.id),
279
+ );
280
+ const materializedAnalysisIds = new Set(
281
+ analysisRows.map((row: { id: string }) => row.id),
282
+ );
283
+
284
+ const dashboards: DashboardSource[] = dashboardRows.map((row: any) => ({
285
+ id: row.id,
286
+ kind: row.kind,
287
+ title: row.title,
288
+ config: parseJson(row.config),
289
+ ownerEmail: row.ownerEmail,
290
+ orgId: row.orgId,
291
+ visibility: row.visibility,
292
+ createdAt: row.createdAt,
293
+ updatedAt: row.updatedAt,
294
+ archivedAt: row.archivedAt ?? null,
295
+ hiddenAt: row.hiddenAt ?? null,
296
+ }));
297
+
298
+ for (const row of legacyDashboards.rows) {
299
+ if (materializedDashboardIds.has(row.id)) continue;
300
+ dashboards.push({
301
+ id: row.id,
302
+ kind: row.kind,
303
+ title: dashboardTitle(row.data),
304
+ config: row.data,
305
+ ownerEmail: ctx.userEmail,
306
+ orgId: ctx.orgId,
307
+ visibility: "org",
308
+ createdAt:
309
+ typeof row.data.createdAt === "string" ? row.data.createdAt : now,
310
+ updatedAt: timestamp(row.data, now),
311
+ archivedAt: null,
312
+ hiddenAt: null,
313
+ });
314
+ }
315
+
316
+ const analyses: AnalysisSource[] = analysisRows.map((row: any) => ({
317
+ id: row.id,
318
+ name: row.name,
319
+ description: row.description,
320
+ question: row.question,
321
+ instructions: row.instructions,
322
+ dataSources: parseArray<string>(row.dataSources),
323
+ resultMarkdown: row.resultMarkdown,
324
+ resultData: row.resultData ? parseJson(row.resultData) : null,
325
+ author: row.author ?? null,
326
+ ownerEmail: row.ownerEmail,
327
+ orgId: row.orgId,
328
+ visibility: row.visibility,
329
+ createdAt: row.createdAt,
330
+ updatedAt: row.updatedAt,
331
+ hiddenAt: row.hiddenAt ?? null,
332
+ }));
333
+
334
+ for (const row of legacyAnalyses.rows) {
335
+ if (materializedAnalysisIds.has(row.id)) continue;
336
+ analyses.push({
337
+ id: row.id,
338
+ name: typeof row.data.name === "string" ? row.data.name : "Untitled",
339
+ description:
340
+ typeof row.data.description === "string" ? row.data.description : "",
341
+ question: typeof row.data.question === "string" ? row.data.question : "",
342
+ instructions:
343
+ typeof row.data.instructions === "string" ? row.data.instructions : "",
344
+ dataSources: parseArray<string>(row.data.dataSources),
345
+ resultMarkdown:
346
+ typeof row.data.resultMarkdown === "string"
347
+ ? row.data.resultMarkdown
348
+ : "",
349
+ resultData: row.data.resultData ? parseJson(row.data.resultData) : null,
350
+ author:
351
+ typeof row.data.author === "string" ? row.data.author : ctx.userEmail,
352
+ ownerEmail: ctx.userEmail,
353
+ orgId: ctx.orgId,
354
+ visibility: "org",
355
+ createdAt:
356
+ typeof row.data.createdAt === "string" ? row.data.createdAt : now,
357
+ updatedAt: timestamp(row.data, now),
358
+ hiddenAt: null,
359
+ });
360
+ }
361
+
362
+ return {
363
+ dashboards,
364
+ analyses,
365
+ extensions: extensionRows.map((row: any) => ({
366
+ id: row.id,
367
+ name: row.name,
368
+ description: row.description,
369
+ content: row.content,
370
+ icon: row.icon ?? null,
371
+ createdAt: row.createdAt,
372
+ updatedAt: row.updatedAt,
373
+ archivedAt: row.archivedAt ?? null,
374
+ hiddenAt: row.hiddenAt ?? null,
375
+ ownerEmail: row.ownerEmail,
376
+ orgId: row.orgId,
377
+ visibility: row.visibility,
378
+ })),
379
+ legacyDashboardKeys: legacyDashboards.keys,
380
+ legacyAnalysisKeys: legacyAnalyses.keys,
381
+ extensionDataIds: new Set(
382
+ extensionDataRows.map((row: { extensionId: string }) => row.extensionId),
383
+ ),
384
+ };
385
+ }
386
+
387
+ function duplicateDashboardGroups(
388
+ rows: DashboardSource[],
389
+ ): DashboardSource[][] {
390
+ const groups = new Map<string, DashboardSource[]>();
391
+ for (const row of rows) {
392
+ if (row.archivedAt) continue;
393
+ const key = scopedFingerprint(row.visibility, row.ownerEmail, {
394
+ kind: row.kind,
395
+ title: row.title,
396
+ config: row.config,
397
+ });
398
+ const group = groups.get(key) ?? [];
399
+ group.push(row);
400
+ groups.set(key, group);
401
+ }
402
+ return Array.from(groups.values()).filter((group) => group.length > 1);
403
+ }
404
+
405
+ function duplicateAnalysisGroups(rows: AnalysisSource[]): AnalysisSource[][] {
406
+ const groups = new Map<string, AnalysisSource[]>();
407
+ for (const row of rows) {
408
+ const key = scopedFingerprint(row.visibility, row.ownerEmail, {
409
+ name: row.name,
410
+ description: row.description,
411
+ question: row.question,
412
+ instructions: row.instructions,
413
+ dataSources: row.dataSources,
414
+ resultMarkdown: row.resultMarkdown,
415
+ resultData: row.resultData,
416
+ });
417
+ const group = groups.get(key) ?? [];
418
+ group.push(row);
419
+ groups.set(key, group);
420
+ }
421
+ return Array.from(groups.values()).filter((group) => group.length > 1);
422
+ }
423
+
424
+ function duplicateExtensionGroups(
425
+ rows: ExtensionSource[],
426
+ ): ExtensionSource[][] {
427
+ const groups = new Map<string, ExtensionSource[]>();
428
+ for (const row of rows) {
429
+ if (row.archivedAt) continue;
430
+ const key = scopedFingerprint(row.visibility, row.ownerEmail, {
431
+ name: row.name,
432
+ description: row.description,
433
+ content: row.content,
434
+ icon: row.icon,
435
+ });
436
+ const group = groups.get(key) ?? [];
437
+ group.push(row);
438
+ groups.set(key, group);
439
+ }
440
+ return Array.from(groups.values()).filter((group) => group.length > 1);
441
+ }
442
+
443
+ function oldest<T extends { createdAt: string; id: string }>(rows: T[]): T {
444
+ return [...rows].sort(
445
+ (a, b) =>
446
+ a.createdAt.localeCompare(b.createdAt) || a.id.localeCompare(b.id),
447
+ )[0];
448
+ }
449
+
450
+ function escapeHtml(value: string): string {
451
+ return value.replace(/[&<>"']/g, (character) => {
452
+ switch (character) {
453
+ case "&":
454
+ return "&amp;";
455
+ case "<":
456
+ return "&lt;";
457
+ case ">":
458
+ return "&gt;";
459
+ case '"':
460
+ return "&quot;";
461
+ default:
462
+ return "&#39;";
463
+ }
464
+ });
465
+ }
466
+
467
+ function analysisExtensionContent(analysis: AnalysisSource): string {
468
+ const sourceList = analysis.dataSources.length
469
+ ? `<p><strong>Data sources:</strong> ${escapeHtml(analysis.dataSources.join(", "))}</p>`
470
+ : "";
471
+ const question = analysis.question
472
+ ? `<p><strong>Question:</strong> ${escapeHtml(analysis.question)}</p>`
473
+ : "";
474
+ const description = analysis.description
475
+ ? `<p>${escapeHtml(analysis.description)}</p>`
476
+ : "";
477
+ const result = analysis.resultMarkdown || "No saved result.";
478
+ return `<article style="font-family:system-ui,sans-serif;color:inherit;line-height:1.5;padding:20px;max-width:900px;margin:auto"><h1 style="font-size:20px;margin:0 0 12px">${escapeHtml(analysis.name)}</h1>${description}${question}${sourceList}<pre style="white-space:pre-wrap;font:inherit;margin:20px 0 0">${escapeHtml(result)}</pre></article>`;
479
+ }
480
+
481
+ function migratedDashboardConfig(
482
+ name: string,
483
+ description: string,
484
+ extensionId: string,
485
+ sourceType: SourceType,
486
+ sourceId: string,
487
+ ): Record<string, unknown> {
488
+ return {
489
+ name,
490
+ description,
491
+ migration: { sourceType, sourceId },
492
+ panels: [
493
+ {
494
+ id: `${sourceType}-content`,
495
+ title: name,
496
+ sql: "",
497
+ source: "first-party",
498
+ chartType: "extension",
499
+ width: 1,
500
+ config: { extensionId },
501
+ },
502
+ ],
503
+ };
504
+ }
505
+
506
+ function sourceMigrationKey(config: Record<string, unknown>): string | null {
507
+ const migration = config.migration;
508
+ if (!migration || typeof migration !== "object") return null;
509
+ const source = migration as Record<string, unknown>;
510
+ if (
511
+ (source.sourceType === "analysis" || source.sourceType === "extension") &&
512
+ typeof source.sourceId === "string"
513
+ ) {
514
+ return `${source.sourceType}:${source.sourceId}`;
515
+ }
516
+ return null;
517
+ }
518
+
519
+ function collectExtensionIds(
520
+ value: unknown,
521
+ output = new Set<string>(),
522
+ ): Set<string> {
523
+ if (Array.isArray(value)) {
524
+ for (const item of value) collectExtensionIds(item, output);
525
+ return output;
526
+ }
527
+ if (!value || typeof value !== "object") return output;
528
+ for (const [key, nested] of Object.entries(
529
+ value as Record<string, unknown>,
530
+ )) {
531
+ if (key === "extensionId" && typeof nested === "string") {
532
+ output.add(nested);
533
+ }
534
+ collectExtensionIds(nested, output);
535
+ }
536
+ return output;
537
+ }
538
+
539
+ function remapExtensionIds(
540
+ value: unknown,
541
+ replacements: Map<string, string>,
542
+ ): { value: unknown; changed: boolean } {
543
+ if (Array.isArray(value)) {
544
+ let changed = false;
545
+ const next = value.map((item) => {
546
+ const result = remapExtensionIds(item, replacements);
547
+ changed ||= result.changed;
548
+ return result.value;
549
+ });
550
+ return { value: next, changed };
551
+ }
552
+ if (!value || typeof value !== "object") return { value, changed: false };
553
+ let changed = false;
554
+ const next: Record<string, unknown> = {};
555
+ for (const [key, nested] of Object.entries(
556
+ value as Record<string, unknown>,
557
+ )) {
558
+ if (key === "extensionId" && typeof nested === "string") {
559
+ const replacement = replacements.get(nested);
560
+ if (replacement) {
561
+ next[key] = replacement;
562
+ changed = true;
563
+ continue;
564
+ }
565
+ }
566
+ const result = remapExtensionIds(nested, replacements);
567
+ next[key] = result.value;
568
+ changed ||= result.changed;
569
+ }
570
+ return { value: next, changed };
571
+ }
572
+
573
+ async function copySharesBatch(
574
+ tx: any,
575
+ sourceTable: any,
576
+ targetTable: any,
577
+ pairs: Array<{ sourceId: string; targetId: string }>,
578
+ runId: string,
579
+ ): Promise<void> {
580
+ if (pairs.length === 0) return;
581
+ const sourceIds = [...new Set(pairs.map((pair) => pair.sourceId))];
582
+ const targetIds = [...new Set(pairs.map((pair) => pair.targetId))];
583
+ const targetBySource = new Map(
584
+ pairs.map((pair) => [pair.sourceId, pair.targetId]),
585
+ );
586
+ const [sourceRows, targetRows] = await Promise.all([
587
+ tx
588
+ .select()
589
+ .from(sourceTable)
590
+ .where(inArray(sourceTable.resourceId, sourceIds)),
591
+ tx
592
+ .select()
593
+ .from(targetTable)
594
+ .where(inArray(targetTable.resourceId, targetIds)),
595
+ ]);
596
+ const existing = new Set(
597
+ targetRows.map(
598
+ (row: any) =>
599
+ `${row.resourceId}:${row.principalType}:${row.principalId}:${row.role}`,
600
+ ),
601
+ );
602
+ const values = sourceRows
603
+ .filter((row: any) => {
604
+ const targetId = targetBySource.get(row.resourceId);
605
+ if (!targetId) return false;
606
+ const key = `${targetId}:${row.principalType}:${row.principalId}:${row.role}`;
607
+ if (existing.has(key)) return false;
608
+ existing.add(key);
609
+ return true;
610
+ })
611
+ .map((row: any) => ({
612
+ resourceId: targetBySource.get(row.resourceId),
613
+ id: `${runId}-${randomUUID()}`,
614
+ principalType: row.principalType,
615
+ principalId: row.principalId,
616
+ role: row.role,
617
+ createdBy: row.createdBy,
618
+ createdAt: row.createdAt,
619
+ }));
620
+ if (values.length > 0) await tx.insert(targetTable).values(values);
621
+ }
622
+
623
+ function summaryTemplate(
624
+ ctx: AnalyticsArtifactMigrationContext,
625
+ dryRun: boolean,
626
+ ): AnalyticsArtifactMigrationSummary {
627
+ return {
628
+ dryRun,
629
+ orgId: ctx.orgId,
630
+ legacyDashboards: 0,
631
+ legacyAnalyses: 0,
632
+ dashboardsMaterialized: 0,
633
+ analysesMaterialized: 0,
634
+ dashboardsCreated: 0,
635
+ analysisDashboardsCreated: 0,
636
+ extensionDashboardsCreated: 0,
637
+ duplicateDashboardsArchived: 0,
638
+ duplicateAnalysesHidden: 0,
639
+ duplicateExtensionsArchived: 0,
640
+ analysesHidden: 0,
641
+ extensionsHidden: 0,
642
+ legacySettingsDeleted: 0,
643
+ dashboardReferencesRewritten: 0,
644
+ skipped: [],
645
+ };
646
+ }
647
+
648
+ export async function migrateAnalyticsArtifacts(
649
+ ctx: AnalyticsArtifactMigrationContext,
650
+ options: { dryRun: boolean },
651
+ ): Promise<AnalyticsArtifactMigrationSummary> {
652
+ const state = await readMigrationState(ctx);
653
+ const summary = summaryTemplate(ctx, options.dryRun);
654
+ summary.legacyDashboards = state.legacyDashboardKeys.length;
655
+ summary.legacyAnalyses = state.legacyAnalysisKeys.length;
656
+ const legacyDashboardIds = new Set(
657
+ state.legacyDashboardKeys.map((key) =>
658
+ key.startsWith("sql-dashboard-")
659
+ ? key.slice("sql-dashboard-".length)
660
+ : key.slice("dashboard-".length),
661
+ ),
662
+ );
663
+ const legacyAnalysisIds = new Set(
664
+ state.legacyAnalysisKeys.map((key) => key.slice("adhoc-analysis-".length)),
665
+ );
666
+ summary.dashboardsMaterialized = state.dashboards.filter((row) =>
667
+ legacyDashboardIds.has(row.id),
668
+ ).length;
669
+ summary.analysesMaterialized = state.analyses.filter((row) =>
670
+ legacyAnalysisIds.has(row.id),
671
+ ).length;
672
+
673
+ const dashboardDuplicates = duplicateDashboardGroups(state.dashboards);
674
+ const analysisDuplicates = duplicateAnalysisGroups(state.analyses);
675
+ const extensionDuplicates = duplicateExtensionGroups(state.extensions);
676
+ const duplicateDashboardMap = new Map<string, string>();
677
+ const duplicateAnalysisMap = new Map<string, string>();
678
+ const duplicateExtensionMap = new Map<string, string>();
679
+
680
+ for (const group of dashboardDuplicates) {
681
+ const canonical = oldest(group);
682
+ for (const row of group) {
683
+ if (row.id !== canonical.id)
684
+ duplicateDashboardMap.set(row.id, canonical.id);
685
+ }
686
+ }
687
+ for (const group of analysisDuplicates) {
688
+ const canonical = oldest(group);
689
+ for (const row of group) {
690
+ if (row.id !== canonical.id)
691
+ duplicateAnalysisMap.set(row.id, canonical.id);
692
+ }
693
+ }
694
+ for (const group of extensionDuplicates) {
695
+ const canonical = oldest(group);
696
+ for (const row of group) {
697
+ if (row.id !== canonical.id && !state.extensionDataIds.has(row.id)) {
698
+ duplicateExtensionMap.set(row.id, canonical.id);
699
+ }
700
+ }
701
+ }
702
+
703
+ if (options.dryRun) {
704
+ summary.duplicateDashboardsArchived = duplicateDashboardMap.size;
705
+ summary.duplicateAnalysesHidden = duplicateAnalysisMap.size;
706
+ summary.duplicateExtensionsArchived = duplicateExtensionMap.size;
707
+ const existingMigrationKeys = new Set(
708
+ state.dashboards
709
+ .map((dashboard) => sourceMigrationKey(dashboard.config))
710
+ .filter((key): key is string => Boolean(key)),
711
+ );
712
+ summary.analysisDashboardsCreated = state.analyses.filter(
713
+ (row) =>
714
+ !duplicateAnalysisMap.has(row.id) &&
715
+ !existingMigrationKeys.has(`analysis:${row.id}`),
716
+ ).length;
717
+ const referencedExtensionIds = state.dashboards.reduce(
718
+ (ids, dashboard) => collectExtensionIds(dashboard.config, ids),
719
+ new Set<string>(),
720
+ );
721
+ summary.extensionDashboardsCreated = state.extensions.filter(
722
+ (row) =>
723
+ !row.archivedAt &&
724
+ !duplicateExtensionMap.has(row.id) &&
725
+ !existingMigrationKeys.has(`extension:${row.id}`) &&
726
+ !referencedExtensionIds.has(row.id),
727
+ ).length;
728
+ summary.dashboardsCreated =
729
+ summary.analysisDashboardsCreated + summary.extensionDashboardsCreated;
730
+ return summary;
731
+ }
732
+
733
+ const db = getDb() as any;
734
+ const runId = `artifact-migration-${Date.now()}`;
735
+ const now = nowIso();
736
+ const publicAnalyses = state.analyses.filter(
737
+ (analysis) => analysis.visibility === "public",
738
+ );
739
+ const publicExtensions = state.extensions.filter(
740
+ (extension) => extension.visibility === "public",
741
+ );
742
+ if (publicAnalyses.length > 0 || publicExtensions.length > 0) {
743
+ const blocked = [
744
+ ...publicAnalyses.map((analysis) => `analysis:${analysis.id}`),
745
+ ...publicExtensions.map((extension) => `extension:${extension.id}`),
746
+ ];
747
+ throw new Error(
748
+ `Refusing to migrate public analytics artifacts because embedded extensions cannot be public: ${blocked.join(", ")}. Make those artifacts organization-visible first, then retry.`,
749
+ );
750
+ }
751
+ await db.transaction(async (tx: any) => {
752
+ for (const row of state.dashboards) {
753
+ await tx
754
+ .insert(schema.dashboards)
755
+ .values({
756
+ id: row.id,
757
+ kind: row.kind,
758
+ title: row.title,
759
+ config: JSON.stringify(row.config),
760
+ ownerEmail: row.ownerEmail,
761
+ orgId: row.orgId,
762
+ visibility: row.visibility,
763
+ createdAt: row.createdAt,
764
+ updatedAt: row.updatedAt,
765
+ updatedBy: ctx.userEmail,
766
+ archivedAt: row.archivedAt,
767
+ hiddenAt: row.hiddenAt,
768
+ hiddenBy: null,
769
+ })
770
+ .onConflictDoNothing();
771
+ }
772
+ for (const row of state.analyses) {
773
+ await tx
774
+ .insert(schema.analyses)
775
+ .values({
776
+ id: row.id,
777
+ name: row.name,
778
+ description: row.description,
779
+ question: row.question,
780
+ instructions: row.instructions,
781
+ dataSources: JSON.stringify(row.dataSources),
782
+ resultMarkdown: row.resultMarkdown,
783
+ resultData: row.resultData ? JSON.stringify(row.resultData) : null,
784
+ author: row.author,
785
+ ownerEmail: row.ownerEmail,
786
+ orgId: row.orgId,
787
+ visibility: row.visibility,
788
+ createdAt: row.createdAt,
789
+ updatedAt: row.updatedAt,
790
+ hiddenAt: row.hiddenAt,
791
+ hiddenBy: null,
792
+ })
793
+ .onConflictDoNothing();
794
+ }
795
+
796
+ for (const [duplicateId, canonicalId] of duplicateDashboardMap) {
797
+ await copySharesBatch(
798
+ tx,
799
+ schema.dashboardShares,
800
+ schema.dashboardShares,
801
+ [{ sourceId: duplicateId, targetId: canonicalId }],
802
+ runId,
803
+ );
804
+ await tx
805
+ .update(schema.dashboardViews)
806
+ .set({ dashboardId: canonicalId })
807
+ .where(eq(schema.dashboardViews.dashboardId, duplicateId));
808
+ await tx
809
+ .update(schema.dashboardReportSubscriptions)
810
+ .set({ dashboardId: canonicalId, updatedAt: now })
811
+ .where(
812
+ eq(schema.dashboardReportSubscriptions.dashboardId, duplicateId),
813
+ );
814
+ await tx
815
+ .update(schema.dashboards)
816
+ .set({ archivedAt: now, updatedAt: now, updatedBy: ctx.userEmail })
817
+ .where(eq(schema.dashboards.id, duplicateId));
818
+ summary.duplicateDashboardsArchived += 1;
819
+ }
820
+
821
+ for (const [duplicateId, canonicalId] of duplicateAnalysisMap) {
822
+ await copySharesBatch(
823
+ tx,
824
+ schema.analysisShares,
825
+ schema.analysisShares,
826
+ [{ sourceId: duplicateId, targetId: canonicalId }],
827
+ runId,
828
+ );
829
+ await tx
830
+ .update(schema.analyses)
831
+ .set({ hiddenAt: now, hiddenBy: ctx.userEmail, updatedAt: now })
832
+ .where(eq(schema.analyses.id, duplicateId));
833
+ summary.duplicateAnalysesHidden += 1;
834
+ }
835
+
836
+ for (const [duplicateId, canonicalId] of duplicateExtensionMap) {
837
+ await copySharesBatch(
838
+ tx,
839
+ migrationExtensionShares,
840
+ migrationExtensionShares,
841
+ [{ sourceId: duplicateId, targetId: canonicalId }],
842
+ runId,
843
+ );
844
+ for (const dashboard of state.dashboards) {
845
+ const remapped = remapExtensionIds(
846
+ dashboard.config,
847
+ new Map([[duplicateId, canonicalId]]),
848
+ );
849
+ if (!remapped.changed) continue;
850
+ await tx
851
+ .update(schema.dashboards)
852
+ .set({
853
+ config: JSON.stringify(remapped.value),
854
+ updatedAt: now,
855
+ updatedBy: ctx.userEmail,
856
+ })
857
+ .where(eq(schema.dashboards.id, dashboard.id));
858
+ summary.dashboardReferencesRewritten += 1;
859
+ }
860
+ await tx
861
+ .update(migrationExtensions)
862
+ .set({ archivedAt: now, updatedAt: now })
863
+ .where(eq(migrationExtensions.id, duplicateId));
864
+ summary.duplicateExtensionsArchived += 1;
865
+ }
866
+
867
+ const extensionReplacements = duplicateExtensionMap;
868
+ const currentDashboards = state.dashboards.filter(
869
+ (row) => !duplicateDashboardMap.has(row.id) && !row.archivedAt,
870
+ );
871
+ const referencedExtensionIds = new Set<string>();
872
+ for (const dashboard of currentDashboards) {
873
+ const remapped = remapExtensionIds(
874
+ dashboard.config,
875
+ extensionReplacements,
876
+ );
877
+ collectExtensionIds(remapped.value, referencedExtensionIds);
878
+ }
879
+
880
+ for (const analysis of state.analyses) {
881
+ if (duplicateAnalysisMap.has(analysis.id)) continue;
882
+ const dashboardId = `dashboard-from-analysis-${analysis.id}`;
883
+ const extensionId = `extension-from-analysis-${analysis.id}`;
884
+ const dashboardExists = await tx
885
+ .select({ id: schema.dashboards.id })
886
+ .from(schema.dashboards)
887
+ .where(eq(schema.dashboards.id, dashboardId));
888
+ if (dashboardExists.length === 0) {
889
+ await tx
890
+ .insert(migrationExtensions)
891
+ .values({
892
+ id: extensionId,
893
+ name: `Dashboard content: ${analysis.name}`,
894
+ description:
895
+ "Hidden implementation block for a migrated dashboard.",
896
+ content: analysisExtensionContent(analysis),
897
+ icon: null,
898
+ createdAt: analysis.createdAt,
899
+ updatedAt: analysis.updatedAt,
900
+ archivedAt: null,
901
+ hiddenAt: now,
902
+ hiddenBy: ctx.userEmail,
903
+ ownerEmail: analysis.ownerEmail,
904
+ orgId: analysis.orgId,
905
+ visibility:
906
+ analysis.visibility === "public" ? "org" : analysis.visibility,
907
+ })
908
+ .onConflictDoNothing();
909
+ await copySharesBatch(
910
+ tx,
911
+ schema.analysisShares,
912
+ migrationExtensionShares,
913
+ [{ sourceId: analysis.id, targetId: extensionId }],
914
+ runId,
915
+ );
916
+ await tx.insert(schema.dashboards).values({
917
+ id: dashboardId,
918
+ kind: "sql",
919
+ title: analysis.name,
920
+ config: JSON.stringify(
921
+ migratedDashboardConfig(
922
+ analysis.name,
923
+ analysis.description,
924
+ extensionId,
925
+ "analysis",
926
+ analysis.id,
927
+ ),
928
+ ),
929
+ ownerEmail: analysis.ownerEmail,
930
+ orgId: analysis.orgId,
931
+ visibility: analysis.visibility,
932
+ createdAt: analysis.createdAt,
933
+ updatedAt: now,
934
+ updatedBy: ctx.userEmail,
935
+ hiddenAt: analysis.hiddenAt,
936
+ hiddenBy: analysis.hiddenAt ? ctx.userEmail : null,
937
+ });
938
+ await copySharesBatch(
939
+ tx,
940
+ schema.analysisShares,
941
+ schema.dashboardShares,
942
+ [{ sourceId: analysis.id, targetId: dashboardId }],
943
+ runId,
944
+ );
945
+ summary.analysisDashboardsCreated += 1;
946
+ summary.dashboardsCreated += 1;
947
+ }
948
+ await tx
949
+ .update(schema.analyses)
950
+ .set({
951
+ hiddenAt: analysis.hiddenAt ?? now,
952
+ hiddenBy: ctx.userEmail,
953
+ updatedAt: now,
954
+ })
955
+ .where(eq(schema.analyses.id, analysis.id));
956
+ if (!analysis.hiddenAt) summary.analysesHidden += 1;
957
+ }
958
+
959
+ for (const extension of state.extensions) {
960
+ if (extension.archivedAt || duplicateExtensionMap.has(extension.id))
961
+ continue;
962
+ if (referencedExtensionIds.has(extension.id)) {
963
+ await tx
964
+ .update(migrationExtensions)
965
+ .set({
966
+ hiddenAt: extension.hiddenAt ?? now,
967
+ hiddenBy: ctx.userEmail,
968
+ updatedAt: now,
969
+ })
970
+ .where(eq(migrationExtensions.id, extension.id));
971
+ if (!extension.hiddenAt) summary.extensionsHidden += 1;
972
+ continue;
973
+ }
974
+ const dashboardId = `dashboard-from-extension-${extension.id}`;
975
+ const existing = await tx
976
+ .select({ id: schema.dashboards.id })
977
+ .from(schema.dashboards)
978
+ .where(eq(schema.dashboards.id, dashboardId));
979
+ if (existing.length === 0) {
980
+ await tx.insert(schema.dashboards).values({
981
+ id: dashboardId,
982
+ kind: "sql",
983
+ title: extension.name,
984
+ config: JSON.stringify(
985
+ migratedDashboardConfig(
986
+ extension.name,
987
+ extension.description,
988
+ extension.id,
989
+ "extension",
990
+ extension.id,
991
+ ),
992
+ ),
993
+ ownerEmail: extension.ownerEmail,
994
+ orgId: extension.orgId,
995
+ visibility: extension.visibility,
996
+ createdAt: extension.createdAt,
997
+ updatedAt: now,
998
+ updatedBy: ctx.userEmail,
999
+ hiddenAt: extension.hiddenAt,
1000
+ hiddenBy: extension.hiddenAt ? ctx.userEmail : null,
1001
+ });
1002
+ await copySharesBatch(
1003
+ tx,
1004
+ migrationExtensionShares,
1005
+ schema.dashboardShares,
1006
+ [{ sourceId: extension.id, targetId: dashboardId }],
1007
+ runId,
1008
+ );
1009
+ summary.extensionDashboardsCreated += 1;
1010
+ summary.dashboardsCreated += 1;
1011
+ }
1012
+ await tx
1013
+ .update(migrationExtensions)
1014
+ .set({
1015
+ hiddenAt: extension.hiddenAt ?? now,
1016
+ hiddenBy: ctx.userEmail,
1017
+ updatedAt: now,
1018
+ })
1019
+ .where(eq(migrationExtensions.id, extension.id));
1020
+ if (!extension.hiddenAt) summary.extensionsHidden += 1;
1021
+ }
1022
+
1023
+ const legacyKeys = [
1024
+ ...state.legacyDashboardKeys,
1025
+ ...state.legacyAnalysisKeys,
1026
+ ].map((key) => `o:${ctx.orgId}:${key}`);
1027
+ for (let offset = 0; offset < legacyKeys.length; offset += 500) {
1028
+ const chunk = legacyKeys.slice(offset, offset + 500);
1029
+ if (chunk.length === 0) continue;
1030
+ await tx
1031
+ .delete(migrationSettings)
1032
+ .where(inArray(migrationSettings.key, chunk));
1033
+ summary.legacySettingsDeleted += chunk.length;
1034
+ }
1035
+ });
1036
+
1037
+ recordChange({
1038
+ source: "dashboards",
1039
+ type: "change",
1040
+ key: ctx.orgId,
1041
+ orgId: ctx.orgId,
1042
+ });
1043
+ recordChange({
1044
+ source: "analyses",
1045
+ type: "change",
1046
+ key: ctx.orgId,
1047
+ orgId: ctx.orgId,
1048
+ });
1049
+ recordChange({
1050
+ source: "extensions",
1051
+ type: "change",
1052
+ key: ctx.orgId,
1053
+ orgId: ctx.orgId,
1054
+ });
1055
+ return summary;
1056
+ }
1057
+
1058
+ export async function analyticsArtifactMigrationInventory(
1059
+ ctx: AnalyticsArtifactMigrationContext,
1060
+ ): Promise<AnalyticsArtifactMigrationSummary> {
1061
+ return migrateAnalyticsArtifacts(ctx, { dryRun: true });
1062
+ }