@agent-native/core 0.141.7 → 0.144.0

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 (328) hide show
  1. package/agent-native.eject.json +9 -0
  2. package/corpus/README.md +1 -1
  3. package/corpus/templates/analytics/.agents/skills/provider-api/SKILL.md +19 -0
  4. package/corpus/templates/analytics/.agents/skills/turn-into-app/SKILL.md +296 -0
  5. package/corpus/templates/analytics/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  6. package/corpus/templates/analytics/.agents/skills/turn-into-skill/SKILL.md +112 -0
  7. package/corpus/templates/analytics/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  8. package/corpus/templates/analytics/AGENTS.md +35 -38
  9. package/corpus/templates/analytics/actions/ensure-native-v2-dashboards.ts +1 -1
  10. package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +17 -22
  11. package/corpus/templates/analytics/actions/navigate.ts +4 -5
  12. package/corpus/templates/analytics/actions/provider-api-register.ts +50 -0
  13. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +44 -11
  14. package/corpus/templates/analytics/actions/test-custom-api-connection.ts +106 -0
  15. package/corpus/templates/analytics/app/components/CustomApiCard.tsx +430 -0
  16. package/corpus/templates/analytics/app/i18n-data.ts +680 -0
  17. package/corpus/templates/analytics/app/lib/native-mcp-exclusions.ts +6 -0
  18. package/corpus/templates/analytics/app/lib/sql-query.ts +33 -6
  19. package/corpus/templates/analytics/app/pages/DataSources.tsx +4 -0
  20. package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-load-reliably-during-background-data-sw.md +6 -0
  21. package/corpus/templates/analytics/changelog/2026-08-06-analytics-dashboards-remain-responsive-while-historical-data.md +6 -0
  22. package/corpus/templates/analytics/changelog/2026-08-06-analytics-query-results-now-render-as-a-table-with-csv-downl.md +6 -0
  23. package/corpus/templates/analytics/changelog/2026-08-06-connect-public-https-custom-apis-and-hand-tested-endpoints-t.md +6 -0
  24. package/corpus/templates/analytics/changelog/2026-08-06-durable-agent-worker-memory.md +6 -0
  25. package/corpus/templates/analytics/changelog/2026-08-06-live-dashboard-panels-use-the-shared-query-timeout.md +6 -0
  26. package/corpus/templates/analytics/changelog/2026-08-06-on-demand-billing-is-available-as-a-native-dashboard-with-ta.md +6 -0
  27. package/corpus/templates/analytics/changelog/2026-08-06-visible-dashboard-names-are-unique.md +6 -0
  28. package/corpus/templates/analytics/netlify.toml +7 -0
  29. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +4 -0
  30. package/corpus/templates/analytics/server/db/index.ts +27 -0
  31. package/corpus/templates/analytics/server/db/schema.ts +10 -0
  32. package/corpus/templates/analytics/server/lib/dashboards-store.ts +227 -60
  33. package/corpus/templates/analytics/server/lib/first-party-analytics-backend.ts +133 -44
  34. package/corpus/templates/analytics/server/lib/native-v2-dashboards.ts +227 -1
  35. package/corpus/templates/analytics/server/plugins/agent-chat.ts +5 -0
  36. package/corpus/templates/analytics/server/plugins/analytics-rollup-backfill-jobs.ts +1 -0
  37. package/corpus/templates/analytics/server/plugins/db.ts +52 -18
  38. package/corpus/templates/assets/.agents/skills/asset-generation/SKILL.md +28 -2
  39. package/corpus/templates/assets/.agents/skills/image-generation/SKILL.md +16 -1
  40. package/corpus/templates/assets/.agents/skills/turn-into-app/SKILL.md +296 -0
  41. package/corpus/templates/assets/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  42. package/corpus/templates/assets/.agents/skills/turn-into-skill/SKILL.md +112 -0
  43. package/corpus/templates/assets/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  44. package/corpus/templates/brain/.agents/skills/turn-into-app/SKILL.md +296 -0
  45. package/corpus/templates/brain/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  46. package/corpus/templates/brain/.agents/skills/turn-into-skill/SKILL.md +112 -0
  47. package/corpus/templates/brain/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  48. package/corpus/templates/brain/actions/list-connection-providers.ts +24 -3
  49. package/corpus/templates/brain/app/lib/dispatch-links.ts +44 -0
  50. package/corpus/templates/brain/app/routes/sources.tsx +15 -7
  51. package/corpus/templates/brain/changelog/2026-08-06-brain-provider-setup-links-open-dispatch-correctly-in-standa.md +6 -0
  52. package/corpus/templates/brain/server/plugins/agent-chat.ts +1 -0
  53. package/corpus/templates/calendar/.agents/skills/event-management/SKILL.md +26 -0
  54. package/corpus/templates/calendar/.agents/skills/turn-into-app/SKILL.md +296 -0
  55. package/corpus/templates/calendar/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  56. package/corpus/templates/calendar/.agents/skills/turn-into-skill/SKILL.md +112 -0
  57. package/corpus/templates/calendar/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  58. package/corpus/templates/calendar/AGENTS.md +11 -32
  59. package/corpus/templates/calendar/actions/update-event.ts +107 -1
  60. package/corpus/templates/calendar/app/components/calendar/EventCalendarSelect.tsx +83 -0
  61. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +57 -0
  62. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +59 -73
  63. package/corpus/templates/calendar/app/hooks/use-events.ts +4 -1
  64. package/corpus/templates/calendar/app/i18n-data.ts +20 -0
  65. package/corpus/templates/calendar/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  66. package/corpus/templates/calendar/changelog/2026-08-06-existing-events-can-move-between-connected-google-account-ca.md +6 -0
  67. package/corpus/templates/calendar/server/lib/google-calendar.ts +101 -0
  68. package/corpus/templates/calendar/server/plugins/agent-chat.ts +7 -1
  69. package/corpus/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  70. package/corpus/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  71. package/corpus/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  72. package/corpus/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  73. package/corpus/templates/clips/.agents/skills/turn-into-app/SKILL.md +296 -0
  74. package/corpus/templates/clips/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  75. package/corpus/templates/clips/.agents/skills/turn-into-skill/SKILL.md +112 -0
  76. package/corpus/templates/clips/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  77. package/corpus/templates/clips/app/components/editor/editor-layout.tsx +9 -24
  78. package/corpus/templates/clips/app/components/editor/timeline-geometry.ts +15 -0
  79. package/corpus/templates/clips/app/components/editor/waveform.tsx +4 -17
  80. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +14 -3
  81. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +28 -38
  82. package/corpus/templates/clips/app/routes/share.$shareId.tsx +21 -27
  83. package/corpus/templates/clips/changelog/2026-08-06-clips-recovers-from-stalled-recording-setup-without-requirin.md +6 -0
  84. package/corpus/templates/clips/changelog/2026-08-06-clips-starts-faster-after-a-deploy-or-idle-period-startup-no.md +6 -0
  85. package/corpus/templates/clips/changelog/2026-08-06-deleting-a-recording-returns-cleanly-to-the-library-and-fit-.md +6 -0
  86. package/corpus/templates/clips/changelog/2026-08-06-hide-clip-insights-from-viewers-without-analytics-access.md +6 -0
  87. package/corpus/templates/clips/desktop/src/app.tsx +48 -20
  88. package/corpus/templates/clips/desktop/src/lib/recorder.ts +275 -74
  89. package/corpus/templates/clips/desktop/src/lib/recording-start-guard.ts +84 -0
  90. package/corpus/templates/clips/desktop/src/overlays/toolbar.tsx +14 -4
  91. package/corpus/templates/clips/server/plugins/db.ts +214 -26
  92. package/corpus/templates/content/.agents/skills/turn-into-app/SKILL.md +296 -0
  93. package/corpus/templates/content/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  94. package/corpus/templates/content/.agents/skills/turn-into-skill/SKILL.md +112 -0
  95. package/corpus/templates/content/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  96. package/corpus/templates/crm/.agents/skills/turn-into-app/SKILL.md +296 -0
  97. package/corpus/templates/crm/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  98. package/corpus/templates/crm/.agents/skills/turn-into-skill/SKILL.md +112 -0
  99. package/corpus/templates/crm/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  100. package/corpus/templates/crm/changelog/2026-08-06-crm-starts-faster-after-a-deploy-or-idle-period-one-time-dat.md +6 -0
  101. package/corpus/templates/crm/server/plugins/db.ts +11 -8
  102. package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +36 -1
  103. package/corpus/templates/design/.agents/skills/turn-into-app/SKILL.md +296 -0
  104. package/corpus/templates/design/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  105. package/corpus/templates/design/.agents/skills/turn-into-skill/SKILL.md +112 -0
  106. package/corpus/templates/design/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  107. package/corpus/templates/design/actions/import-from-url.ts +1 -1
  108. package/corpus/templates/design/app/components/design/LayersPanel.tsx +27 -18
  109. package/corpus/templates/design/app/pages/Index.tsx +6 -2
  110. package/corpus/templates/design/changelog/2026-08-05-deleting-a-design-from-the-home-page-no-longer-leaves-the-ap.md +6 -0
  111. package/corpus/templates/design/changelog/2026-08-05-renaming-a-layer-now-focuses-its-name-field-automatically.md +6 -0
  112. package/corpus/templates/design/server/plugins/agent-chat.ts +2 -0
  113. package/corpus/templates/dispatch/.agents/skills/turn-into-app/SKILL.md +296 -0
  114. package/corpus/templates/dispatch/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  115. package/corpus/templates/dispatch/.agents/skills/turn-into-skill/SKILL.md +112 -0
  116. package/corpus/templates/dispatch/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  117. package/corpus/templates/dispatch/changelog/2026-08-06-ready-workspace-apps-stay-one-click-away-in-a-compact-dispat.md +6 -0
  118. package/corpus/templates/dispatch/changelog/2026-08-06-shared-vault-values-and-management-are-now-restricted-to-wor.md +6 -0
  119. package/corpus/templates/factory/.agents/skills/turn-into-app/SKILL.md +296 -0
  120. package/corpus/templates/factory/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  121. package/corpus/templates/factory/.agents/skills/turn-into-skill/SKILL.md +112 -0
  122. package/corpus/templates/factory/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  123. package/corpus/templates/forms/.agents/skills/turn-into-app/SKILL.md +296 -0
  124. package/corpus/templates/forms/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  125. package/corpus/templates/forms/.agents/skills/turn-into-skill/SKILL.md +112 -0
  126. package/corpus/templates/forms/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  127. package/corpus/templates/forms/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  128. package/corpus/templates/forms/server/plugins/agent-chat.ts +4 -0
  129. package/corpus/templates/macros/.agents/skills/turn-into-app/SKILL.md +296 -0
  130. package/corpus/templates/macros/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  131. package/corpus/templates/macros/.agents/skills/turn-into-skill/SKILL.md +112 -0
  132. package/corpus/templates/macros/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  133. package/corpus/templates/mail/.agents/skills/turn-into-app/SKILL.md +296 -0
  134. package/corpus/templates/mail/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  135. package/corpus/templates/mail/.agents/skills/turn-into-skill/SKILL.md +112 -0
  136. package/corpus/templates/mail/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  137. package/corpus/templates/mail/changelog/2026-08-06-delegated-requests-continue-in-the-background.md +6 -0
  138. package/corpus/templates/mail/server/plugins/agent-chat.ts +6 -0
  139. package/corpus/templates/plan/.agents/skills/turn-into-app/SKILL.md +296 -0
  140. package/corpus/templates/plan/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  141. package/corpus/templates/plan/.agents/skills/turn-into-skill/SKILL.md +112 -0
  142. package/corpus/templates/plan/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  143. package/corpus/templates/slides/.agents/skills/create-deck/SKILL.md +48 -5
  144. package/corpus/templates/slides/.agents/skills/design-systems/SKILL.md +37 -1
  145. package/corpus/templates/slides/.agents/skills/image-generation-via-a2a/SKILL.md +6 -0
  146. package/corpus/templates/slides/.agents/skills/slide-editing/SKILL.md +15 -2
  147. package/corpus/templates/slides/.agents/skills/slide-images/SKILL.md +29 -24
  148. package/corpus/templates/slides/.agents/skills/turn-into-app/SKILL.md +296 -0
  149. package/corpus/templates/slides/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  150. package/corpus/templates/slides/.agents/skills/turn-into-skill/SKILL.md +112 -0
  151. package/corpus/templates/slides/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  152. package/corpus/templates/slides/AGENTS.md +7 -9
  153. package/corpus/templates/slides/_gitignore +1 -0
  154. package/corpus/templates/slides/actions/export-pptx.ts +402 -36
  155. package/corpus/templates/slides/actions/import-file.ts +46 -85
  156. package/corpus/templates/slides/actions/import-google-slides-reference.ts +271 -12
  157. package/corpus/templates/slides/actions/import-pptx.ts +85 -60
  158. package/corpus/templates/slides/app/components/deck/DeckCard.tsx +10 -13
  159. package/corpus/templates/slides/app/components/editor/AddSlidePopover.tsx +5 -1
  160. package/corpus/templates/slides/app/components/editor/EditorToolbar.tsx +25 -12
  161. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +112 -57
  162. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +202 -0
  163. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +26 -14
  164. package/corpus/templates/slides/app/components/editor/PromptDialog.tsx +173 -0
  165. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +182 -141
  166. package/corpus/templates/slides/app/components/editor/canvas-interactions/index.ts +1 -0
  167. package/corpus/templates/slides/app/components/editor/canvas-interactions/slides-canvas-adapter.ts +18 -0
  168. package/corpus/templates/slides/app/components/editor/slide-text-targets.ts +85 -0
  169. package/corpus/templates/slides/app/components/layout/Layout.tsx +3 -0
  170. package/corpus/templates/slides/app/components/onboarding/FirstDeckOnboardingFlow.tsx +4 -0
  171. package/corpus/templates/slides/app/components/presentation/PresentationView.tsx +27 -1
  172. package/corpus/templates/slides/app/i18n/en-US.ts +1 -0
  173. package/corpus/templates/slides/app/lib/create-deck-generation.ts +1 -1
  174. package/corpus/templates/slides/app/lib/deck-preview-frame.ts +15 -0
  175. package/corpus/templates/slides/app/lib/deck-share-links.ts +9 -0
  176. package/corpus/templates/slides/app/lib/export-pptx-client.ts +89 -0
  177. package/corpus/templates/slides/app/lib/slide-animation-elements.ts +3 -0
  178. package/corpus/templates/slides/app/pages/Index.tsx +91 -1
  179. package/corpus/templates/slides/changelog/2026-08-06-clicking-the-gray-canvas-around-a-slide-clears-its-selection.md +6 -0
  180. package/corpus/templates/slides/changelog/2026-08-06-deck-thumbnails-keep-a-consistent-listing-frame-across-aspec.md +6 -0
  181. package/corpus/templates/slides/changelog/2026-08-06-deep-linked-decks-open-directly-instead-of-being-interrupted.md +6 -0
  182. package/corpus/templates/slides/changelog/2026-08-06-google-slides-exports-now-show-clear-import-steps-when-direc.md +6 -0
  183. package/corpus/templates/slides/changelog/2026-08-06-import-pdf-google-slides-and-powerpoint-decks-directly-from-.md +6 -0
  184. package/corpus/templates/slides/changelog/2026-08-06-imported-powerpoint-exports-keep-text-and-images-editable-an.md +6 -0
  185. package/corpus/templates/slides/changelog/2026-08-06-presentation-prompts-continue-adding-rendered-slides-until-t.md +6 -0
  186. package/corpus/templates/slides/changelog/2026-08-06-public-decks-now-share-read-only-presentation-links-by-defau.md +6 -0
  187. package/corpus/templates/slides/changelog/2026-08-06-slides-offers-a-direct-connect-google-button-when-a-drive-co.md +6 -0
  188. package/corpus/templates/slides/changelog/2026-08-06-slides-preserve-uploaded-pdf-layouts-accept-larger-reference.md +6 -0
  189. package/corpus/templates/slides/changelog/2026-08-06-styled-words-stay-part-of-their-text-box-instead-of-becoming.md +6 -0
  190. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +7 -20
  191. package/corpus/templates/slides/server/handlers/import/html-converter.ts +236 -13
  192. package/corpus/templates/slides/server/handlers/import/pptx-assets.ts +67 -0
  193. package/corpus/templates/slides/server/handlers/import/pptx-parser.ts +2 -0
  194. package/corpus/templates/slides/server/handlers/uploads.ts +7 -3
  195. package/corpus/templates/slides/server/lib/chat-attachments.ts +6 -3
  196. package/corpus/templates/slides/server/lib/emails.ts +7 -2
  197. package/corpus/templates/slides/server/lib/google-docs-error.ts +50 -0
  198. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +1 -0
  199. package/corpus/templates/slides/server/lib/import-asset-storage.ts +127 -0
  200. package/corpus/templates/slides/server/plugins/agent-chat.ts +4 -3
  201. package/corpus/templates/slides/server/routes/api/import-assets/[token].get.ts +70 -0
  202. package/corpus/templates/slides/shared/upload-types.ts +3 -0
  203. package/corpus/templates/tasks/.agents/skills/turn-into-app/SKILL.md +296 -0
  204. package/corpus/templates/tasks/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  205. package/corpus/templates/tasks/.agents/skills/turn-into-skill/SKILL.md +112 -0
  206. package/corpus/templates/tasks/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  207. package/dist/agent/production-agent.d.ts +17 -0
  208. package/dist/agent/production-agent.js +72 -1
  209. package/dist/cli/agent-plugin.d.ts +112 -0
  210. package/dist/cli/agent-plugin.js +1081 -0
  211. package/dist/cli/app-skill.js +39 -1
  212. package/dist/cli/code-agent-executor.d.ts +3 -0
  213. package/dist/cli/code-agent-executor.js +51 -13
  214. package/dist/cli/index.js +13 -0
  215. package/dist/cli/templates-meta.js +1 -1
  216. package/dist/client/AgentPanel.d.ts +3 -1
  217. package/dist/client/AgentPanel.js +9 -2
  218. package/dist/client/AssistantChat.d.ts +8 -0
  219. package/dist/client/AssistantChat.js +23 -8
  220. package/dist/client/NewWorkspaceAppFlow.js +1 -0
  221. package/dist/client/agent-chat-adapter.js +123 -4
  222. package/dist/client/assistant-ui-recovery.d.ts +2 -1
  223. package/dist/client/assistant-ui-recovery.js +7 -3
  224. package/dist/client/onboarding/FirstRunOnboarding.js +11 -4
  225. package/dist/client/org/TeamPage.js +1 -1
  226. package/dist/client/require-session.js +1 -1
  227. package/dist/client/resources/McpConnectionSuggestion.d.ts +1 -0
  228. package/dist/client/resources/McpConnectionSuggestion.js +27 -10
  229. package/dist/client/resources/McpIntegrationDialog.js +77 -45
  230. package/dist/client/resources/mcp-integration-catalog.d.ts +13 -1
  231. package/dist/client/resources/mcp-integration-catalog.js +255 -2
  232. package/dist/client/resources/mcp-integration-logos.js +18 -2
  233. package/dist/client/settings/AccountSettingsCard.js +2 -1
  234. package/dist/collab/routes.d.ts +1 -1
  235. package/dist/collab/struct-routes.d.ts +1 -1
  236. package/dist/db/client.d.ts +9 -0
  237. package/dist/db/client.js +51 -16
  238. package/dist/db/create-get-db.js +2 -2
  239. package/dist/db/ddl-guard.d.ts +11 -10
  240. package/dist/db/ddl-guard.js +40 -12
  241. package/dist/deploy/build.d.ts +2 -0
  242. package/dist/deploy/build.js +97 -4
  243. package/dist/deploy/workspace-deploy.js +0 -2
  244. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  245. package/dist/guards/no-env-credentials.js +9 -0
  246. package/dist/ingestion/index.d.ts +1 -1
  247. package/dist/ingestion/pptx.d.ts +68 -0
  248. package/dist/ingestion/pptx.js +660 -213
  249. package/dist/integrations/plugin.js +9 -6
  250. package/dist/localization/default-messages.d.ts +54 -0
  251. package/dist/localization/default-messages.js +57 -2
  252. package/dist/mcp/screen-memory-stdio.d.ts +7 -7
  253. package/dist/mcp-client/oauth-routes.d.ts +2 -0
  254. package/dist/mcp-client/oauth-routes.js +73 -6
  255. package/dist/notifications/routes.d.ts +1 -1
  256. package/dist/observability/routes.d.ts +3 -3
  257. package/dist/onboarding/app-profile.js +11 -0
  258. package/dist/resources/handlers.d.ts +1 -1
  259. package/dist/resources/store.js +15 -1
  260. package/dist/scripts/call-agent.js +34 -4
  261. package/dist/secrets/routes.d.ts +9 -9
  262. package/dist/server/agent-chat/action-filters-a2a.d.ts +1 -1
  263. package/dist/server/agent-chat/action-filters-a2a.js +36 -1
  264. package/dist/server/agent-chat-plugin.js +29 -8
  265. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  266. package/dist/server/auth.js +47 -11
  267. package/dist/server/better-auth-instance.js +5 -3
  268. package/dist/server/core-routes-plugin.d.ts +8 -1
  269. package/dist/server/core-routes-plugin.js +28 -3
  270. package/dist/server/credential-provider.d.ts +3 -3
  271. package/dist/server/credential-provider.js +90 -4
  272. package/dist/server/onboarding-html.d.ts +2 -0
  273. package/dist/server/onboarding-html.js +115 -29
  274. package/dist/server/self-dispatch.d.ts +5 -4
  275. package/dist/server/self-dispatch.js +25 -14
  276. package/dist/server/ssr-handler.d.ts +1 -1
  277. package/dist/server/ssr-handler.js +1 -1
  278. package/dist/server/transcribe-voice.d.ts +1 -1
  279. package/dist/shared/cache-control.d.ts +26 -2
  280. package/dist/shared/cache-control.js +43 -2
  281. package/dist/shared/password-policy.d.ts +3 -0
  282. package/dist/shared/password-policy.js +3 -0
  283. package/dist/sharing/actions/set-resource-visibility.js +16 -4
  284. package/dist/sharing/registry.d.ts +14 -0
  285. package/dist/styles/agent-native.css +6 -0
  286. package/dist/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  287. package/dist/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  288. package/dist/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  289. package/dist/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  290. package/dist/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
  291. package/dist/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  292. package/dist/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
  293. package/dist/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  294. package/dist/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
  295. package/dist/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  296. package/dist/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
  297. package/dist/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  298. package/dist/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
  299. package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
  300. package/dist/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
  301. package/dist/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  302. package/dist/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
  303. package/dist/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  304. package/dist/templates/workspace-root/AGENTS.md +7 -0
  305. package/dist/user-profile/actions/change-password.js +8 -3
  306. package/dist/user-profile/actions/set-password.js +5 -1
  307. package/dist/vite/action-types-plugin.js +59 -0
  308. package/docs/content/skills-guide.mdx +13 -0
  309. package/package.json +6 -2
  310. package/src/templates/chat/.agents/skills/turn-into-app/SKILL.md +296 -0
  311. package/src/templates/chat/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  312. package/src/templates/chat/.agents/skills/turn-into-skill/SKILL.md +112 -0
  313. package/src/templates/chat/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  314. package/src/templates/default/.agents/skills/turn-into-app/SKILL.md +296 -0
  315. package/src/templates/default/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  316. package/src/templates/default/.agents/skills/turn-into-skill/SKILL.md +112 -0
  317. package/src/templates/default/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  318. package/src/templates/headless/.agents/skills/turn-into-app/SKILL.md +296 -0
  319. package/src/templates/headless/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  320. package/src/templates/headless/.agents/skills/turn-into-skill/SKILL.md +112 -0
  321. package/src/templates/headless/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  322. package/src/templates/workspace-core/.agents/skills/adding-workspace-apps/SKILL.md +6 -0
  323. package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +51 -0
  324. package/src/templates/workspace-core/.agents/skills/turn-into-app/SKILL.md +296 -0
  325. package/src/templates/workspace-core/.agents/skills/turn-into-app/agents/openai.yaml +4 -0
  326. package/src/templates/workspace-core/.agents/skills/turn-into-skill/SKILL.md +112 -0
  327. package/src/templates/workspace-core/.agents/skills/turn-into-skill/agents/openai.yaml +4 -0
  328. package/src/templates/workspace-root/AGENTS.md +7 -0
@@ -1,8 +1,14 @@
1
1
  export const ANALYTICS_NATIVE_MCP_PRESET_EXCLUSIONS = [
2
+ "amplitude",
3
+ "apollo",
2
4
  "atlassian",
5
+ "common-room",
3
6
  "github",
7
+ "gong",
8
+ "grafana",
4
9
  "hubspot",
5
10
  "notion",
11
+ "pylon",
6
12
  "sentry",
7
13
  "slack",
8
14
  "stripe",
@@ -59,6 +59,29 @@ function createAbortError(): Error {
59
59
  return error;
60
60
  }
61
61
 
62
+ function createDeadlineSignal(
63
+ parentSignal: AbortSignal | undefined,
64
+ timeoutMs: number,
65
+ ): { signal: AbortSignal; cleanup: () => void } {
66
+ const controller = new AbortController();
67
+ const abortFromParent = () => controller.abort(parentSignal?.reason);
68
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
69
+
70
+ if (parentSignal?.aborted) {
71
+ abortFromParent();
72
+ } else {
73
+ parentSignal?.addEventListener("abort", abortFromParent, { once: true });
74
+ }
75
+
76
+ return {
77
+ signal: controller.signal,
78
+ cleanup: () => {
79
+ clearTimeout(timer);
80
+ parentSignal?.removeEventListener("abort", abortFromParent);
81
+ },
82
+ };
83
+ }
84
+
62
85
  function createSqlQueryRelease(pool: SqlQuerySlotPool): () => void {
63
86
  let released = false;
64
87
  return () => {
@@ -117,21 +140,25 @@ export async function executeSqlQuery(
117
140
  signal?: AbortSignal,
118
141
  options?: { reportScreenshot?: boolean },
119
142
  ): Promise<SqlQueryResult> {
120
- const release = await acquireSqlQuerySlot(source, signal);
143
+ const deadline = createDeadlineSignal(
144
+ signal,
145
+ DASHBOARD_REPORT_ACTION_TIMEOUT_MS,
146
+ );
147
+ let release: (() => void) | undefined;
121
148
  let data: DashboardPanelQueryResponse;
122
149
  try {
150
+ release = await acquireSqlQuerySlot(source, deadline.signal);
123
151
  data = await callAction<DashboardPanelQueryResponse>(
124
152
  "query-dashboard-panel",
125
153
  { query: sql, source },
126
154
  {
127
- signal,
128
- ...(options?.reportScreenshot
129
- ? { timeoutMs: DASHBOARD_REPORT_ACTION_TIMEOUT_MS }
130
- : {}),
155
+ signal: deadline.signal,
156
+ timeoutMs: DASHBOARD_REPORT_ACTION_TIMEOUT_MS,
131
157
  },
132
158
  );
133
159
  } finally {
134
- release();
160
+ release?.();
161
+ deadline.cleanup();
135
162
  }
136
163
 
137
164
  if (typeof data?.error === "string") {
@@ -94,6 +94,8 @@ import {
94
94
  type WalkthroughStep,
95
95
  } from "@/lib/data-sources";
96
96
 
97
+ import { CustomApiCard } from "../components/CustomApiCard";
98
+
97
99
  interface AnalyticsPublicKeyRow {
98
100
  id: string;
99
101
  name: string;
@@ -2099,6 +2101,8 @@ export default function DataSources() {
2099
2101
 
2100
2102
  <GoogleSheetsExportCard statusData={statusData} />
2101
2103
 
2104
+ <CustomApiCard />
2105
+
2102
2106
  {unknownFocusedSourceId && (
2103
2107
  <div
2104
2108
  role="status"
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ Analytics dashboards load reliably during background data sweeps
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ Analytics dashboards remain responsive while historical data processing runs.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ Analytics query results now render as a table with CSV download.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ Connect public HTTPS Custom APIs and hand tested endpoints to refreshable data programs
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ Durable Analytics agent runs now have the maximum supported Netlify function memory allocation to reduce worker deaths during long analyses.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ Live dashboard panels now use the shared query timeout so slower charts fail more predictably.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: added
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ On-demand billing is available as a native dashboard with tabs and built-in analytics panels.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-06
4
+ ---
5
+
6
+ Analytics rejects duplicate names when creating or renaming visible dashboards.
@@ -25,6 +25,13 @@ AGENT_CHAT_DURABLE_BACKGROUND = "true"
25
25
  [functions."*"]
26
26
  timeout = 75
27
27
 
28
+ # Recent durable Analytics runs recorded failed-worker RSS above 4 GB while
29
+ # Netlify Functions default to 1 GB. Give the long-running agent worker the
30
+ # highest supported allocation so memory pressure does not masquerade as a
31
+ # stale heartbeat; query and connection bounds remain the primary safeguards.
32
+ [functions.server-agent-background]
33
+ memory = "4gb"
34
+
28
35
  [functions.dashboard-report-sweep-background]
29
36
  timeout = 300
30
37
  memory = "2gb"
@@ -83,6 +83,7 @@ function emitBackgroundWorker(token: string) {
83
83
  rmSync(path.join(dest, "server.mjs"), { force: true });
84
84
 
85
85
  const source = `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
86
+ globalThis.__AGENT_NATIVE_LOW_CONNECTION_BACKGROUND_RUNTIME__ = true;
86
87
  globalThis.__AGENT_NATIVE_DASHBOARD_REPORT_SCHEDULED_RUNTIME__ = true;
87
88
 
88
89
  const CRON_TOKEN = ${JSON.stringify(token)};
@@ -227,6 +228,7 @@ function emitAlertBackgroundWorker(token: string) {
227
228
  rmSync(path.join(dest, "server.mjs"), { force: true });
228
229
 
229
230
  const source = `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
231
+ globalThis.__AGENT_NATIVE_LOW_CONNECTION_BACKGROUND_RUNTIME__ = true;
230
232
  globalThis.__AGENT_NATIVE_ANALYTICS_ALERT_SCHEDULED_RUNTIME__ = true;
231
233
 
232
234
  const CRON_TOKEN = ${JSON.stringify(token)};
@@ -344,6 +346,7 @@ function emitUptimeBackgroundWorker(token: string) {
344
346
  rmSync(path.join(dest, "server.mjs"), { force: true });
345
347
 
346
348
  const source = `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
349
+ globalThis.__AGENT_NATIVE_LOW_CONNECTION_BACKGROUND_RUNTIME__ = true;
347
350
  globalThis.__AGENT_NATIVE_UPTIME_MONITOR_SCHEDULED_RUNTIME__ = true;
348
351
 
349
352
  const CRON_TOKEN = ${JSON.stringify(token)};
@@ -443,6 +446,7 @@ function emitRollupBackgroundWorker(token: string) {
443
446
  rmSync(path.join(dest, "server.mjs"), { force: true });
444
447
 
445
448
  const source = `globalThis.__AGENT_NATIVE_BACKGROUND_RUNTIME__ = true;
449
+ globalThis.__AGENT_NATIVE_LOW_CONNECTION_BACKGROUND_RUNTIME__ = true;
446
450
  globalThis.__AGENT_NATIVE_ANALYTICS_ROLLUP_BACKFILL_SCHEDULED_RUNTIME__ = true;
447
451
 
448
452
  const CRON_TOKEN = ${JSON.stringify(token)};
@@ -29,6 +29,33 @@ registerShareableResource({
29
29
  resourceKind: ANALYTICS_DASHBOARD_AGENT_RESOURCE_KIND,
30
30
  getContextPath: () => ANALYTICS_DASHBOARD_AGENT_CONTEXT_ENDPOINT,
31
31
  },
32
+ persistVisibilityChange: async ({
33
+ resource,
34
+ resourceId,
35
+ visibility,
36
+ update,
37
+ userEmail,
38
+ orgId,
39
+ }) => {
40
+ const { persistDashboardVisibilityChange } =
41
+ await import("../lib/dashboards-store.js");
42
+ await persistDashboardVisibilityChange(
43
+ {
44
+ id: resourceId,
45
+ title: resource.title,
46
+ orgId: resource.orgId ?? null,
47
+ },
48
+ visibility,
49
+ update,
50
+ {
51
+ email: userEmail ?? resource.ownerEmail,
52
+ orgId:
53
+ typeof update.orgId === "string"
54
+ ? update.orgId
55
+ : (orgId ?? resource.orgId ?? null),
56
+ },
57
+ );
58
+ },
32
59
  getDb,
33
60
  });
34
61
 
@@ -43,6 +43,16 @@ export const dashboards = table("dashboards", {
43
43
  ...ownableColumns(),
44
44
  });
45
45
 
46
+ /**
47
+ * Persistent per-name rows serialize concurrent create/rename checks. The row
48
+ * is deliberately independent of dashboard visibility: callers acquire the
49
+ * same lock before applying their access-scoped collision check.
50
+ */
51
+ export const dashboardNameLocks = table("dashboard_name_locks", {
52
+ nameKey: text("name_key").primaryKey(),
53
+ createdAt: text("created_at").notNull().default(now()),
54
+ });
55
+
46
56
  export const dashboardShares = createSharesTable("dashboard_shares");
47
57
 
48
58
  /**
@@ -138,6 +138,10 @@ const ANALYSIS_PREFIX = "adhoc-analysis-";
138
138
  const DASHBOARD_REVISION_LIMIT = 50;
139
139
  const ANALYSIS_REVISION_LIMIT = 30;
140
140
 
141
+ export function normalizeDashboardName(value: string): string {
142
+ return value.trim().replace(/\s+/g, " ").toLowerCase();
143
+ }
144
+
141
145
  function nowIso(): string {
142
146
  return new Date().toISOString();
143
147
  }
@@ -532,9 +536,11 @@ export async function listDashboardSummaries(
532
536
  archived?: DashboardArchiveFilter;
533
537
  hidden?: DashboardHiddenFilter;
534
538
  includeCatalogMetadata?: boolean;
539
+ legacyScan?: "best-effort" | "strict";
535
540
  },
541
+ dbOverride?: any,
536
542
  ): Promise<DashboardSummaryRecord[]> {
537
- const db = getDb() as any;
543
+ const db = (dbOverride ?? getDb()) as any;
538
544
  const archived = filter?.archived ?? "active";
539
545
  const hidden = filter?.hidden ?? "visible";
540
546
  const includeCatalogMetadata = filter?.includeCatalogMetadata === true;
@@ -663,12 +669,85 @@ export async function listDashboardSummaries(
663
669
  hiddenBy: null,
664
670
  });
665
671
  }
666
- } catch {
672
+ } catch (error) {
673
+ if (filter?.legacyScan === "strict") throw error;
667
674
  // Legacy scan is best-effort.
668
675
  }
669
676
  return out;
670
677
  }
671
678
 
679
+ /**
680
+ * Reject a name that would collide with another dashboard visible to the
681
+ * caller. Archived and hidden dashboards are intentionally excluded because
682
+ * they are not part of the navigation surface this protects.
683
+ */
684
+ export async function assertDashboardNameIsAvailable(
685
+ name: string,
686
+ ctx: AccessCtx,
687
+ excludeId?: string,
688
+ dbOverride?: any,
689
+ ): Promise<void> {
690
+ const normalized = normalizeDashboardName(name);
691
+ if (!normalized) return;
692
+ const conflict = (
693
+ await listDashboardSummaries(ctx, { legacyScan: "strict" }, dbOverride)
694
+ ).find(
695
+ (dashboard) =>
696
+ dashboard.id !== excludeId &&
697
+ normalizeDashboardName(dashboard.name) === normalized,
698
+ );
699
+ if (!conflict) return;
700
+ throw new Error(
701
+ `Dashboard name "${name.trim()}" is already used by visible dashboard "${conflict.name}". Choose a different name.`,
702
+ );
703
+ }
704
+
705
+ async function lockDashboardNames(db: any, names: string[]): Promise<void> {
706
+ const nameKeys = [
707
+ ...new Set(names.map(normalizeDashboardName).filter(Boolean)),
708
+ ].sort();
709
+ for (const nameKey of nameKeys) {
710
+ await db
711
+ .insert(schema.dashboardNameLocks)
712
+ .values({ nameKey, createdAt: nowIso() })
713
+ .onConflictDoNothing();
714
+ if (isPostgres()) {
715
+ await db.execute(
716
+ sql`SELECT name_key FROM dashboard_name_locks WHERE name_key = ${nameKey} FOR UPDATE`,
717
+ );
718
+ }
719
+ }
720
+ }
721
+
722
+ /**
723
+ * Persist a visibility change while holding the same name lock used by create
724
+ * and rename. Sharing a private duplicate must not bypass the visible-name
725
+ * invariant by changing visibility after the dashboard was created.
726
+ */
727
+ export async function persistDashboardVisibilityChange(
728
+ resource: Pick<DashboardRecord, "id" | "title" | "orgId">,
729
+ visibility: DashboardRecord["visibility"],
730
+ update: Record<string, unknown>,
731
+ ctx: AccessCtx,
732
+ ): Promise<void> {
733
+ const db = getDb() as any;
734
+ await db.transaction(async (tx: any) => {
735
+ if (visibility !== "private") {
736
+ await lockDashboardNames(tx, [resource.title]);
737
+ await assertDashboardNameIsAvailable(
738
+ resource.title,
739
+ ctx,
740
+ resource.id,
741
+ tx,
742
+ );
743
+ }
744
+ await tx
745
+ .update(schema.dashboards)
746
+ .set(update)
747
+ .where(eq(schema.dashboards.id, resource.id));
748
+ });
749
+ }
750
+
672
751
  async function pruneDashboardRevisions(
673
752
  db: any,
674
753
  dashboardId: string,
@@ -746,58 +825,80 @@ export async function upsertDashboard(
746
825
  userEmail: ctx.email,
747
826
  orgId: ctx.orgId ?? undefined,
748
827
  });
749
- const changed =
750
- existing.kind !== kind ||
751
- existing.title !== title ||
752
- JSON.stringify(existing.config) !== configJson;
753
- const setValues = {
754
- kind,
755
- title,
756
- config: configJson,
757
- updatedAt: nowIso(),
758
- updatedBy: ctx.email,
759
- };
760
- if (expectedUpdatedAt !== undefined) {
761
- // Fenced write. Snapshot the revision only after we know this exact
762
- // write actually landed — otherwise a lost race would record a
763
- // revision for a save that never happened.
764
- const updateResult = await db
765
- .update(schema.dashboards)
766
- .set(setValues)
767
- .where(
768
- and(
769
- eq(schema.dashboards.id, id),
770
- eq(schema.dashboards.updatedAt, expectedUpdatedAt),
771
- ),
772
- );
773
- const affected = affectedRowCount(updateResult);
774
- if (affected === undefined) {
775
- throw new Error(
776
- "The database driver did not report an affected-row count for the fenced dashboard update.",
777
- );
778
- }
779
- if (affected === 0) {
780
- throw new DashboardConflictError(id);
828
+ }
829
+ const nameChanged =
830
+ !existing ||
831
+ normalizeDashboardName(existing.title) !== normalizeDashboardName(title);
832
+ if (nameChanged) {
833
+ await assertDashboardNameIsAvailable(title, ctx, existing?.id);
834
+ }
835
+ const persist = async (writeDb: any): Promise<void> => {
836
+ if (existing) {
837
+ const changed =
838
+ existing.kind !== kind ||
839
+ existing.title !== title ||
840
+ JSON.stringify(existing.config) !== configJson;
841
+ const setValues = {
842
+ kind,
843
+ title,
844
+ config: configJson,
845
+ updatedAt: nowIso(),
846
+ updatedBy: ctx.email,
847
+ };
848
+ if (expectedUpdatedAt !== undefined) {
849
+ // Fenced write. Snapshot the revision only after we know this exact
850
+ // write actually landed — otherwise a lost race would record a
851
+ // revision for a save that never happened.
852
+ const updateResult = await writeDb
853
+ .update(schema.dashboards)
854
+ .set(setValues)
855
+ .where(
856
+ and(
857
+ eq(schema.dashboards.id, id),
858
+ eq(schema.dashboards.updatedAt, expectedUpdatedAt),
859
+ ),
860
+ );
861
+ const affected = affectedRowCount(updateResult);
862
+ if (affected === undefined) {
863
+ throw new Error(
864
+ "The database driver did not report an affected-row count for the fenced dashboard update.",
865
+ );
866
+ }
867
+ if (affected === 0) {
868
+ throw new DashboardConflictError(id);
869
+ }
870
+ if (changed) await snapshotDashboardRevision(writeDb, existing, ctx);
871
+ } else {
872
+ if (changed) await snapshotDashboardRevision(writeDb, existing, ctx);
873
+ await writeDb
874
+ .update(schema.dashboards)
875
+ .set(setValues)
876
+ .where(eq(schema.dashboards.id, id));
781
877
  }
782
- if (changed) await snapshotDashboardRevision(db, existing, ctx);
783
878
  } else {
784
- if (changed) await snapshotDashboardRevision(db, existing, ctx);
785
- await db
786
- .update(schema.dashboards)
787
- .set(setValues)
788
- .where(eq(schema.dashboards.id, id));
879
+ await writeDb.insert(schema.dashboards).values({
880
+ id,
881
+ kind,
882
+ title,
883
+ config: JSON.stringify(config),
884
+ ownerEmail: ctx.email,
885
+ orgId: ctx.orgId,
886
+ visibility: "private",
887
+ updatedBy: ctx.email,
888
+ });
789
889
  }
790
- } else {
791
- await db.insert(schema.dashboards).values({
792
- id,
793
- kind,
794
- title,
795
- config: JSON.stringify(config),
796
- ownerEmail: ctx.email,
797
- orgId: ctx.orgId,
798
- visibility: "private",
799
- updatedBy: ctx.email,
890
+ };
891
+ if (nameChanged) {
892
+ await db.transaction(async (tx: any) => {
893
+ await lockDashboardNames(tx, [
894
+ title,
895
+ ...(existing ? [existing.title] : []),
896
+ ]);
897
+ await assertDashboardNameIsAvailable(title, ctx, existing?.id, tx);
898
+ await persist(tx);
800
899
  });
900
+ } else {
901
+ await persist(db);
801
902
  }
802
903
  const [row] = await db
803
904
  .select()
@@ -932,7 +1033,23 @@ export async function restoreDashboardRevision(
932
1033
  if (!revisionRow) return null;
933
1034
  const revision = rowToDashboardRevision(revisionRow);
934
1035
  const updatedAt = nowIso();
1036
+ const nameChanged =
1037
+ normalizeDashboardName(existing.title) !==
1038
+ normalizeDashboardName(revision.title);
1039
+ const becomesVisible = !existing.archivedAt && !existing.hiddenAt;
1040
+ if (nameChanged && becomesVisible) {
1041
+ await assertDashboardNameIsAvailable(revision.title, ctx, dashboardId);
1042
+ }
935
1043
  const restored = await db.transaction(async (tx: any) => {
1044
+ if (nameChanged && becomesVisible) {
1045
+ await lockDashboardNames(tx, [existing.title, revision.title]);
1046
+ await assertDashboardNameIsAvailable(
1047
+ revision.title,
1048
+ ctx,
1049
+ dashboardId,
1050
+ tx,
1051
+ );
1052
+ }
936
1053
  const updateResult = await tx
937
1054
  .update(schema.dashboards)
938
1055
  .set({
@@ -1037,11 +1154,25 @@ export async function unarchiveDashboard(
1037
1154
  userEmail: ctx.email,
1038
1155
  orgId: ctx.orgId ?? undefined,
1039
1156
  });
1157
+ if (!existing.hiddenAt) {
1158
+ await assertDashboardNameIsAvailable(existing.title, ctx, id);
1159
+ }
1040
1160
  const db = getDb() as any;
1041
- await db
1042
- .update(schema.dashboards)
1043
- .set({ archivedAt: null, updatedAt: nowIso(), updatedBy: ctx.email })
1044
- .where(eq(schema.dashboards.id, id));
1161
+ const persist = async (writeDb: any) => {
1162
+ await writeDb
1163
+ .update(schema.dashboards)
1164
+ .set({ archivedAt: null, updatedAt: nowIso(), updatedBy: ctx.email })
1165
+ .where(eq(schema.dashboards.id, id));
1166
+ };
1167
+ if (!existing.hiddenAt) {
1168
+ await db.transaction(async (tx: any) => {
1169
+ await lockDashboardNames(tx, [existing.title]);
1170
+ await assertDashboardNameIsAvailable(existing.title, ctx, id, tx);
1171
+ await persist(tx);
1172
+ });
1173
+ } else {
1174
+ await persist(db);
1175
+ }
1045
1176
  const [row] = await db
1046
1177
  .select()
1047
1178
  .from(schema.dashboards)
@@ -1116,6 +1247,9 @@ export async function unhideDashboard(
1116
1247
  userEmail: ctx.email,
1117
1248
  orgId: ctx.orgId ?? undefined,
1118
1249
  });
1250
+ if (!existing.archivedAt) {
1251
+ await assertDashboardNameIsAvailable(existing.title, ctx, id);
1252
+ }
1119
1253
  const db = getDb() as any;
1120
1254
  const now = nowIso();
1121
1255
  const patch: Record<string, unknown> = {
@@ -1127,10 +1261,21 @@ export async function unhideDashboard(
1127
1261
  if (!existing.ownerEmail) {
1128
1262
  patch.ownerEmail = ctx.email;
1129
1263
  }
1130
- await db
1131
- .update(schema.dashboards)
1132
- .set(patch)
1133
- .where(eq(schema.dashboards.id, id));
1264
+ const persist = async (writeDb: any) => {
1265
+ await writeDb
1266
+ .update(schema.dashboards)
1267
+ .set(patch)
1268
+ .where(eq(schema.dashboards.id, id));
1269
+ };
1270
+ if (!existing.archivedAt) {
1271
+ await db.transaction(async (tx: any) => {
1272
+ await lockDashboardNames(tx, [existing.title]);
1273
+ await assertDashboardNameIsAvailable(existing.title, ctx, id, tx);
1274
+ await persist(tx);
1275
+ });
1276
+ } else {
1277
+ await persist(db);
1278
+ }
1134
1279
  const [row] = await db
1135
1280
  .select()
1136
1281
  .from(schema.dashboards)
@@ -1922,8 +2067,30 @@ export async function listDashboardViews(
1922
2067
  ctx: AccessCtx,
1923
2068
  ): Promise<DashboardViewRecord[]> {
1924
2069
  // Parent access gates view visibility.
1925
- const dash = await getDashboard(dashboardId, ctx);
1926
- if (!dash) return [];
2070
+ const access = await resolveAccess(
2071
+ "dashboard",
2072
+ dashboardId,
2073
+ {
2074
+ userEmail: ctx.email,
2075
+ orgId: ctx.orgId ?? undefined,
2076
+ },
2077
+ { skipResourceBody: true },
2078
+ );
2079
+ if (!access) {
2080
+ // Keep the migration-aware legacy fallback for dashboards that predate
2081
+ // SQL materialization while retaining the projected SQL fast path.
2082
+ const legacy = await findLegacyDashboard(dashboardId, ctx);
2083
+ if (!legacy) return [];
2084
+ await migrateDashboardFromSettings(
2085
+ dashboardId,
2086
+ legacy.kind,
2087
+ legacy.data,
2088
+ legacy.ownerEmail,
2089
+ legacy.orgId,
2090
+ legacy.visibility,
2091
+ "owner",
2092
+ );
2093
+ }
1927
2094
  const db = getDb() as any;
1928
2095
  const rows = await db
1929
2096
  .select()