@agent-native/core 0.121.2 → 0.122.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +4 -12
- package/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +148 -0
- package/corpus/core/docs/content/actions.mdx +2 -2
- package/corpus/core/docs/content/agent-surfaces.mdx +1 -1
- package/corpus/core/docs/content/authentication.mdx +2 -0
- package/corpus/core/docs/content/deployment.mdx +36 -0
- package/corpus/core/docs/content/http-api.mdx +266 -0
- package/corpus/core/docs/content/syncing-template-changes.mdx +159 -0
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/a2a/activity.ts +38 -27
- package/corpus/core/src/a2a/client.ts +57 -4
- package/corpus/core/src/agent/engine/builder-engine.ts +35 -4
- package/corpus/core/src/agent/run-manager.ts +20 -1
- package/corpus/core/src/agent/run-store.ts +4 -1
- package/corpus/core/src/agent/types.ts +28 -8
- package/corpus/core/src/chat-threads/store.ts +45 -0
- package/corpus/core/src/checkpoints/index.ts +2 -0
- package/corpus/core/src/checkpoints/route-match.ts +13 -0
- package/corpus/core/src/cli/create.ts +105 -17
- package/corpus/core/src/cli/index.ts +20 -0
- package/corpus/core/src/cli/template-baseline.ts +410 -0
- package/corpus/core/src/cli/template-sync.ts +1004 -0
- package/corpus/core/src/cli/templates-meta.ts +1 -0
- package/corpus/core/src/client/AssistantChat.tsx +38 -12
- package/corpus/core/src/client/NewWorkspaceAppFlow.tsx +75 -10
- package/corpus/core/src/client/chat/message-components.tsx +127 -66
- package/corpus/core/src/client/chat/tool-call-display.tsx +29 -5
- package/corpus/core/src/client/error-format.ts +18 -0
- package/corpus/core/src/client/org/OrgSwitcher.tsx +30 -8
- package/corpus/core/src/client/org/TeamPage.tsx +7 -6
- package/corpus/core/src/client/settings/SettingsPanel.tsx +9 -9
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +12 -2
- package/corpus/core/src/client/sse-event-processor.ts +3 -0
- package/corpus/core/src/data-widgets/index.ts +41 -0
- package/corpus/core/src/deploy/build.ts +11 -9
- package/corpus/core/src/extensions/url-safety.ts +67 -4
- package/corpus/core/src/integrations/webhook-handler.ts +63 -9
- package/corpus/core/src/org/auto-join-domain.ts +4 -3
- package/corpus/core/src/org/context.ts +108 -39
- package/corpus/core/src/org/index.ts +5 -0
- package/corpus/core/src/org/service-identity.ts +60 -0
- package/corpus/core/src/provider-api/index.ts +22 -1
- package/corpus/core/src/scripts/docs/search.ts +12 -1
- package/corpus/core/src/server/agent-chat/context-tools.ts +9 -4
- package/corpus/core/src/server/agent-chat/framework-prompts.ts +38 -66
- package/corpus/core/src/server/agent-chat-plugin.ts +17 -10
- package/corpus/core/src/server/agents-bundle.ts +84 -5
- package/corpus/core/src/server/auth.ts +4 -3
- package/corpus/core/src/server/builder-browser.ts +47 -19
- package/corpus/core/src/server/core-routes-plugin.ts +2 -2
- package/corpus/core/src/server/credential-provider.ts +209 -68
- package/corpus/core/src/server/index.ts +3 -0
- package/corpus/core/src/server/prompts/framework-core-compact.ts +6 -13
- package/corpus/core/src/server/prompts/framework-core.ts +9 -21
- package/corpus/core/src/server/prompts/index.ts +0 -1
- package/corpus/core/src/server/prompts/shared-rules.ts +19 -11
- package/corpus/core/src/server/ssr-handler.ts +13 -2
- package/corpus/core/src/settings/user-settings.ts +13 -1
- package/corpus/core/src/shared/cache-control.ts +141 -0
- package/corpus/core/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/core/src/templates/chat/_gitignore +1 -0
- package/corpus/core/src/templates/chat/app/components/layout/Sidebar.tsx +0 -7
- package/corpus/core/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/core/src/templates/default/app/root.tsx +1 -1
- package/corpus/core/src/templates/default/public/manifest.json +2 -2
- package/corpus/core/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/performance/SKILL.md +19 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/visual-answer/SKILL.md +1 -0
- package/corpus/core/src/vite/agents-bundle-plugin.ts +7 -6
- package/corpus/core/src/vite/client.ts +69 -3
- package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +4 -5
- package/corpus/templates/analytics/.agents/skills/admin-surfaces/SKILL.md +55 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +10 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/references/template-catalog-and-demo.md +62 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-ops/SKILL.md +85 -0
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +27 -2
- package/corpus/templates/analytics/.agents/skills/data-querying/references/inline-chart-embeds.md +139 -0
- package/corpus/templates/analytics/.agents/skills/monitoring/SKILL.md +77 -0
- package/corpus/templates/analytics/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/analytics/AGENTS.md +91 -563
- package/corpus/templates/analytics/_gitignore +1 -0
- package/corpus/templates/analytics/actions/bigquery.ts +12 -4
- package/corpus/templates/analytics/actions/generate-chart.ts +5 -5
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +30 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +1 -23
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/DashboardFilterBar.tsx +3 -0
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +1 -1
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/index.tsx +1 -0
- package/corpus/templates/analytics/app/routes/_index.tsx +1 -1
- package/corpus/templates/analytics/changelog/2026-07-24-analytics-finds-saved-dashboards-and-queries-far-more-reliab.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-24-notion-access-errors-now-explain-that-a-page-needs-sharing-w.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/analytics/package.json +1 -1
- package/corpus/templates/analytics/public/manifest.json +1 -1
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +7 -0
- package/corpus/templates/analytics/server/lib/analytics-query-catalog.ts +191 -30
- package/corpus/templates/analytics/server/lib/bigquery.ts +11 -1
- package/corpus/templates/analytics/server/lib/notion.ts +15 -4
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +10 -0
- package/corpus/templates/analytics/server/plugins/auth.ts +1 -1
- package/corpus/templates/assets/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +0 -2
- package/corpus/templates/assets/app/routes/_index.tsx +1 -1
- package/corpus/templates/assets/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/assets/server/plugins/auth.ts +1 -1
- package/corpus/templates/brain/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/brain/_gitignore +1 -0
- package/corpus/templates/brain/app/lib/brain.ts +0 -7
- package/corpus/templates/brain/app/routes/_index.tsx +1 -2
- package/corpus/templates/brain/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/brain/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/brain/package.json +1 -1
- package/corpus/templates/brain/public/manifest.json +1 -1
- package/corpus/templates/brain/server/plugins/auth.ts +1 -1
- package/corpus/templates/calendar/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/calendar/_gitignore +1 -0
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +0 -6
- package/corpus/templates/calendar/app/routes/_app._index.tsx +1 -1
- package/corpus/templates/calendar/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/calendar/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/calendar/package.json +1 -1
- package/corpus/templates/calendar/public/manifest.json +1 -1
- package/corpus/templates/calendar/server/plugins/auth.ts +1 -1
- package/corpus/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/chat/_gitignore +1 -0
- package/corpus/templates/chat/app/components/layout/Sidebar.tsx +0 -7
- package/corpus/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/clips/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +34 -0
- package/corpus/templates/clips/AGENTS.md +5 -4
- package/corpus/templates/clips/_gitignore +1 -0
- package/corpus/templates/clips/actions/cleanup-transcript.ts +5 -0
- package/corpus/templates/clips/actions/get-recording-insights.ts +44 -8
- package/corpus/templates/clips/actions/get-recording-player-data.ts +39 -70
- package/corpus/templates/clips/actions/import-loom-recording.ts +53 -98
- package/corpus/templates/clips/actions/lib/loom-import-job.ts +260 -0
- package/corpus/templates/clips/actions/list-clip-views.ts +2 -4
- package/corpus/templates/clips/actions/list-recordings.ts +69 -19
- package/corpus/templates/clips/actions/list-viewers.ts +5 -2
- package/corpus/templates/clips/actions/regenerate-summary.ts +5 -1
- package/corpus/templates/clips/actions/request-transcript.ts +28 -1
- package/corpus/templates/clips/app/components/editor/stitch-manager.tsx +21 -1
- package/corpus/templates/clips/app/components/library/library-layout.tsx +0 -7
- package/corpus/templates/clips/app/components/meetings/quick-ask-sidebar.tsx +14 -1
- package/corpus/templates/clips/app/components/player/insights-panel.tsx +8 -20
- package/corpus/templates/clips/app/components/player/recording-views-badge.tsx +335 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +13 -8
- package/corpus/templates/clips/app/components/sharing/share-ui.tsx +2 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +13 -2
- package/corpus/templates/clips/app/lib/capture-permissions.ts +110 -0
- package/corpus/templates/clips/app/lib/recording-link.ts +32 -0
- package/corpus/templates/clips/app/routes/_app.library._index.tsx +1 -1
- package/corpus/templates/clips/app/routes/_app.meetings.$meetingId.tsx +27 -0
- package/corpus/templates/clips/app/routes/_index.tsx +1 -1
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +52 -15
- package/corpus/templates/clips/app/routes/record.tsx +82 -31
- package/corpus/templates/clips/app/routes/share.$shareId.tsx +42 -31
- package/corpus/templates/clips/changelog/2026-07-24-clip-pages-now-show-a-view-count-with-viewer-avatars-next-to.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-clip-summaries-read-like-a-description-you-d-write-yourself-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-clip-view-counts-now-separate-human-views-from-agent-views-s.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-clips-actions-can-now-be-called-by-org-service-tokens-so-ext.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-clips-no-longer-briefly-shows-a-dock-icon-while-launching.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-fixed-owner-and-editor-share-links-redirecting-to-the-record.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-meeting-and-note-recordings-now-start-as-a-compact-pill-on-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-new-clips-now-copy-a-shareable-link-instead-of-the-owner-onl.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-opening-a-share-link-as-the-clip-s-owner-now-goes-straight-t.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-permissions-and-rewind-controls-now-use-compact-consistent-h.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-quitting-the-desktop-app-no-longer-shows-a-false-macos-crash.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-screen-recording-permission-errors-now-name-the-exact-app-to.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-the-desktop-app-now-copies-a-new-clip-s-share-link-to-your-c.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-the-recording-toolbar-now-stays-above-other-apps-including-o.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-transcript-captions-now-use-real-speech-timings-instead-of-a.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-24-video-playback-no-longer-stutters-and-replays-the-last-momen.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +17 -1
- package/corpus/templates/clips/desktop/package.json +2 -0
- package/corpus/templates/clips/desktop/src/app.tsx +89 -0
- package/corpus/templates/clips/desktop/src/hooks/useMeetingTranscription.ts +9 -3
- package/corpus/templates/clips/desktop/src/lib/audio-meter.ts +51 -0
- package/corpus/templates/clips/desktop/src/lib/native-notification.ts +44 -0
- package/corpus/templates/clips/desktop/src/lib/recording-link.ts +67 -0
- package/corpus/templates/clips/desktop/src/overlays/live-audio-bars.tsx +35 -35
- package/corpus/templates/clips/desktop/src/overlays/live-transcript.tsx +3 -2
- package/corpus/templates/clips/desktop/src/overlays/recording-pill.tsx +47 -46
- package/corpus/templates/clips/desktop/src/styles.css +127 -78
- package/corpus/templates/clips/desktop/src-tauri/Cargo.toml +7 -0
- package/corpus/templates/clips/desktop/src-tauri/Info.plist +6 -0
- package/corpus/templates/clips/desktop/src-tauri/capabilities/default.json +1 -0
- package/corpus/templates/clips/desktop/src-tauri/src/clips/mod.rs +7 -3
- package/corpus/templates/clips/desktop/src-tauri/src/lib.rs +4 -1
- package/corpus/templates/clips/desktop/src-tauri/src/main.rs +8 -0
- package/corpus/templates/clips/desktop/src-tauri/src/recording_indicator.rs +21 -10
- package/corpus/templates/clips/desktop/src-tauri/src/util.rs +40 -0
- package/corpus/templates/clips/server/db/schema.ts +27 -0
- package/corpus/templates/clips/server/lib/agent-views.ts +145 -0
- package/corpus/templates/clips/server/lib/post-finalize-dispatch.ts +2 -1
- package/corpus/templates/clips/server/lib/public-agent-context.ts +7 -0
- package/corpus/templates/clips/server/lib/recording-share-grant.ts +79 -0
- package/corpus/templates/clips/server/lib/recordings.ts +51 -3
- package/corpus/templates/clips/server/plugins/auth.ts +1 -1
- package/corpus/templates/clips/server/plugins/db.ts +31 -0
- package/corpus/templates/clips/server/routes/api/_agent-native-background/post-finalize-worker.post.ts +56 -3
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +49 -17
- package/corpus/templates/clips/shared/recording-link.ts +43 -0
- package/corpus/templates/clips/shared/share-attribution.ts +4 -0
- package/corpus/templates/clips/shared/share-dashboard-redirect.ts +42 -0
- package/corpus/templates/clips/shared/view-analytics.ts +26 -0
- package/corpus/templates/content/.agents/skills/content/SKILL.md +5 -0
- package/corpus/templates/content/.agents/skills/content/references/local-file-mode.md +135 -0
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +12 -0
- package/corpus/templates/content/.agents/skills/document-editing/references/databases.md +238 -0
- package/corpus/templates/content/.agents/skills/document-editing/references/document-behavior.md +91 -0
- package/corpus/templates/content/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/content/AGENTS.md +62 -583
- package/corpus/templates/content/_gitignore +1 -0
- package/corpus/templates/content/app/components/sidebar/DocumentSidebar.tsx +1 -39
- package/corpus/templates/content/app/routes/_app._index.tsx +1 -2
- package/corpus/templates/content/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/content/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/content/package.json +1 -1
- package/corpus/templates/content/public/manifest.json +1 -1
- package/corpus/templates/content/server/plugins/auth.ts +1 -1
- package/corpus/templates/design/.agents/skills/creative-context/SKILL.md +11 -0
- package/corpus/templates/design/.agents/skills/design-generation/SKILL.md +50 -8
- package/corpus/templates/design/.agents/skills/design-generation/references/code-layers.md +81 -0
- package/corpus/templates/design/.agents/skills/design-generation/references/code-workspace.md +42 -0
- package/corpus/templates/design/.agents/skills/design-generation/references/editor-extensions.md +34 -0
- package/corpus/templates/design/.agents/skills/responsive-breakpoints/SKILL.md +4 -1
- package/corpus/templates/design/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/design/.agents/skills/visual-edit/SKILL.md +17 -0
- package/corpus/templates/design/.generated/bridge/editor-chrome.generated.ts +8196 -0
- package/corpus/templates/design/.generated/bridge/embedded-wheel.generated.ts +265 -0
- package/corpus/templates/design/.generated/bridge/hit-test.generated.ts +642 -0
- package/corpus/templates/design/.generated/bridge/motion-preview.generated.ts +571 -0
- package/corpus/templates/design/.generated/bridge/nav.generated.ts +91 -0
- package/corpus/templates/design/.generated/bridge/sample.generated.ts +23 -0
- package/corpus/templates/design/.generated/bridge/shader-fill-preview.generated.ts +263 -0
- package/corpus/templates/design/.generated/bridge/shader-runtime.generated.ts +518 -0
- package/corpus/templates/design/.generated/bridge/tweak.generated.ts +20 -0
- package/corpus/templates/design/.generated/bridge/zoom.generated.ts +32 -0
- package/corpus/templates/design/AGENTS.md +88 -503
- package/corpus/templates/design/_gitignore +1 -0
- package/corpus/templates/design/app/components/layout/Sidebar.tsx +0 -2
- package/corpus/templates/design/app/routes/_index.tsx +1 -1
- package/corpus/templates/design/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/design/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/design/server/plugins/auth.ts +1 -1
- package/corpus/templates/dispatch/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/dispatch/_gitignore +1 -0
- package/corpus/templates/dispatch/app/dispatch-extensions.tsx +1 -10
- package/corpus/templates/dispatch/app/routes/overview.tsx +1 -1
- package/corpus/templates/dispatch/changelog/2026-07-24-signing-in-now-puts-you-in-your-companys-workspace.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/dispatch/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/dispatch/package.json +1 -1
- package/corpus/templates/dispatch/public/manifest.json +1 -1
- package/corpus/templates/forms/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/forms/_gitignore +1 -0
- package/corpus/templates/forms/app/components/layout/Sidebar.tsx +0 -35
- package/corpus/templates/forms/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/forms/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/forms/package.json +1 -1
- package/corpus/templates/forms/public/manifest.json +1 -1
- package/corpus/templates/forms/server/lib/public-form-ssr.ts +2 -2
- package/corpus/templates/forms/server/plugins/auth.ts +1 -1
- package/corpus/templates/macros/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/macros/_gitignore +1 -0
- package/corpus/templates/macros/app/components/layout/AppLayout.tsx +0 -2
- package/corpus/templates/macros/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/macros/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/macros/package.json +1 -1
- package/corpus/templates/macros/server/plugins/auth.ts +2 -2
- package/corpus/templates/mail/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/mail/_gitignore +1 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +0 -39
- package/corpus/templates/mail/app/routes/$view.$threadId.tsx +1 -1
- package/corpus/templates/mail/app/routes/$view.tsx +1 -1
- package/corpus/templates/mail/app/routes/_index.tsx +1 -1
- package/corpus/templates/mail/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/mail/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/mail/package.json +1 -1
- package/corpus/templates/mail/public/manifest.json +1 -1
- package/corpus/templates/mail/server/plugins/auth.ts +1 -1
- package/corpus/templates/plan/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/plan/_gitignore +1 -0
- package/corpus/templates/plan/agent-native.app-skill.json +1 -1
- package/corpus/templates/plan/app/components/layout/Sidebar.tsx +4 -9
- package/corpus/templates/plan/app/lib/app-config.ts +1 -1
- package/corpus/templates/plan/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/plan/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/plan/package.json +1 -1
- package/corpus/templates/plan/public/manifest.json +1 -1
- package/corpus/templates/plan/server/plugins/auth.ts +1 -1
- package/corpus/templates/slides/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/slides/_gitignore +1 -0
- package/corpus/templates/slides/app/components/layout/Sidebar.tsx +0 -2
- package/corpus/templates/slides/app/routes/_index.tsx +1 -1
- package/corpus/templates/slides/changelog/2026-07-25-app-branding-now-uses-the-product-name-without-the-agent-nat.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/corpus/templates/slides/package.json +1 -1
- package/corpus/templates/slides/public/manifest.json +1 -1
- package/corpus/templates/slides/server/plugins/auth.ts +1 -1
- package/corpus/templates/slides/server/plugins/db.ts +4 -0
- package/corpus/templates/tasks/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/corpus/templates/tasks/AGENTS.md +1 -1
- package/corpus/templates/tasks/_gitignore +1 -0
- package/dist/a2a/activity.d.ts +4 -1
- package/dist/a2a/activity.d.ts.map +1 -1
- package/dist/a2a/activity.js +27 -16
- package/dist/a2a/activity.js.map +1 -1
- package/dist/a2a/client.d.ts.map +1 -1
- package/dist/a2a/client.js +58 -4
- package/dist/a2a/client.js.map +1 -1
- package/dist/agent/engine/builder-engine.d.ts +1 -0
- package/dist/agent/engine/builder-engine.d.ts.map +1 -1
- package/dist/agent/engine/builder-engine.js +31 -4
- package/dist/agent/engine/builder-engine.js.map +1 -1
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +18 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/agent/run-store.d.ts +1 -0
- package/dist/agent/run-store.d.ts.map +1 -1
- package/dist/agent/run-store.js +2 -1
- package/dist/agent/run-store.js.map +1 -1
- package/dist/agent/types.d.ts +12 -1
- package/dist/agent/types.d.ts.map +1 -1
- package/dist/agent/types.js +21 -1
- package/dist/agent/types.js.map +1 -1
- package/dist/chat-threads/store.d.ts +10 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +41 -0
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/checkpoints/index.d.ts +1 -0
- package/dist/checkpoints/index.d.ts.map +1 -1
- package/dist/checkpoints/index.js +1 -0
- package/dist/checkpoints/index.js.map +1 -1
- package/dist/checkpoints/route-match.d.ts +8 -0
- package/dist/checkpoints/route-match.d.ts.map +1 -0
- package/dist/checkpoints/route-match.js +13 -0
- package/dist/checkpoints/route-match.js.map +1 -0
- package/dist/cli/create.d.ts +35 -4
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +69 -19
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/index.js +19 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/template-baseline.d.ts +54 -0
- package/dist/cli/template-baseline.d.ts.map +1 -0
- package/dist/cli/template-baseline.js +334 -0
- package/dist/cli/template-baseline.js.map +1 -0
- package/dist/cli/template-sync.d.ts +57 -0
- package/dist/cli/template-sync.d.ts.map +1 -0
- package/dist/cli/template-sync.js +787 -0
- package/dist/cli/template-sync.js.map +1 -0
- package/dist/cli/templates-meta.d.ts.map +1 -1
- package/dist/cli/templates-meta.js +1 -0
- package/dist/cli/templates-meta.js.map +1 -1
- package/dist/client/AssistantChat.d.ts +2 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +38 -10
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/NewWorkspaceAppFlow.d.ts.map +1 -1
- package/dist/client/NewWorkspaceAppFlow.js +32 -2
- package/dist/client/NewWorkspaceAppFlow.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +20 -0
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +81 -46
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/chat/tool-call-display.d.ts.map +1 -1
- package/dist/client/chat/tool-call-display.js +14 -6
- package/dist/client/chat/tool-call-display.js.map +1 -1
- package/dist/client/error-format.d.ts.map +1 -1
- package/dist/client/error-format.js +13 -0
- package/dist/client/error-format.js.map +1 -1
- package/dist/client/org/OrgSwitcher.d.ts +8 -1
- package/dist/client/org/OrgSwitcher.d.ts.map +1 -1
- package/dist/client/org/OrgSwitcher.js +3 -2
- package/dist/client/org/OrgSwitcher.js.map +1 -1
- package/dist/client/org/TeamPage.d.ts.map +1 -1
- package/dist/client/org/TeamPage.js +1 -1
- package/dist/client/org/TeamPage.js.map +1 -1
- package/dist/client/settings/SettingsPanel.js +9 -9
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +7 -4
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/client/sse-event-processor.d.ts.map +1 -1
- package/dist/client/sse-event-processor.js +3 -0
- package/dist/client/sse-event-processor.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/data-widgets/index.d.ts +3 -0
- package/dist/data-widgets/index.d.ts.map +1 -1
- package/dist/data-widgets/index.js +33 -0
- package/dist/data-widgets/index.js.map +1 -1
- package/dist/deploy/build.d.ts.map +1 -1
- package/dist/deploy/build.js +11 -7
- package/dist/deploy/build.js.map +1 -1
- package/dist/eject/provider-api-definitions.d.ts +1 -0
- package/dist/eject/provider-api-definitions.d.ts.map +1 -1
- package/dist/extensions/url-safety.d.ts +9 -16
- package/dist/extensions/url-safety.d.ts.map +1 -1
- package/dist/extensions/url-safety.js +48 -4
- package/dist/extensions/url-safety.js.map +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/integrations/webhook-handler.d.ts.map +1 -1
- package/dist/integrations/webhook-handler.js +46 -9
- package/dist/integrations/webhook-handler.js.map +1 -1
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/auto-join-domain.d.ts +3 -2
- package/dist/org/auto-join-domain.d.ts.map +1 -1
- package/dist/org/auto-join-domain.js +1 -1
- package/dist/org/auto-join-domain.js.map +1 -1
- package/dist/org/context.d.ts.map +1 -1
- package/dist/org/context.js +84 -28
- package/dist/org/context.js.map +1 -1
- package/dist/org/index.d.ts +1 -0
- package/dist/org/index.d.ts.map +1 -1
- package/dist/org/index.js +1 -0
- package/dist/org/index.js.map +1 -1
- package/dist/org/service-identity.d.ts +43 -0
- package/dist/org/service-identity.d.ts.map +1 -0
- package/dist/org/service-identity.js +34 -0
- package/dist/org/service-identity.js.map +1 -0
- package/dist/provider-api/actions/custom-provider-registration.d.ts +14 -14
- package/dist/provider-api/actions/provider-api.d.ts +13 -11
- package/dist/provider-api/actions/provider-api.d.ts.map +1 -1
- package/dist/provider-api/index.d.ts +4 -0
- package/dist/provider-api/index.d.ts.map +1 -1
- package/dist/provider-api/index.js +17 -1
- package/dist/provider-api/index.js.map +1 -1
- package/dist/scripts/docs/search.d.ts.map +1 -1
- package/dist/scripts/docs/search.js +12 -1
- package/dist/scripts/docs/search.js.map +1 -1
- package/dist/server/agent-chat/context-tools.d.ts.map +1 -1
- package/dist/server/agent-chat/context-tools.js +3 -3
- package/dist/server/agent-chat/context-tools.js.map +1 -1
- package/dist/server/agent-chat/framework-prompts.d.ts +8 -3
- package/dist/server/agent-chat/framework-prompts.d.ts.map +1 -1
- package/dist/server/agent-chat/framework-prompts.js +33 -62
- package/dist/server/agent-chat/framework-prompts.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +16 -10
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-engine-api-key-route.d.ts +1 -1
- package/dist/server/agents-bundle.d.ts +18 -0
- package/dist/server/agents-bundle.d.ts.map +1 -1
- package/dist/server/agents-bundle.js +70 -4
- package/dist/server/agents-bundle.js.map +1 -1
- package/dist/server/auth.d.ts.map +1 -1
- package/dist/server/auth.js +4 -3
- package/dist/server/auth.js.map +1 -1
- package/dist/server/builder-browser.d.ts.map +1 -1
- package/dist/server/builder-browser.js +43 -19
- package/dist/server/builder-browser.js.map +1 -1
- package/dist/server/core-routes-plugin.js +2 -2
- package/dist/server/core-routes-plugin.js.map +1 -1
- package/dist/server/credential-provider.d.ts +38 -2
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +142 -64
- package/dist/server/credential-provider.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 +1 -1
- package/dist/server/index.js.map +1 -1
- package/dist/server/prompts/framework-core-compact.d.ts +1 -1
- package/dist/server/prompts/framework-core-compact.d.ts.map +1 -1
- package/dist/server/prompts/framework-core-compact.js +7 -13
- package/dist/server/prompts/framework-core-compact.js.map +1 -1
- package/dist/server/prompts/framework-core.d.ts +1 -1
- package/dist/server/prompts/framework-core.d.ts.map +1 -1
- package/dist/server/prompts/framework-core.js +10 -21
- package/dist/server/prompts/framework-core.js.map +1 -1
- package/dist/server/prompts/index.d.ts +1 -1
- package/dist/server/prompts/index.d.ts.map +1 -1
- package/dist/server/prompts/index.js +1 -1
- package/dist/server/prompts/index.js.map +1 -1
- package/dist/server/prompts/shared-rules.d.ts +16 -10
- package/dist/server/prompts/shared-rules.d.ts.map +1 -1
- package/dist/server/prompts/shared-rules.js +19 -10
- package/dist/server/prompts/shared-rules.js.map +1 -1
- package/dist/server/ssr-handler.d.ts +1 -1
- package/dist/server/ssr-handler.d.ts.map +1 -1
- package/dist/server/ssr-handler.js +10 -3
- package/dist/server/ssr-handler.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/settings/user-settings.d.ts.map +1 -1
- package/dist/settings/user-settings.js +13 -1
- package/dist/settings/user-settings.js.map +1 -1
- package/dist/shared/cache-control.d.ts +52 -0
- package/dist/shared/cache-control.d.ts.map +1 -1
- package/dist/shared/cache-control.js +107 -0
- package/dist/shared/cache-control.js.map +1 -1
- package/dist/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/dist/templates/chat/_gitignore +1 -0
- package/dist/templates/chat/app/components/layout/Sidebar.tsx +0 -7
- package/dist/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/dist/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/dist/templates/default/app/root.tsx +1 -1
- package/dist/templates/default/public/manifest.json +2 -2
- package/dist/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/dist/templates/workspace-core/.agents/skills/performance/SKILL.md +19 -0
- package/dist/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/dist/templates/workspace-core/.agents/skills/visual-answer/SKILL.md +1 -0
- package/dist/vite/agents-bundle-plugin.d.ts.map +1 -1
- package/dist/vite/agents-bundle-plugin.js +8 -7
- package/dist/vite/agents-bundle-plugin.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +66 -1
- package/dist/vite/client.js.map +1 -1
- package/docs/content/actions.mdx +2 -2
- package/docs/content/agent-surfaces.mdx +1 -1
- package/docs/content/authentication.mdx +2 -0
- package/docs/content/deployment.mdx +36 -0
- package/docs/content/http-api.mdx +266 -0
- package/docs/content/syncing-template-changes.mdx +159 -0
- package/package.json +4 -3
- package/src/a2a/activity.ts +38 -27
- package/src/a2a/client.ts +57 -4
- package/src/agent/engine/builder-engine.ts +35 -4
- package/src/agent/run-manager.ts +20 -1
- package/src/agent/run-store.ts +4 -1
- package/src/agent/types.ts +28 -8
- package/src/chat-threads/store.ts +45 -0
- package/src/checkpoints/index.ts +2 -0
- package/src/checkpoints/route-match.ts +13 -0
- package/src/cli/create.ts +105 -17
- package/src/cli/index.ts +20 -0
- package/src/cli/template-baseline.ts +410 -0
- package/src/cli/template-sync.ts +1004 -0
- package/src/cli/templates-meta.ts +1 -0
- package/src/client/AssistantChat.tsx +38 -12
- package/src/client/NewWorkspaceAppFlow.tsx +75 -10
- package/src/client/chat/message-components.tsx +127 -66
- package/src/client/chat/tool-call-display.tsx +29 -5
- package/src/client/error-format.ts +18 -0
- package/src/client/org/OrgSwitcher.tsx +30 -8
- package/src/client/org/TeamPage.tsx +7 -6
- package/src/client/settings/SettingsPanel.tsx +9 -9
- package/src/client/settings/SettingsTabsPage.tsx +12 -2
- package/src/client/sse-event-processor.ts +3 -0
- package/src/data-widgets/index.ts +41 -0
- package/src/deploy/build.ts +11 -9
- package/src/extensions/url-safety.ts +67 -4
- package/src/integrations/webhook-handler.ts +63 -9
- package/src/org/auto-join-domain.ts +4 -3
- package/src/org/context.ts +108 -39
- package/src/org/index.ts +5 -0
- package/src/org/service-identity.ts +60 -0
- package/src/provider-api/index.ts +22 -1
- package/src/scripts/docs/search.ts +12 -1
- package/src/server/__snapshots__/agent-chat-plugin.surface.spec.ts.snap +25 -36
- package/src/server/agent-chat/context-tools.ts +9 -4
- package/src/server/agent-chat/framework-prompts.ts +38 -66
- package/src/server/agent-chat-plugin.ts +17 -10
- package/src/server/agents-bundle.ts +84 -5
- package/src/server/auth.ts +4 -3
- package/src/server/builder-browser.ts +47 -19
- package/src/server/core-routes-plugin.ts +2 -2
- package/src/server/credential-provider.ts +209 -68
- package/src/server/index.ts +3 -0
- package/src/server/prompts/framework-core-compact.ts +6 -13
- package/src/server/prompts/framework-core.ts +9 -21
- package/src/server/prompts/index.ts +0 -1
- package/src/server/prompts/shared-rules.ts +19 -11
- package/src/server/ssr-handler.ts +13 -2
- package/src/settings/user-settings.ts +13 -1
- package/src/shared/cache-control.ts +141 -0
- package/src/templates/chat/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/src/templates/chat/_gitignore +1 -0
- package/src/templates/chat/app/components/layout/Sidebar.tsx +0 -7
- package/src/templates/chat/changelog/2026-07-25-settings-navigation-now-keeps-manage-agent-as-a-dedicated-li.md +6 -0
- package/src/templates/default/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/src/templates/default/app/root.tsx +1 -1
- package/src/templates/default/public/manifest.json +2 -2
- package/src/templates/headless/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/src/templates/workspace-core/.agents/skills/performance/SKILL.md +19 -0
- package/src/templates/workspace-core/.agents/skills/upgrade-agent-native/SKILL.md +24 -2
- package/src/templates/workspace-core/.agents/skills/visual-answer/SKILL.md +1 -0
- package/src/vite/agents-bundle-plugin.ts +7 -6
- package/src/vite/client.ts +69 -3
|
@@ -166,6 +166,8 @@ export const recordings = table("recordings", {
|
|
|
166
166
|
.default("uploading"),
|
|
167
167
|
uploadProgress: integer("upload_progress").notNull().default(0),
|
|
168
168
|
failureReason: text("failure_reason"),
|
|
169
|
+
loomImportClaimId: text("loom_import_claim_id"),
|
|
170
|
+
loomImportClaimedAt: text("loom_import_claimed_at"),
|
|
169
171
|
|
|
170
172
|
// Non-destructive edits: JSON `{ trims: [{startMs,endMs,excluded}], blurs: [...], speed: [...] }`
|
|
171
173
|
editsJson: text("edits_json").notNull().default("{}"),
|
|
@@ -382,6 +384,31 @@ export const recordingViews = table("recording_views", {
|
|
|
382
384
|
viewedAt: text("viewed_at").notNull().default(now()),
|
|
383
385
|
});
|
|
384
386
|
|
|
387
|
+
// Agent views — one row per (clip, agent, time bucket). Deliberately separate
|
|
388
|
+
// from `recording_viewers` / `recording_views` so no human-view count can ever
|
|
389
|
+
// pick agents up by forgetting a filter: the human tables stay agent-free.
|
|
390
|
+
export const recordingAgentViews = table(
|
|
391
|
+
"recording_agent_views",
|
|
392
|
+
{
|
|
393
|
+
id: text("id").primaryKey(),
|
|
394
|
+
recordingId: text("recording_id").notNull(),
|
|
395
|
+
// sha256 of user-agent + request IP. Never stores the raw IP.
|
|
396
|
+
agentKey: text("agent_key").notNull(),
|
|
397
|
+
agentLabel: text("agent_label"),
|
|
398
|
+
// Time bucket that collapses one agent's burst of context/transcript/frame
|
|
399
|
+
// polls into a single view.
|
|
400
|
+
viewSessionId: text("view_session_id").notNull(),
|
|
401
|
+
firstSeenAt: text("first_seen_at").notNull().default(now()),
|
|
402
|
+
lastSeenAt: text("last_seen_at").notNull().default(now()),
|
|
403
|
+
requestCount: integer("request_count").notNull().default(1),
|
|
404
|
+
},
|
|
405
|
+
(view) => ({
|
|
406
|
+
recordingAgentViewSessionUnique: uniqueIndex(
|
|
407
|
+
"recording_agent_views_session_unique_idx",
|
|
408
|
+
).on(view.recordingId, view.agentKey, view.viewSessionId),
|
|
409
|
+
}),
|
|
410
|
+
);
|
|
411
|
+
|
|
385
412
|
// -----------------------------------------------------------------------------
|
|
386
413
|
// Meetings (Granola-style) — recording + transcript + AI notes anchored to
|
|
387
414
|
// a calendar event or an ad-hoc meeting block. Composes with the existing
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Agent views — counting when an *outside agent* reads a clip through the
|
|
3
|
+
* public agent APIs (`/api/agent-context.json`, `/api/agent-transcript.json`,
|
|
4
|
+
* `/api/agent-frame.jpg`), as opposed to a human opening the player.
|
|
5
|
+
*
|
|
6
|
+
* Those routes are agent-only surfaces: a human watching a clip never hits
|
|
7
|
+
* them, so a request on one is the signal. Counts live in their own table so
|
|
8
|
+
* every existing human-view query stays agent-free by construction.
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { createHash } from "node:crypto";
|
|
12
|
+
|
|
13
|
+
import { count, desc, eq, sql } from "drizzle-orm";
|
|
14
|
+
import { getRequestHeader, getRequestIP, type H3Event } from "h3";
|
|
15
|
+
|
|
16
|
+
import { getDb, schema } from "../db/index.js";
|
|
17
|
+
import { nanoid } from "./recordings.js";
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* One agent's burst of context + transcript + frame polls is one view.
|
|
21
|
+
* Fixed windows mean a burst straddling a boundary counts twice; switch to
|
|
22
|
+
* last-seen-gap dedup if that ever shows up in the numbers.
|
|
23
|
+
*/
|
|
24
|
+
export const AGENT_VIEW_SESSION_MS = 30 * 60 * 1000;
|
|
25
|
+
|
|
26
|
+
const AGENT_LABELS: [RegExp, string][] = [
|
|
27
|
+
[/claude|anthropic/i, "Claude"],
|
|
28
|
+
[/chatgpt|gptbot|oai-searchbot|openai/i, "ChatGPT"],
|
|
29
|
+
[/perplexity/i, "Perplexity"],
|
|
30
|
+
[/gemini|google-extended|googleother/i, "Gemini"],
|
|
31
|
+
[/copilot/i, "Copilot"],
|
|
32
|
+
[/cursor/i, "Cursor"],
|
|
33
|
+
[/bingbot|bingpreview/i, "Bing"],
|
|
34
|
+
[/applebot/i, "Apple Intelligence"],
|
|
35
|
+
[/meta-external|facebookbot/i, "Meta AI"],
|
|
36
|
+
[/bytespider|amazonbot|ccbot|diffbot|youbot/i, "Crawler"],
|
|
37
|
+
];
|
|
38
|
+
|
|
39
|
+
export const UNKNOWN_AGENT_LABEL = "Agent";
|
|
40
|
+
|
|
41
|
+
export function agentLabelFromUserAgent(userAgent: string): string {
|
|
42
|
+
for (const [pattern, label] of AGENT_LABELS) {
|
|
43
|
+
if (pattern.test(userAgent)) return label;
|
|
44
|
+
}
|
|
45
|
+
return UNKNOWN_AGENT_LABEL;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function agentViewSessionId(
|
|
49
|
+
now: number,
|
|
50
|
+
bucketMs = AGENT_VIEW_SESSION_MS,
|
|
51
|
+
): string {
|
|
52
|
+
return String(Math.floor(now / bucketMs));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
/** Hashed so an agent is distinguishable across polls without storing its IP. */
|
|
56
|
+
export function agentKeyFor(userAgent: string, ip: string): string {
|
|
57
|
+
return createHash("sha256")
|
|
58
|
+
.update(`${userAgent}|${ip}`)
|
|
59
|
+
.digest("hex")
|
|
60
|
+
.slice(0, 32);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Best-effort: never let view accounting fail an agent's read of a clip.
|
|
65
|
+
*/
|
|
66
|
+
export async function recordAgentView(
|
|
67
|
+
event: H3Event,
|
|
68
|
+
recordingId: string,
|
|
69
|
+
nowMs: number = Date.now(),
|
|
70
|
+
): Promise<void> {
|
|
71
|
+
try {
|
|
72
|
+
const userAgent = (getRequestHeader(event, "user-agent") ?? "").slice(
|
|
73
|
+
0,
|
|
74
|
+
512,
|
|
75
|
+
);
|
|
76
|
+
const ip = getRequestIP(event) || "unknown";
|
|
77
|
+
const now = new Date(nowMs).toISOString();
|
|
78
|
+
|
|
79
|
+
await getDb()
|
|
80
|
+
.insert(schema.recordingAgentViews)
|
|
81
|
+
.values({
|
|
82
|
+
id: nanoid(),
|
|
83
|
+
recordingId,
|
|
84
|
+
agentKey: agentKeyFor(userAgent, ip),
|
|
85
|
+
agentLabel: agentLabelFromUserAgent(userAgent),
|
|
86
|
+
viewSessionId: agentViewSessionId(nowMs),
|
|
87
|
+
firstSeenAt: now,
|
|
88
|
+
lastSeenAt: now,
|
|
89
|
+
requestCount: 1,
|
|
90
|
+
})
|
|
91
|
+
.onConflictDoUpdate({
|
|
92
|
+
target: [
|
|
93
|
+
schema.recordingAgentViews.recordingId,
|
|
94
|
+
schema.recordingAgentViews.agentKey,
|
|
95
|
+
schema.recordingAgentViews.viewSessionId,
|
|
96
|
+
],
|
|
97
|
+
set: {
|
|
98
|
+
lastSeenAt: now,
|
|
99
|
+
requestCount: sql`${schema.recordingAgentViews.requestCount} + 1`,
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
} catch (err) {
|
|
103
|
+
console.warn("[agent-views] failed to record agent view:", err);
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
export async function countRecordingAgentViews(
|
|
108
|
+
recordingId: string,
|
|
109
|
+
): Promise<number> {
|
|
110
|
+
const [row] = await getDb()
|
|
111
|
+
.select({ value: count() })
|
|
112
|
+
.from(schema.recordingAgentViews)
|
|
113
|
+
.where(eq(schema.recordingAgentViews.recordingId, recordingId));
|
|
114
|
+
return Number(row?.value ?? 0);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
export interface AgentViewerSummary {
|
|
118
|
+
agentLabel: string;
|
|
119
|
+
views: number;
|
|
120
|
+
lastSeenAt: string;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
/** Agents that read this clip, most recent first, grouped by product label. */
|
|
124
|
+
export async function listRecordingAgentViewers(
|
|
125
|
+
recordingId: string,
|
|
126
|
+
limit = 8,
|
|
127
|
+
): Promise<AgentViewerSummary[]> {
|
|
128
|
+
const rows = await getDb()
|
|
129
|
+
.select({
|
|
130
|
+
agentLabel: schema.recordingAgentViews.agentLabel,
|
|
131
|
+
views: count(),
|
|
132
|
+
lastSeenAt: sql<string>`MAX(${schema.recordingAgentViews.lastSeenAt})`,
|
|
133
|
+
})
|
|
134
|
+
.from(schema.recordingAgentViews)
|
|
135
|
+
.where(eq(schema.recordingAgentViews.recordingId, recordingId))
|
|
136
|
+
.groupBy(schema.recordingAgentViews.agentLabel)
|
|
137
|
+
.orderBy(desc(sql`MAX(${schema.recordingAgentViews.lastSeenAt})`))
|
|
138
|
+
.limit(limit);
|
|
139
|
+
|
|
140
|
+
return rows.map((r) => ({
|
|
141
|
+
agentLabel: r.agentLabel || UNKNOWN_AGENT_LABEL,
|
|
142
|
+
views: Number(r.views ?? 0),
|
|
143
|
+
lastSeenAt: r.lastSeenAt,
|
|
144
|
+
}));
|
|
145
|
+
}
|
|
@@ -31,6 +31,7 @@ import {
|
|
|
31
31
|
} from "../../shared/transcript-segments.js";
|
|
32
32
|
import { resolveTranscriptPresentation } from "../../shared/transcript-status.js";
|
|
33
33
|
import { getDb, schema } from "../db/index.js";
|
|
34
|
+
import { recordAgentView } from "./agent-views.js";
|
|
34
35
|
import { verifySharePassword } from "./share-password.js";
|
|
35
36
|
|
|
36
37
|
export type PublicAgentRecording = typeof schema.recordings._.inferSelect;
|
|
@@ -290,6 +291,12 @@ export async function loadPublicAgentAccess(
|
|
|
290
291
|
}
|
|
291
292
|
}
|
|
292
293
|
|
|
294
|
+
// Every agent API route funnels through here, so this is the one place that
|
|
295
|
+
// sees an outside agent read a clip. Owner requests are previews, not views.
|
|
296
|
+
if (!viewerIsOwner) {
|
|
297
|
+
await recordAgentView(event, recording.id);
|
|
298
|
+
}
|
|
299
|
+
|
|
293
300
|
return {
|
|
294
301
|
ok: true,
|
|
295
302
|
access: {
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Explicit share-grant lookup for the direct `/r/:id` recording page.
|
|
3
|
+
*
|
|
4
|
+
* Lives beside `recording-page-access.ts` rather than inside it so that module
|
|
5
|
+
* stays free of database imports and their registration side effects. Every
|
|
6
|
+
* surface that decides whether a viewer may open the authenticated recording
|
|
7
|
+
* page — the player action and the public share endpoint that auto-redirects
|
|
8
|
+
* into it — must use this helper, or the share page and the page it redirects
|
|
9
|
+
* to will disagree and ping-pong.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
getRequestOrgId,
|
|
14
|
+
getRequestUserEmail,
|
|
15
|
+
} from "@agent-native/core/server/request-context";
|
|
16
|
+
import { and, eq, or, sql } from "drizzle-orm";
|
|
17
|
+
|
|
18
|
+
import { getDb, schema } from "../db/index.js";
|
|
19
|
+
import type { RecordingPageAccessRole } from "./recording-page-access.js";
|
|
20
|
+
import type { RecordingVisibility } from "./recordings.js";
|
|
21
|
+
|
|
22
|
+
export interface RecordingShareGrantInput {
|
|
23
|
+
recordingId: string;
|
|
24
|
+
role: RecordingPageAccessRole;
|
|
25
|
+
visibility: RecordingVisibility;
|
|
26
|
+
hasPassword: boolean;
|
|
27
|
+
/** Agent/MCP/A2A callers, which may open password-less public clips. */
|
|
28
|
+
isAgentCaller?: boolean;
|
|
29
|
+
/** Omit to read the ambient request context. */
|
|
30
|
+
userEmail?: string | null;
|
|
31
|
+
/** Omit to read the ambient request context. */
|
|
32
|
+
orgId?: string | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export async function hasExplicitRecordingShare(
|
|
36
|
+
input: RecordingShareGrantInput,
|
|
37
|
+
): Promise<boolean> {
|
|
38
|
+
if (input.role === "owner") return true;
|
|
39
|
+
if (input.visibility !== "public") return false;
|
|
40
|
+
if (!input.hasPassword && input.isAgentCaller) return true;
|
|
41
|
+
|
|
42
|
+
const userEmail = (
|
|
43
|
+
input.userEmail === undefined ? getRequestUserEmail() : input.userEmail
|
|
44
|
+
)
|
|
45
|
+
?.trim()
|
|
46
|
+
.toLowerCase();
|
|
47
|
+
const orgId = input.orgId === undefined ? getRequestOrgId() : input.orgId;
|
|
48
|
+
|
|
49
|
+
const principals = [];
|
|
50
|
+
if (userEmail) {
|
|
51
|
+
principals.push(
|
|
52
|
+
and(
|
|
53
|
+
eq(schema.recordingShares.principalType, "user"),
|
|
54
|
+
sql`lower(${schema.recordingShares.principalId}) = ${userEmail}`,
|
|
55
|
+
),
|
|
56
|
+
);
|
|
57
|
+
}
|
|
58
|
+
if (orgId) {
|
|
59
|
+
principals.push(
|
|
60
|
+
and(
|
|
61
|
+
eq(schema.recordingShares.principalType, "org"),
|
|
62
|
+
eq(schema.recordingShares.principalId, orgId),
|
|
63
|
+
),
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
if (principals.length === 0) return false;
|
|
67
|
+
|
|
68
|
+
const [share] = await getDb()
|
|
69
|
+
.select({ id: schema.recordingShares.id })
|
|
70
|
+
.from(schema.recordingShares)
|
|
71
|
+
.where(
|
|
72
|
+
and(
|
|
73
|
+
eq(schema.recordingShares.resourceId, input.recordingId),
|
|
74
|
+
or(...principals),
|
|
75
|
+
),
|
|
76
|
+
)
|
|
77
|
+
.limit(1);
|
|
78
|
+
return Boolean(share);
|
|
79
|
+
}
|
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
import { readAppState } from "@agent-native/core/application-state";
|
|
2
|
-
import { orgMembers } from "@agent-native/core/org";
|
|
2
|
+
import { implicitServiceOrgRole, orgMembers } from "@agent-native/core/org";
|
|
3
3
|
import { getSession } from "@agent-native/core/server";
|
|
4
4
|
import {
|
|
5
5
|
getRequestUserEmail,
|
|
6
6
|
getRequestOrgId,
|
|
7
7
|
} from "@agent-native/core/server/request-context";
|
|
8
|
-
import { and, desc, eq, sql } from "drizzle-orm";
|
|
8
|
+
import { and, count, desc, eq, sql } from "drizzle-orm";
|
|
9
9
|
import { HTTPError, type H3Event } from "h3";
|
|
10
10
|
|
|
11
11
|
import { getDb, schema } from "../db/index.js";
|
|
@@ -142,7 +142,11 @@ export async function getOrganizationRoleForEmail(
|
|
|
142
142
|
// org_members table may not exist yet on first boot before migrations finish.
|
|
143
143
|
}
|
|
144
144
|
|
|
145
|
-
return
|
|
145
|
+
return implicitServiceOrgRole({
|
|
146
|
+
email,
|
|
147
|
+
orgId: organizationId,
|
|
148
|
+
requestOrgId: getRequestOrgId(),
|
|
149
|
+
});
|
|
146
150
|
}
|
|
147
151
|
|
|
148
152
|
export async function requireOrganizationAccess(
|
|
@@ -362,3 +366,47 @@ export function shouldCountView(
|
|
|
362
366
|
): boolean {
|
|
363
367
|
return totalWatchMs >= 5000 || completedPct >= 75 || scrubbedToEnd;
|
|
364
368
|
}
|
|
369
|
+
|
|
370
|
+
/**
|
|
371
|
+
* The single definition of a counted *viewer*: one `recording_viewers` row
|
|
372
|
+
* whose `countedView` flag is set. That is one row per person, so it answers
|
|
373
|
+
* "how many distinct viewers", not "how many views" — use
|
|
374
|
+
* `countRecordingViews` for the total. The in-memory twin is
|
|
375
|
+
* `isCountedViewerRow` in `shared/view-analytics.ts`.
|
|
376
|
+
*/
|
|
377
|
+
export function countedViewCondition() {
|
|
378
|
+
return eq(schema.recordingViewers.countedView, true);
|
|
379
|
+
}
|
|
380
|
+
|
|
381
|
+
/**
|
|
382
|
+
* Total views for a recording: one per counted view *session*, so a returning
|
|
383
|
+
* viewer's second visit counts again. Every surface that reports a view count
|
|
384
|
+
* (library list, insights, player, public share page) goes through this.
|
|
385
|
+
*/
|
|
386
|
+
export async function countRecordingViews(
|
|
387
|
+
recordingId: string,
|
|
388
|
+
): Promise<number> {
|
|
389
|
+
const db = getDb();
|
|
390
|
+
const [viewerRow] = await db
|
|
391
|
+
.select({ value: count() })
|
|
392
|
+
.from(schema.recordingViewers)
|
|
393
|
+
.where(
|
|
394
|
+
and(
|
|
395
|
+
eq(schema.recordingViewers.recordingId, recordingId),
|
|
396
|
+
countedViewCondition(),
|
|
397
|
+
),
|
|
398
|
+
);
|
|
399
|
+
const [viewLogRow] = await db
|
|
400
|
+
.select({ value: count() })
|
|
401
|
+
.from(schema.recordingViews)
|
|
402
|
+
.where(eq(schema.recordingViews.recordingId, recordingId));
|
|
403
|
+
|
|
404
|
+
// `recording_views` only exists from migration v46, so clips recorded before
|
|
405
|
+
// it have zero log rows. Floor the total at the counted-viewer count so those
|
|
406
|
+
// clips keep reporting a real number instead of dropping to 0, and so the
|
|
407
|
+
// total can never read below the unique-viewer count beside it.
|
|
408
|
+
return Math.max(
|
|
409
|
+
Number(viewLogRow?.value ?? 0),
|
|
410
|
+
Number(viewerRow?.value ?? 0),
|
|
411
|
+
);
|
|
412
|
+
}
|
|
@@ -7,7 +7,7 @@ export default createAuthPlugin({
|
|
|
7
7
|
// callback can handle both normal sign-in and the Calendar connect flow.
|
|
8
8
|
mountGoogleOAuthRoutes: false,
|
|
9
9
|
marketing: {
|
|
10
|
-
appName: "
|
|
10
|
+
appName: "Clips",
|
|
11
11
|
tagline:
|
|
12
12
|
"Your AI agent transcribes, summarizes, and searches everything you record alongside you.",
|
|
13
13
|
features: [
|
|
@@ -850,6 +850,37 @@ const migrations = runMigrations(
|
|
|
850
850
|
`UPDATE organization_settings SET default_visibility = 'public' WHERE default_visibility = 'private' AND updated_at = created_at`,
|
|
851
851
|
].join("; "),
|
|
852
852
|
},
|
|
853
|
+
// -------------------------------------------------------------------------
|
|
854
|
+
// Agent views — external agents polling a public clip's agent context,
|
|
855
|
+
// transcript, or frame APIs. Kept in its own table so human view counts
|
|
856
|
+
// cannot accidentally include agents.
|
|
857
|
+
// -------------------------------------------------------------------------
|
|
858
|
+
{
|
|
859
|
+
version: 51,
|
|
860
|
+
name: "recording-agent-views",
|
|
861
|
+
sql: [
|
|
862
|
+
`CREATE TABLE IF NOT EXISTS recording_agent_views (
|
|
863
|
+
id TEXT PRIMARY KEY,
|
|
864
|
+
recording_id TEXT NOT NULL,
|
|
865
|
+
agent_key TEXT NOT NULL,
|
|
866
|
+
agent_label TEXT,
|
|
867
|
+
view_session_id TEXT NOT NULL,
|
|
868
|
+
first_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
869
|
+
last_seen_at TEXT NOT NULL DEFAULT (datetime('now')),
|
|
870
|
+
request_count INTEGER NOT NULL DEFAULT 1
|
|
871
|
+
)`,
|
|
872
|
+
`CREATE UNIQUE INDEX IF NOT EXISTS recording_agent_views_session_unique_idx ON recording_agent_views (recording_id, agent_key, view_session_id)`,
|
|
873
|
+
`CREATE INDEX IF NOT EXISTS recording_agent_views_recording_idx ON recording_agent_views (recording_id, last_seen_at)`,
|
|
874
|
+
].join("; "),
|
|
875
|
+
},
|
|
876
|
+
{
|
|
877
|
+
version: 52,
|
|
878
|
+
name: "recording-loom-import-claim-lease",
|
|
879
|
+
sql: [
|
|
880
|
+
`ALTER TABLE recordings ADD COLUMN IF NOT EXISTS loom_import_claim_id TEXT`,
|
|
881
|
+
`ALTER TABLE recordings ADD COLUMN IF NOT EXISTS loom_import_claimed_at TEXT`,
|
|
882
|
+
].join("; "),
|
|
883
|
+
},
|
|
853
884
|
],
|
|
854
885
|
{ table: "clips_migrations" },
|
|
855
886
|
);
|
|
@@ -1,8 +1,10 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
import {
|
|
2
4
|
runWithRequestContext,
|
|
3
5
|
verifyScopedAgentAccessToken,
|
|
4
6
|
} from "@agent-native/core/server";
|
|
5
|
-
import { eq } from "drizzle-orm";
|
|
7
|
+
import { and, eq, isNull, lt, or } from "drizzle-orm";
|
|
6
8
|
import {
|
|
7
9
|
defineEventHandler,
|
|
8
10
|
readBody,
|
|
@@ -14,6 +16,7 @@ import { z } from "zod";
|
|
|
14
16
|
import exportToBrain from "../../../../actions/export-to-brain.js";
|
|
15
17
|
import finalizeRecording from "../../../../actions/finalize-recording.js";
|
|
16
18
|
import { ensureRecordingSeekable } from "../../../../actions/lib/ensure-seekable-video.js";
|
|
19
|
+
import { runLoomImportJob } from "../../../../actions/lib/loom-import-job.js";
|
|
17
20
|
import requestTranscript from "../../../../actions/request-transcript.js";
|
|
18
21
|
import { getDb, schema } from "../../../db/index.js";
|
|
19
22
|
import {
|
|
@@ -24,13 +27,21 @@ import {
|
|
|
24
27
|
|
|
25
28
|
const bodySchema = z.object({
|
|
26
29
|
recordingId: z.string().min(1).max(200),
|
|
27
|
-
kind: z.enum([
|
|
30
|
+
kind: z.enum([
|
|
31
|
+
"media-ready",
|
|
32
|
+
"seekable",
|
|
33
|
+
"transcript",
|
|
34
|
+
"brain-export",
|
|
35
|
+
"loom-import",
|
|
36
|
+
]),
|
|
28
37
|
token: z.string().min(1),
|
|
29
38
|
delayMs: z.number().int().min(0).max(30_000).optional(),
|
|
30
39
|
retryAttempt: z.number().int().min(1).max(10).optional(),
|
|
31
40
|
regenerate: z.boolean().optional(),
|
|
32
41
|
});
|
|
33
42
|
|
|
43
|
+
const LOOM_IMPORT_LEASE_MS = 30 * 60 * 1000;
|
|
44
|
+
|
|
34
45
|
export default defineEventHandler(async (event: H3Event) => {
|
|
35
46
|
const parsed = bodySchema.safeParse(await readBody(event).catch(() => null));
|
|
36
47
|
if (!parsed.success) {
|
|
@@ -63,7 +74,8 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
63
74
|
setResponseStatus(event, 404);
|
|
64
75
|
return { ok: false, error: "Recording not found" };
|
|
65
76
|
}
|
|
66
|
-
const requiredStatus =
|
|
77
|
+
const requiredStatus =
|
|
78
|
+
kind === "media-ready" || kind === "loom-import" ? "processing" : "ready";
|
|
67
79
|
if (recording.status !== requiredStatus) {
|
|
68
80
|
return {
|
|
69
81
|
ok: true,
|
|
@@ -112,6 +124,47 @@ export default defineEventHandler(async (event: H3Event) => {
|
|
|
112
124
|
return { ok: true, kind, result };
|
|
113
125
|
}
|
|
114
126
|
|
|
127
|
+
if (kind === "loom-import") {
|
|
128
|
+
const claimId = randomUUID();
|
|
129
|
+
const claimStartedAt = new Date().toISOString();
|
|
130
|
+
const claimExpiredBefore = new Date(
|
|
131
|
+
Date.now() - LOOM_IMPORT_LEASE_MS,
|
|
132
|
+
).toISOString();
|
|
133
|
+
const [claimed] = await getDb()
|
|
134
|
+
.update(schema.recordings)
|
|
135
|
+
.set({
|
|
136
|
+
loomImportClaimId: claimId,
|
|
137
|
+
loomImportClaimedAt: claimStartedAt,
|
|
138
|
+
})
|
|
139
|
+
.where(
|
|
140
|
+
and(
|
|
141
|
+
eq(schema.recordings.id, recordingId),
|
|
142
|
+
eq(schema.recordings.status, "processing"),
|
|
143
|
+
or(
|
|
144
|
+
isNull(schema.recordings.loomImportClaimId),
|
|
145
|
+
isNull(schema.recordings.loomImportClaimedAt),
|
|
146
|
+
lt(schema.recordings.loomImportClaimedAt, claimExpiredBefore),
|
|
147
|
+
),
|
|
148
|
+
),
|
|
149
|
+
)
|
|
150
|
+
.returning({ id: schema.recordings.id });
|
|
151
|
+
if (!claimed) {
|
|
152
|
+
return {
|
|
153
|
+
ok: true,
|
|
154
|
+
recordingId,
|
|
155
|
+
kind,
|
|
156
|
+
skipped: true,
|
|
157
|
+
reason: "loom-import-already-running",
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
const result = await runLoomImportJob({
|
|
161
|
+
recordingId,
|
|
162
|
+
ownerEmail: recording.ownerEmail,
|
|
163
|
+
claimId,
|
|
164
|
+
});
|
|
165
|
+
return { ok: true, kind, result };
|
|
166
|
+
}
|
|
167
|
+
|
|
115
168
|
if (kind === "brain-export") {
|
|
116
169
|
const result = await exportToBrain.run({
|
|
117
170
|
recordingId,
|
|
@@ -46,8 +46,16 @@ import { isMediaVerificationPending } from "../../lib/media-verification-state.j
|
|
|
46
46
|
import { resolvePlayerThumbnailUrl } from "../../lib/player-thumbnail-url.js";
|
|
47
47
|
import { resolvePlayerVideoUrl } from "../../lib/player-video-url.js";
|
|
48
48
|
import {
|
|
49
|
+
canOpenDirectRecordingPage,
|
|
50
|
+
isRecordingExpired,
|
|
51
|
+
type RecordingPageAccessRole,
|
|
52
|
+
} from "../../lib/recording-page-access.js";
|
|
53
|
+
import { hasExplicitRecordingShare } from "../../lib/recording-share-grant.js";
|
|
54
|
+
import {
|
|
55
|
+
countRecordingViews,
|
|
49
56
|
getOrganizationRoleForEmail,
|
|
50
57
|
parseSpaceIds,
|
|
58
|
+
type RecordingVisibility,
|
|
51
59
|
} from "../../lib/recordings.js";
|
|
52
60
|
import { verifySharePassword } from "../../lib/share-password.js";
|
|
53
61
|
|
|
@@ -258,12 +266,10 @@ export default defineEventHandler(async (event) => {
|
|
|
258
266
|
}
|
|
259
267
|
|
|
260
268
|
// Expiry check
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
return { error: "Recording has expired", expired: true };
|
|
266
|
-
}
|
|
269
|
+
const recordingExpired = isRecordingExpired(rec.expiresAt);
|
|
270
|
+
if (recordingExpired) {
|
|
271
|
+
setResponseStatus(event, 410);
|
|
272
|
+
return { error: "Recording has expired", expired: true };
|
|
267
273
|
}
|
|
268
274
|
|
|
269
275
|
// Password check
|
|
@@ -405,6 +411,32 @@ export default defineEventHandler(async (event) => {
|
|
|
405
411
|
recordingStatus: rec.status,
|
|
406
412
|
});
|
|
407
413
|
|
|
414
|
+
const viewCount = await countRecordingViews(recordingId);
|
|
415
|
+
|
|
416
|
+
const viewerRole =
|
|
417
|
+
viewerAccess?.role ?? (viewerIsOrgMember ? "viewer" : null);
|
|
418
|
+
// Mirrors the gate in `get-recording-player-data` exactly: the share page
|
|
419
|
+
// auto-redirects on this flag, so a false positive bounces the viewer
|
|
420
|
+
// between /share/:id and /r/:id forever. Only a resolved access role can
|
|
421
|
+
// open the direct page — the org-member fallback above is a display role,
|
|
422
|
+
// not access the player action would grant.
|
|
423
|
+
const canOpenDashboard =
|
|
424
|
+
Boolean(session?.email) && viewerAccess && !recordingExpired
|
|
425
|
+
? canOpenDirectRecordingPage({
|
|
426
|
+
role: viewerAccess.role as RecordingPageAccessRole,
|
|
427
|
+
visibility: rec.visibility as RecordingVisibility,
|
|
428
|
+
hasPassword: Boolean(rec.password),
|
|
429
|
+
hasExplicitShare: await hasExplicitRecordingShare({
|
|
430
|
+
recordingId,
|
|
431
|
+
role: viewerAccess.role as RecordingPageAccessRole,
|
|
432
|
+
visibility: rec.visibility as RecordingVisibility,
|
|
433
|
+
hasPassword: Boolean(rec.password),
|
|
434
|
+
userEmail: session?.email ?? null,
|
|
435
|
+
orgId: session?.orgId ?? null,
|
|
436
|
+
}),
|
|
437
|
+
})
|
|
438
|
+
: false;
|
|
439
|
+
|
|
408
440
|
return {
|
|
409
441
|
recording: {
|
|
410
442
|
id: rec.id,
|
|
@@ -439,6 +471,8 @@ export default defineEventHandler(async (event) => {
|
|
|
439
471
|
updatedAt: rec.updatedAt,
|
|
440
472
|
},
|
|
441
473
|
agentContextUrl,
|
|
474
|
+
// Aggregate count only — never viewer identities on this public payload.
|
|
475
|
+
viewCount,
|
|
442
476
|
transcript: transcript
|
|
443
477
|
? {
|
|
444
478
|
status: transcriptPresentation.status,
|
|
@@ -479,17 +513,15 @@ export default defineEventHandler(async (event) => {
|
|
|
479
513
|
placement: c.placement,
|
|
480
514
|
})),
|
|
481
515
|
viewer: session?.email
|
|
482
|
-
?
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
};
|
|
492
|
-
})()
|
|
516
|
+
? {
|
|
517
|
+
canEdit:
|
|
518
|
+
viewerRole === "owner" ||
|
|
519
|
+
viewerRole === "admin" ||
|
|
520
|
+
viewerRole === "editor",
|
|
521
|
+
isOwner: viewerRole === "owner",
|
|
522
|
+
role: viewerRole ?? "viewer",
|
|
523
|
+
canOpenDashboard,
|
|
524
|
+
}
|
|
493
525
|
: null,
|
|
494
526
|
};
|
|
495
527
|
});
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Canonical share URL for a recording, shared by every surface that hands a
|
|
3
|
+
* user a link to paste: the web recorder, the desktop app, and the Chrome
|
|
4
|
+
* extension.
|
|
5
|
+
*
|
|
6
|
+
* `/share/<id>` is the public viewer page — SSR-rendered, password/expiry
|
|
7
|
+
* aware, and the shape Slack unfurls. `/r/<id>` is the owner dashboard: it
|
|
8
|
+
* renders client-side only and shows a sign-in prompt to a recipient. Copying
|
|
9
|
+
* `/r/<id>` produces a link that works for the author and looks broken to
|
|
10
|
+
* everyone they send it to, so share flows must build URLs from here rather
|
|
11
|
+
* than hand-rolling a path.
|
|
12
|
+
*/
|
|
13
|
+
import { withShareAttribution } from "./share-attribution";
|
|
14
|
+
|
|
15
|
+
/** Public share path for a recording, relative to the app base path. */
|
|
16
|
+
export function recordingSharePath(recordingId: string): string {
|
|
17
|
+
return `/share/${encodeURIComponent(recordingId)}`;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export interface RecordingShareUrlParams {
|
|
21
|
+
recordingId: string;
|
|
22
|
+
/** Absolute origin, e.g. `https://clips.example.com` or a desktop serverUrl. */
|
|
23
|
+
origin: string;
|
|
24
|
+
/** App base path when the app is mounted under a subpath. */
|
|
25
|
+
basePath?: string;
|
|
26
|
+
/** Non-PII owner id for viral attribution. Omitted when unknown. */
|
|
27
|
+
ownerId?: string | null;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Absolute, ready-to-paste share URL for a recording, carrying the same
|
|
32
|
+
* attribution params the Share dialog mints so auto-copied links measure the
|
|
33
|
+
* signup funnel identically.
|
|
34
|
+
*/
|
|
35
|
+
export function buildRecordingShareUrl(
|
|
36
|
+
params: RecordingShareUrlParams,
|
|
37
|
+
): string {
|
|
38
|
+
const { recordingId, origin, basePath = "", ownerId } = params;
|
|
39
|
+
const trimmedOrigin = origin.trim().replace(/\/+$/, "");
|
|
40
|
+
const trimmedBase = basePath.trim().replace(/\/+$/, "");
|
|
41
|
+
const url = `${trimmedOrigin}${trimmedBase}${recordingSharePath(recordingId)}`;
|
|
42
|
+
return withShareAttribution(url, ownerId);
|
|
43
|
+
}
|
|
@@ -21,6 +21,10 @@ export const CLIP_SHARE_REF = "clip_share";
|
|
|
21
21
|
export const REF_PARAM = "ref";
|
|
22
22
|
export const VIA_PARAM = "via";
|
|
23
23
|
|
|
24
|
+
/** Marker used only when `/r/:id` falls back to the public share route. */
|
|
25
|
+
export const DASHBOARD_REDIRECT_PARAM = "dashboard_redirect";
|
|
26
|
+
export const DASHBOARD_REDIRECT_VALUE = "1";
|
|
27
|
+
|
|
24
28
|
/**
|
|
25
29
|
* Append `ref=clip_share` (and `via=<ownerId>` when a non-PII owner id is
|
|
26
30
|
* known) to an absolute share/embed URL, preserving any existing query params.
|