@agent-native/core 0.77.6 → 0.77.7
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 +1 -1
- package/corpus/core/CHANGELOG.md +12 -0
- package/corpus/core/package.json +3 -3
- package/corpus/core/src/agent/thread-data-builder.ts +21 -2
- package/corpus/core/src/cli/create.ts +35 -1
- package/corpus/core/src/cli/skills.ts +8 -0
- package/corpus/core/src/client/AgentPanel.tsx +57 -25
- package/corpus/core/src/client/MultiTabAssistantChat.tsx +31 -1
- package/corpus/core/src/client/analytics.ts +24 -2
- package/corpus/core/src/client/use-agent-engine-configured.ts +29 -15
- package/corpus/core/src/client/use-chat-threads.ts +104 -55
- package/corpus/core/src/server/analytics.ts +27 -3
- package/corpus/core/src/sharing/access.ts +35 -13
- package/corpus/core/src/sharing/actions/share-resource.ts +32 -9
- package/corpus/core/src/sharing/actions/unshare-resource.ts +25 -2
- package/corpus/core/src/sharing/registry.ts +9 -0
- package/corpus/core/src/vite/client.ts +3 -0
- package/corpus/templates/analytics/actions/data-source-status.ts +25 -14
- package/corpus/templates/analytics/app/components/layout/NewAnalysisDialog.tsx +2 -2
- package/corpus/templates/analytics/app/lib/data-source-status.ts +47 -6
- package/corpus/templates/analytics/app/lib/data-sources.ts +14 -3
- package/corpus/templates/analytics/app/pages/DataSources.tsx +24 -5
- package/corpus/templates/analytics/app/pages/adhoc/sql-dashboard/PanelEditorDialog.tsx +15 -43
- package/corpus/templates/analytics/changelog/2026-06-25-fixed-hubspot-data-source-status-so-private-app-tokens-legac.md +6 -0
- package/corpus/templates/analytics/netlify.toml +1 -0
- package/corpus/templates/analytics/server/lib/credential-keys.ts +3 -1
- package/corpus/templates/analytics/server/routes/api/test-connection.post.ts +12 -3
- package/corpus/templates/assets/app/components/layout/Sidebar.tsx +5 -2
- package/corpus/templates/assets/changelog/2026-06-25-create-now-returns-from-agent-settings-to-the-asset-chat-eve.md +6 -0
- package/corpus/templates/assets/netlify.toml +1 -0
- package/corpus/templates/brain/netlify.toml +1 -0
- package/corpus/templates/calendar/app/i18n-data.ts +12 -0
- package/corpus/templates/calendar/app/lib/clipboard.ts +58 -0
- package/corpus/templates/calendar/app/pages/AvailabilitySettings.tsx +15 -9
- package/corpus/templates/calendar/app/pages/BookingLinksPage.tsx +6 -2
- package/corpus/templates/calendar/changelog/2026-06-25-booking-link-copy-buttons-no-longer-fail-when-clipboard-perm.md +6 -0
- package/corpus/templates/calendar/netlify.toml +1 -0
- package/corpus/templates/chat/netlify.toml +1 -0
- package/corpus/templates/clips/AGENTS.md +9 -7
- package/corpus/templates/clips/actions/add-vocabulary-term.ts +2 -2
- package/corpus/templates/clips/actions/archive-recording.ts +5 -2
- package/corpus/templates/clips/actions/clear-edits.ts +5 -2
- package/corpus/templates/clips/actions/create-folder.ts +7 -2
- package/corpus/templates/clips/actions/delete-folder.ts +7 -4
- package/corpus/templates/clips/actions/delete-recording-permanent.ts +5 -2
- package/corpus/templates/clips/actions/finalize-recording.ts +5 -2
- package/corpus/templates/clips/actions/import-loom-recording.ts +2 -1
- package/corpus/templates/clips/actions/list-notifications.ts +9 -5
- package/corpus/templates/clips/actions/list-organization-state.ts +2 -1
- package/corpus/templates/clips/actions/list-recordings.ts +4 -1
- package/corpus/templates/clips/actions/move-recording.ts +5 -1
- package/corpus/templates/clips/actions/rename-folder.ts +6 -3
- package/corpus/templates/clips/actions/request-transcript.ts +5 -2
- package/corpus/templates/clips/actions/restore-recording.ts +5 -2
- package/corpus/templates/clips/actions/save-browser-diagnostics.ts +21 -8
- package/corpus/templates/clips/actions/set-chapters.ts +5 -2
- package/corpus/templates/clips/actions/set-thumbnail.ts +5 -2
- package/corpus/templates/clips/actions/split-recording.ts +5 -2
- package/corpus/templates/clips/actions/stitch-recordings.ts +6 -2
- package/corpus/templates/clips/actions/trash-recording.ts +5 -2
- package/corpus/templates/clips/actions/undo-edit.ts +5 -2
- package/corpus/templates/clips/actions/view-screen.ts +2 -1
- package/corpus/templates/clips/app/components/library/recording-card.tsx +24 -7
- package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +5 -1
- package/corpus/templates/clips/changelog/2026-06-25-chrome-extension-recordings-now-upload-large-capture-bursts.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-25-console-diagnostics-keep-more-useful-non-secret-details-when.md +6 -0
- package/corpus/templates/clips/changelog/2026-06-25-fixed-owner-recognition-so-clips-created-from-desktop-and-we.md +6 -0
- package/corpus/templates/clips/chrome-extension/package.json +3 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +1 -1
- package/corpus/templates/clips/chrome-extension/src/background.ts +85 -9
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +55 -0
- package/corpus/templates/clips/chrome-extension/src/offscreen.ts +79 -3
- package/corpus/templates/clips/chrome-extension/src/options.ts +10 -0
- package/corpus/templates/clips/chrome-extension/src/overlay.ts +6 -0
- package/corpus/templates/clips/chrome-extension/src/permission.ts +8 -1
- package/corpus/templates/clips/chrome-extension/src/popup.ts +65 -39
- package/corpus/templates/clips/chrome-extension/src/sentry.ts +206 -0
- package/corpus/templates/clips/chrome-extension/vite.config.ts +47 -0
- package/corpus/templates/clips/netlify.toml +1 -0
- package/corpus/templates/clips/server/db/index.ts +1 -0
- package/corpus/templates/clips/server/lib/google-calendar-oauth.ts +35 -23
- package/corpus/templates/clips/server/lib/public-agent-context.ts +8 -1
- package/corpus/templates/clips/server/lib/recordings.ts +18 -2
- package/corpus/templates/clips/server/lib/slack-oauth.ts +9 -6
- package/corpus/templates/clips/server/plugins/onboarding-calendar.ts +6 -2
- package/corpus/templates/clips/server/routes/api/public-recording.get.ts +2 -2
- package/corpus/templates/clips/server/routes/api/recordings/[recordingId]/thumbnail.post.ts +5 -2
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/abort.post.ts +5 -2
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/chunk.post.ts +5 -2
- package/corpus/templates/clips/server/routes/api/uploads/[recordingId]/reset-chunks.post.ts +5 -2
- package/corpus/templates/clips/shared/browser-diagnostics.ts +14 -4
- package/corpus/templates/content/netlify.toml +1 -0
- package/corpus/templates/design/netlify.toml +1 -0
- package/corpus/templates/dispatch/netlify.toml +1 -0
- package/corpus/templates/forms/netlify.toml +1 -0
- package/corpus/templates/macros/netlify.toml +1 -0
- package/corpus/templates/mail/app/components/email/EmailList.tsx +7 -3
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +4 -1
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +10 -2
- package/corpus/templates/mail/app/hooks/use-compose-state.ts +15 -5
- package/corpus/templates/mail/app/hooks/use-google-auth.ts +47 -9
- package/corpus/templates/mail/changelog/2026-06-25-account-avatars-stay-stable-while-mail-refreshes-google-acco.md +6 -0
- package/corpus/templates/mail/changelog/2026-06-25-mail-background-draft-saves-and-thread-prefetches-no-longer.md +6 -0
- package/corpus/templates/mail/netlify.toml +1 -0
- package/corpus/templates/plan/.agents/skills/visual-plan/references/canvas.md +8 -0
- package/corpus/templates/plan/netlify.toml +1 -0
- package/corpus/templates/slides/netlify.toml +1 -0
- package/corpus/templates/videos/netlify.toml +1 -0
- package/dist/agent/thread-data-builder.d.ts.map +1 -1
- package/dist/agent/thread-data-builder.js +23 -2
- package/dist/agent/thread-data-builder.js.map +1 -1
- package/dist/cli/create.d.ts.map +1 -1
- package/dist/cli/create.js +28 -1
- package/dist/cli/create.js.map +1 -1
- package/dist/cli/skills.d.ts +1 -1
- package/dist/cli/skills.d.ts.map +1 -1
- package/dist/cli/skills.js +8 -0
- package/dist/cli/skills.js.map +1 -1
- package/dist/client/AgentPanel.d.ts +8 -2
- package/dist/client/AgentPanel.d.ts.map +1 -1
- package/dist/client/AgentPanel.js +29 -15
- package/dist/client/AgentPanel.js.map +1 -1
- package/dist/client/MultiTabAssistantChat.d.ts.map +1 -1
- package/dist/client/MultiTabAssistantChat.js +4 -4
- package/dist/client/MultiTabAssistantChat.js.map +1 -1
- package/dist/client/analytics.d.ts.map +1 -1
- package/dist/client/analytics.js +22 -2
- package/dist/client/analytics.js.map +1 -1
- package/dist/client/use-agent-engine-configured.d.ts.map +1 -1
- package/dist/client/use-agent-engine-configured.js +23 -13
- package/dist/client/use-agent-engine-configured.js.map +1 -1
- package/dist/client/use-chat-threads.d.ts +4 -0
- package/dist/client/use-chat-threads.d.ts.map +1 -1
- package/dist/client/use-chat-threads.js +49 -4
- package/dist/client/use-chat-threads.js.map +1 -1
- package/dist/collab/routes.d.ts +2 -2
- package/dist/notifications/routes.d.ts +1 -1
- package/dist/observability/routes.d.ts +5 -5
- package/dist/resources/handlers.d.ts +2 -2
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/analytics.d.ts +4 -0
- package/dist/server/analytics.d.ts.map +1 -1
- package/dist/server/analytics.js +19 -3
- package/dist/server/analytics.js.map +1 -1
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/dist/sharing/access.d.ts.map +1 -1
- package/dist/sharing/access.js +26 -12
- package/dist/sharing/access.js.map +1 -1
- package/dist/sharing/actions/share-resource.js +20 -9
- package/dist/sharing/actions/share-resource.js.map +1 -1
- package/dist/sharing/actions/unshare-resource.js +13 -2
- package/dist/sharing/actions/unshare-resource.js.map +1 -1
- package/dist/sharing/registry.d.ts +9 -0
- package/dist/sharing/registry.d.ts.map +1 -1
- package/dist/sharing/registry.js.map +1 -1
- package/dist/vite/client.d.ts.map +1 -1
- package/dist/vite/client.js +1 -0
- package/dist/vite/client.js.map +1 -1
- package/package.json +3 -3
|
@@ -26,8 +26,12 @@ function redactString(value: string): string {
|
|
|
26
26
|
return redactBrowserDiagnosticString(value);
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
function redactUrlString(value: string): string {
|
|
30
|
+
return redactBrowserDiagnosticString(value, { redactQueryValues: true });
|
|
31
|
+
}
|
|
32
|
+
|
|
29
33
|
function sanitizeUrl(raw: string): string {
|
|
30
|
-
const redacted =
|
|
34
|
+
const redacted = redactUrlString(raw);
|
|
31
35
|
try {
|
|
32
36
|
const parsed = new URL(redacted, window.location.href);
|
|
33
37
|
parsed.username = "";
|
|
@@ -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.3",
|
|
6
6
|
"description": "Start Clips recordings from Chrome with optional redacted console and network diagnostics.",
|
|
7
7
|
"minimum_chrome_version": "116",
|
|
8
8
|
"action": {
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
2
|
+
|
|
3
|
+
initExtensionSentry("background");
|
|
4
|
+
|
|
1
5
|
const DEFAULT_CLIPS_BASE_URL = "https://clips.agent-native.com";
|
|
2
6
|
const DEBUGGER_PROTOCOL_VERSION = "1.3";
|
|
3
7
|
const MAX_CONSOLE_LOGS = 400;
|
|
@@ -149,6 +153,15 @@ type OffscreenStatusMessage = {
|
|
|
149
153
|
error?: string;
|
|
150
154
|
};
|
|
151
155
|
|
|
156
|
+
type ExtensionErrorMessage = {
|
|
157
|
+
type: "CLIPS_EXTENSION_ERROR";
|
|
158
|
+
surface?: string;
|
|
159
|
+
name?: string;
|
|
160
|
+
message?: string;
|
|
161
|
+
stack?: string;
|
|
162
|
+
context?: Record<string, unknown>;
|
|
163
|
+
};
|
|
164
|
+
|
|
152
165
|
type PendingNetworkRequest = {
|
|
153
166
|
requestId: string;
|
|
154
167
|
timestampMs: number;
|
|
@@ -516,19 +529,24 @@ function truncate(value: string, maxLength: number): string {
|
|
|
516
529
|
return value.length > maxLength ? `${value.slice(0, maxLength)}...` : value;
|
|
517
530
|
}
|
|
518
531
|
|
|
519
|
-
function redactString(
|
|
520
|
-
|
|
532
|
+
function redactString(
|
|
533
|
+
value: string,
|
|
534
|
+
options: { redactQueryValues?: boolean } = {},
|
|
535
|
+
): string {
|
|
536
|
+
const redacted = value
|
|
521
537
|
.replace(AUTHORIZATION_SCHEME_RE, "$1$2<redacted>")
|
|
522
538
|
.replace(/\b(bearer|basic)\s+[a-z0-9._~+/-]+=*/gi, "$1 <redacted>")
|
|
523
539
|
.replace(DOUBLE_QUOTED_SECRET_VALUE_RE, '$1$2$1$3"<redacted>"')
|
|
524
540
|
.replace(SINGLE_QUOTED_SECRET_VALUE_RE, "$1$2$1$3'<redacted>'")
|
|
525
|
-
.replace(UNQUOTED_SECRET_VALUE_RE, "$1$2$1$3<redacted>")
|
|
526
|
-
|
|
541
|
+
.replace(UNQUOTED_SECRET_VALUE_RE, "$1$2$1$3<redacted>");
|
|
542
|
+
return options.redactQueryValues
|
|
543
|
+
? redacted.replace(/([?&][^=\s&?#]+)=([^&\s#]+)/g, "$1=<redacted>")
|
|
544
|
+
: redacted;
|
|
527
545
|
}
|
|
528
546
|
|
|
529
547
|
function sanitizeUrl(raw: string | null | undefined): string | null {
|
|
530
548
|
if (!raw) return null;
|
|
531
|
-
const redacted = redactString(raw);
|
|
549
|
+
const redacted = redactString(raw, { redactQueryValues: true });
|
|
532
550
|
try {
|
|
533
551
|
const parsed = new URL(redacted);
|
|
534
552
|
parsed.username = "";
|
|
@@ -1016,6 +1034,17 @@ async function armRecording(args: {
|
|
|
1016
1034
|
visibility: "public",
|
|
1017
1035
|
});
|
|
1018
1036
|
} catch (err) {
|
|
1037
|
+
captureExtensionError(err, {
|
|
1038
|
+
tags: {
|
|
1039
|
+
surface: "background",
|
|
1040
|
+
recordingStep: "create-recording",
|
|
1041
|
+
},
|
|
1042
|
+
extra: {
|
|
1043
|
+
captureSurface: settings.captureSurface,
|
|
1044
|
+
includeCamera: settings.includeCamera,
|
|
1045
|
+
includeMicrophone: settings.includeMicrophone,
|
|
1046
|
+
},
|
|
1047
|
+
});
|
|
1019
1048
|
await abortArming();
|
|
1020
1049
|
throw err;
|
|
1021
1050
|
}
|
|
@@ -1070,6 +1099,18 @@ async function armRecording(args: {
|
|
|
1070
1099
|
});
|
|
1071
1100
|
} catch (err) {
|
|
1072
1101
|
console.error("[clips-bg] arm: BEGIN failed", err);
|
|
1102
|
+
captureExtensionError(err, {
|
|
1103
|
+
tags: {
|
|
1104
|
+
surface: "background",
|
|
1105
|
+
recordingStep: "offscreen-begin",
|
|
1106
|
+
},
|
|
1107
|
+
extra: {
|
|
1108
|
+
recordingId: created.id,
|
|
1109
|
+
captureSurface: settings.captureSurface,
|
|
1110
|
+
includeCamera: settings.includeCamera,
|
|
1111
|
+
includeMicrophone: settings.includeMicrophone,
|
|
1112
|
+
},
|
|
1113
|
+
});
|
|
1073
1114
|
await cancelRecording();
|
|
1074
1115
|
throw err;
|
|
1075
1116
|
}
|
|
@@ -1553,9 +1594,15 @@ function pushConsole(
|
|
|
1553
1594
|
const timestampMs = Number.isFinite(entry.timestampMs)
|
|
1554
1595
|
? (entry.timestampMs as number)
|
|
1555
1596
|
: nowMs();
|
|
1556
|
-
const message = truncate(
|
|
1597
|
+
const message = truncate(
|
|
1598
|
+
redactString(entry.message, { redactQueryValues: true }),
|
|
1599
|
+
MAX_MESSAGE_LENGTH,
|
|
1600
|
+
);
|
|
1557
1601
|
const stack = entry.stack
|
|
1558
|
-
? truncate(
|
|
1602
|
+
? truncate(
|
|
1603
|
+
redactString(entry.stack, { redactQueryValues: true }),
|
|
1604
|
+
MAX_MESSAGE_LENGTH,
|
|
1605
|
+
)
|
|
1559
1606
|
: "";
|
|
1560
1607
|
session.consoleLogs.push({
|
|
1561
1608
|
timestampMs,
|
|
@@ -1762,7 +1809,10 @@ function handleResponseReceived(
|
|
|
1762
1809
|
pending.ok = response.status >= 200 && response.status < 400;
|
|
1763
1810
|
}
|
|
1764
1811
|
if (typeof response.statusText === "string") {
|
|
1765
|
-
pending.statusText = truncate(
|
|
1812
|
+
pending.statusText = truncate(
|
|
1813
|
+
redactString(response.statusText, { redactQueryValues: true }),
|
|
1814
|
+
120,
|
|
1815
|
+
);
|
|
1766
1816
|
}
|
|
1767
1817
|
}
|
|
1768
1818
|
|
|
@@ -1792,7 +1842,12 @@ function finalizeNetworkRequest(
|
|
|
1792
1842
|
...(typeof pending.ok === "boolean" ? { ok: pending.ok } : {}),
|
|
1793
1843
|
durationMs: Math.round(durationMs),
|
|
1794
1844
|
...(error
|
|
1795
|
-
? {
|
|
1845
|
+
? {
|
|
1846
|
+
error: truncate(
|
|
1847
|
+
redactString(error, { redactQueryValues: true }),
|
|
1848
|
+
MAX_MESSAGE_LENGTH,
|
|
1849
|
+
),
|
|
1850
|
+
}
|
|
1796
1851
|
: {}),
|
|
1797
1852
|
});
|
|
1798
1853
|
}
|
|
@@ -1923,6 +1978,12 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
|
1923
1978
|
(message as { type?: unknown })?.type,
|
|
1924
1979
|
err,
|
|
1925
1980
|
);
|
|
1981
|
+
captureExtensionError(err, {
|
|
1982
|
+
tags: {
|
|
1983
|
+
surface: "background",
|
|
1984
|
+
messageType: String((message as { type?: unknown })?.type ?? ""),
|
|
1985
|
+
},
|
|
1986
|
+
});
|
|
1926
1987
|
response = {
|
|
1927
1988
|
ok: false,
|
|
1928
1989
|
error: err instanceof Error ? err.message : "Could not use Clips.",
|
|
@@ -1940,6 +2001,21 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
|
1940
2001
|
async function dispatchRuntimeMessage(message: unknown): Promise<unknown> {
|
|
1941
2002
|
const type = (message as { type?: unknown }).type;
|
|
1942
2003
|
|
|
2004
|
+
if (type === "CLIPS_EXTENSION_ERROR") {
|
|
2005
|
+
const report = message as ExtensionErrorMessage;
|
|
2006
|
+
const error = new Error(report.message || "Chrome extension error");
|
|
2007
|
+
error.name = report.name || "ExtensionError";
|
|
2008
|
+
if (report.stack) error.stack = report.stack;
|
|
2009
|
+
captureExtensionError(error, {
|
|
2010
|
+
tags: {
|
|
2011
|
+
surface: report.surface || "content-script",
|
|
2012
|
+
mechanism: "content-script-report",
|
|
2013
|
+
},
|
|
2014
|
+
extra: report.context,
|
|
2015
|
+
});
|
|
2016
|
+
return { ok: true };
|
|
2017
|
+
}
|
|
2018
|
+
|
|
1943
2019
|
// Status updates streamed from the offscreen recorder.
|
|
1944
2020
|
if (type === "CLIPS_NATIVE_STATUS") {
|
|
1945
2021
|
const status = message as OffscreenStatusMessage;
|
|
@@ -18,6 +18,61 @@
|
|
|
18
18
|
const ALL_PARTS: OverlayPart[] = ["bubble", "countdown", "toolbar", "saving"];
|
|
19
19
|
const flags = window as unknown as { __clipsOverlayHostReady?: boolean };
|
|
20
20
|
|
|
21
|
+
function errorPayload(error: unknown): {
|
|
22
|
+
name: string;
|
|
23
|
+
message: string;
|
|
24
|
+
stack?: string;
|
|
25
|
+
} {
|
|
26
|
+
if (error instanceof Error) {
|
|
27
|
+
return {
|
|
28
|
+
name: error.name || "Error",
|
|
29
|
+
message: error.message || "Unknown content-script error",
|
|
30
|
+
stack: error.stack,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
return {
|
|
34
|
+
name: "Error",
|
|
35
|
+
message: String(error ?? "Unknown content-script error"),
|
|
36
|
+
};
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function reportContentScriptError(
|
|
40
|
+
error: unknown,
|
|
41
|
+
context: Record<string, unknown> = {},
|
|
42
|
+
): void {
|
|
43
|
+
try {
|
|
44
|
+
chrome.runtime.sendMessage(
|
|
45
|
+
{
|
|
46
|
+
type: "CLIPS_EXTENSION_ERROR",
|
|
47
|
+
surface: "content-script",
|
|
48
|
+
...errorPayload(error),
|
|
49
|
+
context: {
|
|
50
|
+
...context,
|
|
51
|
+
pageUrl: location.href,
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
() => void chrome.runtime.lastError,
|
|
55
|
+
);
|
|
56
|
+
} catch {
|
|
57
|
+
/* background unavailable */
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
window.addEventListener("error", (event) => {
|
|
62
|
+
reportContentScriptError(event.error || event.message, {
|
|
63
|
+
mechanism: "global-error",
|
|
64
|
+
filename: event.filename,
|
|
65
|
+
lineno: event.lineno,
|
|
66
|
+
colno: event.colno,
|
|
67
|
+
});
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
window.addEventListener("unhandledrejection", (event) => {
|
|
71
|
+
reportContentScriptError(event.reason, {
|
|
72
|
+
mechanism: "unhandled-rejection",
|
|
73
|
+
});
|
|
74
|
+
});
|
|
75
|
+
|
|
21
76
|
// ----- Draggable, resizable camera bubble ---------------------------------
|
|
22
77
|
// Size + position persist in storage so the bubble stays where the user put it
|
|
23
78
|
// across pages and recordings (like the desktop app). The iframe can't move or
|
|
@@ -19,6 +19,10 @@
|
|
|
19
19
|
import { scheduleReadyChime } from "@shared/recording-audio";
|
|
20
20
|
import { chunkUploadUrl, pickMimeType } from "@shared/recording-core";
|
|
21
21
|
|
|
22
|
+
import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
23
|
+
|
|
24
|
+
initExtensionSentry("offscreen");
|
|
25
|
+
|
|
22
26
|
type CaptureMode = "screen" | "camera";
|
|
23
27
|
|
|
24
28
|
type AcquireMessage = {
|
|
@@ -118,6 +122,8 @@ type ActiveRecording = {
|
|
|
118
122
|
rejectStopped: (error: Error) => void;
|
|
119
123
|
};
|
|
120
124
|
|
|
125
|
+
const UPLOAD_SLICE_BYTES = 3 * 1024 * 1024;
|
|
126
|
+
|
|
121
127
|
let prepared: PreparedStreams | null = null;
|
|
122
128
|
let activeRecording: ActiveRecording | null = null;
|
|
123
129
|
|
|
@@ -399,13 +405,46 @@ async function uploadChunk(
|
|
|
399
405
|
Boolean(recording.authToken),
|
|
400
406
|
text.slice(0, 200),
|
|
401
407
|
);
|
|
402
|
-
|
|
408
|
+
const error = new Error(
|
|
403
409
|
data?.error || `Upload failed (${res.status}): ${text || res.statusText}`,
|
|
404
410
|
);
|
|
411
|
+
captureExtensionError(error, {
|
|
412
|
+
tags: {
|
|
413
|
+
surface: "offscreen",
|
|
414
|
+
recordingStep: "chunk-upload",
|
|
415
|
+
httpStatus: String(res.status),
|
|
416
|
+
isFinal: extra.isFinal ? "true" : "false",
|
|
417
|
+
},
|
|
418
|
+
extra: {
|
|
419
|
+
recordingId: recording.recordingId,
|
|
420
|
+
chunkIndex: index,
|
|
421
|
+
chunkBytes: blob.size,
|
|
422
|
+
total: extra.total,
|
|
423
|
+
mimeType: blob.type || recording.mimeType,
|
|
424
|
+
responseBodyTail: text.slice(0, 2000),
|
|
425
|
+
hadAuth: Boolean(recording.authToken),
|
|
426
|
+
},
|
|
427
|
+
});
|
|
428
|
+
throw error;
|
|
405
429
|
}
|
|
406
430
|
return data;
|
|
407
431
|
}
|
|
408
432
|
|
|
433
|
+
async function uploadBlobInSlices(
|
|
434
|
+
recording: ActiveRecording,
|
|
435
|
+
blob: Blob,
|
|
436
|
+
): Promise<void> {
|
|
437
|
+
const totalSlices = Math.max(1, Math.ceil(blob.size / UPLOAD_SLICE_BYTES));
|
|
438
|
+
for (let sliceIndex = 0; sliceIndex < totalSlices; sliceIndex += 1) {
|
|
439
|
+
if (recording.cancelled || recording.uploadFailure) return;
|
|
440
|
+
const start = sliceIndex * UPLOAD_SLICE_BYTES;
|
|
441
|
+
const end = Math.min(start + UPLOAD_SLICE_BYTES, blob.size);
|
|
442
|
+
const slice = blob.slice(start, end, blob.type || recording.mimeType);
|
|
443
|
+
const index = recording.chunkIndex++;
|
|
444
|
+
await uploadChunk(recording, slice, index);
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
409
448
|
function stopStreams(streams: (MediaStream | null)[]): void {
|
|
410
449
|
for (const stream of streams) {
|
|
411
450
|
if (!stream) continue;
|
|
@@ -616,14 +655,25 @@ async function begin(message: BeginMessage): Promise<{
|
|
|
616
655
|
) {
|
|
617
656
|
return;
|
|
618
657
|
}
|
|
619
|
-
const index = recording.chunkIndex++;
|
|
620
658
|
// Record the failure and stop, but do NOT re-throw: re-throwing leaves a
|
|
621
659
|
// rejected promise that surfaces as an "Uncaught (in promise)" error (bad
|
|
622
660
|
// look in a Chrome Web Store review). finalizeStop reads recording.upload-
|
|
623
661
|
// Failure and surfaces it through the normal error path instead.
|
|
624
|
-
const upload =
|
|
662
|
+
const upload = uploadBlobInSlices(recording, event.data).catch((err) => {
|
|
625
663
|
recording.uploadFailure =
|
|
626
664
|
err instanceof Error ? err : new Error(String(err));
|
|
665
|
+
captureExtensionError(recording.uploadFailure, {
|
|
666
|
+
tags: {
|
|
667
|
+
surface: "offscreen",
|
|
668
|
+
recordingStep: "dataavailable-upload",
|
|
669
|
+
},
|
|
670
|
+
extra: {
|
|
671
|
+
recordingId: recording.recordingId,
|
|
672
|
+
blobBytes: event.data.size,
|
|
673
|
+
chunkIndex: recording.chunkIndex,
|
|
674
|
+
mimeType: event.data.type || recording.mimeType,
|
|
675
|
+
},
|
|
676
|
+
});
|
|
627
677
|
reportStatus(recording.sessionId, "error", {
|
|
628
678
|
error: recording.uploadFailure.message,
|
|
629
679
|
});
|
|
@@ -687,6 +737,20 @@ function startRecorderNow(recording: ActiveRecording): void {
|
|
|
687
737
|
hasCamera: recording.hasCamera,
|
|
688
738
|
});
|
|
689
739
|
} catch (err) {
|
|
740
|
+
captureExtensionError(err, {
|
|
741
|
+
tags: {
|
|
742
|
+
surface: "offscreen",
|
|
743
|
+
recordingStep: "recorder-start",
|
|
744
|
+
},
|
|
745
|
+
extra: {
|
|
746
|
+
recordingId: recording.recordingId,
|
|
747
|
+
mimeType: recording.mimeType,
|
|
748
|
+
width: recording.dimensions.width,
|
|
749
|
+
height: recording.dimensions.height,
|
|
750
|
+
hasAudio: recording.hasAudio,
|
|
751
|
+
hasCamera: recording.hasCamera,
|
|
752
|
+
},
|
|
753
|
+
});
|
|
690
754
|
reportStatus(recording.sessionId, "error", {
|
|
691
755
|
recordingId: recording.recordingId,
|
|
692
756
|
error: err instanceof Error ? err.message : "Could not start recording.",
|
|
@@ -766,6 +830,18 @@ async function finalizeStop(recording: ActiveRecording): Promise<void> {
|
|
|
766
830
|
cleanup(recording);
|
|
767
831
|
if (activeRecording === recording) activeRecording = null;
|
|
768
832
|
const error = err instanceof Error ? err : new Error(String(err));
|
|
833
|
+
captureExtensionError(error, {
|
|
834
|
+
tags: {
|
|
835
|
+
surface: "offscreen",
|
|
836
|
+
recordingStep: "finalize-stop",
|
|
837
|
+
},
|
|
838
|
+
extra: {
|
|
839
|
+
recordingId: recording.recordingId,
|
|
840
|
+
chunkCount: recording.chunkIndex,
|
|
841
|
+
mimeType: recording.mimeType,
|
|
842
|
+
durationMs: Math.max(0, Date.now() - recording.startedAtMs),
|
|
843
|
+
},
|
|
844
|
+
});
|
|
769
845
|
reportStatus(recording.sessionId, "error", {
|
|
770
846
|
recordingId: recording.recordingId,
|
|
771
847
|
error: error.message,
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
2
|
+
|
|
3
|
+
initExtensionSentry("options");
|
|
4
|
+
|
|
1
5
|
type CaptureSurface = "browser" | "window" | "monitor" | "camera";
|
|
2
6
|
|
|
3
7
|
type ExtensionSettings = {
|
|
@@ -116,6 +120,9 @@ async function init(): Promise<void> {
|
|
|
116
120
|
await saveSettings(settings);
|
|
117
121
|
setStatus("Settings saved.");
|
|
118
122
|
} catch (err) {
|
|
123
|
+
captureExtensionError(err, {
|
|
124
|
+
tags: { surface: "options", action: "save-settings" },
|
|
125
|
+
});
|
|
119
126
|
setStatus(
|
|
120
127
|
err instanceof Error ? err.message : "Could not save settings.",
|
|
121
128
|
"error",
|
|
@@ -132,6 +139,9 @@ async function init(): Promise<void> {
|
|
|
132
139
|
}
|
|
133
140
|
|
|
134
141
|
void init().catch((err) => {
|
|
142
|
+
captureExtensionError(err, {
|
|
143
|
+
tags: { surface: "options", action: "init" },
|
|
144
|
+
});
|
|
135
145
|
setStatus(
|
|
136
146
|
err instanceof Error ? err.message : "Could not load settings.",
|
|
137
147
|
"error",
|
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
import "./overlay.css";
|
|
2
|
+
import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
3
|
+
|
|
4
|
+
initExtensionSentry("overlay");
|
|
2
5
|
|
|
3
6
|
// The overlay runs as an extension-origin iframe injected into the page by the
|
|
4
7
|
// content script. Each iframe renders one "part" of the Loom-style recording UI
|
|
@@ -166,6 +169,9 @@ async function initBubble(): Promise<void> {
|
|
|
166
169
|
postBubble("camera-ready");
|
|
167
170
|
} catch (err) {
|
|
168
171
|
console.warn("[clips-overlay] camera getUserMedia failed:", err);
|
|
172
|
+
captureExtensionError(err, {
|
|
173
|
+
tags: { surface: "overlay", overlayPart: "bubble" },
|
|
174
|
+
});
|
|
169
175
|
const empty = document.createElement("div");
|
|
170
176
|
empty.className = "bubble-empty";
|
|
171
177
|
empty.innerHTML = ICONS.cameraOff;
|
|
@@ -5,6 +5,10 @@
|
|
|
5
5
|
// the grant for the whole chrome-extension:// origin — so the offscreen recorder
|
|
6
6
|
// (mic) and the camera-bubble iframe both work afterward.
|
|
7
7
|
|
|
8
|
+
import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
9
|
+
|
|
10
|
+
initExtensionSentry("permission");
|
|
11
|
+
|
|
8
12
|
const enableBtn = document.getElementById("enable") as HTMLButtonElement;
|
|
9
13
|
const statusEl = document.getElementById("status") as HTMLDivElement;
|
|
10
14
|
const successEl = document.getElementById("success-state") as HTMLDivElement;
|
|
@@ -71,7 +75,10 @@ async function requestOne(kind: "mic" | "cam"): Promise<boolean> {
|
|
|
71
75
|
const stream = await navigator.mediaDevices.getUserMedia(constraints);
|
|
72
76
|
for (const track of stream.getTracks()) track.stop();
|
|
73
77
|
return true;
|
|
74
|
-
} catch {
|
|
78
|
+
} catch (err) {
|
|
79
|
+
captureExtensionError(err, {
|
|
80
|
+
tags: { surface: "permission", permission: kind },
|
|
81
|
+
});
|
|
75
82
|
return false;
|
|
76
83
|
}
|
|
77
84
|
}
|
|
@@ -1,3 +1,7 @@
|
|
|
1
|
+
import { captureExtensionError, initExtensionSentry } from "./sentry";
|
|
2
|
+
|
|
3
|
+
initExtensionSentry("popup");
|
|
4
|
+
|
|
1
5
|
type CaptureSurface = "browser" | "window" | "monitor" | "camera";
|
|
2
6
|
type RecordingModeChoice = "screen-camera" | "screen" | "camera";
|
|
3
7
|
|
|
@@ -1032,6 +1036,9 @@ async function init(): Promise<void> {
|
|
|
1032
1036
|
1400,
|
|
1033
1037
|
);
|
|
1034
1038
|
} catch (err) {
|
|
1039
|
+
captureExtensionError(err, {
|
|
1040
|
+
tags: { surface: "popup", action: "submit-feedback" },
|
|
1041
|
+
});
|
|
1035
1042
|
feedbackSubmit.disabled = !feedbackTextarea.value.trim();
|
|
1036
1043
|
feedbackSubmit.textContent = FEEDBACK_SUBMIT_TEXT;
|
|
1037
1044
|
feedbackHint.textContent =
|
|
@@ -1065,51 +1072,67 @@ async function init(): Promise<void> {
|
|
|
1065
1072
|
start.disabled = true;
|
|
1066
1073
|
signIn.hidden = true;
|
|
1067
1074
|
setStatus(""); // no chatty "Checking…/Starting…" text — the disabled button is enough
|
|
1068
|
-
|
|
1069
|
-
|
|
1075
|
+
try {
|
|
1076
|
+
authStatus = await readAuthStatus(settings);
|
|
1077
|
+
if (authStatus === "signed-out") {
|
|
1078
|
+
start.disabled = false;
|
|
1079
|
+
start.hidden = true;
|
|
1080
|
+
signIn.hidden = false;
|
|
1081
|
+
setStatus("");
|
|
1082
|
+
return;
|
|
1083
|
+
}
|
|
1084
|
+
const storageConfigured = await readVideoStorageConfigured(settings);
|
|
1085
|
+
if (!storageConfigured) {
|
|
1086
|
+
start.disabled = false;
|
|
1087
|
+
setStatus(
|
|
1088
|
+
"Connect Builder.io or S3 storage in Clips, then start recording.",
|
|
1089
|
+
"error",
|
|
1090
|
+
);
|
|
1091
|
+
await createTab(`${settings.clipsBaseUrl.replace(/\/+$/, "")}/record`);
|
|
1092
|
+
window.close();
|
|
1093
|
+
return;
|
|
1094
|
+
}
|
|
1095
|
+
// Gate at record time: if the user wants camera/mic but hasn't granted the
|
|
1096
|
+
// extension access yet, send them to the onboarding page first. Requesting
|
|
1097
|
+
// there (a real extension page) is the only place Chrome reliably shows the
|
|
1098
|
+
// permission dialog and persists the grant for the offscreen recorder + bubble.
|
|
1099
|
+
if (!(await ensureMediaPermission(settings))) {
|
|
1100
|
+
start.disabled = false;
|
|
1101
|
+
setStatus("Allow camera & microphone, then start recording.", "error");
|
|
1102
|
+
await createTab(chrome.runtime.getURL("src/permission.html"));
|
|
1103
|
+
window.close();
|
|
1104
|
+
return;
|
|
1105
|
+
}
|
|
1106
|
+
await saveSettings(settings);
|
|
1107
|
+
const response = await sendStartMessage(settings);
|
|
1108
|
+
if (response.ok) {
|
|
1109
|
+
window.close();
|
|
1110
|
+
return;
|
|
1111
|
+
}
|
|
1070
1112
|
start.disabled = false;
|
|
1071
|
-
|
|
1072
|
-
|
|
1073
|
-
|
|
1074
|
-
|
|
1075
|
-
|
|
1076
|
-
|
|
1077
|
-
|
|
1113
|
+
const message = response.error || "Could not start Clips.";
|
|
1114
|
+
if (isSignInError(message)) {
|
|
1115
|
+
start.hidden = true;
|
|
1116
|
+
signIn.hidden = false;
|
|
1117
|
+
setStatus("");
|
|
1118
|
+
return;
|
|
1119
|
+
}
|
|
1120
|
+
setStatus(message, "error");
|
|
1121
|
+
} catch (err) {
|
|
1122
|
+
captureExtensionError(err, {
|
|
1123
|
+
tags: { surface: "popup", action: "start-recording" },
|
|
1124
|
+
extra: {
|
|
1125
|
+
captureSurface: settings.captureSurface,
|
|
1126
|
+
includeCamera: settings.includeCamera,
|
|
1127
|
+
includeMicrophone: settings.includeMicrophone,
|
|
1128
|
+
},
|
|
1129
|
+
});
|
|
1078
1130
|
start.disabled = false;
|
|
1079
1131
|
setStatus(
|
|
1080
|
-
|
|
1132
|
+
err instanceof Error ? err.message : "Could not start Clips.",
|
|
1081
1133
|
"error",
|
|
1082
1134
|
);
|
|
1083
|
-
await createTab(`${settings.clipsBaseUrl.replace(/\/+$/, "")}/record`);
|
|
1084
|
-
window.close();
|
|
1085
|
-
return;
|
|
1086
|
-
}
|
|
1087
|
-
// Gate at record time: if the user wants camera/mic but hasn't granted the
|
|
1088
|
-
// extension access yet, send them to the onboarding page first. Requesting
|
|
1089
|
-
// there (a real extension page) is the only place Chrome reliably shows the
|
|
1090
|
-
// permission dialog and persists the grant for the offscreen recorder + bubble.
|
|
1091
|
-
if (!(await ensureMediaPermission(settings))) {
|
|
1092
|
-
start.disabled = false;
|
|
1093
|
-
setStatus("Allow camera & microphone, then start recording.", "error");
|
|
1094
|
-
await createTab(chrome.runtime.getURL("src/permission.html"));
|
|
1095
|
-
window.close();
|
|
1096
|
-
return;
|
|
1097
1135
|
}
|
|
1098
|
-
await saveSettings(settings);
|
|
1099
|
-
const response = await sendStartMessage(settings);
|
|
1100
|
-
if (response.ok) {
|
|
1101
|
-
window.close();
|
|
1102
|
-
return;
|
|
1103
|
-
}
|
|
1104
|
-
start.disabled = false;
|
|
1105
|
-
const message = response.error || "Could not start Clips.";
|
|
1106
|
-
if (isSignInError(message)) {
|
|
1107
|
-
start.hidden = true;
|
|
1108
|
-
signIn.hidden = false;
|
|
1109
|
-
setStatus("");
|
|
1110
|
-
return;
|
|
1111
|
-
}
|
|
1112
|
-
setStatus(message, "error");
|
|
1113
1136
|
});
|
|
1114
1137
|
|
|
1115
1138
|
signIn.addEventListener("click", async () => {
|
|
@@ -1173,6 +1196,9 @@ async function init(): Promise<void> {
|
|
|
1173
1196
|
}
|
|
1174
1197
|
|
|
1175
1198
|
void init().catch((err) => {
|
|
1199
|
+
captureExtensionError(err, {
|
|
1200
|
+
tags: { surface: "popup", action: "init" },
|
|
1201
|
+
});
|
|
1176
1202
|
setStatus(
|
|
1177
1203
|
err instanceof Error ? err.message : "Could not load popup.",
|
|
1178
1204
|
"error",
|