@agent-native/core 0.115.4 → 0.117.0
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/agent-native.eject.json +1 -0
- package/corpus/README.md +3 -3
- package/corpus/core/CHANGELOG.md +43 -0
- package/corpus/core/agent-native.eject.json +1 -0
- package/corpus/core/docs/AGENTS.md +1 -1
- package/corpus/core/docs/content/agent-mentions.mdx +2 -2
- package/corpus/core/docs/content/{workspace.mdx → agent-resources.mdx} +1 -7
- package/corpus/core/docs/content/agent-teams.mdx +4 -4
- package/corpus/core/docs/content/dispatch.mdx +10 -10
- package/corpus/core/docs/content/drop-in-agent.mdx +4 -4
- package/corpus/core/docs/content/external-agents.mdx +1 -1
- package/corpus/core/docs/content/frames.mdx +1 -1
- package/corpus/core/docs/content/integrations.mdx +13 -2
- package/corpus/core/docs/content/key-concepts.mdx +2 -2
- package/corpus/core/docs/content/mcp-clients.mdx +7 -1
- package/corpus/core/docs/content/multi-app-workspace.mdx +3 -3
- package/corpus/core/docs/content/real-time-collaboration.mdx +33 -15
- package/corpus/core/docs/content/recurring-jobs.mdx +7 -7
- package/corpus/core/docs/content/skills-guide.mdx +4 -4
- package/corpus/core/docs/content/template-brain.mdx +1 -1
- package/corpus/core/docs/content/toolkit-collaboration.mdx +1 -1
- package/corpus/core/docs/content/using-your-agent.mdx +1 -1
- package/corpus/core/docs/content/what-is-agent-native.mdx +4 -4
- package/corpus/core/docs/content/workspace-management.mdx +4 -4
- package/corpus/core/package.json +4 -1
- package/corpus/core/src/agent/production-agent.ts +13 -3
- package/corpus/core/src/cli/doctor.ts +6 -1
- package/corpus/core/src/client/AgentPanel.tsx +79 -41
- package/corpus/core/src/client/AssistantChat.tsx +71 -25
- package/corpus/core/src/client/FeedbackButton.tsx +33 -7
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +6 -8
- package/corpus/core/src/client/agent-chat-adapter.ts +11 -2
- package/corpus/core/src/client/agent-page/AgentTabFrame.tsx +22 -3
- package/corpus/core/src/client/agent-page/AgentTabsPage.tsx +34 -19
- package/corpus/core/src/client/analytics.ts +7 -0
- package/corpus/core/src/client/chat/message-components.tsx +43 -16
- package/corpus/core/src/client/chat/tool-call-display.tsx +30 -18
- package/corpus/core/src/client/chat/tool-render-registry.tsx +2 -0
- package/corpus/core/src/client/resources/McpConnectionSuggestion.tsx +19 -2
- package/corpus/core/src/client/resources/ResourceTree.tsx +7 -10
- package/corpus/core/src/client/resources/ResourcesPanel.tsx +33 -70
- package/corpus/core/src/client/resources/mcp-connection-resume.ts +107 -0
- package/corpus/core/src/client/resources/mcp-integration-catalog.ts +65 -12
- package/corpus/core/src/client/resources/mcp-integration-logos.ts +4 -0
- package/corpus/core/src/client/sse-event-processor.ts +50 -14
- package/corpus/core/src/client/use-db-sync.ts +370 -21
- package/corpus/core/src/collab/client.ts +26 -2
- package/corpus/core/src/connections/catalog.ts +7 -1
- package/corpus/core/src/db/ddl-guard.ts +49 -10
- package/corpus/core/src/deploy/build.ts +25 -2
- package/corpus/core/src/eject/remote-mcp-presets.ts +37 -5
- package/corpus/core/src/guards/explicit-collab-access.ts +229 -0
- package/corpus/core/src/guards/index.ts +1 -0
- package/corpus/core/src/localization/default-messages.ts +54 -5
- package/corpus/core/src/mcp/connect-route.ts +34 -51
- package/corpus/core/src/provider-api/index.ts +1 -1
- package/corpus/core/src/realtime-protocol.ts +92 -0
- package/corpus/core/src/server/collab-plugin.ts +137 -18
- package/corpus/core/src/server/core-routes-plugin.ts +7 -0
- package/corpus/core/src/server/index.ts +2 -0
- package/corpus/core/src/server/poll-events.ts +29 -8
- package/corpus/core/src/server/poll.ts +1123 -865
- package/corpus/core/src/server/realtime-token.ts +106 -0
- package/corpus/core/src/server/sentry-config.ts +40 -0
- package/corpus/core/src/server/short-lived-token.ts +156 -0
- package/corpus/core/src/server/sse.ts +15 -0
- package/corpus/core/src/server/ssr-handler.ts +9 -3
- package/corpus/core/src/styles/agent-native.css +40 -0
- package/corpus/core/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/core/src/templates/default/.agents/skills/real-time-collab/SKILL.md +35 -13
- package/corpus/core/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/core/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/core/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +35 -13
- 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/voice-transcription/SKILL.md +12 -8
- package/corpus/templates/analytics/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/analytics/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/analytics/AGENTS.md +7 -2
- package/corpus/templates/analytics/actions/export-dashboard-panel-to-google-sheet.ts +144 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +7 -88
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +34 -37
- package/corpus/templates/analytics/app/global.css +0 -4
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +2 -7
- package/corpus/templates/analytics/app/i18n-data.ts +107 -0
- package/corpus/templates/analytics/app/lib/data-source-status.ts +9 -0
- package/corpus/templates/analytics/app/lib/sql-query.ts +1 -2
- package/corpus/templates/analytics/app/pages/Ask.tsx +2 -7
- package/corpus/templates/analytics/app/pages/DataSources.tsx +48 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +59 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +2 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/report-panel-window.ts +3 -1
- package/corpus/templates/analytics/changelog/2026-07-22-clear-background-chat-progress.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-22-daily-dashboard-email-query-batches.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-22-export-table-panels-directly-to-google-sheets.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-22-recent-chats-can-expand-in-the-sidebar.md +6 -0
- package/corpus/templates/analytics/seeds/dashboards/agent-native-templates-first-party.json +8 -8
- package/corpus/templates/analytics/server/lib/canonical-first-party-dashboard-repair.ts +31 -0
- package/corpus/templates/analytics/server/lib/dashboard-report-subscriptions.ts +3 -5
- package/corpus/templates/analytics/server/lib/dashboard-report.ts +12 -0
- package/corpus/templates/analytics/server/lib/first-party-dashboard-repair.ts +3 -5
- package/corpus/templates/analytics/server/lib/first-party-metric-catalog.ts +150 -21
- package/corpus/templates/analytics/server/lib/google-sheets-export.ts +188 -0
- package/corpus/templates/analytics/server/lib/provider-api.ts +7 -2
- package/corpus/templates/analytics/server/plugins/collab.ts +6 -3
- package/corpus/templates/analytics/shared/sql-query-limits.ts +1 -0
- package/corpus/templates/assets/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/assets/.agents/skills/real-time-collab/SKILL.md +35 -13
- package/corpus/templates/assets/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/assets/app/components/layout/Layout.tsx +1 -1
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +54 -62
- package/corpus/templates/assets/app/hooks/use-navigation-state.ts +2 -6
- package/corpus/templates/assets/app/routes/_index.tsx +8 -5
- package/corpus/templates/assets/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-22-recent-chats-expand-from-a-compact-list.md +6 -0
- package/corpus/templates/brain/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/brain/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/brain/actions/rotate-source-ingest-token.ts +77 -0
- package/corpus/templates/brain/app/components/layout/Layout.tsx +5 -1
- package/corpus/templates/brain/app/components/layout/Sidebar.tsx +38 -51
- package/corpus/templates/brain/app/hooks/use-navigation-state.ts +2 -9
- package/corpus/templates/brain/app/i18n-data.ts +120 -0
- package/corpus/templates/brain/app/routes/sources.tsx +156 -5
- package/corpus/templates/brain/changelog/2026-07-22-chat-runs-always-end-with-an-answer.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-22-clear-background-chat-progress.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-22-long-chat-titles-stay-inside-the-sidebar.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-22-new-clips-and-webhook-sources-now-show-their-secure-connecti.md +6 -0
- package/corpus/templates/brain/jobs/emit-netlify-brain-queue-cron.ts +125 -0
- package/corpus/templates/brain/netlify.toml +5 -1
- package/corpus/templates/brain/scripts/configure-prod-clips-source.ts +50 -0
- package/corpus/templates/brain/server/lib/capture-sanitization.ts +64 -6
- package/corpus/templates/brain/server/plugins/agent-chat.ts +4 -0
- package/corpus/templates/brain/server/plugins/brain-jobs.ts +1 -0
- package/corpus/templates/brain/server/routes/api/brain/queue/run.post.ts +55 -0
- package/corpus/templates/calendar/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/calendar/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/chat/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +43 -67
- package/corpus/templates/chat/app/hooks/use-navigation-state.ts +2 -7
- package/corpus/templates/chat/app/routes/_index.tsx +8 -5
- package/corpus/templates/chat/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
- package/corpus/templates/chat/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/corpus/templates/chat/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +6 -0
- package/corpus/templates/clips/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/clips/AGENTS.md +9 -1
- package/corpus/templates/clips/actions/export-to-brain.ts +82 -6
- package/corpus/templates/clips/actions/request-transcript.ts +25 -14
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +12 -0
- package/corpus/templates/clips/app/i18n/en-US.ts +1 -0
- package/corpus/templates/clips/changelog/2026-07-22-clips-transcripts-now-retry-brain-export.md +5 -0
- package/corpus/templates/clips/changelog/2026-07-22-german-umlauts-and-other-non-ascii-characters-now-paste-corr.md +6 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +26 -3
- package/corpus/templates/clips/jobs/emit-netlify-brain-export-cron.ts +118 -0
- package/corpus/templates/clips/netlify.toml +4 -1
- package/corpus/templates/clips/scripts/configure-prod-brain-export.ts +92 -0
- package/corpus/templates/clips/server/jobs/brain-export.ts +182 -0
- package/corpus/templates/clips/server/lib/brain-export-state.ts +42 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +5 -1
- package/corpus/templates/clips/server/plugins/calendar-jobs.ts +2 -0
- package/corpus/templates/clips/server/register-secrets.ts +42 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +10 -1
- package/corpus/templates/clips/server/routes/api/clips/brain-export/run.post.ts +45 -0
- package/corpus/templates/content/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/content/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +566 -299
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +14 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +1 -1
- package/corpus/templates/content/app/i18n-data.ts +90 -0
- package/corpus/templates/content/server/plugins/collab.ts +1 -1
- package/corpus/templates/crm/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/crm/actions/_crm-data-program-actions.ts +15 -0
- package/corpus/templates/crm/actions/get-crm-dashboard-panel.ts +2 -0
- package/corpus/templates/crm/actions/run.ts +2 -6
- package/corpus/templates/crm/actions/update-crm-record.ts +4 -2
- package/corpus/templates/crm/app/components/layout/CrmLayout.tsx +6 -0
- package/corpus/templates/crm/changelog/2026-07-22-crm-field-validation-now-explains-which-fields-cannot-be-edi.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-22-fixed-hubspot-contact-syncs-missing-records-updated-after-th.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-22-fixed-pipeline-dashboard-panels-so-their-opportunity-data-lo.md +6 -0
- package/corpus/templates/crm/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/corpus/templates/crm/server/crm/hubspot-adapter.ts +4 -1
- package/corpus/templates/design/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/design/server/plugins/collab.ts +11 -8
- package/corpus/templates/dispatch/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/forms/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/forms/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/forms/app/components/layout/Layout.tsx +1 -0
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/forms/app/hooks/use-navigation-state.ts +2 -9
- package/corpus/templates/forms/app/root.tsx +1 -6
- package/corpus/templates/forms/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/corpus/templates/macros/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/mail/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/mail/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +2 -2
- package/corpus/templates/plan/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/plan/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/plan/app/components/layout/Layout.tsx +5 -1
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +57 -72
- package/corpus/templates/plan/app/hooks/use-navigation-state.ts +2 -9
- package/corpus/templates/plan/app/lib/create-plan-routing.ts +2 -2
- package/corpus/templates/plan/app/pages/PlanChatPage.tsx +0 -1
- package/corpus/templates/plan/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-22-recent-chats-and-sidebar-controls-are-more-compact.md +6 -0
- package/corpus/templates/plan/server/plugins/collab.ts +5 -2
- package/corpus/templates/slides/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +1 -1
- package/corpus/templates/slides/server/plugins/collab.ts +11 -8
- package/corpus/templates/tasks/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/corpus/templates/tasks/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/corpus/templates/tasks/app/components/layout/Sidebar.tsx +5 -22
- package/corpus/templates/tasks/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
- package/corpus/toolkit/CHANGELOG.md +7 -0
- package/corpus/toolkit/README.md +12 -3
- package/corpus/toolkit/package.json +1 -1
- package/corpus/toolkit/src/chat-history/ChatHistoryRail.tsx +89 -0
- package/corpus/toolkit/src/chat-history/index.ts +5 -0
- package/corpus/toolkit/src/chat-history.css +59 -0
- package/corpus/toolkit/src/composer/RealtimeVoiceMode.tsx +106 -36
- package/corpus/toolkit/src/composer/VoiceButton.tsx +49 -0
- package/corpus/toolkit/src/composer/realtime-voice-audio-level.ts +2 -1
- package/corpus/toolkit/src/composer/useRealtimeVoiceMode.tsx +5 -3
- package/corpus/toolkit/src/styles.css +35 -0
- package/dist/agent/production-agent.d.ts.map +1 -1
- package/dist/agent/production-agent.js +13 -3
- package/dist/agent/production-agent.js.map +1 -1
- package/dist/cli/doctor.d.ts +1 -1
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +5 -2
- package/dist/cli/doctor.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +1 -0
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +38 -18
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +55 -20
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/FeedbackButton.d.ts +4 -2
- package/dist/client/FeedbackButton.d.ts.map +1 -1
- package/dist/client/FeedbackButton.js +18 -2
- package/dist/client/FeedbackButton.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +2 -3
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +11 -3
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/agent-page/AgentTabFrame.d.ts +3 -1
- package/dist/client/agent-page/AgentTabFrame.d.ts.map +1 -1
- package/dist/client/agent-page/AgentTabFrame.js +3 -2
- package/dist/client/agent-page/AgentTabFrame.js.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.d.ts +2 -0
- package/dist/client/agent-page/AgentTabsPage.d.ts.map +1 -1
- package/dist/client/agent-page/AgentTabsPage.js +26 -15
- package/dist/client/agent-page/AgentTabsPage.js.map +1 -1
- package/dist/client/analytics.d.ts +10 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +6 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +17 -11
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts +11 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +16 -17
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/chat/tool-render-registry.d.ts +2 -0
- package/dist/client/chat/tool-render-registry.d.ts.map +1 -1
- package/dist/client/chat/tool-render-registry.js.map +1 -1
- package/dist/client/resources/McpConnectionSuggestion.d.ts.map +1 -1
- package/dist/client/resources/McpConnectionSuggestion.js +15 -1
- package/dist/client/resources/McpConnectionSuggestion.js.map +1 -1
- package/dist/client/resources/ResourceTree.d.ts.map +1 -1
- package/dist/client/resources/ResourceTree.js +3 -3
- package/dist/client/resources/ResourceTree.js.map +1 -1
- package/dist/client/resources/ResourcesPanel.d.ts +1 -0
- package/dist/client/resources/ResourcesPanel.d.ts.map +1 -1
- package/dist/client/resources/ResourcesPanel.js +34 -20
- package/dist/client/resources/ResourcesPanel.js.map +1 -1
- package/dist/client/resources/mcp-connection-resume.d.ts +11 -0
- package/dist/client/resources/mcp-connection-resume.d.ts.map +1 -0
- package/dist/client/resources/mcp-connection-resume.js +95 -0
- package/dist/client/resources/mcp-connection-resume.js.map +1 -0
- package/dist/client/resources/mcp-integration-catalog.d.ts +1 -0
- package/dist/client/resources/mcp-integration-catalog.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-catalog.js +63 -8
- package/dist/client/resources/mcp-integration-catalog.js.map +1 -1
- package/dist/client/resources/mcp-integration-logos.d.ts.map +1 -1
- package/dist/client/resources/mcp-integration-logos.js +4 -0
- package/dist/client/resources/mcp-integration-logos.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts +5 -0
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +39 -13
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/client/use-db-sync.d.ts +4 -3
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +332 -21
- package/dist/client/use-db-sync.js.map +1 -1
- package/dist/collab/client.d.ts.map +1 -1
- package/dist/collab/client.js +26 -2
- package/dist/collab/client.js.map +1 -1
- package/dist/collab/routes.d.ts +1 -1
- package/dist/connections/catalog.d.ts +1 -1
- package/dist/connections/catalog.d.ts.map +1 -1
- package/dist/connections/catalog.js +7 -1
- package/dist/connections/catalog.js.map +1 -1
- package/dist/db/ddl-guard.d.ts +7 -2
- package/dist/db/ddl-guard.d.ts.map +1 -1
- package/dist/db/ddl-guard.js +21 -9
- package/dist/db/ddl-guard.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +25 -2
- package/dist/deploy/build.js.map +1 -1
- package/dist/eject/remote-mcp-presets.d.ts.map +1 -1
- package/dist/eject/remote-mcp-presets.js +33 -3
- package/dist/eject/remote-mcp-presets.js.map +1 -1
- package/dist/guards/explicit-collab-access.d.ts +8 -0
- package/dist/guards/explicit-collab-access.d.ts.map +1 -0
- package/dist/guards/explicit-collab-access.js +214 -0
- package/dist/guards/explicit-collab-access.js.map +1 -0
- package/dist/guards/index.d.ts +1 -0
- package/dist/guards/index.d.ts.map +1 -1
- package/dist/guards/index.js +1 -0
- package/dist/guards/index.js.map +1 -1
- package/dist/localization/default-messages.d.ts +43 -0
- package/dist/localization/default-messages.d.ts.map +1 -1
- package/dist/localization/default-messages.js +48 -5
- package/dist/localization/default-messages.js.map +1 -1
- package/dist/mcp/connect-route.d.ts.map +1 -1
- package/dist/mcp/connect-route.js +34 -48
- package/dist/mcp/connect-route.js.map +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/provider-api/actions/custom-provider-registration.d.ts +7 -7
- package/dist/provider-api/actions/provider-api.d.ts +7 -7
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/provider-api/index.js +1 -1
- package/dist/provider-api/index.js.map +1 -1
- package/dist/realtime-protocol.d.ts +40 -0
- package/dist/realtime-protocol.d.ts.map +1 -0
- package/dist/realtime-protocol.js +69 -0
- package/dist/realtime-protocol.js.map +1 -0
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/collab-plugin.d.ts +34 -3
- package/dist/server/collab-plugin.d.ts.map +1 -1
- package/dist/server/collab-plugin.js +65 -12
- package/dist/server/collab-plugin.js.map +1 -1
- package/dist/server/core-routes-plugin.d.ts.map +1 -1
- package/dist/server/core-routes-plugin.js +3 -0
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/poll-events.d.ts +7 -1
- package/dist/server/poll-events.d.ts.map +1 -1
- package/dist/server/poll-events.js +24 -7
- package/dist/server/poll-events.js.map +1 -1
- package/dist/server/poll.d.ts +223 -42
- package/dist/server/poll.d.ts.map +1 -1
- package/dist/server/poll.js +849 -737
- package/dist/server/poll.js.map +1 -1
- package/dist/server/realtime-token.d.ts +34 -0
- package/dist/server/realtime-token.d.ts.map +1 -0
- package/dist/server/realtime-token.js +85 -0
- package/dist/server/realtime-token.js.map +1 -0
- package/dist/server/sentry-config.d.ts +12 -0
- package/dist/server/sentry-config.d.ts.map +1 -1
- package/dist/server/sentry-config.js +34 -0
- package/dist/server/sentry-config.js.map +1 -1
- package/dist/server/short-lived-token.d.ts +46 -0
- package/dist/server/short-lived-token.d.ts.map +1 -1
- package/dist/server/short-lived-token.js +104 -0
- package/dist/server/short-lived-token.js.map +1 -1
- package/dist/server/sse.d.ts +1 -1
- package/dist/server/sse.d.ts.map +1 -1
- package/dist/server/sse.js +4 -0
- package/dist/server/sse.js.map +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +5 -3
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/styles/agent-native.css +40 -0
- package/dist/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/dist/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/chat/app/components/layout/Layout.tsx +1 -0
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +43 -67
- package/dist/templates/chat/app/hooks/use-navigation-state.ts +2 -7
- package/dist/templates/chat/app/routes/_index.tsx +8 -5
- package/dist/templates/chat/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
- package/dist/templates/chat/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/dist/templates/chat/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +6 -0
- package/dist/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/dist/templates/default/.agents/skills/real-time-collab/SKILL.md +35 -13
- package/dist/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/dist/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +35 -13
- package/dist/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/dist/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +12 -8
- package/docs/AGENTS.md +1 -1
- package/docs/content/agent-mentions.mdx +2 -2
- package/docs/content/{workspace.mdx → agent-resources.mdx} +1 -7
- package/docs/content/agent-teams.mdx +4 -4
- package/docs/content/dispatch.mdx +10 -10
- package/docs/content/drop-in-agent.mdx +4 -4
- package/docs/content/external-agents.mdx +1 -1
- package/docs/content/frames.mdx +1 -1
- package/docs/content/integrations.mdx +13 -2
- package/docs/content/key-concepts.mdx +2 -2
- package/docs/content/mcp-clients.mdx +7 -1
- package/docs/content/multi-app-workspace.mdx +3 -3
- package/docs/content/real-time-collaboration.mdx +33 -15
- package/docs/content/recurring-jobs.mdx +7 -7
- package/docs/content/skills-guide.mdx +4 -4
- package/docs/content/template-brain.mdx +1 -1
- package/docs/content/toolkit-collaboration.mdx +1 -1
- package/docs/content/using-your-agent.mdx +1 -1
- package/docs/content/what-is-agent-native.mdx +4 -4
- package/docs/content/workspace-management.mdx +4 -4
- package/package.json +6 -3
- package/src/agent/production-agent.ts +13 -3
- package/src/cli/doctor.ts +6 -1
- package/src/client/AgentPanel.tsx +79 -41
- package/src/client/AssistantChat.tsx +71 -25
- package/src/client/FeedbackButton.tsx +33 -7
- package/src/client/MultiTabAssistantChat.tsx +6 -8
- package/src/client/agent-chat-adapter.ts +11 -2
- package/src/client/agent-page/AgentTabFrame.tsx +22 -3
- package/src/client/agent-page/AgentTabsPage.tsx +34 -19
- package/src/client/analytics.ts +7 -0
- package/src/client/chat/message-components.tsx +43 -16
- package/src/client/chat/tool-call-display.tsx +30 -18
- package/src/client/chat/tool-render-registry.tsx +2 -0
- package/src/client/resources/McpConnectionSuggestion.tsx +19 -2
- package/src/client/resources/ResourceTree.tsx +7 -10
- package/src/client/resources/ResourcesPanel.tsx +33 -70
- package/src/client/resources/mcp-connection-resume.ts +107 -0
- package/src/client/resources/mcp-integration-catalog.ts +65 -12
- package/src/client/resources/mcp-integration-logos.ts +4 -0
- package/src/client/sse-event-processor.ts +50 -14
- package/src/client/use-db-sync.ts +370 -21
- package/src/collab/client.ts +26 -2
- package/src/connections/catalog.ts +7 -1
- package/src/db/ddl-guard.ts +49 -10
- package/src/deploy/build.ts +25 -2
- package/src/eject/remote-mcp-presets.ts +37 -5
- package/src/guards/explicit-collab-access.ts +229 -0
- package/src/guards/index.ts +1 -0
- package/src/localization/default-messages.ts +54 -5
- package/src/mcp/connect-route.ts +34 -51
- package/src/provider-api/index.ts +1 -1
- package/src/realtime-protocol.ts +92 -0
- package/src/server/collab-plugin.ts +137 -18
- package/src/server/core-routes-plugin.ts +7 -0
- package/src/server/index.ts +2 -0
- package/src/server/poll-events.ts +29 -8
- package/src/server/poll.ts +1123 -865
- package/src/server/realtime-token.ts +106 -0
- package/src/server/sentry-config.ts +40 -0
- package/src/server/short-lived-token.ts +156 -0
- package/src/server/sse.ts +15 -0
- package/src/server/ssr-handler.ts +9 -3
- package/src/styles/agent-native.css +40 -0
- package/src/templates/chat/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/src/templates/chat/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/chat/app/components/layout/Layout.tsx +1 -0
- package/src/templates/chat/app/components/layout/Sidebar.tsx +43 -67
- package/src/templates/chat/app/hooks/use-navigation-state.ts +2 -7
- package/src/templates/chat/app/routes/_index.tsx +8 -5
- package/src/templates/chat/changelog/2026-07-22-compact-sidebar-footer.md +6 -0
- package/src/templates/chat/changelog/2026-07-22-full-page-chat-keeps-the-active-conversation-when-moving-to-.md +6 -0
- package/src/templates/chat/changelog/2026-07-22-recent-chats-are-easier-to-scan-and-expand.md +6 -0
- package/src/templates/default/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/src/templates/default/.agents/skills/real-time-collab/SKILL.md +35 -13
- package/src/templates/default/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/headless/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -1
- package/src/templates/workspace-core/.agents/skills/real-time-collab/SKILL.md +35 -13
- package/src/templates/workspace-core/.agents/skills/real-time-sync/SKILL.md +1 -1
- package/src/templates/workspace-core/.agents/skills/voice-transcription/SKILL.md +12 -8
package/dist/db/ddl-guard.js
CHANGED
|
@@ -40,9 +40,13 @@ const PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;
|
|
|
40
40
|
* This is a plain read (no lock), so it never blocks on an `ACCESS EXCLUSIVE`
|
|
41
41
|
* lock the way `CREATE`/`ALTER` would.
|
|
42
42
|
*/
|
|
43
|
-
export async function pgTableExists(table, injectedClient) {
|
|
44
|
-
|
|
43
|
+
export async function pgTableExists(table, injectedClient, dialectIsPostgres) {
|
|
44
|
+
// The dialect override travels with an injected client: a multi-app process
|
|
45
|
+
// (the hosted Realtime Gateway) probes a per-app Postgres DB even when its
|
|
46
|
+
// own process-global DB is absent or SQLite.
|
|
47
|
+
if (!(dialectIsPostgres ?? isPostgres()) || !PLAIN_IDENTIFIER.test(table)) {
|
|
45
48
|
return false;
|
|
49
|
+
}
|
|
46
50
|
const client = injectedClient ?? getDbExec();
|
|
47
51
|
try {
|
|
48
52
|
const { rows } = await client.execute({
|
|
@@ -95,9 +99,11 @@ export async function pgColumnExists(table, column, injectedClient) {
|
|
|
95
99
|
* connection this can hang just like a `CREATE`/`ALTER` would; checking first
|
|
96
100
|
* skips the lock on the already-migrated hot path.
|
|
97
101
|
*/
|
|
98
|
-
export async function pgIndexExists(indexName, injectedClient) {
|
|
99
|
-
if (!
|
|
102
|
+
export async function pgIndexExists(indexName, injectedClient, dialectIsPostgres) {
|
|
103
|
+
if (!(dialectIsPostgres ?? isPostgres()) ||
|
|
104
|
+
!PLAIN_IDENTIFIER.test(indexName)) {
|
|
100
105
|
return false;
|
|
106
|
+
}
|
|
101
107
|
const client = injectedClient ?? getDbExec();
|
|
102
108
|
try {
|
|
103
109
|
const { rows } = await client.execute({
|
|
@@ -138,10 +144,14 @@ export async function pgIndexExists(indexName, injectedClient) {
|
|
|
138
144
|
* `false` only when it already existed up front (no DDL issued).
|
|
139
145
|
*/
|
|
140
146
|
export async function ensureSchemaObject(options) {
|
|
141
|
-
const { probe, ddl, label, lockTimeout, injectedClient } = options;
|
|
147
|
+
const { probe, ddl, label, lockTimeout, injectedClient, dialectIsPostgres } = options;
|
|
142
148
|
if (await probe())
|
|
143
149
|
return false;
|
|
144
|
-
const ran = await runGuardedDdl(ddl, {
|
|
150
|
+
const ran = await runGuardedDdl(ddl, {
|
|
151
|
+
lockTimeout,
|
|
152
|
+
injectedClient,
|
|
153
|
+
dialectIsPostgres,
|
|
154
|
+
});
|
|
145
155
|
if (ran)
|
|
146
156
|
return true;
|
|
147
157
|
// The DDL was swallowed by a lock-timeout. The object is virtually always
|
|
@@ -163,11 +173,12 @@ export async function ensureSchemaObject(options) {
|
|
|
163
173
|
*/
|
|
164
174
|
export async function ensureTableExists(table, createSql, options = {}) {
|
|
165
175
|
return ensureSchemaObject({
|
|
166
|
-
probe: () => pgTableExists(table, options.injectedClient),
|
|
176
|
+
probe: () => pgTableExists(table, options.injectedClient, options.dialectIsPostgres),
|
|
167
177
|
ddl: createSql,
|
|
168
178
|
label: `table ${table}`,
|
|
169
179
|
lockTimeout: options.lockTimeout,
|
|
170
180
|
injectedClient: options.injectedClient,
|
|
181
|
+
dialectIsPostgres: options.dialectIsPostgres,
|
|
171
182
|
});
|
|
172
183
|
}
|
|
173
184
|
/**
|
|
@@ -189,11 +200,12 @@ export async function ensureColumnExists(table, column, addColumnSql, options =
|
|
|
189
200
|
*/
|
|
190
201
|
export async function ensureIndexExists(indexName, createIndexSql, options = {}) {
|
|
191
202
|
return ensureSchemaObject({
|
|
192
|
-
probe: () => pgIndexExists(indexName, options.injectedClient),
|
|
203
|
+
probe: () => pgIndexExists(indexName, options.injectedClient, options.dialectIsPostgres),
|
|
193
204
|
ddl: createIndexSql,
|
|
194
205
|
label: `index ${indexName}`,
|
|
195
206
|
lockTimeout: options.lockTimeout,
|
|
196
207
|
injectedClient: options.injectedClient,
|
|
208
|
+
dialectIsPostgres: options.dialectIsPostgres,
|
|
197
209
|
});
|
|
198
210
|
}
|
|
199
211
|
/** True when an error looks like a Postgres `lock_timeout` (SQLSTATE 55P03). */
|
|
@@ -226,7 +238,7 @@ export function isLockTimeoutError(err) {
|
|
|
226
238
|
*/
|
|
227
239
|
export async function runGuardedDdl(ddl, options = {}) {
|
|
228
240
|
const client = options.injectedClient ?? getDbExec();
|
|
229
|
-
if (!isPostgres()) {
|
|
241
|
+
if (!(options.dialectIsPostgres ?? isPostgres())) {
|
|
230
242
|
await client.execute(ddl);
|
|
231
243
|
return true;
|
|
232
244
|
}
|
package/dist/db/ddl-guard.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ddl-guard.js","sourceRoot":"","sources":["../../src/db/ddl-guard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Guards for on-demand `ensureTable()` DDL so the common already-migrated path\n * takes NO `ACCESS EXCLUSIVE` lock on Postgres.\n *\n * Lives in its own module (like `./widen-columns.js`) so stores can import it\n * without every `vi.mock(\"../db/client.js\")` test needing to stub it: the\n * helpers resolve `isPostgres()` / `getDbExec()` through `client.js`, so a test\n * that mocks the client to SQLite (`isPostgres: () => false`) makes the\n * Postgres-only existence checks no-ops automatically.\n *\n * ## Why\n *\n * `ensureTable()` runs once per process on first DB touch. In a long-lived Node\n * server the cost is paid once and is invisible. In a Netlify `-background`\n * function the process is fresh, so this is the FIRST touch — and the\n * `CREATE TABLE`/`ALTER TABLE ... ADD COLUMN` DDL it issues takes an\n * `ACCESS EXCLUSIVE` lock on the shared Neon Postgres database. Behind a\n * concurrent connection that already holds a conflicting lock, that DDL can\n * block ~indefinitely (observed >16s hangs in the bg worker vs ~1s inline).\n *\n * The tables/columns are essentially always already present in production, so\n * the DDL is redundant in the hot path. These helpers let a store cheaply check\n * `information_schema` first and only issue DDL when something is actually\n * missing — and when DDL must run, wrap it in a short `lock_timeout` so a\n * contended lock fails fast instead of hanging.\n *\n * All of this is Postgres-only behaviour gated on `isPostgres()`. On SQLite\n * (local dev) there is no such lock problem, so callers keep their existing\n * behaviour there.\n */\n\nimport { isPostgres, getDbExec, type DbExec } from \"./client.js\";\n\nconst PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * True when running against Postgres AND the given table already exists in the\n * `public` schema. Returns `false` on SQLite (callers gate their own behaviour\n * there), for invalid identifiers, or when `information_schema` is unreadable —\n * the conservative answer \"not known to exist\" makes the caller fall through to\n * its idempotent `CREATE TABLE IF NOT EXISTS`, preserving today's behaviour.\n *\n * This is a plain read (no lock), so it never blocks on an `ACCESS EXCLUSIVE`\n * lock the way `CREATE`/`ALTER` would.\n */\nexport async function pgTableExists(\n table: string,\n injectedClient?: DbExec,\n): Promise<boolean> {\n if (!isPostgres() || !PLAIN_IDENTIFIER.test(table)) return false;\n const client = injectedClient ?? getDbExec();\n try {\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = ? LIMIT 1`,\n args: [table],\n });\n return rows.length > 0;\n } catch {\n // information_schema unreadable (permissions / non-standard backend) —\n // report \"unknown\" so the caller falls back to IF NOT EXISTS.\n return false;\n }\n}\n\n/**\n * True when running against Postgres AND the given column already exists on the\n * given table in the `public` schema. Returns `false` on SQLite, for invalid\n * identifiers, or when `information_schema` is unreadable.\n *\n * Plain read — no lock taken.\n */\nexport async function pgColumnExists(\n table: string,\n column: string,\n injectedClient?: DbExec,\n): Promise<boolean> {\n if (!isPostgres()) return false;\n if (!PLAIN_IDENTIFIER.test(table) || !PLAIN_IDENTIFIER.test(column)) {\n return false;\n }\n const client = injectedClient ?? getDbExec();\n try {\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = ? AND column_name = ?\n LIMIT 1`,\n args: [table, column],\n });\n return rows.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * True when running against Postgres AND an index with the given name already\n * exists in the `public` schema. Returns `false` on SQLite, for invalid\n * identifiers, or when `pg_indexes` is unreadable.\n *\n * `CREATE INDEX` (without CONCURRENTLY) takes a `SHARE` lock that blocks\n * writes, so on a fresh background-worker process behind a concurrent\n * connection this can hang just like a `CREATE`/`ALTER` would; checking first\n * skips the lock on the already-migrated hot path.\n */\nexport async function pgIndexExists(\n indexName: string,\n injectedClient?: DbExec,\n): Promise<boolean> {\n if (!isPostgres() || !PLAIN_IDENTIFIER.test(indexName)) return false;\n const client = injectedClient ?? getDbExec();\n try {\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM pg_indexes\n WHERE schemaname = 'public' AND indexname = ? LIMIT 1`,\n args: [indexName],\n });\n return rows.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Probe → guarded-DDL → re-probe sequence for one piece of on-demand schema.\n *\n * This is the single safe primitive every `ensureTable()` should use so a\n * swallowed `lock_timeout` can NEVER poison a store's init memo with missing\n * schema. The flow:\n *\n * 1. `probe()` — cheap, lock-free existence check (pgTableExists / etc.).\n * Already present → return `false` (nothing to do; the hot path takes NO\n * lock).\n * 2. Missing → run `ddl` through `runGuardedDdl` (bounded `lock_timeout`).\n * - DDL completed → return `true`.\n * - DDL was swallowed by a lock-timeout (`runGuardedDdl` returned `false`)\n * → RE-PROBE. A concurrent connection virtually always created the\n * object meanwhile, so the re-probe usually now reports it present →\n * return `true`. But if it is STILL missing, the required schema does\n * NOT exist and we must NOT let the caller memoize success — so THROW.\n * The caller's `_initPromise` rejects and the next call retries instead\n * of running forever against absent schema.\n *\n * On SQLite the probe is always `false` (helpers no-op there) and the SQLite\n * branch of each store keeps its own create-then-catch behaviour, so this is a\n * Postgres-path primitive in practice. Returns `true` when the object exists\n * after this call (either pre-existing-after-timeout-race or freshly created),\n * `false` only when it already existed up front (no DDL issued).\n */\nexport async function ensureSchemaObject(options: {\n /** Lock-free existence check; `true` ⇒ already present, skip DDL. */\n probe: () => Promise<boolean>;\n /** DDL to run only when `probe()` reports the object missing. */\n ddl: string;\n /** Human-readable name of what's being ensured, for the error message. */\n label: string;\n /** Forwarded to `runGuardedDdl`. */\n lockTimeout?: string;\n /** Injectable client for tests. */\n injectedClient?: DbExec;\n}): Promise<boolean> {\n const { probe, ddl, label, lockTimeout, injectedClient } = options;\n if (await probe()) return false;\n const ran = await runGuardedDdl(ddl, { lockTimeout, injectedClient });\n if (ran) return true;\n // The DDL was swallowed by a lock-timeout. The object is virtually always\n // already correct by the time a contended boot retries (a concurrent\n // connection created it), so re-probe before giving up.\n if (await probe()) return true;\n // Still missing after a swallowed timeout: do NOT memoize success with absent\n // schema. Throw so the caller's init promise rejects and the next call\n // retries, rather than leaving ensureTable \"initialized\" against a table/\n // column/index that does not exist.\n throw new Error(\n `ensureSchemaObject: required schema \"${label}\" is still missing after a ` +\n `lock-timed-out DDL; refusing to memoize init success. The next call will retry.`,\n );\n}\n\n/**\n * Convenience wrapper: ensure a TABLE exists (probe via `pgTableExists`).\n * No-op-returns `false` on SQLite (probe is always false there) — callers run\n * the SQLite create on their own branch, so this is used on the Postgres path.\n */\nexport async function ensureTableExists(\n table: string,\n createSql: string,\n options: { lockTimeout?: string; injectedClient?: DbExec } = {},\n): Promise<boolean> {\n return ensureSchemaObject({\n probe: () => pgTableExists(table, options.injectedClient),\n ddl: createSql,\n label: `table ${table}`,\n lockTimeout: options.lockTimeout,\n injectedClient: options.injectedClient,\n });\n}\n\n/**\n * Convenience wrapper: ensure a COLUMN exists (probe via `pgColumnExists`).\n * `addColumnSql` should be the full `ALTER TABLE … ADD COLUMN IF NOT EXISTS …`.\n */\nexport async function ensureColumnExists(\n table: string,\n column: string,\n addColumnSql: string,\n options: { lockTimeout?: string; injectedClient?: DbExec } = {},\n): Promise<boolean> {\n return ensureSchemaObject({\n probe: () => pgColumnExists(table, column, options.injectedClient),\n ddl: addColumnSql,\n label: `column ${table}.${column}`,\n lockTimeout: options.lockTimeout,\n injectedClient: options.injectedClient,\n });\n}\n\n/**\n * Convenience wrapper: ensure an INDEX exists (probe via `pgIndexExists`).\n * `createIndexSql` should be the full `CREATE INDEX IF NOT EXISTS <name> …`.\n */\nexport async function ensureIndexExists(\n indexName: string,\n createIndexSql: string,\n options: { lockTimeout?: string; injectedClient?: DbExec } = {},\n): Promise<boolean> {\n return ensureSchemaObject({\n probe: () => pgIndexExists(indexName, options.injectedClient),\n ddl: createIndexSql,\n label: `index ${indexName}`,\n lockTimeout: options.lockTimeout,\n injectedClient: options.injectedClient,\n });\n}\n\n/** True when an error looks like a Postgres `lock_timeout` (SQLSTATE 55P03). */\nexport function isLockTimeoutError(err: unknown): boolean {\n const anyErr = err as { code?: unknown; message?: unknown } | null;\n if (anyErr?.code === \"55P03\") return true;\n const msg = String(anyErr?.message ?? anyErr ?? \"\");\n return /lock[_ ]?timeout|canceling statement due to lock timeout/i.test(msg);\n}\n\n/**\n * Run a DDL statement that MUST execute (the schema is actually missing), with\n * a short `lock_timeout` so a contended `ACCESS EXCLUSIVE` lock fails fast\n * instead of hanging the whole process.\n *\n * Postgres path: wrap the DDL in an explicit transaction and set\n * `SET LOCAL lock_timeout` so the timeout is scoped to THIS transaction only\n * and reset automatically on COMMIT/ROLLBACK — it never leaks onto the pooled\n * (session-reused) connection the way a bare `SET lock_timeout` would. If the\n * DbExec has no `transaction` (shouldn't happen for Postgres, but defensively),\n * fall back to a session `SET` + `RESET` in a finally. A lock-timeout error is\n * swallowed: the table/column is virtually always already correct by the time a\n * contended boot retries, and the caller's memoization should still resolve so\n * the path isn't retried in a tight loop. Any non-lock-timeout error rethrows.\n *\n * SQLite path: no lock problem — just run the DDL directly.\n *\n * @returns `true` if the DDL ran to completion, `false` if it was skipped due to\n * a lock-timeout (so the caller can decide whether to log).\n */\nexport async function runGuardedDdl(\n ddl: string,\n options: { lockTimeout?: string; injectedClient?: DbExec } = {},\n): Promise<boolean> {\n const client = options.injectedClient ?? getDbExec();\n if (!isPostgres()) {\n await client.execute(ddl);\n return true;\n }\n\n const lockTimeout = options.lockTimeout ?? \"3s\";\n try {\n if (typeof client.transaction === \"function\") {\n await client.transaction(async (tx) => {\n // SET LOCAL is transaction-scoped: it reverts on COMMIT/ROLLBACK and\n // never persists on the pooled connection.\n await tx.execute(`SET LOCAL lock_timeout = '${lockTimeout}'`);\n await tx.execute(ddl);\n });\n } else {\n // Defensive fallback: no transaction support. Use a session SET and\n // guarantee a RESET so the timeout never leaks onto a reused connection.\n try {\n await client.execute(`SET lock_timeout = '${lockTimeout}'`);\n await client.execute(ddl);\n } finally {\n await client.execute(`RESET lock_timeout`).catch(() => {});\n }\n }\n return true;\n } catch (err) {\n if (isLockTimeoutError(err)) {\n // Contended lock — the schema is virtually always already correct by now.\n // Proceed; the caller's memoization still resolves so we don't loop.\n return false;\n }\n throw err;\n }\n}\n"]}
|
|
1
|
+
{"version":3,"file":"ddl-guard.js","sourceRoot":"","sources":["../../src/db/ddl-guard.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * Guards for on-demand `ensureTable()` DDL so the common already-migrated path\n * takes NO `ACCESS EXCLUSIVE` lock on Postgres.\n *\n * Lives in its own module (like `./widen-columns.js`) so stores can import it\n * without every `vi.mock(\"../db/client.js\")` test needing to stub it: the\n * helpers resolve `isPostgres()` / `getDbExec()` through `client.js`, so a test\n * that mocks the client to SQLite (`isPostgres: () => false`) makes the\n * Postgres-only existence checks no-ops automatically.\n *\n * ## Why\n *\n * `ensureTable()` runs once per process on first DB touch. In a long-lived Node\n * server the cost is paid once and is invisible. In a Netlify `-background`\n * function the process is fresh, so this is the FIRST touch — and the\n * `CREATE TABLE`/`ALTER TABLE ... ADD COLUMN` DDL it issues takes an\n * `ACCESS EXCLUSIVE` lock on the shared Neon Postgres database. Behind a\n * concurrent connection that already holds a conflicting lock, that DDL can\n * block ~indefinitely (observed >16s hangs in the bg worker vs ~1s inline).\n *\n * The tables/columns are essentially always already present in production, so\n * the DDL is redundant in the hot path. These helpers let a store cheaply check\n * `information_schema` first and only issue DDL when something is actually\n * missing — and when DDL must run, wrap it in a short `lock_timeout` so a\n * contended lock fails fast instead of hanging.\n *\n * All of this is Postgres-only behaviour gated on `isPostgres()`. On SQLite\n * (local dev) there is no such lock problem, so callers keep their existing\n * behaviour there.\n */\n\nimport { isPostgres, getDbExec, type DbExec } from \"./client.js\";\n\nconst PLAIN_IDENTIFIER = /^[A-Za-z_][A-Za-z0-9_]*$/;\n\n/**\n * True when running against Postgres AND the given table already exists in the\n * `public` schema. Returns `false` on SQLite (callers gate their own behaviour\n * there), for invalid identifiers, or when `information_schema` is unreadable —\n * the conservative answer \"not known to exist\" makes the caller fall through to\n * its idempotent `CREATE TABLE IF NOT EXISTS`, preserving today's behaviour.\n *\n * This is a plain read (no lock), so it never blocks on an `ACCESS EXCLUSIVE`\n * lock the way `CREATE`/`ALTER` would.\n */\nexport async function pgTableExists(\n table: string,\n injectedClient?: DbExec,\n dialectIsPostgres?: boolean,\n): Promise<boolean> {\n // The dialect override travels with an injected client: a multi-app process\n // (the hosted Realtime Gateway) probes a per-app Postgres DB even when its\n // own process-global DB is absent or SQLite.\n if (!(dialectIsPostgres ?? isPostgres()) || !PLAIN_IDENTIFIER.test(table)) {\n return false;\n }\n const client = injectedClient ?? getDbExec();\n try {\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM information_schema.tables\n WHERE table_schema = 'public' AND table_name = ? LIMIT 1`,\n args: [table],\n });\n return rows.length > 0;\n } catch {\n // information_schema unreadable (permissions / non-standard backend) —\n // report \"unknown\" so the caller falls back to IF NOT EXISTS.\n return false;\n }\n}\n\n/**\n * True when running against Postgres AND the given column already exists on the\n * given table in the `public` schema. Returns `false` on SQLite, for invalid\n * identifiers, or when `information_schema` is unreadable.\n *\n * Plain read — no lock taken.\n */\nexport async function pgColumnExists(\n table: string,\n column: string,\n injectedClient?: DbExec,\n): Promise<boolean> {\n if (!isPostgres()) return false;\n if (!PLAIN_IDENTIFIER.test(table) || !PLAIN_IDENTIFIER.test(column)) {\n return false;\n }\n const client = injectedClient ?? getDbExec();\n try {\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM information_schema.columns\n WHERE table_schema = 'public' AND table_name = ? AND column_name = ?\n LIMIT 1`,\n args: [table, column],\n });\n return rows.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * True when running against Postgres AND an index with the given name already\n * exists in the `public` schema. Returns `false` on SQLite, for invalid\n * identifiers, or when `pg_indexes` is unreadable.\n *\n * `CREATE INDEX` (without CONCURRENTLY) takes a `SHARE` lock that blocks\n * writes, so on a fresh background-worker process behind a concurrent\n * connection this can hang just like a `CREATE`/`ALTER` would; checking first\n * skips the lock on the already-migrated hot path.\n */\nexport async function pgIndexExists(\n indexName: string,\n injectedClient?: DbExec,\n dialectIsPostgres?: boolean,\n): Promise<boolean> {\n if (\n !(dialectIsPostgres ?? isPostgres()) ||\n !PLAIN_IDENTIFIER.test(indexName)\n ) {\n return false;\n }\n const client = injectedClient ?? getDbExec();\n try {\n const { rows } = await client.execute({\n sql: `SELECT 1 FROM pg_indexes\n WHERE schemaname = 'public' AND indexname = ? LIMIT 1`,\n args: [indexName],\n });\n return rows.length > 0;\n } catch {\n return false;\n }\n}\n\n/**\n * Probe → guarded-DDL → re-probe sequence for one piece of on-demand schema.\n *\n * This is the single safe primitive every `ensureTable()` should use so a\n * swallowed `lock_timeout` can NEVER poison a store's init memo with missing\n * schema. The flow:\n *\n * 1. `probe()` — cheap, lock-free existence check (pgTableExists / etc.).\n * Already present → return `false` (nothing to do; the hot path takes NO\n * lock).\n * 2. Missing → run `ddl` through `runGuardedDdl` (bounded `lock_timeout`).\n * - DDL completed → return `true`.\n * - DDL was swallowed by a lock-timeout (`runGuardedDdl` returned `false`)\n * → RE-PROBE. A concurrent connection virtually always created the\n * object meanwhile, so the re-probe usually now reports it present →\n * return `true`. But if it is STILL missing, the required schema does\n * NOT exist and we must NOT let the caller memoize success — so THROW.\n * The caller's `_initPromise` rejects and the next call retries instead\n * of running forever against absent schema.\n *\n * On SQLite the probe is always `false` (helpers no-op there) and the SQLite\n * branch of each store keeps its own create-then-catch behaviour, so this is a\n * Postgres-path primitive in practice. Returns `true` when the object exists\n * after this call (either pre-existing-after-timeout-race or freshly created),\n * `false` only when it already existed up front (no DDL issued).\n */\nexport async function ensureSchemaObject(options: {\n /** Lock-free existence check; `true` ⇒ already present, skip DDL. */\n probe: () => Promise<boolean>;\n /** DDL to run only when `probe()` reports the object missing. */\n ddl: string;\n /** Human-readable name of what's being ensured, for the error message. */\n label: string;\n /** Forwarded to `runGuardedDdl`. */\n lockTimeout?: string;\n /** Injectable client for tests. */\n injectedClient?: DbExec;\n /** Dialect override for injected per-app clients; defaults to the global. */\n dialectIsPostgres?: boolean;\n}): Promise<boolean> {\n const { probe, ddl, label, lockTimeout, injectedClient, dialectIsPostgres } =\n options;\n if (await probe()) return false;\n const ran = await runGuardedDdl(ddl, {\n lockTimeout,\n injectedClient,\n dialectIsPostgres,\n });\n if (ran) return true;\n // The DDL was swallowed by a lock-timeout. The object is virtually always\n // already correct by the time a contended boot retries (a concurrent\n // connection created it), so re-probe before giving up.\n if (await probe()) return true;\n // Still missing after a swallowed timeout: do NOT memoize success with absent\n // schema. Throw so the caller's init promise rejects and the next call\n // retries, rather than leaving ensureTable \"initialized\" against a table/\n // column/index that does not exist.\n throw new Error(\n `ensureSchemaObject: required schema \"${label}\" is still missing after a ` +\n `lock-timed-out DDL; refusing to memoize init success. The next call will retry.`,\n );\n}\n\n/**\n * Convenience wrapper: ensure a TABLE exists (probe via `pgTableExists`).\n * No-op-returns `false` on SQLite (probe is always false there) — callers run\n * the SQLite create on their own branch, so this is used on the Postgres path.\n */\nexport async function ensureTableExists(\n table: string,\n createSql: string,\n options: {\n lockTimeout?: string;\n injectedClient?: DbExec;\n dialectIsPostgres?: boolean;\n } = {},\n): Promise<boolean> {\n return ensureSchemaObject({\n probe: () =>\n pgTableExists(table, options.injectedClient, options.dialectIsPostgres),\n ddl: createSql,\n label: `table ${table}`,\n lockTimeout: options.lockTimeout,\n injectedClient: options.injectedClient,\n dialectIsPostgres: options.dialectIsPostgres,\n });\n}\n\n/**\n * Convenience wrapper: ensure a COLUMN exists (probe via `pgColumnExists`).\n * `addColumnSql` should be the full `ALTER TABLE … ADD COLUMN IF NOT EXISTS …`.\n */\nexport async function ensureColumnExists(\n table: string,\n column: string,\n addColumnSql: string,\n options: { lockTimeout?: string; injectedClient?: DbExec } = {},\n): Promise<boolean> {\n return ensureSchemaObject({\n probe: () => pgColumnExists(table, column, options.injectedClient),\n ddl: addColumnSql,\n label: `column ${table}.${column}`,\n lockTimeout: options.lockTimeout,\n injectedClient: options.injectedClient,\n });\n}\n\n/**\n * Convenience wrapper: ensure an INDEX exists (probe via `pgIndexExists`).\n * `createIndexSql` should be the full `CREATE INDEX IF NOT EXISTS <name> …`.\n */\nexport async function ensureIndexExists(\n indexName: string,\n createIndexSql: string,\n options: {\n lockTimeout?: string;\n injectedClient?: DbExec;\n dialectIsPostgres?: boolean;\n } = {},\n): Promise<boolean> {\n return ensureSchemaObject({\n probe: () =>\n pgIndexExists(\n indexName,\n options.injectedClient,\n options.dialectIsPostgres,\n ),\n ddl: createIndexSql,\n label: `index ${indexName}`,\n lockTimeout: options.lockTimeout,\n injectedClient: options.injectedClient,\n dialectIsPostgres: options.dialectIsPostgres,\n });\n}\n\n/** True when an error looks like a Postgres `lock_timeout` (SQLSTATE 55P03). */\nexport function isLockTimeoutError(err: unknown): boolean {\n const anyErr = err as { code?: unknown; message?: unknown } | null;\n if (anyErr?.code === \"55P03\") return true;\n const msg = String(anyErr?.message ?? anyErr ?? \"\");\n return /lock[_ ]?timeout|canceling statement due to lock timeout/i.test(msg);\n}\n\n/**\n * Run a DDL statement that MUST execute (the schema is actually missing), with\n * a short `lock_timeout` so a contended `ACCESS EXCLUSIVE` lock fails fast\n * instead of hanging the whole process.\n *\n * Postgres path: wrap the DDL in an explicit transaction and set\n * `SET LOCAL lock_timeout` so the timeout is scoped to THIS transaction only\n * and reset automatically on COMMIT/ROLLBACK — it never leaks onto the pooled\n * (session-reused) connection the way a bare `SET lock_timeout` would. If the\n * DbExec has no `transaction` (shouldn't happen for Postgres, but defensively),\n * fall back to a session `SET` + `RESET` in a finally. A lock-timeout error is\n * swallowed: the table/column is virtually always already correct by the time a\n * contended boot retries, and the caller's memoization should still resolve so\n * the path isn't retried in a tight loop. Any non-lock-timeout error rethrows.\n *\n * SQLite path: no lock problem — just run the DDL directly.\n *\n * @returns `true` if the DDL ran to completion, `false` if it was skipped due to\n * a lock-timeout (so the caller can decide whether to log).\n */\nexport async function runGuardedDdl(\n ddl: string,\n options: {\n lockTimeout?: string;\n injectedClient?: DbExec;\n dialectIsPostgres?: boolean;\n } = {},\n): Promise<boolean> {\n const client = options.injectedClient ?? getDbExec();\n if (!(options.dialectIsPostgres ?? isPostgres())) {\n await client.execute(ddl);\n return true;\n }\n\n const lockTimeout = options.lockTimeout ?? \"3s\";\n try {\n if (typeof client.transaction === \"function\") {\n await client.transaction(async (tx) => {\n // SET LOCAL is transaction-scoped: it reverts on COMMIT/ROLLBACK and\n // never persists on the pooled connection.\n await tx.execute(`SET LOCAL lock_timeout = '${lockTimeout}'`);\n await tx.execute(ddl);\n });\n } else {\n // Defensive fallback: no transaction support. Use a session SET and\n // guarantee a RESET so the timeout never leaks onto a reused connection.\n try {\n await client.execute(`SET lock_timeout = '${lockTimeout}'`);\n await client.execute(ddl);\n } finally {\n await client.execute(`RESET lock_timeout`).catch(() => {});\n }\n }\n return true;\n } catch (err) {\n if (isLockTimeoutError(err)) {\n // Contended lock — the schema is virtually always already correct by now.\n // Proceed; the caller's memoization still resolves so we don't loop.\n return false;\n }\n throw err;\n }\n}\n"]}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/deploy/build.ts"],"names":[],"mappings":";AAEA;;;;;;;;;;;;GAYG;AAuCH,OAAO,EAML,KAAK,eAAe,EACpB,KAAK,gBAAgB,EACtB,MAAM,sBAAsB,CAAC;AAC9B,OAAO,EAEL,KAAK,oBAAoB,EAC1B,MAAM,qBAAqB,CAAC;AAI7B,eAAO,MAAM,6BAA6B,UAiBzC,CAAC;AAEF,eAAO,MAAM,mCAAmC,UAiB/C,CAAC;AACF,eAAO,MAAM,8BAA8B,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAyFjE,CAAC;AAEF,eAAO,MAAM,sCAAsC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAQzE,CAAC;AAEF,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,EAAE,CAWvE;AAED,wBAAgB,0CAA0C,CACxD,IAAI,EAAE,MAAM,EACZ,UAAU,EAAE,MAAM,GACjB,IAAI,CAgBN;AA6BD,eAAO,MAAM,2CAA2C,EAAE,MAAM,CAC9D,MAAM,EACN,MAAM,CAmSP,CAAC;AAEF,MAAM,WAAW,0BAA0B;IACzC,qBAAqB,CAAC,EAAE,OAAO,CAAC;CACjC;AAED,UAAU,wBAAwB;IAChC,KAAK,EAAE,6BAA6B,CAAC;IACrC,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,6BAA6B,CAAC,CAAC;IACtD,GAAG,EAAE,MAAM,CAAC;CACb;AAED,UAAU,6BAA6B;IACrC,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;CAChB;AAED,UAAU,6BAA6B;IACrC,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,CAAC,EAAE,MAAM,EAAE,CAAC;IACnB,GAAG,CAAC,EAAE,MAAM,EAAE,CAAC;IACf,SAAS,CAAC,EAAE,OAAO,CAAC;IACpB,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,kBAAkB,CAAC,EAAE,MAAM,CAAC;IAC5B,sBAAsB,CAAC,EAAE,MAAM,CAAC;IAChC,qBAAqB,CAAC,EAAE,MAAM,CAAC;CAChC;AAwBD,wBAAgB,wCAAwC,CACtD,WAAW,EAAE,MAAM,EAAE,GACpB,MAAM,CAaR;AAgBD,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE;IAAE,OAAO,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAA;CAAE,CAAC,CAAC;AAgBvE,wBAAgB,yCAAyC,CACvD,UAAU,EAAE,UAAU,EACtB,SAAS,EAAE,MAAM,EACjB,WAAW,SAAK,GACf,IAAI,CAQN;AAED;;;;;;;;GAQG;AACH,wBAAgB,mBAAmB,CACjC,MAAM,EAAE,eAAe,EAAE,EACzB,WAAW,EAAE,MAAM,EAAE,EACrB,kBAAkB,GAAE,MAAM,EAAO,EACjC,OAAO,GAAE,gBAAgB,EAAO,EAChC,aAAa,GAAE,oBAAoB,GAAG,IAAW,EACjD,mBAAmB,GAAE,MAAM,EAAO,EAClC,gBAAgB,SAAmC,EACnD,OAAO,GAAE,0BAA+B,GACvC,MAAM,
|
|
1
|
+
{"version":3,"file":"build.d.ts","sourceRoot":"","sources":["../../src/deploy/build.ts"],"names":[],"mappings":"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"}
|
package/dist/deploy/build.js
CHANGED
|
@@ -770,6 +770,26 @@ function getSentryClientConfigScript() {
|
|
|
770
770
|
);
|
|
771
771
|
}
|
|
772
772
|
|
|
773
|
+
function getRealtimeClientConfigScript() {
|
|
774
|
+
// MUST stay byte-for-byte consistent with resolveRealtimeClientConfig in
|
|
775
|
+
// server/sentry-config.ts (worker bundles a string copy; it can't import it).
|
|
776
|
+
// Fail closed: require BOTH hosted transport AND an explicit gateway URL — no
|
|
777
|
+
// production default, since this ships into the CDN-cached shell.
|
|
778
|
+
const env = globalThis.process?.env || {};
|
|
779
|
+
if (firstNonEmpty(env.AGENT_NATIVE_REALTIME_TRANSPORT) !== "hosted") {
|
|
780
|
+
return null;
|
|
781
|
+
}
|
|
782
|
+
const gatewayBaseUrl = firstNonEmpty(env.AGENT_NATIVE_REALTIME_GATEWAY_URL);
|
|
783
|
+
if (!gatewayBaseUrl) return null;
|
|
784
|
+
const config = { realtime: { transport: "hosted", gatewayBaseUrl } };
|
|
785
|
+
return (
|
|
786
|
+
'<script data-agent-native-realtime-config>' +
|
|
787
|
+
'window.__AGENT_NATIVE_CONFIG__=Object.assign({},window.__AGENT_NATIVE_CONFIG__,' +
|
|
788
|
+
JSON.stringify(config) +
|
|
789
|
+
");</script>"
|
|
790
|
+
);
|
|
791
|
+
}
|
|
792
|
+
|
|
773
793
|
function injectHeadScript(html, script) {
|
|
774
794
|
if (!script) return html;
|
|
775
795
|
const headCloseIdx = html.indexOf("</head>");
|
|
@@ -907,7 +927,10 @@ function applyImmutableAssetCacheHeaders(response, request) {
|
|
|
907
927
|
}
|
|
908
928
|
|
|
909
929
|
async function rewriteMountedResponse(response, basePath, pathname, request) {
|
|
910
|
-
const
|
|
930
|
+
const clientConfigScript =
|
|
931
|
+
[getSentryClientConfigScript(), getRealtimeClientConfigScript()]
|
|
932
|
+
.filter(Boolean)
|
|
933
|
+
.join("") || null;
|
|
911
934
|
const headers = new Headers(response.headers);
|
|
912
935
|
applyDefaultSsrCacheHeader(headers, response.status, pathname);
|
|
913
936
|
applyDefaultSpeculationRulesHeader(headers, response.status, basePath);
|
|
@@ -934,7 +957,7 @@ async function rewriteMountedResponse(response, basePath, pathname, request) {
|
|
|
934
957
|
prefixMountedHtml(html, basePath),
|
|
935
958
|
defaultSocialImageUrl(request, basePath),
|
|
936
959
|
),
|
|
937
|
-
|
|
960
|
+
clientConfigScript,
|
|
938
961
|
),
|
|
939
962
|
{
|
|
940
963
|
status: response.status,
|