@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
|
@@ -1,5 +1,10 @@
|
|
|
1
1
|
import { getDbExec, isPostgres } from "@agent-native/core/db";
|
|
2
2
|
|
|
3
|
+
const STALE_CHUNK_PRUNE_LIMIT = 100;
|
|
4
|
+
const STALE_CHUNK_PRUNE_MIN_INTERVAL_MS = 5 * 60 * 1000;
|
|
5
|
+
const DEFAULT_STALE_CHUNK_TTL_MS = 6 * 60 * 60 * 1000;
|
|
6
|
+
const lastPruneByOwner = new Map<string, number>();
|
|
7
|
+
|
|
3
8
|
function escapeLike(value: string): string {
|
|
4
9
|
return value.replace(/[!%_]/g, (match) => `!${match}`);
|
|
5
10
|
}
|
|
@@ -8,6 +13,10 @@ function chunkPrefix(recordingId: string): string {
|
|
|
8
13
|
return `recording-chunks-${recordingId}-`;
|
|
9
14
|
}
|
|
10
15
|
|
|
16
|
+
function allChunksPrefix(): string {
|
|
17
|
+
return "recording-chunks-";
|
|
18
|
+
}
|
|
19
|
+
|
|
11
20
|
function likePrefix(prefix: string): string {
|
|
12
21
|
return `${escapeLike(prefix)}%`;
|
|
13
22
|
}
|
|
@@ -89,6 +98,17 @@ export async function listRecordingChunkKeys(
|
|
|
89
98
|
return rows.map((row) => String(row.key));
|
|
90
99
|
}
|
|
91
100
|
|
|
101
|
+
export async function deleteRecordingChunks(
|
|
102
|
+
ownerEmail: string,
|
|
103
|
+
recordingId: string,
|
|
104
|
+
): Promise<number> {
|
|
105
|
+
const result = await getDbExec().execute({
|
|
106
|
+
sql: `DELETE FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!'`,
|
|
107
|
+
args: [ownerEmail, likePrefix(chunkPrefix(recordingId))],
|
|
108
|
+
});
|
|
109
|
+
return result.rowsAffected ?? 0;
|
|
110
|
+
}
|
|
111
|
+
|
|
92
112
|
export async function sumRecordingChunkBytes(
|
|
93
113
|
ownerEmail: string,
|
|
94
114
|
recordingId: string,
|
|
@@ -102,3 +122,46 @@ export async function sumRecordingChunkBytes(
|
|
|
102
122
|
});
|
|
103
123
|
return numberFromRowValue(rows[0]?.bytes);
|
|
104
124
|
}
|
|
125
|
+
|
|
126
|
+
export async function pruneStaleRecordingChunks(
|
|
127
|
+
ownerEmail: string,
|
|
128
|
+
options: {
|
|
129
|
+
now?: number;
|
|
130
|
+
ttlMs?: number;
|
|
131
|
+
minIntervalMs?: number;
|
|
132
|
+
} = {},
|
|
133
|
+
): Promise<number> {
|
|
134
|
+
const now = options.now ?? Date.now();
|
|
135
|
+
const minIntervalMs =
|
|
136
|
+
options.minIntervalMs ?? STALE_CHUNK_PRUNE_MIN_INTERVAL_MS;
|
|
137
|
+
const lastPrune = lastPruneByOwner.get(ownerEmail) ?? 0;
|
|
138
|
+
if (minIntervalMs > 0 && now - lastPrune < minIntervalMs) return 0;
|
|
139
|
+
lastPruneByOwner.set(ownerEmail, now);
|
|
140
|
+
|
|
141
|
+
const ttlMs = options.ttlMs ?? DEFAULT_STALE_CHUNK_TTL_MS;
|
|
142
|
+
const cutoff = new Date(now - ttlMs).toISOString();
|
|
143
|
+
const createdAtExpression = isPostgres()
|
|
144
|
+
? `value::jsonb ->> 'createdAt'`
|
|
145
|
+
: `json_extract(value, '$.createdAt')`;
|
|
146
|
+
const { rows } = await getDbExec().execute({
|
|
147
|
+
sql: `SELECT key FROM application_state WHERE session_id = ? AND key LIKE ? ESCAPE '!' AND ${createdAtExpression} IS NOT NULL AND ${createdAtExpression} < ? LIMIT ?`,
|
|
148
|
+
args: [
|
|
149
|
+
ownerEmail,
|
|
150
|
+
likePrefix(allChunksPrefix()),
|
|
151
|
+
cutoff,
|
|
152
|
+
STALE_CHUNK_PRUNE_LIMIT,
|
|
153
|
+
],
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
let purged = 0;
|
|
157
|
+
for (const row of rows) {
|
|
158
|
+
const key = String(row.key ?? "");
|
|
159
|
+
if (!key) continue;
|
|
160
|
+
const result = await getDbExec().execute({
|
|
161
|
+
sql: `DELETE FROM application_state WHERE session_id = ? AND key = ?`,
|
|
162
|
+
args: [ownerEmail, key],
|
|
163
|
+
});
|
|
164
|
+
purged += result.rowsAffected ?? 0;
|
|
165
|
+
}
|
|
166
|
+
return purged;
|
|
167
|
+
}
|
|
@@ -25,6 +25,10 @@ export function requiresConfiguredVideoStorage(): boolean {
|
|
|
25
25
|
return process.env.NODE_ENV === "production" || !isLikelyLocalDatabase();
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
+
export function allowsSqlRecordingChunkScratch(): boolean {
|
|
29
|
+
return !requiresConfiguredVideoStorage();
|
|
30
|
+
}
|
|
31
|
+
|
|
28
32
|
interface VideoStorageResolveContext {
|
|
29
33
|
userEmail?: string;
|
|
30
34
|
orgId?: string | null;
|
|
@@ -28,6 +28,10 @@ import {
|
|
|
28
28
|
getEventOwnerContext,
|
|
29
29
|
ownerEmailMatches,
|
|
30
30
|
} from "../../../../lib/recordings.js";
|
|
31
|
+
import {
|
|
32
|
+
requiresConfiguredVideoStorage,
|
|
33
|
+
STORAGE_SETUP_REQUIRED_REASON,
|
|
34
|
+
} from "../../../../lib/video-storage.js";
|
|
31
35
|
|
|
32
36
|
const MAX_THUMBNAIL_BYTES = 2 * 1024 * 1024;
|
|
33
37
|
|
|
@@ -154,12 +158,9 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
154
158
|
return { error: "Thumbnail bytes do not match Content-Type" };
|
|
155
159
|
}
|
|
156
160
|
|
|
157
|
-
// Try the configured file-upload provider first
|
|
158
|
-
//
|
|
159
|
-
//
|
|
160
|
-
// back to a base64 `data:` URL inline in the `recordings.thumbnail_url`
|
|
161
|
-
// column. It's not glamorous but it unblocks the library grid
|
|
162
|
-
// immediately and mirrors what `set-thumbnail` does.
|
|
161
|
+
// Try the configured file-upload provider first. Hosted/persistent SQL
|
|
162
|
+
// must not store base64 data URLs in recordings.thumbnail_url; local dev can
|
|
163
|
+
// still fall back inline to keep the first-frame preview flow lightweight.
|
|
163
164
|
const uploaded = await uploadFile({
|
|
164
165
|
data: bytes,
|
|
165
166
|
mimeType,
|
|
@@ -177,15 +178,20 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
177
178
|
bytes: bytes.byteLength,
|
|
178
179
|
});
|
|
179
180
|
} else {
|
|
181
|
+
if (requiresConfiguredVideoStorage()) {
|
|
182
|
+
setResponseStatus(event, 409);
|
|
183
|
+
return {
|
|
184
|
+
ok: false,
|
|
185
|
+
error: STORAGE_SETUP_REQUIRED_REASON,
|
|
186
|
+
storageSetupRequired: true,
|
|
187
|
+
};
|
|
188
|
+
}
|
|
180
189
|
const base64 = Buffer.from(bytes).toString("base64");
|
|
181
190
|
url = `data:${mimeType};base64,${base64}`;
|
|
182
|
-
console.log(
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
bytes: bytes.byteLength,
|
|
187
|
-
},
|
|
188
|
-
);
|
|
191
|
+
console.log("[thumbnail] local storage fallback stored inline data URL", {
|
|
192
|
+
recordingId,
|
|
193
|
+
bytes: bytes.byteLength,
|
|
194
|
+
});
|
|
189
195
|
}
|
|
190
196
|
|
|
191
197
|
await db
|
|
@@ -37,7 +37,11 @@ import {
|
|
|
37
37
|
import finalizeRecording from "../../../../../actions/finalize-recording.js";
|
|
38
38
|
import { getDb, schema } from "../../../../db/index.js";
|
|
39
39
|
import { debugLog } from "../../../../lib/debug.js";
|
|
40
|
-
import {
|
|
40
|
+
import {
|
|
41
|
+
deleteRecordingChunks,
|
|
42
|
+
pruneStaleRecordingChunks,
|
|
43
|
+
sumRecordingChunkBytes,
|
|
44
|
+
} from "../../../../lib/recording-upload-state.js";
|
|
41
45
|
import {
|
|
42
46
|
getEventOwnerContext,
|
|
43
47
|
ownerEmailMatches,
|
|
@@ -49,6 +53,7 @@ import {
|
|
|
49
53
|
} from "../../../../lib/resumable-session.js";
|
|
50
54
|
import { isStreamingUploadDisabled } from "../../../../lib/streaming-upload-mode.js";
|
|
51
55
|
import {
|
|
56
|
+
allowsSqlRecordingChunkScratch,
|
|
52
57
|
shouldRejectVideoUploadWithoutStorage,
|
|
53
58
|
STORAGE_SETUP_REQUIRED_REASON,
|
|
54
59
|
} from "../../../../lib/video-storage.js";
|
|
@@ -175,6 +180,13 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
175
180
|
debugLog("[chunk] resolved owner:", ownerEmail);
|
|
176
181
|
|
|
177
182
|
return runWithRequestContext({ userEmail: ownerEmail, orgId }, async () => {
|
|
183
|
+
await pruneStaleRecordingChunks(ownerEmail).catch((err) => {
|
|
184
|
+
console.warn("[chunk] stale recording chunk prune failed:", {
|
|
185
|
+
ownerEmail,
|
|
186
|
+
err: err instanceof Error ? err.message : String(err),
|
|
187
|
+
});
|
|
188
|
+
});
|
|
189
|
+
|
|
178
190
|
const db = getDb();
|
|
179
191
|
|
|
180
192
|
// Verify the recording belongs to the current user.
|
|
@@ -271,6 +283,15 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
271
283
|
storageSetupRequired: true,
|
|
272
284
|
};
|
|
273
285
|
}
|
|
286
|
+
if (!allowsSqlRecordingChunkScratch()) {
|
|
287
|
+
setResponseStatus(event, 409);
|
|
288
|
+
return {
|
|
289
|
+
ok: false,
|
|
290
|
+
error:
|
|
291
|
+
"Recording upload storage is configured, but this upload did not start a resumable storage session. Refresh and start the recording again.",
|
|
292
|
+
storageSetupRequired: false,
|
|
293
|
+
};
|
|
294
|
+
}
|
|
274
295
|
|
|
275
296
|
const raw = await readRawBody(event, false);
|
|
276
297
|
const bodySize = raw ? raw.byteLength : 0;
|
|
@@ -320,6 +341,12 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
320
341
|
? latestState.failureReason
|
|
321
342
|
: "Recording upload has already failed.";
|
|
322
343
|
if (latestState?.status === "failed") {
|
|
344
|
+
await deleteRecordingChunks(ownerEmail, recordingId).catch((err) => {
|
|
345
|
+
console.warn("[chunk] failed upload chunk cleanup failed:", {
|
|
346
|
+
recordingId,
|
|
347
|
+
err: err instanceof Error ? err.message : String(err),
|
|
348
|
+
});
|
|
349
|
+
});
|
|
323
350
|
return failedUploadResponse(latestReason);
|
|
324
351
|
}
|
|
325
352
|
|
|
@@ -333,6 +360,12 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
333
360
|
if (current?.status === "failed") {
|
|
334
361
|
const reason =
|
|
335
362
|
current.failureReason ?? "Recording upload has already failed.";
|
|
363
|
+
await deleteRecordingChunks(ownerEmail, recordingId).catch((err) => {
|
|
364
|
+
console.warn("[chunk] failed recording chunk cleanup failed:", {
|
|
365
|
+
recordingId,
|
|
366
|
+
err: err instanceof Error ? err.message : String(err),
|
|
367
|
+
});
|
|
368
|
+
});
|
|
336
369
|
await writeAppState(`recording-upload-${recordingId}`, {
|
|
337
370
|
recordingId,
|
|
338
371
|
status: "failed",
|
|
@@ -364,6 +397,12 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
364
397
|
maxBytes: MAX_RECORDING_UPLOAD_BYTES,
|
|
365
398
|
updatedAt: now,
|
|
366
399
|
});
|
|
400
|
+
await deleteRecordingChunks(ownerEmail, recordingId).catch((err) => {
|
|
401
|
+
console.warn("[chunk] oversized upload chunk cleanup failed:", {
|
|
402
|
+
recordingId,
|
|
403
|
+
err: err instanceof Error ? err.message : String(err),
|
|
404
|
+
});
|
|
405
|
+
});
|
|
367
406
|
setResponseStatus(event, 413);
|
|
368
407
|
return {
|
|
369
408
|
ok: false,
|
|
@@ -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 document editing, Notion, 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 documents, blocks, comments, media, sharing, navigation, and
|
|
14
18
|
Notion integration. Do not mutate document rows directly unless a skill says to
|
|
@@ -43,7 +43,7 @@ export default function SettingsRoute() {
|
|
|
43
43
|
extraTabs={agentSettingsTabs}
|
|
44
44
|
generalSearchEntries={generalSearchEntries}
|
|
45
45
|
general={
|
|
46
|
-
<main className="mx-auto w-full max-w-
|
|
46
|
+
<main className="mx-auto w-full max-w-2xl space-y-6">
|
|
47
47
|
<p className="text-sm leading-6 text-muted-foreground">
|
|
48
48
|
{t("settings.description")}
|
|
49
49
|
</p>
|
|
@@ -77,7 +77,7 @@ export default function SettingsRoute() {
|
|
|
77
77
|
</div>
|
|
78
78
|
}
|
|
79
79
|
whatsNew={
|
|
80
|
-
<div className="mx-auto w-full max-w-
|
|
80
|
+
<div className="mx-auto w-full max-w-2xl">
|
|
81
81
|
<ChangelogSettingsCard markdown={changelog} />
|
|
82
82
|
</div>
|
|
83
83
|
}
|
|
@@ -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)
|
|
@@ -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 @@ patterns live in `.agents/skills/`.
|
|
|
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 the app actions for designs, files, versions, design systems, variants,
|
|
14
18
|
export, and sharing. Do not write design rows directly with SQL.
|
|
@@ -12,10 +12,9 @@
|
|
|
12
12
|
* returns:
|
|
13
13
|
*
|
|
14
14
|
* - `screenshots[]` — a PNG per viewport, persisted through the shared
|
|
15
|
-
* `uploadFile` provider
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
* view it by opening the link, or the agent can embed it as
|
|
15
|
+
* `uploadFile` provider. The result carries a plain `url` per screenshot
|
|
16
|
+
* when storage is configured so a human can view it by opening the link,
|
|
17
|
+
* or the agent can embed it as
|
|
19
18
|
* `` in its own chat reply today. NOTE: tool results are
|
|
20
19
|
* currently text-only end-to-end — this action does not attach the PNG as
|
|
21
20
|
* a model-visible image content block. The moment the engine supports
|
|
@@ -97,8 +96,9 @@ export interface ScreenshotDiagnostics {
|
|
|
97
96
|
export interface ScreenshotResult {
|
|
98
97
|
viewport: ScreenshotViewport;
|
|
99
98
|
url: string;
|
|
100
|
-
/** True when persisted via a durable upload provider
|
|
99
|
+
/** True when persisted via a durable upload provider. */
|
|
101
100
|
persisted: boolean;
|
|
101
|
+
uploadError?: string;
|
|
102
102
|
bytes: number;
|
|
103
103
|
diagnostics: ScreenshotDiagnostics;
|
|
104
104
|
}
|
|
@@ -620,13 +620,16 @@ export default defineAction({
|
|
|
620
620
|
ownerEmail,
|
|
621
621
|
}).catch(() => null);
|
|
622
622
|
|
|
623
|
-
const url =
|
|
624
|
-
uploaded?.url ?? `data:image/png;base64,${png.toString("base64")}`;
|
|
625
|
-
|
|
626
623
|
screenshots.push({
|
|
627
624
|
viewport,
|
|
628
|
-
url,
|
|
625
|
+
url: uploaded?.url ?? "",
|
|
629
626
|
persisted: !!uploaded,
|
|
627
|
+
...(uploaded?.url
|
|
628
|
+
? {}
|
|
629
|
+
: {
|
|
630
|
+
uploadError:
|
|
631
|
+
"Screenshot was rendered but not returned because file storage is not configured.",
|
|
632
|
+
}),
|
|
630
633
|
bytes: png.byteLength,
|
|
631
634
|
diagnostics: {
|
|
632
635
|
viewport,
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { callAction, useT } from "@agent-native/core/client";
|
|
1
2
|
import { IconPhotoPlus, IconX } from "@tabler/icons-react";
|
|
2
3
|
import { useEffect, useRef, useState } from "react";
|
|
3
4
|
|
|
@@ -32,6 +33,11 @@ export interface ImageFillBackgroundStyles {
|
|
|
32
33
|
backgroundPosition?: string;
|
|
33
34
|
}
|
|
34
35
|
|
|
36
|
+
type UploadImageResult = {
|
|
37
|
+
url?: string;
|
|
38
|
+
error?: string;
|
|
39
|
+
};
|
|
40
|
+
|
|
35
41
|
const FIT_MODES: Array<{ mode: ImageFitMode; label: string }> = [
|
|
36
42
|
{ mode: "fill", label: "Fill" }, // i18n-ignore image fit mode
|
|
37
43
|
{ mode: "fit", label: "Fit" }, // i18n-ignore image fit mode
|
|
@@ -51,6 +57,19 @@ function escapeForQuotedUrl(url: string): string {
|
|
|
51
57
|
return url.replace(/\\/g, "\\\\").replace(/"/g, "%22").replace(/\r?\n/g, "");
|
|
52
58
|
}
|
|
53
59
|
|
|
60
|
+
function readFileAsDataUrl(file: File): Promise<string> {
|
|
61
|
+
return new Promise((resolve, reject) => {
|
|
62
|
+
const reader = new FileReader();
|
|
63
|
+
reader.onload = () =>
|
|
64
|
+
typeof reader.result === "string"
|
|
65
|
+
? resolve(reader.result)
|
|
66
|
+
: reject(new Error("Image upload did not produce a data URL"));
|
|
67
|
+
reader.onerror = () =>
|
|
68
|
+
reject(reader.error ?? new Error("Image read failed"));
|
|
69
|
+
reader.readAsDataURL(file);
|
|
70
|
+
});
|
|
71
|
+
}
|
|
72
|
+
|
|
54
73
|
const CHECKER_A = "#d4d4d4";
|
|
55
74
|
const CHECKERBOARD_IMAGE = `linear-gradient(45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(-45deg, ${CHECKER_A} 25%, transparent 25%), linear-gradient(45deg, transparent 75%, ${CHECKER_A} 75%), linear-gradient(-45deg, transparent 75%, ${CHECKER_A} 75%)`;
|
|
56
75
|
const FIT_MARKER_RE =
|
|
@@ -261,9 +280,12 @@ export function ImageFillControls({
|
|
|
261
280
|
disabled = false,
|
|
262
281
|
className,
|
|
263
282
|
}: ImageFillControlsProps) {
|
|
283
|
+
const t = useT();
|
|
264
284
|
const fileInputRef = useRef<HTMLInputElement>(null);
|
|
265
285
|
const [urlDraft, setUrlDraft] = useState(value.url);
|
|
266
286
|
const urlDraftRef = useRef(value.url);
|
|
287
|
+
const [uploadingImage, setUploadingImage] = useState(false);
|
|
288
|
+
const [uploadError, setUploadError] = useState<string | null>(null);
|
|
267
289
|
// Guard re-syncing the draft from an external value change while the field
|
|
268
290
|
// is focused (mirrors ScrubInput's `focused` pattern): without this, an
|
|
269
291
|
// incoming prop update while the user is mid-typing a URL — e.g. a
|
|
@@ -281,20 +303,36 @@ export function ImageFillControls({
|
|
|
281
303
|
onChange({ ...value, url: urlDraftRef.current.trim() });
|
|
282
304
|
};
|
|
283
305
|
|
|
284
|
-
const handleFilePick = (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
306
|
+
const handleFilePick = async (event: React.ChangeEvent<HTMLInputElement>) => {
|
|
285
307
|
const file = event.target.files?.[0];
|
|
286
308
|
if (!file) return;
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
309
|
+
setUploadingImage(true);
|
|
310
|
+
setUploadError(null);
|
|
311
|
+
try {
|
|
312
|
+
const dataUrl = await readFileAsDataUrl(file);
|
|
313
|
+
const result = (await callAction("upload-image", {
|
|
314
|
+
data: dataUrl,
|
|
315
|
+
filename: file.name,
|
|
316
|
+
})) as UploadImageResult;
|
|
317
|
+
if (result.url) {
|
|
318
|
+
urlDraftRef.current = result.url;
|
|
319
|
+
setUrlDraft(result.url);
|
|
320
|
+
onChange({ ...value, url: result.url });
|
|
321
|
+
} else {
|
|
322
|
+
setUploadError(
|
|
323
|
+
result.error ||
|
|
324
|
+
"File storage is not configured. Connect an upload provider before using local images.",
|
|
325
|
+
);
|
|
293
326
|
}
|
|
294
|
-
}
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
327
|
+
} catch (error) {
|
|
328
|
+
setUploadError(
|
|
329
|
+
error instanceof Error ? error.message : t("common.genericError"),
|
|
330
|
+
);
|
|
331
|
+
} finally {
|
|
332
|
+
setUploadingImage(false);
|
|
333
|
+
// Allow re-selecting the same file later.
|
|
334
|
+
event.target.value = "";
|
|
335
|
+
}
|
|
298
336
|
};
|
|
299
337
|
|
|
300
338
|
return (
|
|
@@ -376,13 +414,14 @@ export function ImageFillControls({
|
|
|
376
414
|
<TooltipTrigger asChild>
|
|
377
415
|
<button
|
|
378
416
|
type="button"
|
|
379
|
-
disabled={disabled}
|
|
417
|
+
disabled={disabled || uploadingImage}
|
|
380
418
|
aria-label={"Upload image" /* i18n-ignore */}
|
|
381
419
|
onClick={() => fileInputRef.current?.click()}
|
|
382
420
|
className={cn(
|
|
383
421
|
"flex size-6 shrink-0 items-center justify-center rounded-md border border-[var(--design-editor-control-border)] bg-[var(--design-editor-control-bg)] text-muted-foreground hover:text-foreground",
|
|
384
422
|
"focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring",
|
|
385
|
-
disabled
|
|
423
|
+
(disabled || uploadingImage) &&
|
|
424
|
+
"pointer-events-none opacity-40",
|
|
386
425
|
)}
|
|
387
426
|
>
|
|
388
427
|
<IconPhotoPlus className="size-3.5" />
|
|
@@ -398,6 +437,11 @@ export function ImageFillControls({
|
|
|
398
437
|
onChange={handleFilePick}
|
|
399
438
|
/>
|
|
400
439
|
</div>
|
|
440
|
+
{uploadError && (
|
|
441
|
+
<p className="text-[10px] leading-snug text-destructive">
|
|
442
|
+
{uploadError}
|
|
443
|
+
</p>
|
|
444
|
+
)}
|
|
401
445
|
|
|
402
446
|
{/* ── Fit mode dropdown ─────────────────────────────────────────────── */}
|
|
403
447
|
<Select
|
|
@@ -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)
|
|
@@ -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:
|
|
@@ -8,6 +8,10 @@ specific essentials.
|
|
|
8
8
|
|
|
9
9
|
## Core Rules
|
|
10
10
|
|
|
11
|
+
- Store large file/blob payloads in configured file/blob storage, not SQL: no
|
|
12
|
+
base64, `data:` URLs, images, video/audio, PDFs, ZIPs, screenshots,
|
|
13
|
+
thumbnails, or replay chunks in app tables, `application_state`, `settings`,
|
|
14
|
+
or `resources`; persist URLs, ids, or handles instead.
|
|
11
15
|
- 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.
|
|
12
16
|
- Treat Dispatch as workspace infrastructure. Prefer actions over raw SQL for
|
|
13
17
|
vault, integrations, resource grants, messaging, routing, and approvals.
|
|
@@ -216,7 +216,7 @@ function resolvePath(
|
|
|
216
216
|
case "threads":
|
|
217
217
|
return "/thread-debug";
|
|
218
218
|
case "team":
|
|
219
|
-
return "/settings#
|
|
219
|
+
return "/settings#organization";
|
|
220
220
|
case "extensions":
|
|
221
221
|
return command?.extensionId
|
|
222
222
|
? `/extensions/${encodeURIComponent(command.extensionId)}`
|
|
@@ -54,7 +54,7 @@ export default function SettingsRoute() {
|
|
|
54
54
|
extraTabs={agentSettingsTabs}
|
|
55
55
|
generalSearchEntries={generalSearchEntries}
|
|
56
56
|
general={
|
|
57
|
-
<div className="mx-auto w-full max-w-
|
|
57
|
+
<div className="mx-auto w-full max-w-2xl space-y-6">
|
|
58
58
|
<p className="text-sm leading-6 text-muted-foreground">
|
|
59
59
|
{t("settings.description")}
|
|
60
60
|
</p>
|
|
@@ -102,7 +102,7 @@ export default function SettingsRoute() {
|
|
|
102
102
|
</div>
|
|
103
103
|
}
|
|
104
104
|
whatsNew={
|
|
105
|
-
<div className="mx-auto w-full max-w-
|
|
105
|
+
<div className="mx-auto w-full max-w-2xl">
|
|
106
106
|
<ChangelogSettingsCard markdown={changelog} />
|
|
107
107
|
</div>
|
|
108
108
|
}
|