@agent-native/core 0.79.16 → 0.79.18

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 (344) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +14 -0
  3. package/corpus/core/docs/content/file-uploads.md +11 -4
  4. package/corpus/core/docs/content/locales/ar-SA/multi-app-workspace.md +3 -3
  5. package/corpus/core/docs/content/locales/de-DE/multi-app-workspace.md +3 -3
  6. package/corpus/core/docs/content/locales/es-ES/multi-app-workspace.md +3 -3
  7. package/corpus/core/docs/content/locales/fr-FR/multi-app-workspace.md +3 -3
  8. package/corpus/core/docs/content/locales/hi-IN/multi-app-workspace.md +3 -3
  9. package/corpus/core/docs/content/locales/ja-JP/multi-app-workspace.md +3 -3
  10. package/corpus/core/docs/content/locales/ko-KR/multi-app-workspace.md +3 -3
  11. package/corpus/core/docs/content/locales/pt-BR/multi-app-workspace.md +3 -3
  12. package/corpus/core/docs/content/locales/zh-CN/multi-app-workspace.md +3 -3
  13. package/corpus/core/docs/content/locales/zh-TW/multi-app-workspace.md +3 -3
  14. package/corpus/core/docs/content/multi-app-workspace.md +3 -3
  15. package/corpus/core/docs/content/template-clips.md +14 -5
  16. package/corpus/core/package.json +1 -1
  17. package/corpus/core/src/agent/engine/builder-engine.ts +8 -3
  18. package/corpus/core/src/cli/mcp.ts +11 -6
  19. package/corpus/core/src/client/AssistantChat.tsx +6 -2
  20. package/corpus/core/src/client/chat/markdown-renderer.tsx +1 -0
  21. package/corpus/core/src/client/index.ts +7 -1
  22. package/corpus/core/src/client/org/OrgSwitcher.tsx +5 -5
  23. package/corpus/core/src/client/org/TeamPage.tsx +11 -3
  24. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +216 -0
  25. package/corpus/core/src/client/settings/index.ts +5 -0
  26. package/corpus/core/src/file-upload/index.ts +1 -0
  27. package/corpus/core/src/file-upload/registry.ts +22 -6
  28. package/corpus/core/src/file-upload/types.ts +6 -1
  29. package/corpus/core/src/integrations/adapters/email.ts +26 -18
  30. package/corpus/core/src/integrations/adapters/slack.ts +8 -7
  31. package/corpus/core/src/integrations/adapters/telegram.ts +9 -11
  32. package/corpus/core/src/integrations/adapters/whatsapp.ts +12 -9
  33. package/corpus/core/src/integrations/plugin.ts +79 -13
  34. package/corpus/core/src/onboarding/default-steps.ts +6 -3
  35. package/corpus/core/src/onboarding/types.ts +1 -1
  36. package/corpus/core/src/org/handlers.ts +1 -1
  37. package/corpus/core/src/server/agent-chat-plugin.ts +4 -7
  38. package/corpus/core/src/server/better-auth-instance.ts +19 -62
  39. package/corpus/core/src/server/core-routes-plugin.ts +93 -180
  40. package/corpus/core/src/server/create-server.ts +55 -116
  41. package/corpus/core/src/server/email-actions.ts +3 -6
  42. package/corpus/core/src/server/email.ts +52 -17
  43. package/corpus/core/src/server/index.ts +2 -1
  44. package/corpus/core/src/server/scoped-key-storage.ts +223 -0
  45. package/corpus/core/src/shared/agent-env.ts +3 -3
  46. package/corpus/core/src/sharing/actions/share-resource.ts +1 -1
  47. package/corpus/core/src/styles/agent-native.css +55 -3
  48. package/corpus/core/src/templates/workspace-root/AGENTS.md +3 -2
  49. package/corpus/core/src/templates/workspace-root/README.md +1 -1
  50. package/corpus/core/src/templates/workspace-root/scripts/repair-workspace-org.ts +29 -90
  51. package/corpus/core/src/terminal/pty-server.ts +3 -38
  52. package/corpus/core/src/triggers/dispatcher.ts +1 -2
  53. package/corpus/templates/.retired/issues/package.json +7 -0
  54. package/corpus/templates/.retired/meeting-notes/package.json +7 -0
  55. package/corpus/templates/.retired/recruiting/package.json +7 -0
  56. package/corpus/templates/.retired/scheduling/package.json +7 -0
  57. package/corpus/templates/.retired/voice/package.json +7 -0
  58. package/corpus/templates/analytics/app/components/layout/Header.tsx +0 -1
  59. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +0 -17
  60. package/corpus/templates/analytics/app/i18n/zh-TW.ts +7 -1
  61. package/corpus/templates/analytics/app/i18n-data.ts +71 -2
  62. package/corpus/templates/analytics/app/pages/Settings.tsx +120 -73
  63. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +0 -11
  64. package/corpus/templates/analytics/app/routes/catalog.tsx +33 -176
  65. package/corpus/templates/analytics/app/routes/team.tsx +3 -2
  66. package/corpus/templates/analytics/changelog/2026-06-26-dashboard-templates-are-tucked-into-settings-and-show-simple.md +6 -0
  67. package/corpus/templates/analytics/changelog/2026-06-26-the-sessions-page-shows-a-single-page-title-instead-of-repea.md +6 -0
  68. package/corpus/templates/analytics/server/lib/dashboard-catalog.ts +37 -28
  69. package/corpus/templates/assets/app/routes/settings.tsx +65 -43
  70. package/corpus/templates/assets/app/routes/team.tsx +2 -10
  71. package/corpus/templates/brain/app/routes/settings.tsx +390 -358
  72. package/corpus/templates/brain/app/routes/team.tsx +2 -8
  73. package/corpus/templates/calendar/actions/db-connect.ts +5 -29
  74. package/corpus/templates/calendar/app/components/CloudUpgrade.tsx +8 -51
  75. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +2 -1
  76. package/corpus/templates/calendar/app/components/calendar/GoogleSetupWizard.tsx +4 -2
  77. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +0 -2
  78. package/corpus/templates/calendar/app/pages/Settings.tsx +304 -271
  79. package/corpus/templates/calendar/app/routes/_app.team.tsx +2 -17
  80. package/corpus/templates/calendar/server/handlers/google-auth.ts +22 -9
  81. package/corpus/templates/calendar/server/lib/booking-emails.ts +1 -1
  82. package/corpus/templates/calendar/server/lib/event-guest-notifications.ts +1 -1
  83. package/corpus/templates/calendar/server/lib/google-calendar.ts +48 -28
  84. package/corpus/templates/chat/app/components/layout/Header.tsx +0 -1
  85. package/corpus/templates/chat/app/hooks/use-navigation-state.ts +4 -2
  86. package/corpus/templates/chat/app/routes/settings.tsx +54 -54
  87. package/corpus/templates/chat/app/routes/team.tsx +2 -10
  88. package/corpus/templates/clips/.agents/skills/ai-video-tools/SKILL.md +7 -2
  89. package/corpus/templates/clips/.agents/skills/recording/SKILL.md +1 -1
  90. package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +7 -0
  91. package/corpus/templates/clips/AGENTS.md +8 -0
  92. package/corpus/templates/clips/actions/invite-member.ts +1 -1
  93. package/corpus/templates/clips/actions/request-transcript.ts +1 -1
  94. package/corpus/templates/clips/app/components/library/library-layout.tsx +3 -3
  95. package/corpus/templates/clips/app/components/player/transcript-panel.tsx +2 -0
  96. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +629 -643
  97. package/corpus/templates/clips/app/routes/r.$recordingId.tsx +219 -155
  98. package/corpus/templates/clips/changelog/2026-06-26-long-desktop-recordings-are-checkpointed-so-macos-finalizati.md +6 -0
  99. package/corpus/templates/clips/changelog/2026-06-26-non-mac-desktop-recordings-wait-for-upload-transcription.md +6 -0
  100. package/corpus/templates/clips/changelog/2026-06-26-s3-compatible-storage-keys-now-save-securely-from-clips-sett.md +6 -0
  101. package/corpus/templates/clips/changelog/2026-06-26-shared-clip-links-guide-agents-through-processing-transcripts.md +6 -0
  102. package/corpus/templates/clips/changelog/2026-06-26-the-clip-details-sidebar-now-collapses-into-a-slide-out-pane.md +6 -0
  103. package/corpus/templates/clips/changelog/2026-06-26-the-clips-library-now-matches-the-shared-recessed-shell-styl.md +6 -0
  104. package/corpus/templates/clips/desktop/src/lib/recorder.ts +55 -3
  105. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +217 -5
  106. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -0
  107. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +176 -5
  108. package/corpus/templates/clips/server/lib/public-agent-context.ts +31 -0
  109. package/corpus/templates/clips/server/lib/s3-upload-provider.ts +60 -14
  110. package/corpus/templates/clips/server/lib/video-storage.ts +37 -9
  111. package/corpus/templates/clips/server/routes/api/agent-transcript.json.get.ts +5 -0
  112. package/corpus/templates/content/app/components/layout/Header.tsx +0 -1
  113. package/corpus/templates/content/app/components/sidebar/NotionButton.tsx +1 -0
  114. package/corpus/templates/content/app/routes/_app.settings.tsx +54 -55
  115. package/corpus/templates/content/app/routes/_app.team.tsx +2 -28
  116. package/corpus/templates/content/server/lib/notion.ts +43 -11
  117. package/corpus/templates/content/server/routes/api/notion/auth-url.get.ts +2 -2
  118. package/corpus/templates/content/server/routes/api/notion/status.get.ts +3 -3
  119. package/corpus/templates/design/app/routes/settings.tsx +60 -38
  120. package/corpus/templates/design/app/routes/team.tsx +2 -10
  121. package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +2 -2
  122. package/corpus/templates/dispatch/app/routes/settings.tsx +70 -55
  123. package/corpus/templates/dispatch/app/routes/team.tsx +5 -1
  124. package/corpus/templates/forms/actions/db-connect.ts +7 -36
  125. package/corpus/templates/forms/actions/navigate.ts +2 -2
  126. package/corpus/templates/forms/app/components/CloudUpgrade.tsx +8 -47
  127. package/corpus/templates/forms/app/components/layout/Header.tsx +0 -1
  128. package/corpus/templates/forms/app/components/layout/Layout.tsx +42 -25
  129. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +35 -76
  130. package/corpus/templates/forms/app/global.css +9 -25
  131. package/corpus/templates/forms/app/hooks/use-navigation-state.ts +4 -4
  132. package/corpus/templates/forms/app/i18n/ar-SA.ts +1 -0
  133. package/corpus/templates/forms/app/i18n/de-DE.ts +1 -0
  134. package/corpus/templates/forms/app/i18n/en-US.ts +1 -0
  135. package/corpus/templates/forms/app/i18n/es-ES.ts +1 -0
  136. package/corpus/templates/forms/app/i18n/fr-FR.ts +1 -0
  137. package/corpus/templates/forms/app/i18n/hi-IN.ts +1 -0
  138. package/corpus/templates/forms/app/i18n/ja-JP.ts +1 -0
  139. package/corpus/templates/forms/app/i18n/ko-KR.ts +1 -0
  140. package/corpus/templates/forms/app/i18n/pt-BR.ts +1 -0
  141. package/corpus/templates/forms/app/i18n/zh-CN.ts +1 -0
  142. package/corpus/templates/forms/app/i18n/zh-TW.ts +1 -0
  143. package/corpus/templates/forms/app/lib/route-prewarm.ts +9 -8
  144. package/corpus/templates/forms/app/pages/AskPage.tsx +70 -0
  145. package/corpus/templates/forms/app/pages/FormsListPage.tsx +74 -81
  146. package/corpus/templates/forms/app/root.tsx +1 -1
  147. package/corpus/templates/forms/app/routes/_app.ask.tsx +14 -0
  148. package/corpus/templates/forms/app/routes/_app.settings.tsx +54 -54
  149. package/corpus/templates/forms/app/routes/_app.team.tsx +2 -9
  150. package/corpus/templates/forms/app/routes/_index.tsx +21 -130
  151. package/corpus/templates/forms/changelog/2026-06-26-ask-forms-opens-as-a-standard-app-tab-with-a-simpler-sidebar.md +6 -0
  152. package/corpus/templates/forms/changelog/2026-06-26-forms-are-shown-in-a-tighter-row-list-with-a-denser-sidebar.md +6 -0
  153. package/corpus/templates/forms/shared/navigation.ts +4 -2
  154. package/corpus/templates/macros/app/routes/settings.tsx +54 -54
  155. package/corpus/templates/macros/app/routes/team.tsx +2 -10
  156. package/corpus/templates/mail/actions/helpers.ts +2 -2
  157. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +2 -1
  158. package/corpus/templates/mail/app/pages/SettingsPage.tsx +5 -5
  159. package/corpus/templates/mail/app/routes/team.tsx +3 -2
  160. package/corpus/templates/mail/server/handlers/emails.ts +3 -2
  161. package/corpus/templates/mail/server/handlers/google-auth.ts +19 -12
  162. package/corpus/templates/mail/server/lib/automation-engine.ts +3 -2
  163. package/corpus/templates/mail/server/lib/email-state.ts +2 -3
  164. package/corpus/templates/mail/server/lib/google-auth.ts +28 -11
  165. package/corpus/templates/mail/server/lib/jobs.ts +3 -2
  166. package/corpus/templates/mail/server/onboarding.ts +9 -6
  167. package/corpus/templates/mail/server/routes/api/attachments.get.ts +3 -3
  168. package/corpus/templates/plan/actions/request-plan-access.ts +1 -1
  169. package/corpus/templates/plan/app/components/layout/Header.tsx +0 -1
  170. package/corpus/templates/plan/app/hooks/use-navigation-state.ts +4 -2
  171. package/corpus/templates/plan/app/lib/route-prewarm.ts +0 -4
  172. package/corpus/templates/plan/app/routes/settings.tsx +75 -73
  173. package/corpus/templates/plan/app/routes/team.tsx +2 -8
  174. package/corpus/templates/plan/server/lib/comment-notifications.ts +1 -1
  175. package/corpus/templates/slides/actions/check-image-gen.ts +2 -1
  176. package/corpus/templates/slides/actions/db-connect.ts +7 -45
  177. package/corpus/templates/slides/actions/edit-image.ts +6 -3
  178. package/corpus/templates/slides/actions/generate-image-api.ts +1 -1
  179. package/corpus/templates/slides/actions/generate-image.ts +2 -2
  180. package/corpus/templates/slides/actions/generate-slides-ai.ts +5 -3
  181. package/corpus/templates/slides/actions/image-gen-status.ts +4 -3
  182. package/corpus/templates/slides/actions/image-search.ts +6 -3
  183. package/corpus/templates/slides/actions/search-images.ts +6 -3
  184. package/corpus/templates/slides/app/components/CloudUpgrade.tsx +8 -51
  185. package/corpus/templates/slides/app/components/layout/Header.tsx +0 -1
  186. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +0 -2
  187. package/corpus/templates/slides/app/routes/settings.tsx +54 -54
  188. package/corpus/templates/slides/app/routes/team.tsx +2 -12
  189. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +9 -9
  190. package/corpus/templates/slides/server/handlers/image-gen.ts +5 -1
  191. package/corpus/templates/slides/server/handlers/image-providers/gemini.ts +9 -1
  192. package/corpus/templates/slides/server/handlers/image-providers/index.ts +18 -8
  193. package/corpus/templates/slides/server/handlers/image-providers/openai.ts +10 -4
  194. package/corpus/templates/slides/server/handlers/image-providers/types.ts +1 -0
  195. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +59 -34
  196. package/corpus/templates/slides/server/register-secrets.ts +60 -0
  197. package/corpus/templates/videos/actions/db-connect.ts +7 -45
  198. package/corpus/templates/videos/app/components/CloudUpgrade.tsx +8 -51
  199. package/corpus/templates/videos/app/components/Sidebar.tsx +1 -9
  200. package/corpus/templates/videos/app/components/layout/Header.tsx +0 -1
  201. package/corpus/templates/videos/app/components/layout/NavSidebar.tsx +0 -3
  202. package/corpus/templates/videos/app/routes/settings.tsx +54 -54
  203. package/corpus/templates/videos/app/routes/team.tsx +2 -8
  204. package/dist/agent/engine/builder-engine.d.ts.map +1 -1
  205. package/dist/agent/engine/builder-engine.js +8 -3
  206. package/dist/agent/engine/builder-engine.js.map +1 -1
  207. package/dist/cli/mcp.d.ts.map +1 -1
  208. package/dist/cli/mcp.js +3 -4
  209. package/dist/cli/mcp.js.map +1 -1
  210. package/dist/client/AssistantChat.d.ts.map +1 -1
  211. package/dist/client/AssistantChat.js +6 -2
  212. package/dist/client/AssistantChat.js.map +1 -1
  213. package/dist/client/chat/markdown-renderer.d.ts.map +1 -1
  214. package/dist/client/chat/markdown-renderer.js +1 -0
  215. package/dist/client/chat/markdown-renderer.js.map +1 -1
  216. package/dist/client/index.d.ts +1 -1
  217. package/dist/client/index.d.ts.map +1 -1
  218. package/dist/client/index.js +1 -1
  219. package/dist/client/index.js.map +1 -1
  220. package/dist/client/org/OrgSwitcher.d.ts +3 -3
  221. package/dist/client/org/OrgSwitcher.js +2 -2
  222. package/dist/client/org/OrgSwitcher.js.map +1 -1
  223. package/dist/client/org/TeamPage.d.ts +6 -1
  224. package/dist/client/org/TeamPage.d.ts.map +1 -1
  225. package/dist/client/org/TeamPage.js +2 -2
  226. package/dist/client/org/TeamPage.js.map +1 -1
  227. package/dist/client/settings/SettingsTabsPage.d.ts +27 -0
  228. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -0
  229. package/dist/client/settings/SettingsTabsPage.js +108 -0
  230. package/dist/client/settings/SettingsTabsPage.js.map +1 -0
  231. package/dist/client/settings/index.d.ts +1 -0
  232. package/dist/client/settings/index.d.ts.map +1 -1
  233. package/dist/client/settings/index.js +1 -0
  234. package/dist/client/settings/index.js.map +1 -1
  235. package/dist/collab/awareness.d.ts +2 -2
  236. package/dist/collab/awareness.d.ts.map +1 -1
  237. package/dist/collab/routes.d.ts +1 -1
  238. package/dist/file-upload/index.d.ts +1 -1
  239. package/dist/file-upload/index.d.ts.map +1 -1
  240. package/dist/file-upload/index.js +1 -1
  241. package/dist/file-upload/index.js.map +1 -1
  242. package/dist/file-upload/registry.d.ts +1 -0
  243. package/dist/file-upload/registry.d.ts.map +1 -1
  244. package/dist/file-upload/registry.js +24 -6
  245. package/dist/file-upload/registry.js.map +1 -1
  246. package/dist/file-upload/types.d.ts +6 -1
  247. package/dist/file-upload/types.d.ts.map +1 -1
  248. package/dist/file-upload/types.js.map +1 -1
  249. package/dist/integrations/adapters/email.d.ts.map +1 -1
  250. package/dist/integrations/adapters/email.js +18 -18
  251. package/dist/integrations/adapters/email.js.map +1 -1
  252. package/dist/integrations/adapters/slack.d.ts.map +1 -1
  253. package/dist/integrations/adapters/slack.js +8 -7
  254. package/dist/integrations/adapters/slack.js.map +1 -1
  255. package/dist/integrations/adapters/telegram.d.ts.map +1 -1
  256. package/dist/integrations/adapters/telegram.js +9 -9
  257. package/dist/integrations/adapters/telegram.js.map +1 -1
  258. package/dist/integrations/adapters/whatsapp.d.ts.map +1 -1
  259. package/dist/integrations/adapters/whatsapp.js +10 -9
  260. package/dist/integrations/adapters/whatsapp.js.map +1 -1
  261. package/dist/integrations/plugin.d.ts.map +1 -1
  262. package/dist/integrations/plugin.js +47 -13
  263. package/dist/integrations/plugin.js.map +1 -1
  264. package/dist/notifications/routes.d.ts +1 -1
  265. package/dist/onboarding/default-steps.d.ts.map +1 -1
  266. package/dist/onboarding/default-steps.js +6 -5
  267. package/dist/onboarding/default-steps.js.map +1 -1
  268. package/dist/onboarding/types.d.ts +1 -1
  269. package/dist/onboarding/types.d.ts.map +1 -1
  270. package/dist/onboarding/types.js.map +1 -1
  271. package/dist/org/handlers.js +1 -1
  272. package/dist/org/handlers.js.map +1 -1
  273. package/dist/progress/routes.d.ts +1 -1
  274. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  275. package/dist/server/agent-chat-plugin.js +4 -6
  276. package/dist/server/agent-chat-plugin.js.map +1 -1
  277. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  278. package/dist/server/better-auth-instance.d.ts.map +1 -1
  279. package/dist/server/better-auth-instance.js +17 -56
  280. package/dist/server/better-auth-instance.js.map +1 -1
  281. package/dist/server/core-routes-plugin.d.ts +1 -1
  282. package/dist/server/core-routes-plugin.d.ts.map +1 -1
  283. package/dist/server/core-routes-plugin.js +67 -153
  284. package/dist/server/core-routes-plugin.js.map +1 -1
  285. package/dist/server/create-server.d.ts +3 -9
  286. package/dist/server/create-server.d.ts.map +1 -1
  287. package/dist/server/create-server.js +37 -91
  288. package/dist/server/create-server.js.map +1 -1
  289. package/dist/server/email-actions.d.ts +2 -3
  290. package/dist/server/email-actions.d.ts.map +1 -1
  291. package/dist/server/email-actions.js +3 -6
  292. package/dist/server/email-actions.js.map +1 -1
  293. package/dist/server/email.d.ts +3 -3
  294. package/dist/server/email.d.ts.map +1 -1
  295. package/dist/server/email.js +40 -19
  296. package/dist/server/email.js.map +1 -1
  297. package/dist/server/index.d.ts +3 -3
  298. package/dist/server/index.d.ts.map +1 -1
  299. package/dist/server/index.js +3 -3
  300. package/dist/server/index.js.map +1 -1
  301. package/dist/server/scoped-key-storage.d.ts +22 -0
  302. package/dist/server/scoped-key-storage.d.ts.map +1 -0
  303. package/dist/server/scoped-key-storage.js +152 -0
  304. package/dist/server/scoped-key-storage.js.map +1 -0
  305. package/dist/shared/agent-env.d.ts +3 -3
  306. package/dist/shared/agent-env.d.ts.map +1 -1
  307. package/dist/shared/agent-env.js +3 -3
  308. package/dist/shared/agent-env.js.map +1 -1
  309. package/dist/sharing/actions/share-resource.js +1 -1
  310. package/dist/sharing/actions/share-resource.js.map +1 -1
  311. package/dist/styles/agent-native.css +55 -3
  312. package/dist/templates/workspace-root/AGENTS.md +3 -2
  313. package/dist/templates/workspace-root/README.md +1 -1
  314. package/dist/templates/workspace-root/scripts/repair-workspace-org.ts +29 -90
  315. package/dist/terminal/pty-server.d.ts.map +1 -1
  316. package/dist/terminal/pty-server.js +3 -36
  317. package/dist/terminal/pty-server.js.map +1 -1
  318. package/dist/triggers/dispatcher.js +1 -1
  319. package/dist/triggers/dispatcher.js.map +1 -1
  320. package/docs/content/file-uploads.md +11 -4
  321. package/docs/content/locales/ar-SA/multi-app-workspace.md +3 -3
  322. package/docs/content/locales/de-DE/multi-app-workspace.md +3 -3
  323. package/docs/content/locales/es-ES/multi-app-workspace.md +3 -3
  324. package/docs/content/locales/fr-FR/multi-app-workspace.md +3 -3
  325. package/docs/content/locales/hi-IN/multi-app-workspace.md +3 -3
  326. package/docs/content/locales/ja-JP/multi-app-workspace.md +3 -3
  327. package/docs/content/locales/ko-KR/multi-app-workspace.md +3 -3
  328. package/docs/content/locales/pt-BR/multi-app-workspace.md +3 -3
  329. package/docs/content/locales/zh-CN/multi-app-workspace.md +3 -3
  330. package/docs/content/locales/zh-TW/multi-app-workspace.md +3 -3
  331. package/docs/content/multi-app-workspace.md +3 -3
  332. package/docs/content/template-clips.md +14 -5
  333. package/package.json +1 -1
  334. package/src/templates/workspace-root/AGENTS.md +3 -2
  335. package/src/templates/workspace-root/README.md +1 -1
  336. package/src/templates/workspace-root/scripts/repair-workspace-org.ts +29 -90
  337. package/corpus/core/src/server/env-var-writes.ts +0 -16
  338. package/corpus/templates/analytics/app/pages/Team.tsx +0 -9
  339. package/corpus/templates/calendar/app/pages/Team.tsx +0 -12
  340. package/corpus/templates/mail/app/pages/Team.tsx +0 -17
  341. package/dist/server/env-var-writes.d.ts +0 -10
  342. package/dist/server/env-var-writes.d.ts.map +0 -1
  343. package/dist/server/env-var-writes.js +0 -18
  344. package/dist/server/env-var-writes.js.map +0 -1
@@ -1,5 +1,3 @@
1
- import path from "node:path";
2
-
3
1
  import {
4
2
  defineEventHandler,
5
3
  setResponseStatus,
@@ -46,7 +44,7 @@ import { mountDbAdminRoutes } from "../db-admin/routes.js";
46
44
  import { getDbExec } from "../db/client.js";
47
45
  import {
48
46
  uploadFile,
49
- getActiveFileUploadProvider,
47
+ getActiveFileUploadProviderForRequest,
50
48
  listFileUploadProviders,
51
49
  } from "../file-upload/index.js";
52
50
  import { handleMcpConnect } from "../mcp/connect-route.js";
@@ -57,10 +55,8 @@ import {
57
55
  } from "../mcp/oauth-route.js";
58
56
  import { registerBuiltinNotificationChannels } from "../notifications/channels.js";
59
57
  import { createNotificationsHandler } from "../notifications/routes.js";
60
- import { listOnboardingSteps } from "../onboarding/registry.js";
61
58
  import { getOrgContext } from "../org/context.js";
62
59
  import { createProgressHandler } from "../progress/routes.js";
63
- import { findWorkspaceRoot } from "../scripts/utils.js";
64
60
  import { registerFrameworkSecrets } from "../secrets/register-framework-secrets.js";
65
61
  import {
66
62
  createListSecretsHandler,
@@ -124,14 +120,12 @@ import {
124
120
  getAllowedCorsOrigin,
125
121
  readCorsAllowedOrigins,
126
122
  } from "./cors-origins.js";
127
- import { upsertEnvFile } from "./create-server.js";
128
123
  import type { EnvKeyConfig } from "./create-server.js";
129
124
  import {
130
125
  canUseDeployCredentialFallbackForRequest,
131
126
  resolveSecret,
132
127
  } from "./credential-provider.js";
133
128
  import { createEmbedStartRouteHandler } from "./embed-route.js";
134
- import { isEnvVarWriteAllowed } from "./env-var-writes.js";
135
129
  import {
136
130
  getH3App,
137
131
  awaitBootstrap,
@@ -151,6 +145,12 @@ import { createOpenRouteHandler } from "./open-route.js";
151
145
  import { createPollEventsHandler } from "./poll-events.js";
152
146
  import { createPollHandler } from "./poll.js";
153
147
  import { runWithRequestContext } from "./request-context.js";
148
+ import {
149
+ findUnsupportedScopedKeyNames,
150
+ saveKeyValuesToScopedSecrets,
151
+ ScopedKeyStorageError,
152
+ type ScopedKeySaveRequestScope,
153
+ } from "./scoped-key-storage.js";
154
154
  import { createTranscribeVoiceHandler } from "./transcribe-voice.js";
155
155
  import { createVoiceProvidersStatusHandler } from "./voice-providers-status.js";
156
156
 
@@ -340,10 +340,6 @@ function parseBuilderCallbackBoolean(
340
340
  return /^(1|true)$/i.test(value);
341
341
  }
342
342
 
343
- const PROVIDER_ENV_VAR_KEYS = new Set(
344
- Object.values(PROVIDER_ENV_META).map(({ envVar }) => envVar),
345
- );
346
-
347
343
  // Raster-only data-URI allowlist for avatar writes. SVG is deliberately absent:
348
344
  // data:image/svg+xml payloads can carry inline <script> and event-handler
349
345
  // attributes that execute when the browser renders them as an <img> src or
@@ -652,7 +648,7 @@ export interface CoreRoutesPluginOptions {
652
648
  * GET /_agent-native/ping — health check
653
649
  * GET /_agent-native/health — DB liveness probe + scale-to-zero warmup
654
650
  * GET /_agent-native/env-status — env key configuration status (when envKeys provided)
655
- * POST /_agent-native/env-vars — save env vars to .env (when envKeys provided)
651
+ * POST /_agent-native/env-vars — compatibility route that saves keys to scoped DB secrets
656
652
  * GET /_agent-native/application-state/:key — read application state
657
653
  * PUT /_agent-native/application-state/:key — write application state
658
654
  * DELETE /_agent-native/application-state/:key — delete application state
@@ -681,20 +677,9 @@ export function createCoreRoutesPlugin(
681
677
  try {
682
678
  await awaitBootstrap(nitroApp);
683
679
 
684
- // Restore env vars from the settings table. On serverless, .env
685
- // writes don't persist across invocations the DB is the durable
686
- // store. Only set keys that are currently empty so explicit env
687
- // vars (Netlify dashboard, process-level) always win.
688
- //
689
- // GATED: only rehydrate into `process.env` on local-dev SQLite (or
690
- // with the explicit single-tenant opt-in). On a shared-DB hosted
691
- // multi-tenant deploy the `persisted-env-vars` row is deployment-wide
692
- // global state — pushing user-supplied values into `process.env` from
693
- // it would let any one tenant's writes (or a stale dev seed) leak
694
- // into every other tenant's process. The opt-out scrub of legacy
695
- // BUILDER_* values still runs unconditionally so existing rows on
696
- // multi-tenant deploys self-heal, but new env-var writes never land
697
- // in `process.env` outside the allowed contexts.
680
+ // Legacy cleanup: key saves now go to scoped app_secrets rows. Do not
681
+ // rehydrate the old deployment-global `persisted-env-vars` row into
682
+ // process.env; keep only the Builder scrub so stale leaked keys self-heal.
698
683
  try {
699
684
  const persisted = (await getSetting("persisted-env-vars")) as Record<
700
685
  string,
@@ -702,15 +687,10 @@ export function createCoreRoutesPlugin(
702
687
  > | null;
703
688
  if (persisted) {
704
689
  const builderKeys = new Set<string>(BUILDER_ENV_KEYS);
705
- const writesAllowed = isEnvVarWriteAllowed();
706
690
  let scrubbed = 0;
707
- for (const [k, v] of Object.entries(persisted)) {
691
+ for (const k of Object.keys(persisted)) {
708
692
  if (builderKeys.has(k)) {
709
693
  scrubbed++;
710
- continue;
711
- }
712
- if (writesAllowed && typeof v === "string" && !process.env[k]) {
713
- process.env[k] = v;
714
694
  }
715
695
  }
716
696
  if (scrubbed > 0) {
@@ -2229,24 +2209,7 @@ export function createCoreRoutesPlugin(
2229
2209
  ];
2230
2210
  {
2231
2211
  const envKeys = [...frameworkEnvKeys, ...(options.envKeys ?? [])];
2232
-
2233
- // Onboarding form fields are resolved per-request so late-registered
2234
- // steps (and template overrides) are picked up without a restart.
2235
- // Builder CLI auth writes scoped Builder credentials through the
2236
- // credential provider, never through the deploy-global env sink.
2237
- const collectOnboardingKeys = (): Set<string> => {
2238
- const keys = new Set<string>();
2239
- for (const step of listOnboardingSteps()) {
2240
- for (const method of step.methods) {
2241
- if (method.kind === "form") {
2242
- for (const field of method.payload.fields) {
2243
- if (field?.key) keys.add(field.key);
2244
- }
2245
- }
2246
- }
2247
- }
2248
- return keys;
2249
- };
2212
+ const allowedEnvKeyNames = envKeys.map(({ key }) => key);
2250
2213
 
2251
2214
  getH3App(nitroApp).use(
2252
2215
  `${P}/env-status`,
@@ -2264,12 +2227,11 @@ export function createCoreRoutesPlugin(
2264
2227
  }
2265
2228
  return Promise.all(
2266
2229
  envKeys.map(async (cfg) => {
2267
- const isProviderKey = PROVIDER_ENV_VAR_KEYS.has(cfg.key);
2268
- const configured = isProviderKey
2269
- ? await runWithRequestContext({ userEmail, orgId }, () =>
2270
- resolveSecret(cfg.key).then(Boolean),
2271
- )
2272
- : !!process.env[cfg.key];
2230
+ const configured =
2231
+ Boolean(process.env[cfg.key]) ||
2232
+ (await runWithRequestContext({ userEmail, orgId }, () =>
2233
+ resolveSecret(cfg.key).then(Boolean),
2234
+ ));
2273
2235
  return {
2274
2236
  key: cfg.key,
2275
2237
  label: cfg.label,
@@ -2290,101 +2252,37 @@ export function createCoreRoutesPlugin(
2290
2252
  return { error: "Method not allowed" };
2291
2253
  }
2292
2254
 
2293
- // Env vars are deployment-wide globals, not per-tenant. On any
2294
- // shared-DB multi-tenant deploy, allowing authenticated users to
2295
- // write here lets one tenant overwrite Stripe / OpenAI / Sentry
2296
- // keys for every other tenant. Disable the endpoint outside of
2297
- // local-dev SQLite or an explicit single-tenant opt-in, and
2298
- // direct callers to scoped secret/credential stores instead.
2299
- if (!isEnvVarWriteAllowed()) {
2300
- setResponseStatus(event, 403);
2301
- return {
2302
- error:
2303
- "env-vars endpoint disabled on multi-tenant deployments. Use scoped secrets or credentials for user/org API keys.",
2304
- };
2305
- }
2306
-
2307
2255
  const body = await readBody(event);
2308
- const { vars } = body as {
2256
+ const { vars, scope } = body as {
2309
2257
  vars?: Array<{ key: string; value: string }>;
2258
+ scope?: ScopedKeySaveRequestScope;
2310
2259
  };
2311
-
2312
- if (!Array.isArray(vars) || vars.length === 0) {
2313
- setResponseStatus(event, 400);
2314
- return { error: "vars array required" };
2315
- }
2316
-
2317
- const allowedKeys = new Set<string>([
2318
- ...envKeys.map((k) => k.key),
2319
- ...collectOnboardingKeys(),
2320
- ]);
2321
- const blockedEnvVarWriteKeys = new Set<string>(BUILDER_ENV_KEYS);
2322
- const isWritableEnvKey = (key: string) =>
2323
- allowedKeys.has(key) && !blockedEnvVarWriteKeys.has(key);
2324
-
2325
- const filtered = vars.filter(
2326
- (v) =>
2327
- typeof v.key === "string" &&
2328
- isWritableEnvKey(v.key) &&
2329
- typeof v.value === "string" &&
2330
- v.value.trim().length > 0,
2260
+ const unsupportedKeys = findUnsupportedScopedKeyNames(
2261
+ vars,
2262
+ allowedEnvKeyNames,
2331
2263
  );
2332
- if (filtered.length === 0) {
2264
+ if (unsupportedKeys.length > 0) {
2333
2265
  setResponseStatus(event, 400);
2334
- const rejectedEmpty = vars.some(
2335
- (v) =>
2336
- typeof v.key === "string" &&
2337
- isWritableEnvKey(v.key) &&
2338
- (typeof v.value !== "string" || v.value.trim().length === 0),
2339
- );
2340
2266
  return {
2341
- error: rejectedEmpty
2342
- ? "Env values must be non-empty — refusing to clear a saved key"
2343
- : "No recognized env keys in request",
2267
+ error: `Unsupported env key${unsupportedKeys.length === 1 ? "" : "s"}: ${unsupportedKeys.join(", ")}`,
2344
2268
  };
2345
2269
  }
2346
2270
 
2347
- // Write to .env file. When inside a workspace, write to the
2348
- // workspace root .env so keys are shared across every app. The
2349
- // per-app .env still wins at load time if it also defines a key.
2350
2271
  try {
2351
- const scope =
2352
- (body as { scope?: "workspace" | "app" })?.scope ?? "auto";
2353
- const workspaceRoot = findWorkspaceRoot(process.cwd());
2354
- const envPath =
2355
- scope === "app"
2356
- ? path.join(process.cwd(), ".env")
2357
- : workspaceRoot
2358
- ? path.join(workspaceRoot, ".env")
2359
- : path.join(process.cwd(), ".env");
2360
- await upsertEnvFile(envPath, filtered);
2361
- } catch {
2362
- // Edge runtime — skip file write
2363
- }
2364
-
2365
- // Update process.env immediately
2366
- for (const { key, value } of filtered) {
2367
- process.env[key] = value;
2368
- }
2369
-
2370
- // Persist to settings table for serverless cold-start recovery.
2371
- try {
2372
- const envMap: Record<string, string> = {};
2373
- for (const { key, value } of filtered) envMap[key] = value;
2374
- const existing =
2375
- ((await getSetting("persisted-env-vars")) as Record<
2376
- string,
2377
- string
2378
- > | null) ?? {};
2379
- await putSetting("persisted-env-vars", {
2380
- ...existing,
2381
- ...envMap,
2382
- });
2383
- } catch {
2384
- // DB not ready yet — skip
2272
+ const result = await saveKeyValuesToScopedSecrets(
2273
+ event,
2274
+ vars,
2275
+ scope,
2276
+ );
2277
+ return { saved: result.saved, storage: "scoped-secrets" };
2278
+ } catch (err) {
2279
+ if (err instanceof ScopedKeyStorageError) {
2280
+ setResponseStatus(event, err.statusCode);
2281
+ return { error: err.message };
2282
+ }
2283
+ setResponseStatus(event, 500);
2284
+ return { error: "Failed to save keys" };
2385
2285
  }
2386
-
2387
- return { saved: filtered.map((v) => v.key) };
2388
2286
  }),
2389
2287
  );
2390
2288
  }
@@ -2729,54 +2627,69 @@ export function createCoreRoutesPlugin(
2729
2627
  getH3App(nitroApp).use(
2730
2628
  `${P}/file-upload/status`,
2731
2629
  defineEventHandler(async (event) => {
2732
- const active = getActiveFileUploadProvider();
2733
2630
  // resolveBuilderPrivateKey() reads per-user credentials from app_secrets
2734
2631
  // (DB), which requires request context (AsyncLocalStorage) to know which
2735
2632
  // user to scope by. Without runWithRequestContext() the ALS store is empty
2736
2633
  // and it falls back to process.env only — missing OAuth-connected users.
2737
2634
  const session = await getSession(event).catch(() => null);
2738
2635
  const userEmail = session?.email;
2739
- let builderConfigured = !!process.env.BUILDER_PRIVATE_KEY;
2740
- try {
2741
- const { resolveBuilderPrivateKey } =
2742
- await import("./credential-provider.js");
2743
- const resolve = () => resolveBuilderPrivateKey().then((k) => !!k);
2744
- builderConfigured = userEmail
2745
- ? await runWithRequestContext(
2746
- { userEmail, orgId: session?.orgId },
2747
- resolve,
2748
- )
2749
- : await resolve();
2750
- } catch {
2751
- // fall back to env check above
2752
- }
2753
- // When the builder builtin is selected via env var, its sync
2754
- // isConfigured() doesn't reflect per-user OAuth credentials. Use the
2755
- // async builderConfigured check so the status accurately represents
2756
- // whether this specific user can actually upload (thread 7 fix).
2757
- const isBuilderEnvActive = active?.id === "builder";
2758
- const configured = isBuilderEnvActive
2759
- ? builderConfigured
2760
- : !!active || builderConfigured;
2761
- const activeProvider = isBuilderEnvActive
2762
- ? builderConfigured
2763
- ? { id: "builder", name: "Builder.io" }
2764
- : null
2765
- : active
2766
- ? { id: active.id, name: active.name }
2767
- : builderConfigured
2636
+ const resolveStatus = async () => {
2637
+ const active = await getActiveFileUploadProviderForRequest();
2638
+ let builderConfigured = !!process.env.BUILDER_PRIVATE_KEY;
2639
+ try {
2640
+ const { resolveBuilderPrivateKey } =
2641
+ await import("./credential-provider.js");
2642
+ builderConfigured = await resolveBuilderPrivateKey().then(
2643
+ (k) => !!k,
2644
+ );
2645
+ } catch {
2646
+ // fall back to env check above
2647
+ }
2648
+
2649
+ const providers = await Promise.all(
2650
+ listFileUploadProviders().map(async (p) => {
2651
+ const scopedConfigured = p.isConfiguredForRequest
2652
+ ? await p.isConfiguredForRequest().catch(() => false)
2653
+ : false;
2654
+ return {
2655
+ id: p.id,
2656
+ name: p.name,
2657
+ configured: p.isConfigured() || scopedConfigured,
2658
+ };
2659
+ }),
2660
+ );
2661
+
2662
+ // When the builder builtin is selected via env var, its sync
2663
+ // isConfigured() doesn't reflect per-user OAuth credentials. Use
2664
+ // builderConfigured so status reflects this specific request.
2665
+ const isBuilderEnvActive = active?.id === "builder";
2666
+ const configured = isBuilderEnvActive
2667
+ ? builderConfigured
2668
+ : !!active || builderConfigured;
2669
+ const activeProvider = isBuilderEnvActive
2670
+ ? builderConfigured
2768
2671
  ? { id: "builder", name: "Builder.io" }
2769
- : null;
2770
- return {
2771
- configured,
2772
- activeProvider,
2773
- providers: listFileUploadProviders().map((p) => ({
2774
- id: p.id,
2775
- name: p.name,
2776
- configured: p.isConfigured(),
2777
- })),
2778
- builderConfigured,
2672
+ : null
2673
+ : active
2674
+ ? { id: active.id, name: active.name }
2675
+ : builderConfigured
2676
+ ? { id: "builder", name: "Builder.io" }
2677
+ : null;
2678
+
2679
+ return {
2680
+ configured,
2681
+ activeProvider,
2682
+ providers,
2683
+ builderConfigured,
2684
+ };
2779
2685
  };
2686
+
2687
+ return userEmail
2688
+ ? runWithRequestContext(
2689
+ { userEmail, orgId: session?.orgId },
2690
+ resolveStatus,
2691
+ )
2692
+ : resolveStatus();
2780
2693
  }),
2781
2694
  );
2782
2695
 
@@ -1,5 +1,3 @@
1
- import path from "path";
2
-
3
1
  import {
4
2
  createApp,
5
3
  createRouter,
@@ -11,8 +9,8 @@ import {
11
9
  type H3Event,
12
10
  } from "h3";
13
11
 
12
+ import { getOrgContext } from "../org/context.js";
14
13
  import { readBody } from "../server/h3-helpers.js";
15
- import { agentEnv } from "../shared/agent-env.js";
16
14
  import { EMBED_TARGET_HEADER } from "../shared/embed-auth.js";
17
15
  import {
18
16
  EMBED_TRANSPLANT_HEADER,
@@ -20,12 +18,19 @@ import {
20
18
  MCP_EMBED_CORS_ALLOW_HEADERS,
21
19
  shouldAllowMcpEmbedCredentials,
22
20
  } from "../shared/mcp-embed-headers.js";
23
- import { BUILDER_ENV_KEYS } from "./builder-browser.js";
21
+ import { getSession } from "./auth.js";
24
22
  import {
25
23
  getAllowedCorsOrigin,
26
24
  readCorsAllowedOrigins,
27
25
  } from "./cors-origins.js";
28
- import { isEnvVarWriteAllowed } from "./env-var-writes.js";
26
+ import { resolveSecret } from "./credential-provider.js";
27
+ import { runWithRequestContext } from "./request-context.js";
28
+ import {
29
+ findUnsupportedScopedKeyNames,
30
+ saveKeyValuesToScopedSecrets,
31
+ ScopedKeyStorageError,
32
+ type ScopedKeySaveRequestScope,
33
+ } from "./scoped-key-storage.js";
29
34
 
30
35
  export interface EnvKeyConfig {
31
36
  /** Environment variable name (e.g. "HUBSPOT_ACCESS_TOKEN") */
@@ -47,70 +52,10 @@ export interface CreateServerOptions {
47
52
  pingMessage?: string;
48
53
  /** Disable the /_agent-native/ping health check. Default: false */
49
54
  disablePing?: boolean;
50
- /** Env key configuration for the settings UI. Enables /_agent-native/env-status and /_agent-native/env-vars routes. */
55
+ /** Key configuration for the settings UI. Enables status plus the scoped-secret compatibility save route. */
51
56
  envKeys?: EnvKeyConfig[];
52
57
  }
53
58
 
54
- /**
55
- * Upsert vars into a .env file, preserving existing structure.
56
- */
57
- export async function upsertEnvFile(
58
- envPath: string,
59
- vars: Array<{ key: string; value: string }>,
60
- ): Promise<void> {
61
- // Sanitize: reject values that could inject additional env vars
62
- for (const { key, value } of vars) {
63
- if (/[\n\r\0]/.test(value)) {
64
- throw new Error(
65
- `Invalid env var value for ${key}: must not contain newlines or control characters`,
66
- );
67
- }
68
- }
69
-
70
- const fs = await import("fs");
71
-
72
- let content = "";
73
- try {
74
- content = fs.readFileSync(envPath, "utf-8");
75
- } catch {
76
- // File doesn't exist yet
77
- }
78
-
79
- const lines = content.split("\n");
80
- const remaining = new Map(vars.map((v) => [v.key, v.value]));
81
-
82
- // Update existing lines in place
83
- const updated = lines.map((line) => {
84
- const trimmed = line.trim();
85
- if (!trimmed || trimmed.startsWith("#")) return line;
86
- const eqIndex = trimmed.indexOf("=");
87
- if (eqIndex === -1) return line;
88
- const key = trimmed.slice(0, eqIndex).trim();
89
- if (remaining.has(key)) {
90
- const value = remaining.get(key)!;
91
- remaining.delete(key);
92
- return `${key}=${value}`;
93
- }
94
- return line;
95
- });
96
-
97
- // Append new vars
98
- for (const [key, value] of remaining) {
99
- updated.push(`${key}=${value}`);
100
- }
101
-
102
- // Ensure trailing newline
103
- let result = updated.join("\n");
104
- if (!result.endsWith("\n")) result += "\n";
105
-
106
- try {
107
- fs.mkdirSync(path.dirname(envPath), { recursive: true });
108
- fs.writeFileSync(envPath, result);
109
- } catch {
110
- // Edge runtimes don't have writable filesystem — skip silently
111
- }
112
- }
113
-
114
59
  export interface CreateServerResult {
115
60
  app: ReturnType<typeof createApp>;
116
61
  router: ReturnType<typeof createRouter>;
@@ -120,7 +65,8 @@ export interface CreateServerResult {
120
65
  * Create a pre-configured H3 app with standard agent-native setup:
121
66
  * - CORS headers via middleware
122
67
  * - /_agent-native/ping health check
123
- * - /_agent-native/env-status and /_agent-native/env-vars (when envKeys is provided)
68
+ * - /_agent-native/env-status and the scoped-secret compatibility save route
69
+ * at /_agent-native/env-vars (when envKeys is provided)
124
70
  *
125
71
  * Returns { app, router } — mount routes on `router`.
126
72
  */
@@ -242,7 +188,6 @@ export function createServer(
242
188
  }
243
189
 
244
190
  const router = createRouter();
245
- app.use(router);
246
191
 
247
192
  // Health check
248
193
  if (!options.disablePing) {
@@ -259,73 +204,67 @@ export function createServer(
259
204
  // Env key management routes
260
205
  if (options.envKeys) {
261
206
  const envKeys = options.envKeys;
207
+ const allowedEnvKeyNames = envKeys.map(({ key }) => key);
262
208
 
263
209
  router.get(
264
210
  "/_agent-native/env-status",
265
- defineEventHandler(() => {
266
- return envKeys.map((cfg) => ({
267
- key: cfg.key,
268
- label: cfg.label,
269
- required: cfg.required ?? false,
270
- configured: !!process.env[cfg.key],
271
- ...(cfg.helpText ? { helpText: cfg.helpText } : {}),
272
- }));
211
+ defineEventHandler(async (event) => {
212
+ const session = await getSession(event).catch(() => null);
213
+ const userEmail = session?.email;
214
+ let orgId: string | undefined;
215
+ if (userEmail) {
216
+ const orgCtx = await getOrgContext(event).catch(() => null);
217
+ orgId = orgCtx?.orgId ?? undefined;
218
+ }
219
+ return Promise.all(
220
+ envKeys.map(async (cfg) => ({
221
+ key: cfg.key,
222
+ label: cfg.label,
223
+ required: cfg.required ?? false,
224
+ configured:
225
+ Boolean(process.env[cfg.key]) ||
226
+ (await runWithRequestContext({ userEmail, orgId }, () =>
227
+ resolveSecret(cfg.key).then(Boolean),
228
+ )),
229
+ ...(cfg.helpText ? { helpText: cfg.helpText } : {}),
230
+ })),
231
+ );
273
232
  }),
274
233
  );
275
234
 
276
235
  router.post(
277
236
  "/_agent-native/env-vars",
278
237
  defineEventHandler(async (event: H3Event) => {
279
- // Env vars are deployment-wide globals — see isEnvVarWriteAllowed
280
- // above. Disable the endpoint on any multi-tenant deploy.
281
- if (!isEnvVarWriteAllowed()) {
282
- setResponseStatus(event, 403);
283
- return {
284
- error:
285
- "env-vars endpoint disabled on multi-tenant deployments. Use scoped secrets or credentials for user/org API keys.",
286
- };
287
- }
288
-
289
238
  const body = await readBody(event);
290
- const { vars } = body as {
239
+ const { vars, scope } = body as {
291
240
  vars?: Array<{ key: string; value: string }>;
241
+ scope?: ScopedKeySaveRequestScope;
292
242
  };
293
-
294
- if (!Array.isArray(vars) || vars.length === 0) {
295
- setResponseStatus(event, 400);
296
- return { error: "vars array required" };
297
- }
298
-
299
- // Only allow keys that are in the env config
300
- const allowedKeys = new Set(envKeys.map((k) => k.key));
301
- const blockedEnvVarWriteKeys = new Set<string>(BUILDER_ENV_KEYS);
302
- const filtered = vars.filter(
303
- (v) =>
304
- typeof v.key === "string" &&
305
- allowedKeys.has(v.key) &&
306
- !blockedEnvVarWriteKeys.has(v.key),
243
+ const unsupportedKeys = findUnsupportedScopedKeyNames(
244
+ vars,
245
+ allowedEnvKeyNames,
307
246
  );
308
- if (filtered.length === 0) {
247
+ if (unsupportedKeys.length > 0) {
309
248
  setResponseStatus(event, 400);
310
- return { error: "No recognized env keys in request" };
249
+ return {
250
+ error: `Unsupported env key${unsupportedKeys.length === 1 ? "" : "s"}: ${unsupportedKeys.join(", ")}`,
251
+ };
311
252
  }
312
-
313
- // Write to .env file
314
- const envPath = path.join(process.cwd(), ".env");
315
- await upsertEnvFile(envPath, filtered);
316
-
317
- // Update process.env so the app picks up the new values immediately
318
- for (const { key, value } of filtered) {
319
- process.env[key] = value;
253
+ try {
254
+ const result = await saveKeyValuesToScopedSecrets(event, vars, scope);
255
+ return { saved: result.saved, storage: "scoped-secrets" };
256
+ } catch (err) {
257
+ if (err instanceof ScopedKeyStorageError) {
258
+ setResponseStatus(event, err.statusCode);
259
+ return { error: err.message };
260
+ }
261
+ setResponseStatus(event, 500);
262
+ return { error: "Failed to save keys" };
320
263
  }
321
-
322
- // Notify parent (Builder or frame) via postMessage
323
- agentEnv.setVars(filtered);
324
-
325
- return { saved: filtered.map((v) => v.key) };
326
264
  }),
327
265
  );
328
266
  }
329
267
 
268
+ app.use(router);
330
269
  return { app, router };
331
270
  }
@@ -2,9 +2,8 @@
2
2
  * Framework-level agent action for sending transactional/notification emails
3
3
  * via the configured core email transport (Resend or SendGrid).
4
4
  *
5
- * Registered as a native tool in every template when RESEND_API_KEY or
6
- * SENDGRID_API_KEY is set. When neither key is present the tool is omitted
7
- * from the agent surface so the agent never sees it.
5
+ * Registered as a native tool in every template. sendEmail() checks the
6
+ * scoped Resend/SendGrid configuration at call time.
8
7
  *
9
8
  * SAFETY: the action description instructs the agent to draft-first and only
10
9
  * send when the user explicitly confirms, matching the mail template convention.
@@ -16,7 +15,7 @@
16
15
  */
17
16
 
18
17
  import type { ActionEntry } from "../agent/production-agent.js";
19
- import { isEmailConfigured, sendEmail } from "./email.js";
18
+ import { sendEmail } from "./email.js";
20
19
 
21
20
  function markdownToText(md: string): string {
22
21
  return md
@@ -115,8 +114,6 @@ function markdownToHtml(md: string): string {
115
114
  }
116
115
 
117
116
  export function createCoreEmailActionEntries(): Record<string, ActionEntry> {
118
- if (!isEmailConfigured()) return {};
119
-
120
117
  return {
121
118
  "core-send-email": {
122
119
  tool: {