@agent-native/core 0.180.0-nightly-20260915184036 → 0.180.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/corpus/README.md +1 -1
- package/corpus/templates/analytics/CHANGELOG.md +10 -0
- package/corpus/templates/calendar/CHANGELOG.md +39 -0
- package/corpus/templates/calendar/INTERACTION-MATRIX.md +19 -17
- package/corpus/templates/calendar/actions/update-calendar-visual-preferences.ts +21 -0
- package/corpus/templates/calendar/actions/view-screen.ts +10 -2
- package/corpus/templates/calendar/app/components/calendar/CreateEventDialog.tsx +11 -0
- package/corpus/templates/calendar/app/components/calendar/MonthView.tsx +174 -145
- package/corpus/templates/calendar/app/components/calendar/WeekView.tsx +20 -16
- package/corpus/templates/calendar/app/components/layout/Sidebar.tsx +1 -0
- package/corpus/templates/calendar/app/hooks/use-events.ts +16 -0
- package/corpus/templates/calendar/app/hooks/use-view-preferences.ts +126 -14
- package/corpus/templates/calendar/app/i18n-data.ts +90 -0
- package/corpus/templates/calendar/app/lib/calendar-navigation.ts +29 -9
- package/corpus/templates/calendar/app/lib/calendar-timezone.ts +2 -0
- package/corpus/templates/calendar/app/lib/event-time-range.ts +34 -0
- package/corpus/templates/calendar/app/pages/CalendarView.tsx +245 -35
- package/corpus/templates/calendar/changelog/2026-09-13-calendar-sidebar-overflow-is-easier-to-discover.md +6 -0
- package/corpus/templates/calendar/changelog/2026-09-15-calendar-mirrors-notion-calendar-s-customizable-day-ranges-a.md +6 -0
- package/corpus/templates/calendar/shared/calendar-view-preferences.ts +36 -0
- package/corpus/templates/calendar/shared/timezone.ts +9 -2
- package/corpus/templates/clips/.agents/skills/video-sharing/SKILL.md +2 -0
- package/corpus/templates/clips/CHANGELOG.md +19 -0
- package/corpus/templates/clips/actions/save-browser-diagnostics.ts +43 -4
- package/corpus/templates/clips/app/components/player/browser-diagnostics-panel.tsx +114 -1
- package/corpus/templates/clips/app/i18n/en-US.ts +7 -0
- package/corpus/templates/clips/app/lib/browser-diagnostics-capture.ts +108 -0
- package/corpus/templates/clips/app/routes/record.tsx +1 -0
- package/corpus/templates/clips/changelog/2026-09-15-recordings-include-a-structured-timeline-of-browser-interact.md +6 -0
- package/corpus/templates/clips/chrome-extension/public/manifest.json +7 -0
- package/corpus/templates/clips/chrome-extension/src/background.ts +235 -9
- package/corpus/templates/clips/chrome-extension/src/content-history-bridge.ts +80 -0
- package/corpus/templates/clips/chrome-extension/src/content-script.ts +166 -5
- package/corpus/templates/clips/chrome-extension/vite.config.ts +4 -0
- package/corpus/templates/clips/server/db/schema.ts +3 -0
- package/corpus/templates/clips/server/lib/public-agent-context.ts +40 -2
- package/corpus/templates/clips/server/plugins/db.ts +5 -0
- package/corpus/templates/clips/shared/browser-diagnostics.ts +171 -0
- package/corpus/templates/content/.agents/skills/content/SKILL.md +4 -2
- package/corpus/templates/content/.agents/skills/document-editing/SKILL.md +8 -1
- package/corpus/templates/content/AGENTS.md +5 -6
- package/corpus/templates/content/CHANGELOG.md +24 -0
- package/corpus/templates/content/actions/_content-database-row-migration.ts +67 -36
- package/corpus/templates/content/actions/_document-edit-mutation.ts +74 -23
- package/corpus/templates/content/actions/edit-document.ts +48 -8
- package/corpus/templates/content/actions/migrate-content-database-rows.ts +69 -28
- package/corpus/templates/content/server/plugins/agent-chat.ts +1 -1
- package/corpus/templates/design/CHANGELOG.md +22 -0
- package/corpus/templates/factory/CHANGELOG.md +11 -0
- package/corpus/templates/forms/CHANGELOG.md +6 -0
- package/corpus/templates/mail/.agents/skills/mail-interaction-parity/references/interaction-matrix.md +24 -4
- package/corpus/templates/mail/CHANGELOG.md +68 -0
- package/corpus/templates/mail/app/components/email/ComposeBubbleToolbar.tsx +8 -0
- package/corpus/templates/mail/app/components/email/ComposeModal.tsx +33 -13
- package/corpus/templates/mail/app/components/email/EmailListItem.tsx +23 -4
- package/corpus/templates/mail/app/components/email/EmailThread.tsx +56 -13
- package/corpus/templates/mail/app/components/email/InlineReplyComposer.tsx +23 -4
- package/corpus/templates/mail/app/components/email/RecipientInput.tsx +9 -0
- package/corpus/templates/mail/app/components/layout/AppLayout.tsx +8 -1
- package/corpus/templates/mail/app/i18n/en-US.ts +31 -0
- package/corpus/templates/mail/changelog/2026-09-15-mail-labels-recipient-and-account-controls-for-keyboard-and-.md +6 -0
- package/corpus/templates/slides/CHANGELOG.md +32 -0
- package/dist/client/CommandMenu.js +14 -2
- package/dist/client/settings/AccountSettingsCard.js +3 -2
- package/dist/client/settings/SettingsTabsPage.js +9 -1
- package/dist/client/settings/agent-settings-search.js +5 -1
- package/dist/client/sign-out.d.ts +1 -0
- package/dist/client/sign-out.js +8 -0
- package/dist/collab/routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +2 -2
- package/dist/observability/routes.d.ts +3 -3
- package/dist/org/handlers.d.ts +1 -1
- package/dist/resources/handlers.d.ts +1 -1
- package/dist/secrets/routes.d.ts +3 -3
- package/dist/server/agent-engine-api-key-route.d.ts +2 -2
- package/dist/server/transcribe-voice.d.ts +1 -1
- package/package.json +4 -4
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { sanitizeBrowserDiagnosticNavigationUrl } from "@shared/browser-diagnostics";
|
|
1
2
|
import { buildRecordingShareUrl } from "@shared/recording-link";
|
|
2
3
|
|
|
3
4
|
import {
|
|
@@ -30,6 +31,8 @@ const DEFAULT_CLIPS_BASE_URL = "https://clips.agent-native.com";
|
|
|
30
31
|
const DEBUGGER_PROTOCOL_VERSION = "1.3";
|
|
31
32
|
const MAX_CONSOLE_LOGS = 400;
|
|
32
33
|
const MAX_NETWORK_REQUESTS = 400;
|
|
34
|
+
const CLICK_INPUT_INGRESS_WINDOW_MS = 1_000;
|
|
35
|
+
const MAX_CLICK_INPUT_INGRESS_PER_WINDOW = 100;
|
|
33
36
|
const MAX_MESSAGE_LENGTH = 2_000;
|
|
34
37
|
const MAX_URL_LENGTH = 1_000;
|
|
35
38
|
const STORAGE_SETUP_REQUIRED_MESSAGE =
|
|
@@ -56,6 +59,7 @@ const UNQUOTED_SECRET_VALUE_RE = new RegExp(
|
|
|
56
59
|
type CaptureSurface = "browser" | "window" | "monitor" | "camera";
|
|
57
60
|
type ConsoleLevel = "debug" | "log" | "info" | "warn" | "error";
|
|
58
61
|
type NetworkType = "fetch" | "xhr";
|
|
62
|
+
type InteractionKind = "navigation" | "click" | "input" | "scroll";
|
|
59
63
|
type UploadMode = "streaming" | "buffered";
|
|
60
64
|
|
|
61
65
|
type ExtensionSettings = {
|
|
@@ -124,6 +128,14 @@ type NetworkRequest = {
|
|
|
124
128
|
error?: string;
|
|
125
129
|
};
|
|
126
130
|
|
|
131
|
+
type InteractionEvent = {
|
|
132
|
+
timestampMs: number;
|
|
133
|
+
elapsedMs: number;
|
|
134
|
+
kind: InteractionKind;
|
|
135
|
+
target?: string;
|
|
136
|
+
url?: string;
|
|
137
|
+
};
|
|
138
|
+
|
|
127
139
|
type BrowserDiagnosticsData = {
|
|
128
140
|
pageUrl: string | null;
|
|
129
141
|
userAgent: string | null;
|
|
@@ -131,6 +143,7 @@ type BrowserDiagnosticsData = {
|
|
|
131
143
|
endedAt: string;
|
|
132
144
|
consoleLogs: ConsoleLog[];
|
|
133
145
|
networkRequests: NetworkRequest[];
|
|
146
|
+
interactionEvents?: InteractionEvent[];
|
|
134
147
|
summary: {
|
|
135
148
|
consoleCount: number;
|
|
136
149
|
consoleErrorCount: number;
|
|
@@ -169,6 +182,8 @@ type NativeRecording = {
|
|
|
169
182
|
status: NativeRecordingStatus;
|
|
170
183
|
recordingUrl: string;
|
|
171
184
|
error: string | null;
|
|
185
|
+
diagnosticsPausedAtMs?: number;
|
|
186
|
+
diagnosticsPausedDurationMs?: number;
|
|
172
187
|
// When an upload fails, the recording is saved to the user's Downloads as a
|
|
173
188
|
// fallback so it is never lost; these describe that saved file.
|
|
174
189
|
savedToDisk?: boolean;
|
|
@@ -236,6 +251,11 @@ type CaptureSession = {
|
|
|
236
251
|
attachError: string | null;
|
|
237
252
|
consoleLogs: ConsoleLog[];
|
|
238
253
|
networkRequests: NetworkRequest[];
|
|
254
|
+
interactionEvents: InteractionEvent[];
|
|
255
|
+
clickInputIngressWindowStartedAtMs: number;
|
|
256
|
+
clickInputIngressCount: number;
|
|
257
|
+
diagnosticsPausedAtMs: number | null;
|
|
258
|
+
diagnosticsPausedDurationMs: number;
|
|
239
259
|
pendingNetworkRequests: Map<string, PendingNetworkRequest>;
|
|
240
260
|
};
|
|
241
261
|
|
|
@@ -398,6 +418,15 @@ async function restoreRuntimeState(): Promise<void> {
|
|
|
398
418
|
) {
|
|
399
419
|
activeNativeRecording = restoredRecording;
|
|
400
420
|
}
|
|
421
|
+
if (
|
|
422
|
+
activeNativeRecording &&
|
|
423
|
+
!sessions.has(activeNativeRecording.sessionId) &&
|
|
424
|
+
activeNativeRecording.status !== "complete" &&
|
|
425
|
+
activeNativeRecording.status !== "error"
|
|
426
|
+
) {
|
|
427
|
+
const session = restoreCaptureSession(activeNativeRecording);
|
|
428
|
+
await attachSession(session);
|
|
429
|
+
}
|
|
401
430
|
const freshArmingSessionId = await readFreshPersistedArmingSessionId(
|
|
402
431
|
stored.armingNativeRecordingSessionId,
|
|
403
432
|
);
|
|
@@ -549,11 +578,23 @@ async function injectContentScript(tabId: number): Promise<boolean> {
|
|
|
549
578
|
executeScript: (args: {
|
|
550
579
|
target: { tabId: number };
|
|
551
580
|
files: string[];
|
|
581
|
+
world?: "ISOLATED" | "MAIN";
|
|
552
582
|
}) => Promise<unknown>;
|
|
553
583
|
};
|
|
554
584
|
}
|
|
555
585
|
).scripting;
|
|
556
586
|
if (!scripting) return false;
|
|
587
|
+
try {
|
|
588
|
+
await scripting.executeScript({
|
|
589
|
+
target: { tabId },
|
|
590
|
+
files: ["assets/content-history-bridge.js"],
|
|
591
|
+
world: "MAIN",
|
|
592
|
+
});
|
|
593
|
+
// coercion-ok: MAIN-world injection is optional; the isolated script still records.
|
|
594
|
+
} catch {
|
|
595
|
+
// Some restricted pages reject MAIN-world injection but still accept the
|
|
596
|
+
// isolated overlay/content script.
|
|
597
|
+
}
|
|
557
598
|
try {
|
|
558
599
|
await scripting.executeScript({
|
|
559
600
|
target: { tabId },
|
|
@@ -595,11 +636,16 @@ async function broadcastMount(): Promise<void> {
|
|
|
595
636
|
return;
|
|
596
637
|
}
|
|
597
638
|
const parts = desiredParts();
|
|
639
|
+
const resetDiagnosticQuotas = overlayPhase === "recording";
|
|
598
640
|
const tabs = await allTabs();
|
|
599
641
|
await Promise.all(
|
|
600
642
|
tabs.map((tab) =>
|
|
601
643
|
typeof tab.id === "number"
|
|
602
|
-
? sendTabMessage(tab.id, {
|
|
644
|
+
? sendTabMessage(tab.id, {
|
|
645
|
+
type: "CLIPS_OVERLAY_MOUNT",
|
|
646
|
+
parts,
|
|
647
|
+
...(resetDiagnosticQuotas ? { resetDiagnosticQuotas: true } : {}),
|
|
648
|
+
})
|
|
603
649
|
: Promise.resolve(),
|
|
604
650
|
),
|
|
605
651
|
);
|
|
@@ -1329,6 +1375,11 @@ function createSession(
|
|
|
1329
1375
|
attachError: null,
|
|
1330
1376
|
consoleLogs: [],
|
|
1331
1377
|
networkRequests: [],
|
|
1378
|
+
interactionEvents: [],
|
|
1379
|
+
clickInputIngressWindowStartedAtMs: 0,
|
|
1380
|
+
clickInputIngressCount: 0,
|
|
1381
|
+
diagnosticsPausedAtMs: null,
|
|
1382
|
+
diagnosticsPausedDurationMs: 0,
|
|
1332
1383
|
pendingNetworkRequests: new Map(),
|
|
1333
1384
|
};
|
|
1334
1385
|
sessions.set(sessionId, session);
|
|
@@ -1336,6 +1387,37 @@ function createSession(
|
|
|
1336
1387
|
return session;
|
|
1337
1388
|
}
|
|
1338
1389
|
|
|
1390
|
+
function restoreCaptureSession(recording: NativeRecording): CaptureSession {
|
|
1391
|
+
const session = createSession(
|
|
1392
|
+
recording.sessionId,
|
|
1393
|
+
{
|
|
1394
|
+
id: recording.targetTabId,
|
|
1395
|
+
title: recording.targetTitle ?? undefined,
|
|
1396
|
+
url: recording.targetUrl ?? undefined,
|
|
1397
|
+
},
|
|
1398
|
+
settingsFromRecording(recording),
|
|
1399
|
+
);
|
|
1400
|
+
session.recordingId = recording.recordingId;
|
|
1401
|
+
session.startedAt = recording.startedAt;
|
|
1402
|
+
session.startedAtMs = recording.startedAtMs;
|
|
1403
|
+
session.diagnosticsPausedAtMs =
|
|
1404
|
+
typeof recording.diagnosticsPausedAtMs === "number"
|
|
1405
|
+
? recording.diagnosticsPausedAtMs
|
|
1406
|
+
: null;
|
|
1407
|
+
session.diagnosticsPausedDurationMs =
|
|
1408
|
+
typeof recording.diagnosticsPausedDurationMs === "number"
|
|
1409
|
+
? Math.max(0, recording.diagnosticsPausedDurationMs)
|
|
1410
|
+
: 0;
|
|
1411
|
+
if (session.targetUrl) {
|
|
1412
|
+
pushInteraction(session, {
|
|
1413
|
+
kind: "navigation",
|
|
1414
|
+
url: session.targetUrl,
|
|
1415
|
+
timestampMs: session.startedAtMs,
|
|
1416
|
+
});
|
|
1417
|
+
}
|
|
1418
|
+
return session;
|
|
1419
|
+
}
|
|
1420
|
+
|
|
1339
1421
|
async function handlePopupStart(message: PopupStartMessage) {
|
|
1340
1422
|
const tab = await queryActiveTab();
|
|
1341
1423
|
if (!tab || typeof tab.id !== "number") {
|
|
@@ -1717,6 +1799,8 @@ async function markRecordingStarted() {
|
|
|
1717
1799
|
: null;
|
|
1718
1800
|
if (session) beginSessionCapture(session, overlayBaseEpochMs);
|
|
1719
1801
|
if (activeNativeRecording) {
|
|
1802
|
+
delete activeNativeRecording.diagnosticsPausedAtMs;
|
|
1803
|
+
delete activeNativeRecording.diagnosticsPausedDurationMs;
|
|
1720
1804
|
activeNativeRecording.startedAtMs = overlayBaseEpochMs;
|
|
1721
1805
|
activeNativeRecording.startedAt = new Date(
|
|
1722
1806
|
overlayBaseEpochMs,
|
|
@@ -1743,9 +1827,16 @@ async function handleOverlaySkip() {
|
|
|
1743
1827
|
|
|
1744
1828
|
function handleOverlayPause() {
|
|
1745
1829
|
if (overlayPhase !== "recording") return { ok: true };
|
|
1746
|
-
|
|
1830
|
+
const pausedAtMs = nowMs();
|
|
1831
|
+
overlayBaseElapsedMs += Math.max(0, pausedAtMs - overlayBaseEpochMs);
|
|
1747
1832
|
overlayPhase = "paused";
|
|
1833
|
+
const session = activeNativeRecording
|
|
1834
|
+
? sessions.get(activeNativeRecording.sessionId)
|
|
1835
|
+
: null;
|
|
1836
|
+
if (session) session.diagnosticsPausedAtMs = pausedAtMs;
|
|
1748
1837
|
if (activeNativeRecording) {
|
|
1838
|
+
activeNativeRecording.diagnosticsPausedAtMs = pausedAtMs;
|
|
1839
|
+
activeNativeRecording.diagnosticsPausedDurationMs ??= 0;
|
|
1749
1840
|
activeNativeRecording.status = "paused";
|
|
1750
1841
|
void saveActiveNativeRecording();
|
|
1751
1842
|
void sendOffscreenMessage({
|
|
@@ -1759,7 +1850,27 @@ function handleOverlayPause() {
|
|
|
1759
1850
|
|
|
1760
1851
|
function handleOverlayResume() {
|
|
1761
1852
|
if (overlayPhase !== "paused") return { ok: true };
|
|
1762
|
-
|
|
1853
|
+
const resumedAtMs = nowMs();
|
|
1854
|
+
const session = activeNativeRecording
|
|
1855
|
+
? sessions.get(activeNativeRecording.sessionId)
|
|
1856
|
+
: null;
|
|
1857
|
+
const pausedAtMs =
|
|
1858
|
+
session?.diagnosticsPausedAtMs ??
|
|
1859
|
+
activeNativeRecording?.diagnosticsPausedAtMs;
|
|
1860
|
+
if (typeof pausedAtMs === "number") {
|
|
1861
|
+
const pausedDurationMs = Math.max(0, resumedAtMs - pausedAtMs);
|
|
1862
|
+
if (session) {
|
|
1863
|
+
session.diagnosticsPausedDurationMs += pausedDurationMs;
|
|
1864
|
+
session.diagnosticsPausedAtMs = null;
|
|
1865
|
+
}
|
|
1866
|
+
if (activeNativeRecording) {
|
|
1867
|
+
activeNativeRecording.diagnosticsPausedDurationMs =
|
|
1868
|
+
(activeNativeRecording.diagnosticsPausedDurationMs ?? 0) +
|
|
1869
|
+
pausedDurationMs;
|
|
1870
|
+
delete activeNativeRecording.diagnosticsPausedAtMs;
|
|
1871
|
+
}
|
|
1872
|
+
}
|
|
1873
|
+
overlayBaseEpochMs = resumedAtMs;
|
|
1763
1874
|
overlayPhase = "recording";
|
|
1764
1875
|
if (activeNativeRecording) {
|
|
1765
1876
|
activeNativeRecording.status = "recording";
|
|
@@ -2022,6 +2133,7 @@ async function saveNativeDiagnostics(
|
|
|
2022
2133
|
endedAt: snapshot.endedAt,
|
|
2023
2134
|
consoleLogs: snapshot.consoleLogs,
|
|
2024
2135
|
networkRequests: snapshot.networkRequests,
|
|
2136
|
+
interactionEvents: snapshot.interactionEvents,
|
|
2025
2137
|
},
|
|
2026
2138
|
).catch((err) => {
|
|
2027
2139
|
console.warn("[clips-extension] diagnostics save failed:", err);
|
|
@@ -2164,6 +2276,22 @@ function pendingNetworkSnapshot(session: CaptureSession): NetworkRequest[] {
|
|
|
2164
2276
|
}));
|
|
2165
2277
|
}
|
|
2166
2278
|
|
|
2279
|
+
function diagnosticElapsedMs(
|
|
2280
|
+
timestampMs: number,
|
|
2281
|
+
session: CaptureSession,
|
|
2282
|
+
): number | null {
|
|
2283
|
+
const elapsedMs = elapsedMsFromCaptureStart(timestampMs, session.startedAtMs);
|
|
2284
|
+
if (elapsedMs === null) return null;
|
|
2285
|
+
const activePauseMs =
|
|
2286
|
+
session.diagnosticsPausedAtMs === null
|
|
2287
|
+
? 0
|
|
2288
|
+
: Math.max(0, timestampMs - session.diagnosticsPausedAtMs);
|
|
2289
|
+
return Math.max(
|
|
2290
|
+
0,
|
|
2291
|
+
elapsedMs - session.diagnosticsPausedDurationMs - activePauseMs,
|
|
2292
|
+
);
|
|
2293
|
+
}
|
|
2294
|
+
|
|
2167
2295
|
function snapshotSession(session: CaptureSession): BrowserDiagnosticsData {
|
|
2168
2296
|
const endedAt = nowIso();
|
|
2169
2297
|
const consoleLogs = session.consoleLogs.slice(-MAX_CONSOLE_LOGS);
|
|
@@ -2181,6 +2309,7 @@ function snapshotSession(session: CaptureSession): BrowserDiagnosticsData {
|
|
|
2181
2309
|
endedAt,
|
|
2182
2310
|
consoleLogs,
|
|
2183
2311
|
networkRequests,
|
|
2312
|
+
interactionEvents: session.interactionEvents.slice(-800),
|
|
2184
2313
|
summary: summarize({ endedAt, consoleLogs, networkRequests }),
|
|
2185
2314
|
};
|
|
2186
2315
|
}
|
|
@@ -2193,6 +2322,18 @@ function beginSessionCapture(
|
|
|
2193
2322
|
session.startedAt = new Date(startedAtMs).toISOString();
|
|
2194
2323
|
session.consoleLogs = [];
|
|
2195
2324
|
session.networkRequests = [];
|
|
2325
|
+
session.interactionEvents = [];
|
|
2326
|
+
session.clickInputIngressWindowStartedAtMs = 0;
|
|
2327
|
+
session.clickInputIngressCount = 0;
|
|
2328
|
+
session.diagnosticsPausedAtMs = null;
|
|
2329
|
+
session.diagnosticsPausedDurationMs = 0;
|
|
2330
|
+
if (session.targetUrl) {
|
|
2331
|
+
pushInteraction(session, {
|
|
2332
|
+
kind: "navigation",
|
|
2333
|
+
url: session.targetUrl,
|
|
2334
|
+
timestampMs: startedAtMs,
|
|
2335
|
+
});
|
|
2336
|
+
}
|
|
2196
2337
|
session.pendingNetworkRequests.clear();
|
|
2197
2338
|
}
|
|
2198
2339
|
|
|
@@ -2248,7 +2389,7 @@ function pushConsole(
|
|
|
2248
2389
|
const timestampMs = Number.isFinite(entry.timestampMs)
|
|
2249
2390
|
? (entry.timestampMs as number)
|
|
2250
2391
|
: nowMs();
|
|
2251
|
-
const elapsedMs =
|
|
2392
|
+
const elapsedMs = diagnosticElapsedMs(timestampMs, session);
|
|
2252
2393
|
// Runtime and Log can replay old entries when the debugger attaches. Those
|
|
2253
2394
|
// entries belong to the page history, not the recording that just started.
|
|
2254
2395
|
if (elapsedMs === null) return;
|
|
@@ -2287,6 +2428,62 @@ function pushNetwork(session: CaptureSession, entry: NetworkRequest): void {
|
|
|
2287
2428
|
}
|
|
2288
2429
|
}
|
|
2289
2430
|
|
|
2431
|
+
function pushInteraction(
|
|
2432
|
+
session: CaptureSession,
|
|
2433
|
+
entry: Omit<InteractionEvent, "timestampMs" | "elapsedMs"> & {
|
|
2434
|
+
timestampMs?: number;
|
|
2435
|
+
},
|
|
2436
|
+
): void {
|
|
2437
|
+
const timestampMs = entry.timestampMs ?? nowMs();
|
|
2438
|
+
const elapsedMs = diagnosticElapsedMs(timestampMs, session);
|
|
2439
|
+
if (elapsedMs === null) return;
|
|
2440
|
+
const url = entry.url
|
|
2441
|
+
? sanitizeBrowserDiagnosticNavigationUrl(entry.url)
|
|
2442
|
+
: undefined;
|
|
2443
|
+
if (entry.kind === "navigation" && url) {
|
|
2444
|
+
const first = session.interactionEvents[0];
|
|
2445
|
+
if (
|
|
2446
|
+
session.interactionEvents.length === 1 &&
|
|
2447
|
+
first?.kind === "navigation" &&
|
|
2448
|
+
first.url === url
|
|
2449
|
+
) {
|
|
2450
|
+
return;
|
|
2451
|
+
}
|
|
2452
|
+
}
|
|
2453
|
+
session.interactionEvents.push({
|
|
2454
|
+
timestampMs,
|
|
2455
|
+
elapsedMs,
|
|
2456
|
+
kind: entry.kind,
|
|
2457
|
+
...(entry.target
|
|
2458
|
+
? { target: truncate(redactString(entry.target), 200) }
|
|
2459
|
+
: {}),
|
|
2460
|
+
...(url ? { url } : {}),
|
|
2461
|
+
});
|
|
2462
|
+
if (session.interactionEvents.length > 800) {
|
|
2463
|
+
session.interactionEvents.splice(0, session.interactionEvents.length - 800);
|
|
2464
|
+
}
|
|
2465
|
+
}
|
|
2466
|
+
|
|
2467
|
+
function allowClickInputIngress(
|
|
2468
|
+
session: CaptureSession,
|
|
2469
|
+
kind: InteractionKind,
|
|
2470
|
+
): boolean {
|
|
2471
|
+
if (kind !== "click" && kind !== "input") return true;
|
|
2472
|
+
const now = nowMs();
|
|
2473
|
+
if (
|
|
2474
|
+
now - session.clickInputIngressWindowStartedAtMs >=
|
|
2475
|
+
CLICK_INPUT_INGRESS_WINDOW_MS
|
|
2476
|
+
) {
|
|
2477
|
+
session.clickInputIngressWindowStartedAtMs = now;
|
|
2478
|
+
session.clickInputIngressCount = 0;
|
|
2479
|
+
}
|
|
2480
|
+
if (session.clickInputIngressCount >= MAX_CLICK_INPUT_INGRESS_PER_WINDOW) {
|
|
2481
|
+
return false;
|
|
2482
|
+
}
|
|
2483
|
+
session.clickInputIngressCount += 1;
|
|
2484
|
+
return true;
|
|
2485
|
+
}
|
|
2486
|
+
|
|
2290
2487
|
function consoleLevel(value: unknown): ConsoleLevel {
|
|
2291
2488
|
if (
|
|
2292
2489
|
value === "debug" ||
|
|
@@ -2427,7 +2624,7 @@ function handleRequestWillBeSent(
|
|
|
2427
2624
|
: null;
|
|
2428
2625
|
if (!type || !requestId || !request) return;
|
|
2429
2626
|
const timestampMs = requestTimestampMs(event);
|
|
2430
|
-
const elapsedMs =
|
|
2627
|
+
const elapsedMs = diagnosticElapsedMs(timestampMs, session);
|
|
2431
2628
|
if (elapsedMs === null) return;
|
|
2432
2629
|
const url = sanitizeUrl(typeof request.url === "string" ? request.url : "");
|
|
2433
2630
|
if (!url) return;
|
|
@@ -2616,7 +2813,7 @@ function ensureRestored(): Promise<void> {
|
|
|
2616
2813
|
}
|
|
2617
2814
|
void ensureRestored();
|
|
2618
2815
|
|
|
2619
|
-
chrome.runtime.onMessage.addListener((message,
|
|
2816
|
+
chrome.runtime.onMessage.addListener((message, sender, sendResponse) => {
|
|
2620
2817
|
if (!message || typeof message !== "object") return false;
|
|
2621
2818
|
void (async () => {
|
|
2622
2819
|
// Critical: never read activeNativeRecording/overlayPhase before state is
|
|
@@ -2625,7 +2822,7 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
|
2625
2822
|
await ensureRestored();
|
|
2626
2823
|
let response: unknown;
|
|
2627
2824
|
try {
|
|
2628
|
-
response = await dispatchRuntimeMessage(message);
|
|
2825
|
+
response = await dispatchRuntimeMessage(message, sender);
|
|
2629
2826
|
} catch (err) {
|
|
2630
2827
|
console.error(
|
|
2631
2828
|
"[clips-bg] message failed:",
|
|
@@ -2655,9 +2852,39 @@ chrome.runtime.onMessage.addListener((message, _sender, sendResponse) => {
|
|
|
2655
2852
|
return true;
|
|
2656
2853
|
});
|
|
2657
2854
|
|
|
2658
|
-
async function dispatchRuntimeMessage(
|
|
2855
|
+
async function dispatchRuntimeMessage(
|
|
2856
|
+
message: unknown,
|
|
2857
|
+
sender?: chrome.runtime.MessageSender,
|
|
2858
|
+
): Promise<unknown> {
|
|
2659
2859
|
const type = (message as { type?: unknown }).type;
|
|
2660
2860
|
|
|
2861
|
+
if (type === "CLIPS_DIAGNOSTIC_INTERACTION") {
|
|
2862
|
+
const tabId = sender?.tab?.id;
|
|
2863
|
+
const sessionId =
|
|
2864
|
+
typeof tabId === "number" ? tabToSession.get(tabId) : null;
|
|
2865
|
+
const session = sessionId ? sessions.get(sessionId) : null;
|
|
2866
|
+
const kind = (message as { kind?: unknown }).kind;
|
|
2867
|
+
if (
|
|
2868
|
+
!session ||
|
|
2869
|
+
(kind !== "navigation" &&
|
|
2870
|
+
kind !== "click" &&
|
|
2871
|
+
kind !== "input" &&
|
|
2872
|
+
kind !== "scroll")
|
|
2873
|
+
) {
|
|
2874
|
+
return { ok: false };
|
|
2875
|
+
}
|
|
2876
|
+
if (overlayPhase === "paused") return { ok: false };
|
|
2877
|
+
if (!allowClickInputIngress(session, kind)) return { ok: false };
|
|
2878
|
+
const target = (message as { target?: unknown }).target;
|
|
2879
|
+
const url = (message as { url?: unknown }).url;
|
|
2880
|
+
pushInteraction(session, {
|
|
2881
|
+
kind,
|
|
2882
|
+
...(typeof target === "string" ? { target } : {}),
|
|
2883
|
+
...(typeof url === "string" ? { url } : {}),
|
|
2884
|
+
});
|
|
2885
|
+
return { ok: true };
|
|
2886
|
+
}
|
|
2887
|
+
|
|
2661
2888
|
if (type === "CLIPS_EXTENSION_ERROR") {
|
|
2662
2889
|
const report = message as ExtensionErrorMessage;
|
|
2663
2890
|
const error = new Error(report.message || "Chrome extension error");
|
|
@@ -2997,7 +3224,6 @@ chrome.debugger.onDetach.addListener((source) => {
|
|
|
2997
3224
|
if (!sessionId) return;
|
|
2998
3225
|
const session = sessions.get(sessionId);
|
|
2999
3226
|
if (session) session.attached = false;
|
|
3000
|
-
tabToSession.delete(tabId);
|
|
3001
3227
|
});
|
|
3002
3228
|
|
|
3003
3229
|
// ---- Dev auto-reload (unpacked installs only) ------------------------------
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
(function clipsDiagnosticHistoryBridge() {
|
|
2
|
+
const installedFlag = "__clipsDiagnosticHistoryBridgeInstalled";
|
|
3
|
+
const page = window as unknown as Record<string, unknown>;
|
|
4
|
+
if (page[installedFlag]) return;
|
|
5
|
+
page[installedFlag] = true;
|
|
6
|
+
const createToken = (): string => {
|
|
7
|
+
const webCrypto = globalThis.crypto;
|
|
8
|
+
if (typeof webCrypto?.randomUUID === "function") {
|
|
9
|
+
return webCrypto.randomUUID();
|
|
10
|
+
}
|
|
11
|
+
const bytes = new Uint8Array(16);
|
|
12
|
+
let filled = false;
|
|
13
|
+
try {
|
|
14
|
+
if (typeof webCrypto?.getRandomValues === "function") {
|
|
15
|
+
webCrypto.getRandomValues(bytes);
|
|
16
|
+
filled = true;
|
|
17
|
+
}
|
|
18
|
+
// coercion-ok: insecure pages may reject Web Crypto; the fallback still creates a nonce.
|
|
19
|
+
} catch {}
|
|
20
|
+
if (!filled) {
|
|
21
|
+
for (let index = 0; index < bytes.length; index += 1) {
|
|
22
|
+
bytes[index] = Math.floor(Math.random() * 256);
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
bytes[6] = (bytes[6] & 0x0f) | 0x40;
|
|
26
|
+
bytes[8] = (bytes[8] & 0x3f) | 0x80;
|
|
27
|
+
const hex = Array.from(bytes, (byte) =>
|
|
28
|
+
byte.toString(16).padStart(2, "0"),
|
|
29
|
+
).join("");
|
|
30
|
+
return `${hex.slice(0, 8)}-${hex.slice(8, 12)}-${hex.slice(12, 16)}-${hex.slice(16, 20)}-${hex.slice(20)}`;
|
|
31
|
+
};
|
|
32
|
+
const token = createToken();
|
|
33
|
+
|
|
34
|
+
const notify = (): void => {
|
|
35
|
+
window.postMessage(
|
|
36
|
+
{
|
|
37
|
+
source: "clips-diagnostic-history",
|
|
38
|
+
kind: "navigation",
|
|
39
|
+
token,
|
|
40
|
+
url: window.location.href,
|
|
41
|
+
},
|
|
42
|
+
"*",
|
|
43
|
+
);
|
|
44
|
+
};
|
|
45
|
+
window.addEventListener("message", (event) => {
|
|
46
|
+
const data = event.data as { source?: unknown; kind?: unknown } | undefined;
|
|
47
|
+
if (
|
|
48
|
+
event.source === window &&
|
|
49
|
+
data?.source === "clips-diagnostic-history" &&
|
|
50
|
+
data.kind === "request-token"
|
|
51
|
+
) {
|
|
52
|
+
window.postMessage(
|
|
53
|
+
{ source: "clips-diagnostic-history", kind: "token", token },
|
|
54
|
+
"*",
|
|
55
|
+
);
|
|
56
|
+
}
|
|
57
|
+
});
|
|
58
|
+
const originalPushState = history.pushState;
|
|
59
|
+
const originalReplaceState = history.replaceState;
|
|
60
|
+
history.pushState = function patchedPushState(
|
|
61
|
+
this: History,
|
|
62
|
+
state: unknown,
|
|
63
|
+
unused: string,
|
|
64
|
+
url?: string | URL | null,
|
|
65
|
+
) {
|
|
66
|
+
const result = originalPushState.call(this, state, unused, url);
|
|
67
|
+
notify();
|
|
68
|
+
return result;
|
|
69
|
+
};
|
|
70
|
+
history.replaceState = function patchedReplaceState(
|
|
71
|
+
this: History,
|
|
72
|
+
state: unknown,
|
|
73
|
+
unused: string,
|
|
74
|
+
url?: string | URL | null,
|
|
75
|
+
) {
|
|
76
|
+
const result = originalReplaceState.call(this, state, unused, url);
|
|
77
|
+
notify();
|
|
78
|
+
return result;
|
|
79
|
+
};
|
|
80
|
+
})();
|