@agent-native/core 0.91.2 → 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 +2 -2
- package/corpus/core/CHANGELOG.md +31 -0
- package/corpus/core/docs/content/faq.mdx +17 -0
- package/corpus/core/docs/content/notifications.mdx +12 -6
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/production-agent.ts +140 -46
- package/corpus/core/src/agent/tool-search.ts +71 -4
- package/corpus/core/src/application-state/store.ts +12 -1
- package/corpus/core/src/cli/index.ts +20 -0
- package/corpus/core/src/cli/upgrade.ts +798 -0
- package/corpus/core/src/client/AssistantChat.tsx +28 -15
- package/corpus/core/src/client/HighlightedCodeBlock.tsx +20 -3
- package/corpus/core/src/client/PoweredByBadge.tsx +1 -8
- package/corpus/core/src/client/agent-chat-adapter.ts +68 -16
- package/corpus/core/src/client/chat/message-components.tsx +85 -8
- package/corpus/core/src/client/chat/tool-call-display.tsx +355 -144
- package/corpus/core/src/client/chat/widgets/DataTableWidget.tsx +52 -1
- package/corpus/core/src/client/code-agent-chat-adapter.ts +2 -9
- 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/session-replay.ts +3 -97
- package/corpus/core/src/client/settings/SettingsPanel.tsx +738 -441
- package/corpus/core/src/client/settings/SettingsSection.tsx +161 -17
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +25 -9
- package/corpus/core/src/client/sse-event-processor.ts +29 -2
- 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/actions.ts +113 -5
- package/corpus/core/src/extensions/html-shell.ts +5 -4
- package/corpus/core/src/extensions/routes.ts +10 -0
- package/corpus/core/src/navigation/index.ts +1 -1
- package/corpus/core/src/notifications/channels.ts +88 -25
- package/corpus/core/src/notifications/registry.ts +17 -3
- package/corpus/core/src/notifications/types.ts +4 -1
- package/corpus/core/src/resources/handlers.ts +10 -8
- package/corpus/core/src/server/agent-chat-plugin.ts +7 -3
- package/corpus/core/src/server/poll.ts +293 -8
- package/corpus/core/src/server/request-context.ts +7 -0
- package/corpus/core/src/styles/agent-conversation.css +1 -1
- 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/self-modifying-code/SKILL.md +20 -2
- package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/default/AGENTS.md +14 -5
- package/corpus/core/src/templates/default/package.json +2 -1
- package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/headless/AGENTS.md +13 -3
- package/corpus/core/src/templates/headless/package.json +1 -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/self-modifying-code/SKILL.md +20 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/core/src/templates/workspace-core/AGENTS.md +7 -1
- package/corpus/core/src/templates/workspace-root/AGENTS.md +17 -5
- package/corpus/core/src/templates/workspace-root/package.json +1 -0
- 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/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/analytics/.agents/skills/session-replay/SKILL.md +5 -6
- package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/analytics/AGENTS.md +39 -18
- 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 +15 -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/dashboard/DataTable.tsx +41 -6
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +122 -11
- 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/MonitoringPage.tsx +8 -2
- 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 +157 -35
- package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +17 -2
- package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +4 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +80 -220
- 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-tables-keep-their-size-while-loading.md +6 -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-monitoring-uptime-and-errors-tabs-now-clearly-show-which-view-is-active.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-session-replay-uses-stock-rrweb-sizing-again-so-recorded.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-sidebar-dashboard-menus-now-open-above-the-dashboard-canvas.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-alerting-clarifies-in-app-inbox-vs-email-and-lets-you-paste-slack-or-webhook-urls-per-monitor.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-use-a-10-second-request-timeout-by-defau.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +13 -11
- package/corpus/templates/analytics/server/db/schema-monitoring.ts +5 -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 +65 -16
- package/corpus/templates/analytics/server/plugins/db.ts +122 -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/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- 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/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +87 -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/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- 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/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +87 -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/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +87 -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-desktop-meeting-notes-stay-active-from-tray-and-shortcut-controls.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-desktop-meeting-popovers-can-start-notes-and-join-the-meetin.md +6 -0
- 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-notes-keep-their-recording-state-when-the-desktop.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-meeting-reminder-dropdowns-now-expand-fully-instead-of-being.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/desktop/src/app.tsx +123 -3
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +15 -1
- package/corpus/templates/clips/desktop/src/overlays/meeting-notification.tsx +23 -0
- package/corpus/templates/clips/desktop/src/styles.css +23 -1
- package/corpus/templates/clips/desktop/src-tauri/src/adhoc_meetings_watcher.rs +1 -3
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +40 -6
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -2
- package/corpus/templates/clips/desktop/src-tauri/src/meetings_watcher.rs +3 -5
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +23 -19
- package/corpus/templates/clips/desktop/src-tauri/src/shortcuts.rs +3 -3
- package/corpus/templates/clips/desktop/src-tauri/src/silence_detector.rs +0 -1
- package/corpus/templates/clips/desktop/src-tauri/src/state.rs +5 -0
- package/corpus/templates/clips/desktop/src-tauri/src/tray.rs +18 -6
- 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/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/content/AGENTS.md +4 -0
- package/corpus/templates/content/actions/_batch-utils.ts +8 -0
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +7 -5
- package/corpus/templates/content/actions/_database-source-utils.ts +776 -319
- package/corpus/templates/content/actions/_database-utils.ts +6 -1
- package/corpus/templates/content/actions/_property-utils.ts +168 -0
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +19 -12
- package/corpus/templates/content/actions/bind-content-database-source-field.ts +19 -12
- package/corpus/templates/content/actions/delete-document.ts +9 -13
- package/corpus/templates/content/actions/refresh-content-database-source.ts +5 -5
- package/corpus/templates/content/app/components/editor/body-hydration.ts +35 -11
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +47 -5
- package/corpus/templates/content/app/hooks/use-documents.ts +8 -2
- 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/content/changelog/2026-07-08-builder-source-sync-now-completes-reliably-on-hosted-databases.md +6 -0
- package/corpus/templates/content/changelog/2026-07-08-fixed-builder-database-sync-so-large-sources-continue-past-t.md +6 -0
- package/corpus/templates/content/server/plugins/db.ts +19 -0
- 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/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +87 -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/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +87 -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/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- 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/skills/upgrade-agent-native/SKILL.md +87 -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/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- 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/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +87 -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/skills/self-modifying-code/SKILL.md +20 -2
- package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +87 -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/corpus/templates/videos/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/corpus/templates/voice/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/agent/production-agent.d.ts +5 -1
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +112 -42
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/agent/tool-search.d.ts +2 -0
- package/dist/agent/tool-search.d.ts.map +1 -1
- package/dist/agent/tool-search.js +60 -4
- package/dist/agent/tool-search.js.map +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +8 -2
- package/dist/application-state/store.js.map +1 -1
- package/dist/cli/index.js +19 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/upgrade.d.ts +100 -0
- package/dist/cli/upgrade.d.ts.map +1 -0
- package/dist/cli/upgrade.js +678 -0
- package/dist/cli/upgrade.js.map +1 -0
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +25 -14
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/HighlightedCodeBlock.d.ts +2 -0
- package/dist/client/HighlightedCodeBlock.d.ts.map +1 -1
- package/dist/client/HighlightedCodeBlock.js +10 -1
- package/dist/client/HighlightedCodeBlock.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 +60 -13
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +61 -8
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +12 -0
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +136 -42
- package/dist/client/chat/tool-call-display.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/code-agent-chat-adapter.js +2 -9
- package/dist/client/code-agent-chat-adapter.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/session-replay.d.ts +0 -8
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +3 -80
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +89 -33
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsSection.d.ts +19 -4
- package/dist/client/settings/SettingsSection.d.ts.map +1 -1
- package/dist/client/settings/SettingsSection.js +33 -4
- 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/client/sse-event-processor.d.ts +9 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +20 -2
- package/dist/client/sse-event-processor.js.map +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/actions.d.ts.map +1 -1
- package/dist/extensions/actions.js +85 -5
- package/dist/extensions/actions.js.map +1 -1
- package/dist/extensions/html-shell.d.ts +5 -4
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +8 -0
- package/dist/extensions/routes.js.map +1 -1
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +1 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/notifications/channels.d.ts +8 -3
- package/dist/notifications/channels.d.ts.map +1 -1
- package/dist/notifications/channels.js +61 -22
- package/dist/notifications/channels.js.map +1 -1
- package/dist/notifications/registry.d.ts.map +1 -1
- package/dist/notifications/registry.js +14 -3
- package/dist/notifications/registry.js.map +1 -1
- package/dist/notifications/types.d.ts +4 -1
- package/dist/notifications/types.d.ts.map +1 -1
- package/dist/notifications/types.js.map +1 -1
- package/dist/resources/handlers.d.ts +5 -0
- package/dist/resources/handlers.d.ts.map +1 -1
- package/dist/resources/handlers.js +9 -8
- package/dist/resources/handlers.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts +5 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +2 -2
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/poll.d.ts +12 -4
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +236 -9
- package/dist/server/poll.js.map +1 -1
- package/dist/server/request-context.d.ts +7 -0
- package/dist/server/request-context.d.ts.map +1 -1
- package/dist/server/request-context.js.map +1 -1
- package/dist/styles/agent-conversation.css +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/self-modifying-code/SKILL.md +20 -2
- package/dist/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/default/AGENTS.md +14 -5
- package/dist/templates/default/package.json +2 -1
- package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/headless/AGENTS.md +13 -3
- package/dist/templates/headless/package.json +1 -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/self-modifying-code/SKILL.md +20 -2
- package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/dist/templates/workspace-core/AGENTS.md +7 -1
- package/dist/templates/workspace-root/AGENTS.md +17 -5
- package/dist/templates/workspace-root/package.json +1 -0
- package/docs/content/faq.mdx +17 -0
- package/docs/content/notifications.mdx +12 -6
- package/package.json +2 -2
- 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/self-modifying-code/SKILL.md +20 -2
- package/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/default/AGENTS.md +14 -5
- package/src/templates/default/package.json +2 -1
- package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/headless/AGENTS.md +13 -3
- package/src/templates/headless/package.json +1 -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/self-modifying-code/SKILL.md +20 -2
- package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +87 -0
- package/src/templates/workspace-core/AGENTS.md +7 -1
- package/src/templates/workspace-root/AGENTS.md +17 -5
- package/src/templates/workspace-root/package.json +1 -0
- package/corpus/templates/analytics/changelog/2026-07-08-session-replay-clamps-ultra-wide-viewports-while-keeping-the.md +0 -6
|
@@ -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[] {
|
|
@@ -101,6 +101,8 @@ export interface MonitorInput {
|
|
|
101
101
|
severity?: MonitorSeverity;
|
|
102
102
|
channels?: string[];
|
|
103
103
|
emailRecipients?: string[];
|
|
104
|
+
slackWebhookUrl?: string | null;
|
|
105
|
+
webhookUrl?: string | null;
|
|
104
106
|
cooldownMinutes?: number;
|
|
105
107
|
enabled?: boolean;
|
|
106
108
|
}
|
|
@@ -120,6 +122,8 @@ export interface Monitor {
|
|
|
120
122
|
severity: MonitorSeverity;
|
|
121
123
|
channels: string[];
|
|
122
124
|
emailRecipients: string[];
|
|
125
|
+
slackWebhookUrl: string | null;
|
|
126
|
+
webhookUrl: string | null;
|
|
123
127
|
cooldownMinutes: number;
|
|
124
128
|
enabled: boolean;
|
|
125
129
|
lastStatus: MonitorStatus | null;
|
|
@@ -199,6 +203,7 @@ const MAX_REDIRECT_HOPS = 5;
|
|
|
199
203
|
const MONITOR_RUNNING_STALE_MS = 5 * 60 * 1000;
|
|
200
204
|
const DEFAULT_RESULT_RETENTION_DAYS = 30;
|
|
201
205
|
const DEFAULT_MONITOR_LIMIT_PER_OWNER = 100;
|
|
206
|
+
export const DEFAULT_MONITOR_TIMEOUT_MS = 10_000;
|
|
202
207
|
const MAX_REQUEST_HEADER_COUNT = 20;
|
|
203
208
|
const MAX_REQUEST_HEADER_NAME_LENGTH = 128;
|
|
204
209
|
const MAX_REQUEST_HEADER_VALUE_BYTES = 2048;
|
|
@@ -462,11 +467,50 @@ function normalizeEmailRecipients(recipients: string[] | undefined): string[] {
|
|
|
462
467
|
return normalized;
|
|
463
468
|
}
|
|
464
469
|
|
|
470
|
+
function normalizeOptionalHttpUrl(
|
|
471
|
+
value: string | null | undefined,
|
|
472
|
+
label: string,
|
|
473
|
+
): string | null {
|
|
474
|
+
const trimmed = typeof value === "string" ? value.trim() : "";
|
|
475
|
+
if (!trimmed) return null;
|
|
476
|
+
let parsed: URL;
|
|
477
|
+
try {
|
|
478
|
+
parsed = new URL(trimmed);
|
|
479
|
+
} catch {
|
|
480
|
+
throw badRequest(`${label} must be an absolute http(s) URL`);
|
|
481
|
+
}
|
|
482
|
+
if (parsed.protocol !== "http:" && parsed.protocol !== "https:") {
|
|
483
|
+
throw badRequest(`${label} must be an absolute http(s) URL`);
|
|
484
|
+
}
|
|
485
|
+
return trimmed;
|
|
486
|
+
}
|
|
487
|
+
|
|
465
488
|
function ensureInboxChannel(channels: string[]): string[] {
|
|
466
489
|
const normalized = channels.map((c) => c.trim()).filter(Boolean);
|
|
467
490
|
return normalized.includes("inbox") ? normalized : ["inbox", ...normalized];
|
|
468
491
|
}
|
|
469
492
|
|
|
493
|
+
function monitorNotifyMetadata(monitor: Monitor): Record<string, unknown> {
|
|
494
|
+
return {
|
|
495
|
+
kind: "uptime_monitor",
|
|
496
|
+
monitorId: monitor.id,
|
|
497
|
+
monitorName: monitor.name,
|
|
498
|
+
url: monitor.url,
|
|
499
|
+
emailRecipients: monitor.emailRecipients,
|
|
500
|
+
requestedChannels: monitor.channels,
|
|
501
|
+
};
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
function monitorNotifyDeliveryMetadata(
|
|
505
|
+
monitor: Monitor,
|
|
506
|
+
): Record<string, string> | undefined {
|
|
507
|
+
const delivery: Record<string, string> = {};
|
|
508
|
+
if (monitor.slackWebhookUrl)
|
|
509
|
+
delivery.slackWebhookUrl = monitor.slackWebhookUrl;
|
|
510
|
+
if (monitor.webhookUrl) delivery.webhookUrl = monitor.webhookUrl;
|
|
511
|
+
return Object.keys(delivery).length > 0 ? delivery : undefined;
|
|
512
|
+
}
|
|
513
|
+
|
|
470
514
|
// ---------------------------------------------------------------------------
|
|
471
515
|
// Pure evaluation core (unit-tested)
|
|
472
516
|
// ---------------------------------------------------------------------------
|
|
@@ -779,7 +823,7 @@ export async function runMonitorCheck(
|
|
|
779
823
|
monitor.timeoutMs,
|
|
780
824
|
MIN_TIMEOUT_MS,
|
|
781
825
|
MAX_TIMEOUT_MS,
|
|
782
|
-
|
|
826
|
+
DEFAULT_MONITOR_TIMEOUT_MS,
|
|
783
827
|
);
|
|
784
828
|
const timer = setTimeout(() => controller.abort(), timeoutMs);
|
|
785
829
|
const start = Date.now();
|
|
@@ -882,7 +926,7 @@ function rowToMonitor(row: any): Monitor {
|
|
|
882
926
|
),
|
|
883
927
|
requestBody: row.requestBody ?? null,
|
|
884
928
|
intervalSeconds: Number(row.intervalSeconds ?? 300),
|
|
885
|
-
timeoutMs: Number(row.timeoutMs ??
|
|
929
|
+
timeoutMs: Number(row.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS),
|
|
886
930
|
expectedStatus: normalizeStatusMatcher(
|
|
887
931
|
safeJsonParse<unknown>(row.expectedStatus, null),
|
|
888
932
|
),
|
|
@@ -891,6 +935,8 @@ function rowToMonitor(row: any): Monitor {
|
|
|
891
935
|
severity: row.severity === "warning" ? "warning" : "critical",
|
|
892
936
|
channels: safeJsonParse<string[]>(row.channels, ["inbox"]),
|
|
893
937
|
emailRecipients: safeJsonParse<string[]>(row.emailRecipients, []),
|
|
938
|
+
slackWebhookUrl: row.slackWebhookUrl?.trim() || null,
|
|
939
|
+
webhookUrl: row.webhookUrl?.trim() || null,
|
|
894
940
|
cooldownMinutes: Number(row.cooldownMinutes ?? 15),
|
|
895
941
|
enabled: row.enabled === true || row.enabled === 1,
|
|
896
942
|
lastStatus: (row.lastStatus ?? null) as MonitorStatus | null,
|
|
@@ -1110,10 +1156,10 @@ export async function saveMonitor(
|
|
|
1110
1156
|
300,
|
|
1111
1157
|
);
|
|
1112
1158
|
const timeoutMs = clampInt(
|
|
1113
|
-
input.timeoutMs ??
|
|
1159
|
+
input.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS,
|
|
1114
1160
|
MIN_TIMEOUT_MS,
|
|
1115
1161
|
MAX_TIMEOUT_MS,
|
|
1116
|
-
|
|
1162
|
+
DEFAULT_MONITOR_TIMEOUT_MS,
|
|
1117
1163
|
);
|
|
1118
1164
|
const expectedStatus = normalizeStatusMatcher(input.expectedStatus);
|
|
1119
1165
|
const assertions = normalizeAssertions(input.assertions);
|
|
@@ -1121,6 +1167,11 @@ export async function saveMonitor(
|
|
|
1121
1167
|
const severity = input.severity === "warning" ? "warning" : "critical";
|
|
1122
1168
|
const emailRecipients = normalizeEmailRecipients(input.emailRecipients);
|
|
1123
1169
|
const channels = normalizeChannels(input.channels, emailRecipients);
|
|
1170
|
+
const slackWebhookUrl = normalizeOptionalHttpUrl(
|
|
1171
|
+
input.slackWebhookUrl,
|
|
1172
|
+
"Slack webhook URL",
|
|
1173
|
+
);
|
|
1174
|
+
const webhookUrl = normalizeOptionalHttpUrl(input.webhookUrl, "Webhook URL");
|
|
1124
1175
|
const cooldownMinutes = clampInt(input.cooldownMinutes ?? 15, 0, 24 * 60, 15);
|
|
1125
1176
|
const enabled = input.enabled ?? true;
|
|
1126
1177
|
|
|
@@ -1138,6 +1189,8 @@ export async function saveMonitor(
|
|
|
1138
1189
|
severity,
|
|
1139
1190
|
channels: JSON.stringify(channels),
|
|
1140
1191
|
emailRecipients: JSON.stringify(emailRecipients),
|
|
1192
|
+
slackWebhookUrl,
|
|
1193
|
+
webhookUrl,
|
|
1141
1194
|
cooldownMinutes,
|
|
1142
1195
|
enabled,
|
|
1143
1196
|
};
|
|
@@ -1440,16 +1493,14 @@ async function notifyMonitorDown(monitor: Monitor, outcome: CheckOutcome) {
|
|
|
1440
1493
|
body: `${host} is ${label}${detail}.${latency}`,
|
|
1441
1494
|
channels: ensureInboxChannel(monitor.channels),
|
|
1442
1495
|
metadata: {
|
|
1443
|
-
|
|
1444
|
-
|
|
1445
|
-
|
|
1446
|
-
|
|
1496
|
+
...monitorNotifyMetadata(monitor),
|
|
1497
|
+
...(monitorNotifyDeliveryMetadata(monitor)
|
|
1498
|
+
? { delivery: monitorNotifyDeliveryMetadata(monitor) }
|
|
1499
|
+
: {}),
|
|
1447
1500
|
status: outcome.status,
|
|
1448
1501
|
statusCode: outcome.statusCode,
|
|
1449
1502
|
latencyMs: outcome.latencyMs,
|
|
1450
1503
|
failedAssertions: outcome.failedAssertions,
|
|
1451
|
-
emailRecipients: monitor.emailRecipients,
|
|
1452
|
-
requestedChannels: monitor.channels,
|
|
1453
1504
|
},
|
|
1454
1505
|
},
|
|
1455
1506
|
{ owner: monitor.ownerEmail },
|
|
@@ -1475,16 +1526,14 @@ async function notifyMonitorRecovered(
|
|
|
1475
1526
|
body: `${host} is back up${downFor ? ` after ${downFor} of downtime` : ""}.${latency}`,
|
|
1476
1527
|
channels: ensureInboxChannel(monitor.channels),
|
|
1477
1528
|
metadata: {
|
|
1478
|
-
|
|
1479
|
-
|
|
1480
|
-
|
|
1481
|
-
|
|
1529
|
+
...monitorNotifyMetadata(monitor),
|
|
1530
|
+
...(monitorNotifyDeliveryMetadata(monitor)
|
|
1531
|
+
? { delivery: monitorNotifyDeliveryMetadata(monitor) }
|
|
1532
|
+
: {}),
|
|
1482
1533
|
status: "up",
|
|
1483
1534
|
statusCode: outcome.statusCode,
|
|
1484
1535
|
latencyMs: outcome.latencyMs,
|
|
1485
1536
|
incidentId: incident.id,
|
|
1486
|
-
emailRecipients: monitor.emailRecipients,
|
|
1487
|
-
requestedChannels: monitor.channels,
|
|
1488
1537
|
},
|
|
1489
1538
|
},
|
|
1490
1539
|
{ owner: monitor.ownerEmail },
|