@agent-native/core 0.79.7 → 0.79.10
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 +19 -0
- package/corpus/core/package.json +1 -1
- package/corpus/core/src/agent/run-manager.ts +9 -1
- package/corpus/core/src/client/agent-chat-adapter.ts +24 -16
- package/corpus/core/src/client/session-replay.ts +67 -13
- package/corpus/core/src/onboarding/default-steps.ts +3 -3
- package/corpus/core/src/server/action-routes.ts +6 -2
- package/corpus/core/src/server/agent-chat-plugin.ts +82 -162
- package/corpus/core/src/server/agent-run-context.ts +204 -0
- package/corpus/core/src/server/credential-provider.ts +20 -4
- package/corpus/templates/analytics/app/components/dashboard/SqlChart.tsx +14 -4
- package/corpus/templates/analytics/changelog/2026-06-26-session-replay-uploads-now-accept-compressed-payloads-so-lar.md +6 -0
- package/corpus/templates/analytics/changelog/2026-06-26-sql-chart-tooltips-now-stay-stable-while-moving-across-dense.md +6 -0
- package/corpus/templates/analytics/server/handlers/session-replay.ts +56 -8
- package/corpus/templates/clips/app/components/player/transcript-panel.tsx +1 -1
- package/corpus/templates/clips/app/components/recorder/storage-setup-card.tsx +44 -16
- package/corpus/templates/clips/app/i18n/ar-SA.ts +12 -9
- package/corpus/templates/clips/app/i18n/de-DE.ts +12 -9
- package/corpus/templates/clips/app/i18n/en-US.ts +19 -16
- package/corpus/templates/clips/app/i18n/es-ES.ts +12 -9
- package/corpus/templates/clips/app/i18n/fr-FR.ts +12 -9
- package/corpus/templates/clips/app/i18n/hi-IN.ts +13 -9
- package/corpus/templates/clips/app/i18n/ja-JP.ts +12 -9
- package/corpus/templates/clips/app/i18n/ko-KR.ts +12 -9
- package/corpus/templates/clips/app/i18n/pt-BR.ts +12 -9
- package/corpus/templates/clips/app/i18n/zh-CN.ts +19 -14
- package/corpus/templates/clips/app/i18n/zh-TW.ts +19 -14
- package/corpus/templates/clips/app/routes/r.$recordingId.tsx +7 -0
- package/corpus/templates/clips/app/routes/record.tsx +2 -2
- package/corpus/templates/clips/changelog/2026-06-26-desktop-recordings-recover-more-reliably-when-macos-reports-.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-26-slack-unfurls-now-recognize-recording-dashboard-links-pasted.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-26-storage-setup-now-explains-why-clips-needs-connected-storage.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +24 -3
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +28 -2
- package/corpus/templates/clips/chrome-extension/src/popup.html +16 -0
- package/corpus/templates/clips/chrome-extension/src/popup.ts +59 -7
- package/corpus/templates/clips/chrome-extension/src/sentry.ts +28 -1
- package/corpus/templates/clips/chrome-extension/src/styles.css +74 -0
- package/corpus/templates/clips/desktop/src/app.tsx +85 -18
- package/corpus/templates/clips/desktop/src/styles.css +55 -0
- package/corpus/templates/clips/desktop/src-tauri/src/native_screen.rs +88 -51
- package/corpus/templates/clips/desktop/src-tauri/src/native_speech.rs +12 -3
- package/corpus/templates/clips/server/lib/slack-unfurls.ts +5 -2
- package/corpus/templates/mail/app/hooks/use-emails.ts +28 -4
- package/corpus/templates/mail/app/root.tsx +13 -7
- package/corpus/templates/mail/changelog/2026-06-26-mail-avoids-unnecessary-message-list-reloads-after-backgroun.md +6 -0
- package/dist/agent/run-manager.d.ts.map +1 -1
- package/dist/agent/run-manager.js +12 -1
- package/dist/agent/run-manager.js.map +1 -1
- package/dist/client/agent-chat-adapter.d.ts.map +1 -1
- package/dist/client/agent-chat-adapter.js +15 -8
- package/dist/client/agent-chat-adapter.js.map +1 -1
- package/dist/client/session-replay.d.ts.map +1 -1
- package/dist/client/session-replay.js +55 -14
- package/dist/client/session-replay.js.map +1 -1
- package/dist/collab/awareness.d.ts +2 -2
- package/dist/collab/awareness.d.ts.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/notifications/routes.d.ts +2 -2
- package/dist/observability/routes.d.ts +6 -6
- package/dist/onboarding/default-steps.js +3 -3
- package/dist/onboarding/default-steps.js.map +1 -1
- package/dist/resources/handlers.d.ts +3 -3
- package/dist/server/action-routes.d.ts.map +1 -1
- package/dist/server/action-routes.js +6 -2
- package/dist/server/action-routes.js.map +1 -1
- package/dist/server/agent-chat-plugin.d.ts.map +1 -1
- package/dist/server/agent-chat-plugin.js +69 -154
- package/dist/server/agent-chat-plugin.js.map +1 -1
- package/dist/server/agent-run-context.d.ts +35 -0
- package/dist/server/agent-run-context.d.ts.map +1 -0
- package/dist/server/agent-run-context.js +137 -0
- package/dist/server/agent-run-context.js.map +1 -0
- package/dist/server/credential-provider.d.ts.map +1 -1
- package/dist/server/credential-provider.js +15 -4
- package/dist/server/credential-provider.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +1 -1
- package/corpus/templates/.retired/README.md +0 -9
- package/corpus/templates/.retired/calls/package.json +0 -8
- package/corpus/templates/.retired/code/package.json +0 -8
- package/corpus/templates/.retired/contracts/package.json +0 -8
- package/corpus/templates/.retired/images/package.json +0 -8
- package/corpus/templates/.retired/issues/package.json +0 -8
- package/corpus/templates/.retired/meeting-notes/package.json +0 -8
- package/corpus/templates/.retired/migration/package.json +0 -8
- package/corpus/templates/.retired/recruiting/package.json +0 -8
- package/corpus/templates/.retired/scheduling/package.json +0 -8
- package/corpus/templates/.retired/visual-plans/package.json +0 -8
- package/corpus/templates/.retired/voice/package.json +0 -8
- package/corpus/templates/.retired/workbench/package.json +0 -8
|
@@ -146,7 +146,7 @@ const messages = {
|
|
|
146
146
|
savingWentWrong: "保存此剪辑时出现问题。",
|
|
147
147
|
finishingClip: "正在完成您的剪辑...",
|
|
148
148
|
loomSourcePreserved:
|
|
149
|
-
"Loom
|
|
149
|
+
"Loom 源链接被保留。连接存储:Builder.io(免费套餐存储 + AI)或 S3 兼容存储,Clips 将重试保存自己的副本。",
|
|
150
150
|
clipDataPreserved:
|
|
151
151
|
"Your clip data is still preserved.连接Builder.io或S3存储,Clips将自动上传。",
|
|
152
152
|
details: "细节",
|
|
@@ -223,12 +223,13 @@ const messages = {
|
|
|
223
223
|
savingWentWrong: "保存此剪辑时出现问题。",
|
|
224
224
|
finishingClip: "完成这个剪辑...",
|
|
225
225
|
loomPreservedManage:
|
|
226
|
-
"Loom
|
|
226
|
+
"Loom 源链接被保留。连接存储:Builder.io(免费套餐存储 + AI)或 S3 兼容存储,然后重试导入。",
|
|
227
227
|
videoPreservedManage:
|
|
228
228
|
"视频被保留。连接Builder.io或S3存储,Clips将完成上传。",
|
|
229
|
-
creatorNeedsStorage:
|
|
229
|
+
creatorNeedsStorage:
|
|
230
|
+
"创建者需要先连接存储才能完成此剪辑:Builder.io(免费套餐存储 + AI)或 S3 兼容存储。",
|
|
230
231
|
signInStorage:
|
|
231
|
-
"
|
|
232
|
+
"如果这是您的剪辑,请在此处登录以连接存储:Builder.io(免费套餐存储 + AI)或 S3 兼容存储,并完成上传。",
|
|
232
233
|
uploadNotCompleteSession:
|
|
233
234
|
"上传尚未完成。打开此剪辑的仪表板或要求创建者检查存储空间。",
|
|
234
235
|
uploadNotCompleteSignIn:
|
|
@@ -330,7 +331,8 @@ const messages = {
|
|
|
330
331
|
cleanupKept: "清理工作无法完成。保留了本机转录本。",
|
|
331
332
|
cleanupBuilderFailed:
|
|
332
333
|
"即使 Builder.io 已连接,清理也无法完成。保留了本机转录本。",
|
|
333
|
-
cleanupPaused:
|
|
334
|
+
cleanupPaused:
|
|
335
|
+
"清理已暂停。请在“设置”中连接 AI:Builder.io(免费额度)或你自己的 LLM 密钥。",
|
|
334
336
|
builderNoResponse: "没有收到Builder的回复。允许弹出窗口并重试。",
|
|
335
337
|
saveFailed: "保存失败({{status}})",
|
|
336
338
|
savedRetrying: "已保存。正在重试转录...",
|
|
@@ -562,7 +564,7 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
|
|
|
562
564
|
"Builder.io 是 Clips 上传的主要存储路径。需要自带存储桶时,也可以使用 S3。",
|
|
563
565
|
checkingBuilder: "正在检查 Builder.io",
|
|
564
566
|
builderConnected: "Builder.io 已连接",
|
|
565
|
-
connectBuilder: "
|
|
567
|
+
connectBuilder: "使用 Builder.io(免费)",
|
|
566
568
|
builderConnectedFor: "正在为 {{orgName}} 使用 Builder.io。",
|
|
567
569
|
builderConnectedGeneric: "新的剪辑会使用已连接的 Builder.io 提供方。",
|
|
568
570
|
builderIncludes: "包含对象存储、上传,以及新剪辑的托管转录。",
|
|
@@ -586,12 +588,11 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
|
|
|
586
588
|
s3RegionLabel: "区域",
|
|
587
589
|
s3PublicBaseUrlLabel: "公共基础 URL",
|
|
588
590
|
apiSetup: "AI 设置",
|
|
589
|
-
apiSetupDescription:
|
|
590
|
-
|
|
591
|
-
builderEasySetup: "Builder.io 是最简单的设置方式",
|
|
591
|
+
apiSetupDescription: "使用 Builder.io 免费额度或你自己的 LLM 密钥连接 AI。",
|
|
592
|
+
builderEasySetup: "Builder.io 免费额度",
|
|
592
593
|
builderAiAvailable: "Clips 可使用包含的 AI 额度和托管转录。",
|
|
593
594
|
builderAiDescription:
|
|
594
|
-
"
|
|
595
|
+
"优先使用 Builder.io,可获得内置 AI 额度、对象存储、上传和托管转录。",
|
|
595
596
|
providerKeyTitle: "使用自己的提供方密钥",
|
|
596
597
|
providerKeyDescription:
|
|
597
598
|
"添加 Anthropic、OpenAI、Gemini、Groq 或 OpenRouter 密钥,用于按提供方计费的使用。",
|
|
@@ -730,7 +731,8 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
|
|
|
730
731
|
title: "拼接录制",
|
|
731
732
|
pickAtLeastTwo: "请选择至少 2 个录制进行拼接",
|
|
732
733
|
videoUrlMissing: "一个或多个录制还没有可用的视频 URL",
|
|
733
|
-
connectStorage:
|
|
734
|
+
connectStorage:
|
|
735
|
+
"拼接录制前请连接存储:Builder.io(免费套餐存储 + AI)或 S3 兼容存储。",
|
|
734
736
|
created: "拼接录制已创建",
|
|
735
737
|
failed: "拼接录制失败",
|
|
736
738
|
noOtherRecordings: "没有其他可用录制。",
|
|
@@ -954,9 +956,12 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
|
|
|
954
956
|
builderTimeout: "5 分钟内未收到 Builder 响应。请检查弹出窗口并重试。",
|
|
955
957
|
builderConnected: "Builder.io 已连接",
|
|
956
958
|
waitingForBuilder: "正在等待 Builder...",
|
|
957
|
-
connectBuilder: "
|
|
959
|
+
connectBuilder: "使用 Builder.io(免费)",
|
|
958
960
|
free: "免费",
|
|
959
961
|
configureS3: "配置 S3 兼容存储",
|
|
962
|
+
whyPrompt: "为什么会看到这个?",
|
|
963
|
+
whyDescription:
|
|
964
|
+
"Clips 是 100% 免费且开源的应用,所以你需要连接一种方式来存储剪辑。使用 Builder.io 可获得免费套餐存储和 AI,或使用 S3 兼容对象存储和你自己的 LLM 密钥。",
|
|
960
965
|
},
|
|
961
966
|
captureInstall: {
|
|
962
967
|
title: "Choose your recorder (已本地化)",
|
|
@@ -1159,9 +1164,9 @@ Clips 中所有面向用户的重要更改都会记录在这里。你可以随
|
|
|
1159
1164
|
downloadRecording: "Download recording (已本地化)",
|
|
1160
1165
|
openRecorderInTab: "Open recorder in tab (已本地化)",
|
|
1161
1166
|
connectStorageToFinish:
|
|
1162
|
-
"
|
|
1167
|
+
"在下一屏连接存储:Builder.io(免费套餐存储 + AI)或 S3 兼容存储。Clips 将完成保存。",
|
|
1163
1168
|
connectStorageToRetryLoom:
|
|
1164
|
-
"
|
|
1169
|
+
"在下一屏连接存储:Builder.io(免费套餐存储 + AI)或 S3 兼容存储。Clips 将重试导入。",
|
|
1165
1170
|
},
|
|
1166
1171
|
dictateRoute: {
|
|
1167
1172
|
pageTitle: "Dictate · Clips (已本地化)",
|
|
@@ -146,7 +146,7 @@ const messages = {
|
|
|
146
146
|
savingWentWrong: "儲存此剪輯時出現問題。",
|
|
147
147
|
finishingClip: "正在完成您的剪輯...",
|
|
148
148
|
loomSourcePreserved:
|
|
149
|
-
"Loom
|
|
149
|
+
"Loom 來源連結被保留。連接儲存:Builder.io(免費方案儲存 + AI)或 S3 相容儲存,Clips 將重試儲存自己的副本。",
|
|
150
150
|
clipDataPreserved:
|
|
151
151
|
"Your clip data is still preserved.連線Builder.io或S3存儲,Clips將自動上傳。",
|
|
152
152
|
details: "細節",
|
|
@@ -223,12 +223,13 @@ const messages = {
|
|
|
223
223
|
savingWentWrong: "儲存此剪輯時出現問題。",
|
|
224
224
|
finishingClip: "完成這個剪輯...",
|
|
225
225
|
loomPreservedManage:
|
|
226
|
-
"Loom
|
|
226
|
+
"Loom 來源連結被保留。連接儲存:Builder.io(免費方案儲存 + AI)或 S3 相容儲存,然後重試匯入。",
|
|
227
227
|
videoPreservedManage:
|
|
228
228
|
"影片被保留。連線Builder.io或S3存儲,Clips將完成上傳。",
|
|
229
|
-
creatorNeedsStorage:
|
|
229
|
+
creatorNeedsStorage:
|
|
230
|
+
"建立者需要先連接儲存才能完成此剪輯:Builder.io(免費方案儲存 + AI)或 S3 相容儲存。",
|
|
230
231
|
signInStorage:
|
|
231
|
-
"
|
|
232
|
+
"如果這是您的剪輯,請在此處登入以連接儲存:Builder.io(免費方案儲存 + AI)或 S3 相容儲存,並完成上傳。",
|
|
232
233
|
uploadNotCompleteSession:
|
|
233
234
|
"上傳尚未完成。開啟此剪輯的儀表板或要求建立者檢查存儲空間。",
|
|
234
235
|
uploadNotCompleteSignIn:
|
|
@@ -330,7 +331,8 @@ const messages = {
|
|
|
330
331
|
cleanupKept: "清理工作無法完成。保留了本機轉錄本。",
|
|
331
332
|
cleanupBuilderFailed:
|
|
332
333
|
"即使 Builder.io 已連線,清理也無法完成。保留了本機轉錄本。",
|
|
333
|
-
cleanupPaused:
|
|
334
|
+
cleanupPaused:
|
|
335
|
+
"清理已暫停。請在「設定」中連接 AI:Builder.io(免費額度)或你自己的 LLM 金鑰。",
|
|
334
336
|
builderNoResponse: "沒有收到Builder的回複。允許快顯窗口並重試。",
|
|
335
337
|
saveFailed: "儲存失敗({{status}})",
|
|
336
338
|
savedRetrying: "已儲存。正在重試轉錄...",
|
|
@@ -555,7 +557,7 @@ const messages = {
|
|
|
555
557
|
"Builder.io 是 Clips 上傳的主要存儲路徑。需要自帶存儲桶時,也可以使用 S3。",
|
|
556
558
|
checkingBuilder: "正在檢查 Builder.io",
|
|
557
559
|
builderConnected: "Builder.io 已連線",
|
|
558
|
-
connectBuilder: "
|
|
560
|
+
connectBuilder: "使用 Builder.io(免費)",
|
|
559
561
|
builderConnectedFor: "正在為 {{orgName}} 使用 Builder.io。",
|
|
560
562
|
builderConnectedGeneric: "新的剪輯會使用已連線的 Builder.io 提供方。",
|
|
561
563
|
builderIncludes: "包含物件存儲、上傳,以及新剪輯的託管轉錄。",
|
|
@@ -579,12 +581,11 @@ const messages = {
|
|
|
579
581
|
s3RegionLabel: "區域",
|
|
580
582
|
s3PublicBaseUrlLabel: "公開基礎 URL",
|
|
581
583
|
apiSetup: "AI 設定",
|
|
582
|
-
apiSetupDescription:
|
|
583
|
-
|
|
584
|
-
builderEasySetup: "Builder.io 是最簡單的設定方式",
|
|
584
|
+
apiSetupDescription: "使用 Builder.io 免費額度或你自己的 LLM 金鑰連接 AI。",
|
|
585
|
+
builderEasySetup: "Builder.io 免費額度",
|
|
585
586
|
builderAiAvailable: "Clips 可使用包含的 AI 額度和託管轉錄。",
|
|
586
587
|
builderAiDescription:
|
|
587
|
-
"
|
|
588
|
+
"優先使用 Builder.io,可獲得內建 AI 額度、物件儲存、上傳和託管轉錄。",
|
|
588
589
|
providerKeyTitle: "使用自己的提供方金鑰",
|
|
589
590
|
providerKeyDescription:
|
|
590
591
|
"新增 Anthropic、OpenAI、Gemini、Groq 或 OpenRouter 金鑰,用於按提供方計費的使用。",
|
|
@@ -723,7 +724,8 @@ const messages = {
|
|
|
723
724
|
title: "拼接錄製",
|
|
724
725
|
pickAtLeastTwo: "請選取至少 2 個錄製進行拼接",
|
|
725
726
|
videoUrlMissing: "一個或多個錄製還沒有可用的影片 URL",
|
|
726
|
-
connectStorage:
|
|
727
|
+
connectStorage:
|
|
728
|
+
"拼接錄製前請連接儲存:Builder.io(免費方案儲存 + AI)或 S3 相容儲存。",
|
|
727
729
|
created: "拼接錄製已建立",
|
|
728
730
|
failed: "拼接錄製失敗",
|
|
729
731
|
noOtherRecordings: "沒有其他可用錄製。",
|
|
@@ -947,9 +949,12 @@ const messages = {
|
|
|
947
949
|
builderTimeout: "5 分鐘內未收到 Builder 回應。請檢查快顯窗口並重試。",
|
|
948
950
|
builderConnected: "Builder.io 已連線",
|
|
949
951
|
waitingForBuilder: "正在等待 Builder...",
|
|
950
|
-
connectBuilder: "
|
|
952
|
+
connectBuilder: "使用 Builder.io(免費)",
|
|
951
953
|
free: "免費",
|
|
952
954
|
configureS3: "設定 S3 兼容存儲",
|
|
955
|
+
whyPrompt: "為什麼我會看到這個?",
|
|
956
|
+
whyDescription:
|
|
957
|
+
"Clips 是 100% 免費且開源的,因此你需要連接一種儲存 clips 的方式。使用 Builder.io 可取得免費方案儲存和 AI,或使用 S3 相容物件儲存和你自己的 LLM 金鑰。",
|
|
953
958
|
},
|
|
954
959
|
captureInstall: {
|
|
955
960
|
title: "Choose your recorder (已本機化)",
|
|
@@ -1152,9 +1157,9 @@ const messages = {
|
|
|
1152
1157
|
downloadRecording: "Download recording (已本機化)",
|
|
1153
1158
|
openRecorderInTab: "Open recorder in tab (已本機化)",
|
|
1154
1159
|
connectStorageToFinish:
|
|
1155
|
-
"
|
|
1160
|
+
"在下一屏連接儲存:Builder.io(免費方案儲存 + AI)或 S3 相容儲存。Clips 將完成儲存。",
|
|
1156
1161
|
connectStorageToRetryLoom:
|
|
1157
|
-
"
|
|
1162
|
+
"在下一屏連接儲存:Builder.io(免費方案儲存 + AI)或 S3 相容儲存。Clips 將重試匯入。",
|
|
1158
1163
|
},
|
|
1159
1164
|
dictateRoute: {
|
|
1160
1165
|
pageTitle: "Dictate · Clips (已本機化)",
|
|
@@ -114,6 +114,13 @@ function failureDetail(reason: string | null | undefined): string | null {
|
|
|
114
114
|
|
|
115
115
|
function nativeSaveFailureMessage(reason: string | null | undefined): string {
|
|
116
116
|
const text = reason ?? "";
|
|
117
|
+
if (
|
|
118
|
+
/finalization callback failed|missing required metadata|missing playback metadata|corrupted or incomplete|missing moov/i.test(
|
|
119
|
+
text,
|
|
120
|
+
)
|
|
121
|
+
) {
|
|
122
|
+
return "macOS could not finish writing this desktop recording. The local file is incomplete, so discard it from the Clips menu and record again.";
|
|
123
|
+
}
|
|
117
124
|
if (/too large|compression/i.test(text)) {
|
|
118
125
|
return "Clips tried to compress this desktop recording, but it is still too large to upload. The original is saved locally and can be retried from the Clips menu.";
|
|
119
126
|
}
|
|
@@ -1119,7 +1119,7 @@ export default function RecordRoute() {
|
|
|
1119
1119
|
markStorageConfigured(status);
|
|
1120
1120
|
if (!status.configured) {
|
|
1121
1121
|
throw new Error(
|
|
1122
|
-
"No video storage configured.
|
|
1122
|
+
"No video storage configured. Connect storage: Builder.io (free tier storage + AI) or S3-compatible storage.",
|
|
1123
1123
|
);
|
|
1124
1124
|
}
|
|
1125
1125
|
|
|
@@ -1329,7 +1329,7 @@ export default function RecordRoute() {
|
|
|
1329
1329
|
markStorageConfigured(status);
|
|
1330
1330
|
if (!status.configured) {
|
|
1331
1331
|
throw new Error(
|
|
1332
|
-
"No video storage configured.
|
|
1332
|
+
"No video storage configured. Connect storage: Builder.io (free tier storage + AI) or S3-compatible storage.",
|
|
1333
1333
|
);
|
|
1334
1334
|
}
|
|
1335
1335
|
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"name": "Agent-Native Clips",
|
|
4
4
|
"short_name": "Clips",
|
|
5
|
-
"version": "0.1.
|
|
5
|
+
"version": "0.1.5",
|
|
6
6
|
"description": "Start Clips recordings from Chrome, capture optional diagnostics, and preview Clips links on GitHub.",
|
|
7
7
|
"minimum_chrome_version": "116",
|
|
8
8
|
"action": {
|
|
@@ -8,6 +8,10 @@ const MAX_CONSOLE_LOGS = 400;
|
|
|
8
8
|
const MAX_NETWORK_REQUESTS = 400;
|
|
9
9
|
const MAX_MESSAGE_LENGTH = 2_000;
|
|
10
10
|
const MAX_URL_LENGTH = 1_000;
|
|
11
|
+
const STORAGE_SETUP_REQUIRED_MESSAGE =
|
|
12
|
+
"Connect storage to finish saving this clip: Builder.io (free tier storage + AI) or S3-compatible storage.";
|
|
13
|
+
const STORAGE_SETUP_FAILURE_RE =
|
|
14
|
+
/video storage is not connected|no video storage configured|file upload provider|storage provider|connect builder|s3-compatible/i;
|
|
11
15
|
const SECRET_KEY_FRAGMENT =
|
|
12
16
|
"(?:authorization|cookie|set[-_]?cookie|token|secret|password|passwd|pwd|api[-_]?key|apikey|session|credential)";
|
|
13
17
|
const AUTHORIZATION_SCHEME_RE =
|
|
@@ -151,6 +155,7 @@ type OffscreenStatusMessage = {
|
|
|
151
155
|
recordingId?: string;
|
|
152
156
|
result?: Record<string, unknown>;
|
|
153
157
|
error?: string;
|
|
158
|
+
storageSetupRequired?: boolean;
|
|
154
159
|
};
|
|
155
160
|
|
|
156
161
|
type ExtensionErrorMessage = {
|
|
@@ -162,6 +167,16 @@ type ExtensionErrorMessage = {
|
|
|
162
167
|
context?: Record<string, unknown>;
|
|
163
168
|
};
|
|
164
169
|
|
|
170
|
+
function isStorageSetupFailureMessage(message: string | null | undefined) {
|
|
171
|
+
return STORAGE_SETUP_FAILURE_RE.test(message ?? "");
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
function friendlyRecordingError(message: string | null | undefined): string {
|
|
175
|
+
return isStorageSetupFailureMessage(message)
|
|
176
|
+
? STORAGE_SETUP_REQUIRED_MESSAGE
|
|
177
|
+
: message || "Could not stop recording.";
|
|
178
|
+
}
|
|
179
|
+
|
|
165
180
|
type PendingNetworkRequest = {
|
|
166
181
|
requestId: string;
|
|
167
182
|
timestampMs: number;
|
|
@@ -1369,8 +1384,9 @@ async function stopRecording() {
|
|
|
1369
1384
|
};
|
|
1370
1385
|
} catch (err) {
|
|
1371
1386
|
recording.status = "error";
|
|
1372
|
-
recording.error =
|
|
1373
|
-
err instanceof Error ? err.message :
|
|
1387
|
+
recording.error = friendlyRecordingError(
|
|
1388
|
+
err instanceof Error ? err.message : null,
|
|
1389
|
+
);
|
|
1374
1390
|
resetOverlay();
|
|
1375
1391
|
await broadcastUnmount();
|
|
1376
1392
|
broadcastOverlayState();
|
|
@@ -2032,7 +2048,12 @@ async function dispatchRuntimeMessage(message: unknown): Promise<unknown> {
|
|
|
2032
2048
|
) {
|
|
2033
2049
|
activeNativeRecording.status = status.status;
|
|
2034
2050
|
activeNativeRecording.error =
|
|
2035
|
-
typeof status.error === "string"
|
|
2051
|
+
typeof status.error === "string"
|
|
2052
|
+
? status.storageSetupRequired === true ||
|
|
2053
|
+
isStorageSetupFailureMessage(status.error)
|
|
2054
|
+
? STORAGE_SETUP_REQUIRED_MESSAGE
|
|
2055
|
+
: status.error
|
|
2056
|
+
: null;
|
|
2036
2057
|
if (typeof status.recordingId === "string") {
|
|
2037
2058
|
activeNativeRecording.recordingId = status.recordingId;
|
|
2038
2059
|
activeNativeRecording.recordingUrl = recordingUrl(
|
|
@@ -23,6 +23,15 @@ import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
|
23
23
|
|
|
24
24
|
initExtensionSentry("offscreen");
|
|
25
25
|
|
|
26
|
+
const STORAGE_SETUP_REQUIRED_MESSAGE =
|
|
27
|
+
"Connect storage to finish saving this clip: Builder.io (free tier storage + AI) or S3-compatible storage.";
|
|
28
|
+
const STORAGE_SETUP_FAILURE_RE =
|
|
29
|
+
/video storage is not connected|no video storage configured|file upload provider|storage provider|connect builder|s3-compatible/i;
|
|
30
|
+
|
|
31
|
+
function isStorageSetupFailureMessage(message: string | null | undefined) {
|
|
32
|
+
return STORAGE_SETUP_FAILURE_RE.test(message ?? "");
|
|
33
|
+
}
|
|
34
|
+
|
|
26
35
|
type CaptureMode = "screen" | "camera";
|
|
27
36
|
|
|
28
37
|
type AcquireMessage = {
|
|
@@ -396,7 +405,14 @@ async function uploadChunk(
|
|
|
396
405
|
body,
|
|
397
406
|
});
|
|
398
407
|
const text = await res.text().catch(() => "");
|
|
399
|
-
|
|
408
|
+
let data: UploadResult = {};
|
|
409
|
+
if (text) {
|
|
410
|
+
try {
|
|
411
|
+
data = JSON.parse(text) as UploadResult;
|
|
412
|
+
} catch {
|
|
413
|
+
data = {};
|
|
414
|
+
}
|
|
415
|
+
}
|
|
400
416
|
if (!res.ok) {
|
|
401
417
|
console.error(
|
|
402
418
|
"[clips-offscreen] chunk upload failed",
|
|
@@ -405,9 +421,17 @@ async function uploadChunk(
|
|
|
405
421
|
Boolean(recording.authToken),
|
|
406
422
|
text.slice(0, 200),
|
|
407
423
|
);
|
|
424
|
+
const storageSetupRequired =
|
|
425
|
+
data?.storageSetupRequired === true ||
|
|
426
|
+
isStorageSetupFailureMessage(data?.error || text);
|
|
408
427
|
const error = new Error(
|
|
409
|
-
|
|
428
|
+
storageSetupRequired
|
|
429
|
+
? STORAGE_SETUP_REQUIRED_MESSAGE
|
|
430
|
+
: data?.error ||
|
|
431
|
+
`Upload failed (${res.status}): ${text || res.statusText}`,
|
|
410
432
|
);
|
|
433
|
+
(error as { storageSetupRequired?: boolean }).storageSetupRequired =
|
|
434
|
+
storageSetupRequired;
|
|
411
435
|
captureExtensionError(error, {
|
|
412
436
|
tags: {
|
|
413
437
|
surface: "offscreen",
|
|
@@ -845,6 +869,8 @@ async function finalizeStop(recording: ActiveRecording): Promise<void> {
|
|
|
845
869
|
reportStatus(recording.sessionId, "error", {
|
|
846
870
|
recordingId: recording.recordingId,
|
|
847
871
|
error: error.message,
|
|
872
|
+
storageSetupRequired: (error as { storageSetupRequired?: boolean })
|
|
873
|
+
.storageSetupRequired,
|
|
848
874
|
});
|
|
849
875
|
recording.rejectStopped(error);
|
|
850
876
|
}
|
|
@@ -405,6 +405,22 @@
|
|
|
405
405
|
|
|
406
406
|
<span id="status" class="status" role="status" aria-live="polite"></span>
|
|
407
407
|
|
|
408
|
+
<div id="storage-help" class="storage-help" hidden>
|
|
409
|
+
<button id="storage-help-open" class="storage-help-open" type="button">
|
|
410
|
+
Use Builder.io (free)
|
|
411
|
+
</button>
|
|
412
|
+
<span aria-hidden="true">·</span>
|
|
413
|
+
<span class="storage-help-why" tabindex="0">
|
|
414
|
+
Why am I seeing this?
|
|
415
|
+
<span class="storage-help-details" role="tooltip">
|
|
416
|
+
Clips is 100% free and open source, so you need to hook up a way to
|
|
417
|
+
store your clips. Connect storage with Builder.io for free-tier
|
|
418
|
+
storage and AI, or use S3-compatible object storage and your own LLM
|
|
419
|
+
keys.
|
|
420
|
+
</span>
|
|
421
|
+
</span>
|
|
422
|
+
</div>
|
|
423
|
+
|
|
408
424
|
<nav class="bottom-row" aria-label="Clips shortcuts">
|
|
409
425
|
<button id="open-library" class="bottom-button" type="button">
|
|
410
426
|
<svg viewBox="0 0 24 24" fill="none" aria-hidden="true">
|
|
@@ -93,9 +93,17 @@ const FEEDBACK_URL =
|
|
|
93
93
|
const FEEDBACK_PLACEHOLDER = "Tell us what's on your mind...";
|
|
94
94
|
const FEEDBACK_SUBMIT_TEXT = "Send feedback";
|
|
95
95
|
const FEEDBACK_SUCCESS_MESSAGE = "Thanks for the feedback!";
|
|
96
|
+
const STORAGE_SETUP_REQUIRED_MESSAGE =
|
|
97
|
+
"Connect storage to finish saving this clip: Builder.io (free tier storage + AI) or S3-compatible storage.";
|
|
98
|
+
const STORAGE_SETUP_FAILURE_RE =
|
|
99
|
+
/video storage is not connected|no video storage configured|file upload provider|storage provider|connect builder|s3-compatible/i;
|
|
96
100
|
const feedbackTarget = parseFeedbackTarget(FEEDBACK_URL);
|
|
97
101
|
const feedbackSchemaCache = new Map<string, Promise<FeedbackFormSchema>>();
|
|
98
102
|
|
|
103
|
+
function isStorageSetupFailureMessage(message: string | null | undefined) {
|
|
104
|
+
return STORAGE_SETUP_FAILURE_RE.test(message ?? "");
|
|
105
|
+
}
|
|
106
|
+
|
|
99
107
|
function screenSurface(
|
|
100
108
|
value: CaptureSurface,
|
|
101
109
|
): Exclude<CaptureSurface, "camera"> {
|
|
@@ -507,6 +515,14 @@ function setStatus(message: string, kind: "info" | "error" = "info"): void {
|
|
|
507
515
|
status.dataset.kind = kind;
|
|
508
516
|
}
|
|
509
517
|
|
|
518
|
+
function setStorageHelp(visible: boolean): void {
|
|
519
|
+
byId<HTMLDivElement>("storage-help").hidden = !visible;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
function storageSetupUrl(settings: ExtensionSettings): string {
|
|
523
|
+
return `${settings.clipsBaseUrl.replace(/\/+$/, "")}/record`;
|
|
524
|
+
}
|
|
525
|
+
|
|
510
526
|
function renderMode(settings: ExtensionSettings): void {
|
|
511
527
|
const mode = recordingMode(settings);
|
|
512
528
|
for (const button of document.querySelectorAll<HTMLButtonElement>(
|
|
@@ -690,7 +706,10 @@ function renderActiveRecording(recording: NativeRecording | null): void {
|
|
|
690
706
|
start.hidden = active;
|
|
691
707
|
signIn.hidden = true;
|
|
692
708
|
if (recordingActions) recordingActions.hidden = !active;
|
|
693
|
-
if (!recording)
|
|
709
|
+
if (!recording) {
|
|
710
|
+
setStorageHelp(false);
|
|
711
|
+
return;
|
|
712
|
+
}
|
|
694
713
|
|
|
695
714
|
recordingTitle.textContent = recording.targetTitle || "Current recording";
|
|
696
715
|
const host = hostnameLabel(recording.targetUrl);
|
|
@@ -706,10 +725,16 @@ function renderActiveRecording(recording: NativeRecording | null): void {
|
|
|
706
725
|
: recording.status === "stopping"
|
|
707
726
|
? "Stopping..."
|
|
708
727
|
: recording.status === "error"
|
|
709
|
-
? recording.error
|
|
728
|
+
? isStorageSetupFailureMessage(recording.error)
|
|
729
|
+
? STORAGE_SETUP_REQUIRED_MESSAGE
|
|
730
|
+
: recording.error || "Recording needs attention"
|
|
710
731
|
: `Recording ${formatDuration(recording.startedAtMs)}`;
|
|
711
732
|
recordingStatus.dataset.kind =
|
|
712
733
|
recording.status === "error" ? "error" : "info";
|
|
734
|
+
setStorageHelp(
|
|
735
|
+
recording.status === "error" &&
|
|
736
|
+
isStorageSetupFailureMessage(recording.error),
|
|
737
|
+
);
|
|
713
738
|
}
|
|
714
739
|
|
|
715
740
|
async function init(): Promise<void> {
|
|
@@ -751,6 +776,7 @@ async function init(): Promise<void> {
|
|
|
751
776
|
const openSettings = byId<HTMLButtonElement>("open-settings");
|
|
752
777
|
const openRecent = byId<HTMLButtonElement>("open-recent");
|
|
753
778
|
const signIn = byId<HTMLButtonElement>("sign-in");
|
|
779
|
+
const storageHelpOpen = byId<HTMLButtonElement>("storage-help-open");
|
|
754
780
|
let activeRecording: NativeRecording | null = null;
|
|
755
781
|
let authStatus: AuthStatus = "checking";
|
|
756
782
|
let feedbackOpenedAt = 0;
|
|
@@ -1071,6 +1097,7 @@ async function init(): Promise<void> {
|
|
|
1071
1097
|
start.addEventListener("click", async () => {
|
|
1072
1098
|
start.disabled = true;
|
|
1073
1099
|
signIn.hidden = true;
|
|
1100
|
+
setStorageHelp(false);
|
|
1074
1101
|
setStatus(""); // no chatty "Checking…/Starting…" text — the disabled button is enough
|
|
1075
1102
|
try {
|
|
1076
1103
|
authStatus = await readAuthStatus(settings);
|
|
@@ -1085,10 +1112,11 @@ async function init(): Promise<void> {
|
|
|
1085
1112
|
if (!storageConfigured) {
|
|
1086
1113
|
start.disabled = false;
|
|
1087
1114
|
setStatus(
|
|
1088
|
-
"Connect Builder.io
|
|
1115
|
+
"Connect storage in Clips first: Builder.io (free tier storage + AI) or S3-compatible storage.",
|
|
1089
1116
|
"error",
|
|
1090
1117
|
);
|
|
1091
|
-
|
|
1118
|
+
setStorageHelp(true);
|
|
1119
|
+
await createTab(storageSetupUrl(settings));
|
|
1092
1120
|
window.close();
|
|
1093
1121
|
return;
|
|
1094
1122
|
}
|
|
@@ -1117,7 +1145,14 @@ async function init(): Promise<void> {
|
|
|
1117
1145
|
setStatus("");
|
|
1118
1146
|
return;
|
|
1119
1147
|
}
|
|
1120
|
-
|
|
1148
|
+
const storageSetupFailure = isStorageSetupFailureMessage(message);
|
|
1149
|
+
setStorageHelp(storageSetupFailure);
|
|
1150
|
+
setStatus(
|
|
1151
|
+
storageSetupFailure
|
|
1152
|
+
? "Connect storage in Clips first: Builder.io (free tier storage + AI) or S3-compatible storage."
|
|
1153
|
+
: message,
|
|
1154
|
+
"error",
|
|
1155
|
+
);
|
|
1121
1156
|
} catch (err) {
|
|
1122
1157
|
captureExtensionError(err, {
|
|
1123
1158
|
tags: { surface: "popup", action: "start-recording" },
|
|
@@ -1128,13 +1163,24 @@ async function init(): Promise<void> {
|
|
|
1128
1163
|
},
|
|
1129
1164
|
});
|
|
1130
1165
|
start.disabled = false;
|
|
1166
|
+
const message =
|
|
1167
|
+
err instanceof Error ? err.message : "Could not start Clips.";
|
|
1168
|
+
const storageSetupFailure = isStorageSetupFailureMessage(message);
|
|
1169
|
+
setStorageHelp(storageSetupFailure);
|
|
1131
1170
|
setStatus(
|
|
1132
|
-
|
|
1171
|
+
storageSetupFailure
|
|
1172
|
+
? "Connect storage in Clips first: Builder.io (free tier storage + AI) or S3-compatible storage."
|
|
1173
|
+
: message,
|
|
1133
1174
|
"error",
|
|
1134
1175
|
);
|
|
1135
1176
|
}
|
|
1136
1177
|
});
|
|
1137
1178
|
|
|
1179
|
+
storageHelpOpen.addEventListener("click", async () => {
|
|
1180
|
+
await createTab(storageSetupUrl(settings));
|
|
1181
|
+
window.close();
|
|
1182
|
+
});
|
|
1183
|
+
|
|
1138
1184
|
signIn.addEventListener("click", async () => {
|
|
1139
1185
|
signIn.disabled = true;
|
|
1140
1186
|
const response = await sendRuntimeMessage<PopupStartResponse>({
|
|
@@ -1161,7 +1207,13 @@ async function init(): Promise<void> {
|
|
|
1161
1207
|
}
|
|
1162
1208
|
stop.disabled = false;
|
|
1163
1209
|
discard.disabled = false;
|
|
1164
|
-
|
|
1210
|
+
const message = response.error || "Could not stop recording.";
|
|
1211
|
+
const storageSetupFailure = isStorageSetupFailureMessage(message);
|
|
1212
|
+
setStorageHelp(storageSetupFailure);
|
|
1213
|
+
setStatus(
|
|
1214
|
+
storageSetupFailure ? STORAGE_SETUP_REQUIRED_MESSAGE : message,
|
|
1215
|
+
"error",
|
|
1216
|
+
);
|
|
1165
1217
|
});
|
|
1166
1218
|
|
|
1167
1219
|
discard.addEventListener("click", async () => {
|
|
@@ -67,6 +67,31 @@ function scrubEvent<T extends Sentry.Event>(event: T): T {
|
|
|
67
67
|
return event;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function shouldDropExpectedMediaPermissionError(
|
|
71
|
+
error: unknown,
|
|
72
|
+
context: CaptureContext,
|
|
73
|
+
): boolean {
|
|
74
|
+
const tags = context.tags ?? {};
|
|
75
|
+
const isPermissionProbe =
|
|
76
|
+
tags.surface === "permission" &&
|
|
77
|
+
(tags.permission === "mic" || tags.permission === "cam");
|
|
78
|
+
const isOptionalBubbleCamera =
|
|
79
|
+
tags.surface === "overlay" && tags.overlayPart === "bubble";
|
|
80
|
+
if (!isPermissionProbe && !isOptionalBubbleCamera) return false;
|
|
81
|
+
|
|
82
|
+
const text =
|
|
83
|
+
error instanceof Error
|
|
84
|
+
? `${error.name}: ${error.message}`
|
|
85
|
+
: String(error ?? "");
|
|
86
|
+
|
|
87
|
+
return (
|
|
88
|
+
/\bNotAllowedError\b/i.test(text) ||
|
|
89
|
+
/\bPermission (?:denied|dismissed)\b/i.test(text) ||
|
|
90
|
+
/\b(?:NotFoundError|DevicesNotFoundError)\b/i.test(text) ||
|
|
91
|
+
/\bRequested device not found\b/i.test(text)
|
|
92
|
+
);
|
|
93
|
+
}
|
|
94
|
+
|
|
70
95
|
function scrubScopeString(value: string): string {
|
|
71
96
|
const scrubbed = scrubUrl(value, { redactAllQueryValues: true });
|
|
72
97
|
if (scrubbed !== value) return scrubbed;
|
|
@@ -175,7 +200,9 @@ export function captureExtensionError(
|
|
|
175
200
|
error: unknown,
|
|
176
201
|
context: CaptureContext = {},
|
|
177
202
|
): string | undefined {
|
|
178
|
-
if (!initialized)
|
|
203
|
+
if (!initialized || shouldDropExpectedMediaPermissionError(error, context)) {
|
|
204
|
+
return undefined;
|
|
205
|
+
}
|
|
179
206
|
try {
|
|
180
207
|
return Sentry.withScope((scope) => {
|
|
181
208
|
if (context.tags) {
|