@agent-native/core 0.169.1 → 0.170.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.
- package/agent-native.eject.json +4 -1
- package/bin/agent-native.js +23 -5
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/analytics/actions/query-agent-native-analytics.ts +1 -1
- package/corpus/templates/analytics/app/i18n-data.ts +28 -0
- package/corpus/templates/analytics/app/pages/Agents.tsx +25 -1
- package/corpus/templates/analytics/app/routes/$.tsx +10 -0
- package/corpus/templates/analytics/app/routes.ts +3 -1
- package/corpus/templates/analytics/changelog/2026-08-22-fixed-the-agent-native-templates-first-party-dashboard-panel.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-22-org-admin-panels-no-longer-report-a-database-error-as-a-per.md +9 -0
- package/corpus/templates/analytics/changelog/2026-08-22-slack-requests-report-invalid-inputs.md +6 -0
- package/corpus/templates/analytics/changelog/2026-08-22-unknown-analytics-links-now-return-a-real-not-found-status.md +6 -0
- package/corpus/templates/analytics/docs/schemas/first-party-analytics.md +2 -1
- package/corpus/templates/analytics/docs/schemas/tracked-events.md +2 -0
- package/corpus/templates/analytics/scripts/emit-netlify-dashboard-report-cron.ts +29 -7
- package/corpus/templates/analytics/server/handlers/slack.ts +59 -5
- package/corpus/templates/analytics/server/lib/db-admin-connections.ts +11 -6
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +132 -2
- package/corpus/templates/analytics/server/lib/slack.ts +4 -2
- package/corpus/templates/analytics/vitest.config.ts +6 -1
- package/corpus/templates/assets/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/assets/app/routes/library.tsx +5 -3
- package/corpus/templates/assets/app/routes.ts +3 -1
- package/corpus/templates/assets/changelog/2026-08-22-library-page-loads-again-instead-of-crashing-on-open.md +6 -0
- package/corpus/templates/assets/vitest.config.ts +6 -1
- package/corpus/templates/brain/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/brain/app/components/ui/pointer-lock.ts +1 -0
- package/corpus/templates/brain/app/routes/sources.tsx +8 -3
- package/corpus/templates/brain/changelog/2026-08-22-tuning-or-adding-a-source-no-longer-leaves-the-sources-page.md +6 -0
- package/corpus/templates/brain/vitest.config.ts +1 -0
- package/corpus/templates/calendar/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/calendar/actions/list-booking-links.ts +83 -4
- package/corpus/templates/calendar/actions/update-booking-link.ts +4 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPanel.tsx +11 -3
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +1 -1
- package/corpus/templates/calendar/app/components/calendar/GoogleConnectBanner.tsx +103 -44
- package/corpus/templates/calendar/app/components/calendar/OutOfOfficeEvent.tsx +28 -24
- package/corpus/templates/calendar/app/components/layout/AppLayout.tsx +9 -2
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +47 -58
- package/corpus/templates/calendar/app/hooks/use-booking-links.ts +5 -1
- package/corpus/templates/calendar/app/lib/event-layout.ts +52 -26
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +126 -98
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +1 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +88 -81
- package/corpus/templates/calendar/app/routes.ts +3 -1
- package/corpus/templates/calendar/changelog/2026-08-22-booking-link-visibility-respects-shared-access.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-clicking-an-out-of-office-event-s-marker-no-longer-creates-a.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-event-detail-views-now-show-your-calendar-timezone.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-google-calendar-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-overlapping-events-now-show-each-others-boundaries.md +6 -0
- package/corpus/templates/calendar/changelog/2026-08-22-public-booking-pages-now-offer-time-slots-from-the-host-s-we.md +6 -0
- package/corpus/templates/calendar/server/handlers/bookings.ts +5 -7
- package/corpus/templates/calendar/server/handlers/google-auth.ts +10 -2
- package/corpus/templates/calendar/server/lib/google-calendar.ts +139 -14
- package/corpus/templates/calendar/server/onboarding.ts +85 -0
- package/corpus/templates/calendar/server/plugins/agent-chat.ts +2 -0
- package/corpus/templates/calendar/shared/api.ts +9 -1
- package/corpus/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/chat/app/routes.ts +3 -1
- package/corpus/templates/chat/vitest.config.ts +8 -0
- package/corpus/templates/clips/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/clips/actions/get-meeting.ts +9 -4
- package/corpus/templates/clips/actions/list-recordings.ts +16 -3
- package/corpus/templates/clips/actions/search-meetings.ts +1 -0
- package/corpus/templates/clips/actions/view-screen.ts +8 -1
- package/corpus/templates/clips/app/components/meetings/meeting-history-row.tsx +32 -1
- package/corpus/templates/clips/app/components/player/delete-recording-menu.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/recorder-engine.ts +29 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +5 -0
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +33 -4
- package/corpus/templates/clips/app/routes/_app.meetings._index.tsx +1 -0
- package/corpus/templates/clips/app/routes/access-request.approve.tsx +16 -9
- package/corpus/templates/clips/app/routes/embed.$shareId.tsx +17 -7
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +17 -7
- package/corpus/templates/clips/changelog/2026-08-21-camera-bubble-black-circle.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-22-screen-recordings-respect-the-microphone-toggle.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-22-the-recordings-library-no-longer-fails-right-after-server-start.md +6 -0
- package/corpus/templates/clips/changelog/2026-08-23-the-agent-can-see-your-library-right-after-server-start.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +1 -2
- package/corpus/templates/clips/chrome-extension/src/native-transcription.ts +39 -7
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +21 -25
- package/corpus/templates/clips/desktop/src/app.tsx +53 -24
- package/corpus/templates/clips/desktop/src/lib/bubble-playback.ts +68 -0
- package/corpus/templates/clips/desktop/src/lib/recorder.ts +3 -0
- package/corpus/templates/clips/desktop/src/lib/transcription-capture.ts +72 -14
- package/corpus/templates/clips/desktop/src/lib/updater.ts +8 -3
- package/corpus/templates/clips/desktop/src/overlays/bubble.tsx +162 -20
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +43 -6
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen/custom_capture.rs +150 -13
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +12 -0
- package/corpus/templates/clips/server/lib/calendar-event-meetings.ts +28 -3
- package/corpus/templates/clips/server/lib/emails.ts +17 -33
- package/corpus/templates/content/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/content/app/components/editor/VisualEditor.tsx +140 -15
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +12 -14
- package/corpus/templates/content/app/components/ui/pointer-lock.ts +1 -0
- package/corpus/templates/content/app/hooks/use-content-database.ts +18 -2
- package/corpus/templates/content/app/hooks/use-local-storage.ts +38 -22
- package/corpus/templates/content/app/i18n-data.ts +25 -0
- package/corpus/templates/content/app/routes.ts +3 -1
- package/corpus/templates/content/changelog/2026-08-22-code-blocks-inserted-from-the-slash-menu-no-longer-get-stuck.md +6 -0
- package/corpus/templates/content/changelog/2026-08-22-the-sidebar-page-tree-no-longer-flashes-empty-after-creating-a-page.md +6 -0
- package/corpus/templates/content/docs/solutions/2026-08-21-content-mermaid-rendering-shape.md +233 -0
- package/corpus/templates/content/server/plugins/agent-chat.ts +9 -2
- package/corpus/templates/content/vitest.config.ts +7 -1
- package/corpus/templates/crm/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/design/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +1383 -88
- package/corpus/templates/design/actions/generate-design.ts +98 -47
- package/corpus/templates/design/actions/get-design-system.ts +6 -1
- package/corpus/templates/design/actions/refresh-design-system-with-builder.ts +146 -0
- package/corpus/templates/design/app/components/design/DesignCanvas.tsx +70 -26
- package/corpus/templates/design/app/components/design/MultiScreenCanvas.tsx +284 -136
- package/corpus/templates/design/app/components/design/bridge/editor-chrome.bridge.ts +2057 -141
- package/corpus/templates/design/app/components/design/editor/DesignBottomToolbar.tsx +6 -2
- package/corpus/templates/design/app/components/design/editor/DesignWorkspaceRail.tsx +4 -4
- package/corpus/templates/design/app/components/design/keyboard-shortcuts.ts +10 -1
- package/corpus/templates/design/app/components/design/multi-screen/alt-hover-measurement.ts +19 -2
- package/corpus/templates/design/app/components/design/multi-screen/types.ts +9 -2
- package/corpus/templates/design/app/components/layout/Header.tsx +15 -0
- package/corpus/templates/design/app/components/layout/Layout.tsx +7 -2
- package/corpus/templates/design/app/hooks/useDesignHotkeys.ts +11 -4
- package/corpus/templates/design/app/i18n-data.ts +1 -0
- package/corpus/templates/design/app/i18n-keyboard-shortcuts.ts +12 -0
- package/corpus/templates/design/app/lib/design-system-data.ts +68 -0
- package/corpus/templates/design/app/pages/DesignEditor.tsx +77 -35
- package/corpus/templates/design/app/pages/DesignSystems.tsx +136 -42
- package/corpus/templates/design/app/pages/Index.tsx +2 -0
- package/corpus/templates/design/app/pages/Templates.tsx +0 -10
- package/corpus/templates/design/app/pages/design-editor/code-layer-state.ts +10 -0
- package/corpus/templates/design/app/pages/design-editor/collab-sync.ts +50 -0
- package/corpus/templates/design/app/pages/design-editor/commands/apply-design-editor-command.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/apply-file-content-update.ts +10 -8
- package/corpus/templates/design/app/pages/design-editor/commands/apply-local-content-update.ts +6 -6
- package/corpus/templates/design/app/pages/design-editor/commands/apply-pending-visual-styles-with-agent.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/commit-visual-styles.ts +18 -7
- package/corpus/templates/design/app/pages/design-editor/commands/escape-hotkey.ts +4 -90
- package/corpus/templates/design/app/pages/design-editor/commands/layer-marquee-selection-change.ts +2 -1
- package/corpus/templates/design/app/pages/design-editor/commands/layer-selection-change.ts +4 -3
- package/corpus/templates/design/app/pages/design-editor/commands/screen-element-select.ts +12 -2
- package/corpus/templates/design/app/pages/design-editor/commands/screen-visual-style-change.ts +9 -2
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-move-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/send-runtime-layer-state-semantic-handoff.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/commands/start-sidebar-resize.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/editor-state.ts +11 -1
- package/corpus/templates/design/app/pages/design-editor/effects/adopt-db-file-content.ts +11 -19
- package/corpus/templates/design/app/pages/design-editor/effects/observe-collab-text.ts +6 -6
- package/corpus/templates/design/app/pages/design-editor/effects/publish-agent-selection-context.ts +1 -1
- package/corpus/templates/design/app/pages/design-editor/effects/seed-collab-content.ts +12 -15
- package/corpus/templates/design/app/pages/design-editor/selection-state.ts +5 -59
- package/corpus/templates/design/app/pages/design-editor/tool-state.ts +8 -0
- package/corpus/templates/design/app/pages/design-editor/types.ts +3 -3
- package/corpus/templates/design/app/routes.ts +3 -1
- package/corpus/templates/design/changelog/2026-08-20-canvas-edits-patch-the-frame-instead-of-rebuilding-it.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-builder-design-system-imports-reconcile-after-indexing.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-draw-mode-needs-shift-y.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-eyedropper-shortcut.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-figma-style-alignment-guides-and-snapping.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-group-transforms-keep-the-selection.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-inspector-fields-keep-focus-on-enter.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-pen-alt-keeps-the-incoming-handle.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-pen-on-the-board-creates-one-vector.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-scale-a-multi-selection-from-the-group-handles.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-scale-resizes-text-with-the-object.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-selecting-another-object-keeps-the-scale-tool-armed.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-the-shape-tool-remembers-the-shape-you-picked.md +6 -0
- package/corpus/templates/design/changelog/2026-08-21-vectors-drawn-in-a-frame-land-where-you-drew-them.md +6 -0
- package/corpus/templates/design/changelog/2026-08-22-multi-screen-generation-no-longer-loses-a-saved-screens-layo.md +6 -0
- package/corpus/templates/design/changelog/2026-08-22-rename-and-search-fields-are-labeled-for-screen-readers.md +6 -0
- package/corpus/templates/design/package.json +2 -0
- package/corpus/templates/design/server/lib/builder-design-system-proxy.ts +311 -0
- package/corpus/templates/design/shared/canvas-math.ts +612 -206
- package/corpus/templates/design/shared/pen-path.ts +37 -0
- package/corpus/templates/design/vitest.config.ts +7 -1
- package/corpus/templates/dispatch/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/dispatch/actions/list-workspace-connections.ts +34 -3
- package/corpus/templates/dispatch/app/routes/integrations.tsx +55 -21
- package/corpus/templates/dispatch/app/routes.ts +3 -1
- package/corpus/templates/factory/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/factory/.env.example +2 -7
- package/corpus/templates/factory/AGENTS.md +3 -0
- package/corpus/templates/factory/README.md +5 -3
- package/corpus/templates/factory/actions/create-factory.ts +16 -3
- package/corpus/templates/factory/actions/get-slack-feedback-context.ts +2 -0
- package/corpus/templates/factory/actions/get-triage-item.ts +2 -0
- package/corpus/templates/factory/actions/list-factory-audit.ts +116 -36
- package/corpus/templates/factory/actions/list-factory-automations.ts +2 -1
- package/corpus/templates/factory/actions/list-triage-items.ts +22 -19
- package/corpus/templates/factory/actions/run-factory-automation.ts +7 -3
- package/corpus/templates/factory/actions/save-factory-automation.ts +5 -2
- package/corpus/templates/factory/actions/save-triage-config.ts +2 -8
- package/corpus/templates/factory/actions/start-builder-for-item.ts +21 -0
- package/corpus/templates/factory/app/components/factory/FactoryAuditView.tsx +229 -297
- package/corpus/templates/factory/app/components/factory/FactorySettingsView.tsx +397 -247
- package/corpus/templates/factory/app/components/factory/FactorySourceSettingsGroup.tsx +33 -0
- package/corpus/templates/factory/app/components/factory/NewFactoryForm.tsx +202 -162
- package/corpus/templates/factory/app/i18n/en-US.ts +32 -4
- package/corpus/templates/factory/app/routes/factory.tsx +84 -11
- package/corpus/templates/factory/app/routes.ts +3 -1
- package/corpus/templates/factory/changelog/2026-08-21-activity-runs-use-automation-display-names.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-create-and-settings-source-cards.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-keep-automations-visible-while-running.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-nested-factory-automations-can-poll.md +6 -0
- package/corpus/templates/factory/changelog/2026-08-21-settings-sticky-unsaved-bar.md +6 -0
- package/corpus/templates/factory/server/connectors/credentials.ts +10 -5
- package/corpus/templates/factory/server/lib/factory-audit-report.ts +412 -0
- package/corpus/templates/factory/server/lib/factory-config-reconcile.ts +321 -0
- package/corpus/templates/factory/server/lib/factory-scope.ts +25 -2
- package/corpus/templates/factory/server/lib/require-factory-automation.ts +13 -4
- package/corpus/templates/factory/server/plugins/factory-migrations.ts +148 -4
- package/corpus/templates/factory/server/plugins/factory-scheduler-job.ts +4 -6
- package/corpus/templates/factory/vitest.config.ts +17 -2
- package/corpus/templates/forms/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/forms/app/routes/_app.settings.tsx +4 -1
- package/corpus/templates/forms/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +11 -1
- package/corpus/templates/forms/vitest.config.ts +1 -0
- package/corpus/templates/macros/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/macros/app/routes.ts +3 -1
- package/corpus/templates/macros/vitest.config.ts +6 -1
- package/corpus/templates/mail/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/mail/actions/manage-gmail-filters.ts +10 -2
- package/corpus/templates/mail/app/components/GoogleConnectBanner.tsx +100 -37
- package/corpus/templates/mail/app/components/email/IntegrationsSidebar.tsx +14 -2
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +66 -36
- package/corpus/templates/mail/app/hooks/use-google-auth.ts +2 -0
- package/corpus/templates/mail/app/lib/google-oauth-setup.ts +17 -0
- package/corpus/templates/mail/app/pages/InboxPage.tsx +69 -9
- package/corpus/templates/mail/app/routes/$.tsx +9 -0
- package/corpus/templates/mail/app/routes/$view.$threadId.tsx +23 -1
- package/corpus/templates/mail/app/routes/$view.tsx +47 -1
- package/corpus/templates/mail/app/routes.ts +3 -1
- package/corpus/templates/mail/changelog/2026-08-22-gmail-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-22-gmail-filters-now-shows-the-real-error.md +6 -0
- package/corpus/templates/mail/changelog/2026-08-22-unknown-mail-urls-now-show-the-404-page.md +6 -0
- package/corpus/templates/mail/server/handlers/google-auth.ts +11 -2
- package/corpus/templates/mail/server/lib/google-auth.ts +92 -12
- package/corpus/templates/mail/server/onboarding.ts +66 -18
- package/corpus/templates/mail/vitest.config.ts +6 -1
- package/corpus/templates/plan/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/plan/app/routes/settings.tsx +4 -1
- package/corpus/templates/plan/app/routes.ts +3 -1
- package/corpus/templates/plan/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/plan/netlify.toml +1 -1
- package/corpus/templates/slides/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/slides/actions/patch-deck.ts +79 -10
- package/corpus/templates/slides/actions/update-slide.ts +11 -1
- package/corpus/templates/slides/app/components/deck/SlideRenderer.tsx +14 -4
- package/corpus/templates/slides/app/components/editor/ExportMenu.tsx +10 -52
- package/corpus/templates/slides/app/components/editor/GoogleDocImportHint.tsx +8 -53
- package/corpus/templates/slides/app/components/editor/GoogleDriveConnectionCta.tsx +9 -75
- package/corpus/templates/slides/app/components/editor/GoogleSlidesReferenceImport.tsx +8 -60
- package/corpus/templates/slides/app/components/editor/SlideContextToolbar.tsx +10 -2
- package/corpus/templates/slides/app/components/editor/SlideEditor.tsx +19 -7
- package/corpus/templates/slides/app/lib/sanitize-slide-html.ts +49 -36
- package/corpus/templates/slides/app/routes/access-request.approve.tsx +16 -9
- package/corpus/templates/slides/app/routes.ts +3 -1
- package/corpus/templates/slides/changelog/2026-08-22-already-uploaded-chat-images-are-no-longer-dropped.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-google-drive-and-slides-can-now-connect-with-the-shared-google-oauth.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-multi-slide-repairs-now-report-slides-that-still-overflow.md +6 -0
- package/corpus/templates/slides/changelog/2026-08-22-update-slide-now-reports-which-batched-edit-was-skipped.md +6 -0
- package/corpus/templates/slides/server/handlers/google-docs-auth.ts +52 -1
- package/corpus/templates/slides/server/lib/chat-attachments.ts +29 -3
- package/corpus/templates/slides/server/lib/google-docs-oauth.ts +66 -16
- package/corpus/templates/slides/server/lib/slide-content-patch.ts +13 -2
- package/corpus/templates/slides/server/plugins/agent-chat.ts +5 -2
- package/corpus/templates/slides/tsconfig.json +5 -0
- package/corpus/templates/slides/vitest.config.ts +6 -1
- package/corpus/templates/tasks/.agents/skills/secrets/SKILL.md +11 -4
- package/corpus/templates/tasks/app/routes/settings.tsx +4 -1
- package/corpus/templates/tasks/app/routes.ts +3 -1
- package/corpus/templates/tasks/changelog/2026-08-22-the-extensions-link-now-opens-the-extensions-tab-instead-o.md +6 -0
- package/corpus/templates/tasks/vitest.config.ts +7 -1
- package/dist/action.d.ts +91 -0
- package/dist/action.js +50 -0
- package/dist/agent/abort-reasons.d.ts +38 -0
- package/dist/agent/abort-reasons.js +49 -0
- package/dist/agent/engine/ai-sdk-engine.js +8 -5
- package/dist/agent/engine/anthropic-engine.js +7 -5
- package/dist/agent/engine/first-event-timeout.d.ts +42 -17
- package/dist/agent/engine/first-event-timeout.js +67 -25
- package/dist/agent/production-agent.d.ts +23 -0
- package/dist/agent/production-agent.js +107 -102
- package/dist/agent/run-loop-with-resume.d.ts +2 -28
- package/dist/agent/run-loop-with-resume.js +25 -40
- package/dist/agent/run-manager.js +5 -4
- package/dist/agent/run-store.d.ts +55 -0
- package/dist/agent/run-store.js +124 -1
- package/dist/agent/tool-search.js +2 -1
- package/dist/agent/types.d.ts +9 -4
- package/dist/app-config/configuration-error.d.ts +11 -0
- package/dist/app-config/configuration-error.js +14 -0
- package/dist/app-config/index.d.ts +1 -0
- package/dist/app-config/index.js +1 -0
- package/dist/app-config/integrations.d.ts +1 -0
- package/dist/app-config/integrations.js +10 -0
- package/dist/app-config/plugins.d.ts +34 -0
- package/dist/app-config/plugins.js +39 -0
- package/dist/app-config/run-lifecycle-invariants.d.ts +18 -9
- package/dist/app-config/run-lifecycle-invariants.js +18 -25
- package/dist/app-config/schema.d.ts +16 -0
- package/dist/app-config/schema.js +2 -0
- package/dist/chat-threads/schema-migrations.js +20 -0
- package/dist/chat-threads/store.js +21 -4
- package/dist/cli/native-dependencies.d.ts +4 -0
- package/dist/cli/native-dependencies.js +15 -0
- package/dist/client/EnvironmentBadge.d.ts +1 -1
- package/dist/client/EnvironmentBadge.js +40 -5
- package/dist/client/MultiTabAssistantChat.d.ts +1 -1
- package/dist/client/MultiTabAssistantChat.js +35 -14
- package/dist/client/agent-chat/index.d.ts +1 -1
- package/dist/client/agent-chat/index.js +1 -1
- package/dist/client/agent-page/use-jobs.d.ts +2 -1
- package/dist/client/agent-page/use-jobs.js +3 -5
- package/dist/client/agent-sidebar-state.js +3 -1
- package/dist/client/blocks/library/code-tabs.js +5 -0
- package/dist/client/blocks/library/code.js +1 -0
- package/dist/client/chat-first/apps-rail.js +2 -2
- package/dist/client/chat-model-groups.d.ts +12 -0
- package/dist/client/chat-model-groups.js +3 -2
- package/dist/client/integrations/GoogleProductLogo.d.ts +5 -0
- package/dist/client/integrations/GoogleProductLogo.js +45 -0
- package/dist/client/integrations/index.d.ts +2 -0
- package/dist/client/integrations/index.js +2 -0
- package/dist/client/integrations/workspace-provider-oauth.d.ts +8 -0
- package/dist/client/integrations/workspace-provider-oauth.js +13 -0
- package/dist/client/resources/mcp-integration-catalog.js +0 -29
- package/dist/client/settings/SettingsTabsPage.js +10 -6
- package/dist/client/sharing/ShareButton.js +11 -2
- package/dist/client/use-chat-models.d.ts +6 -0
- package/dist/client/use-chat-models.js +10 -0
- package/dist/client/use-db-sync.js +27 -9
- package/dist/connections/catalog.d.ts +111 -9
- package/dist/connections/catalog.js +86 -30
- package/dist/db/create-get-db.js +18 -0
- package/dist/db/ddl-guard.d.ts +0 -4
- package/dist/db/ddl-guard.js +46 -0
- package/dist/deploy/build.d.ts +19 -0
- package/dist/deploy/build.js +190 -16
- package/dist/deploy/function-bundle.d.ts +45 -0
- package/dist/deploy/function-bundle.js +337 -0
- package/dist/deploy/workspace-deploy.js +19 -1
- package/dist/eject/workspace-connections.js +4 -0
- package/dist/email-catalog/registry.d.ts +4 -0
- package/dist/email-catalog/registry.js +79 -14
- package/dist/extensions/content-patch.js +14 -2
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/plugin.d.ts +10 -0
- package/dist/integrations/plugin.js +40 -6
- package/dist/jobs/actions/run-automation-now.d.ts +2 -1
- package/dist/jobs/actions/run-automation-now.js +11 -5
- package/dist/jobs/frontmatter.js +53 -2
- package/dist/jobs/run-now.d.ts +11 -1
- package/dist/jobs/run-now.js +42 -7
- package/dist/jobs/scheduler.d.ts +1 -0
- package/dist/jobs/scheduler.js +2 -1
- package/dist/localization/core-messages/ar-SA.js +3 -0
- package/dist/localization/core-messages/de-DE.js +3 -0
- package/dist/localization/core-messages/en-US.d.ts +3 -0
- package/dist/localization/core-messages/en-US.js +3 -0
- package/dist/localization/core-messages/es-ES.js +3 -0
- package/dist/localization/core-messages/fr-FR.js +3 -0
- package/dist/localization/core-messages/hi-IN.js +3 -0
- package/dist/localization/core-messages/ja-JP.js +3 -0
- package/dist/localization/core-messages/ko-KR.js +3 -0
- package/dist/localization/core-messages/pt-BR.js +3 -0
- package/dist/localization/core-messages/zh-CN.js +3 -0
- package/dist/localization/core-messages/zh-TW.js +3 -0
- package/dist/localization/core-messages.d.ts +3 -0
- package/dist/localization/default-messages.d.ts +2 -5
- package/dist/localization/default-messages.js +2 -5
- package/dist/mcp/build-server.d.ts +9 -0
- package/dist/mcp/build-server.js +37 -8
- package/dist/mcp-client/oauth-client.d.ts +7 -0
- package/dist/mcp-client/oauth-client.js +12 -1
- package/dist/oauth-tokens/index.d.ts +1 -1
- package/dist/oauth-tokens/index.js +1 -1
- package/dist/oauth-tokens/lifecycle.d.ts +11 -0
- package/dist/oauth-tokens/lifecycle.js +17 -0
- package/dist/oauth-tokens/store.js +7 -0
- package/dist/observability/posthog-ai.d.ts +15 -1
- package/dist/observability/posthog-ai.js +24 -12
- package/dist/observability/routes.d.ts +3 -3
- package/dist/observability/traces.js +158 -28
- package/dist/onboarding/app-profile.js +2 -2
- package/dist/onboarding/plugin.js +10 -2
- package/dist/onboarding/types.d.ts +2 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +1 -1
- package/dist/provider-api/actions/provider-api.d.ts +2 -2
- package/dist/provider-api/index.js +2 -0
- package/dist/resources/store.js +13 -4
- package/dist/scripts/agent-engines/list-agent-engines.js +29 -3
- package/dist/secrets/register-framework-secrets.js +1 -1
- package/dist/server/action-discovery.js +5 -0
- package/dist/server/agent-chat/action-filters-a2a.d.ts +27 -3
- package/dist/server/agent-chat/action-filters-a2a.js +48 -9
- package/dist/server/agent-chat-plugin.js +36 -9
- package/dist/server/agent-discovery.js +43 -20
- package/dist/server/auth.js +18 -16
- package/dist/server/beta-opt-out-html.js +45 -4
- package/dist/server/better-auth-instance.d.ts +2 -1
- package/dist/server/better-auth-instance.js +23 -7
- package/dist/server/builder-design-systems.d.ts +4 -1
- package/dist/server/builder-design-systems.js +87 -6
- package/dist/server/builder-oauth.d.ts +1 -7
- package/dist/server/builder-oauth.js +80 -163
- package/dist/server/core-routes-plugin.d.ts +29 -0
- package/dist/server/core-routes-plugin.js +100 -20
- package/dist/server/default-plugins.d.ts +18 -0
- package/dist/server/default-plugins.js +22 -0
- package/dist/server/edge.d.ts +1 -0
- package/dist/server/edge.js +1 -0
- package/dist/server/framework-request-handler.js +31 -3
- package/dist/server/google-oauth.d.ts +3 -0
- package/dist/server/google-oauth.js +3 -0
- package/dist/server/index.d.ts +4 -4
- package/dist/server/index.js +3 -3
- package/dist/server/onboarding-html.js +37 -3
- package/dist/server/realtime-token.d.ts +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/server/workspace-provider-oauth.d.ts +6 -1
- package/dist/server/workspace-provider-oauth.js +72 -20
- package/dist/shared/agent-sidebar-url.d.ts +1 -0
- package/dist/shared/agent-sidebar-url.js +1 -0
- package/dist/shared/environment-lanes.d.ts +2 -0
- package/dist/shared/environment-lanes.js +3 -1
- package/dist/shared/index.d.ts +3 -1
- package/dist/shared/index.js +3 -1
- package/dist/shared/surface-visibility.d.ts +31 -0
- package/dist/shared/surface-visibility.js +57 -0
- package/dist/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/chat/app/routes.ts +3 -1
- package/dist/templates/chat/vitest.config.ts +8 -0
- package/dist/templates/default/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/default/app/routes.ts +3 -1
- package/dist/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/templates/workspace-core/.agents/skills/secrets/SKILL.md +11 -4
- package/dist/tracking/registry.d.ts +11 -0
- package/dist/tracking/registry.js +5 -2
- package/dist/triggers/actions.js +10 -3
- package/dist/usage/store.js +23 -3
- package/dist/user-profile/actions/get-auth-methods.js +18 -5
- package/dist/vite/client.js +24 -5
- package/docs/content/actions-access-control.mdx +43 -4
- package/docs/content/actions-defining.mdx +16 -0
- package/docs/content/template-clips-capture-everywhere.mdx +29 -30
- package/docs/content/template-clips-developers.mdx +4 -4
- package/docs/content/template-clips.mdx +3 -1
- package/package.json +2 -2
- package/src/eject/workspace-connections.ts +4 -0
- package/src/templates/chat/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/chat/app/routes.ts +3 -1
- package/src/templates/chat/vitest.config.ts +8 -0
- package/src/templates/default/.agents/skills/secrets/SKILL.md +11 -4
- package/src/templates/default/app/routes.ts +3 -1
- package/src/templates/headless/.agents/skills/secrets/SKILL.md +11 -4
- 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
|
-
|
|
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
|
-
|
|
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(
|
|
4
|
+
export default flatRoutes({
|
|
5
|
+
ignoredRouteFiles: ["**/*.test.{ts,tsx}", "**/*.spec.{ts,tsx}"],
|
|
6
|
+
}) satisfies RouteConfig;
|
|
@@ -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
|
-
|
|
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(
|
|
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
|
-
|
|
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) =>
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
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 =
|
|
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
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
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 {
|
|
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: "
|
|
42
|
+
id: "oauth",
|
|
27
43
|
kind: "link",
|
|
28
44
|
primary: true,
|
|
29
|
-
label: "Connect
|
|
30
|
-
description:
|
|
31
|
-
|
|
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
|
|
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
|
-
|
|
46
|
-
|
|
47
|
-
|
|
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: [
|
|
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
|
-
|
|
23
|
-
|
|
24
|
-
`${keys.NAME}` substitution.
|
|
25
|
-
|
|
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
|
-
|
|
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[]>(
|