@agent-native/core 0.169.1 → 0.171.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (463) hide show
  1. package/agent-native.eject.json +4 -1
  2. package/bin/agent-native.js +23 -5
  3. package/corpus/README.md +1 -1
  4. package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +11 -4
  5. package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
  6. package/corpus/templates/analytics/app/i18n-data.ts +28 -0
  7. package/corpus/templates/analytics/app/pages/Agents.tsx +25 -1
  8. package/corpus/templates/analytics/app/routes/$.tsx +10 -0
  9. package/corpus/templates/analytics/app/routes.ts +3 -1
  10. package/corpus/templates/analytics/changelog/2026-08-22-fixed-the-agent-native-templates-first-party-dashboard-panel.md +6 -0
  11. package/corpus/templates/analytics/changelog/2026-08-22-org-admin-panels-no-longer-report-a-database-error-as-a-per.md +9 -0
  12. package/corpus/templates/analytics/changelog/2026-08-22-slack-requests-report-invalid-inputs.md +6 -0
  13. package/corpus/templates/analytics/changelog/2026-08-22-unknown-analytics-links-now-return-a-real-not-found-status.md +6 -0
  14. package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +2 -1
  15. package/corpus/templates/analytics/docs/schemas/tracked-events.md +2 -0
  16. package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +29 -7
  17. package/corpus/templates/analytics/server/handlers/slack.ts +59 -5
  18. package/corpus/templates/analytics/server/lib/db-admin-connections.ts +11 -6
  19. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +132 -2
  20. package/corpus/templates/analytics/server/lib/slack.ts +4 -2
  21. package/corpus/templates/analytics/vitest.config.ts +6 -1
  22. package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +11 -4
  23. package/corpus/templates/assets/app/routes/library.tsx +5 -3
  24. package/corpus/templates/assets/app/routes.ts +3 -1
  25. package/corpus/templates/assets/changelog/2026-08-22-library-page-loads-again-instead-of-crashing-on-open.md +6 -0
  26. package/corpus/templates/assets/vitest.config.ts +6 -1
  27. package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +11 -4
  28. package/corpus/templates/brain/app/components/ui/pointer-lock.ts +1 -0
  29. package/corpus/templates/brain/app/routes/sources.tsx +8 -3
  30. package/corpus/templates/brain/changelog/2026-08-22-tuning-or-adding-a-source-no-longer-leaves-the-sources-page.md +6 -0
  31. package/corpus/templates/brain/vitest.config.ts +1 -0
  32. package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +11 -4
  33. package/corpus/templates/calendar/actions/list-booking-links.ts +83 -4
  34. package/corpus/templates/calendar/actions/update-booking-link.ts +4 -3
  35. package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +11 -3
  36. package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +1 -1
  37. package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +103 -44
  38. package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +28 -24
  39. package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +9 -2
  40. package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +47 -58
  41. package/corpus/templates/calendar/app/hooks/use-booking-links.ts +5 -1
  42. package/corpus/templates/calendar/app/lib/event-layout.ts +52 -26
  43. package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +126 -98
  44. package/corpus/templates/calendar/app/pages/CalendarView.tsx +1 -0
  45. package/corpus/templates/calendar/app/pages/Settings.tsx +88 -81
  46. package/corpus/templates/calendar/app/routes.ts +3 -1
  47. package/corpus/templates/calendar/changelog/2026-08-22-booking-link-visibility-respects-shared-access.md +6 -0
  48. package/corpus/templates/calendar/changelog/2026-08-22-clicking-an-out-of-office-event-s-marker-no-longer-creates-a.md +6 -0
  49. package/corpus/templates/calendar/changelog/2026-08-22-event-detail-views-now-show-your-calendar-timezone.md +6 -0
  50. package/corpus/templates/calendar/changelog/2026-08-22-google-calendar-can-now-connect-with-the-shared-google-oauth.md +6 -0
  51. package/corpus/templates/calendar/changelog/2026-08-22-overlapping-events-now-show-each-others-boundaries.md +6 -0
  52. package/corpus/templates/calendar/changelog/2026-08-22-public-booking-pages-now-offer-time-slots-from-the-host-s-we.md +6 -0
  53. package/corpus/templates/calendar/server/handlers/bookings.ts +5 -7
  54. package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -2
  55. package/corpus/templates/calendar/server/lib/google-calendar.ts +139 -14
  56. package/corpus/templates/calendar/server/onboarding.ts +85 -0
  57. package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
  58. package/corpus/templates/calendar/shared/api.ts +9 -1
  59. package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
  60. package/corpus/templates/chat/app/routes.ts +3 -1
  61. package/corpus/templates/chat/vitest.config.ts +8 -0
  62. package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +11 -4
  63. package/corpus/templates/clips/actions/get-meeting.ts +9 -4
  64. package/corpus/templates/clips/actions/list-recordings.ts +16 -3
  65. package/corpus/templates/clips/actions/search-meetings.ts +1 -0
  66. package/corpus/templates/clips/actions/view-screen.ts +8 -1
  67. package/corpus/templates/clips/app/components/meetings/meeting-history-row.tsx +32 -1
  68. package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +1 -1
  69. package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +29 -1
  70. package/corpus/templates/clips/app/i18n/en-US.ts +5 -0
  71. package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +33 -4
  72. package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -0
  73. package/corpus/templates/clips/app/routes/access-request.approve.tsx +16 -9
  74. package/corpus/templates/clips/app/routes/embed.$shareId.tsx +17 -7
  75. package/corpus/templates/clips/app/routes/share.$shareId.tsx +17 -7
  76. package/corpus/templates/clips/changelog/2026-08-21-camera-bubble-black-circle.md +6 -0
  77. package/corpus/templates/clips/changelog/2026-08-22-screen-recordings-respect-the-microphone-toggle.md +6 -0
  78. package/corpus/templates/clips/changelog/2026-08-22-the-recordings-library-no-longer-fails-right-after-server-start.md +6 -0
  79. package/corpus/templates/clips/changelog/2026-08-23-the-agent-can-see-your-library-right-after-server-start.md +6 -0
  80. package/corpus/templates/clips/chrome-extension/src/background.ts +1 -2
  81. package/corpus/templates/clips/chrome-extension/src/native-transcription.ts +39 -7
  82. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +21 -25
  83. package/corpus/templates/clips/desktop/src/app.tsx +53 -24
  84. package/corpus/templates/clips/desktop/src/lib/bubble-playback.ts +68 -0
  85. package/corpus/templates/clips/desktop/src/lib/recorder.ts +3 -0
  86. package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +72 -14
  87. package/corpus/templates/clips/desktop/src/lib/updater.ts +8 -3
  88. package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +162 -20
  89. package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +43 -6
  90. package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +150 -13
  91. package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +12 -0
  92. package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +28 -3
  93. package/corpus/templates/clips/server/lib/emails.ts +17 -33
  94. package/corpus/templates/content/.agents/skills/secrets/SKILL.md +11 -4
  95. package/corpus/templates/content/app/components/editor/VisualEditor.tsx +140 -15
  96. package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +12 -14
  97. package/corpus/templates/content/app/components/ui/pointer-lock.ts +1 -0
  98. package/corpus/templates/content/app/hooks/use-content-database.ts +18 -2
  99. package/corpus/templates/content/app/hooks/use-local-storage.ts +38 -22
  100. package/corpus/templates/content/app/i18n-data.ts +25 -0
  101. package/corpus/templates/content/app/routes.ts +3 -1
  102. package/corpus/templates/content/changelog/2026-08-22-code-blocks-inserted-from-the-slash-menu-no-longer-get-stuck.md +6 -0
  103. package/corpus/templates/content/changelog/2026-08-22-the-sidebar-page-tree-no-longer-flashes-empty-after-creating-a-page.md +6 -0
  104. package/corpus/templates/content/docs/solutions/2026-08-21-content-mermaid-rendering-shape.md +233 -0
  105. package/corpus/templates/content/server/plugins/agent-chat.ts +9 -2
  106. package/corpus/templates/content/vitest.config.ts +7 -1
  107. package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +11 -4
  108. package/corpus/templates/design/.agents/skills/secrets/SKILL.md +11 -4
  109. package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1383 -88
  110. package/corpus/templates/design/actions/generate-design.ts +98 -47
  111. package/corpus/templates/design/actions/get-design-system.ts +6 -1
  112. package/corpus/templates/design/actions/refresh-design-system-with-builder.ts +146 -0
  113. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +70 -26
  114. package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +284 -136
  115. package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2057 -141
  116. package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +6 -2
  117. package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +4 -4
  118. package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +10 -1
  119. package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +19 -2
  120. package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -2
  121. package/corpus/templates/design/app/components/layout/Header.tsx +15 -0
  122. package/corpus/templates/design/app/components/layout/Layout.tsx +7 -2
  123. package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +11 -4
  124. package/corpus/templates/design/app/i18n-data.ts +1 -0
  125. package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +12 -0
  126. package/corpus/templates/design/app/lib/design-system-data.ts +68 -0
  127. package/corpus/templates/design/app/pages/DesignEditor.tsx +77 -35
  128. package/corpus/templates/design/app/pages/DesignSystems.tsx +136 -42
  129. package/corpus/templates/design/app/pages/Index.tsx +2 -0
  130. package/corpus/templates/design/app/pages/Templates.tsx +0 -10
  131. package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +10 -0
  132. package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +50 -0
  133. package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +1 -1
  134. package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +10 -8
  135. package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +6 -6
  136. package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +1 -1
  137. package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +18 -7
  138. package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +4 -90
  139. package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +2 -1
  140. package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +4 -3
  141. package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +12 -2
  142. package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +9 -2
  143. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +1 -1
  144. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +1 -1
  145. package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +1 -1
  146. package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +1 -1
  147. package/corpus/templates/design/app/pages/design-editor/editor-state.ts +11 -1
  148. package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +11 -19
  149. package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +6 -6
  150. package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +1 -1
  151. package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +12 -15
  152. package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -59
  153. package/corpus/templates/design/app/pages/design-editor/tool-state.ts +8 -0
  154. package/corpus/templates/design/app/pages/design-editor/types.ts +3 -3
  155. package/corpus/templates/design/app/routes.ts +3 -1
  156. package/corpus/templates/design/changelog/2026-08-20-canvas-edits-patch-the-frame-instead-of-rebuilding-it.md +6 -0
  157. package/corpus/templates/design/changelog/2026-08-21-builder-design-system-imports-reconcile-after-indexing.md +6 -0
  158. package/corpus/templates/design/changelog/2026-08-21-draw-mode-needs-shift-y.md +6 -0
  159. package/corpus/templates/design/changelog/2026-08-21-eyedropper-shortcut.md +6 -0
  160. package/corpus/templates/design/changelog/2026-08-21-figma-style-alignment-guides-and-snapping.md +6 -0
  161. package/corpus/templates/design/changelog/2026-08-21-group-transforms-keep-the-selection.md +6 -0
  162. package/corpus/templates/design/changelog/2026-08-21-inspector-fields-keep-focus-on-enter.md +6 -0
  163. package/corpus/templates/design/changelog/2026-08-21-pen-alt-keeps-the-incoming-handle.md +6 -0
  164. package/corpus/templates/design/changelog/2026-08-21-pen-on-the-board-creates-one-vector.md +6 -0
  165. package/corpus/templates/design/changelog/2026-08-21-scale-a-multi-selection-from-the-group-handles.md +6 -0
  166. package/corpus/templates/design/changelog/2026-08-21-scale-resizes-text-with-the-object.md +6 -0
  167. package/corpus/templates/design/changelog/2026-08-21-selecting-another-object-keeps-the-scale-tool-armed.md +6 -0
  168. package/corpus/templates/design/changelog/2026-08-21-the-shape-tool-remembers-the-shape-you-picked.md +6 -0
  169. package/corpus/templates/design/changelog/2026-08-21-vectors-drawn-in-a-frame-land-where-you-drew-them.md +6 -0
  170. package/corpus/templates/design/changelog/2026-08-22-multi-screen-generation-no-longer-loses-a-saved-screens-layo.md +6 -0
  171. package/corpus/templates/design/changelog/2026-08-22-rename-and-search-fields-are-labeled-for-screen-readers.md +6 -0
  172. package/corpus/templates/design/package.json +2 -0
  173. package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +311 -0
  174. package/corpus/templates/design/shared/canvas-math.ts +612 -206
  175. package/corpus/templates/design/shared/pen-path.ts +37 -0
  176. package/corpus/templates/design/vitest.config.ts +7 -1
  177. package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +11 -4
  178. package/corpus/templates/dispatch/actions/list-workspace-connections.ts +34 -3
  179. package/corpus/templates/dispatch/app/routes/integrations.tsx +55 -21
  180. package/corpus/templates/dispatch/app/routes.ts +3 -1
  181. package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +11 -4
  182. package/corpus/templates/factory/.env.example +2 -7
  183. package/corpus/templates/factory/AGENTS.md +3 -0
  184. package/corpus/templates/factory/README.md +5 -3
  185. package/corpus/templates/factory/actions/create-factory.ts +16 -3
  186. package/corpus/templates/factory/actions/get-slack-feedback-context.ts +2 -0
  187. package/corpus/templates/factory/actions/get-triage-item.ts +2 -0
  188. package/corpus/templates/factory/actions/list-factory-audit.ts +116 -36
  189. package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
  190. package/corpus/templates/factory/actions/list-triage-items.ts +22 -19
  191. package/corpus/templates/factory/actions/run-factory-automation.ts +7 -3
  192. package/corpus/templates/factory/actions/save-factory-automation.ts +5 -2
  193. package/corpus/templates/factory/actions/save-triage-config.ts +2 -8
  194. package/corpus/templates/factory/actions/start-builder-for-item.ts +21 -0
  195. package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +229 -297
  196. package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +397 -247
  197. package/corpus/templates/factory/app/components/factory/FactorySourceSettingsGroup.tsx +33 -0
  198. package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +202 -162
  199. package/corpus/templates/factory/app/i18n/en-US.ts +32 -4
  200. package/corpus/templates/factory/app/routes/factory.tsx +84 -11
  201. package/corpus/templates/factory/app/routes.ts +3 -1
  202. package/corpus/templates/factory/changelog/2026-08-21-activity-runs-use-automation-display-names.md +6 -0
  203. package/corpus/templates/factory/changelog/2026-08-21-create-and-settings-source-cards.md +6 -0
  204. package/corpus/templates/factory/changelog/2026-08-21-keep-automations-visible-while-running.md +6 -0
  205. package/corpus/templates/factory/changelog/2026-08-21-nested-factory-automations-can-poll.md +6 -0
  206. package/corpus/templates/factory/changelog/2026-08-21-settings-sticky-unsaved-bar.md +6 -0
  207. package/corpus/templates/factory/server/connectors/credentials.ts +10 -5
  208. package/corpus/templates/factory/server/lib/factory-audit-report.ts +412 -0
  209. package/corpus/templates/factory/server/lib/factory-config-reconcile.ts +321 -0
  210. package/corpus/templates/factory/server/lib/factory-scope.ts +25 -2
  211. package/corpus/templates/factory/server/lib/require-factory-automation.ts +13 -4
  212. package/corpus/templates/factory/server/plugins/factory-migrations.ts +148 -4
  213. package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +4 -6
  214. package/corpus/templates/factory/vitest.config.ts +17 -2
  215. package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +11 -4
  216. package/corpus/templates/forms/app/routes/_app.settings.tsx +4 -1
  217. package/corpus/templates/forms/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
  218. package/corpus/templates/forms/server/lib/public-form-ssr.ts +11 -1
  219. package/corpus/templates/forms/vitest.config.ts +1 -0
  220. package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +11 -4
  221. package/corpus/templates/macros/app/routes.ts +3 -1
  222. package/corpus/templates/macros/vitest.config.ts +6 -1
  223. package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +11 -4
  224. package/corpus/templates/mail/actions/manage-gmail-filters.ts +10 -2
  225. package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -37
  226. package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +14 -2
  227. package/corpus/templates/mail/app/components/layout/AppLayout.tsx +66 -36
  228. package/corpus/templates/mail/app/hooks/use-google-auth.ts +2 -0
  229. package/corpus/templates/mail/app/lib/google-oauth-setup.ts +17 -0
  230. package/corpus/templates/mail/app/pages/InboxPage.tsx +69 -9
  231. package/corpus/templates/mail/app/routes/$.tsx +9 -0
  232. package/corpus/templates/mail/app/routes/$view.$threadId.tsx +23 -1
  233. package/corpus/templates/mail/app/routes/$view.tsx +47 -1
  234. package/corpus/templates/mail/app/routes.ts +3 -1
  235. package/corpus/templates/mail/changelog/2026-08-22-gmail-can-now-connect-with-the-shared-google-oauth.md +6 -0
  236. package/corpus/templates/mail/changelog/2026-08-22-gmail-filters-now-shows-the-real-error.md +6 -0
  237. package/corpus/templates/mail/changelog/2026-08-22-unknown-mail-urls-now-show-the-404-page.md +6 -0
  238. package/corpus/templates/mail/server/handlers/google-auth.ts +11 -2
  239. package/corpus/templates/mail/server/lib/google-auth.ts +92 -12
  240. package/corpus/templates/mail/server/onboarding.ts +66 -18
  241. package/corpus/templates/mail/vitest.config.ts +6 -1
  242. package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +11 -4
  243. package/corpus/templates/plan/app/routes/settings.tsx +4 -1
  244. package/corpus/templates/plan/app/routes.ts +3 -1
  245. package/corpus/templates/plan/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
  246. package/corpus/templates/plan/netlify.toml +1 -1
  247. package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +11 -4
  248. package/corpus/templates/slides/actions/patch-deck.ts +79 -10
  249. package/corpus/templates/slides/actions/update-slide.ts +11 -1
  250. package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +14 -4
  251. package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +10 -52
  252. package/corpus/templates/slides/app/components/editor/GoogleDocImportHint.tsx +8 -53
  253. package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +9 -75
  254. package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +8 -60
  255. package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +10 -2
  256. package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +19 -7
  257. package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +49 -36
  258. package/corpus/templates/slides/app/routes/access-request.approve.tsx +16 -9
  259. package/corpus/templates/slides/app/routes.ts +3 -1
  260. package/corpus/templates/slides/changelog/2026-08-22-already-uploaded-chat-images-are-no-longer-dropped.md +6 -0
  261. package/corpus/templates/slides/changelog/2026-08-22-google-drive-and-slides-can-now-connect-with-the-shared-google-oauth.md +6 -0
  262. package/corpus/templates/slides/changelog/2026-08-22-multi-slide-repairs-now-report-slides-that-still-overflow.md +6 -0
  263. package/corpus/templates/slides/changelog/2026-08-22-update-slide-now-reports-which-batched-edit-was-skipped.md +6 -0
  264. package/corpus/templates/slides/server/handlers/google-docs-auth.ts +52 -1
  265. package/corpus/templates/slides/server/lib/chat-attachments.ts +29 -3
  266. package/corpus/templates/slides/server/lib/google-docs-oauth.ts +66 -16
  267. package/corpus/templates/slides/server/lib/slide-content-patch.ts +13 -2
  268. package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -2
  269. package/corpus/templates/slides/tsconfig.json +5 -0
  270. package/corpus/templates/slides/vitest.config.ts +6 -1
  271. package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +11 -4
  272. package/corpus/templates/tasks/app/routes/settings.tsx +4 -1
  273. package/corpus/templates/tasks/app/routes.ts +3 -1
  274. package/corpus/templates/tasks/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
  275. package/corpus/templates/tasks/vitest.config.ts +7 -1
  276. package/dist/action.d.ts +91 -0
  277. package/dist/action.js +50 -0
  278. package/dist/agent/abort-reasons.d.ts +38 -0
  279. package/dist/agent/abort-reasons.js +49 -0
  280. package/dist/agent/engine/ai-sdk-engine.js +8 -5
  281. package/dist/agent/engine/anthropic-engine.js +7 -5
  282. package/dist/agent/engine/first-event-timeout.d.ts +42 -17
  283. package/dist/agent/engine/first-event-timeout.js +67 -25
  284. package/dist/agent/production-agent.d.ts +23 -0
  285. package/dist/agent/production-agent.js +107 -102
  286. package/dist/agent/run-loop-with-resume.d.ts +2 -28
  287. package/dist/agent/run-loop-with-resume.js +25 -40
  288. package/dist/agent/run-manager.js +5 -4
  289. package/dist/agent/run-store.d.ts +55 -0
  290. package/dist/agent/run-store.js +124 -1
  291. package/dist/agent/tool-search.js +2 -1
  292. package/dist/agent/types.d.ts +9 -4
  293. package/dist/app-config/auth.d.ts +1 -0
  294. package/dist/app-config/auth.js +7 -0
  295. package/dist/app-config/configuration-error.d.ts +11 -0
  296. package/dist/app-config/configuration-error.js +14 -0
  297. package/dist/app-config/index.d.ts +1 -0
  298. package/dist/app-config/index.js +1 -0
  299. package/dist/app-config/integrations.d.ts +1 -0
  300. package/dist/app-config/integrations.js +10 -0
  301. package/dist/app-config/plugins.d.ts +34 -0
  302. package/dist/app-config/plugins.js +39 -0
  303. package/dist/app-config/run-lifecycle-invariants.d.ts +18 -9
  304. package/dist/app-config/run-lifecycle-invariants.js +18 -25
  305. package/dist/app-config/schema.d.ts +17 -0
  306. package/dist/app-config/schema.js +2 -0
  307. package/dist/chat-threads/schema-migrations.js +20 -0
  308. package/dist/chat-threads/store.js +21 -4
  309. package/dist/cli/native-dependencies.d.ts +4 -0
  310. package/dist/cli/native-dependencies.js +15 -0
  311. package/dist/client/EnvironmentBadge.d.ts +1 -1
  312. package/dist/client/EnvironmentBadge.js +40 -5
  313. package/dist/client/MultiTabAssistantChat.d.ts +1 -1
  314. package/dist/client/MultiTabAssistantChat.js +35 -14
  315. package/dist/client/agent-chat/index.d.ts +1 -1
  316. package/dist/client/agent-chat/index.js +1 -1
  317. package/dist/client/agent-page/use-jobs.d.ts +2 -1
  318. package/dist/client/agent-page/use-jobs.js +3 -5
  319. package/dist/client/agent-sidebar-state.js +3 -1
  320. package/dist/client/blocks/library/code-tabs.js +5 -0
  321. package/dist/client/blocks/library/code.js +1 -0
  322. package/dist/client/chat-first/apps-rail.js +2 -2
  323. package/dist/client/chat-model-groups.d.ts +12 -0
  324. package/dist/client/chat-model-groups.js +3 -2
  325. package/dist/client/integrations/GoogleProductLogo.d.ts +5 -0
  326. package/dist/client/integrations/GoogleProductLogo.js +45 -0
  327. package/dist/client/integrations/index.d.ts +2 -0
  328. package/dist/client/integrations/index.js +2 -0
  329. package/dist/client/integrations/workspace-provider-oauth.d.ts +8 -0
  330. package/dist/client/integrations/workspace-provider-oauth.js +13 -0
  331. package/dist/client/resources/mcp-integration-catalog.js +0 -29
  332. package/dist/client/settings/SettingsTabsPage.js +10 -6
  333. package/dist/client/sharing/ShareButton.js +11 -2
  334. package/dist/client/use-chat-models.d.ts +6 -0
  335. package/dist/client/use-chat-models.js +10 -0
  336. package/dist/client/use-db-sync.js +27 -9
  337. package/dist/collab/routes.d.ts +1 -1
  338. package/dist/collab/struct-routes.d.ts +1 -1
  339. package/dist/connections/catalog.d.ts +111 -9
  340. package/dist/connections/catalog.js +86 -30
  341. package/dist/db/create-get-db.js +18 -0
  342. package/dist/db/ddl-guard.d.ts +0 -4
  343. package/dist/db/ddl-guard.js +46 -0
  344. package/dist/deploy/build.d.ts +19 -0
  345. package/dist/deploy/build.js +190 -16
  346. package/dist/deploy/function-bundle.d.ts +45 -0
  347. package/dist/deploy/function-bundle.js +337 -0
  348. package/dist/deploy/workspace-deploy.js +19 -1
  349. package/dist/eject/workspace-connections.js +4 -0
  350. package/dist/email-catalog/registry.d.ts +4 -0
  351. package/dist/email-catalog/registry.js +79 -14
  352. package/dist/extensions/content-patch.js +14 -2
  353. package/dist/integrations/plugin.d.ts +10 -0
  354. package/dist/integrations/plugin.js +40 -6
  355. package/dist/jobs/actions/run-automation-now.d.ts +2 -1
  356. package/dist/jobs/actions/run-automation-now.js +11 -5
  357. package/dist/jobs/frontmatter.js +53 -2
  358. package/dist/jobs/run-now.d.ts +11 -1
  359. package/dist/jobs/run-now.js +42 -7
  360. package/dist/jobs/scheduler.d.ts +1 -0
  361. package/dist/jobs/scheduler.js +2 -1
  362. package/dist/localization/core-messages/ar-SA.js +3 -0
  363. package/dist/localization/core-messages/de-DE.js +3 -0
  364. package/dist/localization/core-messages/en-US.d.ts +3 -0
  365. package/dist/localization/core-messages/en-US.js +3 -0
  366. package/dist/localization/core-messages/es-ES.js +3 -0
  367. package/dist/localization/core-messages/fr-FR.js +3 -0
  368. package/dist/localization/core-messages/hi-IN.js +3 -0
  369. package/dist/localization/core-messages/ja-JP.js +3 -0
  370. package/dist/localization/core-messages/ko-KR.js +3 -0
  371. package/dist/localization/core-messages/pt-BR.js +3 -0
  372. package/dist/localization/core-messages/zh-CN.js +3 -0
  373. package/dist/localization/core-messages/zh-TW.js +3 -0
  374. package/dist/localization/core-messages.d.ts +3 -0
  375. package/dist/localization/default-messages.d.ts +2 -5
  376. package/dist/localization/default-messages.js +2 -5
  377. package/dist/mcp/build-server.d.ts +9 -0
  378. package/dist/mcp/build-server.js +37 -8
  379. package/dist/mcp-client/oauth-client.d.ts +7 -0
  380. package/dist/mcp-client/oauth-client.js +12 -1
  381. package/dist/oauth-tokens/index.d.ts +1 -1
  382. package/dist/oauth-tokens/index.js +1 -1
  383. package/dist/oauth-tokens/lifecycle.d.ts +11 -0
  384. package/dist/oauth-tokens/lifecycle.js +17 -0
  385. package/dist/oauth-tokens/store.js +7 -0
  386. package/dist/observability/posthog-ai.d.ts +15 -1
  387. package/dist/observability/posthog-ai.js +24 -12
  388. package/dist/observability/traces.js +158 -28
  389. package/dist/onboarding/app-profile.js +2 -2
  390. package/dist/onboarding/plugin.js +10 -2
  391. package/dist/onboarding/types.d.ts +2 -0
  392. package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
  393. package/dist/provider-api/actions/provider-api.d.ts +2 -2
  394. package/dist/provider-api/index.js +2 -0
  395. package/dist/resources/handlers.d.ts +2 -2
  396. package/dist/resources/store.js +13 -4
  397. package/dist/scripts/agent-engines/list-agent-engines.js +29 -3
  398. package/dist/secrets/register-framework-secrets.js +1 -1
  399. package/dist/server/action-discovery.js +5 -0
  400. package/dist/server/agent-chat/action-filters-a2a.d.ts +27 -3
  401. package/dist/server/agent-chat/action-filters-a2a.js +48 -9
  402. package/dist/server/agent-chat-plugin.js +36 -9
  403. package/dist/server/agent-discovery.js +43 -20
  404. package/dist/server/auth.js +18 -16
  405. package/dist/server/beta-opt-out-html.js +45 -4
  406. package/dist/server/better-auth-instance.d.ts +2 -1
  407. package/dist/server/better-auth-instance.js +37 -7
  408. package/dist/server/builder-design-systems.d.ts +4 -1
  409. package/dist/server/builder-design-systems.js +87 -6
  410. package/dist/server/builder-oauth.d.ts +1 -7
  411. package/dist/server/builder-oauth.js +80 -163
  412. package/dist/server/core-routes-plugin.d.ts +29 -0
  413. package/dist/server/core-routes-plugin.js +100 -20
  414. package/dist/server/default-plugins.d.ts +18 -0
  415. package/dist/server/default-plugins.js +22 -0
  416. package/dist/server/edge.d.ts +1 -0
  417. package/dist/server/edge.js +1 -0
  418. package/dist/server/framework-request-handler.js +31 -3
  419. package/dist/server/google-oauth.d.ts +3 -0
  420. package/dist/server/google-oauth.js +3 -0
  421. package/dist/server/index.d.ts +4 -4
  422. package/dist/server/index.js +3 -3
  423. package/dist/server/onboarding-html.js +37 -3
  424. package/dist/server/realtime-token.d.ts +1 -1
  425. package/dist/server/workspace-provider-oauth.d.ts +6 -1
  426. package/dist/server/workspace-provider-oauth.js +72 -20
  427. package/dist/shared/agent-sidebar-url.d.ts +1 -0
  428. package/dist/shared/agent-sidebar-url.js +1 -0
  429. package/dist/shared/environment-lanes.d.ts +2 -0
  430. package/dist/shared/environment-lanes.js +3 -1
  431. package/dist/shared/index.d.ts +3 -1
  432. package/dist/shared/index.js +3 -1
  433. package/dist/shared/surface-visibility.d.ts +31 -0
  434. package/dist/shared/surface-visibility.js +57 -0
  435. package/dist/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
  436. package/dist/templates/chat/app/routes.ts +3 -1
  437. package/dist/templates/chat/vitest.config.ts +8 -0
  438. package/dist/templates/default/.agents/skills/secrets/SKILL.md +11 -4
  439. package/dist/templates/default/app/routes.ts +3 -1
  440. package/dist/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
  441. package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
  442. package/dist/tracking/registry.d.ts +11 -0
  443. package/dist/tracking/registry.js +5 -2
  444. package/dist/triggers/actions.js +10 -3
  445. package/dist/usage/store.js +23 -3
  446. package/dist/user-profile/actions/get-auth-methods.js +18 -5
  447. package/dist/vite/client.js +33 -7
  448. package/docs/content/actions-access-control.mdx +43 -4
  449. package/docs/content/actions-defining.mdx +16 -0
  450. package/docs/content/authentication.mdx +40 -21
  451. package/docs/content/environment-variables.mdx +1 -0
  452. package/docs/content/template-clips-capture-everywhere.mdx +29 -30
  453. package/docs/content/template-clips-developers.mdx +4 -4
  454. package/docs/content/template-clips.mdx +3 -1
  455. package/package.json +2 -2
  456. package/src/eject/workspace-connections.ts +4 -0
  457. package/src/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
  458. package/src/templates/chat/app/routes.ts +3 -1
  459. package/src/templates/chat/vitest.config.ts +8 -0
  460. package/src/templates/default/.agents/skills/secrets/SKILL.md +11 -4
  461. package/src/templates/default/app/routes.ts +3 -1
  462. package/src/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
  463. package/src/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
@@ -50,14 +50,78 @@ function ContactPanel({
50
50
  emails.find((e) => e.id === emailId || (e.threadId || e.id) === emailId),
51
51
  [emails, emailId],
52
52
  );
53
- // Always use inbox emails for "recent from contact" — shares React Query cache,
54
- // no extra fetch. The `emails` prop may be a different view (sent, starred, etc.)
55
- const { data: inboxEmails = [] } = useEmails("inbox");
56
-
57
53
  const displayEmail = contactEmail || email?.from.email;
58
54
  const displayName = contactEmail
59
55
  ? contactEmail
60
56
  : email?.from.name || email?.from.email;
57
+ const normalizedDisplayEmail = displayEmail?.trim().toLowerCase() ?? "";
58
+ // Use all mail so contact activity survives sent/archive/inbox navigation.
59
+ // Search at the provider boundary so the contact panel does not page through
60
+ // the entire mailbox. The bounded follow-up fetches cover sparse histories.
61
+ const {
62
+ data: allEmails = [],
63
+ isError: allEmailsError,
64
+ hasNextPage,
65
+ fetchNextPage,
66
+ isFetchingNextPage,
67
+ isFetchNextPageError,
68
+ } = useEmails("all", normalizedDisplayEmail || undefined, undefined, {
69
+ enabled: Boolean(normalizedDisplayEmail),
70
+ });
71
+ const contactPageFetchesRef = useRef(0);
72
+ const contactGenerationRef = useRef(0);
73
+
74
+ useEffect(() => {
75
+ contactPageFetchesRef.current = 0;
76
+ contactGenerationRef.current += 1;
77
+ }, [normalizedDisplayEmail]);
78
+
79
+ const recentFromContact = displayEmail
80
+ ? allEmails
81
+ .filter((e) => {
82
+ if (e.id === emailId) return false;
83
+ const participants = [
84
+ e.from,
85
+ ...e.to,
86
+ ...(e.cc ?? []),
87
+ ...(e.bcc ?? []),
88
+ ];
89
+ return participants.some(
90
+ (participant) =>
91
+ participant.email.trim().toLowerCase() === normalizedDisplayEmail,
92
+ );
93
+ })
94
+ .slice(0, 4)
95
+ .map((e) => ({ id: e.id, subject: e.subject }))
96
+ : [];
97
+
98
+ useEffect(() => {
99
+ const maxContactPages = 4;
100
+ if (
101
+ !normalizedDisplayEmail ||
102
+ recentFromContact.length >= 4 ||
103
+ !hasNextPage ||
104
+ isFetchingNextPage ||
105
+ isFetchNextPageError ||
106
+ contactPageFetchesRef.current >= maxContactPages
107
+ ) {
108
+ return;
109
+ }
110
+ const contactGeneration = contactGenerationRef.current;
111
+ contactPageFetchesRef.current += 1;
112
+ void fetchNextPage().catch(() => {
113
+ if (contactGenerationRef.current === contactGeneration) {
114
+ contactPageFetchesRef.current = maxContactPages;
115
+ }
116
+ });
117
+ }, [
118
+ fetchNextPage,
119
+ hasNextPage,
120
+ isFetchNextPageError,
121
+ isFetchingNextPage,
122
+ normalizedDisplayEmail,
123
+ recentFromContact.length,
124
+ ]);
61
125
 
62
126
  if (!displayEmail) {
63
127
  return (
@@ -69,16 +133,12 @@ function ContactPanel({
69
133
  );
70
134
  }
71
135
 
72
- const recentFromContact = inboxEmails
73
- .filter((e) => e.from.email === displayEmail && e.id !== emailId)
74
- .slice(0, 4)
75
- .map((e) => ({ id: e.id, subject: e.subject }));
76
-
77
136
  return (
78
137
  <IntegrationsSidebar
79
138
  email={displayEmail}
80
139
  displayName={displayName || displayEmail}
81
140
  recentEmails={recentFromContact}
141
+ recentEmailsError={allEmailsError}
82
142
  threadId={email?.threadId}
83
143
  focusedEmailId={email?.id ?? emailId}
84
144
  />
@@ -1,3 +1,5 @@
1
+ import { data } from "react-router";
2
+
1
3
  import messages from "@/i18n/en-US";
2
4
  import { NotFound } from "@/pages/NotFound";
3
5
 
@@ -5,6 +7,13 @@ export function meta() {
5
7
  return [{ title: messages.mail.routeTitles.notFound }];
6
8
  }
7
9
 
10
+ // The splat route matches every unmatched path, so React Router's "no routes
11
+ // matched" 404 branch never fires here and the response defaults to 200. Set
12
+ // the status explicitly so an unknown URL is served as the 404 it actually is.
13
+ export function loader() {
14
+ return data(null, { status: 404 });
15
+ }
16
+
8
17
  export default function CatchAllRoute() {
9
18
  return <NotFound />;
10
19
  }
@@ -1,11 +1,29 @@
1
+ import { data, useParams } from "react-router";
2
+
3
+ import messages from "@/i18n/en-US";
1
4
  import { InboxPage } from "@/pages/InboxPage";
5
+ import { NotFound } from "@/pages/NotFound";
6
+
7
+ import { isKnownMailView } from "./$view";
2
8
 
3
9
  const SEO_TITLE =
4
10
  "Mail - Open Source AI email client and Superhuman alternative";
5
11
  const SEO_DESCRIPTION =
6
12
  "Open Source AI email client for Gmail triage, drafting, organization, follow-ups, and inbox workflows built around shared actions.";
7
13
 
8
- export function meta() {
14
+ // Same rationale as $view.tsx's loader: this route also matches unknown
15
+ // `/:view/:threadId` paths and must not serve them as 200.
16
+ export function loader({ params }: { params: { view?: string } }) {
17
+ if (!isKnownMailView(params.view)) {
18
+ return data(null, { status: 404 });
19
+ }
20
+ return null;
21
+ }
22
+
23
+ export function meta({ params }: { params: { view?: string } }) {
24
+ if (!isKnownMailView(params.view)) {
25
+ return [{ title: messages.mail.routeTitles.notFound }];
26
+ }
9
27
  return [
10
28
  { title: SEO_TITLE },
11
29
  { name: "description", content: SEO_DESCRIPTION },
@@ -18,5 +36,9 @@ export function meta() {
18
36
  }
19
37
 
20
38
  export default function ThreadRoute() {
39
+ const { view } = useParams<{ view: string }>();
40
+ if (!isKnownMailView(view)) {
41
+ return <NotFound />;
42
+ }
21
43
  return <InboxPage />;
22
44
  }
@@ -1,11 +1,53 @@
1
+ import { data, useParams } from "react-router";
2
+
3
+ import messages from "@/i18n/en-US";
1
4
  import { InboxPage } from "@/pages/InboxPage";
5
+ import { NotFound } from "@/pages/NotFound";
2
6
 
3
7
  const SEO_TITLE =
4
8
  "Mail - Open Source AI email client and Superhuman alternative";
5
9
  const SEO_DESCRIPTION =
6
10
  "Open Source AI email client for Gmail triage, drafting, organization, follow-ups, and inbox workflows built around shared actions.";
7
11
 
8
- export function meta() {
12
+ // The only `view` values the app itself ever links to (sidebar hrefs,
13
+ // navigate.ts). This is a sibling of the $.tsx splat 404 route, and
14
+ // react-router always scores a `:view` dynamic segment above a `*` splat at
15
+ // the same depth — so an arbitrary single-segment URL matches here first.
16
+ // Rejecting anything outside this list is what lets a typo'd/unknown path
17
+ // still reach NotFound instead of silently rendering the inbox shell.
18
+ const KNOWN_MAIL_VIEWS = new Set([
19
+ "inbox",
20
+ "unread",
21
+ "starred",
22
+ "snoozed",
23
+ "scheduled",
24
+ "sent",
25
+ "drafts",
26
+ "archive",
27
+ "trash",
28
+ "all",
29
+ ]);
30
+
31
+ export function isKnownMailView(view: string | undefined): boolean {
32
+ return !view || KNOWN_MAIL_VIEWS.has(view);
33
+ }
34
+
35
+ // react-router always scores a `:view` dynamic segment above the `$.tsx`
36
+ // splat at the same depth, so an unmatched single-segment path matches here
37
+ // and would otherwise serve 200 (React Router only auto-404s when literally
38
+ // no route matches). Reject it explicitly so the response status matches the
39
+ // NotFound content the component renders below.
40
+ export function loader({ params }: { params: { view?: string } }) {
41
+ if (!isKnownMailView(params.view)) {
42
+ return data(null, { status: 404 });
43
+ }
44
+ return null;
45
+ }
46
+
47
+ export function meta({ params }: { params: { view?: string } }) {
48
+ if (!isKnownMailView(params.view)) {
49
+ return [{ title: messages.mail.routeTitles.notFound }];
50
+ }
9
51
  return [
10
52
  { title: SEO_TITLE },
11
53
  { name: "description", content: SEO_DESCRIPTION },
@@ -18,5 +60,9 @@ export function meta() {
18
60
  }
19
61
 
20
62
  export default function ViewRoute() {
63
+ const { view } = useParams<{ view: string }>();
64
+ if (!isKnownMailView(view)) {
65
+ return <NotFound />;
66
+ }
21
67
  return <InboxPage />;
22
68
  }
@@ -1,4 +1,6 @@
1
1
  import { type RouteConfig } from "@react-router/dev/routes";
2
2
  import { flatRoutes } from "@react-router/fs-routes";
3
3
 
4
- export default flatRoutes() satisfies RouteConfig;
4
+ export default flatRoutes({
5
+ ignoredRouteFiles: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
6
+ }) satisfies RouteConfig;
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: improved
3
+ date: 2026-08-22
4
+ ---
5
+
6
+ Gmail can now connect with the shared Google OAuth app in one click
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-22
4
+ ---
5
+
6
+ Gmail Filters in Settings now shows the actual reason for a failure, like needing to connect Google, instead of a generic server error.
@@ -0,0 +1,6 @@
1
+ ---
2
+ type: fixed
3
+ date: 2026-08-22
4
+ ---
5
+
6
+ Visiting an unrecognized mail URL now shows the 404 page instead of silently loading the inbox.
@@ -7,6 +7,7 @@ import {
7
7
  getSession,
8
8
  isElectron,
9
9
  getAppUrl,
10
+ hasWorkspaceProviderOAuthCredentials,
10
11
  resolveOAuthRedirectUri,
11
12
  encodeOAuthState,
12
13
  decodeOAuthState,
@@ -21,6 +22,7 @@ import {
21
22
  safeReturnPath,
22
23
  setDesktopExchange,
23
24
  setDesktopExchangeError,
25
+ runWithRequestContext,
24
26
  } from "@agent-native/core/server";
25
27
  import { getUserSetting, putUserSetting } from "@agent-native/core/settings";
26
28
  import {
@@ -490,7 +492,12 @@ export const handleGoogleAddAccountCallback = defineEventHandler(
490
492
  export const getGoogleStatus = defineEventHandler(async (event: H3Event) => {
491
493
  try {
492
494
  const session = await getSession(event);
493
- return await getAuthStatus(session?.email);
495
+ const status = await getAuthStatus(session?.email);
496
+ const configured = await runWithRequestContext(
497
+ { userEmail: session?.email, orgId: session?.orgId },
498
+ () => hasWorkspaceProviderOAuthCredentials("gmail"),
499
+ );
500
+ return { ...status, configured };
494
501
  } catch (error: any) {
495
502
  setResponseStatus(event, 500);
496
503
  return { error: error.message };
@@ -511,7 +518,9 @@ export const disconnectGoogle = defineEventHandler(async (event: H3Event) => {
511
518
  return { error: "email is required" };
512
519
  }
513
520
  const owned = await getAuthStatus(session.email);
514
- const isOwned = owned.accounts.some((a) => a.email === targetEmail);
521
+ const isOwned = owned.accounts.some(
522
+ (a) => a.email === targetEmail && !a.shared,
523
+ );
515
524
  if (!isOwned) {
516
525
  setResponseStatus(event, 403);
517
526
  return { error: "Cannot disconnect an account you don't own" };
@@ -6,14 +6,15 @@ import {
6
6
  setOAuthDisplayName,
7
7
  } from "@agent-native/core/oauth-tokens";
8
8
  import {
9
- isOAuthConnected,
10
9
  getOAuthAccounts,
11
10
  GOOGLE_PRIMARY_PROVIDER_CREDENTIAL_KEYS,
11
+ getCredentialContext,
12
12
  resolveGoogleProviderCredentialCandidatesWithReader,
13
13
  resolveSecret,
14
14
  runWithRequestContext,
15
15
  } from "@agent-native/core/server";
16
16
  import { getUserSetting, putUserSetting } from "@agent-native/core/settings";
17
+ import { resolveWorkspaceConnectionForApp } from "@agent-native/core/workspace-connections";
17
18
  import { decodeCommonHtmlEntities } from "@shared/markdown.js";
18
19
 
19
20
  import type { BulkMarkReadResult } from "./bulk-mark-read.js";
@@ -33,6 +34,7 @@ import {
33
34
  googleFetch,
34
35
  peopleGetProfile,
35
36
  } from "./google-api.js";
37
+ import { getMailProviderApiRuntime } from "./provider-api.js";
36
38
  import { resolveGoogleSenderIdentity } from "./sender-identity.js";
37
39
  import { invalidateThreadCache } from "./thread-cache.js";
38
40
 
@@ -48,6 +50,8 @@ const SCOPES = [
48
50
  "https://www.googleapis.com/auth/calendar.events",
49
51
  ];
50
52
 
53
+ const GMAIL_SCOPE_PREFIX = "https://www.googleapis.com/auth/gmail.";
54
+
51
55
  interface GoogleTokens {
52
56
  access_token: string;
53
57
  refresh_token?: string;
@@ -56,6 +60,41 @@ interface GoogleTokens {
56
60
  scope?: string;
57
61
  }
58
62
 
63
+ function hasGmailScope(tokens: Record<string, unknown>): boolean {
64
+ const scope = tokens.scope;
65
+ if (typeof scope !== "string" || !scope.trim()) return true;
66
+ return scope
67
+ .split(/[\s,]+/)
68
+ .some((value) => value.startsWith(GMAIL_SCOPE_PREFIX));
69
+ }
70
+
71
+ type ManagedGmailClient = {
72
+ email: string;
73
+ accessToken: string;
74
+ refreshToken: string;
75
+ };
76
+
77
+ async function resolveManagedGmailClient(): Promise<ManagedGmailClient | null> {
78
+ if (!getCredentialContext()) return null;
79
+ const connection = await resolveWorkspaceConnectionForApp({
80
+ appId: "mail",
81
+ provider: "gmail",
82
+ requireConnected: true,
83
+ });
84
+ if (!connection.available) return null;
85
+ const credential = await getMailProviderApiRuntime().resolveOAuthAccessToken({
86
+ provider: "gmail",
87
+ });
88
+ if (!credential.accountId) {
89
+ throw new Error("The connected Gmail workspace account has no account id.");
90
+ }
91
+ return {
92
+ email: credential.accountId,
93
+ accessToken: credential.accessToken,
94
+ refreshToken: "",
95
+ };
96
+ }
97
+
59
98
  export async function getOAuth2Credentials(owner?: string): Promise<{
60
99
  clientId: string;
61
100
  clientSecret: string;
@@ -288,8 +327,12 @@ export async function getClient(
288
327
  email: string | undefined,
289
328
  ): Promise<{ accessToken: string; email: string } | null> {
290
329
  if (!email) return null;
291
- const accounts = await listOAuthAccountsByOwner("google", email);
292
- if (accounts.length === 0) return null;
330
+ const accounts = (await listOAuthAccountsByOwner("google", email)).filter(
331
+ (account) => hasGmailScope(account.tokens),
332
+ );
333
+ if (accounts.length === 0) {
334
+ return resolveManagedGmailClient();
335
+ }
293
336
 
294
337
  const account = accounts.find((a) => a.accountId === email) ?? accounts[0];
295
338
 
@@ -318,7 +361,7 @@ export async function getClientForAccount(
318
361
  ): Promise<{ accessToken: string; email: string } | null> {
319
362
  const all = await listOAuthAccounts("google");
320
363
  const account = all.find((a) => a.accountId === accountId);
321
- if (!account) return null;
364
+ if (!account || !hasGmailScope(account.tokens)) return null;
322
365
  return getClientFromAccount({
323
366
  ...account,
324
367
  owner: account.owner ?? undefined,
@@ -335,6 +378,7 @@ export async function getClientFromAccount(account: {
335
378
  owner?: string;
336
379
  tokens: Record<string, unknown>;
337
380
  }): Promise<{ accessToken: string; email: string } | null> {
381
+ if (!hasGmailScope(account.tokens)) return null;
338
382
  const tokens = account.tokens as unknown as GoogleTokens;
339
383
  if (!tokens) return null;
340
384
 
@@ -389,7 +433,9 @@ export async function getClientsWithErrors(
389
433
  // refreshes are writes and an explicitly scoped inventory read must not
390
434
  // refresh unrelated accounts.
391
435
  const accounts = (await listOAuthAccountsByOwner("google", forEmail)).filter(
392
- (account) => !requested || requested.has(account.accountId.toLowerCase()),
436
+ (account) =>
437
+ hasGmailScope(account.tokens) &&
438
+ (!requested || requested.has(account.accountId.toLowerCase())),
393
439
  );
394
440
 
395
441
  const clients: Array<{
@@ -432,6 +478,23 @@ export async function getClientsWithErrors(
432
478
  }
433
479
  }
434
480
 
481
+ if (clients.length === 0) {
482
+ try {
483
+ const managed = await resolveManagedGmailClient();
484
+ if (
485
+ managed &&
486
+ (!requested || requested.has(managed.email.toLowerCase()))
487
+ ) {
488
+ clients.push(managed);
489
+ }
490
+ } catch (err: any) {
491
+ errors.push({
492
+ email: "workspace",
493
+ error: err?.message || "Workspace Gmail connection failed",
494
+ });
495
+ }
496
+ }
497
+
435
498
  return { clients, errors };
436
499
  }
437
500
 
@@ -440,15 +503,22 @@ export async function getClientsWithErrors(
440
503
  * checks only that specific account.
441
504
  */
442
505
  export async function isConnected(forEmail?: string): Promise<boolean> {
443
- return isOAuthConnected("google", forEmail ?? "");
506
+ if (!forEmail) return false;
507
+ const accounts = await listOAuthAccountsByOwner("google", forEmail);
508
+ if (accounts.some((account) => hasGmailScope(account.tokens))) return true;
509
+ return Boolean(await resolveManagedGmailClient());
444
510
  }
445
511
 
446
512
  export async function getConnectedAccounts(
447
513
  forEmail?: string,
448
514
  ): Promise<string[]> {
449
515
  if (!forEmail) return [];
450
- const accounts = await listOAuthAccountsByOwner("google", forEmail);
451
- return accounts.map((a) => a.accountId);
516
+ const accounts = (await listOAuthAccountsByOwner("google", forEmail)).filter(
517
+ (account) => hasGmailScope(account.tokens),
518
+ );
519
+ if (accounts.length > 0) return accounts.map((a) => a.accountId);
520
+ const managed = await resolveManagedGmailClient();
521
+ return managed ? [managed.email] : [];
452
522
  }
453
523
 
454
524
  export interface GoogleAuthStatus {
@@ -458,6 +528,7 @@ export interface GoogleAuthStatus {
458
528
  displayName?: string;
459
529
  expiresAt?: string;
460
530
  photoUrl?: string;
531
+ shared?: boolean;
461
532
  }>;
462
533
  }
463
534
 
@@ -468,10 +539,15 @@ export interface GoogleAuthStatus {
468
539
  export async function getAuthStatus(
469
540
  forEmail?: string,
470
541
  ): Promise<GoogleAuthStatus> {
471
- const oauthAccounts = await getOAuthAccounts("google", forEmail);
542
+ const oauthAccounts = (await getOAuthAccounts("google", forEmail)).filter(
543
+ (account) => hasGmailScope(account.tokens),
544
+ );
472
545
 
473
546
  if (oauthAccounts.length === 0) {
474
- return { connected: false, accounts: [] };
547
+ const managed = await resolveManagedGmailClient();
548
+ return managed
549
+ ? { connected: true, accounts: [{ email: managed.email, shared: true }] }
550
+ : { connected: false, accounts: [] };
475
551
  }
476
552
 
477
553
  const accounts: Array<{
@@ -1883,7 +1959,9 @@ interface GmailMessageReference {
1883
1959
  async function getDefaultOwnedAccountAccessToken(
1884
1960
  ownerEmail: string,
1885
1961
  ): Promise<string> {
1886
- const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
1962
+ const accounts = (
1963
+ await listOAuthAccountsByOwner("google", ownerEmail)
1964
+ ).filter((account) => hasGmailScope(account.tokens));
1887
1965
  const account =
1888
1966
  accounts.find(
1889
1967
  (candidate) =>
@@ -1901,7 +1979,9 @@ async function getOwnedAccountAccessToken(
1901
1979
  ownerEmail: string,
1902
1980
  accountEmail: string,
1903
1981
  ): Promise<string> {
1904
- const accounts = await listOAuthAccountsByOwner("google", ownerEmail);
1982
+ const accounts = (
1983
+ await listOAuthAccountsByOwner("google", ownerEmail)
1984
+ ).filter((account) => hasGmailScope(account.tokens));
1905
1985
  const account = accounts.find(
1906
1986
  (candidate) =>
1907
1987
  candidate.accountId.toLowerCase() === accountEmail.toLowerCase(),
@@ -3,17 +3,32 @@
3
3
  * framework-level onboarding registry. The step is picked up by the onboarding
4
4
  * panel in the agent sidebar on every request.
5
5
  *
6
- * Two completion methods are offered:
7
- * 1. Manual wizard — links back to the app root, where the existing
8
- * `GoogleConnectBanner` auto-detects missing credentials and guides the
9
- * user through the Google Cloud Console setup.
10
- * 2. Agent task — hands the task to the agent, which drives the user's
11
- * browser (or falls back to verbal step-by-step instructions) and saves
12
- * the resulting credentials as workspace-scoped DB keys.
6
+ * The connection uses the framework's managed Google OAuth client. The
7
+ * workspace chooses whether the resulting connection is personal or shared;
8
+ * no user-facing Google Cloud Console setup is required.
13
9
  */
14
10
 
11
+ import { listOAuthAccountsByOwner } from "@agent-native/core/oauth-tokens";
15
12
  import { registerOnboardingStep } from "@agent-native/core/onboarding";
16
- import { readDeployCredentialEnv } from "@agent-native/core/server";
13
+ import { hasWorkspaceProviderOAuthCredentials } from "@agent-native/core/server";
14
+ import { resolveWorkspaceConnectionForApp } from "@agent-native/core/workspace-connections";
15
+
16
+ const GMAIL_SCOPES = [
17
+ "https://www.googleapis.com/auth/gmail.readonly",
18
+ "https://www.googleapis.com/auth/gmail.send",
19
+ "https://www.googleapis.com/auth/gmail.modify",
20
+ "https://www.googleapis.com/auth/gmail.settings.basic",
21
+ ] as const;
22
+
23
+ function hasRequiredScope(tokens: unknown): boolean {
24
+ if (!tokens || typeof tokens !== "object" || Array.isArray(tokens)) {
25
+ return false;
26
+ }
27
+ const scope = (tokens as { scope?: unknown }).scope;
28
+ if (typeof scope !== "string" || !scope.trim()) return true;
29
+ const granted = new Set(scope.split(/[\s,]+/).filter(Boolean));
30
+ return GMAIL_SCOPES.some((requiredScope) => granted.has(requiredScope));
31
+ }
17
32
 
18
33
  registerOnboardingStep({
19
34
  id: "gmail",
@@ -21,14 +36,30 @@ registerOnboardingStep({
21
36
  required: false,
22
37
  title: "Connect Gmail",
23
38
  description: "Send, read, and organize real email.",
39
+ isAvailable: () => hasWorkspaceProviderOAuthCredentials("gmail"),
24
40
  methods: [
25
41
  {
26
- id: "manual-wizard",
42
+ id: "oauth",
27
43
  kind: "link",
28
44
  primary: true,
29
- label: "Connect Google OAuth (guided)",
30
- description: "3-minute guided setup in Google Cloud Console.",
31
- payload: { url: "/" },
45
+ label: "Connect Gmail for me",
46
+ description:
47
+ "One-click Google sign-in using the workspace's managed OAuth connection. Only you can use this connection.",
48
+ payload: {
49
+ url: "/_agent-native/connections/oauth/gmail/start?scope=user&appId=mail&return=/",
50
+ external: false,
51
+ },
52
+ },
53
+ {
54
+ id: "oauth-workspace",
55
+ kind: "link",
56
+ label: "Connect Gmail for my workspace",
57
+ description:
58
+ "Workspace admins can connect once and make this Gmail connection available to selected apps or everyone in the workspace.",
59
+ payload: {
60
+ url: "/_agent-native/connections/oauth/gmail/start?scope=organization&appId=mail&return=/",
61
+ external: false,
62
+ },
32
63
  },
33
64
  {
34
65
  id: "agent-task",
@@ -37,13 +68,30 @@ registerOnboardingStep({
37
68
  label: "Have the agent set it up for me",
38
69
  payload: {
39
70
  prompt:
40
- "Help me connect Gmail. Walk me through creating Google OAuth credentials by driving my browser (use any mcp__*browser* tools available, or fall back to giving me step-by-step instructions with exact URLs and values). When client_id and client_secret are ready, help me configure GOOGLE_CLIENT_ID and GOOGLE_CLIENT_SECRET as deployment or local environment variables for Mail, then restart or redeploy before clicking Sign in with Google.",
71
+ "Help me connect Gmail through the managed Google OAuth connection. Start the one-click Gmail connection flow, explain the Google permission screen, and confirm whether I want the connection available only to me or to the workspace. Do not ask me to create Google Cloud credentials or paste keys.",
41
72
  },
42
73
  },
43
74
  ],
44
- isComplete: async () =>
45
- Boolean(
46
- readDeployCredentialEnv("GOOGLE_CLIENT_ID") &&
47
- readDeployCredentialEnv("GOOGLE_CLIENT_SECRET"),
48
- ),
75
+ isComplete: async (context) => {
76
+ if (!context?.userEmail) return false;
77
+ try {
78
+ const accounts = await listOAuthAccountsByOwner(
79
+ "google",
80
+ context.userEmail,
81
+ );
82
+ if (accounts.some((account) => hasRequiredScope(account.tokens))) {
83
+ return true;
84
+ }
85
+ return (
86
+ await resolveWorkspaceConnectionForApp({
87
+ appId: "mail",
88
+ provider: "gmail",
89
+ requireConnected: true,
90
+ })
91
+ ).available;
92
+ } catch {
93
+ // coercion-ok: a failed lookup must leave onboarding incomplete, never completed.
94
+ return false;
95
+ }
96
+ },
49
97
  });
@@ -14,7 +14,12 @@ export default mergeConfig(
14
14
  },
15
15
  test: {
16
16
  include: ["**/*.{test,spec}.?(c|m)[jt]s?(x)"],
17
- exclude: ["**/node_modules/**", "**/.git/**", "**/dist/**"],
17
+ exclude: [
18
+ "**/node_modules/**",
19
+ "**/.git/**",
20
+ "**/dist/**",
21
+ "**/.react-router/**",
22
+ ],
18
23
  },
19
24
  }),
20
25
  );
@@ -19,10 +19,17 @@ data, and generated extension/app content may mention credential **names** such
19
19
  as `OPENAI_API_KEY`, but must not contain real API keys, tokens, webhook URLs,
20
20
  signing secrets, OAuth refresh tokens, or private Builder/customer data.
21
21
 
22
- Secret values are supplied at runtime through deployment configuration, the
23
- encrypted `app_secrets` vault, `saveCredential` / `resolveCredential`, OAuth, or
24
- `${keys.NAME}` substitution. Examples must use obvious placeholders such as
25
- `<OPENAI_API_KEY>` or `${keys.SLACK_WEBHOOK}`, not real-looking copied values.
22
+ Provider secret values are supplied at runtime through the encrypted
23
+ `app_secrets` vault, `saveCredential` / `resolveCredential`, OAuth, or
24
+ `${keys.NAME}` substitution. Deployment configuration is reserved for
25
+ deploy-level secrets and non-provider configuration. Examples must use obvious
26
+ placeholders such as `<OPENAI_API_KEY>` or `${keys.SLACK_WEBHOOK}`, not
27
+ real-looking copied values.
28
+
29
+ Provider credentials and provider account identifiers are workspace data. Use
30
+ standard workspace connections and org/workspace vault scopes; never put them
31
+ in `.env` or deployment environment variables, and never add a provider-specific
32
+ action or startup bootstrap just to write a credential for one organization.
26
33
 
27
34
  ## Credential Modeling Preflight
28
35
 
@@ -23,7 +23,10 @@ export function meta() {
23
23
 
24
24
  export default function SettingsRoute() {
25
25
  const t = useT();
26
- const agentSettingsTabs = useAgentSettingsTabs();
26
+ // /extensions redirects here and navigation.view supports "extensions"
27
+ // (see use-navigation-state.ts), so the settings tab must exist too —
28
+ // otherwise /settings/extensions silently falls back to General.
29
+ const agentSettingsTabs = useAgentSettingsTabs({ extensionTools: true });
27
30
  useSetPageTitle(t("settings.title"));
28
31
 
29
32
  const generalSearchEntries = useMemo<SettingsSearchEntry[]>(