@agent-native/core 0.117.1 → 0.117.2

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 (347) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +10 -0
  3. package/corpus/core/package.json +2 -1
  4. package/corpus/core/src/cli/context-xray-local.ts +1 -1
  5. package/corpus/core/src/client/extensions/ExtensionViewer.tsx +4 -4
  6. package/corpus/core/src/client/extensions/ExtensionsSidebarSection.tsx +11 -11
  7. package/corpus/core/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  8. package/corpus/core/src/client/extensions/delete-extension.ts +2 -2
  9. package/corpus/core/src/client/org/OrgSwitcher.tsx +25 -1
  10. package/corpus/core/src/client/settings/SettingsPanel.tsx +102 -10
  11. package/corpus/core/src/client/sharing/ShareButton.tsx +7 -6
  12. package/corpus/core/src/db/client.ts +13 -10
  13. package/corpus/core/src/extensions/actions.ts +13 -5
  14. package/corpus/core/src/extensions/local.ts +1 -0
  15. package/corpus/core/src/extensions/routes.ts +10 -5
  16. package/corpus/core/src/extensions/schema.ts +5 -0
  17. package/corpus/core/src/extensions/slots/store.ts +14 -4
  18. package/corpus/core/src/extensions/store.ts +55 -19
  19. package/corpus/core/src/extensions/theme.ts +1 -1
  20. package/corpus/core/src/file-upload/registry.ts +9 -0
  21. package/corpus/core/src/index.browser.ts +5 -0
  22. package/corpus/core/src/index.ts +1 -0
  23. package/corpus/core/src/localization/default-messages.ts +23 -6
  24. package/corpus/core/src/server/action-discovery.ts +8 -0
  25. package/corpus/core/src/server/better-auth-instance.ts +7 -2
  26. package/corpus/core/src/server/email-template.ts +18 -0
  27. package/corpus/core/src/server/email-templates.ts +3 -0
  28. package/corpus/core/src/server/email.ts +40 -4
  29. package/corpus/core/src/sharing/actions/share-resource.ts +1 -0
  30. package/corpus/core/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  31. package/corpus/core/src/templates/default/app/global.css +1 -1
  32. package/corpus/core/src/templates/default/app/i18n/ar-SA.ts +6 -6
  33. package/corpus/core/src/templates/default/app/i18n/de-DE.ts +6 -6
  34. package/corpus/core/src/templates/default/app/i18n/en-US.ts +6 -6
  35. package/corpus/core/src/templates/default/app/i18n/es-ES.ts +6 -6
  36. package/corpus/core/src/templates/default/app/i18n/fr-FR.ts +6 -6
  37. package/corpus/core/src/templates/default/app/i18n/hi-IN.ts +6 -6
  38. package/corpus/core/src/templates/default/app/i18n/ja-JP.ts +6 -6
  39. package/corpus/core/src/templates/default/app/i18n/ko-KR.ts +6 -6
  40. package/corpus/core/src/templates/default/app/i18n/pt-BR.ts +6 -6
  41. package/corpus/core/src/templates/default/app/i18n/zh-CN.ts +7 -7
  42. package/corpus/core/src/templates/default/app/i18n/zh-TW.ts +7 -7
  43. package/corpus/core/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  44. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  45. package/corpus/core/src/user-profile/actions/get-user-profile.ts +16 -0
  46. package/corpus/core/src/user-profile/actions/update-user-profile.ts +24 -0
  47. package/corpus/core/src/user-profile/index.ts +5 -0
  48. package/corpus/core/src/user-profile/shared.ts +14 -0
  49. package/corpus/core/src/user-profile/store.ts +50 -0
  50. package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  51. package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  52. package/corpus/templates/assets/app/components/layout/Sidebar.tsx +2 -2
  53. package/corpus/templates/assets/app/global.css +1 -1
  54. package/corpus/templates/assets/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  55. package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  56. package/corpus/templates/brain/app/global.css +2 -2
  57. package/corpus/templates/brain/app/lib/brain.ts +2 -2
  58. package/corpus/templates/brain/app/root.tsx +2 -2
  59. package/corpus/templates/brain/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  60. package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  61. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +6 -2
  62. package/corpus/templates/calendar/app/global.css +1 -1
  63. package/corpus/templates/calendar/app/root.tsx +2 -2
  64. package/corpus/templates/calendar/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  65. package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  66. package/corpus/templates/chat/app/components/layout/Sidebar.tsx +2 -2
  67. package/corpus/templates/chat/app/global.css +1 -1
  68. package/corpus/templates/chat/app/root.tsx +2 -2
  69. package/corpus/templates/chat/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  70. package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  71. package/corpus/templates/clips/actions/export-to-brain.ts +1 -1
  72. package/corpus/templates/clips/actions/invite-member.ts +1 -0
  73. package/corpus/templates/clips/app/components/library/library-layout.tsx +2 -2
  74. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +20 -11
  75. package/corpus/templates/clips/app/global.css +1 -1
  76. package/corpus/templates/clips/app/root.tsx +7 -2
  77. package/corpus/templates/clips/app/routes/record.tsx +2 -88
  78. package/corpus/templates/clips/changelog/2026-07-22-builder-connected-recordings-start-resumable-upload-sessions.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-07-22-clip-thumbnails-now-come-from-the-recording-instead-of-the-p.md +6 -0
  80. package/corpus/templates/clips/changelog/2026-07-22-clips-emails-now-render-the-agent-native-logo-in-gmail-and-o.md +6 -0
  81. package/corpus/templates/clips/changelog/2026-07-22-dismissed-zoom-and-teams-meeting-note-prompts-now-stay-dismi.md +6 -0
  82. package/corpus/templates/clips/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  83. package/corpus/templates/clips/changelog/2026-07-22-quieter-permissions-and-rewind-controls.md +6 -0
  84. package/corpus/templates/clips/desktop/src/app.tsx +86 -52
  85. package/corpus/templates/clips/desktop/src/lib/meeting-notification-dismissal.ts +31 -0
  86. package/corpus/templates/clips/desktop/src/lib/recorder.ts +4 -167
  87. package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +10 -1
  88. package/corpus/templates/clips/desktop/src/styles.css +98 -36
  89. package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +67 -4
  90. package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +1 -1
  91. package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +51 -0
  92. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +5 -1
  93. package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +0 -189
  94. package/corpus/templates/clips/desktop/src-tauri/src/notifications.rs +62 -0
  95. package/corpus/templates/clips/server/plugins/db.ts +6 -2
  96. package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  97. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +3 -3
  98. package/corpus/templates/content/app/global.css +1 -1
  99. package/corpus/templates/content/app/root.tsx +2 -2
  100. package/corpus/templates/content/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  101. package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  102. package/corpus/templates/crm/app/global.css +1 -1
  103. package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  104. package/corpus/templates/design/app/components/design/CanvasContextMenu.tsx +19 -1
  105. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +12 -1
  106. package/corpus/templates/design/app/components/design/EditPanel.tsx +177 -52
  107. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +179 -59
  108. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +74 -27
  109. package/corpus/templates/design/app/components/design/multi-screen/types.ts +2 -0
  110. package/corpus/templates/design/app/components/layout/Sidebar.tsx +2 -2
  111. package/corpus/templates/design/app/global.css +1 -1
  112. package/corpus/templates/design/app/hooks/use-navigation-state.ts +6 -5
  113. package/corpus/templates/design/app/root.tsx +2 -2
  114. package/corpus/templates/design/changelog/2026-07-22-design-owners-can-keep-editing-their-designs-after-switching.md +6 -0
  115. package/corpus/templates/design/changelog/2026-07-22-inspector-tabs-keep-their-text-labels-at-narrow-widths.md +6 -0
  116. package/corpus/templates/design/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  117. package/corpus/templates/design/changelog/2026-07-22-narrow-breakpoint-previews-keep-their-device-labels-readable.md +6 -0
  118. package/corpus/templates/design/changelog/2026-07-22-removed-the-draft-status-chip-from-the-design-editor.md +6 -0
  119. package/corpus/templates/design/changelog/2026-07-22-responsive-breakpoint-controls-stay-within-narrow-sidebars.md +6 -0
  120. package/corpus/templates/design/changelog/2026-07-22-responsive-peers-now-use-a-softer-selection-highlight-withou.md +6 -0
  121. package/corpus/templates/design/changelog/2026-07-22-rotate-from-context-menu.md +6 -0
  122. package/corpus/templates/design/changelog/2026-07-22-the-selection-outline-now-stays-attached-while-moving-screen.md +6 -0
  123. package/corpus/templates/design/changelog/2026-07-22-view-only-designs-now-support-figma-style-layer-inspection-w.md +6 -0
  124. package/corpus/templates/design/server/db/index.ts +4 -0
  125. package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  126. package/corpus/templates/dispatch/app/dispatch-extensions.tsx +2 -2
  127. package/corpus/templates/dispatch/app/global.css +1 -1
  128. package/corpus/templates/dispatch/app/root.tsx +2 -2
  129. package/corpus/templates/dispatch/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  130. package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  131. package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -1
  132. package/corpus/templates/forms/app/components/layout/Sidebar.tsx +3 -3
  133. package/corpus/templates/forms/app/global.css +1 -1
  134. package/corpus/templates/forms/app/hooks/use-forms.ts +55 -2
  135. package/corpus/templates/forms/app/i18n/en-US.ts +2 -0
  136. package/corpus/templates/forms/app/pages/FormBuilderPage.tsx +3 -3
  137. package/corpus/templates/forms/app/pages/FormsListPage.tsx +23 -6
  138. package/corpus/templates/forms/app/root.tsx +2 -2
  139. package/corpus/templates/forms/changelog/2026-07-22-forms-now-archive-with-optimistic-feedback.md +6 -0
  140. package/corpus/templates/forms/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  141. package/corpus/templates/forms/server/plugins/agent-chat.ts +1 -1
  142. package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  143. package/corpus/templates/macros/app/components/layout/AppLayout.tsx +2 -2
  144. package/corpus/templates/macros/app/root.tsx +2 -2
  145. package/corpus/templates/macros/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  146. package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  147. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +3 -3
  148. package/corpus/templates/mail/app/components/layout/CommandPalette.tsx +2 -2
  149. package/corpus/templates/mail/app/global.css +1 -1
  150. package/corpus/templates/mail/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  151. package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  152. package/corpus/templates/plan/app/components/layout/Sidebar.tsx +2 -2
  153. package/corpus/templates/plan/app/global.css +1 -1
  154. package/corpus/templates/plan/app/root.tsx +2 -2
  155. package/corpus/templates/plan/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  156. package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  157. package/corpus/templates/slides/app/components/layout/Sidebar.tsx +2 -2
  158. package/corpus/templates/slides/app/root.tsx +2 -2
  159. package/corpus/templates/slides/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  160. package/corpus/templates/slides/changelog/2026-07-22-slides-no-longer-fail-intermittently-on-fresh-server-instances.md +6 -0
  161. package/corpus/templates/slides/changelog/2026-07-22-uploaded-screenshots-can-now-be-analyzed-and-recreated-as-ed.md +6 -0
  162. package/corpus/templates/slides/server/lib/chat-attachments.ts +7 -3
  163. package/corpus/templates/slides/server/plugins/db.ts +44 -17
  164. package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  165. package/corpus/templates/tasks/app/global.css +1 -1
  166. package/dist/cli/context-xray-local.js +1 -1
  167. package/dist/cli/context-xray-local.js.map +1 -1
  168. package/dist/client/extensions/ExtensionViewer.js +2 -2
  169. package/dist/client/extensions/ExtensionViewer.js.map +1 -1
  170. package/dist/client/extensions/ExtensionsSidebarSection.js +11 -11
  171. package/dist/client/extensions/ExtensionsSidebarSection.js.map +1 -1
  172. package/dist/client/extensions/agent-native-extension-runtime.js +1 -1
  173. package/dist/client/extensions/agent-native-extension-runtime.js.map +1 -1
  174. package/dist/client/extensions/delete-extension.d.ts +1 -1
  175. package/dist/client/extensions/delete-extension.d.ts.map +1 -1
  176. package/dist/client/extensions/delete-extension.js +1 -1
  177. package/dist/client/extensions/delete-extension.js.map +1 -1
  178. package/dist/client/org/OrgSwitcher.d.ts +3 -1
  179. package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
  180. package/dist/client/org/OrgSwitcher.js +12 -4
  181. package/dist/client/org/OrgSwitcher.js.map +1 -1
  182. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  183. package/dist/client/settings/SettingsPanel.js +37 -4
  184. package/dist/client/settings/SettingsPanel.js.map +1 -1
  185. package/dist/client/sharing/ShareButton.d.ts +3 -4
  186. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  187. package/dist/client/sharing/ShareButton.js +3 -2
  188. package/dist/client/sharing/ShareButton.js.map +1 -1
  189. package/dist/collab/awareness.d.ts +2 -2
  190. package/dist/collab/awareness.d.ts.map +1 -1
  191. package/dist/db/client.d.ts +7 -7
  192. package/dist/db/client.d.ts.map +1 -1
  193. package/dist/db/client.js +13 -10
  194. package/dist/db/client.js.map +1 -1
  195. package/dist/extensions/actions.d.ts.map +1 -1
  196. package/dist/extensions/actions.js +8 -5
  197. package/dist/extensions/actions.js.map +1 -1
  198. package/dist/extensions/local.d.ts.map +1 -1
  199. package/dist/extensions/local.js +1 -0
  200. package/dist/extensions/local.js.map +1 -1
  201. package/dist/extensions/routes.d.ts.map +1 -1
  202. package/dist/extensions/routes.js +9 -4
  203. package/dist/extensions/routes.js.map +1 -1
  204. package/dist/extensions/schema.d.ts +23 -2
  205. package/dist/extensions/schema.d.ts.map +1 -1
  206. package/dist/extensions/schema.js +5 -0
  207. package/dist/extensions/schema.js.map +1 -1
  208. package/dist/extensions/slots/store.d.ts +1 -1
  209. package/dist/extensions/slots/store.d.ts.map +1 -1
  210. package/dist/extensions/slots/store.js +4 -4
  211. package/dist/extensions/slots/store.js.map +1 -1
  212. package/dist/extensions/store.d.ts +1 -0
  213. package/dist/extensions/store.d.ts.map +1 -1
  214. package/dist/extensions/store.js +38 -21
  215. package/dist/extensions/store.js.map +1 -1
  216. package/dist/extensions/theme.js +1 -1
  217. package/dist/extensions/theme.js.map +1 -1
  218. package/dist/file-upload/registry.d.ts.map +1 -1
  219. package/dist/file-upload/registry.js +9 -0
  220. package/dist/file-upload/registry.js.map +1 -1
  221. package/dist/index.browser.d.ts +1 -0
  222. package/dist/index.browser.d.ts.map +1 -1
  223. package/dist/index.browser.js +1 -0
  224. package/dist/index.browser.js.map +1 -1
  225. package/dist/index.d.ts +1 -0
  226. package/dist/index.d.ts.map +1 -1
  227. package/dist/index.js +1 -0
  228. package/dist/index.js.map +1 -1
  229. package/dist/localization/default-messages.d.ts +16 -0
  230. package/dist/localization/default-messages.d.ts.map +1 -1
  231. package/dist/localization/default-messages.js +22 -6
  232. package/dist/localization/default-messages.js.map +1 -1
  233. package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
  234. package/dist/provider-api/actions/provider-api.d.ts +11 -11
  235. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  236. package/dist/secrets/routes.d.ts +6 -6
  237. package/dist/server/action-discovery.d.ts.map +1 -1
  238. package/dist/server/action-discovery.js +8 -0
  239. package/dist/server/action-discovery.js.map +1 -1
  240. package/dist/server/agent-engine-api-key-route.d.ts +1 -1
  241. package/dist/server/better-auth-instance.d.ts +7 -2
  242. package/dist/server/better-auth-instance.d.ts.map +1 -1
  243. package/dist/server/better-auth-instance.js.map +1 -1
  244. package/dist/server/email-template.d.ts +3 -0
  245. package/dist/server/email-template.d.ts.map +1 -1
  246. package/dist/server/email-template.js +13 -0
  247. package/dist/server/email-template.js.map +1 -1
  248. package/dist/server/email-templates.d.ts.map +1 -1
  249. package/dist/server/email-templates.js +3 -0
  250. package/dist/server/email-templates.js.map +1 -1
  251. package/dist/server/email.d.ts.map +1 -1
  252. package/dist/server/email.js +27 -4
  253. package/dist/server/email.js.map +1 -1
  254. package/dist/sharing/actions/share-resource.js +1 -0
  255. package/dist/sharing/actions/share-resource.js.map +1 -1
  256. package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  257. package/dist/templates/chat/app/components/layout/Sidebar.tsx +2 -2
  258. package/dist/templates/chat/app/global.css +1 -1
  259. package/dist/templates/chat/app/root.tsx +2 -2
  260. package/dist/templates/chat/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  261. package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  262. package/dist/templates/default/app/global.css +1 -1
  263. package/dist/templates/default/app/i18n/ar-SA.ts +6 -6
  264. package/dist/templates/default/app/i18n/de-DE.ts +6 -6
  265. package/dist/templates/default/app/i18n/en-US.ts +6 -6
  266. package/dist/templates/default/app/i18n/es-ES.ts +6 -6
  267. package/dist/templates/default/app/i18n/fr-FR.ts +6 -6
  268. package/dist/templates/default/app/i18n/hi-IN.ts +6 -6
  269. package/dist/templates/default/app/i18n/ja-JP.ts +6 -6
  270. package/dist/templates/default/app/i18n/ko-KR.ts +6 -6
  271. package/dist/templates/default/app/i18n/pt-BR.ts +6 -6
  272. package/dist/templates/default/app/i18n/zh-CN.ts +7 -7
  273. package/dist/templates/default/app/i18n/zh-TW.ts +7 -7
  274. package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  275. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  276. package/dist/user-profile/actions/get-user-profile.d.ts +4 -0
  277. package/dist/user-profile/actions/get-user-profile.d.ts.map +1 -0
  278. package/dist/user-profile/actions/get-user-profile.js +14 -0
  279. package/dist/user-profile/actions/get-user-profile.js.map +1 -0
  280. package/dist/user-profile/actions/update-user-profile.d.ts +6 -0
  281. package/dist/user-profile/actions/update-user-profile.d.ts.map +1 -0
  282. package/dist/user-profile/actions/update-user-profile.js +20 -0
  283. package/dist/user-profile/actions/update-user-profile.js.map +1 -0
  284. package/dist/user-profile/index.d.ts +2 -0
  285. package/dist/user-profile/index.d.ts.map +1 -0
  286. package/dist/user-profile/index.js +2 -0
  287. package/dist/user-profile/index.js.map +1 -0
  288. package/dist/user-profile/shared.d.ts +7 -0
  289. package/dist/user-profile/shared.d.ts.map +1 -0
  290. package/dist/user-profile/shared.js +6 -0
  291. package/dist/user-profile/shared.js.map +1 -0
  292. package/dist/user-profile/store.d.ts +4 -0
  293. package/dist/user-profile/store.d.ts.map +1 -0
  294. package/dist/user-profile/store.js +38 -0
  295. package/dist/user-profile/store.js.map +1 -0
  296. package/package.json +2 -1
  297. package/src/cli/context-xray-local.ts +1 -1
  298. package/src/client/extensions/ExtensionViewer.tsx +4 -4
  299. package/src/client/extensions/ExtensionsSidebarSection.tsx +11 -11
  300. package/src/client/extensions/agent-native-extension-runtime.ts +1 -1
  301. package/src/client/extensions/delete-extension.ts +2 -2
  302. package/src/client/org/OrgSwitcher.tsx +25 -1
  303. package/src/client/settings/SettingsPanel.tsx +102 -10
  304. package/src/client/sharing/ShareButton.tsx +7 -6
  305. package/src/db/client.ts +13 -10
  306. package/src/extensions/actions.ts +13 -5
  307. package/src/extensions/local.ts +1 -0
  308. package/src/extensions/routes.ts +10 -5
  309. package/src/extensions/schema.ts +5 -0
  310. package/src/extensions/slots/store.ts +14 -4
  311. package/src/extensions/store.ts +55 -19
  312. package/src/extensions/theme.ts +1 -1
  313. package/src/file-upload/registry.ts +9 -0
  314. package/src/index.browser.ts +5 -0
  315. package/src/index.ts +1 -0
  316. package/src/localization/default-messages.ts +23 -6
  317. package/src/server/action-discovery.ts +8 -0
  318. package/src/server/better-auth-instance.ts +7 -2
  319. package/src/server/email-template.ts +18 -0
  320. package/src/server/email-templates.ts +3 -0
  321. package/src/server/email.ts +40 -4
  322. package/src/sharing/actions/share-resource.ts +1 -0
  323. package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  324. package/src/templates/chat/app/components/layout/Sidebar.tsx +2 -2
  325. package/src/templates/chat/app/global.css +1 -1
  326. package/src/templates/chat/app/root.tsx +2 -2
  327. package/src/templates/chat/changelog/2026-07-22-manage-agent-navigation-now-uses-the-connected-nodes-icon.md +6 -0
  328. package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  329. package/src/templates/default/app/global.css +1 -1
  330. package/src/templates/default/app/i18n/ar-SA.ts +6 -6
  331. package/src/templates/default/app/i18n/de-DE.ts +6 -6
  332. package/src/templates/default/app/i18n/en-US.ts +6 -6
  333. package/src/templates/default/app/i18n/es-ES.ts +6 -6
  334. package/src/templates/default/app/i18n/fr-FR.ts +6 -6
  335. package/src/templates/default/app/i18n/hi-IN.ts +6 -6
  336. package/src/templates/default/app/i18n/ja-JP.ts +6 -6
  337. package/src/templates/default/app/i18n/ko-KR.ts +6 -6
  338. package/src/templates/default/app/i18n/pt-BR.ts +6 -6
  339. package/src/templates/default/app/i18n/zh-CN.ts +7 -7
  340. package/src/templates/default/app/i18n/zh-TW.ts +7 -7
  341. package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  342. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +6 -0
  343. package/src/user-profile/actions/get-user-profile.ts +16 -0
  344. package/src/user-profile/actions/update-user-profile.ts +24 -0
  345. package/src/user-profile/index.ts +5 -0
  346. package/src/user-profile/shared.ts +14 -0
  347. package/src/user-profile/store.ts +50 -0
@@ -1 +1 @@
1
- {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/server/email.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAIH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;CACjC;AAiCD,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAE/D;AAkBD,wBAAsB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CA+GlE"}
1
+ {"version":3,"file":"email.d.ts","sourceRoot":"","sources":["../../src/server/email.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;GAUG;AAOH,MAAM,MAAM,aAAa,GAAG,QAAQ,GAAG,UAAU,GAAG,KAAK,CAAC;AAE1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,GAAG,MAAM,CAAC;IACzB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,WAAW,CAAC,EAAE,YAAY,GAAG,QAAQ,CAAC;CACvC;AAED,MAAM,WAAW,aAAa;IAC5B,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,EAAE,CAAC,EAAE,MAAM,GAAG,MAAM,EAAE,CAAC;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,eAAe,EAAE,CAAC;CACjC;AAiED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,OAAO,CAAC,CAE1D;AAED,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,aAAa,CAAC,CAE/D;AAkBD,wBAAsB,SAAS,CAAC,IAAI,EAAE,aAAa,GAAG,OAAO,CAAC,IAAI,CAAC,CAgHlE"}
@@ -9,7 +9,29 @@
9
9
  * With neither provider configured, `sendEmail` logs the message to the console
10
10
  * so the reset-password flow still works end-to-end for local development.
11
11
  */
12
+ import { readFileSync } from "node:fs";
12
13
  import { resolveSecret } from "./credential-provider.js";
14
+ import { AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID } from "./email-template.js";
15
+ let cachedAgentNativeLogo;
16
+ function getAgentNativeLogoAttachment() {
17
+ cachedAgentNativeLogo ??= readFileSync(new URL("../../src/assets/branding/favicon.png", import.meta.url));
18
+ return {
19
+ filename: "agent-native-logo.png",
20
+ content: cachedAgentNativeLogo,
21
+ contentType: "image/png",
22
+ contentId: AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID,
23
+ disposition: "inline",
24
+ };
25
+ }
26
+ function resolveAttachments(args) {
27
+ if (!args.html.includes(`cid:${AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID}`)) {
28
+ return args.attachments;
29
+ }
30
+ if (args.attachments?.some((attachment) => attachment.contentId === AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID)) {
31
+ return args.attachments;
32
+ }
33
+ return [...(args.attachments ?? []), getAgentNativeLogoAttachment()];
34
+ }
13
35
  async function resolveEmailTransport() {
14
36
  const [resendApiKey, sendgridApiKey, from] = await Promise.all([
15
37
  resolveSecret("RESEND_API_KEY"),
@@ -54,6 +76,7 @@ export async function sendEmail(args) {
54
76
  const config = await resolveEmailTransport();
55
77
  const provider = config.provider;
56
78
  const from = getFromAddress(config, args.from);
79
+ const attachments = resolveAttachments(args);
57
80
  if (provider === "resend") {
58
81
  const payload = {
59
82
  from,
@@ -66,8 +89,8 @@ export async function sendEmail(args) {
66
89
  payload.cc = Array.isArray(args.cc) ? args.cc : [args.cc];
67
90
  if (args.replyTo)
68
91
  payload.reply_to = args.replyTo;
69
- if (args.attachments?.length) {
70
- payload.attachments = args.attachments.map((a) => ({
92
+ if (attachments?.length) {
93
+ payload.attachments = attachments.map((a) => ({
71
94
  filename: a.filename,
72
95
  content: typeof a.content === "string"
73
96
  ? a.content
@@ -123,8 +146,8 @@ export async function sendEmail(args) {
123
146
  sgHeaders["References"] = args.references;
124
147
  if (Object.keys(sgHeaders).length)
125
148
  sgPayload.headers = sgHeaders;
126
- if (args.attachments?.length) {
127
- sgPayload.attachments = args.attachments.map((a) => ({
149
+ if (attachments?.length) {
150
+ sgPayload.attachments = attachments.map((a) => ({
128
151
  filename: a.filename,
129
152
  content: typeof a.content === "string"
130
153
  ? Buffer.from(a.content).toString("base64")
@@ -1 +1 @@
1
- {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/server/email.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Email transport for system emails (password resets, invitations, notifications).\n *\n * Providers are selected by scoped secrets:\n * RESEND_API_KEY — https://resend.com\n * SENDGRID_API_KEY — https://sendgrid.com\n * EMAIL_FROM — \"Name <addr@domain>\" (optional; defaults to Resend's sandbox)\n *\n * With neither provider configured, `sendEmail` logs the message to the console\n * so the reset-password flow still works end-to-end for local development.\n */\n\nimport { resolveSecret } from \"./credential-provider.js\";\n\nexport type EmailProvider = \"resend\" | \"sendgrid\" | \"dev\";\n\nexport interface EmailAttachment {\n filename: string;\n content: string | Buffer;\n contentType?: string;\n contentId?: string;\n disposition?: \"attachment\" | \"inline\";\n}\n\nexport interface SendEmailArgs {\n to: string;\n subject: string;\n html: string;\n text?: string;\n from?: string;\n cc?: string | string[];\n replyTo?: string;\n inReplyTo?: string;\n references?: string;\n attachments?: EmailAttachment[];\n}\n\ninterface EmailTransportConfig {\n provider: EmailProvider;\n resendApiKey?: string;\n sendgridApiKey?: string;\n from?: string;\n}\n\nasync function resolveEmailTransport(): Promise<EmailTransportConfig> {\n const [resendApiKey, sendgridApiKey, from] = await Promise.all([\n resolveSecret(\"RESEND_API_KEY\"),\n resolveSecret(\"SENDGRID_API_KEY\"),\n resolveSecret(\"EMAIL_FROM\"),\n ]);\n const resolvedFrom = from ?? undefined;\n if (resendApiKey) {\n return {\n provider: \"resend\",\n resendApiKey,\n from: resolvedFrom,\n };\n }\n if (sendgridApiKey) {\n return {\n provider: \"sendgrid\",\n sendgridApiKey,\n from: resolvedFrom,\n };\n }\n return { provider: \"dev\", from: resolvedFrom };\n}\n\nexport async function isEmailConfigured(): Promise<boolean> {\n return (await resolveEmailTransport()).provider !== \"dev\";\n}\n\nexport async function getEmailProvider(): Promise<EmailProvider> {\n return (await resolveEmailTransport()).provider;\n}\n\nfunction getFromAddress(\n config: EmailTransportConfig,\n override?: string,\n): string {\n const explicit = override || config.from;\n if (explicit) return explicit;\n // Resend lets unverified accounts send from its sandbox domain; SendGrid\n // does not, so falling back there would cause silent 403s at runtime.\n if (config.provider === \"sendgrid\") {\n throw new Error(\n \"EMAIL_FROM is required when using SendGrid — save it as a verified sender address.\",\n );\n }\n return \"Agent Native <onboarding@resend.dev>\";\n}\n\nexport async function sendEmail(args: SendEmailArgs): Promise<void> {\n const config = await resolveEmailTransport();\n const provider = config.provider;\n const from = getFromAddress(config, args.from);\n\n if (provider === \"resend\") {\n const payload: Record<string, unknown> = {\n from,\n to: args.to,\n subject: args.subject,\n html: args.html,\n text: args.text,\n };\n if (args.cc) payload.cc = Array.isArray(args.cc) ? args.cc : [args.cc];\n if (args.replyTo) payload.reply_to = args.replyTo;\n if (args.attachments?.length) {\n payload.attachments = args.attachments.map((a) => ({\n filename: a.filename,\n content:\n typeof a.content === \"string\"\n ? a.content\n : a.content.toString(\"base64\"),\n content_type: a.contentType,\n content_id: a.contentId,\n }));\n }\n const headers: Record<string, string> = {};\n if (args.inReplyTo) headers[\"In-Reply-To\"] = args.inReplyTo;\n if (args.references) headers[\"References\"] = args.references;\n if (Object.keys(headers).length) payload.headers = headers;\n\n const res = await fetch(\"https://api.resend.com/emails\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${config.resendApiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`Resend error ${res.status}: ${body}`);\n }\n return;\n }\n\n if (provider === \"sendgrid\") {\n const personalization: Record<string, unknown> = {\n to: [{ email: args.to }],\n };\n if (args.cc) {\n const ccList = Array.isArray(args.cc) ? args.cc : [args.cc];\n personalization.cc = ccList.map((email) => ({ email }));\n }\n\n const sgPayload: Record<string, unknown> = {\n personalizations: [personalization],\n from: parseSendGridFrom(from),\n subject: args.subject,\n content: [\n ...(args.text ? [{ type: \"text/plain\", value: args.text }] : []),\n { type: \"text/html\", value: args.html },\n ],\n };\n if (args.replyTo) sgPayload.reply_to = parseSendGridFrom(args.replyTo);\n const sgHeaders: Record<string, string> = {};\n if (args.inReplyTo) sgHeaders[\"In-Reply-To\"] = args.inReplyTo;\n if (args.references) sgHeaders[\"References\"] = args.references;\n if (Object.keys(sgHeaders).length) sgPayload.headers = sgHeaders;\n if (args.attachments?.length) {\n sgPayload.attachments = args.attachments.map((a) => ({\n filename: a.filename,\n content:\n typeof a.content === \"string\"\n ? Buffer.from(a.content).toString(\"base64\")\n : a.content.toString(\"base64\"),\n type: a.contentType,\n disposition: a.disposition ?? (a.contentId ? \"inline\" : undefined),\n content_id: a.contentId,\n }));\n }\n\n const res = await fetch(\"https://api.sendgrid.com/v3/mail/send\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${config.sendgridApiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(sgPayload),\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`SendGrid error ${res.status}: ${body}`);\n }\n return;\n }\n\n // Dev fallback — no provider configured. Logging the full body exposes\n // reset tokens, so only do it outside production. In production, refuse\n // to send rather than silently leaking secrets to logs.\n if (process.env.NODE_ENV === \"production\") {\n throw new Error(\n \"No email provider configured. Save RESEND_API_KEY or SENDGRID_API_KEY in settings.\",\n );\n }\n console.log(\n `\\n[agent-native:email] No email provider configured. ` +\n `Save RESEND_API_KEY or SENDGRID_API_KEY in settings to send real emails.\\n` +\n `---\\nTo: ${args.to}\\nFrom: ${from}\\nSubject: ${args.subject}\\n\\n` +\n `${args.text || stripHtml(args.html)}\\n---\\n`,\n );\n}\n\nfunction parseSendGridFrom(from: string): { email: string; name?: string } {\n const m = from.match(/^\\s*(.*?)\\s*<(.+)>\\s*$/);\n if (m && m[2]) return { name: m[1] || undefined, email: m[2] };\n return { email: from.trim() };\n}\n\nfunction stripHtml(html: string): string {\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/p>/gi, \"\\n\\n\")\n .replace(/<[^>]+>/g, \"\")\n .replace(/&nbsp;/g, \" \")\n .trim();\n}\n"]}
1
+ {"version":3,"file":"email.js","sourceRoot":"","sources":["../../src/server/email.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Email transport for system emails (password resets, invitations, notifications).\n *\n * Providers are selected by scoped secrets:\n * RESEND_API_KEY — https://resend.com\n * SENDGRID_API_KEY — https://sendgrid.com\n * EMAIL_FROM — \"Name <addr@domain>\" (optional; defaults to Resend's sandbox)\n *\n * With neither provider configured, `sendEmail` logs the message to the console\n * so the reset-password flow still works end-to-end for local development.\n */\n\nimport { readFileSync } from \"node:fs\";\n\nimport { resolveSecret } from \"./credential-provider.js\";\nimport { AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID } from \"./email-template.js\";\n\nexport type EmailProvider = \"resend\" | \"sendgrid\" | \"dev\";\n\nexport interface EmailAttachment {\n filename: string;\n content: string | Buffer;\n contentType?: string;\n contentId?: string;\n disposition?: \"attachment\" | \"inline\";\n}\n\nexport interface SendEmailArgs {\n to: string;\n subject: string;\n html: string;\n text?: string;\n from?: string;\n cc?: string | string[];\n replyTo?: string;\n inReplyTo?: string;\n references?: string;\n attachments?: EmailAttachment[];\n}\n\nlet cachedAgentNativeLogo: Buffer | undefined;\n\nfunction getAgentNativeLogoAttachment(): EmailAttachment {\n cachedAgentNativeLogo ??= readFileSync(\n new URL(\"../../src/assets/branding/favicon.png\", import.meta.url),\n );\n return {\n filename: \"agent-native-logo.png\",\n content: cachedAgentNativeLogo,\n contentType: \"image/png\",\n contentId: AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID,\n disposition: \"inline\",\n };\n}\n\nfunction resolveAttachments(\n args: SendEmailArgs,\n): EmailAttachment[] | undefined {\n if (!args.html.includes(`cid:${AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID}`)) {\n return args.attachments;\n }\n if (\n args.attachments?.some(\n (attachment) =>\n attachment.contentId === AGENT_NATIVE_EMAIL_LOGO_CONTENT_ID,\n )\n ) {\n return args.attachments;\n }\n return [...(args.attachments ?? []), getAgentNativeLogoAttachment()];\n}\n\ninterface EmailTransportConfig {\n provider: EmailProvider;\n resendApiKey?: string;\n sendgridApiKey?: string;\n from?: string;\n}\n\nasync function resolveEmailTransport(): Promise<EmailTransportConfig> {\n const [resendApiKey, sendgridApiKey, from] = await Promise.all([\n resolveSecret(\"RESEND_API_KEY\"),\n resolveSecret(\"SENDGRID_API_KEY\"),\n resolveSecret(\"EMAIL_FROM\"),\n ]);\n const resolvedFrom = from ?? undefined;\n if (resendApiKey) {\n return {\n provider: \"resend\",\n resendApiKey,\n from: resolvedFrom,\n };\n }\n if (sendgridApiKey) {\n return {\n provider: \"sendgrid\",\n sendgridApiKey,\n from: resolvedFrom,\n };\n }\n return { provider: \"dev\", from: resolvedFrom };\n}\n\nexport async function isEmailConfigured(): Promise<boolean> {\n return (await resolveEmailTransport()).provider !== \"dev\";\n}\n\nexport async function getEmailProvider(): Promise<EmailProvider> {\n return (await resolveEmailTransport()).provider;\n}\n\nfunction getFromAddress(\n config: EmailTransportConfig,\n override?: string,\n): string {\n const explicit = override || config.from;\n if (explicit) return explicit;\n // Resend lets unverified accounts send from its sandbox domain; SendGrid\n // does not, so falling back there would cause silent 403s at runtime.\n if (config.provider === \"sendgrid\") {\n throw new Error(\n \"EMAIL_FROM is required when using SendGrid — save it as a verified sender address.\",\n );\n }\n return \"Agent Native <onboarding@resend.dev>\";\n}\n\nexport async function sendEmail(args: SendEmailArgs): Promise<void> {\n const config = await resolveEmailTransport();\n const provider = config.provider;\n const from = getFromAddress(config, args.from);\n const attachments = resolveAttachments(args);\n\n if (provider === \"resend\") {\n const payload: Record<string, unknown> = {\n from,\n to: args.to,\n subject: args.subject,\n html: args.html,\n text: args.text,\n };\n if (args.cc) payload.cc = Array.isArray(args.cc) ? args.cc : [args.cc];\n if (args.replyTo) payload.reply_to = args.replyTo;\n if (attachments?.length) {\n payload.attachments = attachments.map((a) => ({\n filename: a.filename,\n content:\n typeof a.content === \"string\"\n ? a.content\n : a.content.toString(\"base64\"),\n content_type: a.contentType,\n content_id: a.contentId,\n }));\n }\n const headers: Record<string, string> = {};\n if (args.inReplyTo) headers[\"In-Reply-To\"] = args.inReplyTo;\n if (args.references) headers[\"References\"] = args.references;\n if (Object.keys(headers).length) payload.headers = headers;\n\n const res = await fetch(\"https://api.resend.com/emails\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${config.resendApiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(payload),\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`Resend error ${res.status}: ${body}`);\n }\n return;\n }\n\n if (provider === \"sendgrid\") {\n const personalization: Record<string, unknown> = {\n to: [{ email: args.to }],\n };\n if (args.cc) {\n const ccList = Array.isArray(args.cc) ? args.cc : [args.cc];\n personalization.cc = ccList.map((email) => ({ email }));\n }\n\n const sgPayload: Record<string, unknown> = {\n personalizations: [personalization],\n from: parseSendGridFrom(from),\n subject: args.subject,\n content: [\n ...(args.text ? [{ type: \"text/plain\", value: args.text }] : []),\n { type: \"text/html\", value: args.html },\n ],\n };\n if (args.replyTo) sgPayload.reply_to = parseSendGridFrom(args.replyTo);\n const sgHeaders: Record<string, string> = {};\n if (args.inReplyTo) sgHeaders[\"In-Reply-To\"] = args.inReplyTo;\n if (args.references) sgHeaders[\"References\"] = args.references;\n if (Object.keys(sgHeaders).length) sgPayload.headers = sgHeaders;\n if (attachments?.length) {\n sgPayload.attachments = attachments.map((a) => ({\n filename: a.filename,\n content:\n typeof a.content === \"string\"\n ? Buffer.from(a.content).toString(\"base64\")\n : a.content.toString(\"base64\"),\n type: a.contentType,\n disposition: a.disposition ?? (a.contentId ? \"inline\" : undefined),\n content_id: a.contentId,\n }));\n }\n\n const res = await fetch(\"https://api.sendgrid.com/v3/mail/send\", {\n method: \"POST\",\n headers: {\n Authorization: `Bearer ${config.sendgridApiKey}`,\n \"Content-Type\": \"application/json\",\n },\n body: JSON.stringify(sgPayload),\n });\n if (!res.ok) {\n const body = await res.text().catch(() => \"\");\n throw new Error(`SendGrid error ${res.status}: ${body}`);\n }\n return;\n }\n\n // Dev fallback — no provider configured. Logging the full body exposes\n // reset tokens, so only do it outside production. In production, refuse\n // to send rather than silently leaking secrets to logs.\n if (process.env.NODE_ENV === \"production\") {\n throw new Error(\n \"No email provider configured. Save RESEND_API_KEY or SENDGRID_API_KEY in settings.\",\n );\n }\n console.log(\n `\\n[agent-native:email] No email provider configured. ` +\n `Save RESEND_API_KEY or SENDGRID_API_KEY in settings to send real emails.\\n` +\n `---\\nTo: ${args.to}\\nFrom: ${from}\\nSubject: ${args.subject}\\n\\n` +\n `${args.text || stripHtml(args.html)}\\n---\\n`,\n );\n}\n\nfunction parseSendGridFrom(from: string): { email: string; name?: string } {\n const m = from.match(/^\\s*(.*?)\\s*<(.+)>\\s*$/);\n if (m && m[2]) return { name: m[1] || undefined, email: m[2] };\n return { email: from.trim() };\n}\n\nfunction stripHtml(html: string): string {\n return html\n .replace(/<br\\s*\\/?>/gi, \"\\n\")\n .replace(/<\\/p>/gi, \"\\n\\n\")\n .replace(/<[^>]+>/g, \"\")\n .replace(/&nbsp;/g, \" \")\n .trim();\n}\n"]}
@@ -228,6 +228,7 @@ export default defineAction({
228
228
  const appName = process.env.APP_NAME || process.env.VITE_APP_NAME || "Agent Native";
229
229
  const subject = `${actor} shared "${resourceTitle}" with you on ${appName}`;
230
230
  const { html, text } = renderEmail({
231
+ brandName: appName,
231
232
  preheader: subject,
232
233
  heading: "You've been given access",
233
234
  paragraphs: [
@@ -1 +1 @@
1
- {"version":3,"file":"share-resource.js","sourceRoot":"","sources":["../../../src/sharing/actions/share-resource.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,GAAG,EAAE,EAAE,EAAE,GAAG,EAAY,MAAM,aAAa,CAAC;AACrD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAC/C,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,mBAAmB,EAAE,MAAM,yBAAyB,CAAC;AAC9D,OAAO,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC1E,OAAO,EAAE,SAAS,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AACrE,OAAO,EAAE,2BAA2B,EAAE,MAAM,sBAAsB,CAAC;AACnE,OAAO,EAAE,mBAAmB,EAAE,MAAM,iCAAiC,CAAC;AACtE,OAAO,EAAE,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAC5D,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAC1D,OAAO,EACL,8BAA8B,EAC9B,2BAA2B,GAC5B,MAAM,uBAAuB,CAAC;AAE/B,MAAM,UAAU,kBAAkB,CAAC,KAAa;IAC9C,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IAC3C,MAAM,EAAE,GAAG,OAAO,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC;IACpC,IAAI,EAAE,IAAI,CAAC;QAAE,OAAO,KAAK,CAAC;IAC1B,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC;IACnC,MAAM,MAAM,GAAG,OAAO,CAAC,KAAK,CAAC,EAAE,GAAG,CAAC,CAAC,CAAC;IACrC,OAAO,CACL,KAAK,CAAC,QAAQ,CAAC,KAAK,CAAC;QACrB,CAAC,MAAM,KAAK,cAAc;YACxB,MAAM,CAAC,QAAQ,CAAC,OAAO,CAAC;YACxB,MAAM,KAAK,iBAAiB;YAC5B,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,CAAC,CAC/B,CAAC;AACJ,CAAC;AAED,SAAS,OAAO,CAAC,IAAY;IAC3B,IAAI,CAAC,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACvC,MAAM,GAAG,GAAG,OAAO,CAAC,GAAG,CAAC,kBAAkB,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,EAAE,CAAC;IAC9E,MAAM,IAAI,GAAG,GAAG,CAAC,IAAI,EAAE,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;IAChE,IAAI,CAAC,IAAI;QAAE,OAAO,IAAI,CAAC;IACvB,MAAM,cAAc,GAAG,IAAI,IAAI,EAAE,CAAC;IAClC,IAAI,IAAI,KAAK,cAAc,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,cAAc,GAAG,CAAC,EAAE,CAAC;QACrE,OAAO,IAAI,CAAC;IACd,CAAC;IACD,OAAO,GAAG,cAAc,GAAG,IAAI,EAAE,CAAC;AACpC,CAAC;AAED,SAAS,mBAAmB,CAAC,KAAa,EAAE,MAAc;IACxD,MAAM,OAAO,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC;IAC7B,IAAI,CAAC,OAAO;QAAE,OAAO,IAAI,CAAC;IAE1B,IAAI,CAAC;QACH,MAAM,IAAI,GAAG,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC;QAC7B,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC5B,MAAM,IAAI,GAAG,OAAO,CAAC,OAAO,CAAC,CAAC;YAC9B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,CAAC;YACnD,MAAM,mBAAmB,GACvB,QAAQ,IAAI,QAAQ,KAAK,GAAG,IAAI,IAAI,CAAC,UAAU,CAAC,GAAG,QAAQ,GAAG,CAAC,CAAC;YAClE,MAAM,MAAM,GAAG,mBAAmB;gBAChC,CAAC,CAAC,GAAG,IAAI,CAAC,MAAM,GAAG,IAAI,EAAE;gBACzB,CAAC,CAAC,GAAG,MAAM,CAAC,OAAO,CAAC,MAAM,EAAE,EAAE,CAAC,GAAG,IAAI,EAAE,CAAC;YAC3C,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,CAAC,QAAQ,EAAE,CAAC;QACpC,CAAC;QAED,MAAM,GAAG,GAAG,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;QAC7B,IAAI,CAAC,CAAC,OAAO,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,GAAG,CAAC,QAAQ,CAAC;YAAE,OAAO,IAAI,CAAC;QAC7D,IAAI,GAAG,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM;YAAE,OAAO,IAAI,CAAC;QAC5C,OAAO,GAAG,CAAC,QAAQ,EAAE,CAAC;IACxB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;AACH,CAAC;AAED,MAAM,UAAU,2BAA2B,CACzC,WAA+B,EAC/B,YAAgC,EAChC,MAAM,GAAG,mBAAmB,EAAE;IAE9B,KAAK,MAAM,SAAS,IAAI,CAAC,WAAW,EAAE,YAAY,CAAC,EAAE,CAAC;QACpD,IAAI,CAAC,SAAS;YAAE,SAAS;QACzB,MAAM,GAAG,GAAG,mBAAmB,CAAC,SAAS,EAAE,MAAM,CAAC,CAAC;QACnD,IAAI,GAAG;YAAE,OAAO,GAAG,CAAC;IACtB,CAAC;IACD,OAAO,MAAM,CAAC;AAChB,CAAC;AAED,SAAS,MAAM,CAAC,IAAI,GAAG,EAAE;IACvB,MAAM,KAAK,GACT,gEAAgE,CAAC;IACnE,IAAI,EAAE,GAAG,EAAE,CAAC;IACZ,MAAM,KAAK,GAAG,MAAM,CAAC,eAAe,CAAC,IAAI,UAAU,CAAC,IAAI,CAAC,CAAC,CAAC;IAC3D,KAAK,MAAM,IAAI,IAAI,KAAK;QAAE,EAAE,IAAI,KAAK,CAAC,IAAI,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC;IAC3D,OAAO,EAAE,CAAC;AACZ,CAAC;AAED,SAAS,oBAAoB,CAC3B,aAA6B,EAC7B,WAAmB;IAEnB,OAAO,aAAa,KAAK,MAAM;QAC7B,CAAC,CAAC,WAAW,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE;QAClC,CAAC,CAAC,WAAW,CAAC;AAClB,CAAC;AAED,SAAS,kBAAkB,CAAC,KAAa;IACvC,OAAO,mBAAmB,CAAC,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,CAAC;AAChD,CAAC;AAED,SAAS,kBAAkB,CACzB,WAAgB,EAChB,aAA6B,EAC7B,WAAmB;IAEnB,OAAO,aAAa,KAAK,MAAM;QAC7B,CAAC,CAAC,GAAG,CAAA,SAAS,WAAW,CAAC,WAAW,OAAO,WAAW,EAAE;QACzD,CAAC,CAAC,EAAE,CAAC,WAAW,CAAC,WAAW,EAAE,WAAW,CAAC,CAAC;AAC/C,CAAC;AAED;;;;;;;;;GASG;AACH,KAAK,UAAU,oBAAoB,CACjC,KAAa,EACb,KAAa;IAEb,MAAM,KAAK,GAAG,KAAK,CAAC,IAAI,EAAE,CAAC,WAAW,EAAE,CAAC;IACzC,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK;QAAE,OAAO,KAAK,CAAC;IACnC,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QAClC,GAAG,EAAE,yEAAyE;QAC9E,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;KACrB,CAAC,CAAC;IACH,IAAI,MAAM,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC;QAAE,OAAO,IAAI,CAAC;IACxC,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,OAAO,CAAC;QACnC,GAAG,EAAE,oGAAoG;QACzG,IAAI,EAAE,CAAC,KAAK,EAAE,KAAK,CAAC;KACrB,CAAC,CAAC;IACH,OAAO,OAAO,CAAC,IAAI,CAAC,MAAM,GAAG,CAAC,CAAC;AACjC,CAAC;AAED,eAAe,YAAY,CAAC;IAC1B,WAAW,EACT,mFAAmF;IACrF,sEAAsE;IACtE,qEAAqE;IACrE,gEAAgE;IAChE,sDAAsD;IACtD,YAAY,EAAE,KAAK;IACnB,MAAM,EAAE,CAAC,CAAC,MAAM,CAAC;QACf,YAAY,EAAE,CAAC;aACZ,MAAM,EAAE;aACR,QAAQ,CAAC,oDAAoD,CAAC;QACjE,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,CAAC,8BAA8B,CAAC;QAC/D,aAAa,EAAE,CAAC;aACb,IAAI,CAAC,CAAC,MAAM,EAAE,KAAK,CAAC,CAAC;aACrB,QAAQ,CAAC,2DAA2D,CAAC;QACxE,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,CAAC,gDAAgD,CAAC;QAC7D,IAAI,EAAE,CAAC;aACJ,IAAI,CAAC,CAAC,QAAQ,EAAE,QAAQ,EAAE,OAAO,CAAC,CAAC;aACnC,OAAO,CAAC,QAAQ,CAAC;aACjB,QAAQ,CAAC,gBAAgB,CAAC;QAC7B,MAAM,EAAE,CAAC;aACN,OAAO,EAAE;aACT,OAAO,CAAC,IAAI,CAAC;aACb,QAAQ,CACP,2EAA2E,CAC5E;QACH,WAAW,EAAE,CAAC;aACX,MAAM,EAAE;aACR,QAAQ,EAAE;aACV,QAAQ,CACP,gGAAgG,CACjG;KACJ,CAAC;IACF,GAAG,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE;QAClB,MAAM,GAAG,GAAG,wBAAwB,CAAC,IAAI,CAAC,YAAY,CAAC,CAAC;QACxD,MAAM,MAAM,GAAG,MAAM,YAAY,CAC/B,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,EACf,OAAO,CACR,CAAC;QACF,MAAM,KAAK,GAAG,mBAAmB,EAAE,CAAC;QACpC,IAAI,CAAC,KAAK;YAAE,MAAM,IAAI,cAAc,CAAC,eAAe,CAAC,CAAC;QACtD,MAAM,WAAW,GAAG,oBAAoB,CACtC,IAAI,CAAC,aAAa,EAClB,IAAI,CAAC,WAAW,CACjB,CAAC;QACF,IAAI,IAAI,CAAC,aAAa,KAAK,MAAM,IAAI,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAAE,CAAC;YACtE,MAAM,IAAI,KAAK,CACb,iEAAiE,CAClE,CAAC;QACJ,CAAC;QACD,MAAM,sBAAsB,GAAG,MAAM,8BAA8B,CACjE,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,CAChB,CAAC;QAEF,IAAI,GAAG,CAAC,6BAA6B,EAAE,CAAC;YACtC,MAAM,aAAa,GAAG,MAAM,CAAC,QAAQ,EAAE,KAAkC,CAAC;YAC1E,IAAI,CAAC,aAAa,EAAE,CAAC;gBACnB,MAAM,IAAI,cAAc,CACtB,GAAG,GAAG,CAAC,WAAW,wFAAwF,CAC3G,CAAC;YACJ,CAAC;YACD,IAAI,IAAI,CAAC,aAAa,KAAK,MAAM,EAAE,CAAC;gBAClC,MAAM,EAAE,GAAG,MAAM,oBAAoB,CAAC,aAAa,EAAE,WAAW,CAAC,CAAC;gBAClE,IAAI,CAAC,EAAE,EAAE,CAAC;oBACR,MAAM,IAAI,cAAc,CACtB,GAAG,WAAW,kFAAkF,CACjG,CAAC;gBACJ,CAAC;YACH,CAAC;iBAAM,IAAI,IAAI,CAAC,aAAa,KAAK,KAAK,EAAE,CAAC;gBACxC,8DAA8D;gBAC9D,gEAAgE;gBAChE,6DAA6D;gBAC7D,8CAA8C;gBAC9C,IAAI,WAAW,KAAK,aAAa,EAAE,CAAC;oBAClC,MAAM,IAAI,cAAc,CACtB,GAAG,GAAG,CAAC,WAAW,qEAAqE,CACxF,CAAC;gBACJ,CAAC;YACH,CAAC;QACH,CAAC;QAED,MAAM,EAAE,GAAG,GAAG,CAAC,KAAK,EAAS,CAAC;QAC9B,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE;aACxB,MAAM,EAAE;aACR,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC;aACrB,KAAK,CACJ,GAAG,CACD,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,UAAU,EAAE,IAAI,CAAC,UAAU,CAAC,EAC/C,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,aAAa,EAAE,IAAI,CAAC,aAAa,CAAC,EACrD,kBAAkB,CAAC,GAAG,CAAC,WAAW,EAAE,IAAI,CAAC,aAAa,EAAE,WAAW,CAAC,CACrE,CACF,CAAC;QAEJ,IAAI,QAAQ,EAAE,CAAC;YACb,MAAM,EAAE;iBACL,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC;iBACvB,GAAG,CAAC,EAAE,IAAI,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC;iBACxB,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,EAAE,QAAQ,CAAC,EAAE,CAAC,CAAC,CAAC;YAC9C,2BAA2B,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;YAChE,MAAM,2BAA2B,CAC/B,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,EACf,sBAAsB,CACvB,CAAC;YACF,OAAO,EAAE,EAAE,EAAE,QAAQ,CAAC,EAAE,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;QAC5C,CAAC;QAED,MAAM,EAAE,GAAG,MAAM,EAAE,CAAC;QACpB,MAAM,EAAE,CAAC,MAAM,CAAC,GAAG,CAAC,WAAW,CAAC,CAAC,MAAM,CAAC;YACtC,EAAE;YACF,UAAU,EAAE,IAAI,CAAC,UAAU;YAC3B,aAAa,EAAE,IAAI,CAAC,aAAa;YACjC,WAAW;YACX,IAAI,EAAE,IAAI,CAAC,IAAI;YACf,SAAS,EAAE,KAAK;YAChB,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;SACpC,CAAC,CAAC;QACH,2BAA2B,CAAC,IAAI,CAAC,YAAY,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC;QAChE,MAAM,2BAA2B,CAC/B,IAAI,CAAC,YAAY,EACjB,IAAI,CAAC,UAAU,EACf,sBAAsB,CACvB,CAAC;QAEF,IACE,IAAI,CAAC,MAAM,KAAK,KAAK;YACrB,IAAI,CAAC,aAAa,KAAK,MAAM;YAC7B,CAAC,MAAM,iBAAiB,EAAE,CAAC;YAC3B,CAAC,kBAAkB,CAAC,WAAW,CAAC,EAChC,CAAC;YACD,IAAI,CAAC;gBACH,MAAM,QAAQ,GAAG,GAAG,CAAC,WAAW,IAAI,OAAO,CAAC;gBAC5C,MAAM,CAAC,QAAQ,CAAC,GAAG,MAAM,EAAE;qBACxB,MAAM,EAAE;qBACR,IAAI,CAAC,GAAG,CAAC,aAAa,CAAC;qBACvB,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC,aAAa,CAAC,EAAE,EAAE,IAAI,CAAC,UAAU,CAAC,CAAC,CAAC;gBACpD,MAAM,aAAa,GAChB,QAAQ,EAAE,CAAC,QAAQ,CAAwB,IAAI,IAAI,CAAC,YAAY,CAAC;gBACpE,MAAM,MAAM,GAAG,mBAAmB,EAAE,CAAC;gBACrC,MAAM,YAAY,GAChB,QAAQ,IAAI,GAAG,CAAC,eAAe;oBAC7B,CAAC,CAAC,GAAG,CAAC,eAAe,CAAC,QAAQ,CAAC;oBAC/B,CAAC,CAAC,SAAS,CAAC;gBAChB,MAAM,eAAe,GAAG,2BAA2B,CACjD,IAAI,CAAC,WAAW,EAChB,YAAY,EACZ,MAAM,CACP,CAAC;gBACF,MAAM,OAAO,GACX,OAAO,CAAC,GAAG,CAAC,QAAQ,IAAI,OAAO,CAAC,GAAG,CAAC,aAAa,IAAI,cAAc,CAAC;gBACtE,MAAM,OAAO,GAAG,GAAG,KAAK,YAAY,aAAa,iBAAiB,OAAO,EAAE,CAAC;gBAC5E,MAAM,EAAE,IAAI,EAAE,IAAI,EAAE,GAAG,WAAW,CAAC;oBACjC,SAAS,EAAE,OAAO;oBAClB,OAAO,EAAE,0BAA0B;oBACnC,UAAU,EAAE;wBACV,GAAG,WAAW,CAAC,KAAK,CAAC,mBAAmB,GAAG,CAAC,WAAW,IAAI,WAAW,CAAC,aAAa,CAAC,kBAAkB,WAAW,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG;wBAChI,8DAA8D,WAAW,CAAC,WAAW,CAAC,GAAG;qBAC1F;oBACD,GAAG,EAAE,EAAE,KAAK,EAAE,QAAQ,GAAG,CAAC,WAAW,EAAE,EAAE,GAAG,EAAE,eAAe,EAAE;oBAC/D,MAAM,EAAE,6BAA6B,KAAK,gBAAgB,IAAI,CAAC,IAAI,UAAU;iBAC9E,CAAC,CAAC;gBACH,MAAM,SAAS,CAAC,EAAE,EAAE,EAAE,WAAW,EAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,CAAC,CAAC;YAC5D,CAAC;YAAC,OAAO,GAAG,EAAE,CAAC;gBACb,OAAO,CAAC,KAAK,CACX,qDAAqD,EACrD,GAAG,CACJ,CAAC;YACJ,CAAC;QACH,CAAC;QAED,OAAO,EAAE,EAAE,EAAE,OAAO,EAAE,KAAK,EAAE,CAAC;IAChC,CAAC;CACF,CAAC,CAAC","sourcesContent":["import { and, eq, sql, type SQL } from \"drizzle-orm\";\nimport { z } from \"zod\";\n\nimport { defineAction } from \"../../action.js\";\nimport { getDbExec } from \"../../db/client.js\";\nimport { getAppProductionUrl } from \"../../server/app-url.js\";\nimport { renderEmail, emailStrong } from \"../../server/email-template.js\";\nimport { sendEmail, isEmailConfigured } from \"../../server/email.js\";\nimport { invalidateCollabAccessCache } from \"../../server/poll.js\";\nimport { getRequestUserEmail } from \"../../server/request-context.js\";\nimport { assertAccess, ForbiddenError } from \"../access.js\";\nimport { requireShareableResource } from \"../registry.js\";\nimport {\n getExtensionShareChangeTargets,\n notifyExtensionShareChanged,\n} from \"./extension-change.js\";\n\nexport function isSyntheticQaEmail(email: string): boolean {\n const trimmed = email.trim().toLowerCase();\n const at = trimmed.lastIndexOf(\"@\");\n if (at <= 0) return false;\n const local = trimmed.slice(0, at);\n const domain = trimmed.slice(at + 1);\n return (\n local.includes(\"+qa\") &&\n (domain === \"example.test\" ||\n domain.endsWith(\".test\") ||\n domain === \"example.invalid\" ||\n domain.endsWith(\".invalid\"))\n );\n}\n\nfunction appPath(path: string): string {\n if (!path.startsWith(\"/\")) return path;\n const raw = process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH || \"\";\n const base = raw.trim().replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n if (!base) return path;\n const normalizedBase = `/${base}`;\n if (path === normalizedBase || path.startsWith(`${normalizedBase}/`)) {\n return path;\n }\n return `${normalizedBase}${path}`;\n}\n\nfunction safeNotificationUrl(value: string, appUrl: string): string | null {\n const trimmed = value.trim();\n if (!trimmed) return null;\n\n try {\n const base = new URL(appUrl);\n if (trimmed.startsWith(\"/\")) {\n const path = appPath(trimmed);\n const basePath = base.pathname.replace(/\\/+$/, \"\");\n const alreadyIncludesBase =\n basePath && basePath !== \"/\" && path.startsWith(`${basePath}/`);\n const joined = alreadyIncludesBase\n ? `${base.origin}${path}`\n : `${appUrl.replace(/\\/+$/, \"\")}${path}`;\n return new URL(joined).toString();\n }\n\n const url = new URL(trimmed);\n if (![\"http:\", \"https:\"].includes(url.protocol)) return null;\n if (url.origin !== base.origin) return null;\n return url.toString();\n } catch {\n return null;\n }\n}\n\nexport function resolveShareNotificationUrl(\n explicitUrl: string | undefined,\n fallbackPath: string | undefined,\n appUrl = getAppProductionUrl(),\n): string {\n for (const candidate of [explicitUrl, fallbackPath]) {\n if (!candidate) continue;\n const url = safeNotificationUrl(candidate, appUrl);\n if (url) return url;\n }\n return appUrl;\n}\n\nfunction nanoid(size = 12): string {\n const chars =\n \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n let id = \"\";\n const bytes = crypto.getRandomValues(new Uint8Array(size));\n for (const byte of bytes) id += chars[byte % chars.length];\n return id;\n}\n\nfunction normalizePrincipalId(\n principalType: \"user\" | \"org\",\n principalId: string,\n): string {\n return principalType === \"user\"\n ? principalId.trim().toLowerCase()\n : principalId;\n}\n\nfunction isEmailPrincipalId(value: string): boolean {\n return /^[^\\s@]+@[^\\s@]+$/.test(value.trim());\n}\n\nfunction principalIdMatches(\n sharesTable: any,\n principalType: \"user\" | \"org\",\n principalId: string,\n): SQL {\n return principalType === \"user\"\n ? sql`lower(${sharesTable.principalId}) = ${principalId}`\n : eq(sharesTable.principalId, principalId);\n}\n\n/**\n * Returns true if the given email is either an active member of `orgId` or\n * has a pending invitation to `orgId`. Used by resources whose registration\n * sets `requireOrgMemberForUserShares` (currently extensions) to refuse\n * cross-org user shares.\n *\n * Both `org_members` and `org_invitations` store email case-insensitively\n * via `LOWER()` in the rest of the framework, so we follow the same\n * convention here.\n */\nasync function isOrgMemberOrInvited(\n orgId: string,\n email: string,\n): Promise<boolean> {\n const lower = email.trim().toLowerCase();\n if (!lower || !orgId) return false;\n const client = getDbExec();\n const member = await client.execute({\n sql: `SELECT 1 FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,\n args: [orgId, lower],\n });\n if (member.rows.length > 0) return true;\n const invited = await client.execute({\n sql: `SELECT 1 FROM org_invitations WHERE org_id = ? AND LOWER(email) = ? AND status = 'pending' LIMIT 1`,\n args: [orgId, lower],\n });\n return invited.rows.length > 0;\n}\n\nexport default defineAction({\n description:\n \"Grant a user or org access to a shareable resource. Owner or admin role required.\",\n // (audit H5) Sharing-grant operations are admin-tier and let a caller\n // expand who can read/write a resource. Refuse from the tools iframe\n // bridge so a malicious shared tool can't silently re-share its\n // viewer's resources to an attacker-controlled email.\n toolCallable: false,\n schema: z.object({\n resourceType: z\n .string()\n .describe(\"Registered resource type, e.g. 'document', 'form'.\"),\n resourceId: z.string().describe(\"Id of the resource to share.\"),\n principalType: z\n .enum([\"user\", \"org\"])\n .describe(\"'user' for an individual, 'org' for a whole organization.\"),\n principalId: z\n .string()\n .describe(\"Email (user) or org id (org) of the principal.\"),\n role: z\n .enum([\"viewer\", \"editor\", \"admin\"])\n .default(\"viewer\")\n .describe(\"Role to grant.\"),\n notify: z\n .boolean()\n .default(true)\n .describe(\n \"Whether to email the user about a new individual share. Defaults to true.\",\n ),\n resourceUrl: z\n .string()\n .optional()\n .describe(\n \"Optional app-relative or same-origin URL recipients should open. External origins are ignored.\",\n ),\n }),\n run: async (args) => {\n const reg = requireShareableResource(args.resourceType);\n const access = await assertAccess(\n args.resourceType,\n args.resourceId,\n \"admin\",\n );\n const actor = getRequestUserEmail();\n if (!actor) throw new ForbiddenError(\"Not signed in\");\n const principalId = normalizePrincipalId(\n args.principalType,\n args.principalId,\n );\n if (args.principalType === \"user\" && !isEmailPrincipalId(principalId)) {\n throw new Error(\n \"User shares must use an email address, not an internal user id.\",\n );\n }\n const beforeExtensionTargets = await getExtensionShareChangeTargets(\n args.resourceType,\n args.resourceId,\n );\n\n if (reg.requireOrgMemberForUserShares) {\n const resourceOrgId = access.resource?.orgId as string | undefined | null;\n if (!resourceOrgId) {\n throw new ForbiddenError(\n `${reg.displayName} can only be shared from within an organization. Create or join an organization first.`,\n );\n }\n if (args.principalType === \"user\") {\n const ok = await isOrgMemberOrInvited(resourceOrgId, principalId);\n if (!ok) {\n throw new ForbiddenError(\n `${principalId} is not in your organization. Invite them to the organization first, then share.`,\n );\n }\n } else if (args.principalType === \"org\") {\n // Cross-org org shares would let an outside org's members run\n // extension code in the viewer's auth context — the same threat\n // model that blocks public + cross-org user shares. Pin org-\n // principal shares to the resource's own org.\n if (principalId !== resourceOrgId) {\n throw new ForbiddenError(\n `${reg.displayName} can only be shared with its own organization, not a different one.`,\n );\n }\n }\n }\n\n const db = reg.getDb() as any;\n const [existing] = await db\n .select()\n .from(reg.sharesTable)\n .where(\n and(\n eq(reg.sharesTable.resourceId, args.resourceId),\n eq(reg.sharesTable.principalType, args.principalType),\n principalIdMatches(reg.sharesTable, args.principalType, principalId),\n ),\n );\n\n if (existing) {\n await db\n .update(reg.sharesTable)\n .set({ role: args.role })\n .where(eq(reg.sharesTable.id, existing.id));\n invalidateCollabAccessCache(args.resourceType, args.resourceId);\n await notifyExtensionShareChanged(\n args.resourceType,\n args.resourceId,\n beforeExtensionTargets,\n );\n return { id: existing.id, updated: true };\n }\n\n const id = nanoid();\n await db.insert(reg.sharesTable).values({\n id,\n resourceId: args.resourceId,\n principalType: args.principalType,\n principalId,\n role: args.role,\n createdBy: actor,\n createdAt: new Date().toISOString(),\n });\n invalidateCollabAccessCache(args.resourceType, args.resourceId);\n await notifyExtensionShareChanged(\n args.resourceType,\n args.resourceId,\n beforeExtensionTargets,\n );\n\n if (\n args.notify !== false &&\n args.principalType === \"user\" &&\n (await isEmailConfigured()) &&\n !isSyntheticQaEmail(principalId)\n ) {\n try {\n const titleCol = reg.titleColumn ?? \"title\";\n const [resource] = await db\n .select()\n .from(reg.resourceTable)\n .where(eq(reg.resourceTable.id, args.resourceId));\n const resourceTitle: string =\n (resource?.[titleCol] as string | undefined) ?? args.resourceType;\n const appUrl = getAppProductionUrl();\n const resourcePath =\n resource && reg.getResourcePath\n ? reg.getResourcePath(resource)\n : undefined;\n const notificationUrl = resolveShareNotificationUrl(\n args.resourceUrl,\n resourcePath,\n appUrl,\n );\n const appName =\n process.env.APP_NAME || process.env.VITE_APP_NAME || \"Agent Native\";\n const subject = `${actor} shared \"${resourceTitle}\" with you on ${appName}`;\n const { html, text } = renderEmail({\n preheader: subject,\n heading: \"You've been given access\",\n paragraphs: [\n `${emailStrong(actor)} has shared the ${reg.displayName} ${emailStrong(resourceTitle)} with you as a ${emailStrong(args.role)}.`,\n `Use the button below to open it. If prompted, sign in with ${emailStrong(principalId)}.`,\n ],\n cta: { label: `Open ${reg.displayName}`, url: notificationUrl },\n footer: `You received this because ${actor} granted you ${args.role} access.`,\n });\n await sendEmail({ to: principalId, subject, html, text });\n } catch (err) {\n console.error(\n \"[share-resource] failed to send share notification:\",\n err,\n );\n }\n }\n\n return { id, updated: false };\n },\n});\n"]}
1
+ {"version":3,"file":"share-resource.js","sourceRoot":"","sources":["../../../src/sharing/actions/share-resource.ts"],"names":[],"mappings":"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","sourcesContent":["import { and, eq, sql, type SQL } from \"drizzle-orm\";\nimport { z } from \"zod\";\n\nimport { defineAction } from \"../../action.js\";\nimport { getDbExec } from \"../../db/client.js\";\nimport { getAppProductionUrl } from \"../../server/app-url.js\";\nimport { renderEmail, emailStrong } from \"../../server/email-template.js\";\nimport { sendEmail, isEmailConfigured } from \"../../server/email.js\";\nimport { invalidateCollabAccessCache } from \"../../server/poll.js\";\nimport { getRequestUserEmail } from \"../../server/request-context.js\";\nimport { assertAccess, ForbiddenError } from \"../access.js\";\nimport { requireShareableResource } from \"../registry.js\";\nimport {\n getExtensionShareChangeTargets,\n notifyExtensionShareChanged,\n} from \"./extension-change.js\";\n\nexport function isSyntheticQaEmail(email: string): boolean {\n const trimmed = email.trim().toLowerCase();\n const at = trimmed.lastIndexOf(\"@\");\n if (at <= 0) return false;\n const local = trimmed.slice(0, at);\n const domain = trimmed.slice(at + 1);\n return (\n local.includes(\"+qa\") &&\n (domain === \"example.test\" ||\n domain.endsWith(\".test\") ||\n domain === \"example.invalid\" ||\n domain.endsWith(\".invalid\"))\n );\n}\n\nfunction appPath(path: string): string {\n if (!path.startsWith(\"/\")) return path;\n const raw = process.env.VITE_APP_BASE_PATH || process.env.APP_BASE_PATH || \"\";\n const base = raw.trim().replace(/^\\/+/, \"\").replace(/\\/+$/, \"\");\n if (!base) return path;\n const normalizedBase = `/${base}`;\n if (path === normalizedBase || path.startsWith(`${normalizedBase}/`)) {\n return path;\n }\n return `${normalizedBase}${path}`;\n}\n\nfunction safeNotificationUrl(value: string, appUrl: string): string | null {\n const trimmed = value.trim();\n if (!trimmed) return null;\n\n try {\n const base = new URL(appUrl);\n if (trimmed.startsWith(\"/\")) {\n const path = appPath(trimmed);\n const basePath = base.pathname.replace(/\\/+$/, \"\");\n const alreadyIncludesBase =\n basePath && basePath !== \"/\" && path.startsWith(`${basePath}/`);\n const joined = alreadyIncludesBase\n ? `${base.origin}${path}`\n : `${appUrl.replace(/\\/+$/, \"\")}${path}`;\n return new URL(joined).toString();\n }\n\n const url = new URL(trimmed);\n if (![\"http:\", \"https:\"].includes(url.protocol)) return null;\n if (url.origin !== base.origin) return null;\n return url.toString();\n } catch {\n return null;\n }\n}\n\nexport function resolveShareNotificationUrl(\n explicitUrl: string | undefined,\n fallbackPath: string | undefined,\n appUrl = getAppProductionUrl(),\n): string {\n for (const candidate of [explicitUrl, fallbackPath]) {\n if (!candidate) continue;\n const url = safeNotificationUrl(candidate, appUrl);\n if (url) return url;\n }\n return appUrl;\n}\n\nfunction nanoid(size = 12): string {\n const chars =\n \"0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz\";\n let id = \"\";\n const bytes = crypto.getRandomValues(new Uint8Array(size));\n for (const byte of bytes) id += chars[byte % chars.length];\n return id;\n}\n\nfunction normalizePrincipalId(\n principalType: \"user\" | \"org\",\n principalId: string,\n): string {\n return principalType === \"user\"\n ? principalId.trim().toLowerCase()\n : principalId;\n}\n\nfunction isEmailPrincipalId(value: string): boolean {\n return /^[^\\s@]+@[^\\s@]+$/.test(value.trim());\n}\n\nfunction principalIdMatches(\n sharesTable: any,\n principalType: \"user\" | \"org\",\n principalId: string,\n): SQL {\n return principalType === \"user\"\n ? sql`lower(${sharesTable.principalId}) = ${principalId}`\n : eq(sharesTable.principalId, principalId);\n}\n\n/**\n * Returns true if the given email is either an active member of `orgId` or\n * has a pending invitation to `orgId`. Used by resources whose registration\n * sets `requireOrgMemberForUserShares` (currently extensions) to refuse\n * cross-org user shares.\n *\n * Both `org_members` and `org_invitations` store email case-insensitively\n * via `LOWER()` in the rest of the framework, so we follow the same\n * convention here.\n */\nasync function isOrgMemberOrInvited(\n orgId: string,\n email: string,\n): Promise<boolean> {\n const lower = email.trim().toLowerCase();\n if (!lower || !orgId) return false;\n const client = getDbExec();\n const member = await client.execute({\n sql: `SELECT 1 FROM org_members WHERE org_id = ? AND LOWER(email) = ? LIMIT 1`,\n args: [orgId, lower],\n });\n if (member.rows.length > 0) return true;\n const invited = await client.execute({\n sql: `SELECT 1 FROM org_invitations WHERE org_id = ? AND LOWER(email) = ? AND status = 'pending' LIMIT 1`,\n args: [orgId, lower],\n });\n return invited.rows.length > 0;\n}\n\nexport default defineAction({\n description:\n \"Grant a user or org access to a shareable resource. Owner or admin role required.\",\n // (audit H5) Sharing-grant operations are admin-tier and let a caller\n // expand who can read/write a resource. Refuse from the tools iframe\n // bridge so a malicious shared tool can't silently re-share its\n // viewer's resources to an attacker-controlled email.\n toolCallable: false,\n schema: z.object({\n resourceType: z\n .string()\n .describe(\"Registered resource type, e.g. 'document', 'form'.\"),\n resourceId: z.string().describe(\"Id of the resource to share.\"),\n principalType: z\n .enum([\"user\", \"org\"])\n .describe(\"'user' for an individual, 'org' for a whole organization.\"),\n principalId: z\n .string()\n .describe(\"Email (user) or org id (org) of the principal.\"),\n role: z\n .enum([\"viewer\", \"editor\", \"admin\"])\n .default(\"viewer\")\n .describe(\"Role to grant.\"),\n notify: z\n .boolean()\n .default(true)\n .describe(\n \"Whether to email the user about a new individual share. Defaults to true.\",\n ),\n resourceUrl: z\n .string()\n .optional()\n .describe(\n \"Optional app-relative or same-origin URL recipients should open. External origins are ignored.\",\n ),\n }),\n run: async (args) => {\n const reg = requireShareableResource(args.resourceType);\n const access = await assertAccess(\n args.resourceType,\n args.resourceId,\n \"admin\",\n );\n const actor = getRequestUserEmail();\n if (!actor) throw new ForbiddenError(\"Not signed in\");\n const principalId = normalizePrincipalId(\n args.principalType,\n args.principalId,\n );\n if (args.principalType === \"user\" && !isEmailPrincipalId(principalId)) {\n throw new Error(\n \"User shares must use an email address, not an internal user id.\",\n );\n }\n const beforeExtensionTargets = await getExtensionShareChangeTargets(\n args.resourceType,\n args.resourceId,\n );\n\n if (reg.requireOrgMemberForUserShares) {\n const resourceOrgId = access.resource?.orgId as string | undefined | null;\n if (!resourceOrgId) {\n throw new ForbiddenError(\n `${reg.displayName} can only be shared from within an organization. Create or join an organization first.`,\n );\n }\n if (args.principalType === \"user\") {\n const ok = await isOrgMemberOrInvited(resourceOrgId, principalId);\n if (!ok) {\n throw new ForbiddenError(\n `${principalId} is not in your organization. Invite them to the organization first, then share.`,\n );\n }\n } else if (args.principalType === \"org\") {\n // Cross-org org shares would let an outside org's members run\n // extension code in the viewer's auth context — the same threat\n // model that blocks public + cross-org user shares. Pin org-\n // principal shares to the resource's own org.\n if (principalId !== resourceOrgId) {\n throw new ForbiddenError(\n `${reg.displayName} can only be shared with its own organization, not a different one.`,\n );\n }\n }\n }\n\n const db = reg.getDb() as any;\n const [existing] = await db\n .select()\n .from(reg.sharesTable)\n .where(\n and(\n eq(reg.sharesTable.resourceId, args.resourceId),\n eq(reg.sharesTable.principalType, args.principalType),\n principalIdMatches(reg.sharesTable, args.principalType, principalId),\n ),\n );\n\n if (existing) {\n await db\n .update(reg.sharesTable)\n .set({ role: args.role })\n .where(eq(reg.sharesTable.id, existing.id));\n invalidateCollabAccessCache(args.resourceType, args.resourceId);\n await notifyExtensionShareChanged(\n args.resourceType,\n args.resourceId,\n beforeExtensionTargets,\n );\n return { id: existing.id, updated: true };\n }\n\n const id = nanoid();\n await db.insert(reg.sharesTable).values({\n id,\n resourceId: args.resourceId,\n principalType: args.principalType,\n principalId,\n role: args.role,\n createdBy: actor,\n createdAt: new Date().toISOString(),\n });\n invalidateCollabAccessCache(args.resourceType, args.resourceId);\n await notifyExtensionShareChanged(\n args.resourceType,\n args.resourceId,\n beforeExtensionTargets,\n );\n\n if (\n args.notify !== false &&\n args.principalType === \"user\" &&\n (await isEmailConfigured()) &&\n !isSyntheticQaEmail(principalId)\n ) {\n try {\n const titleCol = reg.titleColumn ?? \"title\";\n const [resource] = await db\n .select()\n .from(reg.resourceTable)\n .where(eq(reg.resourceTable.id, args.resourceId));\n const resourceTitle: string =\n (resource?.[titleCol] as string | undefined) ?? args.resourceType;\n const appUrl = getAppProductionUrl();\n const resourcePath =\n resource && reg.getResourcePath\n ? reg.getResourcePath(resource)\n : undefined;\n const notificationUrl = resolveShareNotificationUrl(\n args.resourceUrl,\n resourcePath,\n appUrl,\n );\n const appName =\n process.env.APP_NAME || process.env.VITE_APP_NAME || \"Agent Native\";\n const subject = `${actor} shared \"${resourceTitle}\" with you on ${appName}`;\n const { html, text } = renderEmail({\n brandName: appName,\n preheader: subject,\n heading: \"You've been given access\",\n paragraphs: [\n `${emailStrong(actor)} has shared the ${reg.displayName} ${emailStrong(resourceTitle)} with you as a ${emailStrong(args.role)}.`,\n `Use the button below to open it. If prompted, sign in with ${emailStrong(principalId)}.`,\n ],\n cta: { label: `Open ${reg.displayName}`, url: notificationUrl },\n footer: `You received this because ${actor} granted you ${args.role} access.`,\n });\n await sendEmail({ to: principalId, subject, html, text });\n } catch (err) {\n console.error(\n \"[share-resource] failed to send share notification:\",\n err,\n );\n }\n }\n\n return { id, updated: false };\n },\n});\n"]}
@@ -59,6 +59,12 @@ quick controls and deep links such as:
59
59
  - `/settings/usage`
60
60
  - `/settings/apps/:appId`
61
61
 
62
+ The shared Account section is the canonical profile surface at
63
+ `/settings#account`. It owns the editable display name and existing avatar
64
+ control through the authenticated `get-user-profile` and `update-user-profile`
65
+ actions. Shared workspace chrome such as `OrgSwitcher` should link to this
66
+ surface rather than creating an app-local profile page.
67
+
62
68
  When adding a new API key, OAuth grant, provider connection, model selector, app
63
69
  preference, notification preference, or usage/billing surface, register it as a
64
70
  settings tab or app settings panel first. Only add sidebar UI when it is needed
@@ -13,7 +13,7 @@ import {
13
13
  type ChatHistoryItem,
14
14
  } from "@agent-native/toolkit/chat-history";
15
15
  import {
16
- IconBrain,
16
+ IconHierarchy2,
17
17
  IconLayoutSidebarLeftCollapse,
18
18
  IconLayoutSidebarLeftExpand,
19
19
  IconMessageCircle,
@@ -39,7 +39,7 @@ const navItems = [
39
39
  view: "chat",
40
40
  },
41
41
  {
42
- icon: IconBrain,
42
+ icon: IconHierarchy2,
43
43
  labelKey: "settings.agentTitle",
44
44
  href: "/agent",
45
45
  view: "agent",
@@ -53,7 +53,7 @@
53
53
  --muted-foreground: 0 0% 60%;
54
54
  --accent: 0 0% 18%;
55
55
  --accent-foreground: 0 0% 90%;
56
- --destructive: 0 63% 51%;
56
+ --destructive: 0 84% 60%;
57
57
  --destructive-foreground: 0 0% 98%;
58
58
  --border: 0 0% 24%;
59
59
  --input: 0 0% 24%;
@@ -11,7 +11,7 @@ import {
11
11
  useCommandMenuShortcut,
12
12
  } from "@agent-native/core/client/navigation";
13
13
  import { getThemeInitScript } from "@agent-native/core/client/ui";
14
- import { IconBrain, IconSun, IconMoon } from "@tabler/icons-react";
14
+ import { IconHierarchy2, IconSun, IconMoon } from "@tabler/icons-react";
15
15
  import { useQueryClient } from "@tanstack/react-query";
16
16
  import { useTheme } from "next-themes";
17
17
  import { useCallback, useState } from "react";
@@ -143,7 +143,7 @@ function AppContent() {
143
143
  "access",
144
144
  ]}
145
145
  >
146
- <IconBrain size={16} />
146
+ <IconHierarchy2 size={16} />
147
147
  {t("settings.openAgentSettings")}
148
148
  </CommandMenu.Item>
149
149
  </CommandMenu.Group>
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-07-22
4
+ ---
5
+
6
+ Manage agent navigation now uses the connected-nodes icon.
@@ -59,6 +59,12 @@ quick controls and deep links such as:
59
59
  - `/settings/usage`
60
60
  - `/settings/apps/:appId`
61
61
 
62
+ The shared Account section is the canonical profile surface at
63
+ `/settings#account`. It owns the editable display name and existing avatar
64
+ control through the authenticated `get-user-profile` and `update-user-profile`
65
+ actions. Shared workspace chrome such as `OrgSwitcher` should link to this
66
+ surface rather than creating an app-local profile page.
67
+
62
68
  When adding a new API key, OAuth grant, provider connection, model selector, app
63
69
  preference, notification preference, or usage/billing surface, register it as a
64
70
  settings tab or app settings panel first. Only add sidebar UI when it is needed
@@ -52,7 +52,7 @@
52
52
  --muted-foreground: 0 0% 60%;
53
53
  --accent: 0 0% 18%;
54
54
  --accent-foreground: 0 0% 90%;
55
- --destructive: 0 63% 51%;
55
+ --destructive: 0 84% 60%;
56
56
  --destructive-foreground: 0 0% 98%;
57
57
  --border: 0 0% 24%;
58
58
  --input: 0 0% 24%;
@@ -359,24 +359,24 @@ const messages = {
359
359
  hideEveryone: "إخفاء من الجميع",
360
360
  localFileDescription:
361
361
  "This extension is backed by {{entryPath}}. Edit or remove it from the workspace.",
362
- deleteQuestion: "Delete {{name}}?",
362
+ deleteQuestion: "هل تريد أرشفة {{name}}؟",
363
363
  removeQuestion: "Remove {{name}}?",
364
364
  hideForYouDescription:
365
365
  "يؤدي هذا إلى إخفائه من قائمة الإضافات الخاصة بك دون حذفه لأي شخص آخر.",
366
366
  removeEverywhereDescription:
367
- "يؤدي هذا إلى إزالته في كل مكان تتم مشاركته فيه.",
367
+ "يؤدي هذا إلى أرشفته في كل مكان تتم مشاركته فيه.",
368
368
  cancel: "إلغاء",
369
369
  remove: "إزالة",
370
370
  removing: "جارٍ الإزالة...",
371
- delete: "حذف",
372
- deleting: "جار الحذف...",
371
+ delete: "أرشفة",
372
+ deleting: "جارٍ الأرشفة...",
373
373
  openFullView: "فتح عرض كامل",
374
374
  removeFromWidgetArea: "إزالة من منطقة القطعة هذه",
375
- deleteExtensionEllipsis: "حذف الامتداد...",
375
+ deleteExtensionEllipsis: "أرشفة الامتداد...",
376
376
  removeFromMyListEllipsis: "إزالة من قائمتي...",
377
377
  removeFromWidgetAreaForMe: "Remove from this widget area (for me)",
378
378
  deleteEverywhereConfirmation:
379
- "يؤدي هذا إلى إزالة الامتداد في كل مكان، لكل شخص تمت مشاركته معه.",
379
+ "يؤدي هذا إلى أرشفة الامتداد في كل مكان، لكل شخص تمت مشاركته معه.",
380
380
  addWidget: "إضافة القطعة",
381
381
  loading: "جار التحميل...",
382
382
  noWidgetsAvailable: "لا توجد أدوات متاحة لهذه الفتحة حتى الآن.",
@@ -352,24 +352,24 @@ const messages = {
352
352
  hideEveryone: "Versteck dich vor allen",
353
353
  localFileDescription:
354
354
  "This extension is backed by {{entryPath}}. Edit or remove it from the workspace.",
355
- deleteQuestion: "Delete {{name}}?",
355
+ deleteQuestion: "{{name}} archivieren?",
356
356
  removeQuestion: "Remove {{name}}?",
357
357
  hideForYouDescription:
358
358
  "Dadurch wird es aus Ihrer Erweiterungsliste ausgeblendet, ohne dass es für andere gelöscht wird.",
359
359
  removeEverywhereDescription:
360
- "Dadurch wird es überall dort entfernt, wo es geteilt wird.",
360
+ "Dadurch wird es überall dort archiviert, wo es geteilt wird.",
361
361
  cancel: "Abbrechen",
362
362
  remove: "Entfernen",
363
363
  removing: "Entfernen...",
364
- delete: "Löschen",
365
- deleting: "Wird gelöscht...",
364
+ delete: "Archivieren",
365
+ deleting: "Wird archiviert...",
366
366
  openFullView: "Vollansicht öffnen",
367
367
  removeFromWidgetArea: "Aus diesem Widget-Bereich entfernen",
368
- deleteExtensionEllipsis: "Erweiterung löschen...",
368
+ deleteExtensionEllipsis: "Erweiterung archivieren...",
369
369
  removeFromMyListEllipsis: "Von meiner Liste entfernen...",
370
370
  removeFromWidgetAreaForMe: "Remove from this widget area (for me)",
371
371
  deleteEverywhereConfirmation:
372
- "Dadurch wird die Erweiterung überall und für alle entfernt, mit denen sie geteilt wird.",
372
+ "Dadurch wird die Erweiterung überall und für alle archiviert, mit denen sie geteilt wird.",
373
373
  addWidget: "Widget hinzufügen",
374
374
  loading: "Wird geladen...",
375
375
  noWidgetsAvailable: "Für diesen Slot sind noch keine Widgets verfügbar.",
@@ -344,23 +344,23 @@ const messages = {
344
344
  hideEveryone: "Hide from everyone",
345
345
  localFileDescription:
346
346
  "This extension is backed by {{entryPath}}. Edit or remove it from the workspace.",
347
- deleteQuestion: "Delete {{name}}?",
347
+ deleteQuestion: "Archive {{name}}?",
348
348
  removeQuestion: "Remove {{name}}?",
349
349
  hideForYouDescription:
350
350
  "This hides it from your Extensions list without deleting it for anyone else.",
351
- removeEverywhereDescription: "This removes it everywhere it is shared.",
351
+ removeEverywhereDescription: "This archives it everywhere it is shared.",
352
352
  cancel: "Cancel",
353
353
  remove: "Remove",
354
354
  removing: "Removing...",
355
- delete: "Delete",
356
- deleting: "Deleting...",
355
+ delete: "Archive",
356
+ deleting: "Archiving...",
357
357
  openFullView: "Open full view",
358
358
  removeFromWidgetArea: "Remove from this widget area",
359
- deleteExtensionEllipsis: "Delete extension...",
359
+ deleteExtensionEllipsis: "Archive extension...",
360
360
  removeFromMyListEllipsis: "Remove from my list...",
361
361
  removeFromWidgetAreaForMe: "Remove from this widget area (for me)",
362
362
  deleteEverywhereConfirmation:
363
- "This removes the extension everywhere, for everyone it's shared with.",
363
+ "This archives the extension everywhere, for everyone it's shared with.",
364
364
  addWidget: "Add widget",
365
365
  loading: "Loading...",
366
366
  noWidgetsAvailable: "No widgets available for this slot yet.",
@@ -355,24 +355,24 @@ const messages = {
355
355
  hideEveryone: "esconderse de todos",
356
356
  localFileDescription:
357
357
  "This extension is backed by {{entryPath}}. Edit or remove it from the workspace.",
358
- deleteQuestion: "Delete {{name}}?",
358
+ deleteQuestion: "¿Archivar {{name}}?",
359
359
  removeQuestion: "Remove {{name}}?",
360
360
  hideForYouDescription:
361
361
  "Esto lo oculta de su lista de Extensiones sin eliminarlo para nadie más.",
362
362
  removeEverywhereDescription:
363
- "Esto lo elimina en todos los lugares donde se comparte.",
363
+ "Esto lo archiva en todos los lugares donde se comparte.",
364
364
  cancel: "Cancelar",
365
365
  remove: "Eliminar",
366
366
  removing: "Eliminando...",
367
- delete: "Eliminar",
368
- deleting: "Eliminando...",
367
+ delete: "Archivar",
368
+ deleting: "Archivando...",
369
369
  openFullView: "Abrir vista completa",
370
370
  removeFromWidgetArea: "Eliminar de esta área de widgets",
371
- deleteExtensionEllipsis: "Eliminar extensión...",
371
+ deleteExtensionEllipsis: "Archivar extensión...",
372
372
  removeFromMyListEllipsis: "Quitar de mi lista...",
373
373
  removeFromWidgetAreaForMe: "Remove from this widget area (for me)",
374
374
  deleteEverywhereConfirmation:
375
- "Esto elimina la extensión en todas partes, para todas las personas con las que se comparte.",
375
+ "Esto archiva la extensión en todas partes, para todas las personas con las que se comparte.",
376
376
  addWidget: "Agregar widget",
377
377
  loading: "Cargando...",
378
378
  noWidgetsAvailable:
@@ -355,23 +355,23 @@ const messages = {
355
355
  hideEveryone: "Cacher de tout le monde",
356
356
  localFileDescription:
357
357
  "This extension is backed by {{entryPath}}. Edit or remove it from the workspace.",
358
- deleteQuestion: "Delete {{name}}?",
358
+ deleteQuestion: "Archiver {{name}} ?",
359
359
  removeQuestion: "Remove {{name}}?",
360
360
  hideForYouDescription:
361
361
  "Cela le masque de votre liste d'extensions sans le supprimer pour quelqu'un d'autre.",
362
- removeEverywhereDescription: "Cela le supprime partout où il est partagé.",
362
+ removeEverywhereDescription: "Cela l’archive partout où il est partagé.",
363
363
  cancel: "Annuler",
364
364
  remove: "Retirer",
365
365
  removing: "Suppression...",
366
- delete: "Supprimer",
367
- deleting: "Suppression...",
366
+ delete: "Archiver",
367
+ deleting: "Archivage...",
368
368
  openFullView: "Ouvrir la vue complète",
369
369
  removeFromWidgetArea: "Supprimer de cette zone de widget",
370
- deleteExtensionEllipsis: "Supprimer l'extension...",
370
+ deleteExtensionEllipsis: "Archiver l'extension...",
371
371
  removeFromMyListEllipsis: "Supprimer de ma liste...",
372
372
  removeFromWidgetAreaForMe: "Remove from this widget area (for me)",
373
373
  deleteEverywhereConfirmation:
374
- "Cela supprime l'extension partout, pour toutes les personnes avec lesquelles elle est partagée.",
374
+ "Cela archive l'extension partout, pour toutes les personnes avec lesquelles elle est partagée.",
375
375
  addWidget: "Ajouter un widget",
376
376
  loading: "Chargement...",
377
377
  noWidgetsAvailable:
@@ -338,24 +338,24 @@ const messages = {
338
338
  hideEveryone: "सबसे छुपाओ",
339
339
  localFileDescription:
340
340
  "This extension is backed by {{entryPath}}. Edit or remove it from the workspace.",
341
- deleteQuestion: "Delete {{name}}?",
341
+ deleteQuestion: "{{name}} को संग्रहित करें?",
342
342
  removeQuestion: "Remove {{name}}?",
343
343
  hideForYouDescription:
344
344
  "यह इसे किसी अन्य के लिए हटाए बिना आपकी एक्सटेंशन सूची से छिपा देता है।",
345
345
  removeEverywhereDescription:
346
- "यह इसे हर जगह से हटा देता है जहां इसे साझा किया गया है।",
346
+ "यह इसे साझा किए गए हर स्थान पर संग्रहित कर देता है।",
347
347
  cancel: "रद्द करें",
348
348
  remove: "हटाएं",
349
349
  removing: "हटाया जा रहा है...",
350
- delete: "हटाएं",
351
- deleting: "हटाया जा रहा है...",
350
+ delete: "संग्रहित करें",
351
+ deleting: "संग्रहित किया जा रहा है...",
352
352
  openFullView: "पूरा दृश्य खोलें",
353
353
  removeFromWidgetArea: "इस विजेट क्षेत्र से हटाएँ",
354
- deleteExtensionEllipsis: "एक्सटेंशन हटाएं...",
354
+ deleteExtensionEllipsis: "एक्सटेंशन संग्रहित करें...",
355
355
  removeFromMyListEllipsis: "मेरी सूची से हटाएं...",
356
356
  removeFromWidgetAreaForMe: "Remove from this widget area (for me)",
357
357
  deleteEverywhereConfirmation:
358
- "यह एक्सटेंशन को हर जगह से हटा देता है, उन सभी के लिए जिनके साथ इसे साझा किया गया है।",
358
+ "यह एक्सटेंशन को साझा किए गए हर स्थान पर, सभी लोगों के लिए संग्रहित कर देता है।",
359
359
  addWidget: "विजेट जोड़ें",
360
360
  loading: "लोड हो रहा है...",
361
361
  noWidgetsAvailable: "इस स्लॉट के लिए अभी तक कोई विजेट उपलब्ध नहीं है।",
@@ -344,24 +344,24 @@ const messages = {
344
344
  hideEveryone: "みんなから隠れて",
345
345
  localFileDescription:
346
346
  "This extension is backed by {{entryPath}}. Edit or remove it from the workspace.",
347
- deleteQuestion: "Delete {{name}}?",
347
+ deleteQuestion: "{{name}}をアーカイブしますか?",
348
348
  removeQuestion: "Remove {{name}}?",
349
349
  hideForYouDescription:
350
350
  "これにより、他のユーザーが削除することなく、拡張機能リストから非表示になります。",
351
351
  removeEverywhereDescription:
352
- "これにより、共有されているすべての場所で削除されます。",
352
+ "これにより、共有されているすべての場所でアーカイブされます。",
353
353
  cancel: "キャンセル",
354
354
  remove: "削除",
355
355
  removing: "削除中...",
356
- delete: "削除",
357
- deleting: "削除中...",
356
+ delete: "アーカイブ",
357
+ deleting: "アーカイブ中...",
358
358
  openFullView: "フルビューを開く",
359
359
  removeFromWidgetArea: "このウィジェット領域から削除する",
360
- deleteExtensionEllipsis: "拡張機能を削除...",
360
+ deleteExtensionEllipsis: "拡張機能をアーカイブ...",
361
361
  removeFromMyListEllipsis: "リストから削除...",
362
362
  removeFromWidgetAreaForMe: "Remove from this widget area (for me)",
363
363
  deleteEverywhereConfirmation:
364
- "これにより、共有されているすべてのユーザーの拡張機能がどこからでも削除されます。",
364
+ "これにより、共有されているすべてのユーザーの拡張機能がどこからでもアーカイブされます。",
365
365
  addWidget: "ウィジェットを追加する",
366
366
  loading: "読み込み中...",
367
367
  noWidgetsAvailable:
@@ -337,24 +337,24 @@ const messages = {
337
337
  hideEveryone: "모든 사람에게서 숨기기",
338
338
  localFileDescription:
339
339
  "This extension is backed by {{entryPath}}. Edit or remove it from the workspace.",
340
- deleteQuestion: "Delete {{name}}?",
340
+ deleteQuestion: "{{name}}을(를) 보관할까요?",
341
341
  removeQuestion: "Remove {{name}}?",
342
342
  hideForYouDescription:
343
343
  "이렇게 하면 다른 사람을 위해 삭제하지 않고 확장 프로그램 목록에서 숨겨집니다.",
344
344
  removeEverywhereDescription:
345
- "이렇게 하면 공유된 모든 위치에서 해당 항목이 제거됩니다.",
345
+ "이렇게 하면 공유된 모든 위치에서 해당 항목이 보관됩니다.",
346
346
  cancel: "취소",
347
347
  remove: "제거",
348
348
  removing: "삭제 중...",
349
- delete: "삭제",
350
- deleting: "삭제 중...",
349
+ delete: "보관",
350
+ deleting: "보관 중...",
351
351
  openFullView: "전체 보기 열기",
352
352
  removeFromWidgetArea: "이 위젯 영역에서 제거",
353
- deleteExtensionEllipsis: "확장 프로그램 삭제...",
353
+ deleteExtensionEllipsis: "확장 프로그램 보관...",
354
354
  removeFromMyListEllipsis: "내 목록에서 제거...",
355
355
  removeFromWidgetAreaForMe: "Remove from this widget area (for me)",
356
356
  deleteEverywhereConfirmation:
357
- "이렇게 하면 확장 프로그램을 공유하는 모든 사람의 모든 위치에서 확장 프로그램이 제거됩니다.",
357
+ "이렇게 하면 확장 프로그램을 공유하는 모든 사람의 모든 위치에서 확장 프로그램이 보관됩니다.",
358
358
  addWidget: "위젯 추가",
359
359
  loading: "로드 중...",
360
360
  noWidgetsAvailable: "이 슬롯에는 아직 사용할 수 있는 위젯이 없습니다.",