@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,3 +1,7 @@
|
|
|
1
|
+
import { stat, readFile } from "node:fs/promises";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { fileURLToPath } from "node:url";
|
|
4
|
+
|
|
1
5
|
/**
|
|
2
6
|
* Server-side error capture — OWNED BY THE ERROR CAPTURE FEATURE.
|
|
3
7
|
*
|
|
@@ -58,6 +62,10 @@ const DEFAULT_ISSUE_LIMIT = 50;
|
|
|
58
62
|
const MAX_ISSUE_LIMIT = 100;
|
|
59
63
|
const DEFAULT_EVENTS_PER_ISSUE_READ = 50;
|
|
60
64
|
const SPARKLINE_DAYS = 14;
|
|
65
|
+
const SOURCE_CONTEXT_BEFORE = 4;
|
|
66
|
+
const SOURCE_CONTEXT_AFTER = 4;
|
|
67
|
+
const MAX_SOURCE_CONTEXT_FILE_BYTES = 2_000_000;
|
|
68
|
+
const MAX_SOURCE_CONTEXT_LINE_CHARS = 500;
|
|
61
69
|
|
|
62
70
|
// ---------------------------------------------------------------------------
|
|
63
71
|
// Pure helpers (unit tested)
|
|
@@ -70,6 +78,13 @@ export interface ParsedStackFrame {
|
|
|
70
78
|
colno: number | null;
|
|
71
79
|
inApp: boolean;
|
|
72
80
|
raw: string;
|
|
81
|
+
sourceContext?: SourceContextLine[];
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export interface SourceContextLine {
|
|
85
|
+
line: number;
|
|
86
|
+
text: string;
|
|
87
|
+
highlight: boolean;
|
|
73
88
|
}
|
|
74
89
|
|
|
75
90
|
const VENDOR_FILE_RE =
|
|
@@ -179,6 +194,172 @@ export function parseStack(
|
|
|
179
194
|
return frames;
|
|
180
195
|
}
|
|
181
196
|
|
|
197
|
+
export function sourceContextFromText(
|
|
198
|
+
source: string,
|
|
199
|
+
lineNumber: number | null | undefined,
|
|
200
|
+
options: { before?: number; after?: number } = {},
|
|
201
|
+
): SourceContextLine[] | null {
|
|
202
|
+
if (!lineNumber || lineNumber < 1) return null;
|
|
203
|
+
const lines = source.split(/\r?\n/);
|
|
204
|
+
if (lineNumber > lines.length) return null;
|
|
205
|
+
const before = options.before ?? SOURCE_CONTEXT_BEFORE;
|
|
206
|
+
const after = options.after ?? SOURCE_CONTEXT_AFTER;
|
|
207
|
+
const start = Math.max(1, lineNumber - before);
|
|
208
|
+
const end = Math.min(lines.length, lineNumber + after);
|
|
209
|
+
const context: SourceContextLine[] = [];
|
|
210
|
+
for (let line = start; line <= end; line += 1) {
|
|
211
|
+
const text = lines[line - 1] ?? "";
|
|
212
|
+
context.push({
|
|
213
|
+
line,
|
|
214
|
+
text:
|
|
215
|
+
text.length > MAX_SOURCE_CONTEXT_LINE_CHARS
|
|
216
|
+
? `${text.slice(0, MAX_SOURCE_CONTEXT_LINE_CHARS)}…`
|
|
217
|
+
: text,
|
|
218
|
+
highlight: line === lineNumber,
|
|
219
|
+
});
|
|
220
|
+
}
|
|
221
|
+
return context;
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const SOURCE_EXT_RE = /\.(?:[cm]?[jt]sx?|vue|svelte|css|scss|json)$/i;
|
|
225
|
+
const SOURCE_CONTEXT_ALLOWED_PREFIXES = ["app/"] as const;
|
|
226
|
+
|
|
227
|
+
function templateRootFrom(value: string): string | null {
|
|
228
|
+
const marker = `${path.sep}templates${path.sep}analytics`;
|
|
229
|
+
const index = value.indexOf(marker);
|
|
230
|
+
if (index < 0) return null;
|
|
231
|
+
return value.slice(0, index + marker.length);
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function sourceRoots(): string[] {
|
|
235
|
+
const roots = new Set<string>();
|
|
236
|
+
const cwdTemplateRoot = templateRootFrom(process.cwd());
|
|
237
|
+
roots.add(cwdTemplateRoot ?? process.cwd());
|
|
238
|
+
try {
|
|
239
|
+
const modulePath = fileURLToPath(import.meta.url);
|
|
240
|
+
const moduleTemplateRoot = templateRootFrom(modulePath);
|
|
241
|
+
if (moduleTemplateRoot) roots.add(moduleTemplateRoot);
|
|
242
|
+
} catch {
|
|
243
|
+
// import.meta.url is always file: in Node, but source context is best-effort.
|
|
244
|
+
}
|
|
245
|
+
return Array.from(roots).map((root) => path.resolve(root));
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
function isWithinRoot(candidate: string, root: string): boolean {
|
|
249
|
+
const relative = path.relative(root, candidate);
|
|
250
|
+
return (
|
|
251
|
+
relative === "" ||
|
|
252
|
+
(!relative.startsWith("..") && !path.isAbsolute(relative))
|
|
253
|
+
);
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
function cleanFrameFile(file: string | null): string | null {
|
|
257
|
+
if (!file) return null;
|
|
258
|
+
let cleaned = file.trim();
|
|
259
|
+
if (!cleaned || cleaned === "<anonymous>" || cleaned === "[native code]") {
|
|
260
|
+
return null;
|
|
261
|
+
}
|
|
262
|
+
if (/^https?:\/\//i.test(cleaned)) {
|
|
263
|
+
try {
|
|
264
|
+
cleaned = new URL(cleaned).pathname;
|
|
265
|
+
} catch {
|
|
266
|
+
return null;
|
|
267
|
+
}
|
|
268
|
+
} else if (cleaned.startsWith("file://")) {
|
|
269
|
+
try {
|
|
270
|
+
cleaned = fileURLToPath(cleaned);
|
|
271
|
+
} catch {
|
|
272
|
+
return null;
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
cleaned = cleaned.replace(/[?#].*$/, "");
|
|
276
|
+
try {
|
|
277
|
+
cleaned = decodeURIComponent(cleaned);
|
|
278
|
+
} catch {
|
|
279
|
+
// Keep the original path when a browser stack includes malformed escapes.
|
|
280
|
+
}
|
|
281
|
+
return cleaned;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export function trustedSourceRelativePath(cleaned: string): string | null {
|
|
285
|
+
const normalized = cleaned.replace(/\\/g, "/").replace(/^\/+/, "");
|
|
286
|
+
if (
|
|
287
|
+
!normalized ||
|
|
288
|
+
normalized.startsWith("../") ||
|
|
289
|
+
normalized.includes("/../")
|
|
290
|
+
) {
|
|
291
|
+
return null;
|
|
292
|
+
}
|
|
293
|
+
if (
|
|
294
|
+
!SOURCE_CONTEXT_ALLOWED_PREFIXES.some((prefix) =>
|
|
295
|
+
normalized.startsWith(prefix),
|
|
296
|
+
)
|
|
297
|
+
) {
|
|
298
|
+
return null;
|
|
299
|
+
}
|
|
300
|
+
return normalized;
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
async function resolveSourcePath(
|
|
304
|
+
frame: ParsedStackFrame,
|
|
305
|
+
): Promise<string | null> {
|
|
306
|
+
if (!frame.inApp) return null;
|
|
307
|
+
const cleaned = cleanFrameFile(frame.file);
|
|
308
|
+
if (!cleaned || !SOURCE_EXT_RE.test(cleaned)) return null;
|
|
309
|
+
const relativePath = trustedSourceRelativePath(cleaned);
|
|
310
|
+
if (!relativePath) return null;
|
|
311
|
+
const roots = sourceRoots();
|
|
312
|
+
const candidates = new Set<string>();
|
|
313
|
+
for (const root of roots) candidates.add(path.resolve(root, relativePath));
|
|
314
|
+
|
|
315
|
+
for (const candidate of candidates) {
|
|
316
|
+
if (!roots.some((root) => isWithinRoot(candidate, root))) continue;
|
|
317
|
+
try {
|
|
318
|
+
const fileStat = await stat(candidate);
|
|
319
|
+
if (
|
|
320
|
+
fileStat.isFile() &&
|
|
321
|
+
fileStat.size > 0 &&
|
|
322
|
+
fileStat.size <= MAX_SOURCE_CONTEXT_FILE_BYTES
|
|
323
|
+
) {
|
|
324
|
+
return candidate;
|
|
325
|
+
}
|
|
326
|
+
} catch {
|
|
327
|
+
// Try the next candidate.
|
|
328
|
+
}
|
|
329
|
+
}
|
|
330
|
+
return null;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
async function sourceContextForFrame(
|
|
334
|
+
frame: ParsedStackFrame,
|
|
335
|
+
): Promise<SourceContextLine[] | undefined> {
|
|
336
|
+
if (!frame.lineno) return undefined;
|
|
337
|
+
const sourcePath = await resolveSourcePath(frame);
|
|
338
|
+
if (!sourcePath) return undefined;
|
|
339
|
+
try {
|
|
340
|
+
const source = await readFile(sourcePath, "utf8");
|
|
341
|
+
return (
|
|
342
|
+
sourceContextFromText(source, frame.lineno, {
|
|
343
|
+
before: SOURCE_CONTEXT_BEFORE,
|
|
344
|
+
after: SOURCE_CONTEXT_AFTER,
|
|
345
|
+
}) ?? undefined
|
|
346
|
+
);
|
|
347
|
+
} catch {
|
|
348
|
+
return undefined;
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
async function addSourceContexts(
|
|
353
|
+
frames: ParsedStackFrame[],
|
|
354
|
+
): Promise<ParsedStackFrame[]> {
|
|
355
|
+
return Promise.all(
|
|
356
|
+
frames.map(async (frame) => {
|
|
357
|
+
const sourceContext = await sourceContextForFrame(frame);
|
|
358
|
+
return sourceContext?.length ? { ...frame, sourceContext } : frame;
|
|
359
|
+
}),
|
|
360
|
+
);
|
|
361
|
+
}
|
|
362
|
+
|
|
182
363
|
/** Strip content hashes + query/hash from a filename for stable grouping. */
|
|
183
364
|
export function normalizeFrameFile(file: string | null): string {
|
|
184
365
|
if (!file) return "";
|
|
@@ -1031,6 +1212,7 @@ export interface ErrorEventDetail {
|
|
|
1031
1212
|
culprit: string | null;
|
|
1032
1213
|
level: ExceptionLevel;
|
|
1033
1214
|
stack: ParsedStackFrame[];
|
|
1215
|
+
rawStack: string | null;
|
|
1034
1216
|
handled: boolean;
|
|
1035
1217
|
url: string | null;
|
|
1036
1218
|
userId: string | null;
|
|
@@ -1164,42 +1346,55 @@ export async function getErrorIssue(
|
|
|
1164
1346
|
);
|
|
1165
1347
|
|
|
1166
1348
|
const sessions = new Map<string, { recordingId: string; path: string }>();
|
|
1167
|
-
const events
|
|
1168
|
-
|
|
1169
|
-
|
|
1170
|
-
|
|
1171
|
-
|
|
1172
|
-
|
|
1173
|
-
|
|
1174
|
-
|
|
1175
|
-
|
|
1176
|
-
|
|
1177
|
-
|
|
1178
|
-
|
|
1179
|
-
|
|
1180
|
-
|
|
1181
|
-
|
|
1182
|
-
|
|
1183
|
-
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1191
|
-
|
|
1192
|
-
|
|
1193
|
-
|
|
1194
|
-
|
|
1195
|
-
|
|
1196
|
-
|
|
1197
|
-
|
|
1198
|
-
|
|
1199
|
-
|
|
1200
|
-
|
|
1201
|
-
|
|
1202
|
-
|
|
1349
|
+
const events = await Promise.all(
|
|
1350
|
+
eventRows.map(
|
|
1351
|
+
async (row: any, index: number): Promise<ErrorEventDetail> => {
|
|
1352
|
+
let recordingId: string | null = null;
|
|
1353
|
+
if (row.sessionRecordingId && byId.has(row.sessionRecordingId)) {
|
|
1354
|
+
recordingId = row.sessionRecordingId;
|
|
1355
|
+
} else if (
|
|
1356
|
+
row.clientRecordingId &&
|
|
1357
|
+
byClientId.has(row.clientRecordingId)
|
|
1358
|
+
) {
|
|
1359
|
+
recordingId = byClientId.get(row.clientRecordingId) ?? null;
|
|
1360
|
+
}
|
|
1361
|
+
if (recordingId && !sessions.has(recordingId)) {
|
|
1362
|
+
sessions.set(recordingId, {
|
|
1363
|
+
recordingId,
|
|
1364
|
+
path: `/sessions/${recordingId}`,
|
|
1365
|
+
});
|
|
1366
|
+
}
|
|
1367
|
+
return {
|
|
1368
|
+
id: row.id,
|
|
1369
|
+
type: row.type,
|
|
1370
|
+
message: row.message ?? "",
|
|
1371
|
+
culprit: row.culprit ?? null,
|
|
1372
|
+
level: coerceLevel(row.level),
|
|
1373
|
+
stack:
|
|
1374
|
+
index === 0
|
|
1375
|
+
? await addSourceContexts(
|
|
1376
|
+
parseJson<ParsedStackFrame[]>(row.stack, []),
|
|
1377
|
+
)
|
|
1378
|
+
: parseJson<ParsedStackFrame[]>(row.stack, []),
|
|
1379
|
+
rawStack: row.rawStack ?? null,
|
|
1380
|
+
handled: Boolean(row.handled),
|
|
1381
|
+
url: row.url ?? null,
|
|
1382
|
+
userId: row.userId ?? null,
|
|
1383
|
+
anonymousId: row.anonymousId ?? null,
|
|
1384
|
+
userKey: row.userKey ?? null,
|
|
1385
|
+
sessionId: row.sessionId ?? null,
|
|
1386
|
+
sessionRecordingId: recordingId,
|
|
1387
|
+
sessionRecordingPath: recordingPath(recordingId),
|
|
1388
|
+
release: row.release ?? null,
|
|
1389
|
+
environment: row.environment ?? null,
|
|
1390
|
+
tags: parseJson<Record<string, unknown>>(row.tags, {}),
|
|
1391
|
+
extra: parseJson<Record<string, unknown>>(row.extra, {}),
|
|
1392
|
+
breadcrumbs: parseJson<unknown[]>(row.breadcrumbs, []),
|
|
1393
|
+
occurredAt: row.occurredAt,
|
|
1394
|
+
};
|
|
1395
|
+
},
|
|
1396
|
+
),
|
|
1397
|
+
);
|
|
1203
1398
|
|
|
1204
1399
|
const sparklines = await sparklinesForIssues([issueId]);
|
|
1205
1400
|
const issue: ErrorIssueSummary = {
|
|
@@ -2,6 +2,7 @@ import { Buffer } from "node:buffer";
|
|
|
2
2
|
import { createHash, randomUUID } from "node:crypto";
|
|
3
3
|
import { gzipSync, gunzipSync } from "node:zlib";
|
|
4
4
|
|
|
5
|
+
import { readAppState } from "@agent-native/core/application-state";
|
|
5
6
|
import {
|
|
6
7
|
deletePrivateBlob,
|
|
7
8
|
putPrivateBlob,
|
|
@@ -237,6 +238,7 @@ const DEFAULT_REPLAY_MAX_REQUESTS_PER_MINUTE = 120;
|
|
|
237
238
|
const RETENTION_DELETE_BATCH_SIZE = 500;
|
|
238
239
|
const REPLAY_PRIVATE_BLOB_REF_KIND = "agent-native.session-replay.private-blob";
|
|
239
240
|
const REPLAY_PRIVATE_BLOB_REF_VERSION = 1;
|
|
241
|
+
const SESSION_DEMO_EMAIL_PATTERN = /[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}/gi;
|
|
240
242
|
let inlineReplayFallbackWarned = false;
|
|
241
243
|
|
|
242
244
|
function replayError(message: string, statusCode: number): Error {
|
|
@@ -274,6 +276,92 @@ function replayEmail(value: unknown): string | null {
|
|
|
274
276
|
return raw && raw.includes("@") ? raw : null;
|
|
275
277
|
}
|
|
276
278
|
|
|
279
|
+
function normalizedEmail(value: unknown): string | null {
|
|
280
|
+
return replayEmail(value)?.toLowerCase() ?? null;
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
function isBuilderEmail(value: unknown): boolean {
|
|
284
|
+
return normalizedEmail(value)?.endsWith("@builder.io") === true;
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
async function isSessionDemoModeEnabled(): Promise<boolean> {
|
|
288
|
+
if (process.env.DEMO_MODE === "true") return true;
|
|
289
|
+
try {
|
|
290
|
+
const state = await readAppState("demo-mode");
|
|
291
|
+
return state?.enabled === true;
|
|
292
|
+
} catch {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
function demoBuilderSessionCondition() {
|
|
298
|
+
return or(
|
|
299
|
+
sql`lower(${schema.sessionRecordings.userId}) like ${"%@builder.io"}`,
|
|
300
|
+
sql`lower(${schema.sessionRecordings.userKey}) like ${"%@builder.io"}`,
|
|
301
|
+
);
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
class SessionDemoAnonymizer {
|
|
305
|
+
private readonly aliases = new Map<string, string>();
|
|
306
|
+
|
|
307
|
+
aliasFor(value: unknown): string | null {
|
|
308
|
+
const raw = replayString(value);
|
|
309
|
+
if (!raw || raw.match(SESSION_DEMO_EMAIL_PATTERN)?.[0] !== raw) {
|
|
310
|
+
return null;
|
|
311
|
+
}
|
|
312
|
+
const email = raw.toLowerCase();
|
|
313
|
+
let alias = this.aliases.get(email);
|
|
314
|
+
if (!alias) {
|
|
315
|
+
alias = `anonymized-${this.aliases.size + 1}@builder.io`;
|
|
316
|
+
this.aliases.set(email, alias);
|
|
317
|
+
}
|
|
318
|
+
return alias;
|
|
319
|
+
}
|
|
320
|
+
|
|
321
|
+
summarize(row: any, role?: SessionReplayAccessRole): SessionRecordingSummary {
|
|
322
|
+
const summary = rowToSessionRecordingSummary(row, role);
|
|
323
|
+
return {
|
|
324
|
+
...summary,
|
|
325
|
+
userId: this.aliasFor(row.userId) ?? summary.userId,
|
|
326
|
+
userKey: this.aliasFor(row.userKey) ?? summary.userKey,
|
|
327
|
+
ownerEmail: this.aliasFor(summary.ownerEmail) ?? summary.ownerEmail,
|
|
328
|
+
metadata: anonymizeEmailValue(summary.metadata, this) as Record<
|
|
329
|
+
string,
|
|
330
|
+
unknown
|
|
331
|
+
>,
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
function anonymizeEmailValue(
|
|
337
|
+
value: unknown,
|
|
338
|
+
anonymizer: SessionDemoAnonymizer,
|
|
339
|
+
): unknown {
|
|
340
|
+
const alias = anonymizer.aliasFor(value);
|
|
341
|
+
if (alias) return alias;
|
|
342
|
+
if (Array.isArray(value)) {
|
|
343
|
+
return value.map((item) => anonymizeEmailValue(item, anonymizer));
|
|
344
|
+
}
|
|
345
|
+
if (typeof value === "string") {
|
|
346
|
+
return value.replace(SESSION_DEMO_EMAIL_PATTERN, (email) => {
|
|
347
|
+
return anonymizer.aliasFor(email) ?? email;
|
|
348
|
+
});
|
|
349
|
+
}
|
|
350
|
+
if (value && typeof value === "object") {
|
|
351
|
+
const out: Record<string, unknown> = {};
|
|
352
|
+
for (const [key, item] of Object.entries(value)) {
|
|
353
|
+
out[key] = anonymizeEmailValue(item, anonymizer);
|
|
354
|
+
}
|
|
355
|
+
return out;
|
|
356
|
+
}
|
|
357
|
+
return value;
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
function assertDemoVisibleSession(row: any): void {
|
|
361
|
+
if (isBuilderEmail(row.userId) || isBuilderEmail(row.userKey)) return;
|
|
362
|
+
throw replayError("Session recording not found", 404);
|
|
363
|
+
}
|
|
364
|
+
|
|
277
365
|
function replayInteger(value: unknown): number | null {
|
|
278
366
|
if (typeof value === "number" && Number.isInteger(value)) return value;
|
|
279
367
|
if (typeof value === "string" && /^-?\d+$/.test(value.trim())) {
|
|
@@ -580,16 +668,16 @@ function abandonedReplayMinutes(): number {
|
|
|
580
668
|
);
|
|
581
669
|
}
|
|
582
670
|
|
|
583
|
-
function
|
|
671
|
+
function inlineReplayFallbackAllowed(): boolean {
|
|
584
672
|
if (process.env.NODE_ENV !== "production") return true;
|
|
585
|
-
return
|
|
673
|
+
return false;
|
|
586
674
|
}
|
|
587
675
|
|
|
588
676
|
function warnInlineReplayFallback(): void {
|
|
589
677
|
if (inlineReplayFallbackWarned) return;
|
|
590
678
|
inlineReplayFallbackWarned = true;
|
|
591
679
|
console.warn(
|
|
592
|
-
"[session-replay] Private blob storage is not configured; storing capped replay chunks inline in SQL. This is
|
|
680
|
+
"[session-replay] Private blob storage is not configured; storing capped replay chunks inline in SQL. This is for local/dev use only.",
|
|
593
681
|
);
|
|
594
682
|
}
|
|
595
683
|
|
|
@@ -619,9 +707,9 @@ async function storeReplayChunkBlob(
|
|
|
619
707
|
},
|
|
620
708
|
});
|
|
621
709
|
if (!handle) {
|
|
622
|
-
if (!
|
|
710
|
+
if (!inlineReplayFallbackAllowed()) {
|
|
623
711
|
throw replayError(
|
|
624
|
-
"Session replay blob storage is required in production. Configure a private blob provider
|
|
712
|
+
"Session replay blob storage is required in production. Configure a private blob provider before enabling full snapshot playback.",
|
|
625
713
|
503,
|
|
626
714
|
);
|
|
627
715
|
}
|
|
@@ -1580,6 +1668,7 @@ export async function listSessionRecordings(
|
|
|
1580
1668
|
filters: SessionReplayListFilters = {},
|
|
1581
1669
|
): Promise<SessionRecordingSummary[]> {
|
|
1582
1670
|
const db = getDb() as any;
|
|
1671
|
+
const demoMode = await isSessionDemoModeEnabled();
|
|
1583
1672
|
const limit = Math.min(
|
|
1584
1673
|
MAX_SESSION_RECORDINGS_LIMIT,
|
|
1585
1674
|
Math.max(1, filters.limit ?? DEFAULT_SESSION_RECORDINGS_LIMIT),
|
|
@@ -1592,6 +1681,7 @@ export async function listSessionRecordings(
|
|
|
1592
1681
|
replayVisibleIdentityCondition(),
|
|
1593
1682
|
replayPlayableEventsCondition(),
|
|
1594
1683
|
];
|
|
1684
|
+
if (demoMode) conditions.push(demoBuilderSessionCondition());
|
|
1595
1685
|
if (filters.app)
|
|
1596
1686
|
conditions.push(eq(schema.sessionRecordings.app, filters.app));
|
|
1597
1687
|
if (filters.template) {
|
|
@@ -1645,13 +1735,22 @@ export async function listSessionRecordings(
|
|
|
1645
1735
|
.where(and(...conditions))
|
|
1646
1736
|
.orderBy(desc(schema.sessionRecordings.startedAt))
|
|
1647
1737
|
.limit(limit);
|
|
1648
|
-
|
|
1738
|
+
if (!demoMode) {
|
|
1739
|
+
return rows.map((row: any) => rowToSessionRecordingSummary(row));
|
|
1740
|
+
}
|
|
1741
|
+
const anonymizer = new SessionDemoAnonymizer();
|
|
1742
|
+
return rows
|
|
1743
|
+
.filter(
|
|
1744
|
+
(row: any) => isBuilderEmail(row.userId) || isBuilderEmail(row.userKey),
|
|
1745
|
+
)
|
|
1746
|
+
.map((row: any) => anonymizer.summarize(row));
|
|
1649
1747
|
}
|
|
1650
1748
|
|
|
1651
1749
|
export async function getSessionReplaySummary(
|
|
1652
1750
|
recordingId: string,
|
|
1653
1751
|
scope: SessionReplayScope,
|
|
1654
1752
|
): Promise<SessionRecordingSummary> {
|
|
1753
|
+
const demoMode = await isSessionDemoModeEnabled();
|
|
1655
1754
|
const access = await resolveAccess("session-recording", recordingId, {
|
|
1656
1755
|
userEmail: scope.userEmail,
|
|
1657
1756
|
orgId: scope.orgId ?? undefined,
|
|
@@ -1660,12 +1759,17 @@ export async function getSessionReplaySummary(
|
|
|
1660
1759
|
if (!isVisibleSessionRecording(access.resource)) {
|
|
1661
1760
|
throw replayError("Session recording not found", 404);
|
|
1662
1761
|
}
|
|
1663
|
-
|
|
1762
|
+
if (!demoMode) {
|
|
1763
|
+
return rowToSessionRecordingSummary(access.resource, access.role);
|
|
1764
|
+
}
|
|
1765
|
+
assertDemoVisibleSession(access.resource);
|
|
1766
|
+
return new SessionDemoAnonymizer().summarize(access.resource, access.role);
|
|
1664
1767
|
}
|
|
1665
1768
|
|
|
1666
1769
|
export async function getSessionReplayTokenizedSummary(
|
|
1667
1770
|
recordingId: string,
|
|
1668
1771
|
): Promise<SessionRecordingSummary> {
|
|
1772
|
+
const demoMode = await isSessionDemoModeEnabled();
|
|
1669
1773
|
const db = getDb() as any;
|
|
1670
1774
|
// guard:allow-unscoped -- called only after verifySessionReplayAgentAccess(recordingId, token) verifies a signed, recording-scoped agent_access token.
|
|
1671
1775
|
const [row] = await db
|
|
@@ -1676,7 +1780,9 @@ export async function getSessionReplayTokenizedSummary(
|
|
|
1676
1780
|
if (!row || !isVisibleSessionRecording(row)) {
|
|
1677
1781
|
throw replayError("Session recording not found", 404);
|
|
1678
1782
|
}
|
|
1679
|
-
return rowToSessionRecordingSummary(row, "viewer");
|
|
1783
|
+
if (!demoMode) return rowToSessionRecordingSummary(row, "viewer");
|
|
1784
|
+
assertDemoVisibleSession(row);
|
|
1785
|
+
return new SessionDemoAnonymizer().summarize(row, "viewer");
|
|
1680
1786
|
}
|
|
1681
1787
|
|
|
1682
1788
|
function parseInlineReplayEvents(inlineData: string): unknown[] {
|
|
@@ -203,6 +203,7 @@ const MAX_REDIRECT_HOPS = 5;
|
|
|
203
203
|
const MONITOR_RUNNING_STALE_MS = 5 * 60 * 1000;
|
|
204
204
|
const DEFAULT_RESULT_RETENTION_DAYS = 30;
|
|
205
205
|
const DEFAULT_MONITOR_LIMIT_PER_OWNER = 100;
|
|
206
|
+
export const DEFAULT_MONITOR_TIMEOUT_MS = 10_000;
|
|
206
207
|
const MAX_REQUEST_HEADER_COUNT = 20;
|
|
207
208
|
const MAX_REQUEST_HEADER_NAME_LENGTH = 128;
|
|
208
209
|
const MAX_REQUEST_HEADER_VALUE_BYTES = 2048;
|
|
@@ -822,7 +823,7 @@ export async function runMonitorCheck(
|
|
|
822
823
|
monitor.timeoutMs,
|
|
823
824
|
MIN_TIMEOUT_MS,
|
|
824
825
|
MAX_TIMEOUT_MS,
|
|
825
|
-
|
|
826
|
+
DEFAULT_MONITOR_TIMEOUT_MS,
|
|
826
827
|
);
|
|
827
828
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
828
829
|
const start = Date.now();
|
|
@@ -925,7 +926,7 @@ function rowToMonitor(row: any): Monitor {
|
|
|
925
926
|
),
|
|
926
927
|
requestBody: row.requestBody ?? null,
|
|
927
928
|
intervalSeconds: Number(row.intervalSeconds ?? 300),
|
|
928
|
-
timeoutMs: Number(row.timeoutMs ??
|
|
929
|
+
timeoutMs: Number(row.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS),
|
|
929
930
|
expectedStatus: normalizeStatusMatcher(
|
|
930
931
|
safeJsonParse<unknown>(row.expectedStatus, null),
|
|
931
932
|
),
|
|
@@ -1155,10 +1156,10 @@ export async function saveMonitor(
|
|
|
1155
1156
|
300,
|
|
1156
1157
|
);
|
|
1157
1158
|
const timeoutMs = clampInt(
|
|
1158
|
-
input.timeoutMs ??
|
|
1159
|
+
input.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS,
|
|
1159
1160
|
MIN_TIMEOUT_MS,
|
|
1160
1161
|
MAX_TIMEOUT_MS,
|
|
1161
|
-
|
|
1162
|
+
DEFAULT_MONITOR_TIMEOUT_MS,
|
|
1162
1163
|
);
|
|
1163
1164
|
const expectedStatus = normalizeStatusMatcher(input.expectedStatus);
|
|
1164
1165
|
const assertions = normalizeAssertions(input.assertions);
|