@agent-native/core 0.90.11 → 0.91.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/corpus/README.md +2 -2
- package/corpus/core/CHANGELOG.md +22 -0
- package/corpus/core/docs/content/toolkit-settings.mdx +56 -3
- package/corpus/core/package.json +2 -1
- package/corpus/core/src/agent/run-ownership.ts +29 -1
- package/corpus/core/src/changelog/parse.ts +78 -0
- package/corpus/core/src/chat-threads/schema.ts +43 -0
- package/corpus/core/src/chat-threads/store.ts +144 -12
- package/corpus/core/src/cli/workspace-dev.ts +25 -5
- package/corpus/core/src/client/AgentPanel.tsx +79 -14
- package/corpus/core/src/client/AssistantChat.tsx +359 -425
- package/corpus/core/src/client/analytics-session.ts +70 -0
- package/corpus/core/src/client/analytics.ts +175 -49
- package/corpus/core/src/client/chat/message-components.tsx +22 -2
- package/corpus/core/src/client/components/ui/message-scroller.tsx +131 -0
- package/corpus/core/src/client/conversation/AgentConversation.tsx +63 -41
- package/corpus/core/src/client/error-capture.ts +616 -0
- package/corpus/core/src/client/index.ts +12 -0
- package/corpus/core/src/client/session-replay.ts +57 -7
- package/corpus/core/src/client/settings/SettingsPanel.tsx +123 -1
- package/corpus/core/src/client/settings/SettingsTabsPage.tsx +292 -45
- package/corpus/core/src/client/settings/index.ts +1 -0
- package/corpus/core/src/client/sharing/ShareButton.tsx +14 -11
- package/corpus/core/src/client/sharing/ShareDialog.tsx +13 -10
- package/corpus/core/src/mcp/builtin-tools.ts +29 -2
- package/corpus/core/src/server/agent-chat-plugin.ts +174 -79
- package/corpus/core/src/server/onboarding-html.ts +28 -16
- package/corpus/core/src/sharing/actions/list-resource-shares.ts +45 -1
- package/corpus/core/src/sharing/actions/share-resource.ts +9 -0
- package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
- package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
- package/corpus/core/src/vite/client.ts +217 -35
- package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +8 -7
- package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +14 -10
- package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +15 -15
- package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +1 -0
- package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -22
- package/corpus/templates/analytics/.agents/skills/data-programs/SKILL.md +114 -61
- package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +1 -0
- package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +7 -7
- package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +1 -0
- package/corpus/templates/analytics/.agents/skills/prometheus/SKILL.md +1 -1
- package/corpus/templates/analytics/.builder/skills/bigquery/SKILL.md +17 -17
- package/corpus/templates/analytics/.builder/skills/gcloud/SKILL.md +5 -5
- package/corpus/templates/analytics/.builder/skills/github/SKILL.md +7 -7
- package/corpus/templates/analytics/.builder/skills/gong/SKILL.md +12 -12
- package/corpus/templates/analytics/.builder/skills/grafana/SKILL.md +7 -7
- package/corpus/templates/analytics/.builder/skills/sentry/SKILL.md +7 -7
- package/corpus/templates/analytics/.builder/skills/slack/SKILL.md +6 -6
- package/corpus/templates/analytics/.builder/skills/stripe/SKILL.md +7 -7
- package/corpus/templates/analytics/AGENTS.md +47 -2
- package/corpus/templates/analytics/actions/add-status-page-monitor.ts +36 -0
- package/corpus/templates/analytics/actions/capture-test-error.ts +33 -0
- package/corpus/templates/analytics/actions/delete-monitor.ts +24 -0
- package/corpus/templates/analytics/actions/delete-status-page.ts +23 -0
- package/corpus/templates/analytics/actions/get-error-issue.ts +36 -0
- package/corpus/templates/analytics/actions/get-monitor-stats.ts +55 -0
- package/corpus/templates/analytics/actions/get-monitor.ts +27 -0
- package/corpus/templates/analytics/actions/get-public-status-page.ts +41 -0
- package/corpus/templates/analytics/actions/get-status-page.ts +29 -0
- package/corpus/templates/analytics/actions/list-error-issues.ts +40 -0
- package/corpus/templates/analytics/actions/list-monitors.ts +21 -0
- package/corpus/templates/analytics/actions/list-status-pages.ts +21 -0
- package/corpus/templates/analytics/actions/match-error-issues.ts +51 -0
- package/corpus/templates/analytics/actions/navigate.ts +58 -4
- package/corpus/templates/analytics/actions/remove-status-page-monitor.ts +22 -0
- package/corpus/templates/analytics/actions/reorder-status-page-monitors.ts +25 -0
- package/corpus/templates/analytics/actions/resolve-error-issue.ts +38 -0
- package/corpus/templates/analytics/actions/run-monitor-check.ts +23 -0
- package/corpus/templates/analytics/actions/save-analysis.ts +1 -0
- package/corpus/templates/analytics/actions/save-monitor.ts +145 -0
- package/corpus/templates/analytics/actions/save-status-page.ts +47 -0
- package/corpus/templates/analytics/actions/view-screen.ts +212 -0
- package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
- package/corpus/templates/analytics/app/components/layout/Layout.tsx +10 -1
- package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +134 -39
- package/corpus/templates/analytics/app/components/monitoring/PublicStatusView.tsx +351 -0
- package/corpus/templates/analytics/app/components/monitoring/ResponseTimeChart.tsx +174 -0
- package/corpus/templates/analytics/app/components/monitoring/UptimeStatCards.tsx +85 -0
- package/corpus/templates/analytics/app/components/monitoring/UptimeTimelineBars.tsx +106 -0
- package/corpus/templates/analytics/app/components/monitoring/chart-utils.ts +101 -0
- package/corpus/templates/analytics/app/components/monitoring/index.ts +28 -0
- package/corpus/templates/analytics/app/components/monitoring/types.ts +42 -0
- package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +34 -0
- package/corpus/templates/analytics/app/i18n/zh-TW.ts +6 -0
- package/corpus/templates/analytics/app/i18n-data.ts +33 -0
- package/corpus/templates/analytics/app/pages/DataSources.tsx +28 -0
- package/corpus/templates/analytics/app/pages/Settings.tsx +77 -9
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -3
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +7 -0
- package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +205 -0
- package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +100 -0
- package/corpus/templates/analytics/app/pages/monitoring/UptimePanel.tsx +475 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +372 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +360 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/Sparkline.tsx +55 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +106 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/types.ts +75 -0
- package/corpus/templates/analytics/app/pages/monitoring/errors/utils.ts +132 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorDetail.tsx +535 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +1026 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorList.tsx +311 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +235 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/StatusPageEditor.tsx +661 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/StatusPagesView.tsx +355 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/i18n.ts +106 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/slug.ts +24 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/types.ts +20 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/status-summary.ts +87 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +161 -0
- package/corpus/templates/analytics/app/pages/monitoring/uptime/utils.ts +210 -0
- package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +76 -1
- package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +74 -9
- package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +197 -20
- package/corpus/templates/analytics/app/root.tsx +33 -1
- package/corpus/templates/analytics/app/routes/monitoring._index.tsx +10 -0
- package/corpus/templates/analytics/app/routes/status.$slug.tsx +118 -0
- package/corpus/templates/analytics/changelog/2026-07-08-add-uptime-monitors-that-ping-your-urls-and-alert-you-when-t.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-analytics-chats-keep-large-data-dictionaries-compact-so-prov.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-create-public-status-pages-that-share-the-live-health-of-cho.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-dashboard-extension-panels-use-clearer-open-copy.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-error-capture.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-fixed-single-panel-dashboard-drags-so-they-no-longer-rewrite.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-jump-straight-from-an-error-in-a-session-recording-to-its-fu.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-monitor-detail-and-list-now-show-colorful-uptime-timelines-r.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-pinned-ask-chats-now-show-a-pin-icon-in-the-sidebar.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-publish-a-public-uptime-status-page-with-colorful-uptime-tim.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-sessions-now-hide-visitor-emails-in-demo-mode-and-show-clean.md +6 -0
- package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +5 -0
- package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +5 -0
- package/corpus/templates/analytics/changelog/2026-07-08-the-uptime-monitor-list-now-has-a-current-status-overview-wi.md +6 -0
- package/corpus/templates/analytics/docs/error-capture.md +172 -0
- package/corpus/templates/analytics/docs/uptime-monitoring.md +150 -0
- package/corpus/templates/analytics/server/db/schema-errors.ts +150 -0
- package/corpus/templates/analytics/server/db/schema-monitoring.ts +177 -0
- package/corpus/templates/analytics/server/db/schema.ts +6 -0
- package/corpus/templates/analytics/server/jobs/uptime-monitors.ts +123 -0
- package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +41 -0
- package/corpus/templates/analytics/server/lib/data-dictionary-context.ts +55 -12
- package/corpus/templates/analytics/server/lib/error-capture.ts +1331 -0
- package/corpus/templates/analytics/server/lib/first-party-analytics.ts +47 -1
- package/corpus/templates/analytics/server/lib/monitor-stats.ts +494 -0
- package/corpus/templates/analytics/server/lib/status-pages.ts +798 -0
- package/corpus/templates/analytics/server/lib/uptime-monitors.ts +1665 -0
- package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -37
- package/corpus/templates/analytics/server/plugins/auth.ts +11 -1
- package/corpus/templates/analytics/server/plugins/db.ts +408 -0
- package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +63 -0
- package/corpus/templates/assets/app/routes/_index.tsx +1 -1
- package/corpus/templates/assets/app/routes/settings.tsx +22 -1
- package/corpus/templates/assets/changelog/2026-07-08-recent-drafts-keep-comfortable-side-padding.md +6 -0
- package/corpus/templates/assets/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/brain/app/routes/settings.tsx +43 -5
- package/corpus/templates/brain/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/calendar/app/pages/Settings.tsx +44 -6
- package/corpus/templates/calendar/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/chat/app/routes/settings.tsx +16 -1
- package/corpus/templates/chat/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +79 -37
- package/corpus/templates/clips/app/components/player/player-controls.tsx +35 -0
- package/corpus/templates/clips/app/components/player/video-player.tsx +162 -18
- package/corpus/templates/clips/app/routes/_app.settings._index.tsx +63 -6
- package/corpus/templates/clips/app/routes/record.tsx +18 -1
- package/corpus/templates/clips/changelog/2026-07-07-shared-clips-start-playback-reliably-from-the-first-play-click.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-camera-and-microphone-menus-now-show-which-.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-copies-new-clip-links-after-saving.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-recordings-now-continue-after-granting-came.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-mobile-shared-clips-now-have-loom-style-tap-and-skip-controls.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-recordings-and-uploads-copy-links-after-saving.md +6 -0
- package/corpus/templates/clips/changelog/2026-07-08-settings-are-cleaner-searchable-and-collapse-s3-with-builder.md +5 -0
- package/corpus/templates/clips/changelog/2026-07-08-transcripts-are-more-reliable-for-longer-desktop-recordings-.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +162 -13
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +14 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +36 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +99 -25
- package/corpus/templates/clips/chrome-extension/src/permission.ts +63 -14
- package/corpus/templates/clips/chrome-extension/src/popup.ts +123 -23
- package/corpus/templates/content/actions/_builder-cms-read-client.ts +47 -0
- package/corpus/templates/content/actions/_database-source-utils.ts +33 -4
- package/corpus/templates/content/actions/add-content-database-source-field-property.ts +214 -29
- package/corpus/templates/content/actions/attach-content-database-source.ts +3 -0
- package/corpus/templates/content/actions/change-content-database-source-role.ts +1 -0
- package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +42 -32
- package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +352 -81
- package/corpus/templates/content/app/components/editor/database/settings.tsx +49 -24
- package/corpus/templates/content/app/routes/_app.settings.tsx +19 -1
- package/corpus/templates/content/changelog/2026-07-07-builder-source-tag-fields-now-import-as-multi-select.md +6 -0
- package/corpus/templates/content/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/content/shared/api.ts +3 -0
- package/corpus/templates/content/shared/properties.ts +42 -0
- package/corpus/templates/design/actions/generate-design.ts +3 -0
- package/corpus/templates/design/actions/get-design-system.ts +147 -1
- package/corpus/templates/design/app/routes/settings.tsx +16 -1
- package/corpus/templates/design/changelog/2026-07-08-design-generation-now-follows-hydrated-builder-design-system.md +6 -0
- package/corpus/templates/design/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/dispatch/app/routes/settings.tsx +23 -2
- package/corpus/templates/dispatch/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/forms/app/routes/_app.settings.tsx +16 -1
- package/corpus/templates/forms/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/macros/app/routes/settings.tsx +16 -1
- package/corpus/templates/macros/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/mail/app/components/settings/GmailFiltersSection.tsx +1 -1
- package/corpus/templates/mail/app/components/settings/SnippetsSection.tsx +1 -1
- package/corpus/templates/mail/app/pages/SettingsPage.tsx +123 -105
- package/corpus/templates/mail/changelog/2026-07-08-settings-redesigned-with-a-consistent-searchable-layout.md +5 -0
- package/corpus/templates/plan/app/routes/settings.tsx +23 -2
- package/corpus/templates/plan/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/corpus/templates/slides/actions/add-slide.ts +1 -0
- package/corpus/templates/slides/actions/get-design-system.ts +147 -1
- package/corpus/templates/slides/app/pages/Index.tsx +46 -1
- package/corpus/templates/slides/app/routes/settings.tsx +16 -1
- package/corpus/templates/slides/changelog/2026-07-08-deck-generation-now-follows-hydrated-builder-design-system-g.md +6 -0
- package/corpus/templates/slides/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
- package/dist/agent/run-ownership.d.ts +6 -0
- package/dist/agent/run-ownership.d.ts.map +1 -1
- package/dist/agent/run-ownership.js +11 -1
- package/dist/agent/run-ownership.js.map +1 -1
- package/dist/changelog/parse.d.ts +7 -0
- package/dist/changelog/parse.d.ts.map +1 -1
- package/dist/changelog/parse.js +64 -0
- package/dist/changelog/parse.js.map +1 -1
- package/dist/chat-threads/schema.d.ts +445 -0
- package/dist/chat-threads/schema.d.ts.map +1 -0
- package/dist/chat-threads/schema.js +39 -0
- package/dist/chat-threads/schema.js.map +1 -0
- package/dist/chat-threads/store.d.ts +12 -0
- package/dist/chat-threads/store.d.ts.map +1 -1
- package/dist/chat-threads/store.js +99 -10
- package/dist/chat-threads/store.js.map +1 -1
- package/dist/cli/workspace-dev.d.ts.map +1 -1
- package/dist/cli/workspace-dev.js +20 -5
- package/dist/cli/workspace-dev.js.map +1 -1
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +35 -7
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/AssistantChat.d.ts.map +1 -1
- package/dist/client/AssistantChat.js +105 -183
- package/dist/client/AssistantChat.js.map +1 -1
- package/dist/client/analytics-session.d.ts +3 -0
- package/dist/client/analytics-session.d.ts.map +1 -0
- package/dist/client/analytics-session.js +66 -0
- package/dist/client/analytics-session.js.map +1 -0
- package/dist/client/analytics.d.ts +32 -0
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +116 -46
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/chat/message-components.d.ts +2 -1
- package/dist/client/chat/message-components.d.ts.map +1 -1
- package/dist/client/chat/message-components.js +16 -6
- package/dist/client/chat/message-components.js.map +1 -1
- package/dist/client/components/ui/message-scroller.d.ts +16 -0
- package/dist/client/components/ui/message-scroller.d.ts.map +1 -0
- package/dist/client/components/ui/message-scroller.js +25 -0
- package/dist/client/components/ui/message-scroller.js.map +1 -0
- package/dist/client/conversation/AgentConversation.d.ts +1 -1
- package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
- package/dist/client/conversation/AgentConversation.js +3 -8
- package/dist/client/conversation/AgentConversation.js.map +1 -1
- package/dist/client/error-capture.d.ts +99 -0
- package/dist/client/error-capture.d.ts.map +1 -0
- package/dist/client/error-capture.js +437 -0
- package/dist/client/error-capture.js.map +1 -0
- package/dist/client/index.d.ts +2 -2
- package/dist/client/index.d.ts.map +1 -1
- package/dist/client/index.js +3 -1
- package/dist/client/index.js.map +1 -1
- package/dist/client/session-replay.d.ts +22 -0
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +45 -4
- package/dist/client/session-replay.js.map +1 -1
- package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
- package/dist/client/settings/SettingsPanel.js +100 -5
- package/dist/client/settings/SettingsPanel.js.map +1 -1
- package/dist/client/settings/SettingsTabsPage.d.ts +49 -1
- package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
- package/dist/client/settings/SettingsTabsPage.js +131 -19
- package/dist/client/settings/SettingsTabsPage.js.map +1 -1
- package/dist/client/settings/index.d.ts +1 -1
- package/dist/client/settings/index.d.ts.map +1 -1
- package/dist/client/settings/index.js.map +1 -1
- package/dist/client/sharing/ShareButton.d.ts.map +1 -1
- package/dist/client/sharing/ShareButton.js +13 -8
- package/dist/client/sharing/ShareButton.js.map +1 -1
- package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
- package/dist/client/sharing/ShareDialog.js +12 -7
- package/dist/client/sharing/ShareDialog.js.map +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/mcp/builtin-tools.d.ts.map +1 -1
- package/dist/mcp/builtin-tools.js +21 -2
- package/dist/mcp/builtin-tools.js.map +1 -1
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/org/schema.d.ts +15 -15
- package/dist/provider-api/corpus-jobs.d.ts +2 -2
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +109 -73
- 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/onboarding-html.d.ts.map +1 -1
- package/dist/server/onboarding-html.js +28 -16
- package/dist/server/onboarding-html.js.map +1 -1
- package/dist/sharing/actions/list-resource-shares.js +30 -1
- package/dist/sharing/actions/list-resource-shares.js.map +1 -1
- package/dist/sharing/actions/share-resource.js +6 -0
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
- package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +192 -30
- package/dist/vite/client.js.map +1 -1
- package/docs/content/toolkit-settings.mdx +56 -3
- package/package.json +2 -1
- package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
- package/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
|
@@ -55,7 +55,12 @@ type PopupMessage =
|
|
|
55
55
|
| { type: "CLIPS_POPUP_STOP" }
|
|
56
56
|
| { type: "CLIPS_POPUP_CANCEL" }
|
|
57
57
|
| { type: "CLIPS_POPUP_OPEN" }
|
|
58
|
-
| { type: "CLIPS_POPUP_SIGN_IN"; settings?: Partial<ExtensionSettings> }
|
|
58
|
+
| { type: "CLIPS_POPUP_SIGN_IN"; settings?: Partial<ExtensionSettings> }
|
|
59
|
+
| {
|
|
60
|
+
type: "CLIPS_POPUP_PREPARE_PERMISSION_START";
|
|
61
|
+
settings?: Partial<ExtensionSettings>;
|
|
62
|
+
targetTabId?: number;
|
|
63
|
+
};
|
|
59
64
|
|
|
60
65
|
type ExternalMessage =
|
|
61
66
|
| {
|
|
@@ -82,6 +87,7 @@ type ExternalMessage =
|
|
|
82
87
|
|
|
83
88
|
type ChromeTab = {
|
|
84
89
|
id?: number;
|
|
90
|
+
windowId?: number;
|
|
85
91
|
title?: string;
|
|
86
92
|
url?: string;
|
|
87
93
|
};
|
|
@@ -312,6 +318,7 @@ function overlayStateForBroadcast(): {
|
|
|
312
318
|
// the overlay can't be injected) the recorder must still start. Without this,
|
|
313
319
|
// recording would silently never begin on those pages.
|
|
314
320
|
const COUNTDOWN_SECONDS = 3;
|
|
321
|
+
const PENDING_PERMISSION_START_TTL_MS = 5 * 60 * 1000;
|
|
315
322
|
|
|
316
323
|
function clearCountdownTimer(): void {
|
|
317
324
|
countdownEndsAtMs = 0;
|
|
@@ -861,6 +868,33 @@ function queryActiveTab(): Promise<ChromeTab | null> {
|
|
|
861
868
|
});
|
|
862
869
|
}
|
|
863
870
|
|
|
871
|
+
function getTab(tabId: number): Promise<ChromeTab | null> {
|
|
872
|
+
return new Promise((resolve) => {
|
|
873
|
+
chrome.tabs.get(tabId, (tab) => {
|
|
874
|
+
const error = chromeLastError();
|
|
875
|
+
resolve(error ? null : ((tab as ChromeTab | undefined) ?? null));
|
|
876
|
+
});
|
|
877
|
+
});
|
|
878
|
+
}
|
|
879
|
+
|
|
880
|
+
async function activateTab(tab: ChromeTab): Promise<void> {
|
|
881
|
+
if (typeof tab.id !== "number") return;
|
|
882
|
+
await new Promise<void>((resolve) => {
|
|
883
|
+
chrome.tabs.update(tab.id as number, { active: true }, () => {
|
|
884
|
+
void chrome.runtime.lastError;
|
|
885
|
+
resolve();
|
|
886
|
+
});
|
|
887
|
+
});
|
|
888
|
+
if (typeof tab.windowId === "number") {
|
|
889
|
+
await new Promise<void>((resolve) => {
|
|
890
|
+
chrome.windows.update(tab.windowId as number, { focused: true }, () => {
|
|
891
|
+
void chrome.runtime.lastError;
|
|
892
|
+
resolve();
|
|
893
|
+
});
|
|
894
|
+
});
|
|
895
|
+
}
|
|
896
|
+
}
|
|
897
|
+
|
|
864
898
|
function createTab(url: string): Promise<void> {
|
|
865
899
|
return new Promise((resolve, reject) => {
|
|
866
900
|
chrome.tabs.create({ url }, () => {
|
|
@@ -871,6 +905,44 @@ function createTab(url: string): Promise<void> {
|
|
|
871
905
|
});
|
|
872
906
|
}
|
|
873
907
|
|
|
908
|
+
type PendingPermissionStart = {
|
|
909
|
+
settings: ExtensionSettings;
|
|
910
|
+
targetTabId: number;
|
|
911
|
+
createdAtMs: number;
|
|
912
|
+
};
|
|
913
|
+
|
|
914
|
+
async function writePendingPermissionStart(
|
|
915
|
+
pending: PendingPermissionStart,
|
|
916
|
+
): Promise<void> {
|
|
917
|
+
await sessionStorageSet({ pendingPermissionStart: pending }).catch(
|
|
918
|
+
() => undefined,
|
|
919
|
+
);
|
|
920
|
+
}
|
|
921
|
+
|
|
922
|
+
async function clearPendingPermissionStart(): Promise<void> {
|
|
923
|
+
await sessionStorageRemove("pendingPermissionStart").catch(() => undefined);
|
|
924
|
+
}
|
|
925
|
+
|
|
926
|
+
async function readPendingPermissionStart(): Promise<PendingPermissionStart | null> {
|
|
927
|
+
const raw = (await sessionStorageGet(["pendingPermissionStart"]))
|
|
928
|
+
.pendingPermissionStart;
|
|
929
|
+
if (!raw || typeof raw !== "object") return null;
|
|
930
|
+
const pending = raw as Partial<PendingPermissionStart>;
|
|
931
|
+
if (
|
|
932
|
+
!pending.settings ||
|
|
933
|
+
typeof pending.targetTabId !== "number" ||
|
|
934
|
+
typeof pending.createdAtMs !== "number"
|
|
935
|
+
) {
|
|
936
|
+
await clearPendingPermissionStart();
|
|
937
|
+
return null;
|
|
938
|
+
}
|
|
939
|
+
if (Date.now() - pending.createdAtMs > PENDING_PERMISSION_START_TTL_MS) {
|
|
940
|
+
await clearPendingPermissionStart();
|
|
941
|
+
return null;
|
|
942
|
+
}
|
|
943
|
+
return pending as PendingPermissionStart;
|
|
944
|
+
}
|
|
945
|
+
|
|
874
946
|
function debuggerAttach(tabId: number): Promise<void> {
|
|
875
947
|
return new Promise((resolve, reject) => {
|
|
876
948
|
chrome.debugger.attach({ tabId }, DEBUGGER_PROTOCOL_VERSION, () => {
|
|
@@ -974,9 +1046,9 @@ async function ensureOffscreenDocument(): Promise<void> {
|
|
|
974
1046
|
|
|
975
1047
|
await chrome.offscreen.createDocument({
|
|
976
1048
|
url: path,
|
|
977
|
-
reasons: ["USER_MEDIA"],
|
|
1049
|
+
reasons: ["USER_MEDIA", "CLIPBOARD"],
|
|
978
1050
|
justification:
|
|
979
|
-
"Record tab, camera, and microphone streams after the user starts Clips.",
|
|
1051
|
+
"Record tab, camera, and microphone streams after the user starts Clips, then copy the finished clip link.",
|
|
980
1052
|
});
|
|
981
1053
|
}
|
|
982
1054
|
|
|
@@ -1003,6 +1075,24 @@ function recordingUrl(
|
|
|
1003
1075
|
return `${recording.clipsBaseUrl}/r/${encodeURIComponent(recording.recordingId)}`;
|
|
1004
1076
|
}
|
|
1005
1077
|
|
|
1078
|
+
async function copyRecordingUrlToClipboard(
|
|
1079
|
+
recording: NativeRecording,
|
|
1080
|
+
): Promise<void> {
|
|
1081
|
+
try {
|
|
1082
|
+
await ensureOffscreenDocument();
|
|
1083
|
+
await sendOffscreenMessage({
|
|
1084
|
+
type: "CLIPS_OFFSCREEN_COPY_TEXT",
|
|
1085
|
+
text: recording.recordingUrl,
|
|
1086
|
+
});
|
|
1087
|
+
} catch (err) {
|
|
1088
|
+
console.warn("[clips-bg] could not copy recording URL", err);
|
|
1089
|
+
captureExtensionError(err, {
|
|
1090
|
+
tags: { surface: "background", recordingStep: "copy-recording-url" },
|
|
1091
|
+
extra: { recordingId: recording.recordingId },
|
|
1092
|
+
});
|
|
1093
|
+
}
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1006
1096
|
function settingsFromRecording(recording: NativeRecording): ExtensionSettings {
|
|
1007
1097
|
return {
|
|
1008
1098
|
clipsBaseUrl: recording.clipsBaseUrl,
|
|
@@ -1049,6 +1139,19 @@ function createSession(
|
|
|
1049
1139
|
}
|
|
1050
1140
|
|
|
1051
1141
|
async function handlePopupStart(message: PopupStartMessage) {
|
|
1142
|
+
const tab = await queryActiveTab();
|
|
1143
|
+
if (!tab || typeof tab.id !== "number") {
|
|
1144
|
+
return { ok: false, error: "No active tab is available to record." };
|
|
1145
|
+
}
|
|
1146
|
+
const settings = await readSettings(message.settings);
|
|
1147
|
+
return startRecordingFromTab({ tab, settings });
|
|
1148
|
+
}
|
|
1149
|
+
|
|
1150
|
+
async function startRecordingFromTab(args: {
|
|
1151
|
+
tab: ChromeTab;
|
|
1152
|
+
settings: ExtensionSettings;
|
|
1153
|
+
}) {
|
|
1154
|
+
const { tab, settings } = args;
|
|
1052
1155
|
// The persisted value is authoritative (survives a worker suspension during
|
|
1053
1156
|
// arming); the in-memory var is only a same-tick fast path on top of it.
|
|
1054
1157
|
// Stale (past-TTL) or legacy-shaped guards are treated as absent — see
|
|
@@ -1067,18 +1170,14 @@ async function handlePopupStart(message: PopupStartMessage) {
|
|
|
1067
1170
|
error: "Clips is already recording. Stop the active clip first.",
|
|
1068
1171
|
};
|
|
1069
1172
|
}
|
|
1173
|
+
if (typeof tab.id !== "number") {
|
|
1174
|
+
return { ok: false, error: "No active tab is available to record." };
|
|
1175
|
+
}
|
|
1070
1176
|
|
|
1071
1177
|
const sessionId = crypto.randomUUID();
|
|
1072
1178
|
await setArmingGuard(sessionId);
|
|
1073
1179
|
try {
|
|
1074
|
-
const tab = await queryActiveTab();
|
|
1075
|
-
if (!tab || typeof tab.id !== "number") {
|
|
1076
|
-
return { ok: false, error: "No active tab is available to record." };
|
|
1077
|
-
}
|
|
1078
|
-
|
|
1079
|
-
const settings = await readSettings(message.settings);
|
|
1080
1180
|
await storageSet(settings);
|
|
1081
|
-
|
|
1082
1181
|
return await armRecording({ sessionId, tab, settings });
|
|
1083
1182
|
} finally {
|
|
1084
1183
|
if (armingNativeRecordingSessionId === sessionId) {
|
|
@@ -1087,6 +1186,43 @@ async function handlePopupStart(message: PopupStartMessage) {
|
|
|
1087
1186
|
}
|
|
1088
1187
|
}
|
|
1089
1188
|
|
|
1189
|
+
async function handlePopupPreparePermissionStart(message: {
|
|
1190
|
+
settings?: Partial<ExtensionSettings>;
|
|
1191
|
+
targetTabId?: number;
|
|
1192
|
+
}) {
|
|
1193
|
+
const settings = await readSettings(message.settings);
|
|
1194
|
+
const tab =
|
|
1195
|
+
typeof message.targetTabId === "number"
|
|
1196
|
+
? await getTab(message.targetTabId)
|
|
1197
|
+
: await queryActiveTab();
|
|
1198
|
+
if (!tab || typeof tab.id !== "number") {
|
|
1199
|
+
return { ok: false, error: "No active tab is available to record." };
|
|
1200
|
+
}
|
|
1201
|
+
await writePendingPermissionStart({
|
|
1202
|
+
settings,
|
|
1203
|
+
targetTabId: tab.id,
|
|
1204
|
+
createdAtMs: Date.now(),
|
|
1205
|
+
});
|
|
1206
|
+
return { ok: true };
|
|
1207
|
+
}
|
|
1208
|
+
|
|
1209
|
+
async function handlePermissionStartAfterGrant() {
|
|
1210
|
+
const pending = await readPendingPermissionStart();
|
|
1211
|
+
if (!pending) {
|
|
1212
|
+
return {
|
|
1213
|
+
ok: false,
|
|
1214
|
+
error: "Start the recording again from the Clips icon.",
|
|
1215
|
+
};
|
|
1216
|
+
}
|
|
1217
|
+
await clearPendingPermissionStart();
|
|
1218
|
+
const tab = await getTab(pending.targetTabId);
|
|
1219
|
+
if (!tab || typeof tab.id !== "number") {
|
|
1220
|
+
return { ok: false, error: "The original tab is no longer available." };
|
|
1221
|
+
}
|
|
1222
|
+
await activateTab(tab);
|
|
1223
|
+
return startRecordingFromTab({ tab, settings: pending.settings });
|
|
1224
|
+
}
|
|
1225
|
+
|
|
1090
1226
|
// Arm a Loom-style in-page recording: show the native picker, create the row,
|
|
1091
1227
|
// then hand the recorder its pre-roll delay. The MediaRecorder starts inside the
|
|
1092
1228
|
// offscreen document after that delay, which reports "recording" back here
|
|
@@ -1390,6 +1526,10 @@ async function handleOverlayRestart() {
|
|
|
1390
1526
|
overlayBaseElapsedMs = 0;
|
|
1391
1527
|
overlayBaseEpochMs = nowMs();
|
|
1392
1528
|
countdownEndsAtMs = nowMs() + COUNTDOWN_SECONDS * 1000;
|
|
1529
|
+
const cameraInvolved =
|
|
1530
|
+
recording.captureSurface === "camera" || recording.includeCamera;
|
|
1531
|
+
overlayShowsBubble = cameraInvolved;
|
|
1532
|
+
recordingShowsBubble = cameraInvolved;
|
|
1393
1533
|
recording.status = "recording";
|
|
1394
1534
|
const restartAuthToken = (
|
|
1395
1535
|
await readAuthSession(settingsFromRecording(recording))
|
|
@@ -1403,9 +1543,8 @@ async function handleOverlayRestart() {
|
|
|
1403
1543
|
recordingId: recording.recordingId,
|
|
1404
1544
|
uploadUrl: recording.uploadUrl,
|
|
1405
1545
|
uploadMode: recording.uploadMode ?? "buffered",
|
|
1406
|
-
hasCamera:
|
|
1407
|
-
|
|
1408
|
-
startDelayMs: COUNTDOWN_SECONDS * 1000,
|
|
1546
|
+
hasCamera: cameraInvolved,
|
|
1547
|
+
startDelayMs: cameraInvolved ? 20000 : COUNTDOWN_SECONDS * 1000 + 1000,
|
|
1409
1548
|
authToken: restartAuthToken,
|
|
1410
1549
|
});
|
|
1411
1550
|
} catch (err) {
|
|
@@ -1460,6 +1599,7 @@ async function finishSaving(
|
|
|
1460
1599
|
await deleteSession(recording.sessionId);
|
|
1461
1600
|
await broadcastUnmount();
|
|
1462
1601
|
broadcastOverlayState();
|
|
1602
|
+
await copyRecordingUrlToClipboard(recording);
|
|
1463
1603
|
await clearNativeRecording();
|
|
1464
1604
|
await createTab(recording.recordingUrl);
|
|
1465
1605
|
return true;
|
|
@@ -2288,6 +2428,15 @@ async function dispatchRuntimeMessage(message: unknown): Promise<unknown> {
|
|
|
2288
2428
|
return handlePopupSignIn(
|
|
2289
2429
|
message as { settings?: Partial<ExtensionSettings> },
|
|
2290
2430
|
);
|
|
2431
|
+
case "CLIPS_POPUP_PREPARE_PERMISSION_START":
|
|
2432
|
+
return handlePopupPreparePermissionStart(
|
|
2433
|
+
message as {
|
|
2434
|
+
settings?: Partial<ExtensionSettings>;
|
|
2435
|
+
targetTabId?: number;
|
|
2436
|
+
},
|
|
2437
|
+
);
|
|
2438
|
+
case "CLIPS_PERMISSION_START_AFTER_GRANT":
|
|
2439
|
+
return handlePermissionStartAfterGrant();
|
|
2291
2440
|
case "CLIPS_OVERLAY_COUNTDOWN_DONE":
|
|
2292
2441
|
// Skip button on the countdown overlay: start the recorder now.
|
|
2293
2442
|
return handleOverlaySkip();
|
|
@@ -350,6 +350,7 @@
|
|
|
350
350
|
let cameraReady = false;
|
|
351
351
|
let countdownDeferred = false;
|
|
352
352
|
let countdownFallbackTimer: ReturnType<typeof setTimeout> | undefined;
|
|
353
|
+
let lastWantedParts = new Set<OverlayPart>();
|
|
353
354
|
|
|
354
355
|
function showConnecting(container: HTMLDivElement): void {
|
|
355
356
|
if (document.getElementById(CONNECTING_ID)) return;
|
|
@@ -430,6 +431,10 @@
|
|
|
430
431
|
function reconcile(parts: OverlayPart[]): void {
|
|
431
432
|
console.log("[clips-cs] reconcile parts:", parts, "on", location.href);
|
|
432
433
|
const wanted = new Set(parts.filter((p) => ALL_PARTS.includes(p)));
|
|
434
|
+
const enteringCameraCountdown =
|
|
435
|
+
wanted.has("countdown") &&
|
|
436
|
+
wanted.has("bubble") &&
|
|
437
|
+
!lastWantedParts.has("countdown");
|
|
433
438
|
// Leaving the countdown phase (recording / saving / idle): cancel any pending
|
|
434
439
|
// deferred countdown + spinner so a late fallback timer can't pop the "3-2-1"
|
|
435
440
|
// back up over a later overlay (this was the "countdown over the saving card"
|
|
@@ -438,13 +443,21 @@
|
|
|
438
443
|
countdownDeferred = false;
|
|
439
444
|
clearTimeout(countdownFallbackTimer);
|
|
440
445
|
hideConnecting();
|
|
446
|
+
setBubbleHidden(false);
|
|
441
447
|
}
|
|
442
448
|
if (wanted.size === 0) {
|
|
443
449
|
document.getElementById(CONTAINER_ID)?.remove();
|
|
444
450
|
cameraReady = false;
|
|
451
|
+
lastWantedParts = wanted;
|
|
445
452
|
return;
|
|
446
453
|
}
|
|
447
454
|
const container = ensureContainer();
|
|
455
|
+
if (enteringCameraCountdown) {
|
|
456
|
+
cameraReady = false;
|
|
457
|
+
hideConnecting();
|
|
458
|
+
setBubbleHidden(false);
|
|
459
|
+
document.getElementById(partFrameId("bubble"))?.remove();
|
|
460
|
+
}
|
|
448
461
|
const gateCountdown =
|
|
449
462
|
wanted.has("countdown") && wanted.has("bubble") && !cameraReady;
|
|
450
463
|
for (const part of ALL_PARTS) {
|
|
@@ -467,6 +480,7 @@
|
|
|
467
480
|
}
|
|
468
481
|
}
|
|
469
482
|
if (gateCountdown) showConnecting(container);
|
|
483
|
+
lastWantedParts = wanted;
|
|
470
484
|
}
|
|
471
485
|
|
|
472
486
|
// Guard against rare double-injection (SPA soft-reloads re-running the script).
|
|
@@ -60,6 +60,34 @@ function waitForRetry(ms: number): Promise<void> {
|
|
|
60
60
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
async function copyTextToClipboard(
|
|
64
|
+
message: CopyTextMessage,
|
|
65
|
+
): Promise<{ ok: boolean }> {
|
|
66
|
+
const text = message.text.trim();
|
|
67
|
+
if (!text) throw new Error("Missing text to copy.");
|
|
68
|
+
|
|
69
|
+
if (navigator.clipboard?.writeText) {
|
|
70
|
+
await navigator.clipboard.writeText(text);
|
|
71
|
+
return { ok: true };
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
const textarea = document.createElement("textarea");
|
|
75
|
+
textarea.value = text;
|
|
76
|
+
textarea.style.position = "fixed";
|
|
77
|
+
textarea.style.opacity = "0";
|
|
78
|
+
document.body.appendChild(textarea);
|
|
79
|
+
textarea.focus();
|
|
80
|
+
textarea.select();
|
|
81
|
+
try {
|
|
82
|
+
if (!document.execCommand("copy")) {
|
|
83
|
+
throw new Error("Clipboard copy was rejected.");
|
|
84
|
+
}
|
|
85
|
+
return { ok: true };
|
|
86
|
+
} finally {
|
|
87
|
+
textarea.remove();
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
63
91
|
function isFinalUploadRecoveryCandidate(error: Error): boolean {
|
|
64
92
|
const tagged = error as {
|
|
65
93
|
finalUploadRecoveryAttempted?: boolean;
|
|
@@ -114,6 +142,11 @@ type SimpleMessage = {
|
|
|
114
142
|
sessionId: string;
|
|
115
143
|
};
|
|
116
144
|
|
|
145
|
+
type CopyTextMessage = {
|
|
146
|
+
type: "CLIPS_OFFSCREEN_COPY_TEXT";
|
|
147
|
+
text: string;
|
|
148
|
+
};
|
|
149
|
+
|
|
117
150
|
type StatusName = "recording" | "paused" | "uploading" | "complete" | "error";
|
|
118
151
|
|
|
119
152
|
type UploadResult = {
|
|
@@ -1679,6 +1712,9 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
|
1679
1712
|
case "CLIPS_OFFSCREEN_START_NOW":
|
|
1680
1713
|
task = Promise.resolve(startNow(message as SimpleMessage));
|
|
1681
1714
|
break;
|
|
1715
|
+
case "CLIPS_OFFSCREEN_COPY_TEXT":
|
|
1716
|
+
task = copyTextToClipboard(message as CopyTextMessage);
|
|
1717
|
+
break;
|
|
1682
1718
|
default:
|
|
1683
1719
|
return false;
|
|
1684
1720
|
}
|
|
@@ -201,40 +201,114 @@ async function initBubble(): Promise<void> {
|
|
|
201
201
|
|
|
202
202
|
root.appendChild(bubble);
|
|
203
203
|
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
const stream = await getCameraBubbleStream(videoDeviceId);
|
|
215
|
-
const video = document.createElement("video");
|
|
216
|
-
video.muted = true;
|
|
217
|
-
video.autoplay = true;
|
|
218
|
-
video.playsInline = true;
|
|
219
|
-
video.srcObject = stream;
|
|
220
|
-
ring.appendChild(video);
|
|
221
|
-
await video.play().catch(() => undefined);
|
|
222
|
-
console.log("[clips-overlay] camera bubble live");
|
|
204
|
+
let currentStream: MediaStream | null = null;
|
|
205
|
+
let currentVideo: HTMLVideoElement | null = null;
|
|
206
|
+
let healthTimer: number | undefined;
|
|
207
|
+
let reconnectTimer: number | undefined;
|
|
208
|
+
let unhealthyTicks = 0;
|
|
209
|
+
let readyPosted = false;
|
|
210
|
+
|
|
211
|
+
const postReadyOnce = (): void => {
|
|
212
|
+
if (readyPosted) return;
|
|
213
|
+
readyPosted = true;
|
|
223
214
|
// Tell the host the feed is live so it can start the countdown — the "3"
|
|
224
215
|
// shouldn't appear until the camera is actually showing.
|
|
225
216
|
postBubble("camera-ready");
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
217
|
+
};
|
|
218
|
+
|
|
219
|
+
const videoDeviceId = await new Promise<string>((resolve) => {
|
|
220
|
+
try {
|
|
221
|
+
chrome.storage.sync.get("videoDeviceId", (v) =>
|
|
222
|
+
resolve(typeof v.videoDeviceId === "string" ? v.videoDeviceId : ""),
|
|
223
|
+
);
|
|
224
|
+
} catch {
|
|
225
|
+
resolve("");
|
|
226
|
+
}
|
|
227
|
+
});
|
|
228
|
+
|
|
229
|
+
const stopCurrentStream = (): void => {
|
|
230
|
+
if (healthTimer !== undefined) window.clearInterval(healthTimer);
|
|
231
|
+
healthTimer = undefined;
|
|
232
|
+
if (reconnectTimer !== undefined) window.clearTimeout(reconnectTimer);
|
|
233
|
+
reconnectTimer = undefined;
|
|
234
|
+
currentVideo?.remove();
|
|
235
|
+
currentVideo = null;
|
|
236
|
+
currentStream?.getTracks().forEach((track) => track.stop());
|
|
237
|
+
currentStream = null;
|
|
238
|
+
unhealthyTicks = 0;
|
|
239
|
+
};
|
|
240
|
+
|
|
241
|
+
const showCameraUnavailable = (): void => {
|
|
242
|
+
stopCurrentStream();
|
|
243
|
+
ring.replaceChildren();
|
|
231
244
|
const empty = document.createElement("div");
|
|
232
245
|
empty.className = "bubble-empty";
|
|
233
246
|
empty.innerHTML = ICONS.cameraOff;
|
|
234
247
|
ring.appendChild(empty);
|
|
235
248
|
// Still release the countdown — a blocked/failed camera must not hang it.
|
|
236
|
-
|
|
249
|
+
postReadyOnce();
|
|
250
|
+
};
|
|
251
|
+
|
|
252
|
+
const scheduleReconnect = (reason: string): void => {
|
|
253
|
+
if (reconnectTimer !== undefined) return;
|
|
254
|
+
reconnectTimer = window.setTimeout(() => {
|
|
255
|
+
reconnectTimer = undefined;
|
|
256
|
+
void connectCamera(reason);
|
|
257
|
+
}, 500);
|
|
258
|
+
};
|
|
259
|
+
|
|
260
|
+
const watchCameraHealth = (
|
|
261
|
+
video: HTMLVideoElement,
|
|
262
|
+
track: MediaStreamTrack,
|
|
263
|
+
) => {
|
|
264
|
+
healthTimer = window.setInterval(() => {
|
|
265
|
+
const unhealthy =
|
|
266
|
+
track.readyState !== "live" ||
|
|
267
|
+
track.muted ||
|
|
268
|
+
video.readyState < HTMLMediaElement.HAVE_CURRENT_DATA ||
|
|
269
|
+
video.videoWidth === 0;
|
|
270
|
+
unhealthyTicks = unhealthy ? unhealthyTicks + 1 : 0;
|
|
271
|
+
if (unhealthyTicks >= 3) scheduleReconnect("camera stream stalled");
|
|
272
|
+
}, 2000);
|
|
273
|
+
};
|
|
274
|
+
|
|
275
|
+
async function connectCamera(reason = "initial"): Promise<void> {
|
|
276
|
+
stopCurrentStream();
|
|
277
|
+
try {
|
|
278
|
+
const stream = await getCameraBubbleStream(videoDeviceId);
|
|
279
|
+
const video = document.createElement("video");
|
|
280
|
+
video.muted = true;
|
|
281
|
+
video.autoplay = true;
|
|
282
|
+
video.playsInline = true;
|
|
283
|
+
video.srcObject = stream;
|
|
284
|
+
ring.replaceChildren(video);
|
|
285
|
+
currentStream = stream;
|
|
286
|
+
currentVideo = video;
|
|
287
|
+
const track = stream.getVideoTracks()[0];
|
|
288
|
+
track?.addEventListener("ended", () => scheduleReconnect("track ended"));
|
|
289
|
+
track?.addEventListener("mute", () => scheduleReconnect("track muted"));
|
|
290
|
+
video.addEventListener("stalled", () =>
|
|
291
|
+
scheduleReconnect("video stalled"),
|
|
292
|
+
);
|
|
293
|
+
video.addEventListener("emptied", () =>
|
|
294
|
+
scheduleReconnect("video emptied"),
|
|
295
|
+
);
|
|
296
|
+
await video.play().catch(() => undefined);
|
|
297
|
+
console.log("[clips-overlay] camera bubble live", reason);
|
|
298
|
+
postReadyOnce();
|
|
299
|
+
if (track) watchCameraHealth(video, track);
|
|
300
|
+
} catch (err) {
|
|
301
|
+
console.warn("[clips-overlay] camera getUserMedia failed:", err);
|
|
302
|
+
captureExtensionError(err, {
|
|
303
|
+
tags: { surface: "overlay", overlayPart: "bubble" },
|
|
304
|
+
extra: { reason },
|
|
305
|
+
});
|
|
306
|
+
showCameraUnavailable();
|
|
307
|
+
}
|
|
237
308
|
}
|
|
309
|
+
|
|
310
|
+
window.addEventListener("pagehide", stopCurrentStream, { once: true });
|
|
311
|
+
await connectCamera();
|
|
238
312
|
}
|
|
239
313
|
|
|
240
314
|
/* ------------------------------------------------------------- countdown --- */
|
|
@@ -23,6 +23,13 @@ const checkCam = document.getElementById("check-cam") as HTMLSpanElement;
|
|
|
23
23
|
|
|
24
24
|
const CHECK = "✓";
|
|
25
25
|
const DOT = "●";
|
|
26
|
+
const params = new URLSearchParams(location.search);
|
|
27
|
+
const startAfterGrant = params.get("startAfterGrant") === "1";
|
|
28
|
+
const shouldRequestCamera = params.get("needsCamera") !== "false";
|
|
29
|
+
const shouldRequestMicrophone = params.get("needsMicrophone") !== "false";
|
|
30
|
+
|
|
31
|
+
if (!shouldRequestMicrophone) rowMic.hidden = true;
|
|
32
|
+
if (!shouldRequestCamera) rowCam.hidden = true;
|
|
26
33
|
|
|
27
34
|
function setStatus(text: string, isError = false): void {
|
|
28
35
|
statusEl.textContent = text;
|
|
@@ -40,8 +47,9 @@ function showSuccess(title: string): void {
|
|
|
40
47
|
enableBtn.hidden = true;
|
|
41
48
|
enableBtn.disabled = true;
|
|
42
49
|
successTitle.textContent = title;
|
|
43
|
-
successCopy.textContent =
|
|
44
|
-
"
|
|
50
|
+
successCopy.textContent = startAfterGrant
|
|
51
|
+
? "Opening Chrome's screen picker now."
|
|
52
|
+
: "You can close this tab and start recording from the Clips icon.";
|
|
45
53
|
successEl.hidden = false;
|
|
46
54
|
setStatus(
|
|
47
55
|
"Chrome still asks you what to share before each recording starts.",
|
|
@@ -83,14 +91,53 @@ async function requestOne(kind: "mic" | "cam"): Promise<boolean> {
|
|
|
83
91
|
}
|
|
84
92
|
}
|
|
85
93
|
|
|
86
|
-
function
|
|
87
|
-
|
|
88
|
-
|
|
94
|
+
function requiredPermissionsReady(camOk: boolean, micOk: boolean): boolean {
|
|
95
|
+
return (!shouldRequestCamera || camOk) && (!shouldRequestMicrophone || micOk);
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
function writePermissionCache(camOk: boolean, micOk: boolean): Promise<void> {
|
|
99
|
+
return new Promise((resolve) => {
|
|
100
|
+
chrome.storage.local.get("clipsMediaPermission", (value) => {
|
|
101
|
+
const current =
|
|
102
|
+
value.clipsMediaPermission &&
|
|
103
|
+
typeof value.clipsMediaPermission === "object"
|
|
104
|
+
? (value.clipsMediaPermission as {
|
|
105
|
+
camera?: boolean;
|
|
106
|
+
microphone?: boolean;
|
|
107
|
+
})
|
|
108
|
+
: {};
|
|
109
|
+
const next = {
|
|
110
|
+
...current,
|
|
111
|
+
...(shouldRequestCamera ? { camera: camOk } : {}),
|
|
112
|
+
...(shouldRequestMicrophone ? { microphone: micOk } : {}),
|
|
113
|
+
};
|
|
114
|
+
chrome.storage.local.set({ clipsMediaPermission: next }, () => resolve());
|
|
115
|
+
});
|
|
89
116
|
});
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
async function startPendingRecording(): Promise<void> {
|
|
120
|
+
const response = (await chrome.runtime.sendMessage({
|
|
121
|
+
type: "CLIPS_PERMISSION_START_AFTER_GRANT",
|
|
122
|
+
})) as { ok?: boolean; error?: string } | undefined;
|
|
123
|
+
if (response?.ok) {
|
|
124
|
+
window.close();
|
|
125
|
+
return;
|
|
126
|
+
}
|
|
127
|
+
showEnableButton("Try again", false);
|
|
128
|
+
setStatus(
|
|
129
|
+
response?.error || "Start the recording again from the Clips icon.",
|
|
130
|
+
true,
|
|
131
|
+
);
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
async function finish(camOk: boolean, micOk: boolean): Promise<void> {
|
|
135
|
+
await writePermissionCache(camOk, micOk);
|
|
90
136
|
markRow("mic", micOk);
|
|
91
137
|
markRow("cam", camOk);
|
|
92
|
-
if (camOk
|
|
93
|
-
showSuccess(
|
|
138
|
+
if (requiredPermissionsReady(camOk, micOk)) {
|
|
139
|
+
showSuccess(startAfterGrant ? "Starting recording..." : "You're all done");
|
|
140
|
+
if (startAfterGrant) await startPendingRecording();
|
|
94
141
|
} else {
|
|
95
142
|
showEnableButton("Try again", false);
|
|
96
143
|
setStatus(
|
|
@@ -104,9 +151,9 @@ async function enable(): Promise<void> {
|
|
|
104
151
|
showEnableButton(enableBtn.textContent ?? "Enable camera & microphone", true);
|
|
105
152
|
setStatus("Waiting for Chrome's permission prompt…");
|
|
106
153
|
// Request separately so a camera denial doesn't also block the microphone.
|
|
107
|
-
const micOk = await requestOne("mic");
|
|
108
|
-
const camOk = await requestOne("cam");
|
|
109
|
-
finish(camOk, micOk);
|
|
154
|
+
const micOk = shouldRequestMicrophone ? await requestOne("mic") : true;
|
|
155
|
+
const camOk = shouldRequestCamera ? await requestOne("cam") : true;
|
|
156
|
+
await finish(camOk, micOk);
|
|
110
157
|
}
|
|
111
158
|
|
|
112
159
|
enableBtn.addEventListener("click", () => void enable());
|
|
@@ -117,9 +164,11 @@ void (async () => {
|
|
|
117
164
|
permissionState("camera"),
|
|
118
165
|
permissionState("microphone"),
|
|
119
166
|
]);
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
167
|
+
const micOk = !shouldRequestMicrophone || mic === "granted";
|
|
168
|
+
const camOk = !shouldRequestCamera || cam === "granted";
|
|
169
|
+
markRow("mic", micOk);
|
|
170
|
+
markRow("cam", camOk);
|
|
171
|
+
if (requiredPermissionsReady(camOk, micOk)) {
|
|
172
|
+
await finish(camOk, micOk);
|
|
124
173
|
}
|
|
125
174
|
})();
|