@agent-native/core 0.90.11 → 0.91.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.
Files changed (322) hide show
  1. package/corpus/README.md +2 -2
  2. package/corpus/core/CHANGELOG.md +34 -0
  3. package/corpus/core/docs/content/toolkit-settings.mdx +56 -3
  4. package/corpus/core/package.json +2 -1
  5. package/corpus/core/src/agent/run-ownership.ts +29 -1
  6. package/corpus/core/src/changelog/parse.ts +78 -0
  7. package/corpus/core/src/chat-threads/schema.ts +43 -0
  8. package/corpus/core/src/chat-threads/store.ts +144 -12
  9. package/corpus/core/src/cli/design-connect.ts +53 -9
  10. package/corpus/core/src/cli/workspace-dev.ts +25 -5
  11. package/corpus/core/src/client/AgentPanel.tsx +79 -14
  12. package/corpus/core/src/client/AssistantChat.tsx +359 -425
  13. package/corpus/core/src/client/analytics-session.ts +70 -0
  14. package/corpus/core/src/client/analytics.ts +175 -49
  15. package/corpus/core/src/client/chat/message-components.tsx +22 -2
  16. package/corpus/core/src/client/components/ui/message-scroller.tsx +131 -0
  17. package/corpus/core/src/client/conversation/AgentConversation.tsx +63 -41
  18. package/corpus/core/src/client/error-capture.ts +616 -0
  19. package/corpus/core/src/client/index.ts +12 -0
  20. package/corpus/core/src/client/session-replay.ts +57 -7
  21. package/corpus/core/src/client/settings/SettingsPanel.tsx +123 -1
  22. package/corpus/core/src/client/settings/SettingsTabsPage.tsx +292 -45
  23. package/corpus/core/src/client/settings/index.ts +1 -0
  24. package/corpus/core/src/client/sharing/ShareButton.tsx +14 -11
  25. package/corpus/core/src/client/sharing/ShareDialog.tsx +13 -10
  26. package/corpus/core/src/mcp/builtin-tools.ts +29 -2
  27. package/corpus/core/src/server/agent-chat-plugin.ts +174 -79
  28. package/corpus/core/src/server/onboarding-html.ts +28 -16
  29. package/corpus/core/src/sharing/actions/list-resource-shares.ts +45 -1
  30. package/corpus/core/src/sharing/actions/share-resource.ts +9 -0
  31. package/corpus/core/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  32. package/corpus/core/src/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
  33. package/corpus/core/src/vite/client.ts +217 -35
  34. package/corpus/templates/analytics/.agents/skills/adhoc-analysis/SKILL.md +8 -7
  35. package/corpus/templates/analytics/.agents/skills/analysis-workspace/SKILL.md +14 -10
  36. package/corpus/templates/analytics/.agents/skills/bigquery/SKILL.md +15 -15
  37. package/corpus/templates/analytics/.agents/skills/cross-source-analysis/SKILL.md +1 -0
  38. package/corpus/templates/analytics/.agents/skills/dashboard-management/SKILL.md +38 -22
  39. package/corpus/templates/analytics/.agents/skills/data-programs/SKILL.md +114 -61
  40. package/corpus/templates/analytics/.agents/skills/data-querying/SKILL.md +1 -0
  41. package/corpus/templates/analytics/.agents/skills/gong/SKILL.md +7 -7
  42. package/corpus/templates/analytics/.agents/skills/hubspot/SKILL.md +1 -0
  43. package/corpus/templates/analytics/.agents/skills/prometheus/SKILL.md +1 -1
  44. package/corpus/templates/analytics/.builder/skills/bigquery/SKILL.md +17 -17
  45. package/corpus/templates/analytics/.builder/skills/gcloud/SKILL.md +5 -5
  46. package/corpus/templates/analytics/.builder/skills/github/SKILL.md +7 -7
  47. package/corpus/templates/analytics/.builder/skills/gong/SKILL.md +12 -12
  48. package/corpus/templates/analytics/.builder/skills/grafana/SKILL.md +7 -7
  49. package/corpus/templates/analytics/.builder/skills/sentry/SKILL.md +7 -7
  50. package/corpus/templates/analytics/.builder/skills/slack/SKILL.md +6 -6
  51. package/corpus/templates/analytics/.builder/skills/stripe/SKILL.md +7 -7
  52. package/corpus/templates/analytics/AGENTS.md +47 -2
  53. package/corpus/templates/analytics/actions/add-status-page-monitor.ts +36 -0
  54. package/corpus/templates/analytics/actions/capture-test-error.ts +33 -0
  55. package/corpus/templates/analytics/actions/delete-monitor.ts +24 -0
  56. package/corpus/templates/analytics/actions/delete-status-page.ts +23 -0
  57. package/corpus/templates/analytics/actions/get-error-issue.ts +36 -0
  58. package/corpus/templates/analytics/actions/get-monitor-stats.ts +55 -0
  59. package/corpus/templates/analytics/actions/get-monitor.ts +27 -0
  60. package/corpus/templates/analytics/actions/get-public-status-page.ts +41 -0
  61. package/corpus/templates/analytics/actions/get-status-page.ts +29 -0
  62. package/corpus/templates/analytics/actions/list-error-issues.ts +40 -0
  63. package/corpus/templates/analytics/actions/list-monitors.ts +21 -0
  64. package/corpus/templates/analytics/actions/list-status-pages.ts +21 -0
  65. package/corpus/templates/analytics/actions/match-error-issues.ts +51 -0
  66. package/corpus/templates/analytics/actions/navigate.ts +58 -4
  67. package/corpus/templates/analytics/actions/remove-status-page-monitor.ts +22 -0
  68. package/corpus/templates/analytics/actions/reorder-status-page-monitors.ts +25 -0
  69. package/corpus/templates/analytics/actions/resolve-error-issue.ts +38 -0
  70. package/corpus/templates/analytics/actions/run-monitor-check.ts +23 -0
  71. package/corpus/templates/analytics/actions/save-analysis.ts +1 -0
  72. package/corpus/templates/analytics/actions/save-monitor.ts +145 -0
  73. package/corpus/templates/analytics/actions/save-status-page.ts +47 -0
  74. package/corpus/templates/analytics/actions/view-screen.ts +212 -0
  75. package/corpus/templates/analytics/app/components/layout/Header.tsx +1 -0
  76. package/corpus/templates/analytics/app/components/layout/Layout.tsx +10 -1
  77. package/corpus/templates/analytics/app/components/layout/Sidebar.tsx +134 -39
  78. package/corpus/templates/analytics/app/components/monitoring/PublicStatusView.tsx +351 -0
  79. package/corpus/templates/analytics/app/components/monitoring/ResponseTimeChart.tsx +174 -0
  80. package/corpus/templates/analytics/app/components/monitoring/UptimeStatCards.tsx +85 -0
  81. package/corpus/templates/analytics/app/components/monitoring/UptimeTimelineBars.tsx +106 -0
  82. package/corpus/templates/analytics/app/components/monitoring/chart-utils.ts +101 -0
  83. package/corpus/templates/analytics/app/components/monitoring/index.ts +28 -0
  84. package/corpus/templates/analytics/app/components/monitoring/types.ts +42 -0
  85. package/corpus/templates/analytics/app/hooks/use-navigation-state.ts +34 -0
  86. package/corpus/templates/analytics/app/i18n/zh-TW.ts +6 -0
  87. package/corpus/templates/analytics/app/i18n-data.ts +33 -0
  88. package/corpus/templates/analytics/app/pages/DataSources.tsx +28 -0
  89. package/corpus/templates/analytics/app/pages/Settings.tsx +77 -9
  90. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/SqlChartCard.tsx +3 -3
  91. package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/dashboard-layout.ts +7 -0
  92. package/corpus/templates/analytics/app/pages/monitoring/ErrorsPanel.tsx +205 -0
  93. package/corpus/templates/analytics/app/pages/monitoring/MonitoringPage.tsx +100 -0
  94. package/corpus/templates/analytics/app/pages/monitoring/UptimePanel.tsx +475 -0
  95. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueDetail.tsx +372 -0
  96. package/corpus/templates/analytics/app/pages/monitoring/errors/IssueList.tsx +360 -0
  97. package/corpus/templates/analytics/app/pages/monitoring/errors/Sparkline.tsx +55 -0
  98. package/corpus/templates/analytics/app/pages/monitoring/errors/i18n.ts +106 -0
  99. package/corpus/templates/analytics/app/pages/monitoring/errors/types.ts +75 -0
  100. package/corpus/templates/analytics/app/pages/monitoring/errors/utils.ts +132 -0
  101. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorDetail.tsx +535 -0
  102. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorFormPage.tsx +1026 -0
  103. package/corpus/templates/analytics/app/pages/monitoring/uptime/MonitorList.tsx +311 -0
  104. package/corpus/templates/analytics/app/pages/monitoring/uptime/i18n.ts +235 -0
  105. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/StatusPageEditor.tsx +661 -0
  106. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/StatusPagesView.tsx +355 -0
  107. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/i18n.ts +106 -0
  108. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/slug.ts +24 -0
  109. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-pages/types.ts +20 -0
  110. package/corpus/templates/analytics/app/pages/monitoring/uptime/status-summary.ts +87 -0
  111. package/corpus/templates/analytics/app/pages/monitoring/uptime/types.ts +161 -0
  112. package/corpus/templates/analytics/app/pages/monitoring/uptime/utils.ts +210 -0
  113. package/corpus/templates/analytics/app/pages/sessions/SessionDetailPage.tsx +76 -1
  114. package/corpus/templates/analytics/app/pages/sessions/SessionDevToolsPanel.tsx +74 -9
  115. package/corpus/templates/analytics/app/pages/sessions/SessionsPage.tsx +197 -20
  116. package/corpus/templates/analytics/app/root.tsx +33 -1
  117. package/corpus/templates/analytics/app/routes/monitoring._index.tsx +10 -0
  118. package/corpus/templates/analytics/app/routes/status.$slug.tsx +118 -0
  119. package/corpus/templates/analytics/changelog/2026-07-08-add-uptime-monitors-that-ping-your-urls-and-alert-you-when-t.md +6 -0
  120. package/corpus/templates/analytics/changelog/2026-07-08-analytics-chats-keep-large-data-dictionaries-compact-so-prov.md +6 -0
  121. package/corpus/templates/analytics/changelog/2026-07-08-create-public-status-pages-that-share-the-live-health-of-cho.md +6 -0
  122. package/corpus/templates/analytics/changelog/2026-07-08-dashboard-extension-panels-use-clearer-open-copy.md +6 -0
  123. package/corpus/templates/analytics/changelog/2026-07-08-error-capture.md +6 -0
  124. package/corpus/templates/analytics/changelog/2026-07-08-fixed-single-panel-dashboard-drags-so-they-no-longer-rewrite.md +6 -0
  125. package/corpus/templates/analytics/changelog/2026-07-08-jump-straight-from-an-error-in-a-session-recording-to-its-fu.md +6 -0
  126. package/corpus/templates/analytics/changelog/2026-07-08-monitor-detail-and-list-now-show-colorful-uptime-timelines-r.md +6 -0
  127. package/corpus/templates/analytics/changelog/2026-07-08-pinned-ask-chats-now-show-a-pin-icon-in-the-sidebar.md +6 -0
  128. package/corpus/templates/analytics/changelog/2026-07-08-publish-a-public-uptime-status-page-with-colorful-uptime-tim.md +6 -0
  129. package/corpus/templates/analytics/changelog/2026-07-08-sessions-now-hide-visitor-emails-in-demo-mode-and-show-clean.md +6 -0
  130. package/corpus/templates/analytics/changelog/2026-07-08-settings-are-cleaner-searchable-and-alerts-have-their-own-tab.md +5 -0
  131. package/corpus/templates/analytics/changelog/2026-07-08-sidebar-sections-stay-collapsed-until-opened.md +5 -0
  132. package/corpus/templates/analytics/changelog/2026-07-08-the-uptime-monitor-list-now-has-a-current-status-overview-wi.md +6 -0
  133. package/corpus/templates/analytics/docs/error-capture.md +172 -0
  134. package/corpus/templates/analytics/docs/uptime-monitoring.md +150 -0
  135. package/corpus/templates/analytics/server/db/schema-errors.ts +150 -0
  136. package/corpus/templates/analytics/server/db/schema-monitoring.ts +177 -0
  137. package/corpus/templates/analytics/server/db/schema.ts +6 -0
  138. package/corpus/templates/analytics/server/jobs/uptime-monitors.ts +123 -0
  139. package/corpus/templates/analytics/server/lib/agent-chat-plan-mode.ts +41 -0
  140. package/corpus/templates/analytics/server/lib/data-dictionary-context.ts +55 -12
  141. package/corpus/templates/analytics/server/lib/error-capture.ts +1331 -0
  142. package/corpus/templates/analytics/server/lib/first-party-analytics.ts +47 -1
  143. package/corpus/templates/analytics/server/lib/monitor-stats.ts +494 -0
  144. package/corpus/templates/analytics/server/lib/status-pages.ts +798 -0
  145. package/corpus/templates/analytics/server/lib/uptime-monitors.ts +1665 -0
  146. package/corpus/templates/analytics/server/plugins/agent-chat.ts +7 -37
  147. package/corpus/templates/analytics/server/plugins/auth.ts +11 -1
  148. package/corpus/templates/analytics/server/plugins/db.ts +408 -0
  149. package/corpus/templates/analytics/server/plugins/uptime-monitor-jobs.ts +63 -0
  150. package/corpus/templates/assets/app/routes/_index.tsx +1 -1
  151. package/corpus/templates/assets/app/routes/settings.tsx +22 -1
  152. package/corpus/templates/assets/changelog/2026-07-08-recent-drafts-keep-comfortable-side-padding.md +6 -0
  153. package/corpus/templates/assets/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  154. package/corpus/templates/brain/app/routes/settings.tsx +43 -5
  155. package/corpus/templates/brain/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  156. package/corpus/templates/calendar/app/pages/Settings.tsx +44 -6
  157. package/corpus/templates/calendar/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  158. package/corpus/templates/chat/app/routes/settings.tsx +16 -1
  159. package/corpus/templates/chat/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  160. package/corpus/templates/clips/actions/lib/audio-only-transcription.ts +79 -37
  161. package/corpus/templates/clips/app/components/player/player-controls.tsx +35 -0
  162. package/corpus/templates/clips/app/components/player/video-player.tsx +162 -18
  163. package/corpus/templates/clips/app/routes/_app.settings._index.tsx +63 -6
  164. package/corpus/templates/clips/app/routes/record.tsx +18 -1
  165. package/corpus/templates/clips/changelog/2026-07-07-shared-clips-start-playback-reliably-from-the-first-play-click.md +6 -0
  166. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-camera-and-microphone-menus-now-show-which-.md +6 -0
  167. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-copies-new-clip-links-after-saving.md +6 -0
  168. package/corpus/templates/clips/changelog/2026-07-08-chrome-extension-recordings-now-continue-after-granting-came.md +6 -0
  169. package/corpus/templates/clips/changelog/2026-07-08-mobile-shared-clips-now-have-loom-style-tap-and-skip-controls.md +6 -0
  170. package/corpus/templates/clips/changelog/2026-07-08-recordings-and-uploads-copy-links-after-saving.md +6 -0
  171. package/corpus/templates/clips/changelog/2026-07-08-settings-are-cleaner-searchable-and-collapse-s3-with-builder.md +5 -0
  172. package/corpus/templates/clips/changelog/2026-07-08-transcripts-are-more-reliable-for-longer-desktop-recordings-.md +6 -0
  173. package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -0
  174. package/corpus/templates/clips/chrome-extension/src/background.ts +162 -13
  175. package/corpus/templates/clips/chrome-extension/src/content-script.ts +14 -0
  176. package/corpus/templates/clips/chrome-extension/src/offscreen.ts +36 -0
  177. package/corpus/templates/clips/chrome-extension/src/overlay.ts +99 -25
  178. package/corpus/templates/clips/chrome-extension/src/permission.ts +63 -14
  179. package/corpus/templates/clips/chrome-extension/src/popup.ts +123 -23
  180. package/corpus/templates/content/actions/_builder-cms-read-client.ts +47 -0
  181. package/corpus/templates/content/actions/_database-source-utils.ts +33 -4
  182. package/corpus/templates/content/actions/add-content-database-source-field-property.ts +214 -29
  183. package/corpus/templates/content/actions/attach-content-database-source.ts +3 -0
  184. package/corpus/templates/content/actions/change-content-database-source-role.ts +1 -0
  185. package/corpus/templates/content/app/components/editor/DocumentProperties.tsx +42 -32
  186. package/corpus/templates/content/app/components/editor/database/DatabaseView.tsx +352 -81
  187. package/corpus/templates/content/app/components/editor/database/settings.tsx +49 -24
  188. package/corpus/templates/content/app/routes/_app.settings.tsx +19 -1
  189. package/corpus/templates/content/changelog/2026-07-07-builder-source-tag-fields-now-import-as-multi-select.md +6 -0
  190. package/corpus/templates/content/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  191. package/corpus/templates/content/shared/api.ts +3 -0
  192. package/corpus/templates/content/shared/properties.ts +42 -0
  193. package/corpus/templates/design/AGENTS.md +9 -0
  194. package/corpus/templates/design/actions/generate-design.ts +3 -0
  195. package/corpus/templates/design/actions/get-design-system.ts +147 -1
  196. package/corpus/templates/design/actions/request-localhost-write-consent.ts +141 -0
  197. package/corpus/templates/design/app/components/design/DesignCanvas.tsx +13 -19
  198. package/corpus/templates/design/app/routes/settings.tsx +16 -1
  199. package/corpus/templates/design/changelog/2026-07-08-design-generation-now-follows-hydrated-builder-design-system.md +6 -0
  200. package/corpus/templates/design/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  201. package/corpus/templates/design/server/lib/verify-write-grant.ts +4 -2
  202. package/corpus/templates/dispatch/app/routes/settings.tsx +23 -2
  203. package/corpus/templates/dispatch/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  204. package/corpus/templates/forms/app/routes/_app.settings.tsx +16 -1
  205. package/corpus/templates/forms/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  206. package/corpus/templates/macros/app/routes/settings.tsx +16 -1
  207. package/corpus/templates/macros/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  208. package/corpus/templates/mail/app/components/settings/GmailFiltersSection.tsx +1 -1
  209. package/corpus/templates/mail/app/components/settings/SnippetsSection.tsx +1 -1
  210. package/corpus/templates/mail/app/pages/SettingsPage.tsx +123 -105
  211. package/corpus/templates/mail/changelog/2026-07-08-settings-redesigned-with-a-consistent-searchable-layout.md +5 -0
  212. package/corpus/templates/plan/app/routes/settings.tsx +23 -2
  213. package/corpus/templates/plan/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  214. package/corpus/templates/slides/actions/add-slide.ts +1 -0
  215. package/corpus/templates/slides/actions/get-design-system.ts +147 -1
  216. package/corpus/templates/slides/app/pages/Index.tsx +46 -1
  217. package/corpus/templates/slides/app/routes/settings.tsx +16 -1
  218. package/corpus/templates/slides/changelog/2026-07-08-deck-generation-now-follows-hydrated-builder-design-system-g.md +6 -0
  219. package/corpus/templates/slides/changelog/2026-07-08-settings-are-cleaner-and-searchable.md +5 -0
  220. package/dist/agent/run-ownership.d.ts +6 -0
  221. package/dist/agent/run-ownership.d.ts.map +1 -1
  222. package/dist/agent/run-ownership.js +11 -1
  223. package/dist/agent/run-ownership.js.map +1 -1
  224. package/dist/changelog/parse.d.ts +7 -0
  225. package/dist/changelog/parse.d.ts.map +1 -1
  226. package/dist/changelog/parse.js +64 -0
  227. package/dist/changelog/parse.js.map +1 -1
  228. package/dist/chat-threads/schema.d.ts +445 -0
  229. package/dist/chat-threads/schema.d.ts.map +1 -0
  230. package/dist/chat-threads/schema.js +39 -0
  231. package/dist/chat-threads/schema.js.map +1 -0
  232. package/dist/chat-threads/store.d.ts +12 -0
  233. package/dist/chat-threads/store.d.ts.map +1 -1
  234. package/dist/chat-threads/store.js +99 -10
  235. package/dist/chat-threads/store.js.map +1 -1
  236. package/dist/cli/design-connect.d.ts.map +1 -1
  237. package/dist/cli/design-connect.js +40 -10
  238. package/dist/cli/design-connect.js.map +1 -1
  239. package/dist/cli/workspace-dev.d.ts.map +1 -1
  240. package/dist/cli/workspace-dev.js +20 -5
  241. package/dist/cli/workspace-dev.js.map +1 -1
  242. package/dist/client/AgentPanel.d.ts.map +1 -1
  243. package/dist/client/AgentPanel.js +35 -7
  244. package/dist/client/AgentPanel.js.map +1 -1
  245. package/dist/client/AssistantChat.d.ts.map +1 -1
  246. package/dist/client/AssistantChat.js +105 -183
  247. package/dist/client/AssistantChat.js.map +1 -1
  248. package/dist/client/analytics-session.d.ts +3 -0
  249. package/dist/client/analytics-session.d.ts.map +1 -0
  250. package/dist/client/analytics-session.js +66 -0
  251. package/dist/client/analytics-session.js.map +1 -0
  252. package/dist/client/analytics.d.ts +32 -0
  253. package/dist/client/analytics.d.ts.map +1 -1
  254. package/dist/client/analytics.js +116 -46
  255. package/dist/client/analytics.js.map +1 -1
  256. package/dist/client/chat/message-components.d.ts +2 -1
  257. package/dist/client/chat/message-components.d.ts.map +1 -1
  258. package/dist/client/chat/message-components.js +16 -6
  259. package/dist/client/chat/message-components.js.map +1 -1
  260. package/dist/client/components/ui/message-scroller.d.ts +16 -0
  261. package/dist/client/components/ui/message-scroller.d.ts.map +1 -0
  262. package/dist/client/components/ui/message-scroller.js +25 -0
  263. package/dist/client/components/ui/message-scroller.js.map +1 -0
  264. package/dist/client/conversation/AgentConversation.d.ts +1 -1
  265. package/dist/client/conversation/AgentConversation.d.ts.map +1 -1
  266. package/dist/client/conversation/AgentConversation.js +3 -8
  267. package/dist/client/conversation/AgentConversation.js.map +1 -1
  268. package/dist/client/error-capture.d.ts +99 -0
  269. package/dist/client/error-capture.d.ts.map +1 -0
  270. package/dist/client/error-capture.js +437 -0
  271. package/dist/client/error-capture.js.map +1 -0
  272. package/dist/client/index.d.ts +2 -2
  273. package/dist/client/index.d.ts.map +1 -1
  274. package/dist/client/index.js +3 -1
  275. package/dist/client/index.js.map +1 -1
  276. package/dist/client/session-replay.d.ts +22 -0
  277. package/dist/client/session-replay.d.ts.map +1 -1
  278. package/dist/client/session-replay.js +45 -4
  279. package/dist/client/session-replay.js.map +1 -1
  280. package/dist/client/settings/SettingsPanel.d.ts.map +1 -1
  281. package/dist/client/settings/SettingsPanel.js +100 -5
  282. package/dist/client/settings/SettingsPanel.js.map +1 -1
  283. package/dist/client/settings/SettingsTabsPage.d.ts +49 -1
  284. package/dist/client/settings/SettingsTabsPage.d.ts.map +1 -1
  285. package/dist/client/settings/SettingsTabsPage.js +131 -19
  286. package/dist/client/settings/SettingsTabsPage.js.map +1 -1
  287. package/dist/client/settings/index.d.ts +1 -1
  288. package/dist/client/settings/index.d.ts.map +1 -1
  289. package/dist/client/settings/index.js.map +1 -1
  290. package/dist/client/sharing/ShareButton.d.ts.map +1 -1
  291. package/dist/client/sharing/ShareButton.js +13 -8
  292. package/dist/client/sharing/ShareButton.js.map +1 -1
  293. package/dist/client/sharing/ShareDialog.d.ts.map +1 -1
  294. package/dist/client/sharing/ShareDialog.js +12 -7
  295. package/dist/client/sharing/ShareDialog.js.map +1 -1
  296. package/dist/collab/routes.d.ts +1 -1
  297. package/dist/file-upload/actions/upload-image.d.ts +1 -1
  298. package/dist/mcp/builtin-tools.d.ts.map +1 -1
  299. package/dist/mcp/builtin-tools.js +21 -2
  300. package/dist/mcp/builtin-tools.js.map +1 -1
  301. package/dist/observability/routes.d.ts +1 -1
  302. package/dist/org/schema.d.ts +15 -15
  303. package/dist/provider-api/corpus-jobs.d.ts +2 -2
  304. package/dist/server/agent-chat-plugin.d.ts.map +1 -1
  305. package/dist/server/agent-chat-plugin.js +109 -73
  306. package/dist/server/agent-chat-plugin.js.map +1 -1
  307. package/dist/server/onboarding-html.d.ts.map +1 -1
  308. package/dist/server/onboarding-html.js +28 -16
  309. package/dist/server/onboarding-html.js.map +1 -1
  310. package/dist/sharing/actions/list-resource-shares.js +30 -1
  311. package/dist/sharing/actions/list-resource-shares.js.map +1 -1
  312. package/dist/sharing/actions/share-resource.js +6 -0
  313. package/dist/sharing/actions/share-resource.js.map +1 -1
  314. package/dist/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  315. package/dist/templates/workspace-core/.agents/skills/changelog/SKILL.md +9 -6
  316. package/dist/vite/client.d.ts.map +1 -1
  317. package/dist/vite/client.js +192 -30
  318. package/dist/vite/client.js.map +1 -1
  319. package/docs/content/toolkit-settings.mdx +56 -3
  320. package/package.json +2 -1
  321. package/src/templates/workspace-core/.agents/skills/agent-native-toolkit/SKILL.md +4 -2
  322. 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
- recording.captureSurface === "camera" || recording.includeCamera,
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
- try {
205
- const videoDeviceId = await new Promise<string>((resolve) => {
206
- try {
207
- chrome.storage.sync.get("videoDeviceId", (v) =>
208
- resolve(typeof v.videoDeviceId === "string" ? v.videoDeviceId : ""),
209
- );
210
- } catch {
211
- resolve("");
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
- } catch (err) {
227
- console.warn("[clips-overlay] camera getUserMedia failed:", err);
228
- captureExtensionError(err, {
229
- tags: { surface: "overlay", overlayPart: "bubble" },
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
- postBubble("camera-ready");
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
- "You can close this tab and start recording from the Clips icon.";
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 finish(camOk: boolean, micOk: boolean): void {
87
- void chrome.storage.local.set({
88
- clipsMediaPermission: { camera: camOk, microphone: micOk },
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 || micOk) {
93
- showSuccess(camOk && micOk ? "You're all done" : "Saved");
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
- markRow("mic", mic === "granted");
121
- markRow("cam", cam === "granted");
122
- if (cam === "granted" && mic === "granted") {
123
- showSuccess("You're all done");
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
  })();