@agent-native/core 0.115.4 → 0.116.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 +28 -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 +1 -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/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 +16 -6
- package/corpus/core/src/connections/catalog.ts +7 -1
- 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/server/collab-plugin.ts +137 -18
- package/corpus/core/src/server/index.ts +2 -0
- 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/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/sidebar/DocumentSidebar.tsx +1 -1
- 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/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 +3 -2
- package/dist/client/use-db-sync.d.ts.map +1 -1
- package/dist/client/use-db-sync.js +17 -6
- package/dist/client/use-db-sync.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/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/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +1 -1
- package/dist/progress/routes.d.ts +1 -1
- package/dist/provider-api/actions/custom-provider-registration.d.ts +11 -11
- 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/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/index.d.ts +1 -1
- package/dist/server/index.d.ts.map +1 -1
- package/dist/server/index.js.map +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 +3 -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/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 +16 -6
- package/src/connections/catalog.ts +7 -1
- 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/server/collab-plugin.ts +137 -18
- package/src/server/index.ts +2 -0
- 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
|
@@ -124,7 +124,7 @@ const USER_KEY_SQL = "NULLIF(user_key, '')";
|
|
|
124
124
|
const RETENTION_ROLLING_DAYS = 7;
|
|
125
125
|
const RETENTION_MIN_COHORT_SIZE = 5;
|
|
126
126
|
const PER_TEMPLATE_RETENTION_MIN_COHORT_SIZE = 20;
|
|
127
|
-
const
|
|
127
|
+
const OBSERVED_ACTIVITY_LOOKBACK_DAYS = 365;
|
|
128
128
|
|
|
129
129
|
function daysAgoSql(days: number): string {
|
|
130
130
|
const unit = days === 1 ? "day" : "days";
|
|
@@ -222,42 +222,160 @@ export function usesFirstPartyDashboardFilters(sql: string): boolean {
|
|
|
222
222
|
|
|
223
223
|
const TOTAL_SIGNUPS_SQL = `SELECT COUNT(*) AS signups FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}`;
|
|
224
224
|
const SIGNUPS_OVER_TIME_SQL = `WITH offsets AS (SELECT (ROW_NUMBER() OVER (ORDER BY ${EVENT_DATE_SQL}) - 1)::int AS n FROM analytics_events LIMIT 800), signup_events AS (SELECT ${EVENT_DATE_SQL} AS date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), bounds AS (SELECT MIN(date::date) AS start_date, MAX(date::date) AS end_date FROM signup_events), dates AS (SELECT to_char(bounds.start_date + offsets.n, 'YYYY-MM-DD') AS date FROM bounds CROSS JOIN offsets WHERE bounds.start_date IS NOT NULL AND bounds.start_date + offsets.n <= bounds.end_date), templates AS (SELECT DISTINCT template FROM signup_events), daily AS (SELECT date, template, COUNT(*) AS count FROM signup_events GROUP BY date, template) SELECT dates.date, templates.template, COALESCE(daily.count, 0) AS count FROM dates CROSS JOIN templates LEFT JOIN daily ON daily.date = dates.date AND daily.template = templates.template ORDER BY dates.date, templates.template`;
|
|
225
|
-
const
|
|
226
|
-
const
|
|
227
|
-
const
|
|
225
|
+
export const LEGACY_RETENTION_OVER_TIME_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, MIN(event_date) AS cohort_date FROM base GROUP BY user_key), anchor_dates AS (SELECT DISTINCT cohort_date AS date FROM first_seen WHERE cohort_date <= ${daysAgoSql(14)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_windows AS (SELECT a.date, f.user_key, f.cohort_date FROM anchor_dates a JOIN first_seen f ON f.cohort_date >= ${rollingWindowStartSql()} AND f.cohort_date <= a.date), cohort_sizes AS (SELECT date, COUNT(DISTINCT user_key) AS users FROM cohort_windows GROUP BY date), periods AS (SELECT '1-7d return' AS period UNION ALL SELECT '7-14d return' AS period), retained AS (SELECT cw.date, '1-7d return' AS period, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.event_date > cw.cohort_date AND b.event_date <= to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY cw.date UNION ALL SELECT cw.date, '7-14d return' AS period, COUNT(DISTINCT cw.user_key) AS retained FROM cohort_windows cw JOIN base b ON b.user_key = cw.user_key AND b.event_date >= to_char(cw.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') AND b.event_date <= to_char(cw.cohort_date::date + INTERVAL '14 days', 'YYYY-MM-DD') GROUP BY cw.date) SELECT cs.date, p.period, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs CROSS JOIN periods p LEFT JOIN retained r ON r.date = cs.date AND r.period = p.period WHERE cs.users >= ${RETENTION_MIN_COHORT_SIZE} ORDER BY cs.date, p.period`;
|
|
226
|
+
export const LEGACY_ONE_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), ranked_first_seen AS (SELECT user_key, template, event_date AS cohort_date, ROW_NUMBER() OVER (PARTITION BY user_key ORDER BY event_date, template) AS rn FROM base), first_seen AS (SELECT user_key, template, cohort_date FROM ranked_first_seen WHERE rn = 1), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= ${daysAgoSql(7)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT template, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY template), retained AS (SELECT c.template, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.event_date > c.cohort_date AND b.event_date <= to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') GROUP BY c.template) SELECT cs.template, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.template = cs.template WHERE cs.users >= ${PER_TEMPLATE_RETENTION_MIN_COHORT_SIZE} ORDER BY rate DESC, cs.users DESC, cs.template`;
|
|
227
|
+
export const LEGACY_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL = `WITH base AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${TEMPLATE_EXPR} AS template, ${EVENT_DATE_SQL} AS event_date, user_id FROM analytics_events WHERE ${SIGNED_IN_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), ranked_first_seen AS (SELECT user_key, template, event_date AS cohort_date, ROW_NUMBER() OVER (PARTITION BY user_key ORDER BY event_date, template) AS rn FROM base), first_seen AS (SELECT user_key, template, cohort_date FROM ranked_first_seen WHERE rn = 1), cohorts AS (SELECT user_key, template, cohort_date FROM first_seen WHERE cohort_date <= ${daysAgoSql(14)} AND ${dashboardTimeRangeFilter("cohort_date")}), cohort_sizes AS (SELECT template, COUNT(DISTINCT user_key) AS users FROM cohorts GROUP BY template), retained AS (SELECT c.template, COUNT(DISTINCT c.user_key) AS retained FROM cohorts c JOIN base b ON b.user_key = c.user_key AND b.event_date >= to_char(c.cohort_date::date + INTERVAL '7 days', 'YYYY-MM-DD') AND b.event_date <= to_char(c.cohort_date::date + INTERVAL '14 days', 'YYYY-MM-DD') GROUP BY c.template) SELECT cs.template, COALESCE(r.retained, 0) AS retained_users, cs.users AS cohort_users, COALESCE(r.retained::float / NULLIF(cs.users, 0), 0) AS rate FROM cohort_sizes cs LEFT JOIN retained r ON r.template = cs.template WHERE cs.users >= ${PER_TEMPLATE_RETENTION_MIN_COHORT_SIZE} ORDER BY rate DESC, cs.users DESC, cs.template`;
|
|
228
|
+
export const LEGACY_V0_RETENTION_OVER_TIME_SQL =
|
|
229
|
+
LEGACY_RETENTION_OVER_TIME_SQL.replace(
|
|
230
|
+
"cohort_date <= to_char(CURRENT_DATE, 'YYYY-MM-DD') AND ",
|
|
231
|
+
"",
|
|
232
|
+
)
|
|
233
|
+
.replace("AND (('{{timeRange}}", "AND ('{{timeRange}}")
|
|
234
|
+
.replace(
|
|
235
|
+
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))))",
|
|
236
|
+
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))",
|
|
237
|
+
);
|
|
238
|
+
export const LEGACY_V0_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
239
|
+
LEGACY_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL.replace(
|
|
240
|
+
"cohort_date <= to_char(CURRENT_DATE, 'YYYY-MM-DD') AND ",
|
|
241
|
+
"",
|
|
242
|
+
)
|
|
243
|
+
.replace("AND (('{{timeRange}}", "AND ('{{timeRange}}")
|
|
244
|
+
.replace(
|
|
245
|
+
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))))",
|
|
246
|
+
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))",
|
|
247
|
+
);
|
|
248
|
+
export const LEGACY_V0_ONE_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
249
|
+
LEGACY_ONE_DAY_RETENTION_BY_TEMPLATE_SQL.replace(
|
|
250
|
+
"cohort_date <= to_char(CURRENT_DATE, 'YYYY-MM-DD') AND ",
|
|
251
|
+
"",
|
|
252
|
+
)
|
|
253
|
+
.replace("AND (('{{timeRange}}", "AND ('{{timeRange}}")
|
|
254
|
+
.replace(
|
|
255
|
+
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))))",
|
|
256
|
+
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))",
|
|
257
|
+
);
|
|
228
258
|
const SIGNUPS_BY_TEMPLATE_SQL = `SELECT ${TEMPLATE_EXPR} AS template, COUNT(*) AS count FROM analytics_events WHERE event_name = 'signup' AND ${DASHBOARD_TIME_RANGE_FILTER} AND ${DASHBOARD_EMAIL_FILTER} GROUP BY ${TEMPLATE_EXPR} ORDER BY count DESC`;
|
|
229
259
|
export const LEGACY_RECURRING_USERS_BY_TEMPLATE_SQL = `WITH all_users AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL}, user_id, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, MIN(event_date) AS first_date FROM all_users GROUP BY user_key) SELECT a.event_date AS date, a.template AS template, COUNT(DISTINCT a.user_key) AS users FROM all_users a JOIN first_seen f ON f.user_key = a.user_key WHERE a.event_date <> f.first_date AND a.template <> 'unknown' AND ${dashboardTimeRangeFilter("a.event_date")} GROUP BY 1, 2 ORDER BY date, template`;
|
|
230
260
|
export const LEGACY_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL = `WITH all_users AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL}, user_id, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER}), first_seen AS (SELECT user_key, MIN(event_date) AS first_date FROM all_users GROUP BY user_key) SELECT to_char(date_trunc('week', a.event_date::date), 'YYYY-MM-DD') AS date, a.template AS template, COUNT(DISTINCT a.user_key) AS users FROM all_users a JOIN first_seen f ON f.user_key = a.user_key WHERE a.event_date <> f.first_date AND a.template <> 'unknown' AND ${dashboardTimeRangeFilter("a.event_date")} GROUP BY 1, 2 ORDER BY date, template`;
|
|
231
|
-
const
|
|
232
|
-
const
|
|
261
|
+
const OBSERVED_ACTIVITY_LOOKBACK_FILTER = `${EVENT_DATE_SQL} >= ${daysAgoSql(OBSERVED_ACTIVITY_LOOKBACK_DAYS)}`;
|
|
262
|
+
export const INTERMEDIATE_RECURRING_USERS_BY_TEMPLATE_SQL =
|
|
233
263
|
LEGACY_RECURRING_USERS_BY_TEMPLATE_SQL.replace(
|
|
234
264
|
`${DASHBOARD_EMAIL_FILTER}), first_seen`,
|
|
235
|
-
`${DASHBOARD_EMAIL_FILTER} AND ${
|
|
265
|
+
`${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER}), first_seen`,
|
|
236
266
|
);
|
|
237
|
-
const
|
|
267
|
+
export const INTERMEDIATE_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL =
|
|
238
268
|
LEGACY_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL.replace(
|
|
239
269
|
`${DASHBOARD_EMAIL_FILTER}), first_seen`,
|
|
240
|
-
`${DASHBOARD_EMAIL_FILTER} AND ${
|
|
270
|
+
`${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER}), first_seen`,
|
|
241
271
|
);
|
|
272
|
+
export const DEPLOYED_RECURRING_USERS_BY_TEMPLATE_SQL =
|
|
273
|
+
LEGACY_RECURRING_USERS_BY_TEMPLATE_SQL.replace(
|
|
274
|
+
"a.event_date <= to_char(CURRENT_DATE, 'YYYY-MM-DD') AND ",
|
|
275
|
+
"",
|
|
276
|
+
)
|
|
277
|
+
.replace("AND (('{{timeRange}}", "AND ('{{timeRange}}")
|
|
278
|
+
.replace(
|
|
279
|
+
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD'))))",
|
|
280
|
+
"CURRENT_DATE - INTERVAL '365 days', 'YYYY-MM-DD')))",
|
|
281
|
+
);
|
|
282
|
+
const RETENTION_OVER_TIME_SQL = LEGACY_RETENTION_OVER_TIME_SQL.replace(
|
|
283
|
+
`${DASHBOARD_EMAIL_FILTER}), first_seen`,
|
|
284
|
+
`${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER}), first_seen`,
|
|
285
|
+
);
|
|
286
|
+
const ONE_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
287
|
+
LEGACY_ONE_DAY_RETENTION_BY_TEMPLATE_SQL.replace(
|
|
288
|
+
`${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), ranked_first_seen`,
|
|
289
|
+
`${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER}), ranked_first_seen`,
|
|
290
|
+
);
|
|
291
|
+
const SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL =
|
|
292
|
+
LEGACY_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL.replace(
|
|
293
|
+
`${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER}), ranked_first_seen`,
|
|
294
|
+
`${KNOWN_PRODUCT_ACTIVITY_TEMPLATE_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER}), ranked_first_seen`,
|
|
295
|
+
);
|
|
296
|
+
const RECURRING_USERS_BY_TEMPLATE_SQL = `WITH first_seen AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, MIN(${EVENT_DATE_SQL}) AS first_date FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} GROUP BY 1), activity AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} AND ${DASHBOARD_TIME_RANGE_FILTER}) SELECT a.event_date AS date, a.template AS template, COUNT(DISTINCT a.user_key) AS users FROM activity a JOIN first_seen f ON f.user_key = a.user_key WHERE a.event_date <> f.first_date AND a.template <> 'unknown' GROUP BY 1, 2 ORDER BY date, template`;
|
|
297
|
+
const RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL = `WITH first_seen AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, MIN(${EVENT_DATE_SQL}) AS first_date FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} GROUP BY 1), activity AS (SELECT ${SIGNED_IN_ACTIVITY_KEY_SQL} AS user_key, ${EVENT_DATE_SQL} AS event_date, ${TEMPLATE_EXPR} AS template FROM analytics_events WHERE ${SIGNED_IN_PRODUCT_ACTIVITY_FILTER} AND ${DASHBOARD_EMAIL_FILTER} AND ${OBSERVED_ACTIVITY_LOOKBACK_FILTER} AND ${DASHBOARD_TIME_RANGE_FILTER}) SELECT to_char(date_trunc('week', a.event_date::date), 'YYYY-MM-DD') AS date, a.template AS template, COUNT(DISTINCT a.user_key) AS users FROM activity a JOIN first_seen f ON f.user_key = a.user_key WHERE a.event_date <> f.first_date AND a.template <> 'unknown' GROUP BY 1, 2 ORDER BY date, template`;
|
|
242
298
|
const LEGACY_RECURRING_USERS_DESCRIPTION =
|
|
243
299
|
"Daily signed-in visitors who are NOT on their all-time first active day (Recurring only), stacked by inferred template/app used that day. Docs traffic and unknown template are excluded.";
|
|
244
300
|
const LEGACY_RECURRING_USERS_WEEKLY_DESCRIPTION =
|
|
245
301
|
"Weekly distinct signed-in visitors who are NOT on their all-time first active day (Recurring only), stacked by inferred template/app used that week. Weeks start Monday; docs traffic and unknown template are excluded.";
|
|
302
|
+
const LEGACY_RETENTION_OVER_TIME_DESCRIPTION =
|
|
303
|
+
"Trailing 7-day first-seen signed-in app session cohorts, keyed by browser identity. Counts returns within 1-7d and 7-14d windows. Docs traffic is excluded; windows under 5 identities are hidden.";
|
|
304
|
+
const LEGACY_ONE_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION =
|
|
305
|
+
"Selected-range signed-in cohorts by the browser identity's first non-docs app/template. Counts returns to any non-docs app within 1-7 days. Templates with fewer than 20 mature cohort identities are hidden.";
|
|
306
|
+
const LEGACY_SEVEN_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION =
|
|
307
|
+
"Selected-range signed-in cohorts by the browser identity's first non-docs app/template. Counts returns to any non-docs app within 7-14 days. Templates with fewer than 20 mature cohort identities are hidden.";
|
|
246
308
|
const RECURRING_USERS_DESCRIPTION =
|
|
247
309
|
"Daily signed-in visitors who are not on their first active day observed in the previous 365 days, stacked by inferred template/app used that day. Docs traffic and unknown template are excluded.";
|
|
248
310
|
const RECURRING_USERS_WEEKLY_DESCRIPTION =
|
|
249
311
|
"Weekly distinct signed-in visitors who are not on their first active day observed in the previous 365 days, stacked by inferred template/app used that week. Weeks start Monday; docs traffic and unknown template are excluded.";
|
|
312
|
+
const RETENTION_OVER_TIME_DESCRIPTION =
|
|
313
|
+
"Trailing 7-day cohorts whose first signed-in app session was observed in the previous 365 days, keyed by browser identity. Counts returns within 1-7d and 7-14d windows. Docs traffic is excluded; windows under 5 identities are hidden.";
|
|
314
|
+
const ONE_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION =
|
|
315
|
+
"Selected-range signed-in cohorts by the browser identity's first non-docs app/template observed in the previous 365 days. Counts returns to any non-docs app within 1-7 days. Templates with fewer than 20 mature cohort identities are hidden.";
|
|
316
|
+
const SEVEN_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION =
|
|
317
|
+
"Selected-range signed-in cohorts by the browser identity's first non-docs app/template observed in the previous 365 days. Counts returns to any non-docs app within 7-14 days. Templates with fewer than 20 mature cohort identities are hidden.";
|
|
250
318
|
|
|
251
|
-
export
|
|
319
|
+
export type ExactFirstPartyPanelReplacement = {
|
|
320
|
+
id: string;
|
|
321
|
+
legacySql: readonly string[];
|
|
322
|
+
sql: string;
|
|
323
|
+
legacyDescription?: string;
|
|
324
|
+
description?: string;
|
|
325
|
+
};
|
|
326
|
+
type FirstPartyPanelReplacement = Omit<ExactFirstPartyPanelReplacement, "id">;
|
|
327
|
+
|
|
328
|
+
export function repairFirstPartyObservedRetentionPanels(
|
|
252
329
|
config: Record<string, unknown>,
|
|
330
|
+
additionalReplacements: readonly ExactFirstPartyPanelReplacement[] = [],
|
|
253
331
|
): { config: Record<string, unknown>; changed: boolean } {
|
|
254
332
|
if (!Array.isArray(config.panels)) return { config, changed: false };
|
|
255
333
|
|
|
256
|
-
const replacements = new Map([
|
|
334
|
+
const replacements = new Map<string, FirstPartyPanelReplacement>([
|
|
335
|
+
[
|
|
336
|
+
"retention-over-time",
|
|
337
|
+
{
|
|
338
|
+
legacySql: [
|
|
339
|
+
LEGACY_RETENTION_OVER_TIME_SQL,
|
|
340
|
+
LEGACY_V0_RETENTION_OVER_TIME_SQL,
|
|
341
|
+
],
|
|
342
|
+
sql: RETENTION_OVER_TIME_SQL,
|
|
343
|
+
legacyDescription: LEGACY_RETENTION_OVER_TIME_DESCRIPTION,
|
|
344
|
+
description: RETENTION_OVER_TIME_DESCRIPTION,
|
|
345
|
+
},
|
|
346
|
+
],
|
|
347
|
+
[
|
|
348
|
+
"one-day-retention-by-template",
|
|
349
|
+
{
|
|
350
|
+
legacySql: [
|
|
351
|
+
LEGACY_ONE_DAY_RETENTION_BY_TEMPLATE_SQL,
|
|
352
|
+
LEGACY_V0_ONE_DAY_RETENTION_BY_TEMPLATE_SQL,
|
|
353
|
+
],
|
|
354
|
+
sql: ONE_DAY_RETENTION_BY_TEMPLATE_SQL,
|
|
355
|
+
legacyDescription: LEGACY_ONE_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION,
|
|
356
|
+
description: ONE_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION,
|
|
357
|
+
},
|
|
358
|
+
],
|
|
359
|
+
[
|
|
360
|
+
"seven-day-retention-by-template",
|
|
361
|
+
{
|
|
362
|
+
legacySql: [
|
|
363
|
+
LEGACY_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL,
|
|
364
|
+
LEGACY_V0_SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL,
|
|
365
|
+
],
|
|
366
|
+
sql: SEVEN_DAY_RETENTION_BY_TEMPLATE_SQL,
|
|
367
|
+
legacyDescription: LEGACY_SEVEN_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION,
|
|
368
|
+
description: SEVEN_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION,
|
|
369
|
+
},
|
|
370
|
+
],
|
|
257
371
|
[
|
|
258
372
|
"recurring-users-by-template",
|
|
259
373
|
{
|
|
260
|
-
legacySql:
|
|
374
|
+
legacySql: [
|
|
375
|
+
LEGACY_RECURRING_USERS_BY_TEMPLATE_SQL,
|
|
376
|
+
INTERMEDIATE_RECURRING_USERS_BY_TEMPLATE_SQL,
|
|
377
|
+
DEPLOYED_RECURRING_USERS_BY_TEMPLATE_SQL,
|
|
378
|
+
],
|
|
261
379
|
sql: RECURRING_USERS_BY_TEMPLATE_SQL,
|
|
262
380
|
legacyDescription: LEGACY_RECURRING_USERS_DESCRIPTION,
|
|
263
381
|
description: RECURRING_USERS_DESCRIPTION,
|
|
@@ -266,12 +384,18 @@ export function repairFirstPartyRecurringUserPanels(
|
|
|
266
384
|
[
|
|
267
385
|
"recurring-users-by-template-bar",
|
|
268
386
|
{
|
|
269
|
-
legacySql:
|
|
387
|
+
legacySql: [
|
|
388
|
+
LEGACY_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL,
|
|
389
|
+
INTERMEDIATE_RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL,
|
|
390
|
+
],
|
|
270
391
|
sql: RECURRING_USERS_BY_TEMPLATE_WEEKLY_SQL,
|
|
271
392
|
legacyDescription: LEGACY_RECURRING_USERS_WEEKLY_DESCRIPTION,
|
|
272
393
|
description: RECURRING_USERS_WEEKLY_DESCRIPTION,
|
|
273
394
|
},
|
|
274
395
|
],
|
|
396
|
+
...additionalReplacements.map(
|
|
397
|
+
(replacement) => [replacement.id, replacement] as const,
|
|
398
|
+
),
|
|
275
399
|
]);
|
|
276
400
|
let changed = false;
|
|
277
401
|
const panels = config.panels.map((rawPanel) => {
|
|
@@ -279,7 +403,13 @@ export function repairFirstPartyRecurringUserPanels(
|
|
|
279
403
|
const panel = rawPanel as Record<string, unknown>;
|
|
280
404
|
const replacement =
|
|
281
405
|
typeof panel.id === "string" ? replacements.get(panel.id) : undefined;
|
|
282
|
-
if (
|
|
406
|
+
if (
|
|
407
|
+
!replacement ||
|
|
408
|
+
typeof panel.sql !== "string" ||
|
|
409
|
+
!replacement.legacySql.includes(panel.sql)
|
|
410
|
+
) {
|
|
411
|
+
return rawPanel;
|
|
412
|
+
}
|
|
283
413
|
|
|
284
414
|
changed = true;
|
|
285
415
|
const panelConfig =
|
|
@@ -293,7 +423,9 @@ export function repairFirstPartyRecurringUserPanels(
|
|
|
293
423
|
? {
|
|
294
424
|
config: {
|
|
295
425
|
...panelConfig,
|
|
296
|
-
...(
|
|
426
|
+
...(replacement.legacyDescription !== undefined &&
|
|
427
|
+
replacement.description !== undefined &&
|
|
428
|
+
panelConfig.description === replacement.legacyDescription
|
|
297
429
|
? { description: replacement.description }
|
|
298
430
|
: {}),
|
|
299
431
|
},
|
|
@@ -806,8 +938,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
806
938
|
valueKey: "rate",
|
|
807
939
|
},
|
|
808
940
|
colors: ["#10b981", "#8b5cf6"],
|
|
809
|
-
description:
|
|
810
|
-
"Trailing 7-day first-seen signed-in app session cohorts, keyed by browser identity. Counts returns within 1-7d and 7-14d windows. Docs traffic is excluded; windows under 5 identities are hidden.",
|
|
941
|
+
description: RETENTION_OVER_TIME_DESCRIPTION,
|
|
811
942
|
},
|
|
812
943
|
},
|
|
813
944
|
{
|
|
@@ -828,8 +959,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
828
959
|
{ key: "retained_users", label: "Returned", format: "number" },
|
|
829
960
|
{ key: "cohort_users", label: "Cohort", format: "number" },
|
|
830
961
|
],
|
|
831
|
-
description:
|
|
832
|
-
"Selected-range signed-in cohorts by the browser identity's first non-docs app/template. Counts returns to any non-docs app within 1-7 days. Templates with fewer than 20 mature cohort identities are hidden.",
|
|
962
|
+
description: ONE_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION,
|
|
833
963
|
},
|
|
834
964
|
},
|
|
835
965
|
{
|
|
@@ -850,8 +980,7 @@ const ENTRIES: FirstPartyMetric[] = [
|
|
|
850
980
|
{ key: "retained_users", label: "Returned", format: "number" },
|
|
851
981
|
{ key: "cohort_users", label: "Cohort", format: "number" },
|
|
852
982
|
],
|
|
853
|
-
description:
|
|
854
|
-
"Selected-range signed-in cohorts by the browser identity's first non-docs app/template. Counts returns to any non-docs app within 7-14 days. Templates with fewer than 20 mature cohort identities are hidden.",
|
|
983
|
+
description: SEVEN_DAY_RETENTION_BY_TEMPLATE_DESCRIPTION,
|
|
855
984
|
},
|
|
856
985
|
},
|
|
857
986
|
{
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
import { executeProviderApiRequest } from "./provider-api";
|
|
2
|
+
|
|
3
|
+
export interface GoogleSheetExportInput {
|
|
4
|
+
dashboardId: string;
|
|
5
|
+
dashboardTitle: string;
|
|
6
|
+
panelId: string;
|
|
7
|
+
panelTitle: string;
|
|
8
|
+
source: string;
|
|
9
|
+
rows: Record<string, unknown>[];
|
|
10
|
+
schema: { name: string; type: string }[];
|
|
11
|
+
truncated?: boolean;
|
|
12
|
+
bytesProcessed?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface GoogleSheetExportResult {
|
|
16
|
+
spreadsheetUrl: string;
|
|
17
|
+
spreadsheetId: string;
|
|
18
|
+
rowCount: number;
|
|
19
|
+
metadata: {
|
|
20
|
+
dashboardId: string;
|
|
21
|
+
dashboardTitle: string;
|
|
22
|
+
panelId: string;
|
|
23
|
+
panelTitle: string;
|
|
24
|
+
source: string;
|
|
25
|
+
chartType: "table";
|
|
26
|
+
sheetTitle: string;
|
|
27
|
+
columns: string[];
|
|
28
|
+
columnCount: number;
|
|
29
|
+
truncated: boolean;
|
|
30
|
+
bytesProcessed: number | null;
|
|
31
|
+
provider: "google_drive";
|
|
32
|
+
updatedCells: number | null;
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ProviderResponse = {
|
|
37
|
+
ok?: boolean;
|
|
38
|
+
status?: number;
|
|
39
|
+
statusText?: string;
|
|
40
|
+
json?: unknown;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
function providerResponse(result: unknown, label: string): ProviderResponse {
|
|
44
|
+
const response = (result as { response?: unknown } | null)?.response;
|
|
45
|
+
if (!response || typeof response !== "object") {
|
|
46
|
+
throw new Error(`${label} returned an unexpected response.`);
|
|
47
|
+
}
|
|
48
|
+
const typed = response as ProviderResponse;
|
|
49
|
+
if (!typed.ok) {
|
|
50
|
+
const status = typed.status ? ` HTTP ${typed.status}` : "";
|
|
51
|
+
const statusText = typed.statusText ? ` ${typed.statusText}` : "";
|
|
52
|
+
throw new Error(`${label} failed${status}${statusText}.`);
|
|
53
|
+
}
|
|
54
|
+
return typed;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function asRecord(value: unknown): Record<string, unknown> {
|
|
58
|
+
return value && typeof value === "object" && !Array.isArray(value)
|
|
59
|
+
? (value as Record<string, unknown>)
|
|
60
|
+
: {};
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function sheetCell(value: unknown): string | number | boolean {
|
|
64
|
+
if (value === null || value === undefined) return "";
|
|
65
|
+
if (typeof value === "string" || typeof value === "boolean") return value;
|
|
66
|
+
if (typeof value === "number") return Number.isFinite(value) ? value : "";
|
|
67
|
+
try {
|
|
68
|
+
return JSON.stringify(value);
|
|
69
|
+
} catch {
|
|
70
|
+
return String(value);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function quoteSheetTitle(title: string): string {
|
|
75
|
+
return `'${title.replace(/'/g, "''")}'`;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
function normalizeSheetTitle(
|
|
79
|
+
dashboardTitle: string,
|
|
80
|
+
panelTitle: string,
|
|
81
|
+
): string {
|
|
82
|
+
const title = `${dashboardTitle} - ${panelTitle}`
|
|
83
|
+
.replace(/[\u0000-\u001f\u007f]/g, " ")
|
|
84
|
+
.trim();
|
|
85
|
+
return (title || "Analytics export").slice(0, 100);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
function sheetColumnCount(
|
|
89
|
+
schema: GoogleSheetExportInput["schema"],
|
|
90
|
+
rows: GoogleSheetExportInput["rows"],
|
|
91
|
+
): string[] {
|
|
92
|
+
const names = schema
|
|
93
|
+
.map((column) => column.name.trim())
|
|
94
|
+
.filter((name, index, columns) => name && columns.indexOf(name) === index);
|
|
95
|
+
if (names.length > 0) return names;
|
|
96
|
+
return Object.keys(rows[0] ?? {});
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
export async function exportDashboardPanelToGoogleSheet(
|
|
100
|
+
input: GoogleSheetExportInput,
|
|
101
|
+
): Promise<GoogleSheetExportResult> {
|
|
102
|
+
const columns = sheetColumnCount(input.schema, input.rows);
|
|
103
|
+
const sheetTitle = normalizeSheetTitle(
|
|
104
|
+
input.dashboardTitle,
|
|
105
|
+
input.panelTitle,
|
|
106
|
+
);
|
|
107
|
+
const createResult = await executeProviderApiRequest({
|
|
108
|
+
provider: "google_drive",
|
|
109
|
+
method: "POST",
|
|
110
|
+
path: "https://sheets.googleapis.com/v4/spreadsheets",
|
|
111
|
+
query: {
|
|
112
|
+
fields: "spreadsheetId,spreadsheetUrl,properties.title,sheets.properties",
|
|
113
|
+
},
|
|
114
|
+
body: { properties: { title: sheetTitle } },
|
|
115
|
+
});
|
|
116
|
+
const createResponse = providerResponse(
|
|
117
|
+
createResult,
|
|
118
|
+
"Google Sheet creation",
|
|
119
|
+
);
|
|
120
|
+
const created = asRecord(createResponse.json);
|
|
121
|
+
const spreadsheetId =
|
|
122
|
+
typeof created.spreadsheetId === "string" ? created.spreadsheetId : "";
|
|
123
|
+
if (!spreadsheetId) {
|
|
124
|
+
throw new Error("Google Sheet creation did not return a spreadsheet id.");
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const sheets = Array.isArray(created.sheets) ? created.sheets : [];
|
|
128
|
+
const firstSheet = asRecord(sheets[0]);
|
|
129
|
+
const firstProperties = asRecord(firstSheet.properties);
|
|
130
|
+
const actualSheetTitle =
|
|
131
|
+
typeof firstProperties.title === "string" && firstProperties.title
|
|
132
|
+
? firstProperties.title
|
|
133
|
+
: "Sheet1";
|
|
134
|
+
let updatedCells: number | null = null;
|
|
135
|
+
|
|
136
|
+
if (columns.length > 0) {
|
|
137
|
+
const values = [
|
|
138
|
+
columns,
|
|
139
|
+
...input.rows.map((row) =>
|
|
140
|
+
columns.map((column) => sheetCell(row[column])),
|
|
141
|
+
),
|
|
142
|
+
];
|
|
143
|
+
const updateResult = await executeProviderApiRequest({
|
|
144
|
+
provider: "google_drive",
|
|
145
|
+
method: "PUT",
|
|
146
|
+
path: `https://sheets.googleapis.com/v4/spreadsheets/${encodeURIComponent(spreadsheetId)}/values/${encodeURIComponent(`${quoteSheetTitle(actualSheetTitle)}!A1`)}`,
|
|
147
|
+
query: { valueInputOption: "RAW" },
|
|
148
|
+
body: {
|
|
149
|
+
range: `${quoteSheetTitle(actualSheetTitle)}!A1`,
|
|
150
|
+
majorDimension: "ROWS",
|
|
151
|
+
values,
|
|
152
|
+
},
|
|
153
|
+
});
|
|
154
|
+
const updateResponse = providerResponse(
|
|
155
|
+
updateResult,
|
|
156
|
+
"Google Sheet values update",
|
|
157
|
+
);
|
|
158
|
+
const updated = asRecord(updateResponse.json);
|
|
159
|
+
updatedCells =
|
|
160
|
+
typeof updated.updatedCells === "number" ? updated.updatedCells : null;
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
const spreadsheetUrl =
|
|
164
|
+
typeof created.spreadsheetUrl === "string" && created.spreadsheetUrl
|
|
165
|
+
? created.spreadsheetUrl
|
|
166
|
+
: `https://docs.google.com/spreadsheets/d/${encodeURIComponent(spreadsheetId)}/edit`;
|
|
167
|
+
|
|
168
|
+
return {
|
|
169
|
+
spreadsheetUrl,
|
|
170
|
+
spreadsheetId,
|
|
171
|
+
rowCount: input.rows.length,
|
|
172
|
+
metadata: {
|
|
173
|
+
dashboardId: input.dashboardId,
|
|
174
|
+
dashboardTitle: input.dashboardTitle,
|
|
175
|
+
panelId: input.panelId,
|
|
176
|
+
panelTitle: input.panelTitle,
|
|
177
|
+
source: input.source,
|
|
178
|
+
chartType: "table",
|
|
179
|
+
sheetTitle: actualSheetTitle,
|
|
180
|
+
columns,
|
|
181
|
+
columnCount: columns.length,
|
|
182
|
+
truncated: input.truncated === true,
|
|
183
|
+
bytesProcessed: input.bytesProcessed ?? null,
|
|
184
|
+
provider: "google_drive",
|
|
185
|
+
updatedCells,
|
|
186
|
+
},
|
|
187
|
+
};
|
|
188
|
+
}
|
|
@@ -11,8 +11,13 @@ import {
|
|
|
11
11
|
import { requireRequestCredentialContext } from "./credentials-context";
|
|
12
12
|
import { resolveAnalyticsProviderCredential } from "./provider-credentials";
|
|
13
13
|
|
|
14
|
-
export const ANALYTICS_PROVIDER_API_IDS =
|
|
15
|
-
|
|
14
|
+
export const ANALYTICS_PROVIDER_API_IDS = Array.from(
|
|
15
|
+
new Set([
|
|
16
|
+
...listProviderApiIdsForTemplateUse("analytics"),
|
|
17
|
+
// Google Sheets creation and writes use the existing Google Drive OAuth
|
|
18
|
+
// connection, whose consent is limited to drive.file.
|
|
19
|
+
"google_drive" as ProviderApiId,
|
|
20
|
+
]),
|
|
16
21
|
) as [ProviderApiId, ...ProviderApiId[]];
|
|
17
22
|
export type AnalyticsProviderApiId = ProviderApiId;
|
|
18
23
|
export type { ProviderApiMethod, ProviderApiRequestArgs };
|
|
@@ -10,7 +10,10 @@ export default createCollabPlugin({
|
|
|
10
10
|
contentColumn: "config",
|
|
11
11
|
idColumn: "id",
|
|
12
12
|
autoSeed: true,
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
13
|
+
access: {
|
|
14
|
+
mode: "resource",
|
|
15
|
+
resourceType: "dashboard",
|
|
16
|
+
resolveResourceId: (docId) =>
|
|
17
|
+
docId.startsWith("dash-") ? docId.slice("dash-".length) : docId,
|
|
18
|
+
},
|
|
16
19
|
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export const MAX_CONCURRENT_SQL_QUERIES = 4;
|
|
@@ -85,7 +85,10 @@ in the moment of agent use.
|
|
|
85
85
|
approval, generative UI, progress, and screen-state exposure.
|
|
86
86
|
- **Chat history kit**: presentational chat lists and recent-chat rails belong
|
|
87
87
|
in Toolkit; Core keeps thread persistence, agent execution, transport, and
|
|
88
|
-
page-to-sidebar handoff.
|
|
88
|
+
page-to-sidebar handoff. Use Toolkit's `ChatHistoryRail` for the standard
|
|
89
|
+
five-item sidebar preview and a footer row with New chat followed by an
|
|
90
|
+
ellipsis disclosure up to fifteen. Apps inject routing, labels, and domain
|
|
91
|
+
actions.
|
|
89
92
|
- **Agent page kit**: the full-page `/agent` surface (`AgentTabsPage` from
|
|
90
93
|
`@agent-native/core/client`) with Context, Files, Connections, Jobs, and
|
|
91
94
|
Access tabs plus a Personal/Organization scope toggle. The canonical home
|
|
@@ -16,7 +16,7 @@ metadata:
|
|
|
16
16
|
|
|
17
17
|
Collaborative editing uses Yjs CRDT via TipTap. The agent and human users are
|
|
18
18
|
equal participants — both edit the same Y.Doc and changes merge cleanly without
|
|
19
|
-
conflicts. Always
|
|
19
|
+
conflicts. Always choose an explicit `access` mode on `createCollabPlugin`.
|
|
20
20
|
|
|
21
21
|
## How It Works
|
|
22
22
|
|
|
@@ -104,7 +104,16 @@ in **different** regions merge fine through the CRDT.
|
|
|
104
104
|
|
|
105
105
|
## Security
|
|
106
106
|
|
|
107
|
-
### Always
|
|
107
|
+
### Always choose an explicit access mode
|
|
108
|
+
|
|
109
|
+
Inspect the schema and sharing registration before configuring collaboration:
|
|
110
|
+
|
|
111
|
+
- For owned or shareable records, use `mode: "resource"` and the resource type
|
|
112
|
+
registered through `registerShareableResource`.
|
|
113
|
+
- For records deliberately visible to every authenticated user on the
|
|
114
|
+
deployment, use `mode: "all-authenticated"`.
|
|
115
|
+
- Never invent a `resourceType` merely to silence a warning. Resource mode is
|
|
116
|
+
correct only when the matching ownership and sharing model exists.
|
|
108
117
|
|
|
109
118
|
```ts
|
|
110
119
|
// server/plugins/collab.ts
|
|
@@ -114,14 +123,23 @@ export default createCollabPlugin({
|
|
|
114
123
|
table: "documents",
|
|
115
124
|
contentColumn: "content",
|
|
116
125
|
idColumn: "id",
|
|
117
|
-
resourceType: "document",
|
|
126
|
+
access: { mode: "resource", resourceType: "document" },
|
|
118
127
|
});
|
|
119
128
|
```
|
|
120
129
|
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
130
|
+
For intentionally deployment-wide authenticated collaboration:
|
|
131
|
+
|
|
132
|
+
```ts
|
|
133
|
+
createCollabPlugin({
|
|
134
|
+
table: "todos",
|
|
135
|
+
contentColumn: "content",
|
|
136
|
+
access: { mode: "all-authenticated" },
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
Omitting `access` is supported only for legacy compatibility and is deprecated.
|
|
141
|
+
It behaves like `all-authenticated`, logs a warning, and is flagged by Doctor.
|
|
142
|
+
Choose the mode explicitly so access intent is reviewable.
|
|
125
143
|
|
|
126
144
|
Non-owner sharees who have explicit access fall back to state-vector catch-up
|
|
127
145
|
(safe, slightly higher latency). Awareness routes require the same viewer
|
|
@@ -133,7 +151,10 @@ Write routes reject payloads exceeding `maxPayloadBytes` (default 2 MB) with
|
|
|
133
151
|
HTTP 413. Override:
|
|
134
152
|
|
|
135
153
|
```ts
|
|
136
|
-
createCollabPlugin({
|
|
154
|
+
createCollabPlugin({
|
|
155
|
+
access: { mode: "resource", resourceType: "document" },
|
|
156
|
+
maxPayloadBytes: 512 * 1024,
|
|
157
|
+
});
|
|
137
158
|
```
|
|
138
159
|
|
|
139
160
|
## Enabling Collaboration
|
|
@@ -144,7 +165,7 @@ createCollabPlugin({ resourceType: "document", maxPayloadBytes: 512 * 1024 });
|
|
|
144
165
|
pnpm add @tiptap/extension-collaboration @tiptap/extension-collaboration-caret @tiptap/y-tiptap @tiptap/core
|
|
145
166
|
```
|
|
146
167
|
|
|
147
|
-
### 2. Add collab server plugin (with
|
|
168
|
+
### 2. Add collab server plugin (with explicit access)
|
|
148
169
|
|
|
149
170
|
```ts
|
|
150
171
|
// server/plugins/collab.ts
|
|
@@ -154,7 +175,7 @@ export default createCollabPlugin({
|
|
|
154
175
|
table: "documents",
|
|
155
176
|
contentColumn: "content",
|
|
156
177
|
idColumn: "id",
|
|
157
|
-
resourceType: "document",
|
|
178
|
+
access: { mode: "resource", resourceType: "document" },
|
|
158
179
|
});
|
|
159
180
|
```
|
|
160
181
|
|
|
@@ -343,9 +364,10 @@ the whole history — external/agent edits must not wipe the user's undo stack.
|
|
|
343
364
|
|
|
344
365
|
## Common Pitfalls
|
|
345
366
|
|
|
346
|
-
- **Missing `
|
|
347
|
-
|
|
348
|
-
|
|
367
|
+
- **Missing `access`** — Legacy omission behaves like `all-authenticated`, logs
|
|
368
|
+
a startup warning, and is flagged by Doctor. Choose `resource` for records
|
|
369
|
+
backed by ownership/sharing, or explicit `all-authenticated` for intentionally
|
|
370
|
+
deployment-wide records. Never invent a resource type to silence the warning.
|
|
349
371
|
- **Don't pass `content` as a TipTap prop** when Collaboration is enabled —
|
|
350
372
|
Yjs owns the content. Seed via `editor.commands.setContent()` only when the
|
|
351
373
|
Y.XmlFragment is empty.
|
|
@@ -72,7 +72,7 @@ The agent modifies data in SQL, but the UI runs in the browser. SSE bridges same
|
|
|
72
72
|
});
|
|
73
73
|
```
|
|
74
74
|
|
|
75
|
-
`useDbSync` and every `subscribeSyncEvents` subscriber share one `EventSource` and one fallback poll loop per tab — this is how collaborative documents receive doc updates and cursor/awareness events.
|
|
75
|
+
`useDbSync` and every `subscribeSyncEvents` subscriber share one `EventSource` and one fallback poll loop per tab — this is how collaborative documents receive doc updates and cursor/awareness events. Hidden tabs keep that shared transport alive by default: SSE stays connected and active fallback polling relaxes to a 10-second floor (idle polling remains once per minute). Pass `pauseWhenHidden: true` only for a consumer that explicitly must stop all hidden-tab sync.
|
|
76
76
|
|
|
77
77
|
## Which sources to depend on
|
|
78
78
|
|
|
@@ -71,7 +71,7 @@ export function Layout({ children }: LayoutProps) {
|
|
|
71
71
|
// A direct /chat/:id link already names the thread and must preserve the
|
|
72
72
|
// normal chat-to-sidebar transition. Only the empty home chat needs the
|
|
73
73
|
// recent marker that proves an agent handoff is in flight.
|
|
74
|
-
requireActiveHandoff:
|
|
74
|
+
requireActiveHandoff: false,
|
|
75
75
|
});
|
|
76
76
|
|
|
77
77
|
useEffect(() => {
|