@agent-native/core 0.114.16 → 0.115.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 (476) hide show
  1. package/agent-native.eject.json +2 -0
  2. package/corpus/README.md +3 -3
  3. package/corpus/core/CHANGELOG.md +41 -0
  4. package/corpus/core/agent-native.eject.json +2 -0
  5. package/corpus/core/docs/content/agent-native-toolkit.mdx +16 -0
  6. package/corpus/core/docs/content/recurring-jobs.mdx +35 -6
  7. package/corpus/core/export-snapshot.json +4 -0
  8. package/corpus/core/package.json +5 -1
  9. package/corpus/core/src/action.ts +22 -1
  10. package/corpus/core/src/agent/production-agent.ts +122 -3
  11. package/corpus/core/src/cli/skills-content/visual-plan-skill.ts +19 -2
  12. package/corpus/core/src/cli/templates-meta.ts +12 -0
  13. package/corpus/core/src/client/AssistantChat.tsx +53 -26
  14. package/corpus/core/src/client/agent-page/AgentJobsTab.tsx +19 -0
  15. package/corpus/core/src/client/agent-page/use-jobs.ts +1 -0
  16. package/corpus/core/src/client/blocks/library/wireframe.tsx +3 -1
  17. package/corpus/core/src/client/chat/message-components.tsx +35 -1
  18. package/corpus/core/src/client/chat/tool-call-display.tsx +38 -3
  19. package/corpus/core/src/connections/catalog.ts +20 -1
  20. package/corpus/core/src/connections/reader.ts +65 -0
  21. package/corpus/core/src/dashboard-storage/index.ts +27 -0
  22. package/corpus/core/src/dashboard-storage/panel-source.ts +168 -0
  23. package/corpus/core/src/dashboard-storage/schema.ts +62 -0
  24. package/corpus/core/src/dashboard-storage/store.ts +333 -0
  25. package/corpus/core/src/deploy/build.ts +95 -16
  26. package/corpus/core/src/eject/provider-api-definitions.ts +1 -0
  27. package/corpus/core/src/eject/workspace-connections.ts +1 -0
  28. package/corpus/core/src/extensions/actions.ts +199 -73
  29. package/corpus/core/src/jobs/actions/list-recurring-jobs.ts +2 -0
  30. package/corpus/core/src/jobs/scheduler.ts +71 -4
  31. package/corpus/core/src/jobs/tools.ts +31 -1
  32. package/corpus/core/src/localization/default-messages.ts +1 -0
  33. package/corpus/core/src/mcp-client/index.ts +6 -0
  34. package/corpus/core/src/provider-api/actions/provider-api.ts +1 -1
  35. package/corpus/core/src/provider-api/index.ts +190 -5
  36. package/corpus/core/src/secrets/register-framework-secrets.ts +8 -0
  37. package/corpus/core/src/server/agent-chat/context-tools.ts +9 -0
  38. package/corpus/core/src/server/agent-chat/framework-prompts.ts +2 -2
  39. package/corpus/core/src/server/agent-chat-plugin.ts +22 -2
  40. package/corpus/core/src/server/core-routes-plugin.ts +1 -0
  41. package/corpus/core/src/server/credential-provider.ts +9 -3
  42. package/corpus/core/src/server/workspace-provider-oauth.ts +181 -4
  43. package/corpus/core/src/styles/agent-native.css +45 -2
  44. package/corpus/core/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +27 -0
  45. package/corpus/core/src/templates/default/.agents/skills/app-branding/SKILL.md +159 -0
  46. package/corpus/core/src/templates/default/.agents/skills/app-permissions/SKILL.md +155 -0
  47. package/corpus/core/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +27 -0
  48. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +27 -0
  49. package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +31 -18
  50. package/corpus/core/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +15 -0
  51. package/corpus/core/src/triggers/actions.ts +11 -2
  52. package/corpus/core/src/triggers/dispatcher.ts +20 -1
  53. package/corpus/core/src/triggers/types.ts +5 -0
  54. package/corpus/core/src/vite/action-types-plugin.ts +9 -1
  55. package/corpus/templates/analytics/.agents/skills/agent-native-docs/SKILL.md +115 -0
  56. package/corpus/templates/analytics/AGENTS.md +11 -11
  57. package/corpus/templates/analytics/_gitignore +39 -0
  58. package/corpus/templates/analytics/actions/query-dashboard-panel.ts +30 -0
  59. package/corpus/templates/analytics/actions/update-dashboard.ts +2 -1
  60. package/corpus/templates/analytics/app/lib/sql-query.ts +11 -25
  61. package/corpus/templates/analytics/changelog/2026-07-21-fixed-extension-updates-failing-before-changes-could-be-appl.md +6 -0
  62. package/corpus/templates/analytics/server/lib/dashboard-panel-query.ts +1 -0
  63. package/corpus/templates/analytics/server/lib/dashboard-panel-source-resolver.ts +51 -0
  64. package/corpus/templates/analytics/server/lib/prometheus.ts +1 -1
  65. package/corpus/templates/analytics/server/plugins/agent-chat.ts +1 -1
  66. package/corpus/templates/assets/.agents/skills/agent-native-docs/SKILL.md +115 -0
  67. package/corpus/templates/brain/.agents/skills/agent-native-docs/SKILL.md +115 -0
  68. package/corpus/templates/calendar/.agents/skills/agent-native-docs/SKILL.md +115 -0
  69. package/corpus/templates/calendar/_gitignore +39 -0
  70. package/corpus/templates/chat/.agents/skills/agent-native-docs/SKILL.md +27 -0
  71. package/corpus/templates/chat/AGENTS.md +1 -1
  72. package/corpus/templates/clips/.agents/skills/agent-native-docs/SKILL.md +115 -0
  73. package/corpus/templates/clips/AGENTS.md +7 -0
  74. package/corpus/templates/clips/actions/prepare-crm-call-evidence.ts +58 -0
  75. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +88 -11
  76. package/corpus/templates/clips/changelog/2026-07-21-crm-call-evidence-handoff.md +6 -0
  77. package/corpus/templates/clips/changelog/2026-07-21-recording-retries-and-restarts-keep-their-upload-session-so-.md +6 -0
  78. package/corpus/templates/clips/chrome-extension/src/background.ts +20 -5
  79. package/corpus/templates/clips/chrome-extension/src/native-recording-state.ts +19 -0
  80. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +8 -0
  81. package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +9 -0
  82. package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +54 -28
  83. package/corpus/templates/clips/server/plugins/agent-chat.ts +1 -0
  84. package/corpus/templates/content/.agents/skills/agent-native-docs/SKILL.md +115 -0
  85. package/corpus/templates/content/_gitignore +39 -0
  86. package/corpus/templates/content/learnings.defaults.md +5 -0
  87. package/corpus/templates/crm/.agents/skills/agent-native-docs/SKILL.md +115 -0
  88. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +133 -0
  89. package/corpus/templates/crm/.agents/skills/crm/SKILL.md +128 -0
  90. package/corpus/templates/crm/.agents/skills/customizing-agent-native/SKILL.md +145 -0
  91. package/corpus/templates/crm/.agents/skills/feature-flags/SKILL.md +169 -0
  92. package/corpus/templates/crm/.agents/skills/upgrade-agent-native/SKILL.md +100 -0
  93. package/corpus/templates/crm/.env.example +5 -0
  94. package/corpus/templates/crm/AGENTS.md +120 -0
  95. package/corpus/templates/crm/CHANGELOG.md +1 -0
  96. package/corpus/templates/crm/DEVELOPING.md +66 -0
  97. package/corpus/templates/crm/README.md +106 -0
  98. package/corpus/templates/crm/_gitignore +12 -0
  99. package/corpus/templates/crm/actions/_crm-action-utils.ts +87 -0
  100. package/corpus/templates/crm/actions/_crm-dashboard.ts +69 -0
  101. package/corpus/templates/crm/actions/_crm-signal-utils.ts +79 -0
  102. package/corpus/templates/crm/actions/apply-crm-proposals.ts +203 -0
  103. package/corpus/templates/crm/actions/attach-call-evidence.ts +170 -0
  104. package/corpus/templates/crm/actions/configure-crm-connection.ts +126 -0
  105. package/corpus/templates/crm/actions/configure-native-crm.ts +44 -0
  106. package/corpus/templates/crm/actions/create-crm-record.ts +148 -0
  107. package/corpus/templates/crm/actions/create-crm-signal-tracker.ts +58 -0
  108. package/corpus/templates/crm/actions/delete-staged-dataset.ts +14 -0
  109. package/corpus/templates/crm/actions/get-crm-automation-recipe.ts +36 -0
  110. package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +33 -0
  111. package/corpus/templates/crm/actions/get-crm-dashboard.ts +14 -0
  112. package/corpus/templates/crm/actions/get-crm-overview.ts +13 -0
  113. package/corpus/templates/crm/actions/get-crm-pipeline-data.ts +61 -0
  114. package/corpus/templates/crm/actions/get-crm-record.ts +75 -0
  115. package/corpus/templates/crm/actions/install-crm-pipeline-dashboard.ts +81 -0
  116. package/corpus/templates/crm/actions/list-crm-dashboard-revisions.ts +15 -0
  117. package/corpus/templates/crm/actions/list-crm-dashboards.ts +15 -0
  118. package/corpus/templates/crm/actions/list-crm-proposals.ts +31 -0
  119. package/corpus/templates/crm/actions/list-crm-records.ts +79 -0
  120. package/corpus/templates/crm/actions/list-crm-saved-views.ts +15 -0
  121. package/corpus/templates/crm/actions/list-crm-signal-hits.ts +43 -0
  122. package/corpus/templates/crm/actions/list-crm-signal-trackers.ts +32 -0
  123. package/corpus/templates/crm/actions/list-crm-tasks.ts +22 -0
  124. package/corpus/templates/crm/actions/list-staged-datasets.ts +14 -0
  125. package/corpus/templates/crm/actions/list-workspace-connections.ts +47 -0
  126. package/corpus/templates/crm/actions/manage-crm-signal-tracker.ts +87 -0
  127. package/corpus/templates/crm/actions/manage-crm-task.ts +132 -0
  128. package/corpus/templates/crm/actions/navigate.ts +63 -0
  129. package/corpus/templates/crm/actions/provider-api-catalog.ts +22 -0
  130. package/corpus/templates/crm/actions/provider-api-docs.ts +54 -0
  131. package/corpus/templates/crm/actions/provider-api-request.ts +92 -0
  132. package/corpus/templates/crm/actions/query-staged-dataset.ts +44 -0
  133. package/corpus/templates/crm/actions/record-crm-call-insight.ts +160 -0
  134. package/corpus/templates/crm/actions/record-crm-smart-signal.ts +150 -0
  135. package/corpus/templates/crm/actions/restore-crm-dashboard-revision.ts +23 -0
  136. package/corpus/templates/crm/actions/review-crm-signal.ts +31 -0
  137. package/corpus/templates/crm/actions/run-crm-saved-view-program.ts +103 -0
  138. package/corpus/templates/crm/actions/run-crm-signal-trackers.ts +275 -0
  139. package/corpus/templates/crm/actions/run.ts +11 -0
  140. package/corpus/templates/crm/actions/save-crm-dashboard.ts +51 -0
  141. package/corpus/templates/crm/actions/save-crm-saved-view.ts +119 -0
  142. package/corpus/templates/crm/actions/sync-crm.ts +146 -0
  143. package/corpus/templates/crm/actions/update-crm-record.ts +508 -0
  144. package/corpus/templates/crm/actions/view-screen.ts +218 -0
  145. package/corpus/templates/crm/agent-native.app-skill.json +58 -0
  146. package/corpus/templates/crm/app/components/crm/CreateCrmRecordDialog.tsx +158 -0
  147. package/corpus/templates/crm/app/components/crm/CrmDashboardPanel.tsx +83 -0
  148. package/corpus/templates/crm/app/components/crm/CrmSignalsPanel.tsx +225 -0
  149. package/corpus/templates/crm/app/components/crm/IntelligenceSettings.tsx +413 -0
  150. package/corpus/templates/crm/app/components/crm/RecordActions.tsx +510 -0
  151. package/corpus/templates/crm/app/components/crm/RecordGrid.tsx +134 -0
  152. package/corpus/templates/crm/app/components/crm/RecordWorkspace.tsx +367 -0
  153. package/corpus/templates/crm/app/components/crm/SavedViewDataProgram.tsx +92 -0
  154. package/corpus/templates/crm/app/components/crm/Surface.tsx +96 -0
  155. package/corpus/templates/crm/app/components/crm/WorkOverview.tsx +180 -0
  156. package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +106 -0
  157. package/corpus/templates/crm/app/components/layout/CrmSidebar.tsx +84 -0
  158. package/corpus/templates/crm/app/entry.client.tsx +16 -0
  159. package/corpus/templates/crm/app/entry.server.tsx +10 -0
  160. package/corpus/templates/crm/app/global.css +167 -0
  161. package/corpus/templates/crm/app/hooks/use-navigation-state.ts +60 -0
  162. package/corpus/templates/crm/app/i18n/en-US.ts +89 -0
  163. package/corpus/templates/crm/app/i18n/index.ts +34 -0
  164. package/corpus/templates/crm/app/lib/dashboard.ts +13 -0
  165. package/corpus/templates/crm/app/lib/navigation.ts +54 -0
  166. package/corpus/templates/crm/app/lib/tab-id.ts +1 -0
  167. package/corpus/templates/crm/app/lib/types.ts +172 -0
  168. package/corpus/templates/crm/app/lib/utils.ts +1 -0
  169. package/corpus/templates/crm/app/root.tsx +88 -0
  170. package/corpus/templates/crm/app/routes/_index.tsx +26 -0
  171. package/corpus/templates/crm/app/routes/accounts.tsx +41 -0
  172. package/corpus/templates/crm/app/routes/agent.tsx +5 -0
  173. package/corpus/templates/crm/app/routes/ask.tsx +53 -0
  174. package/corpus/templates/crm/app/routes/dashboard.tsx +118 -0
  175. package/corpus/templates/crm/app/routes/opportunities.tsx +41 -0
  176. package/corpus/templates/crm/app/routes/people.tsx +41 -0
  177. package/corpus/templates/crm/app/routes/proposals.tsx +266 -0
  178. package/corpus/templates/crm/app/routes/records.$recordId.tsx +35 -0
  179. package/corpus/templates/crm/app/routes/settings.tsx +63 -0
  180. package/corpus/templates/crm/app/routes/setup.tsx +308 -0
  181. package/corpus/templates/crm/app/routes/tasks.tsx +252 -0
  182. package/corpus/templates/crm/app/routes/views.tsx +333 -0
  183. package/corpus/templates/crm/app/routes.ts +17 -0
  184. package/corpus/templates/crm/app/vite-env.d.ts +6 -0
  185. package/corpus/templates/crm/changelog/2026-07-21-agent-native-crm.md +6 -0
  186. package/corpus/templates/crm/changelog/2026-07-21-clips-call-evidence-review-recipe.md +6 -0
  187. package/corpus/templates/crm/changelog/2026-07-21-crm-can-now-find-and-review-evidence-grounded-call-signals-w.md +6 -0
  188. package/corpus/templates/crm/changelog/2026-07-21-crm-can-now-manage-keyword-and-smart-call-signal-trackers-fr.md +6 -0
  189. package/corpus/templates/crm/changelog/2026-07-21-delegated-local-crm-automation.md +6 -0
  190. package/corpus/templates/crm/changelog/2026-07-21-native-sql.md +6 -0
  191. package/corpus/templates/crm/changelog/2026-07-21-pipeline-dashboard.md +6 -0
  192. package/corpus/templates/crm/changelog/2026-07-21-pipeline-dashboards-now-install-correctly-from-the-crm-actio.md +6 -0
  193. package/corpus/templates/crm/changelog/2026-07-21-salesforce-connection.md +6 -0
  194. package/corpus/templates/crm/components.json +20 -0
  195. package/corpus/templates/crm/docs/architecture/crm-contract.md +180 -0
  196. package/corpus/templates/crm/learnings.defaults.md +5 -0
  197. package/corpus/templates/crm/package.json +59 -0
  198. package/corpus/templates/crm/public/favicon.svg +1 -0
  199. package/corpus/templates/crm/public/icon-180.svg +1 -0
  200. package/corpus/templates/crm/public/manifest.json +11 -0
  201. package/corpus/templates/crm/react-router.config.ts +7 -0
  202. package/corpus/templates/crm/server/crm/adapter.ts +32 -0
  203. package/corpus/templates/crm/server/crm/crm-field-firewall.ts +64 -0
  204. package/corpus/templates/crm/server/crm/crm-mirror.ts +711 -0
  205. package/corpus/templates/crm/server/crm/hubspot-adapter.ts +1143 -0
  206. package/corpus/templates/crm/server/crm/native-adapter.ts +1715 -0
  207. package/corpus/templates/crm/server/crm/read-through.ts +207 -0
  208. package/corpus/templates/crm/server/crm/salesforce-adapter.ts +1255 -0
  209. package/corpus/templates/crm/server/db/crm-store.ts +1170 -0
  210. package/corpus/templates/crm/server/db/index.ts +169 -0
  211. package/corpus/templates/crm/server/db/schema.ts +441 -0
  212. package/corpus/templates/crm/server/lib/intelligence/default-detectors.ts +65 -0
  213. package/corpus/templates/crm/server/lib/intelligence/evidence.ts +105 -0
  214. package/corpus/templates/crm/server/lib/intelligence/keyword-detector.ts +100 -0
  215. package/corpus/templates/crm/server/lib/intelligence/smart-detector.ts +144 -0
  216. package/corpus/templates/crm/server/lib/intelligence/summary.ts +141 -0
  217. package/corpus/templates/crm/server/lib/provider-api.ts +64 -0
  218. package/corpus/templates/crm/server/middleware/auth.ts +4 -0
  219. package/corpus/templates/crm/server/plugins/agent-chat.ts +68 -0
  220. package/corpus/templates/crm/server/plugins/auth.ts +14 -0
  221. package/corpus/templates/crm/server/plugins/core-routes.ts +17 -0
  222. package/corpus/templates/crm/server/plugins/db.ts +454 -0
  223. package/corpus/templates/crm/server/routes/[...page].get.ts +5 -0
  224. package/corpus/templates/crm/shared/crm-automation-recipes.ts +132 -0
  225. package/corpus/templates/crm/shared/crm-contract.ts +254 -0
  226. package/corpus/templates/crm/shared/crm-sales-config.ts +63 -0
  227. package/corpus/templates/crm/ssr-entry.ts +11 -0
  228. package/corpus/templates/crm/tsconfig.json +24 -0
  229. package/corpus/templates/crm/vite.config.ts +15 -0
  230. package/corpus/templates/crm/vitest.config.ts +5 -0
  231. package/corpus/templates/design/.agents/skills/agent-native-docs/SKILL.md +115 -0
  232. package/corpus/templates/dispatch/.agents/skills/agent-native-docs/SKILL.md +115 -0
  233. package/corpus/templates/forms/.agents/skills/agent-native-docs/SKILL.md +115 -0
  234. package/corpus/templates/forms/_gitignore +39 -0
  235. package/corpus/templates/forms/learnings.defaults.md +5 -0
  236. package/corpus/templates/macros/.agents/skills/agent-native-docs/SKILL.md +115 -0
  237. package/corpus/templates/macros/_gitignore +39 -0
  238. package/corpus/templates/macros/learnings.defaults.md +5 -0
  239. package/corpus/templates/mail/.agents/skills/agent-native-docs/SKILL.md +115 -0
  240. package/corpus/templates/mail/_gitignore +39 -0
  241. package/corpus/templates/plan/.agents/skills/agent-native-docs/SKILL.md +115 -0
  242. package/corpus/templates/plan/.agents/skills/visual-plan/SKILL.md +19 -2
  243. package/corpus/templates/plan/AGENTS.md +10 -0
  244. package/corpus/templates/plan/actions/create-prototype-plan.ts +27 -3
  245. package/corpus/templates/plan/actions/get-plan-blocks.ts +1 -1
  246. package/corpus/templates/plan/actions/update-visual-plan.ts +3 -2
  247. package/corpus/templates/plan/app/components/plan/PlanContentRenderer.tsx +18 -4
  248. package/corpus/templates/plan/app/components/plan/PlanVisualSurface.tsx +10 -3
  249. package/corpus/templates/plan/app/components/plan/wireframe/Wireframe.tsx +3 -1
  250. package/corpus/templates/plan/app/i18n/en-US.ts +6 -0
  251. package/corpus/templates/plan/app/lib/create-plan-routing.ts +39 -0
  252. package/corpus/templates/plan/app/pages/PlansPage.tsx +31 -107
  253. package/corpus/templates/plan/changelog/2026-07-22-high-fidelity-mockup-requests-now-preserve-branded-css-use-d.md +6 -0
  254. package/corpus/templates/plan/shared/plan-content.ts +110 -2
  255. package/corpus/templates/slides/.agents/skills/agent-native-docs/SKILL.md +115 -0
  256. package/corpus/templates/slides/_gitignore +39 -0
  257. package/corpus/templates/tasks/.agents/skills/agent-native-docs/SKILL.md +27 -0
  258. package/corpus/templates/tasks/_gitignore +39 -0
  259. package/corpus/toolkit/CHANGELOG.md +12 -0
  260. package/corpus/toolkit/README.md +30 -0
  261. package/corpus/toolkit/agent-native.eject.json +33 -0
  262. package/corpus/toolkit/package.json +15 -1
  263. package/corpus/toolkit/src/dashboard/DataTable.tsx +273 -0
  264. package/corpus/toolkit/src/dashboard/DateRangePicker.tsx +57 -0
  265. package/corpus/toolkit/src/dashboard/GenericChartPanel.tsx +461 -0
  266. package/corpus/toolkit/src/dashboard/MetricCard.tsx +54 -0
  267. package/corpus/toolkit/src/dashboard/StatsCard.tsx +49 -0
  268. package/corpus/toolkit/src/dashboard/dashboard-layout.ts +329 -0
  269. package/corpus/toolkit/src/dashboard/index.ts +6 -0
  270. package/corpus/toolkit/src/index.ts +1 -0
  271. package/corpus/toolkit/src/ui/calendar.tsx +8 -8
  272. package/corpus/toolkit/src/ui/date-picker.tsx +4 -1
  273. package/dist/action.d.ts +14 -1
  274. package/dist/action.d.ts.map +1 -1
  275. package/dist/action.js.map +1 -1
  276. package/dist/agent/production-agent.d.ts +3 -1
  277. package/dist/agent/production-agent.d.ts.map +1 -1
  278. package/dist/agent/production-agent.js +97 -3
  279. package/dist/agent/production-agent.js.map +1 -1
  280. package/dist/cli/skills-content/visual-plan-skill.d.ts +1 -1
  281. package/dist/cli/skills-content/visual-plan-skill.d.ts.map +1 -1
  282. package/dist/cli/skills-content/visual-plan-skill.js +19 -2
  283. package/dist/cli/skills-content/visual-plan-skill.js.map +1 -1
  284. package/dist/cli/templates-meta.d.ts.map +1 -1
  285. package/dist/cli/templates-meta.js +12 -0
  286. package/dist/cli/templates-meta.js.map +1 -1
  287. package/dist/client/AssistantChat.d.ts +4 -0
  288. package/dist/client/AssistantChat.d.ts.map +1 -1
  289. package/dist/client/AssistantChat.js +30 -17
  290. package/dist/client/AssistantChat.js.map +1 -1
  291. package/dist/client/agent-page/AgentJobsTab.d.ts.map +1 -1
  292. package/dist/client/agent-page/AgentJobsTab.js +3 -1
  293. package/dist/client/agent-page/AgentJobsTab.js.map +1 -1
  294. package/dist/client/agent-page/use-jobs.d.ts +1 -0
  295. package/dist/client/agent-page/use-jobs.d.ts.map +1 -1
  296. package/dist/client/agent-page/use-jobs.js.map +1 -1
  297. package/dist/client/blocks/library/wireframe.d.ts.map +1 -1
  298. package/dist/client/blocks/library/wireframe.js +3 -2
  299. package/dist/client/blocks/library/wireframe.js.map +1 -1
  300. package/dist/client/chat/message-components.d.ts +5 -0
  301. package/dist/client/chat/message-components.d.ts.map +1 -1
  302. package/dist/client/chat/message-components.js +17 -1
  303. package/dist/client/chat/message-components.js.map +1 -1
  304. package/dist/client/chat/tool-call-display.d.ts +3 -1
  305. package/dist/client/chat/tool-call-display.d.ts.map +1 -1
  306. package/dist/client/chat/tool-call-display.js +17 -7
  307. package/dist/client/chat/tool-call-display.js.map +1 -1
  308. package/dist/collab/routes.d.ts +1 -1
  309. package/dist/collab/struct-routes.d.ts +1 -1
  310. package/dist/connections/catalog.d.ts +17 -3
  311. package/dist/connections/catalog.d.ts.map +1 -1
  312. package/dist/connections/catalog.js +16 -1
  313. package/dist/connections/catalog.js.map +1 -1
  314. package/dist/connections/reader.d.ts +18 -0
  315. package/dist/connections/reader.d.ts.map +1 -1
  316. package/dist/connections/reader.js +41 -0
  317. package/dist/connections/reader.js.map +1 -1
  318. package/dist/dashboard-storage/index.d.ts +4 -0
  319. package/dist/dashboard-storage/index.d.ts.map +1 -0
  320. package/dist/dashboard-storage/index.js +4 -0
  321. package/dist/dashboard-storage/index.js.map +1 -0
  322. package/dist/dashboard-storage/panel-source.d.ts +68 -0
  323. package/dist/dashboard-storage/panel-source.d.ts.map +1 -0
  324. package/dist/dashboard-storage/panel-source.js +76 -0
  325. package/dist/dashboard-storage/panel-source.js.map +1 -0
  326. package/dist/dashboard-storage/schema.d.ts +562 -0
  327. package/dist/dashboard-storage/schema.d.ts.map +1 -0
  328. package/dist/dashboard-storage/schema.js +32 -0
  329. package/dist/dashboard-storage/schema.js.map +1 -0
  330. package/dist/dashboard-storage/store.d.ts +62 -0
  331. package/dist/dashboard-storage/store.d.ts.map +1 -0
  332. package/dist/dashboard-storage/store.js +227 -0
  333. package/dist/dashboard-storage/store.js.map +1 -0
  334. package/dist/deploy/build.d.ts +7 -0
  335. package/dist/deploy/build.d.ts.map +1 -1
  336. package/dist/deploy/build.js +78 -14
  337. package/dist/deploy/build.js.map +1 -1
  338. package/dist/eject/provider-api-definitions.d.ts +2 -1
  339. package/dist/eject/provider-api-definitions.d.ts.map +1 -1
  340. package/dist/eject/provider-api-definitions.js +1 -0
  341. package/dist/eject/provider-api-definitions.js.map +1 -1
  342. package/dist/eject/workspace-connections.d.ts.map +1 -1
  343. package/dist/eject/workspace-connections.js +1 -0
  344. package/dist/eject/workspace-connections.js.map +1 -1
  345. package/dist/extensions/actions.d.ts.map +1 -1
  346. package/dist/extensions/actions.js +165 -67
  347. package/dist/extensions/actions.js.map +1 -1
  348. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  349. package/dist/jobs/actions/list-recurring-jobs.d.ts +1 -0
  350. package/dist/jobs/actions/list-recurring-jobs.d.ts.map +1 -1
  351. package/dist/jobs/actions/list-recurring-jobs.js +1 -0
  352. package/dist/jobs/actions/list-recurring-jobs.js.map +1 -1
  353. package/dist/jobs/scheduler.d.ts +17 -2
  354. package/dist/jobs/scheduler.d.ts.map +1 -1
  355. package/dist/jobs/scheduler.js +51 -2
  356. package/dist/jobs/scheduler.js.map +1 -1
  357. package/dist/jobs/tools.d.ts.map +1 -1
  358. package/dist/jobs/tools.js +32 -2
  359. package/dist/jobs/tools.js.map +1 -1
  360. package/dist/localization/default-messages.d.ts +1 -0
  361. package/dist/localization/default-messages.d.ts.map +1 -1
  362. package/dist/localization/default-messages.js +1 -0
  363. package/dist/localization/default-messages.js.map +1 -1
  364. package/dist/mcp-client/index.d.ts +2 -0
  365. package/dist/mcp-client/index.d.ts.map +1 -1
  366. package/dist/mcp-client/index.js +5 -0
  367. package/dist/mcp-client/index.js.map +1 -1
  368. package/dist/notifications/routes.d.ts +3 -3
  369. package/dist/progress/routes.d.ts +1 -1
  370. package/dist/provider-api/actions/custom-provider-registration.d.ts +8 -8
  371. package/dist/provider-api/actions/provider-api.d.ts +8 -6
  372. package/dist/provider-api/actions/provider-api.d.ts.map +1 -1
  373. package/dist/provider-api/actions/provider-api.js +1 -1
  374. package/dist/provider-api/actions/provider-api.js.map +1 -1
  375. package/dist/provider-api/index.d.ts +11 -7
  376. package/dist/provider-api/index.d.ts.map +1 -1
  377. package/dist/provider-api/index.js +143 -5
  378. package/dist/provider-api/index.js.map +1 -1
  379. package/dist/resources/handlers.d.ts +1 -1
  380. package/dist/secrets/register-framework-secrets.d.ts.map +1 -1
  381. package/dist/secrets/register-framework-secrets.js +7 -0
  382. package/dist/secrets/register-framework-secrets.js.map +1 -1
  383. package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
  384. package/dist/server/agent-chat/context-tools.js +9 -0
  385. package/dist/server/agent-chat/context-tools.js.map +1 -1
  386. package/dist/server/agent-chat/framework-prompts.js +2 -2
  387. package/dist/server/agent-chat/framework-prompts.js.map +1 -1
  388. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  389. package/dist/server/agent-chat-plugin.js +18 -2
  390. package/dist/server/agent-chat-plugin.js.map +1 -1
  391. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  392. package/dist/server/core-routes-plugin.js +1 -0
  393. package/dist/server/core-routes-plugin.js.map +1 -1
  394. package/dist/server/credential-provider.d.ts +4 -3
  395. package/dist/server/credential-provider.d.ts.map +1 -1
  396. package/dist/server/credential-provider.js +9 -3
  397. package/dist/server/credential-provider.js.map +1 -1
  398. package/dist/server/workspace-provider-oauth.d.ts +6 -1
  399. package/dist/server/workspace-provider-oauth.d.ts.map +1 -1
  400. package/dist/server/workspace-provider-oauth.js +141 -4
  401. package/dist/server/workspace-provider-oauth.js.map +1 -1
  402. package/dist/styles/agent-native.css +45 -2
  403. package/dist/templates/chat/.agents/skills/agent-native-docs/SKILL.md +27 -0
  404. package/dist/templates/chat/AGENTS.md +1 -1
  405. package/dist/templates/default/.agents/skills/agent-native-docs/SKILL.md +27 -0
  406. package/dist/templates/default/.agents/skills/app-branding/SKILL.md +159 -0
  407. package/dist/templates/default/.agents/skills/app-permissions/SKILL.md +155 -0
  408. package/dist/templates/headless/.agents/skills/agent-native-docs/SKILL.md +27 -0
  409. package/dist/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +27 -0
  410. package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +31 -18
  411. package/dist/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +15 -0
  412. package/dist/triggers/actions.d.ts.map +1 -1
  413. package/dist/triggers/actions.js +10 -2
  414. package/dist/triggers/actions.js.map +1 -1
  415. package/dist/triggers/dispatcher.d.ts.map +1 -1
  416. package/dist/triggers/dispatcher.js +15 -0
  417. package/dist/triggers/dispatcher.js.map +1 -1
  418. package/dist/triggers/types.d.ts +5 -0
  419. package/dist/triggers/types.d.ts.map +1 -1
  420. package/dist/triggers/types.js.map +1 -1
  421. package/dist/vite/action-types-plugin.d.ts.map +1 -1
  422. package/dist/vite/action-types-plugin.js +4 -1
  423. package/dist/vite/action-types-plugin.js.map +1 -1
  424. package/docs/content/agent-native-toolkit.mdx +16 -0
  425. package/docs/content/recurring-jobs.mdx +35 -6
  426. package/package.json +7 -3
  427. package/src/action.ts +22 -1
  428. package/src/agent/production-agent.ts +122 -3
  429. package/src/cli/skills-content/visual-plan-skill.ts +19 -2
  430. package/src/cli/templates-meta.ts +12 -0
  431. package/src/client/AssistantChat.tsx +53 -26
  432. package/src/client/agent-page/AgentJobsTab.tsx +19 -0
  433. package/src/client/agent-page/use-jobs.ts +1 -0
  434. package/src/client/blocks/library/wireframe.tsx +3 -1
  435. package/src/client/chat/message-components.tsx +35 -1
  436. package/src/client/chat/tool-call-display.tsx +38 -3
  437. package/src/connections/catalog.ts +20 -1
  438. package/src/connections/reader.ts +65 -0
  439. package/src/dashboard-storage/index.ts +27 -0
  440. package/src/dashboard-storage/panel-source.ts +168 -0
  441. package/src/dashboard-storage/schema.ts +62 -0
  442. package/src/dashboard-storage/store.ts +333 -0
  443. package/src/deploy/build.ts +95 -16
  444. package/src/eject/provider-api-definitions.ts +1 -0
  445. package/src/eject/workspace-connections.ts +1 -0
  446. package/src/extensions/actions.ts +199 -73
  447. package/src/jobs/actions/list-recurring-jobs.ts +2 -0
  448. package/src/jobs/scheduler.ts +71 -4
  449. package/src/jobs/tools.ts +31 -1
  450. package/src/localization/default-messages.ts +1 -0
  451. package/src/mcp-client/index.ts +6 -0
  452. package/src/provider-api/actions/provider-api.ts +1 -1
  453. package/src/provider-api/index.ts +190 -5
  454. package/src/secrets/register-framework-secrets.ts +8 -0
  455. package/src/server/agent-chat/context-tools.ts +9 -0
  456. package/src/server/agent-chat/framework-prompts.ts +2 -2
  457. package/src/server/agent-chat-plugin.ts +22 -2
  458. package/src/server/core-routes-plugin.ts +1 -0
  459. package/src/server/credential-provider.ts +9 -3
  460. package/src/server/workspace-provider-oauth.ts +181 -4
  461. package/src/styles/agent-native.css +45 -2
  462. package/src/templates/chat/.agents/skills/agent-native-docs/SKILL.md +27 -0
  463. package/src/templates/chat/AGENTS.md +1 -1
  464. package/src/templates/default/.agents/skills/agent-native-docs/SKILL.md +27 -0
  465. package/src/templates/default/.agents/skills/app-branding/SKILL.md +159 -0
  466. package/src/templates/default/.agents/skills/app-permissions/SKILL.md +155 -0
  467. package/src/templates/headless/.agents/skills/agent-native-docs/SKILL.md +27 -0
  468. package/src/templates/workspace-core/.agents/skills/agent-native-docs/SKILL.md +27 -0
  469. package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +31 -18
  470. package/src/templates/workspace-core/.agents/skills/recurring-jobs/SKILL.md +15 -0
  471. package/src/triggers/actions.ts +11 -2
  472. package/src/triggers/dispatcher.ts +20 -1
  473. package/src/triggers/types.ts +5 -0
  474. package/src/vite/action-types-plugin.ts +9 -1
  475. package/corpus/templates/analytics/server/handlers/sql-query.ts +0 -36
  476. package/corpus/templates/analytics/server/routes/api/sql-query.post.ts +0 -1
@@ -13,6 +13,7 @@ export type WorkspaceConnectionTemplateUse =
13
13
  | "calendar"
14
14
  | "clips"
15
15
  | "content"
16
+ | "crm"
16
17
  | "design"
17
18
  | "dispatch"
18
19
  | "forms"
@@ -27,6 +28,7 @@ export type WorkspaceConnectionProviderId =
27
28
  | "gmail"
28
29
  | "google_drive"
29
30
  | "hubspot"
31
+ | "salesforce"
30
32
  | "jira"
31
33
  | "sentry"
32
34
  | "granola"
@@ -239,7 +241,24 @@ export const WORKSPACE_CONNECTION_PROVIDERS = [
239
241
  ],
240
242
  },
241
243
  capabilities: ["search", "import", "crm"],
242
- recommendedTemplateUses: ["analytics", "brain", "mail", "dispatch"],
244
+ recommendedTemplateUses: ["analytics", "brain", "crm", "mail", "dispatch"],
245
+ }),
246
+ defineWorkspaceConnectionProvider({
247
+ id: "salesforce",
248
+ label: "Salesforce",
249
+ description:
250
+ "CRM accounts, contacts, opportunities, custom objects, and activity history for customer-aware apps.",
251
+ credentialKeys: [],
252
+ oauth: {
253
+ provider: "salesforce",
254
+ authorizationUrl:
255
+ "https://login.salesforce.com/services/oauth2/authorize",
256
+ tokenUrl: "https://login.salesforce.com/services/oauth2/token",
257
+ refreshUrl: "https://login.salesforce.com/services/oauth2/token",
258
+ scopes: ["api", "refresh_token", "id"],
259
+ },
260
+ capabilities: ["search", "import", "crm"],
261
+ recommendedTemplateUses: ["analytics", "brain", "crm", "dispatch"],
243
262
  }),
244
263
  defineWorkspaceConnectionProvider({
245
264
  id: "jira",
@@ -506,6 +506,71 @@ export const PROVIDER_READERS = [
506
506
  notes:
507
507
  "Templates define object allow-lists and property projections; core only documents the reader shape.",
508
508
  }),
509
+ defineProviderReader({
510
+ providerId: "salesforce",
511
+ label: "Salesforce reader",
512
+ description:
513
+ "Normalized metadata for CRM object lookup through template-owned Salesforce integrations.",
514
+ implementationStatus: "template-owned",
515
+ credentialKeys: providerCredentialKeys("salesforce"),
516
+ requiredCredentialKeys: requiredCredentialKeys("salesforce"),
517
+ capabilities: [
518
+ {
519
+ capability: "search",
520
+ label: "CRM search",
521
+ description:
522
+ "Search Salesforce CRM records with provider-native SOQL or object filters.",
523
+ },
524
+ {
525
+ capability: "crm",
526
+ label: "CRM retrieval",
527
+ description:
528
+ "Read Salesforce standard and custom CRM objects when a template owns object mapping.",
529
+ },
530
+ ],
531
+ operations: [
532
+ operation(
533
+ "search",
534
+ "Search Salesforce CRM",
535
+ "Find CRM records matching text, SOQL, or provider-native filters.",
536
+ "template-owned",
537
+ [
538
+ ...searchParameters,
539
+ {
540
+ name: "objectType",
541
+ type: "string",
542
+ description:
543
+ "Optional Salesforce object API name such as Account, Contact, Opportunity, or a custom object.",
544
+ },
545
+ ],
546
+ ),
547
+ operation(
548
+ "get",
549
+ "Get Salesforce CRM object",
550
+ "Load one Salesforce CRM record by object API name and id.",
551
+ "template-owned",
552
+ [
553
+ ...getParameters,
554
+ {
555
+ name: "objectType",
556
+ type: "string",
557
+ required: true,
558
+ description:
559
+ "Salesforce object API name such as Account, Contact, Opportunity, or a custom object.",
560
+ },
561
+ ],
562
+ ),
563
+ operation(
564
+ "listRecent",
565
+ "List recent Salesforce CRM objects",
566
+ "List recently updated CRM records visible to the connected Salesforce user.",
567
+ "template-owned",
568
+ listRecentParameters,
569
+ ),
570
+ ],
571
+ notes:
572
+ "Templates define object allow-lists, field projections, and API-version selection; core only documents the reader shape.",
573
+ }),
509
574
  defineProviderReader({
510
575
  providerId: "gmail",
511
576
  label: "Gmail reader",
@@ -0,0 +1,27 @@
1
+ export {
2
+ createDashboardStorageSchema,
3
+ type DashboardStorageSchema,
4
+ type DashboardStorageSchemaOptions,
5
+ } from "./schema.js";
6
+ export {
7
+ createDashboardStorage,
8
+ DashboardStorageConflictError,
9
+ type DashboardRecord,
10
+ type DashboardRevisionRecord,
11
+ type DashboardStorageOptions,
12
+ type DashboardWriteInput,
13
+ } from "./store.js";
14
+ export {
15
+ createPanelSourceResolverRegistry,
16
+ createProgramPanelSourceResolver,
17
+ parseProgramPanelDescriptor,
18
+ type DashboardPanelColumn,
19
+ type PanelSourceFailure,
20
+ type PanelSourceRequest,
21
+ type PanelSourceResolver,
22
+ type PanelSourceResolverRegistry,
23
+ type PanelSourceResponse,
24
+ type PanelSourceResult,
25
+ type ProgramPanelContext,
26
+ type ProgramPanelDescriptor,
27
+ } from "./panel-source.js";
@@ -0,0 +1,168 @@
1
+ import { runDataProgram } from "../data-programs/index.js";
2
+
3
+ export interface DashboardPanelColumn {
4
+ name: string;
5
+ type: string;
6
+ }
7
+
8
+ export interface PanelSourceRequest<TSource extends string = string> {
9
+ source: TSource;
10
+ query: string;
11
+ }
12
+
13
+ export interface PanelSourceResult {
14
+ rows: Record<string, unknown>[];
15
+ schema?: DashboardPanelColumn[];
16
+ truncated?: boolean;
17
+ bytesProcessed?: number;
18
+ }
19
+
20
+ export interface PanelSourceFailure {
21
+ error: string;
22
+ message?: string;
23
+ }
24
+
25
+ export type PanelSourceResponse = PanelSourceResult | PanelSourceFailure;
26
+
27
+ export interface PanelSourceResolver<
28
+ TSource extends string = string,
29
+ TContext = unknown,
30
+ > {
31
+ source: TSource;
32
+ resolve(
33
+ request: PanelSourceRequest<TSource>,
34
+ context: TContext,
35
+ ): Promise<PanelSourceResponse>;
36
+ }
37
+
38
+ export interface PanelSourceResolverRegistry<
39
+ TSource extends string = string,
40
+ TContext = unknown,
41
+ > {
42
+ readonly sources: readonly TSource[];
43
+ resolve(
44
+ request: PanelSourceRequest<TSource>,
45
+ context: TContext,
46
+ ): Promise<PanelSourceResponse>;
47
+ }
48
+
49
+ export function createPanelSourceResolverRegistry<
50
+ TSource extends string = string,
51
+ TContext = unknown,
52
+ >(options: {
53
+ resolvers: readonly PanelSourceResolver<TSource, TContext>[];
54
+ }): PanelSourceResolverRegistry<TSource, TContext> {
55
+ const resolvers = new Map<TSource, PanelSourceResolver<TSource, TContext>>();
56
+ for (const resolver of options.resolvers) {
57
+ const source = resolver.source.trim() as TSource;
58
+ if (!source) throw new Error("Panel source resolver requires a source.");
59
+ if (resolvers.has(source)) {
60
+ throw new Error(`Duplicate panel source resolver: ${source}`);
61
+ }
62
+ resolvers.set(source, resolver);
63
+ }
64
+
65
+ return {
66
+ sources: [...resolvers.keys()],
67
+ async resolve(request, context) {
68
+ const resolver = resolvers.get(request.source);
69
+ if (!resolver) {
70
+ throw new Error(
71
+ `Unsupported dashboard panel source: ${request.source}`,
72
+ );
73
+ }
74
+ return resolver.resolve(request, context);
75
+ },
76
+ };
77
+ }
78
+
79
+ export interface ProgramPanelContext {
80
+ userEmail: string;
81
+ orgId?: string | null;
82
+ }
83
+
84
+ export interface ProgramPanelDescriptor {
85
+ programId: string;
86
+ params?: Record<string, unknown>;
87
+ }
88
+
89
+ export interface ProgramPanelRunResult {
90
+ ok: boolean;
91
+ rows?: Record<string, unknown>[];
92
+ schema?: DashboardPanelColumn[];
93
+ truncated?: boolean;
94
+ lastGoodRun?: {
95
+ rows: Record<string, unknown>[];
96
+ schema: DashboardPanelColumn[];
97
+ truncated?: boolean;
98
+ };
99
+ error?: { code: string; message: string };
100
+ }
101
+
102
+ export function parseProgramPanelDescriptor(
103
+ query: string,
104
+ ): ProgramPanelDescriptor {
105
+ let value: unknown;
106
+ try {
107
+ value = JSON.parse(query);
108
+ } catch {
109
+ throw new Error("Program panel query must be a JSON object.");
110
+ }
111
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
112
+ throw new Error("Program panel query must be a JSON object.");
113
+ }
114
+ const descriptor = value as Record<string, unknown>;
115
+ if (
116
+ typeof descriptor.programId !== "string" ||
117
+ !descriptor.programId.trim()
118
+ ) {
119
+ throw new Error("Program panel query requires a programId.");
120
+ }
121
+ const params =
122
+ descriptor.params &&
123
+ typeof descriptor.params === "object" &&
124
+ !Array.isArray(descriptor.params)
125
+ ? (descriptor.params as Record<string, unknown>)
126
+ : undefined;
127
+ return { programId: descriptor.programId.trim(), params };
128
+ }
129
+
130
+ export function createProgramPanelSourceResolver(options: {
131
+ appId: string;
132
+ run?: (args: {
133
+ programId: string;
134
+ appId: string;
135
+ params?: Record<string, unknown>;
136
+ ctx: { userEmail: string; orgId: string | null };
137
+ triggeredBy: "panel_view";
138
+ }) => Promise<ProgramPanelRunResult>;
139
+ }): PanelSourceResolver<"program", ProgramPanelContext> {
140
+ const run = options.run ?? runDataProgram;
141
+ return {
142
+ source: "program",
143
+ async resolve(request, context) {
144
+ const descriptor = parseProgramPanelDescriptor(request.query);
145
+ const result = await run({
146
+ programId: descriptor.programId,
147
+ appId: options.appId,
148
+ params: descriptor.params,
149
+ ctx: {
150
+ userEmail: context.userEmail,
151
+ orgId: context.orgId ?? null,
152
+ },
153
+ triggeredBy: "panel_view",
154
+ });
155
+ if (result.ok) {
156
+ return {
157
+ rows: result.rows ?? [],
158
+ schema: result.schema,
159
+ truncated: result.truncated,
160
+ };
161
+ }
162
+ if (result.lastGoodRun) return result.lastGoodRun;
163
+ throw new Error(
164
+ `${result.error?.code ?? "program_failed"}: ${result.error?.message ?? "Data program failed."}`,
165
+ );
166
+ },
167
+ };
168
+ }
@@ -0,0 +1,62 @@
1
+ import {
2
+ createSharesTable,
3
+ index,
4
+ now,
5
+ ownableColumns,
6
+ table,
7
+ text,
8
+ } from "../db/schema.js";
9
+
10
+ export interface DashboardStorageSchemaOptions {
11
+ dashboardsTable?: string;
12
+ revisionsTable?: string;
13
+ sharesTable?: string;
14
+ }
15
+
16
+ export function createDashboardStorageSchema(
17
+ options: DashboardStorageSchemaOptions = {},
18
+ ) {
19
+ const dashboardsTable = options.dashboardsTable ?? "dashboards";
20
+ const revisionsTable = options.revisionsTable ?? "dashboard_revisions";
21
+ const sharesTable = options.sharesTable ?? "dashboard_shares";
22
+
23
+ const dashboards = table(dashboardsTable, {
24
+ id: text("id").primaryKey(),
25
+ kind: text("kind").notNull(),
26
+ title: text("title").notNull().default("Untitled"),
27
+ config: text("config").notNull(),
28
+ createdAt: text("created_at").notNull().default(now()),
29
+ updatedAt: text("updated_at").notNull().default(now()),
30
+ updatedBy: text("updated_by"),
31
+ archivedAt: text("archived_at"),
32
+ ...ownableColumns(),
33
+ });
34
+
35
+ const dashboardShares = createSharesTable(sharesTable);
36
+
37
+ const dashboardRevisions = table(
38
+ revisionsTable,
39
+ {
40
+ id: text("id").primaryKey(),
41
+ dashboardId: text("dashboard_id").notNull(),
42
+ kind: text("kind").notNull(),
43
+ title: text("title").notNull(),
44
+ config: text("config").notNull(),
45
+ createdAt: text("created_at").notNull().default(now()),
46
+ createdBy: text("created_by"),
47
+ ...ownableColumns(),
48
+ },
49
+ (revision) => ({
50
+ dashboardCreatedIdx: index(`${revisionsTable}_dashboard_created_idx`).on(
51
+ revision.dashboardId,
52
+ revision.createdAt,
53
+ ),
54
+ }),
55
+ );
56
+
57
+ return { dashboards, dashboardRevisions, dashboardShares };
58
+ }
59
+
60
+ export type DashboardStorageSchema = ReturnType<
61
+ typeof createDashboardStorageSchema
62
+ >;
@@ -0,0 +1,333 @@
1
+ import { randomUUID } from "node:crypto";
2
+
3
+ import { and, desc, eq, inArray, isNotNull, isNull } from "drizzle-orm";
4
+
5
+ import type { AccessContext } from "../sharing/access.js";
6
+ import { accessFilter, assertAccess } from "../sharing/access.js";
7
+ import { registerShareableResource } from "../sharing/registry.js";
8
+ import type { DashboardStorageSchema } from "./schema.js";
9
+
10
+ export interface DashboardRecord<
11
+ TKind extends string = string,
12
+ TConfig = Record<string, unknown>,
13
+ > {
14
+ id: string;
15
+ kind: TKind;
16
+ title: string;
17
+ config: TConfig;
18
+ ownerEmail: string;
19
+ orgId: string | null;
20
+ visibility: "private" | "org" | "public";
21
+ createdAt: string;
22
+ updatedAt: string;
23
+ updatedBy: string | null;
24
+ archivedAt: string | null;
25
+ }
26
+
27
+ export interface DashboardRevisionRecord<
28
+ TKind extends string = string,
29
+ TConfig = Record<string, unknown>,
30
+ > {
31
+ id: string;
32
+ dashboardId: string;
33
+ kind: TKind;
34
+ title: string;
35
+ config: TConfig;
36
+ createdAt: string;
37
+ createdBy: string | null;
38
+ }
39
+
40
+ export interface DashboardWriteInput<
41
+ TKind extends string = string,
42
+ TConfig = Record<string, unknown>,
43
+ > {
44
+ id: string;
45
+ kind: TKind;
46
+ title: string;
47
+ config: TConfig;
48
+ expectedUpdatedAt?: string;
49
+ }
50
+
51
+ export class DashboardStorageConflictError extends Error {
52
+ constructor(id: string) {
53
+ super(`Dashboard "${id}" changed between read and write.`);
54
+ this.name = "DashboardStorageConflictError";
55
+ }
56
+ }
57
+
58
+ export interface DashboardStorageOptions<TKind extends string, TConfig> {
59
+ schema: DashboardStorageSchema;
60
+ getDb: () => any;
61
+ resourceType: string;
62
+ displayName?: string;
63
+ maxRevisions?: number;
64
+ validateKind?: (kind: string) => kind is TKind;
65
+ parseConfig?: (raw: string) => TConfig;
66
+ serializeConfig?: (config: TConfig) => string;
67
+ getResourcePath?: (dashboard: { id: string }) => string;
68
+ allowPublic?: boolean;
69
+ requireOrgMemberForUserShares?: boolean;
70
+ onChange?: (event: {
71
+ type: "change" | "delete";
72
+ dashboard: DashboardRecord<TKind, TConfig>;
73
+ }) => void;
74
+ }
75
+
76
+ function affectedRowCount(result: unknown): number | undefined {
77
+ if (!result || typeof result !== "object") return undefined;
78
+ const candidate = result as Record<string, unknown>;
79
+ for (const key of ["rowCount", "rowsAffected", "changes"]) {
80
+ const value = candidate[key];
81
+ if (typeof value === "number") return value;
82
+ }
83
+ return undefined;
84
+ }
85
+
86
+ function requireWriter(ctx: AccessContext): string {
87
+ const email = ctx.userEmail?.trim().toLowerCase();
88
+ if (!email)
89
+ throw new Error("Dashboard writes require an authenticated user.");
90
+ return email;
91
+ }
92
+
93
+ export function createDashboardStorage<
94
+ TKind extends string = string,
95
+ TConfig = Record<string, unknown>,
96
+ >(options: DashboardStorageOptions<TKind, TConfig>) {
97
+ const parseConfig =
98
+ options.parseConfig ?? ((raw: string) => JSON.parse(raw) as TConfig);
99
+ const serializeConfig = options.serializeConfig ?? JSON.stringify;
100
+ const maxRevisions = Math.max(1, options.maxRevisions ?? 50);
101
+ const { dashboards, dashboardRevisions, dashboardShares } = options.schema;
102
+
103
+ function recordFromRow(row: any): DashboardRecord<TKind, TConfig> {
104
+ if (options.validateKind && !options.validateKind(row.kind)) {
105
+ throw new Error(`Unsupported dashboard kind: ${String(row.kind)}`);
106
+ }
107
+ return {
108
+ id: row.id,
109
+ kind: row.kind as TKind,
110
+ title: row.title,
111
+ config: parseConfig(row.config),
112
+ ownerEmail: row.ownerEmail,
113
+ orgId: row.orgId ?? null,
114
+ visibility: row.visibility,
115
+ createdAt: row.createdAt,
116
+ updatedAt: row.updatedAt,
117
+ updatedBy: row.updatedBy ?? null,
118
+ archivedAt: row.archivedAt ?? null,
119
+ };
120
+ }
121
+
122
+ function revisionFromRow(row: any): DashboardRevisionRecord<TKind, TConfig> {
123
+ return {
124
+ id: row.id,
125
+ dashboardId: row.dashboardId,
126
+ kind: row.kind as TKind,
127
+ title: row.title,
128
+ config: parseConfig(row.config),
129
+ createdAt: row.createdAt,
130
+ createdBy: row.createdBy ?? null,
131
+ };
132
+ }
133
+
134
+ async function get(id: string, ctx: AccessContext) {
135
+ const [row] = await options
136
+ .getDb()
137
+ .select()
138
+ .from(dashboards)
139
+ .where(
140
+ and(
141
+ eq(dashboards.id, id),
142
+ accessFilter(dashboards, dashboardShares, ctx),
143
+ ),
144
+ )
145
+ .limit(1);
146
+ return row ? recordFromRow(row) : null;
147
+ }
148
+
149
+ async function list(
150
+ ctx: AccessContext,
151
+ archive: "active" | "archived" | "all" = "active",
152
+ ) {
153
+ const archiveClause =
154
+ archive === "active"
155
+ ? isNull(dashboards.archivedAt)
156
+ : archive === "archived"
157
+ ? and(
158
+ accessFilter(dashboards, dashboardShares, ctx),
159
+ isNotNull(dashboards.archivedAt),
160
+ )
161
+ : undefined;
162
+ const scope = accessFilter(dashboards, dashboardShares, ctx);
163
+ const where = archiveClause
164
+ ? archive === "archived"
165
+ ? archiveClause
166
+ : and(scope, archiveClause)
167
+ : scope;
168
+ const rows = await options
169
+ .getDb()
170
+ .select()
171
+ .from(dashboards)
172
+ .where(where)
173
+ .orderBy(desc(dashboards.updatedAt));
174
+ return rows.map(recordFromRow);
175
+ }
176
+
177
+ async function pruneRevisions(db: any, dashboardId: string) {
178
+ const rows = await db
179
+ .select({ id: dashboardRevisions.id })
180
+ .from(dashboardRevisions)
181
+ .where(eq(dashboardRevisions.dashboardId, dashboardId))
182
+ .orderBy(desc(dashboardRevisions.createdAt));
183
+ const staleIds = rows
184
+ .slice(maxRevisions)
185
+ .map((row: { id: string }) => row.id);
186
+ if (staleIds.length > 0) {
187
+ await db
188
+ .delete(dashboardRevisions)
189
+ .where(inArray(dashboardRevisions.id, staleIds));
190
+ }
191
+ }
192
+
193
+ async function snapshot(
194
+ db: any,
195
+ dashboard: DashboardRecord<TKind, TConfig>,
196
+ writer: string,
197
+ ) {
198
+ await db.insert(dashboardRevisions).values({
199
+ id: `dashboard-revision-${randomUUID()}`,
200
+ dashboardId: dashboard.id,
201
+ kind: dashboard.kind,
202
+ title: dashboard.title,
203
+ config: serializeConfig(dashboard.config),
204
+ createdBy: writer,
205
+ ownerEmail: dashboard.ownerEmail,
206
+ orgId: dashboard.orgId,
207
+ visibility: dashboard.visibility,
208
+ });
209
+ await pruneRevisions(db, dashboard.id);
210
+ }
211
+
212
+ async function write(
213
+ input: DashboardWriteInput<TKind, TConfig>,
214
+ ctx: AccessContext,
215
+ ) {
216
+ const writer = requireWriter(ctx);
217
+ const existing = await get(input.id, ctx);
218
+ const db = options.getDb();
219
+ if (!existing) {
220
+ if (input.expectedUpdatedAt !== undefined) {
221
+ throw new DashboardStorageConflictError(input.id);
222
+ }
223
+ await db.insert(dashboards).values({
224
+ id: input.id,
225
+ kind: input.kind,
226
+ title: input.title,
227
+ config: serializeConfig(input.config),
228
+ ownerEmail: writer,
229
+ orgId: ctx.orgId ?? null,
230
+ visibility: "private",
231
+ updatedBy: writer,
232
+ });
233
+ } else {
234
+ await assertAccess(options.resourceType, input.id, "editor", ctx);
235
+ const values = {
236
+ kind: input.kind,
237
+ title: input.title,
238
+ config: serializeConfig(input.config),
239
+ updatedAt: new Date().toISOString(),
240
+ updatedBy: writer,
241
+ };
242
+ await db.transaction(async (tx: any) => {
243
+ if (input.expectedUpdatedAt !== undefined) {
244
+ const result = await tx
245
+ .update(dashboards)
246
+ .set(values)
247
+ .where(
248
+ and(
249
+ eq(dashboards.id, input.id),
250
+ eq(dashboards.updatedAt, input.expectedUpdatedAt),
251
+ ),
252
+ );
253
+ const affected = affectedRowCount(result);
254
+ if (affected === undefined) {
255
+ throw new Error(
256
+ "Dashboard storage requires affected-row counts for conditional writes.",
257
+ );
258
+ }
259
+ if (affected === 0) throw new DashboardStorageConflictError(input.id);
260
+ } else {
261
+ await tx
262
+ .update(dashboards)
263
+ .set(values)
264
+ .where(eq(dashboards.id, input.id));
265
+ }
266
+ await snapshot(tx, existing, writer);
267
+ });
268
+ }
269
+ const stored = await get(input.id, ctx);
270
+ if (!stored) throw new Error(`Dashboard "${input.id}" was not persisted.`);
271
+ options.onChange?.({ type: "change", dashboard: stored });
272
+ return stored;
273
+ }
274
+
275
+ async function listRevisions(id: string, ctx: AccessContext) {
276
+ const dashboard = await get(id, ctx);
277
+ if (!dashboard) return [];
278
+ await assertAccess(options.resourceType, id, "viewer", ctx);
279
+ const rows = await options
280
+ .getDb()
281
+ .select()
282
+ .from(dashboardRevisions)
283
+ .where(eq(dashboardRevisions.dashboardId, id))
284
+ .orderBy(desc(dashboardRevisions.createdAt))
285
+ .limit(maxRevisions);
286
+ return rows.map(revisionFromRow);
287
+ }
288
+
289
+ async function restore(id: string, revisionId: string, ctx: AccessContext) {
290
+ const existing = await get(id, ctx);
291
+ if (!existing) return null;
292
+ await assertAccess(options.resourceType, id, "editor", ctx);
293
+ const [row] = await options
294
+ .getDb()
295
+ .select()
296
+ .from(dashboardRevisions)
297
+ .where(
298
+ and(
299
+ eq(dashboardRevisions.id, revisionId),
300
+ eq(dashboardRevisions.dashboardId, id),
301
+ ),
302
+ )
303
+ .limit(1);
304
+ if (!row) return null;
305
+ const revision = revisionFromRow(row);
306
+ return write(
307
+ {
308
+ id,
309
+ kind: revision.kind,
310
+ title: revision.title,
311
+ config: revision.config,
312
+ expectedUpdatedAt: existing.updatedAt,
313
+ },
314
+ ctx,
315
+ );
316
+ }
317
+
318
+ function registerShareable() {
319
+ registerShareableResource({
320
+ type: options.resourceType,
321
+ resourceTable: dashboards,
322
+ sharesTable: dashboardShares,
323
+ displayName: options.displayName ?? "Dashboard",
324
+ titleColumn: "title",
325
+ getResourcePath: options.getResourcePath,
326
+ allowPublic: options.allowPublic,
327
+ requireOrgMemberForUserShares: options.requireOrgMemberForUserShares,
328
+ getDb: options.getDb,
329
+ });
330
+ }
331
+
332
+ return { get, list, write, listRevisions, restore, registerShareable };
333
+ }