@agent-native/core 0.92.0 → 0.92.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/core/CHANGELOG.md +20 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/application-state/store.ts +12 -1
- package/corpus/core/src/cli/sync-builder-starter-manifest.ts +29 -1
- package/corpus/core/src/client/AssistantChat.tsx +27 -14
- package/corpus/core/src/client/PoweredByBadge.tsx +1 -8
- package/corpus/core/src/client/agent-chat-adapter.ts +37 -10
- package/corpus/core/src/client/chat/widgets/DataTableWidget.tsx +52 -1
- package/corpus/core/src/client/db-admin/TableEditor.tsx +49 -0
- package/corpus/core/src/client/db-admin/export-utils.ts +13 -4
- package/corpus/core/src/client/db-admin/useAgentSync.ts +72 -0
- package/corpus/core/src/client/error-capture.ts +69 -7
- package/corpus/core/src/client/extensions/EmbeddedExtension.tsx +5 -1
- package/corpus/core/src/client/extensions/ExtensionViewer.tsx +53 -8
- package/corpus/core/src/client/extensions/InlineExtensionFrame.tsx +5 -1
- package/corpus/core/src/client/extensions/portable-extension.ts +1 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +12 -9
- package/corpus/core/src/client/org/TeamPage.tsx +77 -71
- package/corpus/core/src/client/settings/SettingsPanel.tsx +205 -52
- package/corpus/core/src/client/settings/SettingsSection.tsx +8 -1
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +25 -9
- package/corpus/core/src/db-admin/agent-tools.ts +8 -2
- package/corpus/core/src/db-admin/operations.ts +116 -4
- package/corpus/core/src/db-admin/routes.ts +1 -0
- package/corpus/core/src/db-admin/types.ts +4 -0
- package/corpus/core/src/extensions/html-shell.ts +5 -4
- package/corpus/core/src/extensions/routes.ts +10 -0
- package/corpus/core/src/navigation/index.ts +1 -1
- package/corpus/core/src/notifications/channels.ts +1 -22
- package/corpus/core/src/resources/handlers.ts +10 -8
- package/corpus/core/src/server/poll.ts +293 -8
- package/corpus/core/src/styles/agent-native.css +27 -0
- package/corpus/core/src/templates/default/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/core/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/core/src/templates/default/.agents/skills/security/SKILL.md +7 -0
- package/corpus/core/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/core/src/templates/default/AGENTS.md +4 -2
- package/corpus/core/src/templates/headless/AGENTS.md +4 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/core/src/templates/workspace-core/AGENTS.md +7 -1
- package/corpus/core/src/templates/workspace-root/AGENTS.md +7 -1
- package/corpus/templates/analytics/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +10 -0
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/analytics/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/analytics/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/analytics/AGENTS.md +33 -14
- package/corpus/templates/analytics/actions/capture-test-error.ts +4 -0
- package/corpus/templates/analytics/actions/get-analytics-alert-rule-defaults.ts +23 -0
- package/corpus/templates/analytics/actions/get-error-issue.ts +1 -1
- package/corpus/templates/analytics/actions/list-analysis-revisions.ts +40 -0
- package/corpus/templates/analytics/actions/list-dashboard-revisions.ts +40 -0
- package/corpus/templates/analytics/actions/list-dashboard-usage-stats.ts +299 -0
- package/corpus/templates/analytics/actions/navigate.ts +4 -2
- package/corpus/templates/analytics/actions/restore-analysis-revision.ts +43 -0
- package/corpus/templates/analytics/actions/restore-dashboard-revision.ts +68 -0
- package/corpus/templates/analytics/actions/save-analytics-alert-rule.ts +14 -0
- package/corpus/templates/analytics/actions/save-monitor.ts +1 -1
- package/corpus/templates/analytics/actions/view-screen.ts +24 -0
- package/corpus/templates/analytics/app/components/analysis/AnalysisHistoryPanel.tsx +181 -0
- package/corpus/templates/analytics/app/components/dashboard/DashboardHistoryPanel.tsx +184 -0
- package/corpus/templates/analytics/app/components/layout/CommandPalette.tsx +22 -16
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +27 -21
- package/corpus/templates/analytics/app/hooks/use-analysis-revisions.ts +43 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-chat-context.ts +110 -0
- package/corpus/templates/analytics/app/hooks/use-dashboard-revisions.ts +43 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +8 -3
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +64 -2
- package/corpus/templates/analytics/app/i18n-data.ts +783 -3
- package/corpus/templates/analytics/app/pages/Agents.tsx +359 -19
- package/corpus/templates/analytics/app/pages/Settings.tsx +3 -2
- package/corpus/templates/analytics/app/pages/adhoc/explorer-dashboard/index.tsx +34 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +28 -0
- package/corpus/templates/analytics/app/pages/analyses/AnalysisDetail.tsx +18 -1
- package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +7 -10
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +479 -23
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +0 -9
- package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +11 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/types.ts +9 -1
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +6 -3
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +26 -30
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +9 -99
- package/corpus/templates/analytics/app/pages/settings/AlertRulesSettingsCard.tsx +262 -104
- package/corpus/templates/analytics/app/routes/team.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-07-08-admins-can-audit-dashboard-usage-ownership-and-cleanup-signa.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-agent-monitoring-stays-available-to-non-admins.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-alert-emails-now-show-the-message-without-raw-metadata-json.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-alert-rules-are-easier-to-scan-expand-and-edit-from-settings.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-analytics-alert-forms-remember-last-email-recipients.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-analytics-alert-rules-can-now-use-per-rule-slack-and-webhook.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-history-and-chat-context.md +5 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-usage-now-counts-explorer-dashboard-traffic.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-demo-mode-session-emails-are-filtered-and-anonymized.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-error-issue-detail-adds-stack-traces-and-frequency-bars.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-error-issue-detail-now-shows-the-latest-occurrence-message-a.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-monitoring-errors-only-show-test-action-when-empty.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-production-session-replay-now-requires-private-blob-storage-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-shared-extension-links-now-show-a-clear-message-when-you-do-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-team-settings-show-access-controls-next-to-member-lists.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-checks-no-longer-count-ssrf-setup-time-against-the-re.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-checks-reuse-one-keep-alive-connection-so-recorded-la.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-confirm-transient-timeout-failures-befor.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-uptime-monitors-now-use-a-10-second-request-timeout-by-defau.md +6 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +30 -18
- package/corpus/templates/analytics/server/db/schema-monitoring.ts +1 -1
- package/corpus/templates/analytics/server/db/schema.ts +56 -0
- package/corpus/templates/analytics/server/lib/analytics-alerts.ts +92 -0
- package/corpus/templates/analytics/server/lib/dashboards-store.ts +334 -5
- package/corpus/templates/analytics/server/lib/error-capture.ts +231 -36
- package/corpus/templates/analytics/server/lib/session-replay.ts +114 -8
- package/corpus/templates/analytics/server/lib/uptime-monitors.ts +244 -33
- package/corpus/templates/analytics/server/plugins/db.ts +112 -2
- package/corpus/templates/analytics/server/routes/api/dashboard-agent-context.json.get.ts +1 -0
- package/corpus/templates/assets/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/assets/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/assets/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/assets/AGENTS.md +4 -0
- package/corpus/templates/assets/app/routes/settings.tsx +1 -1
- package/corpus/templates/assets/app/routes/team.tsx +1 -1
- package/corpus/templates/brain/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/brain/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/brain/AGENTS.md +4 -0
- package/corpus/templates/brain/app/routes/team.tsx +1 -1
- package/corpus/templates/calendar/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/calendar/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/calendar/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/calendar/AGENTS.md +4 -0
- package/corpus/templates/calendar/app/components/calendar/DayView.tsx +11 -2
- package/corpus/templates/calendar/app/components/calendar/EventDetailPopover.tsx +21 -8
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +7 -1
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +11 -2
- package/corpus/templates/calendar/app/lib/popover-click-guard.ts +48 -4
- package/corpus/templates/calendar/app/pages/Settings.tsx +1 -1
- package/corpus/templates/calendar/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-07-08-clicking-empty-calendar-space-closes-an-open-event-popover-b.md +6 -0
- package/corpus/templates/chat/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/chat/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/chat/AGENTS.md +4 -0
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/chat/app/routes/settings.tsx +2 -2
- package/corpus/templates/chat/app/routes/team.tsx +1 -1
- package/corpus/templates/clips/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/clips/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/clips/AGENTS.md +4 -0
- package/corpus/templates/clips/actions/finalize-recording.ts +9 -3
- package/corpus/templates/clips/actions/set-thumbnail.ts +11 -2
- package/corpus/templates/clips/app/components/meetings/canvas-editor.tsx +1 -7
- package/corpus/templates/clips/app/components/player/player-controls.tsx +21 -13
- package/corpus/templates/clips/app/components/player/video-player.tsx +30 -75
- package/corpus/templates/clips/app/i18n/ar-SA.ts +6 -5
- package/corpus/templates/clips/app/i18n/de-DE.ts +6 -6
- package/corpus/templates/clips/app/i18n/en-US.ts +6 -5
- package/corpus/templates/clips/app/i18n/es-ES.ts +6 -5
- package/corpus/templates/clips/app/i18n/fr-FR.ts +6 -6
- package/corpus/templates/clips/app/i18n/hi-IN.ts +6 -5
- package/corpus/templates/clips/app/i18n/ja-JP.ts +6 -5
- package/corpus/templates/clips/app/i18n/ko-KR.ts +6 -5
- package/corpus/templates/clips/app/i18n/pt-BR.ts +6 -5
- package/corpus/templates/clips/app/i18n/zh-CN.ts +6 -5
- package/corpus/templates/clips/app/i18n/zh-TW.ts +6 -5
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +51 -118
- package/corpus/templates/clips/changelog/2026-07-08-hosted-clip-uploads-now-clear-failed-buffered-chunks.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-hosted-recording-uploads-now-avoid-sql-chunk-storage.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-meeting-recaps-now-keep-summaries-action-items-and-transcrip.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-video-playback-controls-now-match-loom-style-5-second-skips-.md +6 -0
- package/corpus/templates/clips/server/lib/recording-upload-state.ts +63 -0
- package/corpus/templates/clips/server/lib/video-storage.ts +4 -0
- package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +19 -13
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +40 -1
- package/corpus/templates/content/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/content/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/content/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/content/AGENTS.md +4 -0
- package/corpus/templates/content/app/routes/_app.settings.tsx +2 -2
- package/corpus/templates/content/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/design/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/design/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/design/AGENTS.md +4 -0
- package/corpus/templates/design/actions/take-design-screenshot.ts +12 -9
- package/corpus/templates/design/app/components/design/inspector/ImageFillControls.tsx +57 -13
- package/corpus/templates/design/app/routes/team.tsx +1 -1
- package/corpus/templates/design/changelog/2026-07-08-local-images-added-to-designs-now-upload-to-file-storage-ins.md +6 -0
- package/corpus/templates/dispatch/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/dispatch/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/dispatch/AGENTS.md +4 -0
- package/corpus/templates/dispatch/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/dispatch/app/routes/settings.tsx +2 -2
- package/corpus/templates/dispatch/app/routes/team.tsx +1 -1
- package/corpus/templates/forms/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/forms/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/forms/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/forms/AGENTS.md +4 -0
- package/corpus/templates/forms/app/routes/_app.settings.tsx +2 -2
- package/corpus/templates/forms/app/routes/_app.team.tsx +1 -1
- package/corpus/templates/forms/shared/navigation.ts +1 -1
- package/corpus/templates/macros/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/macros/AGENTS.md +4 -0
- package/corpus/templates/macros/app/routes/settings.tsx +2 -2
- package/corpus/templates/macros/app/routes/team.tsx +1 -1
- package/corpus/templates/mail/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/mail/.agents/skills/email-drafts/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/mail/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/mail/.agents/skills/storing-data/SKILL.md +5 -1
- package/corpus/templates/mail/AGENTS.md +4 -0
- package/corpus/templates/mail/changelog/2026-07-08-mail-attachments-in-hosted-environments-now-require-file-sto.md +6 -0
- package/corpus/templates/mail/server/handlers/media.ts +26 -1
- package/corpus/templates/mail/server/lib/outgoing-email.ts +24 -1
- package/corpus/templates/mail/server/lib/upload-store.ts +2 -1
- package/corpus/templates/plan/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/plan/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/plan/AGENTS.md +4 -0
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +1 -1
- package/corpus/templates/plan/app/routes/settings.tsx +2 -2
- package/corpus/templates/plan/app/routes/team.tsx +1 -1
- package/corpus/templates/plan/changelog/2026-07-08-hosted-plan-images-now-require-connected-storage.md +6 -0
- package/corpus/templates/plan/server/lib/plan-assets.ts +33 -4
- package/corpus/templates/slides/.agents/skills/actions/SKILL.md +5 -0
- package/corpus/templates/slides/.agents/skills/security/SKILL.md +7 -0
- package/corpus/templates/slides/AGENTS.md +4 -0
- package/corpus/templates/slides/actions/generate-image-api.ts +15 -3
- package/corpus/templates/slides/app/routes/settings.tsx +2 -2
- package/corpus/templates/slides/app/routes/team.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-07-08-generated-slide-images-now-return-hosted-file-storage-urls-i.md +6 -0
- package/corpus/templates/slides/shared/api.ts +1 -1
- package/dist/application-state/store.d.ts.map +1 -1
- package/dist/application-state/store.js +8 -2
- package/dist/application-state/store.js.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.d.ts.map +1 -1
- package/dist/cli/sync-builder-starter-manifest.js +24 -1
- package/dist/cli/sync-builder-starter-manifest.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +24 -13
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/PoweredByBadge.d.ts +1 -1
- package/dist/client/PoweredByBadge.d.ts.map +1 -1
- package/dist/client/PoweredByBadge.js +3 -9
- package/dist/client/PoweredByBadge.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +28 -9
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/chat/widgets/DataTableWidget.d.ts.map +1 -1
- package/dist/client/chat/widgets/DataTableWidget.js +19 -2
- package/dist/client/chat/widgets/DataTableWidget.js.map +1 -1
- package/dist/client/db-admin/TableEditor.d.ts.map +1 -1
- package/dist/client/db-admin/TableEditor.js +19 -2
- package/dist/client/db-admin/TableEditor.js.map +1 -1
- package/dist/client/db-admin/export-utils.d.ts +2 -0
- package/dist/client/db-admin/export-utils.d.ts.map +1 -1
- package/dist/client/db-admin/export-utils.js +11 -3
- package/dist/client/db-admin/export-utils.js.map +1 -1
- package/dist/client/db-admin/useAgentSync.d.ts.map +1 -1
- package/dist/client/db-admin/useAgentSync.js +63 -0
- package/dist/client/db-admin/useAgentSync.js.map +1 -1
- package/dist/client/error-capture.d.ts.map +1 -1
- package/dist/client/error-capture.js +41 -7
- package/dist/client/error-capture.js.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.d.ts.map +1 -1
- package/dist/client/extensions/EmbeddedExtension.js +3 -1
- package/dist/client/extensions/EmbeddedExtension.js.map +1 -1
- package/dist/client/extensions/ExtensionViewer.d.ts.map +1 -1
- package/dist/client/extensions/ExtensionViewer.js +32 -5
- package/dist/client/extensions/ExtensionViewer.js.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.d.ts.map +1 -1
- package/dist/client/extensions/InlineExtensionFrame.js +3 -1
- package/dist/client/extensions/InlineExtensionFrame.js.map +1 -1
- package/dist/client/extensions/portable-extension.d.ts.map +1 -1
- package/dist/client/extensions/portable-extension.js +1 -0
- package/dist/client/extensions/portable-extension.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +1 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +11 -6
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +5 -5
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +37 -14
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsSection.js +1 -1
- package/dist/client/settings/SettingsSection.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +21 -10
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/db-admin/agent-tools.d.ts.map +1 -1
- package/dist/db-admin/agent-tools.js +7 -2
- package/dist/db-admin/agent-tools.js.map +1 -1
- package/dist/db-admin/operations.d.ts.map +1 -1
- package/dist/db-admin/operations.js +90 -4
- package/dist/db-admin/operations.js.map +1 -1
- package/dist/db-admin/routes.d.ts.map +1 -1
- package/dist/db-admin/routes.js +1 -0
- package/dist/db-admin/routes.js.map +1 -1
- package/dist/db-admin/types.d.ts +4 -0
- package/dist/db-admin/types.d.ts.map +1 -1
- package/dist/db-admin/types.js.map +1 -1
- package/dist/extensions/html-shell.d.ts +5 -4
- package/dist/extensions/html-shell.d.ts.map +1 -1
- package/dist/extensions/html-shell.js.map +1 -1
- package/dist/extensions/routes.d.ts.map +1 -1
- package/dist/extensions/routes.js +8 -0
- package/dist/extensions/routes.js.map +1 -1
- package/dist/navigation/index.d.ts +1 -1
- package/dist/navigation/index.d.ts.map +1 -1
- package/dist/navigation/index.js +1 -1
- package/dist/navigation/index.js.map +1 -1
- package/dist/notifications/channels.js +1 -17
- package/dist/notifications/channels.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/resources/handlers.d.ts +5 -0
- package/dist/resources/handlers.d.ts.map +1 -1
- package/dist/resources/handlers.js +9 -8
- package/dist/resources/handlers.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/poll.d.ts +12 -4
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +236 -9
- package/dist/server/poll.js.map +1 -1
- package/dist/styles/agent-native.css +27 -0
- package/dist/templates/default/.agents/skills/actions/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/default/.agents/skills/security/SKILL.md +7 -0
- package/dist/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/dist/templates/default/AGENTS.md +4 -2
- package/dist/templates/headless/AGENTS.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/dist/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
- package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
- package/dist/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/dist/templates/workspace-core/AGENTS.md +7 -1
- package/dist/templates/workspace-root/AGENTS.md +7 -1
- package/package.json +1 -1
- package/src/templates/default/.agents/skills/actions/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/default/.agents/skills/security/SKILL.md +7 -0
- package/src/templates/default/.agents/skills/storing-data/SKILL.md +5 -1
- package/src/templates/default/AGENTS.md +4 -2
- package/src/templates/headless/AGENTS.md +4 -0
- package/src/templates/workspace-core/.agents/skills/actions/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/adding-a-feature/SKILL.md +5 -0
- package/src/templates/workspace-core/.agents/skills/context-awareness/SKILL.md +4 -0
- package/src/templates/workspace-core/.agents/skills/extensions/SKILL.md +6 -0
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +4 -0
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/security/SKILL.md +7 -0
- package/src/templates/workspace-core/.agents/skills/storing-data/SKILL.md +5 -1
- package/src/templates/workspace-core/AGENTS.md +7 -1
- package/src/templates/workspace-root/AGENTS.md +7 -1
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
count,
|
|
35
35
|
desc,
|
|
36
36
|
eq,
|
|
37
|
+
gt,
|
|
37
38
|
gte,
|
|
38
39
|
isNull,
|
|
39
40
|
lte,
|
|
@@ -203,6 +204,7 @@ const MAX_REDIRECT_HOPS = 5;
|
|
|
203
204
|
const MONITOR_RUNNING_STALE_MS = 5 * 60 * 1000;
|
|
204
205
|
const DEFAULT_RESULT_RETENTION_DAYS = 30;
|
|
205
206
|
const DEFAULT_MONITOR_LIMIT_PER_OWNER = 100;
|
|
207
|
+
export const DEFAULT_MONITOR_TIMEOUT_MS = 10_000;
|
|
206
208
|
const MAX_REQUEST_HEADER_COUNT = 20;
|
|
207
209
|
const MAX_REQUEST_HEADER_NAME_LENGTH = 128;
|
|
208
210
|
const MAX_REQUEST_HEADER_VALUE_BYTES = 2048;
|
|
@@ -215,6 +217,7 @@ const MIN_INTERVAL_SECONDS = 30;
|
|
|
215
217
|
const MAX_INTERVAL_SECONDS = 24 * 60 * 60;
|
|
216
218
|
const MIN_TIMEOUT_MS = 1000;
|
|
217
219
|
const MAX_TIMEOUT_MS = 120_000;
|
|
220
|
+
const DEFAULT_TRANSIENT_FAILURE_CONFIRMATION_CHECKS = 2;
|
|
218
221
|
|
|
219
222
|
const ASSERTION_TYPES: AssertionType[] = [
|
|
220
223
|
"body_contains",
|
|
@@ -280,6 +283,14 @@ function monitorLimitPerOwner(): number {
|
|
|
280
283
|
: DEFAULT_MONITOR_LIMIT_PER_OWNER;
|
|
281
284
|
}
|
|
282
285
|
|
|
286
|
+
function transientFailureConfirmationChecks(): number {
|
|
287
|
+
const raw = process.env.UPTIME_MONITOR_TRANSIENT_FAILURE_CONFIRMATION_CHECKS;
|
|
288
|
+
const parsed = Number.parseInt(raw ?? "", 10);
|
|
289
|
+
return Number.isFinite(parsed) && parsed > 0
|
|
290
|
+
? Math.min(parsed, 10)
|
|
291
|
+
: DEFAULT_TRANSIENT_FAILURE_CONFIRMATION_CHECKS;
|
|
292
|
+
}
|
|
293
|
+
|
|
283
294
|
export function hostFromUrl(url: string): string {
|
|
284
295
|
try {
|
|
285
296
|
return new URL(url).host;
|
|
@@ -674,6 +685,40 @@ export function evaluateCheck(params: EvaluateCheckParams): {
|
|
|
674
685
|
// SSRF-safe fetch + probe
|
|
675
686
|
// ---------------------------------------------------------------------------
|
|
676
687
|
|
|
688
|
+
// A single SSRF-safe dispatcher (undici Agent) is reused across every probe.
|
|
689
|
+
// Building a fresh Agent per check disabled HTTP keep-alive — each check paid a
|
|
690
|
+
// cold DNS + TCP + TLS handshake, inflating the recorded latency well above the
|
|
691
|
+
// site's real response time (and occasionally spiking near the timeout) — and
|
|
692
|
+
// leaked Agents, which are never closed. The connect-time private-IP guard runs
|
|
693
|
+
// on every new socket, so reuse keeps the exact same SSRF protection.
|
|
694
|
+
// `undefined` = not built yet; the resolved value may be `null` on runtimes
|
|
695
|
+
// without undici / node:dns, in which case callers fall back to plain `fetch`.
|
|
696
|
+
let sharedSsrfDispatcherPromise: Promise<unknown | null> | undefined;
|
|
697
|
+
|
|
698
|
+
function getSharedSsrfDispatcher(): Promise<unknown | null> {
|
|
699
|
+
if (!sharedSsrfDispatcherPromise) {
|
|
700
|
+
sharedSsrfDispatcherPromise = createSsrfSafeDispatcher().catch(() => null);
|
|
701
|
+
}
|
|
702
|
+
return sharedSsrfDispatcherPromise;
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
async function prepareMonitorFetch(
|
|
706
|
+
url: string,
|
|
707
|
+
opts: { allowPrivateHosts: boolean },
|
|
708
|
+
): Promise<{ dispatcher: unknown | undefined }> {
|
|
709
|
+
const dispatcher = opts.allowPrivateHosts
|
|
710
|
+
? undefined
|
|
711
|
+
: ((await getSharedSsrfDispatcher()) ?? undefined);
|
|
712
|
+
|
|
713
|
+
if (!opts.allowPrivateHosts && (await isBlockedExtensionUrlWithDns(url))) {
|
|
714
|
+
throw new Error(
|
|
715
|
+
`SSRF blocked: refusing to fetch private/internal address (${url})`,
|
|
716
|
+
);
|
|
717
|
+
}
|
|
718
|
+
|
|
719
|
+
return { dispatcher };
|
|
720
|
+
}
|
|
721
|
+
|
|
677
722
|
async function safeMonitorFetch(
|
|
678
723
|
url: string,
|
|
679
724
|
init: RequestInit,
|
|
@@ -681,17 +726,26 @@ async function safeMonitorFetch(
|
|
|
681
726
|
followRedirects: boolean;
|
|
682
727
|
maxRedirects: number;
|
|
683
728
|
allowPrivateHosts: boolean;
|
|
729
|
+
/** Prebuilt outside the abort window so SSRF setup is not billed as site latency. */
|
|
730
|
+
dispatcher?: unknown;
|
|
731
|
+
/** When true, the caller already DNS-checked `url` before starting the timer. */
|
|
732
|
+
initialDnsChecked?: boolean;
|
|
684
733
|
},
|
|
685
734
|
): Promise<Response> {
|
|
686
|
-
const dispatcher =
|
|
687
|
-
|
|
688
|
-
|
|
735
|
+
const dispatcher =
|
|
736
|
+
opts.dispatcher !== undefined
|
|
737
|
+
? opts.dispatcher
|
|
738
|
+
: opts.allowPrivateHosts
|
|
739
|
+
? undefined
|
|
740
|
+
: ((await getSharedSsrfDispatcher()) ?? undefined);
|
|
689
741
|
|
|
690
742
|
let currentUrl = url;
|
|
691
743
|
const maxHops = opts.followRedirects ? opts.maxRedirects : 0;
|
|
692
744
|
|
|
693
745
|
for (let hop = 0; hop <= maxHops; hop++) {
|
|
746
|
+
const skipDns = hop === 0 && opts.initialDnsChecked;
|
|
694
747
|
if (
|
|
748
|
+
!skipDns &&
|
|
695
749
|
!opts.allowPrivateHosts &&
|
|
696
750
|
(await isBlockedExtensionUrlWithDns(currentUrl))
|
|
697
751
|
) {
|
|
@@ -710,6 +764,7 @@ async function safeMonitorFetch(
|
|
|
710
764
|
if (opts.followRedirects && isRedirect) {
|
|
711
765
|
const location = response.headers.get("location");
|
|
712
766
|
if (!location) return response;
|
|
767
|
+
await cancelResponseBody(response);
|
|
713
768
|
currentUrl = new URL(location, currentUrl).href;
|
|
714
769
|
continue;
|
|
715
770
|
}
|
|
@@ -720,17 +775,50 @@ async function safeMonitorFetch(
|
|
|
720
775
|
);
|
|
721
776
|
}
|
|
722
777
|
|
|
723
|
-
async function
|
|
778
|
+
async function cancelResponseBody(res: Response): Promise<void> {
|
|
779
|
+
try {
|
|
780
|
+
await res.body?.cancel();
|
|
781
|
+
} catch {
|
|
782
|
+
// Best-effort cleanup only.
|
|
783
|
+
}
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
async function readCappedText(
|
|
787
|
+
res: Response,
|
|
788
|
+
cap: number,
|
|
789
|
+
timeoutMs?: number,
|
|
790
|
+
): Promise<{ text: string; timedOut: boolean }> {
|
|
791
|
+
let timedOut = false;
|
|
792
|
+
let timer: ReturnType<typeof setTimeout> | null = null;
|
|
724
793
|
const body = res.body;
|
|
725
794
|
if (!body) {
|
|
726
795
|
try {
|
|
727
|
-
const text =
|
|
728
|
-
|
|
796
|
+
const text =
|
|
797
|
+
timeoutMs && timeoutMs > 0
|
|
798
|
+
? await Promise.race([
|
|
799
|
+
res.text(),
|
|
800
|
+
new Promise<string>((resolve) => {
|
|
801
|
+
timer = setTimeout(() => {
|
|
802
|
+
timedOut = true;
|
|
803
|
+
resolve("");
|
|
804
|
+
}, timeoutMs);
|
|
805
|
+
}),
|
|
806
|
+
])
|
|
807
|
+
: await res.text();
|
|
808
|
+
return { text: text.length > cap ? text.slice(0, cap) : text, timedOut };
|
|
729
809
|
} catch {
|
|
730
|
-
return "";
|
|
810
|
+
return { text: "", timedOut };
|
|
811
|
+
} finally {
|
|
812
|
+
if (timer) clearTimeout(timer);
|
|
731
813
|
}
|
|
732
814
|
}
|
|
733
815
|
const reader = body.getReader();
|
|
816
|
+
if (timeoutMs && timeoutMs > 0) {
|
|
817
|
+
timer = setTimeout(() => {
|
|
818
|
+
timedOut = true;
|
|
819
|
+
void reader.cancel().catch(() => {});
|
|
820
|
+
}, timeoutMs);
|
|
821
|
+
}
|
|
734
822
|
const chunks: Uint8Array[] = [];
|
|
735
823
|
let total = 0;
|
|
736
824
|
try {
|
|
@@ -745,6 +833,7 @@ async function readCappedText(res: Response, cap: number): Promise<string> {
|
|
|
745
833
|
} catch {
|
|
746
834
|
// Partial body is fine for text assertions.
|
|
747
835
|
} finally {
|
|
836
|
+
if (timer) clearTimeout(timer);
|
|
748
837
|
try {
|
|
749
838
|
await reader.cancel();
|
|
750
839
|
} catch {
|
|
@@ -761,7 +850,10 @@ async function readCappedText(res: Response, cap: number): Promise<string> {
|
|
|
761
850
|
merged.set(slice, offset);
|
|
762
851
|
offset += slice.length;
|
|
763
852
|
}
|
|
764
|
-
return
|
|
853
|
+
return {
|
|
854
|
+
text: new TextDecoder("utf-8", { fatal: false }).decode(merged),
|
|
855
|
+
timedOut,
|
|
856
|
+
};
|
|
765
857
|
}
|
|
766
858
|
|
|
767
859
|
function headersToObject(headers: Headers): Record<string, string> {
|
|
@@ -772,10 +864,22 @@ function headersToObject(headers: Headers): Record<string, string> {
|
|
|
772
864
|
return out;
|
|
773
865
|
}
|
|
774
866
|
|
|
867
|
+
function needsResponseBody(assertions: Assertion[]): boolean {
|
|
868
|
+
return assertions.some(
|
|
869
|
+
(assertion) =>
|
|
870
|
+
assertion.type === "body_contains" || assertion.type === "body_absent",
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
|
|
775
874
|
/**
|
|
776
875
|
* Execute one probe for `monitor`. Performs an SSRF-safe fetch with an
|
|
777
876
|
* AbortController timeout, measures latency, caps the response body, and
|
|
778
877
|
* classifies the result. Never throws — failures are captured in the outcome.
|
|
878
|
+
*
|
|
879
|
+
* The abort budget covers only the HTTP fetch (headers / redirect chain), not
|
|
880
|
+
* SSRF dispatcher/DNS setup or optional body reads. Billing setup time against
|
|
881
|
+
* `timeoutMs` produced false "Timed out after Nms" alerts when the site itself
|
|
882
|
+
* was fine.
|
|
779
883
|
*/
|
|
780
884
|
export async function runMonitorCheck(
|
|
781
885
|
monitor: Pick<
|
|
@@ -817,14 +921,50 @@ export async function runMonitorCheck(
|
|
|
817
921
|
};
|
|
818
922
|
}
|
|
819
923
|
|
|
820
|
-
const controller = new AbortController();
|
|
821
924
|
const timeoutMs = clampInt(
|
|
822
925
|
monitor.timeoutMs,
|
|
823
926
|
MIN_TIMEOUT_MS,
|
|
824
927
|
MAX_TIMEOUT_MS,
|
|
825
|
-
|
|
928
|
+
DEFAULT_MONITOR_TIMEOUT_MS,
|
|
826
929
|
);
|
|
827
|
-
|
|
930
|
+
|
|
931
|
+
let dispatcher: unknown | undefined;
|
|
932
|
+
try {
|
|
933
|
+
({ dispatcher } = await prepareMonitorFetch(monitor.url, {
|
|
934
|
+
allowPrivateHosts,
|
|
935
|
+
}));
|
|
936
|
+
} catch (err) {
|
|
937
|
+
const message =
|
|
938
|
+
err instanceof Error ? err.message : String(err ?? "check failed");
|
|
939
|
+
const isConfig = message.startsWith("SSRF blocked");
|
|
940
|
+
const errorText = message.slice(0, 500);
|
|
941
|
+
const outcome = evaluateCheck({
|
|
942
|
+
statusCode: null,
|
|
943
|
+
latencyMs: null,
|
|
944
|
+
bodyText: "",
|
|
945
|
+
headers: {},
|
|
946
|
+
matcher,
|
|
947
|
+
assertions,
|
|
948
|
+
fetchError: errorText,
|
|
949
|
+
errorKind: isConfig ? "config" : "network",
|
|
950
|
+
});
|
|
951
|
+
return {
|
|
952
|
+
checkedAt,
|
|
953
|
+
statusCode: null,
|
|
954
|
+
latencyMs: null,
|
|
955
|
+
status: outcome.status,
|
|
956
|
+
ok: outcome.ok,
|
|
957
|
+
failedAssertions: outcome.failedAssertions,
|
|
958
|
+
error: errorText,
|
|
959
|
+
};
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
const controller = new AbortController();
|
|
963
|
+
let timedOut = false;
|
|
964
|
+
const timer = setTimeout(() => {
|
|
965
|
+
timedOut = true;
|
|
966
|
+
controller.abort();
|
|
967
|
+
}, timeoutMs);
|
|
828
968
|
const start = Date.now();
|
|
829
969
|
|
|
830
970
|
try {
|
|
@@ -845,15 +985,32 @@ export async function runMonitorCheck(
|
|
|
845
985
|
followRedirects: monitor.followRedirects,
|
|
846
986
|
maxRedirects: MAX_REDIRECT_HOPS,
|
|
847
987
|
allowPrivateHosts,
|
|
988
|
+
dispatcher,
|
|
989
|
+
initialDnsChecked: Boolean(dispatcher),
|
|
848
990
|
});
|
|
849
991
|
|
|
850
|
-
|
|
851
|
-
|
|
852
|
-
|
|
853
|
-
|
|
992
|
+
// Stop the abort timer as soon as headers arrive so a slow/optional body
|
|
993
|
+
// read cannot be mislabeled as a request timeout with a null status code.
|
|
994
|
+
clearTimeout(timer);
|
|
995
|
+
|
|
854
996
|
const latencyMs = Date.now() - start;
|
|
855
997
|
const statusCode = response.status;
|
|
856
998
|
const headers = headersToObject(response.headers);
|
|
999
|
+
let bodyText = "";
|
|
1000
|
+
let bodyReadError: string | null = null;
|
|
1001
|
+
if (method !== "HEAD" && needsResponseBody(assertions)) {
|
|
1002
|
+
const body = await readCappedText(
|
|
1003
|
+
response,
|
|
1004
|
+
MAX_RESPONSE_BODY_BYTES,
|
|
1005
|
+
timeoutMs,
|
|
1006
|
+
);
|
|
1007
|
+
bodyText = body.text;
|
|
1008
|
+
if (body.timedOut) {
|
|
1009
|
+
bodyReadError = `Response body read timed out after ${timeoutMs}ms`;
|
|
1010
|
+
}
|
|
1011
|
+
} else {
|
|
1012
|
+
await cancelResponseBody(response);
|
|
1013
|
+
}
|
|
857
1014
|
|
|
858
1015
|
const outcome = evaluateCheck({
|
|
859
1016
|
statusCode,
|
|
@@ -863,31 +1020,33 @@ export async function runMonitorCheck(
|
|
|
863
1020
|
matcher,
|
|
864
1021
|
assertions,
|
|
865
1022
|
});
|
|
1023
|
+
const failedAssertions = bodyReadError
|
|
1024
|
+
? [...outcome.failedAssertions, bodyReadError]
|
|
1025
|
+
: outcome.failedAssertions;
|
|
1026
|
+
const status = bodyReadError ? "down" : outcome.status;
|
|
866
1027
|
|
|
867
1028
|
return {
|
|
868
1029
|
checkedAt,
|
|
869
1030
|
statusCode,
|
|
870
1031
|
latencyMs,
|
|
871
|
-
status
|
|
872
|
-
ok: outcome.ok,
|
|
873
|
-
failedAssertions
|
|
874
|
-
error:
|
|
875
|
-
? outcome.failedAssertions.join("; ")
|
|
876
|
-
: null,
|
|
1032
|
+
status,
|
|
1033
|
+
ok: status === "up" && outcome.ok,
|
|
1034
|
+
failedAssertions,
|
|
1035
|
+
error: failedAssertions.length ? failedAssertions.join("; ") : null,
|
|
877
1036
|
};
|
|
878
1037
|
} catch (err) {
|
|
879
|
-
const isTimeout =
|
|
880
|
-
(err as { name?: string })?.name === "AbortError" ||
|
|
881
|
-
controller.signal.aborted;
|
|
882
1038
|
const message =
|
|
883
1039
|
err instanceof Error ? err.message : String(err ?? "check failed");
|
|
884
1040
|
const isConfig = message.startsWith("SSRF blocked");
|
|
1041
|
+
// Only our timer sets `timedOut`. Prefer the real SSRF/config message when
|
|
1042
|
+
// both happened (e.g. abort fired while a redirect DNS check was in flight).
|
|
1043
|
+
const isTimeout = timedOut && !isConfig;
|
|
885
1044
|
const errorText = isTimeout
|
|
886
1045
|
? `Timed out after ${timeoutMs}ms`
|
|
887
1046
|
: message.slice(0, 500);
|
|
888
1047
|
const outcome = evaluateCheck({
|
|
889
1048
|
statusCode: null,
|
|
890
|
-
latencyMs: null,
|
|
1049
|
+
latencyMs: isTimeout ? timeoutMs : null,
|
|
891
1050
|
bodyText: "",
|
|
892
1051
|
headers: {},
|
|
893
1052
|
matcher,
|
|
@@ -898,7 +1057,7 @@ export async function runMonitorCheck(
|
|
|
898
1057
|
return {
|
|
899
1058
|
checkedAt,
|
|
900
1059
|
statusCode: null,
|
|
901
|
-
latencyMs: null,
|
|
1060
|
+
latencyMs: isTimeout ? timeoutMs : null,
|
|
902
1061
|
status: outcome.status,
|
|
903
1062
|
ok: outcome.ok,
|
|
904
1063
|
failedAssertions: outcome.failedAssertions,
|
|
@@ -925,7 +1084,7 @@ function rowToMonitor(row: any): Monitor {
|
|
|
925
1084
|
),
|
|
926
1085
|
requestBody: row.requestBody ?? null,
|
|
927
1086
|
intervalSeconds: Number(row.intervalSeconds ?? 300),
|
|
928
|
-
timeoutMs: Number(row.timeoutMs ??
|
|
1087
|
+
timeoutMs: Number(row.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS),
|
|
929
1088
|
expectedStatus: normalizeStatusMatcher(
|
|
930
1089
|
safeJsonParse<unknown>(row.expectedStatus, null),
|
|
931
1090
|
),
|
|
@@ -1155,10 +1314,10 @@ export async function saveMonitor(
|
|
|
1155
1314
|
300,
|
|
1156
1315
|
);
|
|
1157
1316
|
const timeoutMs = clampInt(
|
|
1158
|
-
input.timeoutMs ??
|
|
1317
|
+
input.timeoutMs ?? DEFAULT_MONITOR_TIMEOUT_MS,
|
|
1159
1318
|
MIN_TIMEOUT_MS,
|
|
1160
1319
|
MAX_TIMEOUT_MS,
|
|
1161
|
-
|
|
1320
|
+
DEFAULT_MONITOR_TIMEOUT_MS,
|
|
1162
1321
|
);
|
|
1163
1322
|
const expectedStatus = normalizeStatusMatcher(input.expectedStatus);
|
|
1164
1323
|
const assertions = normalizeAssertions(input.assertions);
|
|
@@ -1433,6 +1592,25 @@ function describeCause(outcome: CheckOutcome): string {
|
|
|
1433
1592
|
return "Check failed";
|
|
1434
1593
|
}
|
|
1435
1594
|
|
|
1595
|
+
function isTransientNoResponseFailure(outcome: CheckOutcome): boolean {
|
|
1596
|
+
return (
|
|
1597
|
+
outcome.status === "down" &&
|
|
1598
|
+
outcome.statusCode == null &&
|
|
1599
|
+
Boolean(outcome.error)
|
|
1600
|
+
);
|
|
1601
|
+
}
|
|
1602
|
+
|
|
1603
|
+
export function shouldOpenMonitorIncident(
|
|
1604
|
+
outcome: CheckOutcome,
|
|
1605
|
+
priorConsecutiveFailures: number,
|
|
1606
|
+
confirmationChecks = transientFailureConfirmationChecks(),
|
|
1607
|
+
): boolean {
|
|
1608
|
+
const needed = isTransientNoResponseFailure(outcome)
|
|
1609
|
+
? Math.max(1, Math.min(10, Math.floor(confirmationChecks)))
|
|
1610
|
+
: 1;
|
|
1611
|
+
return Math.max(0, Math.floor(priorConsecutiveFailures)) + 1 >= needed;
|
|
1612
|
+
}
|
|
1613
|
+
|
|
1436
1614
|
async function getOpenIncident(
|
|
1437
1615
|
monitorId: string,
|
|
1438
1616
|
ctx: AccessCtx,
|
|
@@ -1477,6 +1655,29 @@ async function recentlyResolvedWithinCooldown(
|
|
|
1477
1655
|
return now.getTime() - resolved < monitor.cooldownMinutes * 60 * 1000;
|
|
1478
1656
|
}
|
|
1479
1657
|
|
|
1658
|
+
async function getFailureStreakStartedAt(
|
|
1659
|
+
monitor: Monitor,
|
|
1660
|
+
ctx: AccessCtx,
|
|
1661
|
+
fallback: string,
|
|
1662
|
+
): Promise<string> {
|
|
1663
|
+
const db = getDb() as any;
|
|
1664
|
+
const table = schema.monitorCheckResults;
|
|
1665
|
+
const clauses: any[] = [
|
|
1666
|
+
resultsOwnerWhere(ctx),
|
|
1667
|
+
eq(table.monitorId, monitor.id),
|
|
1668
|
+
eq(table.ok, false),
|
|
1669
|
+
];
|
|
1670
|
+
if (monitor.lastSuccessAt)
|
|
1671
|
+
clauses.push(gt(table.checkedAt, monitor.lastSuccessAt));
|
|
1672
|
+
const [row] = await db
|
|
1673
|
+
.select({ checkedAt: table.checkedAt })
|
|
1674
|
+
.from(table)
|
|
1675
|
+
.where(and(...clauses))
|
|
1676
|
+
.orderBy(asc(table.checkedAt))
|
|
1677
|
+
.limit(1);
|
|
1678
|
+
return row?.checkedAt ?? fallback;
|
|
1679
|
+
}
|
|
1680
|
+
|
|
1480
1681
|
async function notifyMonitorDown(monitor: Monitor, outcome: CheckOutcome) {
|
|
1481
1682
|
const host = hostFromUrl(monitor.url);
|
|
1482
1683
|
const label = outcome.status === "degraded" ? "degraded" : "down";
|
|
@@ -1560,8 +1761,8 @@ export interface EvaluateMonitorResult {
|
|
|
1560
1761
|
|
|
1561
1762
|
/**
|
|
1562
1763
|
* Open / update / resolve incidents based on the latest outcome and send
|
|
1563
|
-
* notifications. On a transition into failure it opens an incident
|
|
1564
|
-
* notifies (respecting an anti-flap cooldown); on recovery it resolves the
|
|
1764
|
+
* notifications. On a transition into confirmed failure it opens an incident
|
|
1765
|
+
* and notifies (respecting an anti-flap cooldown); on recovery it resolves the
|
|
1565
1766
|
* open incident and sends a recovery notice.
|
|
1566
1767
|
*/
|
|
1567
1768
|
export async function evaluateAndNotifyMonitor(
|
|
@@ -1575,6 +1776,8 @@ export async function evaluateAndNotifyMonitor(
|
|
|
1575
1776
|
|
|
1576
1777
|
if (!outcome.ok) {
|
|
1577
1778
|
const cause = describeCause(outcome);
|
|
1779
|
+
const priorConsecutiveFailures = monitor.consecutiveFailures ?? 0;
|
|
1780
|
+
const nextChecksFailed = priorConsecutiveFailures + 1;
|
|
1578
1781
|
if (open) {
|
|
1579
1782
|
const nextStatus =
|
|
1580
1783
|
open.status === "down" ? "down" : (outcome.status as MonitorStatus);
|
|
@@ -1590,6 +1793,10 @@ export async function evaluateAndNotifyMonitor(
|
|
|
1590
1793
|
return { status: outcome.status, incidentId: open.id, notified: false };
|
|
1591
1794
|
}
|
|
1592
1795
|
|
|
1796
|
+
if (!shouldOpenMonitorIncident(outcome, priorConsecutiveFailures)) {
|
|
1797
|
+
return { status: outcome.status, notified: false };
|
|
1798
|
+
}
|
|
1799
|
+
|
|
1593
1800
|
const suppressed = await recentlyResolvedWithinCooldown(monitor, ctx, now);
|
|
1594
1801
|
let notificationId: string | undefined;
|
|
1595
1802
|
if (!suppressed) {
|
|
@@ -1604,17 +1811,21 @@ export async function evaluateAndNotifyMonitor(
|
|
|
1604
1811
|
}
|
|
1605
1812
|
}
|
|
1606
1813
|
const incidentId = randomUUID();
|
|
1814
|
+
const startedAt =
|
|
1815
|
+
nextChecksFailed > 1
|
|
1816
|
+
? await getFailureStreakStartedAt(monitor, ctx, outcome.checkedAt)
|
|
1817
|
+
: outcome.checkedAt;
|
|
1607
1818
|
await db.insert(schema.monitorIncidents).values({
|
|
1608
1819
|
id: incidentId,
|
|
1609
1820
|
monitorId: monitor.id,
|
|
1610
|
-
startedAt
|
|
1821
|
+
startedAt,
|
|
1611
1822
|
resolvedAt: null,
|
|
1612
1823
|
status: outcome.status === "degraded" ? "degraded" : "down",
|
|
1613
1824
|
severity: monitor.severity,
|
|
1614
1825
|
cause,
|
|
1615
1826
|
lastError: outcome.error,
|
|
1616
1827
|
notificationId: notificationId ?? null,
|
|
1617
|
-
checksFailed:
|
|
1828
|
+
checksFailed: nextChecksFailed,
|
|
1618
1829
|
createdAt: outcome.checkedAt,
|
|
1619
1830
|
ownerEmail: monitor.ownerEmail,
|
|
1620
1831
|
orgId: monitor.orgId,
|
|
@@ -568,6 +568,8 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
568
568
|
severity TEXT NOT NULL DEFAULT 'warning',
|
|
569
569
|
channels TEXT NOT NULL DEFAULT '["inbox"]',
|
|
570
570
|
email_recipients TEXT NOT NULL DEFAULT '[]',
|
|
571
|
+
slack_webhook_url TEXT,
|
|
572
|
+
webhook_url TEXT,
|
|
571
573
|
enabled BOOLEAN NOT NULL DEFAULT true,
|
|
572
574
|
last_evaluated_at TEXT,
|
|
573
575
|
last_triggered_at TEXT,
|
|
@@ -592,6 +594,8 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
592
594
|
severity TEXT NOT NULL DEFAULT 'warning',
|
|
593
595
|
channels TEXT NOT NULL DEFAULT '["inbox"]',
|
|
594
596
|
email_recipients TEXT NOT NULL DEFAULT '[]',
|
|
597
|
+
slack_webhook_url TEXT,
|
|
598
|
+
webhook_url TEXT,
|
|
595
599
|
enabled INTEGER NOT NULL DEFAULT 1,
|
|
596
600
|
last_evaluated_at TEXT,
|
|
597
601
|
last_triggered_at TEXT,
|
|
@@ -891,7 +895,7 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
891
895
|
request_headers TEXT NOT NULL DEFAULT '{}',
|
|
892
896
|
request_body TEXT,
|
|
893
897
|
interval_seconds INTEGER NOT NULL DEFAULT 300,
|
|
894
|
-
timeout_ms INTEGER NOT NULL DEFAULT
|
|
898
|
+
timeout_ms INTEGER NOT NULL DEFAULT 10000,
|
|
895
899
|
expected_status TEXT NOT NULL DEFAULT '{"mode":"class","classes":["2xx"]}',
|
|
896
900
|
assertions TEXT NOT NULL DEFAULT '[]',
|
|
897
901
|
follow_redirects BOOLEAN NOT NULL DEFAULT true,
|
|
@@ -921,7 +925,7 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
921
925
|
request_headers TEXT NOT NULL DEFAULT '{}',
|
|
922
926
|
request_body TEXT,
|
|
923
927
|
interval_seconds INTEGER NOT NULL DEFAULT 300,
|
|
924
|
-
timeout_ms INTEGER NOT NULL DEFAULT
|
|
928
|
+
timeout_ms INTEGER NOT NULL DEFAULT 10000,
|
|
925
929
|
expected_status TEXT NOT NULL DEFAULT '{"mode":"class","classes":["2xx"]}',
|
|
926
930
|
assertions TEXT NOT NULL DEFAULT '[]',
|
|
927
931
|
follow_redirects INTEGER NOT NULL DEFAULT 1,
|
|
@@ -1124,6 +1128,112 @@ const runAnalyticsMigrations = runMigrations(
|
|
|
1124
1128
|
name: "uptime-monitors-webhook-url",
|
|
1125
1129
|
sql: `ALTER TABLE monitors ADD COLUMN IF NOT EXISTS webhook_url TEXT`,
|
|
1126
1130
|
},
|
|
1131
|
+
{
|
|
1132
|
+
version: 108,
|
|
1133
|
+
name: "analytics-alert-rules-slack-webhook-url",
|
|
1134
|
+
sql: `ALTER TABLE analytics_alert_rules ADD COLUMN IF NOT EXISTS slack_webhook_url TEXT`,
|
|
1135
|
+
},
|
|
1136
|
+
{
|
|
1137
|
+
version: 109,
|
|
1138
|
+
name: "analytics-alert-rules-webhook-url",
|
|
1139
|
+
sql: `ALTER TABLE analytics_alert_rules ADD COLUMN IF NOT EXISTS webhook_url TEXT`,
|
|
1140
|
+
},
|
|
1141
|
+
{
|
|
1142
|
+
version: 110,
|
|
1143
|
+
name: "dashboards-updated-by",
|
|
1144
|
+
sql: `ALTER TABLE dashboards ADD COLUMN IF NOT EXISTS updated_by TEXT`,
|
|
1145
|
+
},
|
|
1146
|
+
{
|
|
1147
|
+
version: 111,
|
|
1148
|
+
name: "dashboard-revisions-table",
|
|
1149
|
+
sql: {
|
|
1150
|
+
postgres: `CREATE TABLE IF NOT EXISTS dashboard_revisions (
|
|
1151
|
+
id TEXT PRIMARY KEY,
|
|
1152
|
+
dashboard_id TEXT NOT NULL,
|
|
1153
|
+
kind TEXT NOT NULL,
|
|
1154
|
+
title TEXT NOT NULL,
|
|
1155
|
+
config TEXT NOT NULL,
|
|
1156
|
+
created_at TEXT NOT NULL DEFAULT (now()::text),
|
|
1157
|
+
created_by TEXT,
|
|
1158
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1159
|
+
org_id TEXT
|
|
1160
|
+
)`,
|
|
1161
|
+
sqlite: `CREATE TABLE IF NOT EXISTS dashboard_revisions (
|
|
1162
|
+
id TEXT PRIMARY KEY,
|
|
1163
|
+
dashboard_id TEXT NOT NULL,
|
|
1164
|
+
kind TEXT NOT NULL,
|
|
1165
|
+
title TEXT NOT NULL,
|
|
1166
|
+
config TEXT NOT NULL,
|
|
1167
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1168
|
+
created_by TEXT,
|
|
1169
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1170
|
+
org_id TEXT
|
|
1171
|
+
)`,
|
|
1172
|
+
},
|
|
1173
|
+
},
|
|
1174
|
+
{
|
|
1175
|
+
version: 112,
|
|
1176
|
+
name: "dashboard-revisions-dashboard-created-idx",
|
|
1177
|
+
sql: `CREATE INDEX IF NOT EXISTS dashboard_revisions_dashboard_created_idx ON dashboard_revisions (dashboard_id, created_at)`,
|
|
1178
|
+
},
|
|
1179
|
+
{
|
|
1180
|
+
version: 113,
|
|
1181
|
+
name: "analysis-revisions-table",
|
|
1182
|
+
sql: {
|
|
1183
|
+
postgres: `CREATE TABLE IF NOT EXISTS analysis_revisions (
|
|
1184
|
+
id TEXT PRIMARY KEY,
|
|
1185
|
+
analysis_id TEXT NOT NULL,
|
|
1186
|
+
name TEXT NOT NULL,
|
|
1187
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1188
|
+
question TEXT NOT NULL DEFAULT '',
|
|
1189
|
+
instructions TEXT NOT NULL DEFAULT '',
|
|
1190
|
+
data_sources TEXT NOT NULL DEFAULT '[]',
|
|
1191
|
+
result_markdown TEXT NOT NULL DEFAULT '',
|
|
1192
|
+
result_data TEXT,
|
|
1193
|
+
created_at TEXT NOT NULL DEFAULT (now()::text),
|
|
1194
|
+
created_by TEXT,
|
|
1195
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1196
|
+
org_id TEXT
|
|
1197
|
+
)`,
|
|
1198
|
+
sqlite: `CREATE TABLE IF NOT EXISTS analysis_revisions (
|
|
1199
|
+
id TEXT PRIMARY KEY,
|
|
1200
|
+
analysis_id TEXT NOT NULL,
|
|
1201
|
+
name TEXT NOT NULL,
|
|
1202
|
+
description TEXT NOT NULL DEFAULT '',
|
|
1203
|
+
question TEXT NOT NULL DEFAULT '',
|
|
1204
|
+
instructions TEXT NOT NULL DEFAULT '',
|
|
1205
|
+
data_sources TEXT NOT NULL DEFAULT '[]',
|
|
1206
|
+
result_markdown TEXT NOT NULL DEFAULT '',
|
|
1207
|
+
result_data TEXT,
|
|
1208
|
+
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
1209
|
+
created_by TEXT,
|
|
1210
|
+
owner_email TEXT NOT NULL DEFAULT 'local@localhost',
|
|
1211
|
+
org_id TEXT
|
|
1212
|
+
)`,
|
|
1213
|
+
},
|
|
1214
|
+
},
|
|
1215
|
+
{
|
|
1216
|
+
version: 114,
|
|
1217
|
+
name: "analysis-revisions-analysis-created-idx",
|
|
1218
|
+
sql: `CREATE INDEX IF NOT EXISTS analysis_revisions_analysis_created_idx ON analysis_revisions (analysis_id, created_at)`,
|
|
1219
|
+
},
|
|
1220
|
+
{
|
|
1221
|
+
version: 115,
|
|
1222
|
+
name: "uptime-monitors-timeout-10s",
|
|
1223
|
+
sql: {
|
|
1224
|
+
postgres: `
|
|
1225
|
+
ALTER TABLE monitors ALTER COLUMN timeout_ms SET DEFAULT 10000;
|
|
1226
|
+
UPDATE monitors
|
|
1227
|
+
SET timeout_ms = 10000, updated_at = COALESCE(NULLIF(updated_at, ''), now()::text)
|
|
1228
|
+
WHERE timeout_ms IS NULL OR timeout_ms < 10000 OR timeout_ms = 15000
|
|
1229
|
+
`,
|
|
1230
|
+
sqlite: `
|
|
1231
|
+
UPDATE monitors
|
|
1232
|
+
SET timeout_ms = 10000, updated_at = COALESCE(NULLIF(updated_at, ''), datetime('now'))
|
|
1233
|
+
WHERE timeout_ms IS NULL OR timeout_ms < 10000 OR timeout_ms = 15000
|
|
1234
|
+
`,
|
|
1235
|
+
},
|
|
1236
|
+
},
|
|
1127
1237
|
],
|
|
1128
1238
|
{ table: "analytics_migrations" },
|
|
1129
1239
|
);
|
|
@@ -51,6 +51,7 @@ function rowToDashboard(row: any): DashboardRecord {
|
|
|
51
51
|
visibility: row.visibility,
|
|
52
52
|
createdAt: row.createdAt,
|
|
53
53
|
updatedAt: row.updatedAt,
|
|
54
|
+
updatedBy: row.updatedBy ?? null,
|
|
54
55
|
archivedAt: row.archivedAt ?? null,
|
|
55
56
|
hiddenAt: row.hiddenAt ?? null,
|
|
56
57
|
hiddenBy: row.hiddenBy ?? null,
|
|
@@ -377,6 +377,11 @@ Most operations should be actions. You only need custom routes in `server/routes
|
|
|
377
377
|
- **Webhooks** — external services POST to a specific URL
|
|
378
378
|
- **OAuth callbacks** — redirect-based flows that need specific URL patterns
|
|
379
379
|
|
|
380
|
+
When the agent needs a durable image or file URL, call the core `upload-image`
|
|
381
|
+
action or use `uploadFile()` in server code. Do not write base64 into SQL,
|
|
382
|
+
markdown, deck/design JSON, or action results. `_agentImages` on action results
|
|
383
|
+
is for ephemeral vision previews only, not persistence.
|
|
384
|
+
|
|
380
385
|
If it's a standard CRUD operation, data query, or a wrapper around an action, use the action instead.
|
|
381
386
|
|
|
382
387
|
## Legacy Pattern (bare export)
|
|
@@ -50,7 +50,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
50
50
|
|
|
51
51
|
For list/sidebar queries, use the same pattern — pass the counter into the queryKey of every list query you want to keep fresh.
|
|
52
52
|
|
|
53
|
-
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback
|
|
53
|
+
4. **Fallback** polling calls `/_agent-native/poll?since=N`. It runs every 2 seconds until SSE is connected, then relaxes to 15 seconds (`SSE_FALLBACK_INTERVAL_MS`). If SSE is disabled or unavailable (e.g., edge/serverless deployments), polling continues at the 2 s cadence. Polling is the universal serverless fallback: new framework writes are read from the durable `sync_events` log, while the older DB timestamp scan remains as a slower safety net for direct SQL writes and older processes.
|
|
54
54
|
|
|
55
55
|
5. When the agent writes to the database, the version increments, SSE/polling detects it, and React Query refetches the affected queries.
|
|
56
56
|
|
|
@@ -51,6 +51,13 @@ export default defineAction({
|
|
|
51
51
|
|
|
52
52
|
The legacy `parameters:` field (plain JSON Schema) has no runtime validation — do not use it for new code.
|
|
53
53
|
|
|
54
|
+
## Large Payloads
|
|
55
|
+
|
|
56
|
+
Do not accept or persist unbounded base64/file blobs through actions, SQL writes,
|
|
57
|
+
or `application_state`. Route uploads through the file-upload provider and store
|
|
58
|
+
references. This prevents database bloat, slow hot paths, and accidental secret
|
|
59
|
+
or customer-data embedding inside binary payloads.
|
|
60
|
+
|
|
54
61
|
## SQL Injection
|
|
55
62
|
|
|
56
63
|
Never concatenate user input into SQL strings. Use Drizzle ORM's query builder (always safe) or parameterized queries:
|