@agent-native/core 0.92.0 → 0.92.1
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/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +14 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/store.ts +12 -1
- package/corpus/core/src/client/AssistantChat.tsx +27 -14
- package/corpus/core/src/client/PoweredByBadge.tsx +1 -8
- package/corpus/core/src/client/agent-chat-adapter.ts +37 -10
- package/corpus/core/src/client/chat/widgets/DataTableWidget.tsx +52 -1
- package/corpus/core/src/client/db-admin/TableEditor.tsx +49 -0
- package/corpus/core/src/client/db-admin/export-utils.ts +13 -4
- package/corpus/core/src/client/db-admin/useAgentSync.ts +72 -0
- package/corpus/core/src/client/error-capture.ts +69 -7
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +5 -1
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +53 -8
- package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +5 -1
- package/corpus/core/src/client/extensions/portable-extension.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +12 -9
- package/corpus/core/src/client/org/TeamPage.tsx +77 -71
- package/corpus/core/src/client/settings/SettingsPanel.tsx +205 -52
- package/corpus/core/src/client/settings/SettingsSection.tsx +8 -1
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +25 -9
- package/corpus/core/src/db-admin/agent-tools.ts +8 -2
- package/corpus/core/src/db-admin/operations.ts +116 -4
- package/corpus/core/src/db-admin/routes.ts +1 -0
- package/corpus/core/src/db-admin/types.ts +4 -0
- package/corpus/core/src/extensions/html-shell.ts +5 -4
- package/corpus/core/src/extensions/routes.ts +10 -0
- package/corpus/core/src/navigation/index.ts +1 -1
- package/corpus/core/src/resources/handlers.ts +10 -8
- package/corpus/core/src/server/poll.ts +293 -8
- package/corpus/core/src/styles/agent-native.css +27 -0
- package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/core/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +7 -0
- package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/core/src/templates/default/AGENTS.md +4 -2
- package/corpus/core/src/templates/headless/AGENTS.md +4 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/core/src/templates/workspace-core/AGENTS.md +7 -1
- package/corpus/core/src/templates/workspace-root/AGENTS.md +7 -1
- package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +10 -0
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/analytics/AGENTS.md +33 -14
- package/corpus/templates/analytics/actions/capture-test-error.ts +4 -0
- package/corpus/templates/analytics/actions/get-analytics-alert-rule-defaults.ts +23 -0
- package/corpus/templates/analytics/actions/get-error-issue.ts +1 -1
- package/corpus/templates/analytics/actions/list-analysis-revisions.ts +40 -0
- package/corpus/templates/analytics/actions/list-dashboard-revisions.ts +40 -0
- package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +299 -0
- package/corpus/templates/analytics/actions/navigate.ts +4 -2
- package/corpus/templates/analytics/actions/restore-analysis-revision.ts +43 -0
- package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +68 -0
- package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +14 -0
- package/corpus/templates/analytics/actions/save-monitor.ts +1 -1
- package/corpus/templates/analytics/actions/view-screen.ts +24 -0
- package/corpus/templates/analytics/app/components/analysis/AnalysisHistoryPanel.tsx +181 -0
- package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +184 -0
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +22 -16
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +27 -21
- package/corpus/templates/analytics/app/hooks/use-analysis-revisions.ts +43 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +110 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +43 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +8 -3
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +64 -2
- package/corpus/templates/analytics/app/i18n-data.ts +783 -3
- package/corpus/templates/analytics/app/pages/Agents.tsx +359 -19
- package/corpus/templates/analytics/app/pages/Settings.tsx +3 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +34 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +28 -0
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +18 -1
- package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +7 -10
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +479 -23
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +0 -9
- package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +11 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/types.ts +9 -1
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +6 -3
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +26 -30
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -99
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +262 -104
- package/corpus/templates/analytics/app/routes/team.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-07-08-admins-can-audit-dashboard-usage-ownership-and-cleanup-signa.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-agent-monitoring-stays-available-to-non-admins.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-alert-rules-are-easier-to-scan-expand-and-edit-from-settings.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-analytics-alert-forms-remember-last-email-recipients.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-analytics-alert-rules-can-now-use-per-rule-slack-and-webhook.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +5 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-usage-now-counts-explorer-dashboard-traffic.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-demo-mode-session-emails-are-filtered-and-anonymized.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-error-issue-detail-adds-stack-traces-and-frequency-bars.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-error-issue-detail-now-shows-the-latest-occurrence-message-a.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-monitoring-errors-only-show-test-action-when-empty.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-production-session-replay-now-requires-private-blob-storage-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-shared-extension-links-now-show-a-clear-message-when-you-do-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-team-settings-show-access-controls-next-to-member-lists.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-use-a-10-second-request-timeout-by-defau.md +6 -0
- package/corpus/templates/analytics/server/db/schema-monitoring.ts +1 -1
- package/corpus/templates/analytics/server/db/schema.ts +56 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +92 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +334 -5
- package/corpus/templates/analytics/server/lib/error-capture.ts +231 -36
- package/corpus/templates/analytics/server/lib/session-replay.ts +114 -8
- package/corpus/templates/analytics/server/lib/uptime-monitors.ts +5 -4
- package/corpus/templates/analytics/server/plugins/db.ts +112 -2
- package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/assets/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/app/routes/settings.tsx +1 -1
- package/corpus/templates/assets/app/routes/team.tsx +1 -1
- package/corpus/templates/brain/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/brain/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/brain/AGENTS.md +4 -0
- package/corpus/templates/brain/app/routes/team.tsx +1 -1
- package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/calendar/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/calendar/AGENTS.md +4 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +11 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +21 -8
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +7 -1
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -2
- package/corpus/templates/calendar/app/lib/popover-click-guard.ts +48 -4
- package/corpus/templates/calendar/app/pages/Settings.tsx +1 -1
- package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-07-08-clicking-empty-calendar-space-closes-an-open-event-popover-b.md +6 -0
- package/corpus/templates/chat/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/chat/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/chat/AGENTS.md +4 -0
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/chat/app/routes/settings.tsx +2 -2
- package/corpus/templates/chat/app/routes/team.tsx +1 -1
- package/corpus/templates/clips/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/clips/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +9 -3
- package/corpus/templates/clips/actions/set-thumbnail.ts +11 -2
- package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +1 -7
- package/corpus/templates/clips/app/components/player/player-controls.tsx +21 -13
- package/corpus/templates/clips/app/components/player/video-player.tsx +30 -75
- package/corpus/templates/clips/app/i18n/ar-SA.ts +6 -5
- package/corpus/templates/clips/app/i18n/de-DE.ts +6 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +6 -5
- package/corpus/templates/clips/app/i18n/es-ES.ts +6 -5
- package/corpus/templates/clips/app/i18n/fr-FR.ts +6 -6
- package/corpus/templates/clips/app/i18n/hi-IN.ts +6 -5
- package/corpus/templates/clips/app/i18n/ja-JP.ts +6 -5
- package/corpus/templates/clips/app/i18n/ko-KR.ts +6 -5
- package/corpus/templates/clips/app/i18n/pt-BR.ts +6 -5
- package/corpus/templates/clips/app/i18n/zh-CN.ts +6 -5
- package/corpus/templates/clips/app/i18n/zh-TW.ts +6 -5
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +51 -118
- package/corpus/templates/clips/changelog/2026-07-08-hosted-clip-uploads-now-clear-failed-buffered-chunks.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-hosted-recording-uploads-now-avoid-sql-chunk-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-meeting-recaps-now-keep-summaries-action-items-and-transcrip.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-video-playback-controls-now-match-loom-style-5-second-skips-.md +6 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +63 -0
- package/corpus/templates/clips/server/lib/video-storage.ts +4 -0
- package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +19 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +40 -1
- package/corpus/templates/content/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/content/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/content/AGENTS.md +4 -0
- package/corpus/templates/content/app/routes/_app.settings.tsx +2 -2
- package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/design/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/design/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/design/AGENTS.md +4 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +12 -9
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +57 -13
- package/corpus/templates/design/app/routes/team.tsx +1 -1
- package/corpus/templates/design/changelog/2026-07-08-local-images-added-to-designs-now-upload-to-file-storage-ins.md +6 -0
- package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/dispatch/AGENTS.md +4 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/dispatch/app/routes/settings.tsx +2 -2
- package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
- package/corpus/templates/forms/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/forms/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/forms/AGENTS.md +4 -0
- package/corpus/templates/forms/app/routes/_app.settings.tsx +2 -2
- package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/forms/shared/navigation.ts +1 -1
- package/corpus/templates/macros/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/macros/AGENTS.md +4 -0
- package/corpus/templates/macros/app/routes/settings.tsx +2 -2
- package/corpus/templates/macros/app/routes/team.tsx +1 -1
- package/corpus/templates/mail/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/mail/AGENTS.md +4 -0
- package/corpus/templates/mail/changelog/2026-07-08-mail-attachments-in-hosted-environments-now-require-file-sto.md +6 -0
- package/corpus/templates/mail/server/handlers/media.ts +26 -1
- package/corpus/templates/mail/server/lib/outgoing-email.ts +24 -1
- package/corpus/templates/mail/server/lib/upload-store.ts +2 -1
- package/corpus/templates/plan/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/plan/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/plan/AGENTS.md +4 -0
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/plan/app/routes/settings.tsx +2 -2
- package/corpus/templates/plan/app/routes/team.tsx +1 -1
- package/corpus/templates/plan/changelog/2026-07-08-hosted-plan-images-now-require-connected-storage.md +6 -0
- package/corpus/templates/plan/server/lib/plan-assets.ts +33 -4
- package/corpus/templates/slides/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/slides/AGENTS.md +4 -0
- package/corpus/templates/slides/actions/generate-image-api.ts +15 -3
- package/corpus/templates/slides/app/routes/settings.tsx +2 -2
- package/corpus/templates/slides/app/routes/team.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-07-08-generated-slide-images-now-return-hosted-file-storage-urls-i.md +6 -0
- package/corpus/templates/slides/shared/api.ts +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +8 -2
- package/dist/application-state/store.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +24 -13
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/PoweredByBadge.d.ts +1 -1
- package/dist/client/PoweredByBadge.d.ts.map +1 -1
- package/dist/client/PoweredByBadge.js +3 -9
- package/dist/client/PoweredByBadge.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +28 -9
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/widgets/DataTableWidget.d.ts.map +1 -1
- package/dist/client/chat/widgets/DataTableWidget.js +19 -2
- package/dist/client/chat/widgets/DataTableWidget.js.map +1 -1
- package/dist/client/db-admin/TableEditor.d.ts.map +1 -1
- package/dist/client/db-admin/TableEditor.js +19 -2
- package/dist/client/db-admin/TableEditor.js.map +1 -1
- package/dist/client/db-admin/export-utils.d.ts +2 -0
- package/dist/client/db-admin/export-utils.d.ts.map +1 -1
- package/dist/client/db-admin/export-utils.js +11 -3
- package/dist/client/db-admin/export-utils.js.map +1 -1
- package/dist/client/db-admin/useAgentSync.d.ts.map +1 -1
- package/dist/client/db-admin/useAgentSync.js +63 -0
- package/dist/client/db-admin/useAgentSync.js.map +1 -1
- package/dist/client/error-capture.d.ts.map +1 -1
- package/dist/client/error-capture.js +41 -7
- package/dist/client/error-capture.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +3 -1
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +32 -5
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.js +3 -1
- package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js +1 -0
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -6
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +5 -5
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +37 -14
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsSection.js +1 -1
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +21 -10
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db-admin/agent-tools.d.ts.map +1 -1
- package/dist/db-admin/agent-tools.js +7 -2
- package/dist/db-admin/agent-tools.js.map +1 -1
- package/dist/db-admin/operations.d.ts.map +1 -1
- package/dist/db-admin/operations.js +90 -4
- package/dist/db-admin/operations.js.map +1 -1
- package/dist/db-admin/routes.d.ts.map +1 -1
- package/dist/db-admin/routes.js +1 -0
- package/dist/db-admin/routes.js.map +1 -1
- package/dist/db-admin/types.d.ts +4 -0
- package/dist/db-admin/types.d.ts.map +1 -1
- package/dist/db-admin/types.js.map +1 -1
- package/dist/extensions/html-shell.d.ts +5 -4
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +8 -0
- package/dist/extensions/routes.js.map +1 -1
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +1 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +6 -1
- package/dist/resources/handlers.d.ts.map +1 -1
- package/dist/resources/handlers.js +9 -8
- package/dist/resources/handlers.js.map +1 -1
- package/dist/server/poll.d.ts +12 -4
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +236 -9
- package/dist/server/poll.js.map +1 -1
- package/dist/styles/agent-native.css +27 -0
- package/dist/templates/default/.agents/skills/actions/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/default/.agents/skills/security/SKILL.md +7 -0
- package/dist/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/dist/templates/default/AGENTS.md +4 -2
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
- package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
- package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/dist/templates/workspace-core/AGENTS.md +7 -1
- package/dist/templates/workspace-root/AGENTS.md +7 -1
- package/package.json +1 -1
- package/src/templates/default/.agents/skills/actions/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/default/.agents/skills/security/SKILL.md +7 -0
- package/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/src/templates/default/AGENTS.md +4 -2
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
- package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/src/templates/workspace-core/AGENTS.md +7 -1
- package/src/templates/workspace-root/AGENTS.md +7 -1
|
@@ -568,6 +568,8 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
568
568
|
severity TEXT NOT NULL DEFAULT 'warning',
|
|
569
569
|
channels TEXT NOT NULL DEFAULT '["inbox"]',
|
|
570
570
|
email_recipients TEXT NOT NULL DEFAULT '[]',
|
|
571
|
+
slack_webhook_url TEXT,
|
|
572
|
+
webhook_url TEXT,
|
|
571
573
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
572
574
|
last_evaluated_at TEXT,
|
|
573
575
|
last_triggered_at TEXT,
|
|
@@ -592,6 +594,8 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
592
594
|
severity TEXT NOT NULL DEFAULT 'warning',
|
|
593
595
|
channels TEXT NOT NULL DEFAULT '["inbox"]',
|
|
594
596
|
email_recipients TEXT NOT NULL DEFAULT '[]',
|
|
597
|
+
slack_webhook_url TEXT,
|
|
598
|
+
webhook_url TEXT,
|
|
595
599
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
596
600
|
last_evaluated_at TEXT,
|
|
597
601
|
last_triggered_at TEXT,
|
|
@@ -891,7 +895,7 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
891
895
|
request_headers TEXT NOT NULL DEFAULT '{}',
|
|
892
896
|
request_body TEXT,
|
|
893
897
|
interval_seconds INTEGER NOT NULL DEFAULT 300,
|
|
894
|
-
timeout_ms INTEGER NOT NULL DEFAULT
|
|
898
|
+
timeout_ms INTEGER NOT NULL DEFAULT 10000,
|
|
895
899
|
expected_status TEXT NOT NULL DEFAULT '{"mode":"class","classes":["2xx"]}',
|
|
896
900
|
assertions TEXT NOT NULL DEFAULT '[]',
|
|
897
901
|
follow_redirects BOOLEAN NOT NULL DEFAULT true,
|
|
@@ -921,7 +925,7 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
921
925
|
request_headers TEXT NOT NULL DEFAULT '{}',
|
|
922
926
|
request_body TEXT,
|
|
923
927
|
interval_seconds INTEGER NOT NULL DEFAULT 300,
|
|
924
|
-
timeout_ms INTEGER NOT NULL DEFAULT
|
|
928
|
+
timeout_ms INTEGER NOT NULL DEFAULT 10000,
|
|
925
929
|
expected_status TEXT NOT NULL DEFAULT '{"mode":"class","classes":["2xx"]}',
|
|
926
930
|
assertions TEXT NOT NULL DEFAULT '[]',
|
|
927
931
|
follow_redirects INTEGER NOT NULL DEFAULT 1,
|
|
@@ -1124,6 +1128,112 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
1124
1128
|
name: "uptime-monitors-webhook-url",
|
|
1125
1129
|
sql: `ALTER TABLE monitors ADD COLUMN IF NOT EXISTS webhook_url TEXT`,
|
|
1126
1130
|
},
|
|
1131
|
+
{
|
|
1132
|
+
version: 108,
|
|
1133
|
+
name: "analytics-alert-rules-slack-webhook-url",
|
|
1134
|
+
sql: `ALTER TABLE analytics_alert_rules ADD COLUMN IF NOT EXISTS slack_webhook_url TEXT`,
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
version: 109,
|
|
1138
|
+
name: "analytics-alert-rules-webhook-url",
|
|
1139
|
+
sql: `ALTER TABLE analytics_alert_rules ADD COLUMN IF NOT EXISTS webhook_url TEXT`,
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
version: 110,
|
|
1143
|
+
name: "dashboards-updated-by",
|
|
1144
|
+
sql: `ALTER TABLE dashboards ADD COLUMN IF NOT EXISTS updated_by TEXT`,
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
version: 111,
|
|
1148
|
+
name: "dashboard-revisions-table",
|
|
1149
|
+
sql: {
|
|
1150
|
+
postgres: `CREATE TABLE IF NOT EXISTS dashboard_revisions (
|
|
1151
|
+
id TEXT PRIMARY KEY,
|
|
1152
|
+
dashboard_id TEXT NOT NULL,
|
|
1153
|
+
kind TEXT NOT NULL,
|
|
1154
|
+
title TEXT NOT NULL,
|
|
1155
|
+
config TEXT NOT NULL,
|
|
1156
|
+
created_at TEXT NOT NULL DEFAULT (now()::text),
|
|
1157
|
+
created_by TEXT,
|
|
1158
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1159
|
+
org_id TEXT
|
|
1160
|
+
)`,
|
|
1161
|
+
sqlite: `CREATE TABLE IF NOT EXISTS dashboard_revisions (
|
|
1162
|
+
id TEXT PRIMARY KEY,
|
|
1163
|
+
dashboard_id TEXT NOT NULL,
|
|
1164
|
+
kind TEXT NOT NULL,
|
|
1165
|
+
title TEXT NOT NULL,
|
|
1166
|
+
config TEXT NOT NULL,
|
|
1167
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1168
|
+
created_by TEXT,
|
|
1169
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1170
|
+
org_id TEXT
|
|
1171
|
+
)`,
|
|
1172
|
+
},
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
version: 112,
|
|
1176
|
+
name: "dashboard-revisions-dashboard-created-idx",
|
|
1177
|
+
sql: `CREATE INDEX IF NOT EXISTS dashboard_revisions_dashboard_created_idx ON dashboard_revisions (dashboard_id, created_at)`,
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
version: 113,
|
|
1181
|
+
name: "analysis-revisions-table",
|
|
1182
|
+
sql: {
|
|
1183
|
+
postgres: `CREATE TABLE IF NOT EXISTS analysis_revisions (
|
|
1184
|
+
id TEXT PRIMARY KEY,
|
|
1185
|
+
analysis_id TEXT NOT NULL,
|
|
1186
|
+
name TEXT NOT NULL,
|
|
1187
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1188
|
+
question TEXT NOT NULL DEFAULT '',
|
|
1189
|
+
instructions TEXT NOT NULL DEFAULT '',
|
|
1190
|
+
data_sources TEXT NOT NULL DEFAULT '[]',
|
|
1191
|
+
result_markdown TEXT NOT NULL DEFAULT '',
|
|
1192
|
+
result_data TEXT,
|
|
1193
|
+
created_at TEXT NOT NULL DEFAULT (now()::text),
|
|
1194
|
+
created_by TEXT,
|
|
1195
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1196
|
+
org_id TEXT
|
|
1197
|
+
)`,
|
|
1198
|
+
sqlite: `CREATE TABLE IF NOT EXISTS analysis_revisions (
|
|
1199
|
+
id TEXT PRIMARY KEY,
|
|
1200
|
+
analysis_id TEXT NOT NULL,
|
|
1201
|
+
name TEXT NOT NULL,
|
|
1202
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1203
|
+
question TEXT NOT NULL DEFAULT '',
|
|
1204
|
+
instructions TEXT NOT NULL DEFAULT '',
|
|
1205
|
+
data_sources TEXT NOT NULL DEFAULT '[]',
|
|
1206
|
+
result_markdown TEXT NOT NULL DEFAULT '',
|
|
1207
|
+
result_data TEXT,
|
|
1208
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1209
|
+
created_by TEXT,
|
|
1210
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1211
|
+
org_id TEXT
|
|
1212
|
+
)`,
|
|
1213
|
+
},
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
version: 114,
|
|
1217
|
+
name: "analysis-revisions-analysis-created-idx",
|
|
1218
|
+
sql: `CREATE INDEX IF NOT EXISTS analysis_revisions_analysis_created_idx ON analysis_revisions (analysis_id, created_at)`,
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
version: 115,
|
|
1222
|
+
name: "uptime-monitors-timeout-10s",
|
|
1223
|
+
sql: {
|
|
1224
|
+
postgres: `
|
|
1225
|
+
ALTER TABLE monitors ALTER COLUMN timeout_ms SET DEFAULT 10000;
|
|
1226
|
+
UPDATE monitors
|
|
1227
|
+
SET timeout_ms = 10000, updated_at = COALESCE(NULLIF(updated_at, ''), now()::text)
|
|
1228
|
+
WHERE timeout_ms IS NULL OR timeout_ms < 10000 OR timeout_ms = 15000
|
|
1229
|
+
`,
|
|
1230
|
+
sqlite: `
|
|
1231
|
+
UPDATE monitors
|
|
1232
|
+
SET timeout_ms = 10000, updated_at = COALESCE(NULLIF(updated_at, ''), datetime('now'))
|
|
1233
|
+
WHERE timeout_ms IS NULL OR timeout_ms < 10000 OR timeout_ms = 15000
|
|
1234
|
+
`,
|
|
1235
|
+
},
|
|
1236
|
+
},
|
|
1127
1237
|
],
|
|
1128
1238
|
{ table: "analytics_migrations" },
|
|
1129
1239
|
);
|
|
@@ -51,6 +51,7 @@ function rowToDashboard(row: any): DashboardRecord {
|
|
|
51
51
|
visibility: row.visibility,
|
|
52
52
|
createdAt: row.createdAt,
|
|
53
53
|
updatedAt: row.updatedAt,
|
|
54
|
+
updatedBy: row.updatedBy ?? null,
|
|
54
55
|
archivedAt: row.archivedAt ?? null,
|
|
55
56
|
hiddenAt: row.hiddenAt ?? null,
|
|
56
57
|
hiddenBy: row.hiddenBy ?? null,
|
|
@@ -377,6 +377,11 @@ Most operations should be actions. You only need custom routes in `server/routes
|
|
|
377
377
|
- **Webhooks** — external services POST to a specific URL
|
|
378
378
|
- **OAuth callbacks** — redirect-based flows that need specific URL patterns
|
|
379
379
|
|
|
380
|
+
When the agent needs a durable image or file URL, call the core `upload-image`
|
|
381
|
+
action or use `uploadFile()` in server code. Do not write base64 into SQL,
|
|
382
|
+
markdown, deck/design JSON, or action results. `_agentImages` on action results
|
|
383
|
+
is for ephemeral vision previews only, not persistence.
|
|
384
|
+
|
|
380
385
|
If it's a standard CRUD operation, data query, or a wrapper around an action, use the action instead.
|
|
381
386
|
|
|
382
387
|
## Legacy Pattern (bare export)
|
|
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
50
50
|
|
|
51
51
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
52
52
|
|
|
53
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback
|
|
53
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
|
|
54
54
|
|
|
55
55
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
56
56
|
|
|
@@ -51,6 +51,13 @@ export default defineAction({
|
|
|
51
51
|
|
|
52
52
|
The legacy `parameters:` field (plain JSON Schema) has no runtime validation — do not use it for new code.
|
|
53
53
|
|
|
54
|
+
## Large Payloads
|
|
55
|
+
|
|
56
|
+
Do not accept or persist unbounded base64/file blobs through actions, SQL writes,
|
|
57
|
+
or `application_state`. Route uploads through the file-upload provider and store
|
|
58
|
+
references. This prevents database bloat, slow hot paths, and accidental secret
|
|
59
|
+
or customer-data embedding inside binary payloads.
|
|
60
|
+
|
|
54
61
|
## SQL Injection
|
|
55
62
|
|
|
56
63
|
Never concatenate user input into SQL strings. Use Drizzle ORM's query builder (always safe) or parameterized queries:
|
|
@@ -13,7 +13,9 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
## Rule
|
|
15
15
|
|
|
16
|
-
All application data lives in **SQL** (SQLite locally, persistent database in production). The agent and UI share the same database. Do not store durable app data in the filesystem unless the app is explicitly running a Local File Mode artifact flow described below.
|
|
16
|
+
All application data lives in **SQL** (SQLite locally, persistent database in production). The agent and UI share the same database. SQL stores structured records, metadata, references, and searchable text — not large raw file payloads. Do not store durable app data in the filesystem unless the app is explicitly running a Local File Mode artifact flow described below.
|
|
17
|
+
|
|
18
|
+
Large binary or file-like payloads (images, video/audio, PDFs, ZIPs, screenshots, session replay chunks, thumbnails, generated assets, `data:` URLs, and base64 file bodies) must go through configured file/blob storage such as `uploadFile()` or `putPrivateBlob()`. Persist only the returned URL, asset id, or opaque blob handle in SQL. If storage is unavailable in hosted or persistent-database mode, fail closed with setup guidance instead of falling back to base64 in `application_state`, `settings`, `resources`, or app tables. Local SQLite-only dev fallbacks may exist for tiny assets, but they must be capped, documented as dev-only, and kept off hot list/read paths.
|
|
17
19
|
|
|
18
20
|
**Local File Mode exception:** some artifact apps (Content, Plans, Slides, Dashboards, Designs, etc.) can intentionally use repo files as the source of truth for the artifact itself. This must be explicit via `agent-native.json`, `AGENT_NATIVE_MODE=local-files`, or an app-owned local-file action helper. In that mode, the UI and agent still go through app actions, but those actions read/write scoped files through `@agent-native/core/local-artifacts` instead of SQL rows. App state, auth, settings, credentials, collaboration metadata, and hosted database mode remain SQL. File-to-database or file-to-provider synchronization is an explicit sync step, not an implicit side effect of editing.
|
|
19
21
|
|
|
@@ -136,6 +138,7 @@ Polling streams database changes to the UI. When the agent writes to the databas
|
|
|
136
138
|
- Use the `settings` store for app configuration and user preferences
|
|
137
139
|
- Use `application-state` for ephemeral UI state that the agent and UI share
|
|
138
140
|
- Use `oauth-tokens` for OAuth credentials
|
|
141
|
+
- Use `uploadFile()` or `putPrivateBlob()` for large files/blob data and store only URLs, ids, or handles in SQL
|
|
139
142
|
- Use core DB scripts (`db-schema`, `db-query`, `db-exec`, `db-patch`) for ad-hoc database operations
|
|
140
143
|
- Use `db-exec --statements` instead of several separate `db-exec` calls for related writes; it is faster and rolls back the whole batch if one statement fails
|
|
141
144
|
- Reach for `db-patch` instead of `db-exec UPDATE` whenever you're making a small change to a large text/JSON column — it's much cheaper on tokens
|
|
@@ -146,6 +149,7 @@ Polling streams database changes to the UI. When the agent writes to the databas
|
|
|
146
149
|
- Don't store app state in localStorage, sessionStorage, or cookies (except for UI-only preferences like sidebar width)
|
|
147
150
|
- Don't keep state only in memory (server variables, global stores)
|
|
148
151
|
- Don't use Redis or any external state store for app data
|
|
152
|
+
- Don't store large files, base64 blobs, `data:` URLs, screenshots, videos, audio, PDFs, ZIPs, or session replay chunks directly in SQL rows, `application_state`, `settings`, or `resources`
|
|
149
153
|
- Don't implement product features with raw SQL or `getDbExec()` when Drizzle can express the query
|
|
150
154
|
- Don't write SQLite-only or Postgres-only SQL in app code
|
|
151
155
|
- Don't interpolate user input directly into SQL queries — use Drizzle ORM's query builder
|
|
@@ -10,6 +10,10 @@ Detailed library, generation, image, embed, and engine rules live in
|
|
|
10
10
|
|
|
11
11
|
## Core Rules
|
|
12
12
|
|
|
13
|
+
- Store large file/blob payloads in configured file/blob storage, not SQL: no
|
|
14
|
+
base64, `data:` URLs, images, video/audio, PDFs, ZIPs, screenshots,
|
|
15
|
+
thumbnails, or replay chunks in app tables, `application_state`, `settings`,
|
|
16
|
+
or `resources`; persist URLs, ids, or handles instead.
|
|
13
17
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
14
18
|
- Use actions for asset lifecycle, generation, library organization, uploads,
|
|
15
19
|
embeds, notifications, progress, sharing, and collaboration. Do not bypass
|
|
@@ -142,7 +142,7 @@ export default function SettingsPage() {
|
|
|
142
142
|
</div>
|
|
143
143
|
}
|
|
144
144
|
team={
|
|
145
|
-
<div className="mx-auto w-full max-w-
|
|
145
|
+
<div className="mx-auto w-full max-w-3xl">
|
|
146
146
|
<TeamPage
|
|
147
147
|
showTitle={false}
|
|
148
148
|
createOrgDescription={t("team.createOrgDescription")}
|
|
@@ -377,6 +377,11 @@ Most operations should be actions. You only need custom routes in `server/routes
|
|
|
377
377
|
- **Webhooks** — external services POST to a specific URL
|
|
378
378
|
- **OAuth callbacks** — redirect-based flows that need specific URL patterns
|
|
379
379
|
|
|
380
|
+
When the agent needs a durable image or file URL, call the core `upload-image`
|
|
381
|
+
action or use `uploadFile()` in server code. Do not write base64 into SQL,
|
|
382
|
+
markdown, deck/design JSON, or action results. `_agentImages` on action results
|
|
383
|
+
is for ephemeral vision previews only, not persistence.
|
|
384
|
+
|
|
380
385
|
If it's a standard CRUD operation, data query, or a wrapper around an action, use the action instead.
|
|
381
386
|
|
|
382
387
|
## Legacy Pattern (bare export)
|
|
@@ -74,6 +74,11 @@ Builder/internal data, or customer data in the action, UI, seed data, fixtures,
|
|
|
74
74
|
docs, prompts, or generated extension/app content. Register required secrets,
|
|
75
75
|
use OAuth helpers, or read scoped values from the vault/credential store.
|
|
76
76
|
|
|
77
|
+
If the feature involves attachments, images, recordings, screenshots, exports,
|
|
78
|
+
or other file-like payloads, design the upload path in the same change:
|
|
79
|
+
provider upload first, then URL/id/blob handle in SQL. Do not add base64/binary
|
|
80
|
+
columns or stuff files into `application_state`.
|
|
81
|
+
|
|
77
82
|
**If the action produces or lists a navigable resource**, add a `link` builder that returns `{ url: buildDeepLink({ app, view, params }), label }`. External coding agents and MCP hosts (Claude / ChatGPT / Claude Code / Cowork / Codex, over MCP/A2A) then surface an "Open in … →" deep link that drops the user back into the running UI focused on the record — for free. If a compatible MCP host should render an inline review/edit surface, also add `mcpApp` with `embedApp()` so the action embeds the real React app route instead of a one-off HTML UI. The `link` builder and `mcpApp` metadata must be pure and synchronous (no I/O). Any external-agent read/ingest action must be `http: { method: "GET" }` + `readOnly: true` + `publicAgent: { expose: true, readOnly: true, requiresAuth: true }`. See the `external-agents` skill.
|
|
78
83
|
|
|
79
84
|
### 3. Skills / Instructions
|
|
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
50
50
|
|
|
51
51
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
52
52
|
|
|
53
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback
|
|
53
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
|
|
54
54
|
|
|
55
55
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
56
56
|
|
|
@@ -51,6 +51,13 @@ export default defineAction({
|
|
|
51
51
|
|
|
52
52
|
The legacy `parameters:` field (plain JSON Schema) has no runtime validation — do not use it for new code.
|
|
53
53
|
|
|
54
|
+
## Large Payloads
|
|
55
|
+
|
|
56
|
+
Do not accept or persist unbounded base64/file blobs through actions, SQL writes,
|
|
57
|
+
or `application_state`. Route uploads through the file-upload provider and store
|
|
58
|
+
references. This prevents database bloat, slow hot paths, and accidental secret
|
|
59
|
+
or customer-data embedding inside binary payloads.
|
|
60
|
+
|
|
54
61
|
## SQL Injection
|
|
55
62
|
|
|
56
63
|
Never concatenate user input into SQL strings. Use Drizzle ORM's query builder (always safe) or parameterized queries:
|
|
@@ -9,6 +9,10 @@ Detailed retrieval, capture, connector, and feature rules live in
|
|
|
9
9
|
|
|
10
10
|
## Core Rules
|
|
11
11
|
|
|
12
|
+
- Store large file/blob payloads in configured file/blob storage, not SQL: no
|
|
13
|
+
base64, `data:` URLs, images, video/audio, PDFs, ZIPs, screenshots,
|
|
14
|
+
thumbnails, or replay chunks in app tables, `application_state`, `settings`,
|
|
15
|
+
or `resources`; persist URLs, ids, or handles instead.
|
|
12
16
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
13
17
|
- Use Brain actions for ingestion, search, retrieval, distillation, capture,
|
|
14
18
|
review, and connector work. Do not bypass access checks or ownable scopes.
|
|
@@ -377,6 +377,11 @@ Most operations should be actions. You only need custom routes in `server/routes
|
|
|
377
377
|
- **Webhooks** — external services POST to a specific URL
|
|
378
378
|
- **OAuth callbacks** — redirect-based flows that need specific URL patterns
|
|
379
379
|
|
|
380
|
+
When the agent needs a durable image or file URL, call the core `upload-image`
|
|
381
|
+
action or use `uploadFile()` in server code. Do not write base64 into SQL,
|
|
382
|
+
markdown, deck/design JSON, or action results. `_agentImages` on action results
|
|
383
|
+
is for ephemeral vision previews only, not persistence.
|
|
384
|
+
|
|
380
385
|
If it's a standard CRUD operation, data query, or a wrapper around an action, use the action instead.
|
|
381
386
|
|
|
382
387
|
## Legacy Pattern (bare export)
|
|
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
50
50
|
|
|
51
51
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
52
52
|
|
|
53
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback
|
|
53
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
|
|
54
54
|
|
|
55
55
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
56
56
|
|
|
@@ -51,6 +51,13 @@ export default defineAction({
|
|
|
51
51
|
|
|
52
52
|
The legacy `parameters:` field (plain JSON Schema) has no runtime validation — do not use it for new code.
|
|
53
53
|
|
|
54
|
+
## Large Payloads
|
|
55
|
+
|
|
56
|
+
Do not accept or persist unbounded base64/file blobs through actions, SQL writes,
|
|
57
|
+
or `application_state`. Route uploads through the file-upload provider and store
|
|
58
|
+
references. This prevents database bloat, slow hot paths, and accidental secret
|
|
59
|
+
or customer-data embedding inside binary payloads.
|
|
60
|
+
|
|
54
61
|
## SQL Injection
|
|
55
62
|
|
|
56
63
|
Never concatenate user input into SQL strings. Use Drizzle ORM's query builder (always safe) or parameterized queries:
|
|
@@ -13,7 +13,9 @@ metadata:
|
|
|
13
13
|
|
|
14
14
|
## Rule
|
|
15
15
|
|
|
16
|
-
All application data lives in **SQL** (SQLite locally, persistent database in production). The agent and UI share the same database. Do not store durable app data in the filesystem unless the app is explicitly running a Local File Mode artifact flow described below.
|
|
16
|
+
All application data lives in **SQL** (SQLite locally, persistent database in production). The agent and UI share the same database. SQL stores structured records, metadata, references, and searchable text — not large raw file payloads. Do not store durable app data in the filesystem unless the app is explicitly running a Local File Mode artifact flow described below.
|
|
17
|
+
|
|
18
|
+
Large binary or file-like payloads (images, video/audio, PDFs, ZIPs, screenshots, session replay chunks, thumbnails, generated assets, `data:` URLs, and base64 file bodies) must go through configured file/blob storage such as `uploadFile()` or `putPrivateBlob()`. Persist only the returned URL, asset id, or opaque blob handle in SQL. If storage is unavailable in hosted or persistent-database mode, fail closed with setup guidance instead of falling back to base64 in `application_state`, `settings`, `resources`, or app tables. Local SQLite-only dev fallbacks may exist for tiny assets, but they must be capped, documented as dev-only, and kept off hot list/read paths.
|
|
17
19
|
|
|
18
20
|
**Local File Mode exception:** some artifact apps (Content, Plans, Slides, Dashboards, Designs, etc.) can intentionally use repo files as the source of truth for the artifact itself. This must be explicit via `agent-native.json`, `AGENT_NATIVE_MODE=local-files`, or an app-owned local-file action helper. In that mode, the UI and agent still go through app actions, but those actions read/write scoped files through `@agent-native/core/local-artifacts` instead of SQL rows. App state, auth, settings, credentials, collaboration metadata, and hosted database mode remain SQL. File-to-database or file-to-provider synchronization is an explicit sync step, not an implicit side effect of editing.
|
|
19
21
|
|
|
@@ -136,6 +138,7 @@ Polling streams database changes to the UI. When the agent writes to the databas
|
|
|
136
138
|
- Use the `settings` store for app configuration and user preferences
|
|
137
139
|
- Use `application-state` for ephemeral UI state that the agent and UI share
|
|
138
140
|
- Use `oauth-tokens` for OAuth credentials
|
|
141
|
+
- Use `uploadFile()` or `putPrivateBlob()` for large files/blob data and store only URLs, ids, or handles in SQL
|
|
139
142
|
- Use core DB scripts (`db-schema`, `db-query`, `db-exec`, `db-patch`) for ad-hoc database operations
|
|
140
143
|
- Use `db-exec --statements` instead of several separate `db-exec` calls for related writes; it is faster and rolls back the whole batch if one statement fails
|
|
141
144
|
- Reach for `db-patch` instead of `db-exec UPDATE` whenever you're making a small change to a large text/JSON column — it's much cheaper on tokens
|
|
@@ -146,6 +149,7 @@ Polling streams database changes to the UI. When the agent writes to the databas
|
|
|
146
149
|
- Don't store app state in localStorage, sessionStorage, or cookies (except for UI-only preferences like sidebar width)
|
|
147
150
|
- Don't keep state only in memory (server variables, global stores)
|
|
148
151
|
- Don't use Redis or any external state store for app data
|
|
152
|
+
- Don't store large files, base64 blobs, `data:` URLs, screenshots, videos, audio, PDFs, ZIPs, or session replay chunks directly in SQL rows, `application_state`, `settings`, or `resources`
|
|
149
153
|
- Don't implement product features with raw SQL or `getDbExec()` when Drizzle can express the query
|
|
150
154
|
- Don't write SQLite-only or Postgres-only SQL in app code
|
|
151
155
|
- Don't interpolate user input directly into SQL queries — use Drizzle ORM's query builder
|
|
@@ -9,6 +9,10 @@ Detailed event, availability, booking, storage, and UI rules live in
|
|
|
9
9
|
|
|
10
10
|
## Core Rules
|
|
11
11
|
|
|
12
|
+
- Store large file/blob payloads in configured file/blob storage, not SQL: no
|
|
13
|
+
base64, `data:` URLs, images, video/audio, PDFs, ZIPs, screenshots,
|
|
14
|
+
thumbnails, or replay chunks in app tables, `application_state`, `settings`,
|
|
15
|
+
or `resources`; persist URLs, ids, or handles instead.
|
|
12
16
|
- Never hardcode API keys, tokens, webhook URLs, signing secrets, private Builder/internal data, customer data, or credential-looking literals. Use secrets/OAuth/runtime configuration and obvious placeholders in examples.
|
|
13
17
|
- Use actions for events, availability, booking links, settings, navigation,
|
|
14
18
|
Google Calendar connection, and sharing. Do not bypass app access checks.
|
|
@@ -29,7 +29,10 @@ import {
|
|
|
29
29
|
type ViewPreferences,
|
|
30
30
|
} from "@/hooks/use-view-preferences";
|
|
31
31
|
import { getEventDisplayColor, allOtherDeclined } from "@/lib/event-colors";
|
|
32
|
-
import {
|
|
32
|
+
import {
|
|
33
|
+
shouldSuppressAfterPopoverClose,
|
|
34
|
+
shouldSuppressCreatePointerDown,
|
|
35
|
+
} from "@/lib/popover-click-guard";
|
|
33
36
|
import { EventStatusIcon } from "@/lib/rsvp-status";
|
|
34
37
|
import { cn } from "@/lib/utils";
|
|
35
38
|
|
|
@@ -814,11 +817,17 @@ export const DayView = memo(function DayView({
|
|
|
814
817
|
|
|
815
818
|
{/* Positioned events overlay */}
|
|
816
819
|
<div
|
|
820
|
+
data-calendar-create-surface="true"
|
|
817
821
|
className="absolute inset-0 ml-[40px] mr-2 sm:ml-[56px] sm:mr-4"
|
|
818
822
|
onPointerDown={(e) => {
|
|
819
823
|
// Only start a create-drag from empty space, not on an event or its resize handles
|
|
820
824
|
if ((e.target as HTMLElement).closest("button")) return;
|
|
821
|
-
if (
|
|
825
|
+
if (
|
|
826
|
+
!onClickTimeSlot ||
|
|
827
|
+
e.button !== 0 ||
|
|
828
|
+
shouldSuppressCreatePointerDown()
|
|
829
|
+
)
|
|
830
|
+
return;
|
|
822
831
|
startCreateDrag(e, 0);
|
|
823
832
|
}}
|
|
824
833
|
onClick={(e) => {
|
|
@@ -91,7 +91,11 @@ import {
|
|
|
91
91
|
type ReminderMode,
|
|
92
92
|
validateAttachmentDrafts,
|
|
93
93
|
} from "@/lib/event-form-utils";
|
|
94
|
-
import {
|
|
94
|
+
import {
|
|
95
|
+
createEventDetailPopoverToken,
|
|
96
|
+
markPopoverInteractOutside,
|
|
97
|
+
setEventDetailPopoverOpen,
|
|
98
|
+
} from "@/lib/popover-click-guard";
|
|
95
99
|
import { shortcutModifierLabel } from "@/lib/utils";
|
|
96
100
|
|
|
97
101
|
const ZOOM_AFTER_CONNECT_EVENT_ID_KEY = "calendar.zoomAfterConnectEventId";
|
|
@@ -431,6 +435,10 @@ export function EventDetailPopover({
|
|
|
431
435
|
const [isEditingTitle, setIsEditingTitle] = useState(defaultOpen);
|
|
432
436
|
const isNewEventRef = useRef(defaultOpen);
|
|
433
437
|
const titleInputRef = useRef<HTMLInputElement>(null);
|
|
438
|
+
const popoverTokenRef = useRef<symbol | null>(null);
|
|
439
|
+
if (!popoverTokenRef.current) {
|
|
440
|
+
popoverTokenRef.current = createEventDetailPopoverToken();
|
|
441
|
+
}
|
|
434
442
|
const {
|
|
435
443
|
eventDetailSidebar,
|
|
436
444
|
setEventDetailSidebar,
|
|
@@ -1218,13 +1226,18 @@ export function EventDetailPopover({
|
|
|
1218
1226
|
],
|
|
1219
1227
|
);
|
|
1220
1228
|
|
|
1229
|
+
const popoverOpen =
|
|
1230
|
+
eventDetailSidebar && !isNewEventRef.current && !isDraft ? false : open;
|
|
1231
|
+
|
|
1232
|
+
useEffect(() => {
|
|
1233
|
+
const token = popoverTokenRef.current;
|
|
1234
|
+
if (!token) return;
|
|
1235
|
+
setEventDetailPopoverOpen(token, popoverOpen);
|
|
1236
|
+
return () => setEventDetailPopoverOpen(token, false);
|
|
1237
|
+
}, [popoverOpen]);
|
|
1238
|
+
|
|
1221
1239
|
return (
|
|
1222
|
-
<Popover
|
|
1223
|
-
open={
|
|
1224
|
-
eventDetailSidebar && !isNewEventRef.current && !isDraft ? false : open
|
|
1225
|
-
}
|
|
1226
|
-
onOpenChange={handleOpenChange}
|
|
1227
|
-
>
|
|
1240
|
+
<Popover open={popoverOpen} onOpenChange={handleOpenChange}>
|
|
1228
1241
|
<PopoverTrigger asChild onClick={handleTriggerClick}>
|
|
1229
1242
|
{children}
|
|
1230
1243
|
</PopoverTrigger>
|
|
@@ -1256,7 +1269,7 @@ export function EventDetailPopover({
|
|
|
1256
1269
|
return;
|
|
1257
1270
|
}
|
|
1258
1271
|
// Mark that a popover was dismissed so the grid suppresses time-slot creation
|
|
1259
|
-
markPopoverInteractOutside();
|
|
1272
|
+
markPopoverInteractOutside(e.target);
|
|
1260
1273
|
}}
|
|
1261
1274
|
>
|
|
1262
1275
|
<TooltipProvider>
|
|
@@ -17,6 +17,7 @@ import { memo, useState, useMemo } from "react";
|
|
|
17
17
|
|
|
18
18
|
import { useIsMobile } from "@/hooks/use-mobile";
|
|
19
19
|
import { useViewPreferences } from "@/hooks/use-view-preferences";
|
|
20
|
+
import { shouldSuppressAfterPopoverClose } from "@/lib/popover-click-guard";
|
|
20
21
|
import { cn } from "@/lib/utils";
|
|
21
22
|
|
|
22
23
|
import { EventCard } from "./EventCard";
|
|
@@ -185,7 +186,12 @@ export const MonthView = memo(function MonthView({
|
|
|
185
186
|
return (
|
|
186
187
|
<div
|
|
187
188
|
key={dayKey}
|
|
188
|
-
|
|
189
|
+
data-calendar-create-surface="true"
|
|
190
|
+
onClick={(e) => {
|
|
191
|
+
if ((e.target as HTMLElement).closest("button")) return;
|
|
192
|
+
if (shouldSuppressAfterPopoverClose()) return;
|
|
193
|
+
onDateSelect(day);
|
|
194
|
+
}}
|
|
189
195
|
onDragOver={(e) => handleDragOver(e, dayKey)}
|
|
190
196
|
onDragEnter={(e) => {
|
|
191
197
|
e.preventDefault();
|
|
@@ -32,7 +32,10 @@ import {
|
|
|
32
32
|
type ViewPreferences,
|
|
33
33
|
} from "@/hooks/use-view-preferences";
|
|
34
34
|
import { getEventDisplayColor, allOtherDeclined } from "@/lib/event-colors";
|
|
35
|
-
import {
|
|
35
|
+
import {
|
|
36
|
+
shouldSuppressAfterPopoverClose,
|
|
37
|
+
shouldSuppressCreatePointerDown,
|
|
38
|
+
} from "@/lib/popover-click-guard";
|
|
36
39
|
import { EventStatusIcon } from "@/lib/rsvp-status";
|
|
37
40
|
import { cn } from "@/lib/utils";
|
|
38
41
|
|
|
@@ -1143,6 +1146,7 @@ export const WeekView = memo(function WeekView({
|
|
|
1143
1146
|
return (
|
|
1144
1147
|
<div
|
|
1145
1148
|
key={day.toISOString()}
|
|
1149
|
+
data-calendar-create-surface="true"
|
|
1146
1150
|
className={cn(
|
|
1147
1151
|
"relative flex-1 border-r border-border last:border-r-0",
|
|
1148
1152
|
isCurrentDay && "bg-primary/[0.02]",
|
|
@@ -1150,7 +1154,12 @@ export const WeekView = memo(function WeekView({
|
|
|
1150
1154
|
onPointerDown={(e) => {
|
|
1151
1155
|
// Only start a create-drag from empty space, not on an event or its resize handles
|
|
1152
1156
|
if ((e.target as HTMLElement).closest("button")) return;
|
|
1153
|
-
if (
|
|
1157
|
+
if (
|
|
1158
|
+
!onClickTimeSlot ||
|
|
1159
|
+
e.button !== 0 ||
|
|
1160
|
+
shouldSuppressCreatePointerDown()
|
|
1161
|
+
)
|
|
1162
|
+
return;
|
|
1154
1163
|
startCreateDrag(e, dayIndex);
|
|
1155
1164
|
}}
|
|
1156
1165
|
onClick={(e) => {
|
|
@@ -1,13 +1,57 @@
|
|
|
1
|
+
const CALENDAR_CREATE_SURFACE_SELECTOR =
|
|
2
|
+
"[data-calendar-create-surface='true']";
|
|
3
|
+
const OUTSIDE_CLICK_SUPPRESSION_MS = 500;
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
|
-
* Tracks
|
|
3
|
-
*
|
|
6
|
+
* Tracks event detail popovers and outside clicks into empty calendar space.
|
|
7
|
+
* Calendar views use this to turn the first empty-space click into a dismiss
|
|
8
|
+
* action instead of creating a new event underneath the closing popover.
|
|
4
9
|
*/
|
|
10
|
+
const openPopoverTokens = new Set<symbol>();
|
|
5
11
|
let popoverInteractOutsideAt = 0;
|
|
12
|
+
let pendingEmptySpaceSuppression = false;
|
|
13
|
+
|
|
14
|
+
function isCalendarCreateSurfaceTarget(target: EventTarget | null) {
|
|
15
|
+
if (typeof Element === "undefined") return false;
|
|
16
|
+
if (!(target instanceof Element)) return false;
|
|
17
|
+
if (target.closest("button")) return false;
|
|
18
|
+
return !!target.closest(CALENDAR_CREATE_SURFACE_SELECTOR);
|
|
19
|
+
}
|
|
6
20
|
|
|
7
|
-
|
|
21
|
+
function hasPendingEmptySpaceSuppression() {
|
|
22
|
+
if (!pendingEmptySpaceSuppression) return false;
|
|
23
|
+
if (Date.now() - popoverInteractOutsideAt > OUTSIDE_CLICK_SUPPRESSION_MS) {
|
|
24
|
+
pendingEmptySpaceSuppression = false;
|
|
25
|
+
return false;
|
|
26
|
+
}
|
|
27
|
+
return true;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export function createEventDetailPopoverToken() {
|
|
31
|
+
return Symbol("event-detail-popover");
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export function setEventDetailPopoverOpen(token: symbol, open: boolean) {
|
|
35
|
+
if (open) {
|
|
36
|
+
openPopoverTokens.add(token);
|
|
37
|
+
} else {
|
|
38
|
+
openPopoverTokens.delete(token);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export function markPopoverInteractOutside(target: EventTarget | null) {
|
|
43
|
+
if (!isCalendarCreateSurfaceTarget(target)) return;
|
|
8
44
|
popoverInteractOutsideAt = Date.now();
|
|
45
|
+
pendingEmptySpaceSuppression = true;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function shouldSuppressCreatePointerDown() {
|
|
49
|
+
return openPopoverTokens.size > 0 || hasPendingEmptySpaceSuppression();
|
|
9
50
|
}
|
|
10
51
|
|
|
11
52
|
export function shouldSuppressAfterPopoverClose() {
|
|
12
|
-
|
|
53
|
+
if (openPopoverTokens.size > 0) return true;
|
|
54
|
+
if (!hasPendingEmptySpaceSuppression()) return false;
|
|
55
|
+
pendingEmptySpaceSuppression = false;
|
|
56
|
+
return true;
|
|
13
57
|
}
|
|
@@ -486,7 +486,7 @@ export default function Settings() {
|
|
|
486
486
|
</div>
|
|
487
487
|
}
|
|
488
488
|
team={
|
|
489
|
-
<div className="mx-auto w-full max-w-
|
|
489
|
+
<div className="mx-auto w-full max-w-3xl">
|
|
490
490
|
<TeamPage
|
|
491
491
|
showTitle={false}
|
|
492
492
|
createOrgDescription="Set up a team to share calendars and booking links with your colleagues."
|