@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
@@ -0,0 +1,216 @@
1
+ import { IconHistory, IconSettings, IconUsers } from "@tabler/icons-react";
2
+ import {
3
+ useEffect,
4
+ useMemo,
5
+ useState,
6
+ type ComponentType,
7
+ type ReactNode,
8
+ } from "react";
9
+
10
+ import { cn } from "../utils.js";
11
+
12
+ type SettingsTabIcon = ComponentType<{ className?: string }>;
13
+
14
+ export interface SettingsTabItem {
15
+ id: string;
16
+ label: string;
17
+ icon?: SettingsTabIcon;
18
+ content: ReactNode;
19
+ }
20
+
21
+ export interface SettingsTabsPageProps {
22
+ general: ReactNode;
23
+ team?: ReactNode;
24
+ whatsNew?: ReactNode;
25
+ extraTabs?: SettingsTabItem[];
26
+ generalLabel?: string;
27
+ teamLabel?: string;
28
+ whatsNewLabel?: string;
29
+ ariaLabel?: string;
30
+ defaultTab?: string;
31
+ className?: string;
32
+ navClassName?: string;
33
+ contentClassName?: string;
34
+ }
35
+
36
+ function normalizeTabId(value?: string | null): string | null {
37
+ const normalized = value
38
+ ?.replace(/^#/, "")
39
+ .trim()
40
+ .toLowerCase()
41
+ .replace(/['"]/g, "")
42
+ .replace(/[\s_]+/g, "-");
43
+ if (!normalized) return null;
44
+ if (
45
+ normalized === "whats-new" ||
46
+ normalized === "what-s-new" ||
47
+ normalized === "changelog" ||
48
+ normalized === "updates"
49
+ ) {
50
+ return "whats-new";
51
+ }
52
+ if (
53
+ normalized === "workspace" ||
54
+ normalized === "workspace-settings" ||
55
+ normalized === "organization" ||
56
+ normalized === "org"
57
+ ) {
58
+ return "team";
59
+ }
60
+ return normalized;
61
+ }
62
+
63
+ function activeTabFromHash(
64
+ tabs: SettingsTabItem[],
65
+ defaultTab: string,
66
+ ): string {
67
+ if (typeof window === "undefined") return defaultTab;
68
+ const fromHash = normalizeTabId(window.location.hash);
69
+ if (fromHash && tabs.some((tab) => tab.id === fromHash)) return fromHash;
70
+ return defaultTab;
71
+ }
72
+
73
+ function updateHashForTab(tabId: string) {
74
+ if (typeof window === "undefined") return;
75
+ const { pathname, search } = window.location;
76
+ const hash = tabId === "general" ? "" : `#${encodeURIComponent(tabId)}`;
77
+ window.history.pushState(null, "", `${pathname}${search}${hash}`);
78
+ }
79
+
80
+ export function SettingsTabsPage({
81
+ general,
82
+ team,
83
+ whatsNew,
84
+ extraTabs = [],
85
+ generalLabel = "General",
86
+ teamLabel = "Team",
87
+ whatsNewLabel = "What's new",
88
+ ariaLabel = "Settings sections",
89
+ defaultTab = "general",
90
+ className,
91
+ navClassName,
92
+ contentClassName,
93
+ }: SettingsTabsPageProps) {
94
+ const tabs = useMemo<SettingsTabItem[]>(() => {
95
+ const next: SettingsTabItem[] = [
96
+ {
97
+ id: "general",
98
+ label: generalLabel,
99
+ icon: IconSettings,
100
+ content: general,
101
+ },
102
+ ];
103
+ if (team) {
104
+ next.push({
105
+ id: "team",
106
+ label: teamLabel,
107
+ icon: IconUsers,
108
+ content: team,
109
+ });
110
+ }
111
+ if (whatsNew) {
112
+ next.push({
113
+ id: "whats-new",
114
+ label: whatsNewLabel,
115
+ icon: IconHistory,
116
+ content: whatsNew,
117
+ });
118
+ }
119
+ next.push(...extraTabs);
120
+ return next;
121
+ }, [
122
+ extraTabs,
123
+ general,
124
+ generalLabel,
125
+ team,
126
+ teamLabel,
127
+ whatsNew,
128
+ whatsNewLabel,
129
+ ]);
130
+
131
+ const fallbackTab = tabs.some((tab) => tab.id === defaultTab)
132
+ ? defaultTab
133
+ : (tabs[0]?.id ?? "general");
134
+ const [activeTab, setActiveTab] = useState(() =>
135
+ activeTabFromHash(tabs, fallbackTab),
136
+ );
137
+
138
+ useEffect(() => {
139
+ if (tabs.some((tab) => tab.id === activeTab)) return;
140
+ setActiveTab(fallbackTab);
141
+ }, [activeTab, fallbackTab, tabs]);
142
+
143
+ useEffect(() => {
144
+ const handleHashChange = () => {
145
+ setActiveTab(activeTabFromHash(tabs, fallbackTab));
146
+ };
147
+ window.addEventListener("hashchange", handleHashChange);
148
+ return () => window.removeEventListener("hashchange", handleHashChange);
149
+ }, [fallbackTab, tabs]);
150
+
151
+ const selectedTab = tabs.find((tab) => tab.id === activeTab) ?? tabs[0];
152
+
153
+ return (
154
+ <div
155
+ className={cn(
156
+ "flex min-h-full w-full flex-col overflow-hidden bg-background sm:flex-row",
157
+ className,
158
+ )}
159
+ >
160
+ <nav
161
+ aria-label={ariaLabel}
162
+ role="tablist"
163
+ className={cn(
164
+ "flex shrink-0 gap-1 overflow-x-auto border-b border-border/50 bg-muted/30 p-2 sm:w-48 sm:flex-col sm:overflow-x-visible sm:border-b-0 sm:border-e",
165
+ navClassName,
166
+ )}
167
+ >
168
+ {tabs.map((tab) => {
169
+ const Icon = tab.icon;
170
+ const selected = tab.id === selectedTab?.id;
171
+ return (
172
+ <button
173
+ key={tab.id}
174
+ type="button"
175
+ role="tab"
176
+ aria-selected={selected}
177
+ aria-controls={`settings-tabpanel-${tab.id}`}
178
+ id={`settings-tab-${tab.id}`}
179
+ onClick={() => {
180
+ setActiveTab(tab.id);
181
+ updateHashForTab(tab.id);
182
+ }}
183
+ className={cn(
184
+ "flex min-h-9 shrink-0 items-center gap-2 rounded-md px-3 py-2 text-start text-sm font-medium transition-colors sm:w-full",
185
+ selected
186
+ ? "bg-background text-foreground shadow-sm ring-1 ring-border/60"
187
+ : "text-muted-foreground hover:bg-accent/60 hover:text-foreground",
188
+ )}
189
+ >
190
+ {Icon ? (
191
+ <Icon
192
+ className={cn(
193
+ "size-4 shrink-0",
194
+ selected ? "text-foreground" : "text-muted-foreground",
195
+ )}
196
+ />
197
+ ) : null}
198
+ <span className="truncate">{tab.label}</span>
199
+ </button>
200
+ );
201
+ })}
202
+ </nav>
203
+ <div
204
+ id={`settings-tabpanel-${selectedTab?.id ?? "general"}`}
205
+ role="tabpanel"
206
+ aria-labelledby={`settings-tab-${selectedTab?.id ?? "general"}`}
207
+ className={cn(
208
+ "min-w-0 flex-1 overflow-y-auto p-4 sm:p-6",
209
+ contentClassName,
210
+ )}
211
+ >
212
+ {selectedTab?.content}
213
+ </div>
214
+ </div>
215
+ );
216
+ }
@@ -1,3 +1,8 @@
1
1
  export { SettingsPanel, type SettingsPanelProps } from "./SettingsPanel.js";
2
+ export {
3
+ SettingsTabsPage,
4
+ type SettingsTabItem,
5
+ type SettingsTabsPageProps,
6
+ } from "./SettingsTabsPage.js";
2
7
  export { useBuilderStatus } from "./useBuilderStatus.js";
3
8
  export { SecretsSection, type SecretsSectionProps } from "./SecretsSection.js";
@@ -8,6 +8,7 @@ export {
8
8
  unregisterFileUploadProvider,
9
9
  listFileUploadProviders,
10
10
  getActiveFileUploadProvider,
11
+ getActiveFileUploadProviderForRequest,
11
12
  uploadFile,
12
13
  } from "./registry.js";
13
14
  export { builderFileUploadProvider } from "./builder.js";
@@ -54,6 +54,24 @@ export function getActiveFileUploadProvider(): FileUploadProvider | null {
54
54
  return null;
55
55
  }
56
56
 
57
+ export async function getActiveFileUploadProviderForRequest(): Promise<FileUploadProvider | null> {
58
+ for (const provider of providers.values()) {
59
+ if (provider.isConfigured()) return provider;
60
+ if (provider.isConfiguredForRequest) {
61
+ try {
62
+ if (await provider.isConfiguredForRequest()) return provider;
63
+ } catch {
64
+ // Treat failed scoped credential lookups as unavailable. The upload
65
+ // call will surface real provider errors after a provider is selected.
66
+ }
67
+ }
68
+ }
69
+ if (builderFileUploadProvider.isConfigured()) {
70
+ return builderFileUploadProvider;
71
+ }
72
+ return null;
73
+ }
74
+
57
75
  /**
58
76
  * Upload a file via the active provider, or `null` if no provider is
59
77
  * configured. Callers use `null` as the signal to fall back to SQL
@@ -63,12 +81,10 @@ export function getActiveFileUploadProvider(): FileUploadProvider | null {
63
81
  export async function uploadFile(
64
82
  input: FileUploadInput,
65
83
  ): Promise<FileUploadResult | null> {
66
- const provider = getActiveFileUploadProvider();
67
- // Only trust user-registered providers (S3, etc.) from the sync check.
68
- // The builder builtin's isConfigured() only checks process.env, which causes
69
- // hard failures for authenticated non-local users on multi-tenant deployments
70
- // where BUILDER_PRIVATE_KEY is set at the deploy level but the user has no
71
- // personal credentials. Always resolve builder credentials asynchronously.
84
+ const provider = await getActiveFileUploadProviderForRequest();
85
+ // User-registered providers (S3, etc.) may be configured by sync runtime
86
+ // state or request-scoped DB secrets. Builder still gets an explicit async
87
+ // credential check below because its sync isConfigured() only checks env.
72
88
  if (provider && provider !== builderFileUploadProvider) {
73
89
  return provider.upload(input);
74
90
  }
@@ -32,8 +32,13 @@ export interface FileUploadProvider {
32
32
  id: string;
33
33
  /** Human-readable name. */
34
34
  name: string;
35
- /** Returns true if this provider is currently configured (env vars etc.). */
35
+ /** Returns true if this provider is configured from synchronous runtime state. */
36
36
  isConfigured: () => boolean;
37
+ /**
38
+ * Returns true if this provider is configured for the active request.
39
+ * Use for DB-backed user/org/workspace secrets that require request context.
40
+ */
41
+ isConfiguredForRequest?: () => Promise<boolean>;
37
42
  /** Upload a file and return a URL. Throw on failure. */
38
43
  upload: (input: FileUploadInput) => Promise<FileUploadResult>;
39
44
  }
@@ -5,6 +5,7 @@ import { getHeader, readRawBody as h3ReadRawBody } from "h3";
5
5
 
6
6
  import { getDbExec } from "../../db/client.js";
7
7
  import type { EnvKeyConfig } from "../../server/create-server.js";
8
+ import { resolveSecret } from "../../server/credential-provider.js";
8
9
  import {
9
10
  sendEmail,
10
11
  isEmailConfigured,
@@ -113,8 +114,9 @@ export function emailAdapter(): PlatformAdapter {
113
114
  },
114
115
 
115
116
  async verifyWebhook(event: H3Event): Promise<boolean> {
116
- const secret = process.env.EMAIL_INBOUND_WEBHOOK_SECRET;
117
- const provider = getEmailProvider();
117
+ const secret =
118
+ (await resolveSecret("EMAIL_INBOUND_WEBHOOK_SECRET")) ?? undefined;
119
+ const provider = await getEmailProvider();
118
120
 
119
121
  if (provider === "resend") {
120
122
  return verifyResendWebhook(event, secret);
@@ -132,8 +134,10 @@ export function emailAdapter(): PlatformAdapter {
132
134
  async parseIncomingMessage(
133
135
  event: H3Event,
134
136
  ): Promise<IncomingMessage | null> {
135
- const provider = getEmailProvider();
136
- const agentAddress = process.env.EMAIL_AGENT_ADDRESS?.toLowerCase();
137
+ const provider = await getEmailProvider();
138
+ const agentAddress = (
139
+ await resolveSecret("EMAIL_AGENT_ADDRESS")
140
+ )?.toLowerCase();
137
141
  if (!agentAddress) {
138
142
  console.warn("[email] EMAIL_AGENT_ADDRESS not configured");
139
143
  return null;
@@ -246,7 +250,7 @@ export function emailAdapter(): PlatformAdapter {
246
250
  message: OutgoingMessage,
247
251
  context: IncomingMessage,
248
252
  ): Promise<void> {
249
- const agentAddress = process.env.EMAIL_AGENT_ADDRESS;
253
+ const agentAddress = await resolveSecret("EMAIL_AGENT_ADDRESS");
250
254
  if (!agentAddress) {
251
255
  console.error("[email] EMAIL_AGENT_ADDRESS not configured");
252
256
  return;
@@ -259,9 +263,8 @@ export function emailAdapter(): PlatformAdapter {
259
263
  // EMAIL_FROM overrides the from-address — required when the receiving
260
264
  // address is on a sub-domain that can't be a verified sender (e.g.
261
265
  // *.resend.app). Inbound and outbound addresses can differ.
262
- const fromAddress = process.env.EMAIL_FROM
263
- ? process.env.EMAIL_FROM
264
- : `${displayName} <${agentAddress}>`;
266
+ const emailFrom = await resolveSecret("EMAIL_FROM");
267
+ const fromAddress = emailFrom || `${displayName} <${agentAddress}>`;
265
268
 
266
269
  const subject = context.platformContext.subject as string;
267
270
  const reSubject = subject.startsWith("Re: ") ? subject : `Re: ${subject}`;
@@ -276,7 +279,7 @@ export function emailAdapter(): PlatformAdapter {
276
279
  inReplyTo: context.platformContext.messageId as string,
277
280
  references: buildReferencesHeader(context.platformContext),
278
281
  cc: context.platformContext.isCC
279
- ? buildReplyAllCc(context)
282
+ ? buildReplyAllCc(context, agentAddress)
280
283
  : undefined,
281
284
  });
282
285
  } catch (err) {
@@ -288,7 +291,7 @@ export function emailAdapter(): PlatformAdapter {
288
291
  message: OutgoingMessage,
289
292
  target: OutboundTarget,
290
293
  ): Promise<void> {
291
- const agentAddress = process.env.EMAIL_AGENT_ADDRESS;
294
+ const agentAddress = await resolveSecret("EMAIL_AGENT_ADDRESS");
292
295
  if (!agentAddress) {
293
296
  console.error("[email] EMAIL_AGENT_ADDRESS not configured");
294
297
  return;
@@ -325,9 +328,11 @@ export function emailAdapter(): PlatformAdapter {
325
328
  },
326
329
 
327
330
  async getStatus(_baseUrl?: string): Promise<IntegrationStatus> {
328
- const hasAgentAddress = !!process.env.EMAIL_AGENT_ADDRESS;
329
- const hasEmailProvider = isEmailConfigured();
330
- const hasWebhookSecret = !!process.env.EMAIL_INBOUND_WEBHOOK_SECRET;
331
+ const hasAgentAddress = !!(await resolveSecret("EMAIL_AGENT_ADDRESS"));
332
+ const hasEmailProvider = await isEmailConfigured();
333
+ const hasWebhookSecret = !!(await resolveSecret(
334
+ "EMAIL_INBOUND_WEBHOOK_SECRET",
335
+ ));
331
336
  const configured = hasAgentAddress && hasEmailProvider;
332
337
 
333
338
  return {
@@ -339,10 +344,10 @@ export function emailAdapter(): PlatformAdapter {
339
344
  hasAgentAddress,
340
345
  hasEmailProvider,
341
346
  hasWebhookSecret,
342
- provider: getEmailProvider(),
347
+ provider: await getEmailProvider(),
343
348
  },
344
349
  error: !configured
345
- ? "Set EMAIL_AGENT_ADDRESS and either RESEND_API_KEY or SENDGRID_API_KEY"
350
+ ? "Save EMAIL_AGENT_ADDRESS and either RESEND_API_KEY or SENDGRID_API_KEY in settings"
346
351
  : undefined,
347
352
  };
348
353
  },
@@ -906,8 +911,11 @@ function buildReferencesHeader(ctx: Record<string, unknown>): string {
906
911
  * Build CC list for reply-all when agent was CC'd.
907
912
  * Include original To addresses and other CC addresses, excluding the agent and the original sender.
908
913
  */
909
- function buildReplyAllCc(context: IncomingMessage): string[] | undefined {
910
- const agentAddress = process.env.EMAIL_AGENT_ADDRESS?.toLowerCase();
914
+ function buildReplyAllCc(
915
+ context: IncomingMessage,
916
+ agentAddress: string,
917
+ ): string[] | undefined {
918
+ const normalizedAgentAddress = agentAddress.toLowerCase();
911
919
  const senderEmail = context.senderId?.toLowerCase();
912
920
  const toAddresses = (context.platformContext.to as string[]) || [];
913
921
  const ccAddresses = (context.platformContext.cc as string[]) || [];
@@ -916,7 +924,7 @@ function buildReplyAllCc(context: IncomingMessage): string[] | undefined {
916
924
  for (const addr of [...toAddresses, ...ccAddresses]) {
917
925
  const normalized = addr.toLowerCase().trim();
918
926
  // Exclude agent address and original sender (sender goes in To)
919
- if (normalized !== agentAddress && normalized !== senderEmail) {
927
+ if (normalized !== normalizedAgentAddress && normalized !== senderEmail) {
920
928
  allRecipients.add(normalized);
921
929
  }
922
930
  }
@@ -2,6 +2,7 @@ import type { H3Event } from "h3";
2
2
  import { createError, getHeader, readRawBody } from "h3";
3
3
 
4
4
  import type { EnvKeyConfig } from "../../server/create-server.js";
5
+ import { resolveSecret } from "../../server/credential-provider.js";
5
6
  import type {
6
7
  PlatformAdapter,
7
8
  IncomingMessage,
@@ -80,7 +81,7 @@ export function slackAdapter(): PlatformAdapter {
80
81
  },
81
82
 
82
83
  async verifyWebhook(event: H3Event): Promise<boolean> {
83
- const signingSecret = process.env.SLACK_SIGNING_SECRET;
84
+ const signingSecret = await resolveSecret("SLACK_SIGNING_SECRET");
84
85
  if (!signingSecret) return false;
85
86
 
86
87
  const signature = getHeader(event, "x-slack-signature");
@@ -185,7 +186,7 @@ export function slackAdapter(): PlatformAdapter {
185
186
  // redundant and added an extra chunk that we then had to overwrite.
186
187
  // We just set the native status and return null so sendResponse posts
187
188
  // the final reply as a fresh message.
188
- const token = process.env.SLACK_BOT_TOKEN;
189
+ const token = await resolveSecret("SLACK_BOT_TOKEN");
189
190
  if (!token) return null;
190
191
 
191
192
  const channelId = incoming.platformContext.channelId as string;
@@ -204,7 +205,7 @@ export function slackAdapter(): PlatformAdapter {
204
205
  context: IncomingMessage,
205
206
  opts?: { placeholderRef?: string },
206
207
  ): Promise<void> {
207
- const token = process.env.SLACK_BOT_TOKEN;
208
+ const token = await resolveSecret("SLACK_BOT_TOKEN");
208
209
  if (!token) {
209
210
  console.error("[slack] SLACK_BOT_TOKEN not configured");
210
211
  return;
@@ -297,7 +298,7 @@ export function slackAdapter(): PlatformAdapter {
297
298
  message: OutgoingMessage,
298
299
  target: OutboundTarget,
299
300
  ): Promise<void> {
300
- const token = process.env.SLACK_BOT_TOKEN;
301
+ const token = await resolveSecret("SLACK_BOT_TOKEN");
301
302
  if (!token) {
302
303
  console.error("[slack] SLACK_BOT_TOKEN not configured");
303
304
  return;
@@ -348,8 +349,8 @@ export function slackAdapter(): PlatformAdapter {
348
349
  },
349
350
 
350
351
  async getStatus(_baseUrl?: string): Promise<IntegrationStatus> {
351
- const hasToken = !!process.env.SLACK_BOT_TOKEN;
352
- const hasSecret = !!process.env.SLACK_SIGNING_SECRET;
352
+ const hasToken = !!(await resolveSecret("SLACK_BOT_TOKEN"));
353
+ const hasSecret = !!(await resolveSecret("SLACK_SIGNING_SECRET"));
353
354
  const configured = hasToken && hasSecret;
354
355
 
355
356
  return {
@@ -362,7 +363,7 @@ export function slackAdapter(): PlatformAdapter {
362
363
  hasSecret,
363
364
  },
364
365
  error: !configured
365
- ? "Set SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET in your environment"
366
+ ? "Save SLACK_BOT_TOKEN and SLACK_SIGNING_SECRET in settings"
366
367
  : undefined,
367
368
  };
368
369
  },
@@ -2,6 +2,7 @@ import type { H3Event } from "h3";
2
2
  import { getHeader } from "h3";
3
3
 
4
4
  import type { EnvKeyConfig } from "../../server/create-server.js";
5
+ import { resolveSecret } from "../../server/credential-provider.js";
5
6
  import { readBody } from "../../server/h3-helpers.js";
6
7
  import type {
7
8
  PlatformAdapter,
@@ -86,7 +87,7 @@ export function telegramAdapter(): PlatformAdapter {
86
87
  },
87
88
 
88
89
  async verifyWebhook(event: H3Event): Promise<boolean> {
89
- const secret = process.env.TELEGRAM_WEBHOOK_SECRET;
90
+ const secret = await resolveSecret("TELEGRAM_WEBHOOK_SECRET");
90
91
  if (!secret) {
91
92
  if (shouldRefuseWhenSecretMissing()) {
92
93
  if (!_telegramUnverifiedWarned) {
@@ -105,7 +106,7 @@ export function telegramAdapter(): PlatformAdapter {
105
106
  );
106
107
  }
107
108
  // Dev mode: still require the bot token to be configured at all.
108
- return !!process.env.TELEGRAM_BOT_TOKEN;
109
+ return !!(await resolveSecret("TELEGRAM_BOT_TOKEN"));
109
110
  }
110
111
 
111
112
  const headerSecret = getHeader(event, "x-telegram-bot-api-secret-token");
@@ -172,7 +173,7 @@ export function telegramAdapter(): PlatformAdapter {
172
173
  message: OutgoingMessage,
173
174
  context: IncomingMessage,
174
175
  ): Promise<void> {
175
- const token = process.env.TELEGRAM_BOT_TOKEN;
176
+ const token = await resolveSecret("TELEGRAM_BOT_TOKEN");
176
177
  if (!token) {
177
178
  console.error("[telegram] TELEGRAM_BOT_TOKEN not configured");
178
179
  return;
@@ -224,7 +225,7 @@ export function telegramAdapter(): PlatformAdapter {
224
225
  message: OutgoingMessage,
225
226
  target: OutboundTarget,
226
227
  ): Promise<void> {
227
- const token = process.env.TELEGRAM_BOT_TOKEN;
228
+ const token = await resolveSecret("TELEGRAM_BOT_TOKEN");
228
229
  if (!token) {
229
230
  console.error("[telegram] TELEGRAM_BOT_TOKEN not configured");
230
231
  return;
@@ -272,14 +273,13 @@ export function telegramAdapter(): PlatformAdapter {
272
273
  },
273
274
 
274
275
  async getStatus(_baseUrl?: string): Promise<IntegrationStatus> {
275
- const hasToken = !!process.env.TELEGRAM_BOT_TOKEN;
276
+ const token = await resolveSecret("TELEGRAM_BOT_TOKEN");
277
+ const hasToken = !!token;
276
278
 
277
279
  let botName: string | undefined;
278
280
  if (hasToken) {
279
281
  try {
280
- const res = await fetch(
281
- `https://api.telegram.org/bot${process.env.TELEGRAM_BOT_TOKEN}/getMe`,
282
- );
282
+ const res = await fetch(`https://api.telegram.org/bot${token}/getMe`);
283
283
  const data = (await res.json()) as {
284
284
  ok: boolean;
285
285
  result?: { username?: string };
@@ -299,9 +299,7 @@ export function telegramAdapter(): PlatformAdapter {
299
299
  hasToken,
300
300
  botUsername: botName,
301
301
  },
302
- error: !hasToken
303
- ? "Set TELEGRAM_BOT_TOKEN in your environment"
304
- : undefined,
302
+ error: !hasToken ? "Save TELEGRAM_BOT_TOKEN in settings" : undefined,
305
303
  };
306
304
  },
307
305
  };
@@ -2,6 +2,7 @@ import type { H3Event } from "h3";
2
2
  import { getQuery, getHeader, readRawBody as h3ReadRawBody } from "h3";
3
3
 
4
4
  import type { EnvKeyConfig } from "../../server/create-server.js";
5
+ import { resolveSecret } from "../../server/credential-provider.js";
5
6
  import type {
6
7
  PlatformAdapter,
7
8
  IncomingMessage,
@@ -103,7 +104,7 @@ export function whatsappAdapter(): PlatformAdapter {
103
104
  const mode = query["hub.mode"];
104
105
  const token = query["hub.verify_token"];
105
106
  const challenge = query["hub.challenge"];
106
- const expected = process.env.WHATSAPP_VERIFY_TOKEN;
107
+ const expected = await resolveSecret("WHATSAPP_VERIFY_TOKEN");
107
108
 
108
109
  if (mode === "subscribe" && expected && typeof token === "string") {
109
110
  // Timing-safe compare so an attacker can't measure character-wise
@@ -126,7 +127,7 @@ export function whatsappAdapter(): PlatformAdapter {
126
127
  },
127
128
 
128
129
  async verifyWebhook(event: H3Event): Promise<boolean> {
129
- const appSecret = process.env.WHATSAPP_APP_SECRET;
130
+ const appSecret = await resolveSecret("WHATSAPP_APP_SECRET");
130
131
  if (!appSecret) {
131
132
  if (shouldRefuseWhenSecretMissing()) {
132
133
  if (!_whatsappUnverifiedWarned) {
@@ -145,7 +146,7 @@ export function whatsappAdapter(): PlatformAdapter {
145
146
  );
146
147
  }
147
148
  // Dev mode: still require the access token to be configured at all.
148
- return !!process.env.WHATSAPP_ACCESS_TOKEN;
149
+ return !!(await resolveSecret("WHATSAPP_ACCESS_TOKEN"));
149
150
  }
150
151
 
151
152
  const signature = getHeader(event, "x-hub-signature-256");
@@ -223,8 +224,8 @@ export function whatsappAdapter(): PlatformAdapter {
223
224
  message: OutgoingMessage,
224
225
  context: IncomingMessage,
225
226
  ): Promise<void> {
226
- const accessToken = process.env.WHATSAPP_ACCESS_TOKEN;
227
- const phoneNumberId = process.env.WHATSAPP_PHONE_NUMBER_ID;
227
+ const accessToken = await resolveSecret("WHATSAPP_ACCESS_TOKEN");
228
+ const phoneNumberId = await resolveSecret("WHATSAPP_PHONE_NUMBER_ID");
228
229
  if (!accessToken || !phoneNumberId) {
229
230
  console.error(
230
231
  "[whatsapp] WHATSAPP_ACCESS_TOKEN or WHATSAPP_PHONE_NUMBER_ID not configured",
@@ -269,9 +270,11 @@ export function whatsappAdapter(): PlatformAdapter {
269
270
  },
270
271
 
271
272
  async getStatus(_baseUrl?: string): Promise<IntegrationStatus> {
272
- const hasAccessToken = !!process.env.WHATSAPP_ACCESS_TOKEN;
273
- const hasVerifyToken = !!process.env.WHATSAPP_VERIFY_TOKEN;
274
- const hasPhoneNumberId = !!process.env.WHATSAPP_PHONE_NUMBER_ID;
273
+ const hasAccessToken = !!(await resolveSecret("WHATSAPP_ACCESS_TOKEN"));
274
+ const hasVerifyToken = !!(await resolveSecret("WHATSAPP_VERIFY_TOKEN"));
275
+ const hasPhoneNumberId = !!(await resolveSecret(
276
+ "WHATSAPP_PHONE_NUMBER_ID",
277
+ ));
275
278
  const configured = hasAccessToken && hasVerifyToken && hasPhoneNumberId;
276
279
 
277
280
  return {
@@ -285,7 +288,7 @@ export function whatsappAdapter(): PlatformAdapter {
285
288
  hasPhoneNumberId,
286
289
  },
287
290
  error: !configured
288
- ? "Set WHATSAPP_ACCESS_TOKEN, WHATSAPP_VERIFY_TOKEN, and WHATSAPP_PHONE_NUMBER_ID"
291
+ ? "Save WHATSAPP_ACCESS_TOKEN, WHATSAPP_VERIFY_TOKEN, and WHATSAPP_PHONE_NUMBER_ID in settings"
289
292
  : undefined,
290
293
  };
291
294
  },