@agent-native/core 0.92.0 → 0.92.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/store.ts +12 -1
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +29 -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/notifications/channels.ts +1 -22
- 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-emails-now-show-the-message-without-raw-metadata-json.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-checks-no-longer-count-ssrf-setup-time-against-the-re.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-checks-reuse-one-keep-alive-connection-so-recorded-la.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-confirm-transient-timeout-failures-befor.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/docs/uptime-monitoring.md +30 -18
- 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 +244 -33
- 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/cli/sync-builder-starter-manifest.d.ts.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.js +24 -1
- package/dist/cli/sync-builder-starter-manifest.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/channels.js +1 -17
- package/dist/notifications/channels.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +5 -0
- 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/agent-engine-api-key-route.d.ts +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
|
@@ -23,7 +23,8 @@ import {
|
|
|
23
23
|
type ActionChangeTarget,
|
|
24
24
|
} from "../action-change-marker.js";
|
|
25
25
|
import { getAppStateEmitter } from "../application-state/emitter.js";
|
|
26
|
-
import { getDbExec } from "../db/client.js";
|
|
26
|
+
import { getDbExec, isPostgres } from "../db/client.js";
|
|
27
|
+
import { ensureIndexExists, ensureTableExists } from "../db/ddl-guard.js";
|
|
27
28
|
import {
|
|
28
29
|
EXTENSION_CHANGE_MARKER_KEY,
|
|
29
30
|
parseExtensionChangeMarker,
|
|
@@ -66,11 +67,17 @@ export interface ChangeEvent {
|
|
|
66
67
|
// In-memory ring buffer of recent changes. Kept small since clients
|
|
67
68
|
// poll frequently (every 2-3s) and only need events since their last poll.
|
|
68
69
|
const MAX_BUFFER = 200;
|
|
70
|
+
const DURABLE_READ_LIMIT = 1000;
|
|
71
|
+
const DURABLE_RETENTION_MS = 24 * 60 * 60 * 1000;
|
|
72
|
+
const LEGACY_DB_CHECK_INTERVAL_MS = 1000;
|
|
73
|
+
const DURABLE_LEGACY_DB_CHECK_INTERVAL_MS = 30_000;
|
|
69
74
|
let _version = 0;
|
|
70
75
|
const _buffer: ChangeEvent[] = [];
|
|
71
76
|
export const POLL_CHANGE_EVENT = "poll-change";
|
|
72
77
|
const _pollEmitter = new EventEmitter();
|
|
73
78
|
_pollEmitter.setMaxListeners(0);
|
|
79
|
+
let _syncEventsInitPromise: Promise<boolean> | undefined;
|
|
80
|
+
let _lastDurablePrune = 0;
|
|
74
81
|
|
|
75
82
|
/**
|
|
76
83
|
* Whether we've seeded _version from the DB. In serverless (Netlify,
|
|
@@ -144,6 +151,130 @@ function sqlWatermarkValue(value: unknown): string | number | undefined {
|
|
|
144
151
|
return undefined;
|
|
145
152
|
}
|
|
146
153
|
|
|
154
|
+
function syncEventsDisabled(): boolean {
|
|
155
|
+
return (
|
|
156
|
+
process.env.AGENT_NATIVE_SYNC_EVENTS_DISABLE === "1" ||
|
|
157
|
+
(process.env.VITEST === "true" &&
|
|
158
|
+
process.env.AGENT_NATIVE_SYNC_EVENTS_ENABLE_IN_TESTS !== "1")
|
|
159
|
+
);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function ensureSyncEventsTable(): Promise<boolean> {
|
|
163
|
+
if (syncEventsDisabled()) return false;
|
|
164
|
+
if (!_syncEventsInitPromise) {
|
|
165
|
+
_syncEventsInitPromise = (async () => {
|
|
166
|
+
const client = getDbExec();
|
|
167
|
+
const createSql = `
|
|
168
|
+
CREATE TABLE IF NOT EXISTS sync_events (
|
|
169
|
+
id TEXT PRIMARY KEY,
|
|
170
|
+
version BIGINT NOT NULL,
|
|
171
|
+
event_json TEXT NOT NULL,
|
|
172
|
+
source TEXT NOT NULL,
|
|
173
|
+
type TEXT NOT NULL,
|
|
174
|
+
event_key TEXT,
|
|
175
|
+
owner TEXT,
|
|
176
|
+
org_id TEXT,
|
|
177
|
+
resource_type TEXT,
|
|
178
|
+
resource_id TEXT,
|
|
179
|
+
created_at BIGINT NOT NULL
|
|
180
|
+
)
|
|
181
|
+
`;
|
|
182
|
+
|
|
183
|
+
if (isPostgres()) {
|
|
184
|
+
await ensureTableExists("sync_events", createSql);
|
|
185
|
+
await ensureIndexExists(
|
|
186
|
+
"sync_events_version_idx",
|
|
187
|
+
"CREATE INDEX IF NOT EXISTS sync_events_version_idx ON sync_events (version)",
|
|
188
|
+
);
|
|
189
|
+
await ensureIndexExists(
|
|
190
|
+
"sync_events_owner_version_idx",
|
|
191
|
+
"CREATE INDEX IF NOT EXISTS sync_events_owner_version_idx ON sync_events (owner, version)",
|
|
192
|
+
);
|
|
193
|
+
await ensureIndexExists(
|
|
194
|
+
"sync_events_org_version_idx",
|
|
195
|
+
"CREATE INDEX IF NOT EXISTS sync_events_org_version_idx ON sync_events (org_id, version)",
|
|
196
|
+
);
|
|
197
|
+
return true;
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
await client.execute(createSql);
|
|
201
|
+
for (const ddl of [
|
|
202
|
+
"CREATE INDEX IF NOT EXISTS sync_events_version_idx ON sync_events (version)",
|
|
203
|
+
"CREATE INDEX IF NOT EXISTS sync_events_owner_version_idx ON sync_events (owner, version)",
|
|
204
|
+
"CREATE INDEX IF NOT EXISTS sync_events_org_version_idx ON sync_events (org_id, version)",
|
|
205
|
+
]) {
|
|
206
|
+
try {
|
|
207
|
+
await client.execute(ddl);
|
|
208
|
+
} catch {
|
|
209
|
+
// Index already exists or the dialect rejected a duplicate.
|
|
210
|
+
}
|
|
211
|
+
}
|
|
212
|
+
return true;
|
|
213
|
+
})().catch(() => {
|
|
214
|
+
_syncEventsInitPromise = undefined;
|
|
215
|
+
return false;
|
|
216
|
+
});
|
|
217
|
+
}
|
|
218
|
+
return _syncEventsInitPromise;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
function durableEventId(version: number): string {
|
|
222
|
+
return `${version}-${Math.random().toString(36).slice(2, 10)}`;
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
async function pruneDurableEvents(client: ReturnType<typeof getDbExec>) {
|
|
226
|
+
const now = Date.now();
|
|
227
|
+
if (now - _lastDurablePrune < 5 * 60 * 1000) return;
|
|
228
|
+
_lastDurablePrune = now;
|
|
229
|
+
await client
|
|
230
|
+
.execute({
|
|
231
|
+
sql: "DELETE FROM sync_events WHERE created_at < ?",
|
|
232
|
+
args: [now - DURABLE_RETENTION_MS],
|
|
233
|
+
})
|
|
234
|
+
.catch(() => {});
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
async function persistSyncEvent(event: ChangeEvent): Promise<void> {
|
|
238
|
+
if (!(await ensureSyncEventsTable())) return;
|
|
239
|
+
const client = getDbExec();
|
|
240
|
+
await client
|
|
241
|
+
.execute({
|
|
242
|
+
sql: isPostgres()
|
|
243
|
+
? `INSERT INTO sync_events (id, version, event_json, source, type, event_key, owner, org_id, resource_type, resource_id, created_at)
|
|
244
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
245
|
+
ON CONFLICT (id) DO NOTHING`
|
|
246
|
+
: `INSERT OR IGNORE INTO sync_events (id, version, event_json, source, type, event_key, owner, org_id, resource_type, resource_id, created_at)
|
|
247
|
+
VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`,
|
|
248
|
+
args: [
|
|
249
|
+
durableEventId(event.version),
|
|
250
|
+
event.version,
|
|
251
|
+
JSON.stringify(event),
|
|
252
|
+
event.source,
|
|
253
|
+
event.type,
|
|
254
|
+
event.key ?? null,
|
|
255
|
+
event.owner ?? null,
|
|
256
|
+
event.orgId ?? null,
|
|
257
|
+
event.resourceType ?? null,
|
|
258
|
+
event.resourceId ?? null,
|
|
259
|
+
Date.now(),
|
|
260
|
+
],
|
|
261
|
+
})
|
|
262
|
+
.catch(() => {});
|
|
263
|
+
await pruneDurableEvents(client);
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
async function readMaxSyncEventVersion(): Promise<number> {
|
|
267
|
+
if (!(await ensureSyncEventsTable())) return 0;
|
|
268
|
+
try {
|
|
269
|
+
const result = await getDbExec().execute(
|
|
270
|
+
"SELECT MAX(version) as max_version FROM sync_events",
|
|
271
|
+
);
|
|
272
|
+
return timestampValue(result.rows[0]?.max_version);
|
|
273
|
+
} catch {
|
|
274
|
+
return 0;
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
147
278
|
async function readMaxUpdatedAtRaw(
|
|
148
279
|
db: {
|
|
149
280
|
execute: (
|
|
@@ -340,6 +471,16 @@ export function __resetCollabAccessCacheForTests(): void {
|
|
|
340
471
|
}
|
|
341
472
|
|
|
342
473
|
type ChangeVisibility = "visible" | "hidden" | "pending";
|
|
474
|
+
type ChangeReadResult = {
|
|
475
|
+
version: number;
|
|
476
|
+
events: ChangeEvent[];
|
|
477
|
+
/**
|
|
478
|
+
* True when the returned version is an intentional cursor stop, not the
|
|
479
|
+
* source high-water mark. This happens when access is still pending or when a
|
|
480
|
+
* durable page hit the read limit and more rows may remain unread.
|
|
481
|
+
*/
|
|
482
|
+
cursorLimited?: boolean;
|
|
483
|
+
};
|
|
343
484
|
|
|
344
485
|
/**
|
|
345
486
|
* Decide whether a poll/SSE change event should be delivered to a user.
|
|
@@ -424,6 +565,7 @@ export function recordChange(event: {
|
|
|
424
565
|
_buffer.splice(0, _buffer.length - MAX_BUFFER);
|
|
425
566
|
}
|
|
426
567
|
_pollEmitter.emit(POLL_CHANGE_EVENT, entry);
|
|
568
|
+
void persistSyncEvent(entry);
|
|
427
569
|
}
|
|
428
570
|
|
|
429
571
|
function extensionTargetKey(target: ExtensionChangeTarget): string | null {
|
|
@@ -565,7 +707,7 @@ export function getChangesSinceForUser(
|
|
|
565
707
|
since: number,
|
|
566
708
|
userEmail: string,
|
|
567
709
|
orgId: string | undefined,
|
|
568
|
-
):
|
|
710
|
+
): ChangeReadResult {
|
|
569
711
|
if (since >= _version) {
|
|
570
712
|
return { version: _version, events: [] };
|
|
571
713
|
}
|
|
@@ -580,12 +722,139 @@ export function getChangesSinceForUser(
|
|
|
580
722
|
}
|
|
581
723
|
if (visibility === "pending") {
|
|
582
724
|
version = Math.max(since, event.version - 1);
|
|
583
|
-
|
|
725
|
+
return { version, events, cursorLimited: true };
|
|
584
726
|
}
|
|
585
727
|
}
|
|
586
728
|
return { version, events };
|
|
587
729
|
}
|
|
588
730
|
|
|
731
|
+
async function getDurableChangesSinceForUser(
|
|
732
|
+
since: number,
|
|
733
|
+
userEmail: string,
|
|
734
|
+
orgId: string | undefined,
|
|
735
|
+
): Promise<ChangeReadResult> {
|
|
736
|
+
if (since <= 0 || !(await ensureSyncEventsTable())) {
|
|
737
|
+
return { version: _version, events: [] };
|
|
738
|
+
}
|
|
739
|
+
|
|
740
|
+
try {
|
|
741
|
+
const result = await getDbExec().execute({
|
|
742
|
+
sql: `SELECT version, event_json FROM sync_events WHERE version > ? ORDER BY version ASC LIMIT ?`,
|
|
743
|
+
args: [since, DURABLE_READ_LIMIT + 1],
|
|
744
|
+
});
|
|
745
|
+
const events: ChangeEvent[] = [];
|
|
746
|
+
let version = Math.max(_version, since);
|
|
747
|
+
let lastDurableVersion = since;
|
|
748
|
+
const rows = result.rows.slice(0, DURABLE_READ_LIMIT);
|
|
749
|
+
const overflowVersion = timestampValue(
|
|
750
|
+
result.rows[DURABLE_READ_LIMIT]?.version,
|
|
751
|
+
);
|
|
752
|
+
|
|
753
|
+
for (const row of rows) {
|
|
754
|
+
const rawVersion = timestampValue(row.version);
|
|
755
|
+
if (rawVersion > lastDurableVersion) lastDurableVersion = rawVersion;
|
|
756
|
+
if (rawVersion > version) version = rawVersion;
|
|
757
|
+
let event: ChangeEvent | null = null;
|
|
758
|
+
try {
|
|
759
|
+
const parsed = JSON.parse(String(row.event_json));
|
|
760
|
+
if (
|
|
761
|
+
parsed &&
|
|
762
|
+
typeof parsed === "object" &&
|
|
763
|
+
typeof parsed.source === "string" &&
|
|
764
|
+
typeof parsed.type === "string"
|
|
765
|
+
) {
|
|
766
|
+
event = {
|
|
767
|
+
...(parsed as ChangeEvent),
|
|
768
|
+
version: rawVersion || (parsed as ChangeEvent).version,
|
|
769
|
+
};
|
|
770
|
+
}
|
|
771
|
+
} catch {
|
|
772
|
+
event = null;
|
|
773
|
+
}
|
|
774
|
+
if (!event) continue;
|
|
775
|
+
|
|
776
|
+
const visibility = getChangeVisibilityForUser(event, userEmail, orgId);
|
|
777
|
+
if (visibility === "visible") {
|
|
778
|
+
events.push(event);
|
|
779
|
+
continue;
|
|
780
|
+
}
|
|
781
|
+
if (visibility === "pending") {
|
|
782
|
+
return {
|
|
783
|
+
version: Math.max(since, event.version - 1),
|
|
784
|
+
events,
|
|
785
|
+
cursorLimited: true,
|
|
786
|
+
};
|
|
787
|
+
}
|
|
788
|
+
}
|
|
789
|
+
|
|
790
|
+
if (rows.length >= DURABLE_READ_LIMIT) {
|
|
791
|
+
if (overflowVersion === lastDurableVersion) {
|
|
792
|
+
const boundaryVersion = lastDurableVersion;
|
|
793
|
+
return {
|
|
794
|
+
version: Math.max(since, boundaryVersion - 1),
|
|
795
|
+
events: events.filter((event) => event.version < boundaryVersion),
|
|
796
|
+
cursorLimited: true,
|
|
797
|
+
};
|
|
798
|
+
}
|
|
799
|
+
return {
|
|
800
|
+
version: Math.max(since, lastDurableVersion),
|
|
801
|
+
events,
|
|
802
|
+
cursorLimited: true,
|
|
803
|
+
};
|
|
804
|
+
}
|
|
805
|
+
|
|
806
|
+
return { version, events };
|
|
807
|
+
} catch {
|
|
808
|
+
return { version: _version, events: [] };
|
|
809
|
+
}
|
|
810
|
+
}
|
|
811
|
+
|
|
812
|
+
async function getCombinedChangesSinceForUser(
|
|
813
|
+
since: number,
|
|
814
|
+
userEmail: string,
|
|
815
|
+
orgId: string | undefined,
|
|
816
|
+
useDurableEvents: boolean,
|
|
817
|
+
): Promise<{ version: number; events: ChangeEvent[] }> {
|
|
818
|
+
const memory = getChangesSinceForUser(since, userEmail, orgId);
|
|
819
|
+
if (!useDurableEvents) return memory;
|
|
820
|
+
|
|
821
|
+
const durable = await getDurableChangesSinceForUser(since, userEmail, orgId);
|
|
822
|
+
const byIdentity = new Map<string, ChangeEvent>();
|
|
823
|
+
for (const event of [...durable.events, ...memory.events]) {
|
|
824
|
+
byIdentity.set(
|
|
825
|
+
JSON.stringify([
|
|
826
|
+
event.version,
|
|
827
|
+
event.source,
|
|
828
|
+
event.type,
|
|
829
|
+
event.key,
|
|
830
|
+
event.owner,
|
|
831
|
+
event.orgId,
|
|
832
|
+
event.resourceType,
|
|
833
|
+
event.resourceId,
|
|
834
|
+
]),
|
|
835
|
+
event,
|
|
836
|
+
);
|
|
837
|
+
}
|
|
838
|
+
const events = Array.from(byIdentity.values()).sort(
|
|
839
|
+
(a, b) => a.version - b.version,
|
|
840
|
+
);
|
|
841
|
+
const limitedVersions = [memory, durable]
|
|
842
|
+
.filter((result) => result.cursorLimited)
|
|
843
|
+
.map((result) => result.version);
|
|
844
|
+
return {
|
|
845
|
+
version:
|
|
846
|
+
limitedVersions.length > 0
|
|
847
|
+
? Math.min(...limitedVersions)
|
|
848
|
+
: Math.max(memory.version, durable.version, since),
|
|
849
|
+
events:
|
|
850
|
+
limitedVersions.length > 0
|
|
851
|
+
? events.filter(
|
|
852
|
+
(event) => event.version <= Math.min(...limitedVersions),
|
|
853
|
+
)
|
|
854
|
+
: events,
|
|
855
|
+
};
|
|
856
|
+
}
|
|
857
|
+
|
|
589
858
|
/**
|
|
590
859
|
* Seed _version from DB timestamps on the first call so serverless
|
|
591
860
|
* cold starts don't return version 0 and confuse polling clients.
|
|
@@ -598,6 +867,7 @@ async function seedVersionFromDb(): Promise<void> {
|
|
|
598
867
|
const db = getDbExec();
|
|
599
868
|
|
|
600
869
|
const [
|
|
870
|
+
syncEventsTs,
|
|
601
871
|
appTs,
|
|
602
872
|
settingsTs,
|
|
603
873
|
extensionsMaxUpdatedAt,
|
|
@@ -605,6 +875,7 @@ async function seedVersionFromDb(): Promise<void> {
|
|
|
605
875
|
actionMarkerTs,
|
|
606
876
|
refreshResult,
|
|
607
877
|
] = await Promise.all([
|
|
878
|
+
readMaxSyncEventVersion(),
|
|
608
879
|
readMaxUpdatedAt(db, "application_state"),
|
|
609
880
|
readMaxUpdatedAt(db, "settings"),
|
|
610
881
|
readMaxUpdatedAtRaw(db, "tools"),
|
|
@@ -627,6 +898,7 @@ async function seedVersionFromDb(): Promise<void> {
|
|
|
627
898
|
// Seed version — never decrease an already-set value
|
|
628
899
|
_version = Math.max(
|
|
629
900
|
_version,
|
|
901
|
+
syncEventsTs,
|
|
630
902
|
appTs,
|
|
631
903
|
settingsTs,
|
|
632
904
|
extensionsTs,
|
|
@@ -667,9 +939,14 @@ async function seedVersionFromDb(): Promise<void> {
|
|
|
667
939
|
* Check for cross-process DB writes by comparing updated_at timestamps.
|
|
668
940
|
* Runs at most once per second to avoid excessive queries.
|
|
669
941
|
*/
|
|
670
|
-
async function checkExternalDbChanges(
|
|
942
|
+
async function checkExternalDbChanges(options: {
|
|
943
|
+
durableEvents: boolean;
|
|
944
|
+
}): Promise<void> {
|
|
671
945
|
const now = Date.now();
|
|
672
|
-
|
|
946
|
+
const interval = options.durableEvents
|
|
947
|
+
? DURABLE_LEGACY_DB_CHECK_INTERVAL_MS
|
|
948
|
+
: LEGACY_DB_CHECK_INTERVAL_MS;
|
|
949
|
+
if (now - _lastDbCheck < interval) return;
|
|
673
950
|
// Coalesce: if a check is already running, await it instead of starting a
|
|
674
951
|
// second overlapping run that would double-advance the shared watermarks
|
|
675
952
|
// (and double-emit change events).
|
|
@@ -906,11 +1183,19 @@ export function createPollHandler() {
|
|
|
906
1183
|
}
|
|
907
1184
|
// On cold start, seed _version from DB so we don't return version: 0
|
|
908
1185
|
await seedVersionFromDb();
|
|
909
|
-
|
|
910
|
-
|
|
1186
|
+
const durableEvents = await ensureSyncEventsTable();
|
|
1187
|
+
// Durable sync_events rows are the cheap cross-process path. Keep the
|
|
1188
|
+
// legacy watermark scan as a slower safety net for direct SQL writes and
|
|
1189
|
+
// older processes that have not started writing durable events yet.
|
|
1190
|
+
await checkExternalDbChanges({ durableEvents });
|
|
911
1191
|
|
|
912
1192
|
const query = getQuery(event);
|
|
913
1193
|
const since = parseInt(String(query.since ?? "0"), 10) || 0;
|
|
914
|
-
return
|
|
1194
|
+
return getCombinedChangesSinceForUser(
|
|
1195
|
+
since,
|
|
1196
|
+
session.email,
|
|
1197
|
+
session.orgId,
|
|
1198
|
+
durableEvents,
|
|
1199
|
+
);
|
|
915
1200
|
});
|
|
916
1201
|
}
|
|
@@ -515,6 +515,33 @@
|
|
|
515
515
|
--ring: 215 12% 60%;
|
|
516
516
|
}
|
|
517
517
|
|
|
518
|
+
/* Full settings page section bodies.
|
|
519
|
+
The framework's shared settings sections (LLM, App model defaults, Agent
|
|
520
|
+
limits, Email, Account, Secrets, Usage, Voice, Automations, A2A, hosting
|
|
521
|
+
cards, etc.) are authored dense for the compact agent sidebar. On the
|
|
522
|
+
full-width settings page they render inside polished shadcn-style cards
|
|
523
|
+
(PageSettingsSection), so bump the smallest fixed type up to a comfortable,
|
|
524
|
+
consistent reading size that matches the app-owned General/Team cards.
|
|
525
|
+
Scoped to `[data-agent-native-settings-page]` (only PageSettingsSection's
|
|
526
|
+
body) so app-owned tab content is never affected. The `.text-[…px]` classes
|
|
527
|
+
are Tailwind arbitrary values; the double-class specificity here wins. */
|
|
528
|
+
[data-agent-native-settings-page] .text-\[10px\] {
|
|
529
|
+
font-size: 0.75rem;
|
|
530
|
+
line-height: 1rem;
|
|
531
|
+
}
|
|
532
|
+
[data-agent-native-settings-page] .text-\[11px\] {
|
|
533
|
+
font-size: 0.8125rem;
|
|
534
|
+
line-height: 1.15rem;
|
|
535
|
+
}
|
|
536
|
+
[data-agent-native-settings-page] .text-\[12px\] {
|
|
537
|
+
font-size: 0.875rem;
|
|
538
|
+
line-height: 1.25rem;
|
|
539
|
+
}
|
|
540
|
+
[data-agent-native-settings-page] .text-\[13px\] {
|
|
541
|
+
font-size: 0.9375rem;
|
|
542
|
+
line-height: 1.4rem;
|
|
543
|
+
}
|
|
544
|
+
|
|
518
545
|
/* Composer toolbar: keep the model name + version readable on narrow
|
|
519
546
|
chatfields. The bordered composer box is a size container; below 370px
|
|
520
547
|
wide (e.g. a narrow agent sidebar) we drop the reasoning-level suffix
|
|
@@ -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:
|
|
@@ -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
|
|
@@ -6,7 +6,7 @@ This is an **@agent-native/core** application -- the AI agent and UI share state
|
|
|
6
6
|
|
|
7
7
|
### Core Principles
|
|
8
8
|
|
|
9
|
-
1. **Shared SQL database** -- All app state lives in SQL. Local SQLite at `data/app.db` is the zero-setup dev fallback; local PGlite is available as a Postgres-dialect opt-in with `DATABASE_URL=pglite:./data/pglite` after installing `@electric-sql/pglite`. Deployed apps need a persistent `DATABASE_URL` so data survives container/serverless restarts. Turso is optional, not required: Neon, Supabase, Turso/libSQL, plain Postgres, durable SQLite, D1 bindings, and Builder.io-managed environments are all valid when supported by the deploy. Core stores: `application_state`, `settings`, `oauth_tokens`, `sessions`, `resources`.
|
|
9
|
+
1. **Shared SQL database** -- All app state lives in SQL. SQL stores structured records, metadata, references, and searchable text; large files/blob payloads must use file/blob storage and persist only URLs, ids, or handles. Never store large base64, `data:` URLs, images, videos, audio, PDFs, ZIPs, screenshots, thumbnails, or session replay chunks in SQL, `application_state`, `settings`, or `resources`. Local SQLite at `data/app.db` is the zero-setup dev fallback; local PGlite is available as a Postgres-dialect opt-in with `DATABASE_URL=pglite:./data/pglite` after installing `@electric-sql/pglite`. Deployed apps need a persistent `DATABASE_URL` so data survives container/serverless restarts. Turso is optional, not required: Neon, Supabase, Turso/libSQL, plain Postgres, durable SQLite, D1 bindings, and Builder.io-managed environments are all valid when supported by the deploy. Core stores: `application_state`, `settings`, `oauth_tokens`, `sessions`, `resources`.
|
|
10
10
|
2. **AI through the right framework surface** -- Product workflows delegate to the agent via `sendToAgentChat()` / `agentChat.submit()`. Use `sendToAgentChat({ message, context, submit })` for simple UI handoffs and prefill/review flows; add `newTab: true, background: true, openSidebar: false` when the agent should work silently without focusing the sidebar. Only use the agent-chat context state helpers (`useAgentChatContext`, `setAgentChatContextItem`, `listAgentChatContext`, `removeAgentChatContextItem`, `clearAgentChatContext`) when the UI needs two-way sync with staged context chips. For rare server-side text transforms that intentionally need no tools, chat history, or run state, use `completeText()` from `@agent-native/core/server` inside an action instead of importing provider SDKs directly.
|
|
11
11
|
3. **Actions for app operations** -- `pnpm action <name>` dispatches to callable action files in `actions/`; `defineAction` also auto-exposes those operations at `/_agent-native/actions/:name` for the UI. Do not create custom REST routes that re-export actions.
|
|
12
12
|
4. **Live sync keeps the UI current** -- Database writes stream over `/_agent-native/events` first, with `/_agent-native/poll` as the fallback. **When you (the agent) write data, the UI must reflect the change without a manual refresh.** This is non-negotiable. Use `useActionQuery` / `useActionMutation` for action-backed data (preferred). If you use raw `useQuery`, fold `useChangeVersions([<source>, "action"])` into the key for targeted refreshes. See the `real-time-sync` and `adding-a-feature` skills.
|
|
@@ -67,7 +67,9 @@ Use `getSession(event)` server-side and `useSession()` client-side. When there i
|
|
|
67
67
|
|
|
68
68
|
## Resources
|
|
69
69
|
|
|
70
|
-
Resources are
|
|
70
|
+
Resources are persistent files for notes, learnings, and context. Text resources
|
|
71
|
+
live in SQL; binary resource uploads require configured file storage and store
|
|
72
|
+
only the hosted URL/reference in SQL.
|
|
71
73
|
|
|
72
74
|
**At the start of every conversation, read these resources (workspace, shared, and personal scopes as relevant):**
|
|
73
75
|
|
|
@@ -8,6 +8,10 @@ This app is not stateless. The Agent Native runtime uses SQL-backed stores for a
|
|
|
8
8
|
|
|
9
9
|
- Prefer actions in `actions/` for every app operation. Do not create REST wrappers around actions.
|
|
10
10
|
- Keep action inputs validated with Zod and return structured data, not JSON strings.
|
|
11
|
+
- SQL is for structured records, metadata, references, and searchable text. Store
|
|
12
|
+
large files/blob payloads (base64, `data:` URLs, images, video/audio, PDFs,
|
|
13
|
+
ZIPs, screenshots, thumbnails, session replay chunks) in configured file/blob
|
|
14
|
+
storage and persist only URLs, ids, or handles.
|
|
11
15
|
- Do not hardcode API keys, tokens, webhook URLs, private data, or credential-looking literals.
|
|
12
16
|
- `actions/run.ts` is the CLI dispatcher for `pnpm action ...`, not an app
|
|
13
17
|
action. Leave it in place and add callable primitives as separate
|
|
@@ -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
|
|
@@ -65,6 +65,10 @@ const navigation = await readAppState("navigation");
|
|
|
65
65
|
|
|
66
66
|
Raw URL query params are already synced by the framework to `__url__` and shown to the built-in agent as `<current-url>`. Keep shareable filters in URL state, then use `view-screen` to summarize important query params as `activeFilters` when helpful.
|
|
67
67
|
|
|
68
|
+
Keep `application_state` values small. Do not store pasted files, base64 images,
|
|
69
|
+
recording chunks, screenshots, or other large blobs in navigation or app-state
|
|
70
|
+
keys; upload them and store only a URL or storage handle.
|
|
71
|
+
|
|
68
72
|
### 2. Current URL (`__url__` key)
|
|
69
73
|
|
|
70
74
|
`AgentPanel` automatically writes `__url__` with `{ pathname, search, hash, searchParams }`. The built-in agent sees it as a `<current-url>` block in every turn.
|
|
@@ -391,6 +391,12 @@ extension HTML, inline scripts, docs, examples, or extension seed content.
|
|
|
391
391
|
Extensions are stored in SQL and rendered in the browser; anything written into
|
|
392
392
|
the extension body should be treated as visible.
|
|
393
393
|
|
|
394
|
+
Extension HTML belongs in SQL, but large media does not. Do not embed pasted
|
|
395
|
+
files, base64 assets, screenshots, or binary blobs in `content` or
|
|
396
|
+
`extensionData`; upload media to file/blob storage and reference hosted URLs or
|
|
397
|
+
opaque handles. For large pasted text bodies, use the documented
|
|
398
|
+
`contentFromAttachment` flow instead of a megabyte inline string.
|
|
399
|
+
|
|
394
400
|
For external API calls, use `extensionFetch()` with `${keys.NAME}` placeholders
|
|
395
401
|
inside single-quoted strings, for example
|
|
396
402
|
`Authorization: 'Bearer ${keys.GITHUB_TOKEN}'`. The proxy resolves the value
|
|
@@ -128,6 +128,10 @@ index the growing tables first.
|
|
|
128
128
|
demand.
|
|
129
129
|
- Don't store **unbounded blobs inline** in a row that a list/load pulls.
|
|
130
130
|
Reference large content separately so opening the parent stays cheap.
|
|
131
|
+
- Never inline binary payloads in columns a list, poll, or `view-screen` summary
|
|
132
|
+
reads. Images, PDFs, audio/video, archives, screenshots, and base64
|
|
133
|
+
attachments belong in file/blob storage; SQL rows should hold URLs, asset ids,
|
|
134
|
+
storage keys, or opaque blob refs.
|
|
131
135
|
- **Virtualize** very long rendered lists on the client so off-screen rows aren't
|
|
132
136
|
parsed/rendered every update.
|
|
133
137
|
|
|
@@ -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:
|