@agent-native/core 0.180.0-nightly-20260915193344 → 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/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/_document-edit-mutation.ts +74 -23
- package/corpus/templates/content/actions/edit-document.ts +48 -8
- 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/CHANGELOG.md +68 -0
- package/corpus/templates/slides/CHANGELOG.md +32 -0
- package/dist/automation/index.d.ts +1 -1
- 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/awareness.d.ts +2 -2
- package/dist/collab/routes.d.ts +1 -1
- package/dist/collab/struct-routes.d.ts +1 -1
- package/dist/file-upload/actions/upload-image.d.ts +1 -1
- package/dist/notifications/routes.d.ts +3 -3
- package/dist/observability/routes.d.ts +3 -3
- package/dist/progress/routes.d.ts +1 -1
- package/dist/secrets/routes.d.ts +6 -6
- package/dist/server/core-routes-plugin.d.ts +1 -1
- package/package.json +4 -4
|
@@ -22,6 +22,148 @@
|
|
|
22
22
|
flags.__clipsOverlayHostReady = true;
|
|
23
23
|
let recordingActive = false;
|
|
24
24
|
|
|
25
|
+
type InteractionKind = "navigation" | "click" | "input" | "scroll";
|
|
26
|
+
const HISTORY_NAVIGATION_DEDUPE_MS = 250;
|
|
27
|
+
const HISTORY_NAVIGATION_WINDOW_MS = 1000;
|
|
28
|
+
const MAX_HISTORY_NAVIGATION_MESSAGES_PER_WINDOW = 20;
|
|
29
|
+
const CLICK_INPUT_WINDOW_MS = 1000;
|
|
30
|
+
const MAX_CLICK_INPUT_MESSAGES_PER_WINDOW = 100;
|
|
31
|
+
let historyBridgeToken: string | null = null;
|
|
32
|
+
let historyNavigationWindowStartedAt = 0;
|
|
33
|
+
let historyNavigationCount = 0;
|
|
34
|
+
let clickInputWindowStartedAt = 0;
|
|
35
|
+
let clickInputCount = 0;
|
|
36
|
+
let lastHistoryNavigation: { url: string; sentAtMs: number } | null = null;
|
|
37
|
+
const OVERLAY_ROOT_ID = "clips-recorder-overlay-root";
|
|
38
|
+
|
|
39
|
+
function resetDiagnosticQuotas(): void {
|
|
40
|
+
historyNavigationWindowStartedAt = 0;
|
|
41
|
+
historyNavigationCount = 0;
|
|
42
|
+
clickInputWindowStartedAt = 0;
|
|
43
|
+
clickInputCount = 0;
|
|
44
|
+
lastHistoryNavigation = null;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
function targetDescriptor(target: EventTarget | null): string | undefined {
|
|
48
|
+
if (!(target instanceof Element)) return undefined;
|
|
49
|
+
if (target.closest(`#${OVERLAY_ROOT_ID}`)) return undefined;
|
|
50
|
+
const element = target.closest(
|
|
51
|
+
"button,a,input,textarea,select,[role=button]",
|
|
52
|
+
) as Element | null;
|
|
53
|
+
const candidate = element ?? target;
|
|
54
|
+
const tag = candidate.tagName.toLowerCase();
|
|
55
|
+
const id = candidate.id;
|
|
56
|
+
const testId = candidate.getAttribute("data-testid");
|
|
57
|
+
const name = candidate.getAttribute("name");
|
|
58
|
+
const part = [id, testId, name].find((value): value is string => {
|
|
59
|
+
if (!value || value.length > 80) return false;
|
|
60
|
+
return /^[A-Za-z0-9_.:-]+$/.test(value);
|
|
61
|
+
});
|
|
62
|
+
return `${tag}${part ? `#${part}` : ""}`.slice(0, 200);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function sendDiagnosticInteraction(
|
|
66
|
+
kind: InteractionKind,
|
|
67
|
+
target: EventTarget | null = null,
|
|
68
|
+
url?: string,
|
|
69
|
+
): void {
|
|
70
|
+
if (!recordingActive) return;
|
|
71
|
+
if (kind === "click" || kind === "input") {
|
|
72
|
+
const now = Date.now();
|
|
73
|
+
if (now - clickInputWindowStartedAt >= CLICK_INPUT_WINDOW_MS) {
|
|
74
|
+
clickInputWindowStartedAt = now;
|
|
75
|
+
clickInputCount = 0;
|
|
76
|
+
}
|
|
77
|
+
if (clickInputCount >= MAX_CLICK_INPUT_MESSAGES_PER_WINDOW) return;
|
|
78
|
+
clickInputCount += 1;
|
|
79
|
+
}
|
|
80
|
+
try {
|
|
81
|
+
chrome.runtime.sendMessage(
|
|
82
|
+
{
|
|
83
|
+
type: "CLIPS_DIAGNOSTIC_INTERACTION",
|
|
84
|
+
kind,
|
|
85
|
+
target: targetDescriptor(target),
|
|
86
|
+
...(url ? { url } : {}),
|
|
87
|
+
},
|
|
88
|
+
() => void chrome.runtime.lastError,
|
|
89
|
+
);
|
|
90
|
+
// coercion-ok: the extension context can disappear after page unload; capture is best-effort
|
|
91
|
+
} catch {
|
|
92
|
+
/* background unavailable */
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
function sendDiagnosticNavigation(url: string): void {
|
|
97
|
+
if (!recordingActive) return;
|
|
98
|
+
const now = Date.now();
|
|
99
|
+
if (
|
|
100
|
+
lastHistoryNavigation?.url === url &&
|
|
101
|
+
now - lastHistoryNavigation.sentAtMs < HISTORY_NAVIGATION_DEDUPE_MS
|
|
102
|
+
) {
|
|
103
|
+
return;
|
|
104
|
+
}
|
|
105
|
+
if (
|
|
106
|
+
now - historyNavigationWindowStartedAt >=
|
|
107
|
+
HISTORY_NAVIGATION_WINDOW_MS
|
|
108
|
+
) {
|
|
109
|
+
historyNavigationWindowStartedAt = now;
|
|
110
|
+
historyNavigationCount = 0;
|
|
111
|
+
}
|
|
112
|
+
if (historyNavigationCount >= MAX_HISTORY_NAVIGATION_MESSAGES_PER_WINDOW) {
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
historyNavigationCount += 1;
|
|
116
|
+
lastHistoryNavigation = { url, sentAtMs: now };
|
|
117
|
+
sendDiagnosticInteraction("navigation", null, url);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
window.addEventListener("message", (event) => {
|
|
121
|
+
if (event.source !== window) return;
|
|
122
|
+
const data = event.data as
|
|
123
|
+
| { source?: unknown; kind?: unknown; token?: unknown; url?: unknown }
|
|
124
|
+
| undefined;
|
|
125
|
+
if (
|
|
126
|
+
data?.source === "clips-diagnostic-history" &&
|
|
127
|
+
data.kind === "token" &&
|
|
128
|
+
typeof data.token === "string"
|
|
129
|
+
) {
|
|
130
|
+
historyBridgeToken = data.token;
|
|
131
|
+
return;
|
|
132
|
+
}
|
|
133
|
+
if (!recordingActive) return;
|
|
134
|
+
if (
|
|
135
|
+
data?.source !== "clips-diagnostic-history" ||
|
|
136
|
+
data.kind !== "navigation" ||
|
|
137
|
+
data.token !== historyBridgeToken ||
|
|
138
|
+
typeof data.url !== "string"
|
|
139
|
+
) {
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
sendDiagnosticNavigation(data.url);
|
|
143
|
+
});
|
|
144
|
+
window.postMessage(
|
|
145
|
+
{ source: "clips-diagnostic-history", kind: "request-token" },
|
|
146
|
+
"*",
|
|
147
|
+
);
|
|
148
|
+
|
|
149
|
+
let lastScrollAt = 0;
|
|
150
|
+
const onClick = (event: MouseEvent) =>
|
|
151
|
+
sendDiagnosticInteraction("click", event.target);
|
|
152
|
+
const onInput = (event: Event) =>
|
|
153
|
+
sendDiagnosticInteraction("input", event.target);
|
|
154
|
+
const onScroll = (event: Event) => {
|
|
155
|
+
const now = Date.now();
|
|
156
|
+
if (now - lastScrollAt < 250) return;
|
|
157
|
+
lastScrollAt = now;
|
|
158
|
+
sendDiagnosticInteraction("scroll", event.target);
|
|
159
|
+
};
|
|
160
|
+
const onNavigation = () => sendDiagnosticNavigation(window.location.href);
|
|
161
|
+
document.addEventListener("click", onClick, true);
|
|
162
|
+
document.addEventListener("input", onInput, true);
|
|
163
|
+
document.addEventListener("scroll", onScroll, true);
|
|
164
|
+
window.addEventListener("popstate", onNavigation);
|
|
165
|
+
window.addEventListener("hashchange", onNavigation);
|
|
166
|
+
|
|
25
167
|
function errorPayload(error: unknown): {
|
|
26
168
|
name: string;
|
|
27
169
|
message: string;
|
|
@@ -82,7 +224,12 @@
|
|
|
82
224
|
try {
|
|
83
225
|
chrome.storage.onChanged.addListener((changes, areaName) => {
|
|
84
226
|
if (areaName !== "local" || !changes.clipsRecordingActive) return;
|
|
85
|
-
|
|
227
|
+
const nextRecordingActive =
|
|
228
|
+
changes.clipsRecordingActive.newValue === true;
|
|
229
|
+
if (nextRecordingActive && !recordingActive) {
|
|
230
|
+
resetDiagnosticQuotas();
|
|
231
|
+
}
|
|
232
|
+
recordingActive = nextRecordingActive;
|
|
86
233
|
});
|
|
87
234
|
} catch {
|
|
88
235
|
/* storage unavailable */
|
|
@@ -354,8 +501,15 @@
|
|
|
354
501
|
try {
|
|
355
502
|
chrome.storage.local.get("clipsRecordingActive", (value) => {
|
|
356
503
|
if (chrome.runtime.lastError) return;
|
|
357
|
-
|
|
358
|
-
if (recordingActive)
|
|
504
|
+
const nextRecordingActive = value?.clipsRecordingActive === true;
|
|
505
|
+
if (nextRecordingActive && !recordingActive) {
|
|
506
|
+
resetDiagnosticQuotas();
|
|
507
|
+
}
|
|
508
|
+
recordingActive = nextRecordingActive;
|
|
509
|
+
if (recordingActive) {
|
|
510
|
+
sendDiagnosticNavigation(window.location.href);
|
|
511
|
+
requestState();
|
|
512
|
+
}
|
|
359
513
|
});
|
|
360
514
|
} catch {
|
|
361
515
|
/* ignore */
|
|
@@ -554,9 +708,12 @@
|
|
|
554
708
|
});
|
|
555
709
|
}
|
|
556
710
|
|
|
557
|
-
function reconcile(parts: OverlayPart[]): void {
|
|
711
|
+
function reconcile(parts: OverlayPart[], resetQuotas = false): void {
|
|
558
712
|
console.log("[clips-cs] reconcile parts:", parts, "on", location.href);
|
|
559
713
|
const wanted = new Set(parts.filter((p) => ALL_PARTS.includes(p)));
|
|
714
|
+
const enteringRecording =
|
|
715
|
+
wanted.has("toolbar") && !lastWantedParts.has("toolbar");
|
|
716
|
+
if (resetQuotas || enteringRecording) resetDiagnosticQuotas();
|
|
560
717
|
const enteringCameraCountdown =
|
|
561
718
|
wanted.has("countdown") &&
|
|
562
719
|
wanted.has("bubble") &&
|
|
@@ -614,7 +771,11 @@
|
|
|
614
771
|
const type = (message as { type?: unknown }).type;
|
|
615
772
|
if (type === "CLIPS_OVERLAY_MOUNT") {
|
|
616
773
|
const parts = (message as { parts?: unknown }).parts;
|
|
617
|
-
reconcile(
|
|
774
|
+
reconcile(
|
|
775
|
+
Array.isArray(parts) ? (parts as OverlayPart[]) : [],
|
|
776
|
+
(message as { resetDiagnosticQuotas?: unknown })
|
|
777
|
+
.resetDiagnosticQuotas === true,
|
|
778
|
+
);
|
|
618
779
|
} else if (type === "CLIPS_OVERLAY_UNMOUNT") {
|
|
619
780
|
reconcile([]);
|
|
620
781
|
}
|
|
@@ -68,6 +68,10 @@ export default defineConfig({
|
|
|
68
68
|
rollupOptions: {
|
|
69
69
|
input: {
|
|
70
70
|
background: resolve(root, "src/background.ts"),
|
|
71
|
+
"content-history-bridge": resolve(
|
|
72
|
+
root,
|
|
73
|
+
"src/content-history-bridge.ts",
|
|
74
|
+
),
|
|
71
75
|
"content-script": resolve(root, "src/content-script.ts"),
|
|
72
76
|
"github-preview-content": resolve(
|
|
73
77
|
root,
|
|
@@ -305,6 +305,9 @@ export const recordingBrowserDiagnostics = table(
|
|
|
305
305
|
endedAt: text("ended_at").notNull(),
|
|
306
306
|
consoleLogsJson: text("console_logs_json").notNull().default("[]"),
|
|
307
307
|
networkRequestsJson: text("network_requests_json").notNull().default("[]"),
|
|
308
|
+
interactionEventsJson: text("interaction_events_json")
|
|
309
|
+
.notNull()
|
|
310
|
+
.default("[]"),
|
|
308
311
|
redactionVersion: integer("redaction_version").notNull().default(1),
|
|
309
312
|
createdAt: text("created_at").notNull().default(now()),
|
|
310
313
|
updatedAt: text("updated_at").notNull().default(now()),
|
|
@@ -21,7 +21,9 @@ import {
|
|
|
21
21
|
} from "../../shared/agent-context.js";
|
|
22
22
|
import {
|
|
23
23
|
parseBrowserDiagnosticsRow,
|
|
24
|
+
sanitizeBrowserDiagnosticNavigationUrl,
|
|
24
25
|
type BrowserDiagnosticsData,
|
|
26
|
+
type BrowserDiagnosticTimelineEvent,
|
|
25
27
|
} from "../../shared/browser-diagnostics.js";
|
|
26
28
|
import {
|
|
27
29
|
isLoomEmbedBackedRecording,
|
|
@@ -414,6 +416,7 @@ export async function loadAgentBugReport(
|
|
|
414
416
|
// the true total counts.
|
|
415
417
|
const MAX_PUBLIC_AGENT_CONSOLE_LOGS = 100;
|
|
416
418
|
const MAX_PUBLIC_AGENT_NETWORK_REQUESTS = 100;
|
|
419
|
+
const MAX_PUBLIC_AGENT_TIMELINE_EVENTS = 100;
|
|
417
420
|
// Curated warn/error highlights and failed-request highlights.
|
|
418
421
|
const MAX_PUBLIC_AGENT_DIAGNOSTIC_ISSUES = 20;
|
|
419
422
|
|
|
@@ -443,6 +446,37 @@ function toPublicNetworkEntry(
|
|
|
443
446
|
};
|
|
444
447
|
}
|
|
445
448
|
|
|
449
|
+
function toPublicTimelineEntry(entry: BrowserDiagnosticTimelineEvent) {
|
|
450
|
+
const base = {
|
|
451
|
+
timestampMs: entry.elapsedMs,
|
|
452
|
+
kind: entry.kind,
|
|
453
|
+
};
|
|
454
|
+
if (entry.kind === "console") {
|
|
455
|
+
return {
|
|
456
|
+
...base,
|
|
457
|
+
level: entry.level,
|
|
458
|
+
message: entry.message,
|
|
459
|
+
};
|
|
460
|
+
}
|
|
461
|
+
if (entry.kind === "network") {
|
|
462
|
+
return {
|
|
463
|
+
...base,
|
|
464
|
+
phase: entry.phase,
|
|
465
|
+
type: entry.type,
|
|
466
|
+
method: entry.method,
|
|
467
|
+
url: entry.url,
|
|
468
|
+
status: entry.status ?? null,
|
|
469
|
+
error: entry.error ?? null,
|
|
470
|
+
durationMs: entry.durationMs ?? null,
|
|
471
|
+
};
|
|
472
|
+
}
|
|
473
|
+
return {
|
|
474
|
+
...base,
|
|
475
|
+
target: entry.target ?? null,
|
|
476
|
+
url: entry.url ? sanitizeBrowserDiagnosticNavigationUrl(entry.url) : null,
|
|
477
|
+
};
|
|
478
|
+
}
|
|
479
|
+
|
|
446
480
|
function isFailedNetworkRequest(
|
|
447
481
|
entry: BrowserDiagnosticsData["networkRequests"][number],
|
|
448
482
|
): boolean {
|
|
@@ -474,13 +508,17 @@ function compactBrowserDiagnostics(diagnostics: BrowserDiagnosticsData | null) {
|
|
|
474
508
|
.filter(isFailedNetworkRequest)
|
|
475
509
|
.slice(-MAX_PUBLIC_AGENT_DIAGNOSTIC_ISSUES)
|
|
476
510
|
.map(toPublicNetworkEntry);
|
|
511
|
+
const timeline = (diagnostics.timeline ?? [])
|
|
512
|
+
.slice(-MAX_PUBLIC_AGENT_TIMELINE_EVENTS)
|
|
513
|
+
.map(toPublicTimelineEntry);
|
|
477
514
|
return {
|
|
478
515
|
summary: diagnostics.summary,
|
|
516
|
+
timeline,
|
|
479
517
|
consoleLogs,
|
|
480
518
|
consoleIssues,
|
|
481
519
|
networkRequests,
|
|
482
520
|
failedNetworkRequests,
|
|
483
|
-
note: "
|
|
521
|
+
note: "Timeline entries are relative to recording start and include redacted navigation, click/input targets, console events, and request/response markers. Diagnostics are bounded; the recording's own page URL, request headers, request/response bodies, and cookies are omitted.",
|
|
484
522
|
};
|
|
485
523
|
}
|
|
486
524
|
|
|
@@ -613,7 +651,7 @@ export function buildPublicAgentContext({
|
|
|
613
651
|
: []),
|
|
614
652
|
...(browserDiagnostics
|
|
615
653
|
? [
|
|
616
|
-
"Use browserDiagnostics.
|
|
654
|
+
"Use browserDiagnostics.timeline for the bounded, relative event sequence: navigation, click/input targets, console events, and request/response markers. Use browserDiagnostics.consoleLogs and browserDiagnostics.networkRequests for the full redacted streams; consoleIssues and failedNetworkRequests are curated failure highlights.",
|
|
617
655
|
]
|
|
618
656
|
: []),
|
|
619
657
|
...(!clipIsReady
|
|
@@ -1245,6 +1245,11 @@ export const migrations = runMigrations(
|
|
|
1245
1245
|
CREATE INDEX IF NOT EXISTS clips_intake_sessions_expires_idx
|
|
1246
1246
|
ON clips_intake_sessions (status, expires_at)`,
|
|
1247
1247
|
},
|
|
1248
|
+
{
|
|
1249
|
+
version: 73,
|
|
1250
|
+
name: "recording-browser-diagnostics-interaction-events",
|
|
1251
|
+
sql: `ALTER TABLE recording_browser_diagnostics ADD COLUMN IF NOT EXISTS interaction_events_json TEXT NOT NULL DEFAULT '[]'`,
|
|
1252
|
+
},
|
|
1248
1253
|
],
|
|
1249
1254
|
{ table: "clips_migrations" },
|
|
1250
1255
|
);
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
export const MAX_BROWSER_DIAGNOSTIC_CONSOLE_LOGS = 400;
|
|
2
2
|
export const MAX_BROWSER_DIAGNOSTIC_NETWORK_REQUESTS = 400;
|
|
3
|
+
export const MAX_BROWSER_DIAGNOSTIC_INTERACTION_EVENTS = 800;
|
|
4
|
+
export const MAX_BROWSER_DIAGNOSTIC_TIMELINE_EVENTS =
|
|
5
|
+
MAX_BROWSER_DIAGNOSTIC_CONSOLE_LOGS +
|
|
6
|
+
MAX_BROWSER_DIAGNOSTIC_NETWORK_REQUESTS * 2 +
|
|
7
|
+
MAX_BROWSER_DIAGNOSTIC_INTERACTION_EVENTS;
|
|
3
8
|
export const MAX_BROWSER_DIAGNOSTIC_MESSAGE_LENGTH = 2_000;
|
|
9
|
+
export const MAX_BROWSER_DIAGNOSTIC_TARGET_LENGTH = 200;
|
|
4
10
|
export const MAX_BROWSER_DIAGNOSTIC_URL_LENGTH = 1_000;
|
|
5
11
|
|
|
6
12
|
const SECRET_KEY_FRAGMENT =
|
|
@@ -40,6 +46,29 @@ export function redactBrowserDiagnosticString(
|
|
|
40
46
|
: redacted;
|
|
41
47
|
}
|
|
42
48
|
|
|
49
|
+
export function sanitizeBrowserDiagnosticNavigationUrl(raw: string): string {
|
|
50
|
+
const redacted = redactBrowserDiagnosticString(raw, {
|
|
51
|
+
redactQueryValues: true,
|
|
52
|
+
});
|
|
53
|
+
try {
|
|
54
|
+
const parsed = new URL(redacted, "https://clips.local");
|
|
55
|
+
parsed.username = "";
|
|
56
|
+
parsed.password = "";
|
|
57
|
+
parsed.hash = "";
|
|
58
|
+
const params = new URLSearchParams();
|
|
59
|
+
for (const key of parsed.searchParams.keys()) {
|
|
60
|
+
params.set(key, "<redacted>");
|
|
61
|
+
}
|
|
62
|
+
parsed.search = params.toString();
|
|
63
|
+
return `${parsed.pathname}${parsed.search}`.slice(
|
|
64
|
+
0,
|
|
65
|
+
MAX_BROWSER_DIAGNOSTIC_URL_LENGTH,
|
|
66
|
+
);
|
|
67
|
+
} catch {
|
|
68
|
+
return "<redacted>";
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
|
|
43
72
|
export type BrowserDiagnosticConsoleLevel =
|
|
44
73
|
| "debug"
|
|
45
74
|
| "log"
|
|
@@ -68,6 +97,50 @@ export interface BrowserDiagnosticNetworkRequest {
|
|
|
68
97
|
error?: string;
|
|
69
98
|
}
|
|
70
99
|
|
|
100
|
+
export type BrowserDiagnosticInteractionKind =
|
|
101
|
+
| "navigation"
|
|
102
|
+
| "click"
|
|
103
|
+
| "input"
|
|
104
|
+
| "scroll";
|
|
105
|
+
|
|
106
|
+
export interface BrowserDiagnosticInteractionEvent {
|
|
107
|
+
timestampMs: number;
|
|
108
|
+
elapsedMs: number;
|
|
109
|
+
kind: BrowserDiagnosticInteractionKind;
|
|
110
|
+
target?: string;
|
|
111
|
+
url?: string;
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
type BrowserDiagnosticTimelineBase = Pick<
|
|
115
|
+
BrowserDiagnosticInteractionEvent,
|
|
116
|
+
"timestampMs" | "elapsedMs"
|
|
117
|
+
>;
|
|
118
|
+
|
|
119
|
+
export type BrowserDiagnosticTimelineEvent =
|
|
120
|
+
| (BrowserDiagnosticTimelineBase & {
|
|
121
|
+
kind: BrowserDiagnosticInteractionKind;
|
|
122
|
+
target?: string;
|
|
123
|
+
url?: string;
|
|
124
|
+
})
|
|
125
|
+
| (BrowserDiagnosticTimelineBase & {
|
|
126
|
+
kind: "console";
|
|
127
|
+
level: BrowserDiagnosticConsoleLevel;
|
|
128
|
+
message: string;
|
|
129
|
+
stack?: string;
|
|
130
|
+
})
|
|
131
|
+
| (BrowserDiagnosticTimelineBase & {
|
|
132
|
+
kind: "network";
|
|
133
|
+
phase: "request" | "response";
|
|
134
|
+
type: BrowserDiagnosticNetworkRequest["type"];
|
|
135
|
+
method: string;
|
|
136
|
+
url: string;
|
|
137
|
+
status?: number;
|
|
138
|
+
statusText?: string;
|
|
139
|
+
ok?: boolean;
|
|
140
|
+
durationMs?: number;
|
|
141
|
+
error?: string;
|
|
142
|
+
});
|
|
143
|
+
|
|
71
144
|
export interface BrowserDiagnosticsSnapshot {
|
|
72
145
|
pageUrl: string | null;
|
|
73
146
|
userAgent: string | null;
|
|
@@ -75,6 +148,7 @@ export interface BrowserDiagnosticsSnapshot {
|
|
|
75
148
|
endedAt: string;
|
|
76
149
|
consoleLogs: BrowserDiagnosticConsoleLog[];
|
|
77
150
|
networkRequests: BrowserDiagnosticNetworkRequest[];
|
|
151
|
+
interactionEvents?: BrowserDiagnosticInteractionEvent[];
|
|
78
152
|
}
|
|
79
153
|
|
|
80
154
|
export interface BrowserDiagnosticsSummary {
|
|
@@ -88,6 +162,7 @@ export interface BrowserDiagnosticsSummary {
|
|
|
88
162
|
|
|
89
163
|
export interface BrowserDiagnosticsData extends BrowserDiagnosticsSnapshot {
|
|
90
164
|
summary: BrowserDiagnosticsSummary;
|
|
165
|
+
timeline?: BrowserDiagnosticTimelineEvent[];
|
|
91
166
|
}
|
|
92
167
|
|
|
93
168
|
function safeArray(value: string | null | undefined): unknown[] {
|
|
@@ -176,6 +251,93 @@ export function normalizeBrowserDiagnosticNetworkRequest(
|
|
|
176
251
|
};
|
|
177
252
|
}
|
|
178
253
|
|
|
254
|
+
export function normalizeBrowserDiagnosticInteractionEvent(
|
|
255
|
+
value: unknown,
|
|
256
|
+
): BrowserDiagnosticInteractionEvent | null {
|
|
257
|
+
if (!value || typeof value !== "object") return null;
|
|
258
|
+
const entry = value as Record<string, unknown>;
|
|
259
|
+
const timestampMs = safeNumber(entry.timestampMs);
|
|
260
|
+
const elapsedMs = safeNumber(entry.elapsedMs);
|
|
261
|
+
const kind =
|
|
262
|
+
entry.kind === "navigation" ||
|
|
263
|
+
entry.kind === "click" ||
|
|
264
|
+
entry.kind === "input" ||
|
|
265
|
+
entry.kind === "scroll"
|
|
266
|
+
? entry.kind
|
|
267
|
+
: null;
|
|
268
|
+
if (timestampMs === null || elapsedMs === null || !kind) return null;
|
|
269
|
+
const target = safeString(entry.target, MAX_BROWSER_DIAGNOSTIC_TARGET_LENGTH);
|
|
270
|
+
const rawUrl = safeString(entry.url, MAX_BROWSER_DIAGNOSTIC_URL_LENGTH);
|
|
271
|
+
const url = rawUrl ? sanitizeBrowserDiagnosticNavigationUrl(rawUrl) : null;
|
|
272
|
+
return {
|
|
273
|
+
timestampMs,
|
|
274
|
+
elapsedMs,
|
|
275
|
+
kind,
|
|
276
|
+
...(target ? { target } : {}),
|
|
277
|
+
...(url ? { url } : {}),
|
|
278
|
+
};
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
export function buildBrowserDiagnosticTimeline({
|
|
282
|
+
consoleLogs,
|
|
283
|
+
networkRequests,
|
|
284
|
+
interactionEvents = [],
|
|
285
|
+
}: Pick<
|
|
286
|
+
BrowserDiagnosticsSnapshot,
|
|
287
|
+
"consoleLogs" | "networkRequests" | "interactionEvents"
|
|
288
|
+
>): BrowserDiagnosticTimelineEvent[] {
|
|
289
|
+
const entries: Array<{
|
|
290
|
+
event: BrowserDiagnosticTimelineEvent;
|
|
291
|
+
order: number;
|
|
292
|
+
}> = [];
|
|
293
|
+
let order = 0;
|
|
294
|
+
const add = (event: BrowserDiagnosticTimelineEvent) => {
|
|
295
|
+
entries.push({ event, order: order++ });
|
|
296
|
+
};
|
|
297
|
+
|
|
298
|
+
for (const event of interactionEvents) add(event);
|
|
299
|
+
for (const entry of consoleLogs) {
|
|
300
|
+
add({
|
|
301
|
+
timestampMs: entry.timestampMs,
|
|
302
|
+
elapsedMs: entry.elapsedMs,
|
|
303
|
+
kind: "console",
|
|
304
|
+
level: entry.level,
|
|
305
|
+
message: entry.message,
|
|
306
|
+
...(entry.stack ? { stack: entry.stack } : {}),
|
|
307
|
+
});
|
|
308
|
+
}
|
|
309
|
+
for (const entry of networkRequests) {
|
|
310
|
+
add({
|
|
311
|
+
timestampMs: entry.timestampMs,
|
|
312
|
+
elapsedMs: entry.elapsedMs,
|
|
313
|
+
kind: "network",
|
|
314
|
+
phase: "request",
|
|
315
|
+
type: entry.type,
|
|
316
|
+
method: entry.method,
|
|
317
|
+
url: entry.url,
|
|
318
|
+
});
|
|
319
|
+
add({
|
|
320
|
+
timestampMs: entry.timestampMs + entry.durationMs,
|
|
321
|
+
elapsedMs: entry.elapsedMs + entry.durationMs,
|
|
322
|
+
kind: "network",
|
|
323
|
+
phase: "response",
|
|
324
|
+
type: entry.type,
|
|
325
|
+
method: entry.method,
|
|
326
|
+
url: entry.url,
|
|
327
|
+
...(typeof entry.status === "number" ? { status: entry.status } : {}),
|
|
328
|
+
...(entry.statusText ? { statusText: entry.statusText } : {}),
|
|
329
|
+
...(typeof entry.ok === "boolean" ? { ok: entry.ok } : {}),
|
|
330
|
+
durationMs: entry.durationMs,
|
|
331
|
+
...(entry.error ? { error: entry.error } : {}),
|
|
332
|
+
});
|
|
333
|
+
}
|
|
334
|
+
|
|
335
|
+
return entries
|
|
336
|
+
.sort((a, b) => a.event.elapsedMs - b.event.elapsedMs || a.order - b.order)
|
|
337
|
+
.slice(0, MAX_BROWSER_DIAGNOSTIC_TIMELINE_EVENTS)
|
|
338
|
+
.map(({ event }) => event);
|
|
339
|
+
}
|
|
340
|
+
|
|
179
341
|
export function summarizeBrowserDiagnostics(
|
|
180
342
|
snapshot: Pick<
|
|
181
343
|
BrowserDiagnosticsSnapshot,
|
|
@@ -209,6 +371,7 @@ export function parseBrowserDiagnosticsRow(
|
|
|
209
371
|
endedAt?: string | null;
|
|
210
372
|
consoleLogsJson?: string | null;
|
|
211
373
|
networkRequestsJson?: string | null;
|
|
374
|
+
interactionEventsJson?: string | null;
|
|
212
375
|
}
|
|
213
376
|
| null
|
|
214
377
|
| undefined,
|
|
@@ -222,6 +385,12 @@ export function parseBrowserDiagnosticsRow(
|
|
|
222
385
|
.map(normalizeBrowserDiagnosticNetworkRequest)
|
|
223
386
|
.filter((entry): entry is BrowserDiagnosticNetworkRequest => Boolean(entry))
|
|
224
387
|
.slice(0, MAX_BROWSER_DIAGNOSTIC_NETWORK_REQUESTS);
|
|
388
|
+
const interactionEvents = safeArray(row.interactionEventsJson)
|
|
389
|
+
.map(normalizeBrowserDiagnosticInteractionEvent)
|
|
390
|
+
.filter((entry): entry is BrowserDiagnosticInteractionEvent =>
|
|
391
|
+
Boolean(entry),
|
|
392
|
+
)
|
|
393
|
+
.slice(0, MAX_BROWSER_DIAGNOSTIC_INTERACTION_EVENTS);
|
|
225
394
|
const snapshot: BrowserDiagnosticsSnapshot = {
|
|
226
395
|
pageUrl: row.pageUrl ?? null,
|
|
227
396
|
userAgent: row.userAgent ?? null,
|
|
@@ -229,9 +398,11 @@ export function parseBrowserDiagnosticsRow(
|
|
|
229
398
|
endedAt: row.endedAt ?? "",
|
|
230
399
|
consoleLogs,
|
|
231
400
|
networkRequests,
|
|
401
|
+
interactionEvents,
|
|
232
402
|
};
|
|
233
403
|
return {
|
|
234
404
|
...snapshot,
|
|
235
405
|
summary: summarizeBrowserDiagnostics(snapshot),
|
|
406
|
+
timeline: buildBrowserDiagnosticTimeline(snapshot),
|
|
236
407
|
};
|
|
237
408
|
}
|
|
@@ -33,8 +33,10 @@ actions, and local folders that sync into the same collection model.
|
|
|
33
33
|
`share-local-file-document`, `list-local-component-files`, and
|
|
34
34
|
`write-local-component-file`.
|
|
35
35
|
- Use `pull-document` or `get-document` before editing a page. Use
|
|
36
|
-
`edit-document` for
|
|
37
|
-
|
|
36
|
+
`edit-document` for revision-guarded body changes. When `get-document`
|
|
37
|
+
returns a literally empty body, initialize it with `initializeContent`, the
|
|
38
|
+
returned `baseRevision`, and a stable `idempotencyKey`. Browser-owned full
|
|
39
|
+
rewrites and metadata changes use `update-document`.
|
|
38
40
|
- Use `list-documents` or `search-documents` to find an existing collection by
|
|
39
41
|
its exact title, then inspect it with `get-content-database` before creating
|
|
40
42
|
or submitting anything. Do not create a second collection when the canonical
|
|
@@ -99,9 +99,16 @@ pnpm action edit-document --id abc123 --find "delete me" --replace ""
|
|
|
99
99
|
pnpm action edit-document --id abc123 --edits '[{"find":"old","replace":"new"},{"find":"also old","replace":"also new"}]'
|
|
100
100
|
```
|
|
101
101
|
|
|
102
|
+
External MCP, WebMCP, tool, and A2A callers first read the document, then pass
|
|
103
|
+
its `baseRevision` and one stable `idempotencyKey`. When the returned body is
|
|
104
|
+
literally empty, pass non-whitespace `initializeContent` instead of `find` or `edits`.
|
|
105
|
+
Initialization rejects whitespace-only and all other nonempty bodies, preserves
|
|
106
|
+
the Markdown bytes exactly, and safely replays an identical retry.
|
|
107
|
+
|
|
102
108
|
### update-document
|
|
103
109
|
|
|
104
|
-
Update an existing document
|
|
110
|
+
Update an existing document's metadata or browser-owned content. External
|
|
111
|
+
callers use the revisioned `edit-document` protocol for every body change.
|
|
105
112
|
|
|
106
113
|
```bash
|
|
107
114
|
pnpm action update-document --id abc123 --title "New Title"
|
|
@@ -25,10 +25,9 @@ Read the relevant skill before deeper work:
|
|
|
25
25
|
and access checks are preserved. Never use `curl`, raw HTTP requests, or
|
|
26
26
|
`db-exec` with raw SQL for document operations.
|
|
27
27
|
- Call these actions directly; `ask_app` only delegates to Content's agent.
|
|
28
|
-
- The
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
and syncs live via CRDT instead of regenerating the whole document).
|
|
28
|
+
- The live Yjs editor requires actions for body writes. External agents use
|
|
29
|
+
revisioned `edit-document`, with `initializeContent` only for an empty body.
|
|
30
|
+
Browser full rewrites use `update-document`.
|
|
32
31
|
- Preserve user-authored content. Prefer targeted edits over wholesale rewrites
|
|
33
32
|
unless requested.
|
|
34
33
|
- `create-document`, `update-document`, and `delete-document` already signal
|
|
@@ -80,8 +79,8 @@ Read the relevant skill before deeper work:
|
|
|
80
79
|
| `get-blocks-field-word-count` | Count one exact Blocks field; omit `propertyId` for the primary Content body |
|
|
81
80
|
| `create-document` | Create a page, optionally under a parent |
|
|
82
81
|
| `resolve-content-landing` | Restore the caller's last authorized page |
|
|
83
|
-
| `edit-document` |
|
|
84
|
-
| `update-document` |
|
|
82
|
+
| `edit-document` | Revisioned find/replace, or initialize an empty body |
|
|
83
|
+
| `update-document` | Metadata or browser-owned full rewrite |
|
|
85
84
|
| `delete-document` | Move a page and its children to Trash |
|
|
86
85
|
| `list-content-database-blocks` | List stable blocks and revisions in one exact collection row/property |
|
|
87
86
|
| `mutate-content-database-block` | Insert, update, upsert, delete, or reorder one supported stable block |
|
|
@@ -5,14 +5,34 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
5
5
|
|
|
6
6
|
## 2026-09-14
|
|
7
7
|
|
|
8
|
+
### Improved
|
|
9
|
+
|
|
10
|
+
- Search is now available from the Content sidebar, with clearer date filters and keyboard-friendly calendar navigation.
|
|
11
|
+
|
|
8
12
|
### Fixed
|
|
9
13
|
|
|
14
|
+
- Pages created in agent conversations now appear in navigation and open when they are ready.
|
|
15
|
+
- Cmd+K now opens the command menu while writing in a page.
|
|
10
16
|
- The block drag-handle menu now always opens beside the block instead of sometimes jumping to the corner of the window.
|
|
11
17
|
- Pasting a checklist from Notion or GitHub now keeps its checkboxes instead of turning into plain bullets.
|
|
12
18
|
- Asking the agent about Notion no longer reports "not connected" when the Notion MCP server is connected — it now names the account connection and the MCP connection separately.
|
|
13
19
|
|
|
20
|
+
## 2026-09-13
|
|
21
|
+
|
|
22
|
+
### Fixed
|
|
23
|
+
|
|
24
|
+
- Suggestion previews and highlights stay aligned when editing text near lists.
|
|
25
|
+
|
|
14
26
|
## 2026-09-12
|
|
15
27
|
|
|
28
|
+
### Improved
|
|
29
|
+
|
|
30
|
+
- Search with quoted phrases, exclusions, OR, and intitle: operators, search all workspaces, pick a modified date from the calendar, and see snippets anchored at the matching sentence deep inside long pages.
|
|
31
|
+
|
|
32
|
+
### Fixed
|
|
33
|
+
|
|
34
|
+
- Setting up Notion from a document now opens the Integrations settings, where Notion can actually be connected.
|
|
35
|
+
|
|
16
36
|
### Changed
|
|
17
37
|
|
|
18
38
|
- Databases are now called **collections** across Content — same pages, views, and sharing, friendlier name.
|
|
@@ -29,6 +49,10 @@ time from the command menu (Cmd+K → "What's new").
|
|
|
29
49
|
|
|
30
50
|
- Connected agents can set up ordinary databases, configure fields and table views, and recover them from Trash with verified results.
|
|
31
51
|
|
|
52
|
+
### Improved
|
|
53
|
+
|
|
54
|
+
- Search pages and databases with scoped filters, result previews, and keyboard navigation across result pages.
|
|
55
|
+
|
|
32
56
|
### Fixed
|
|
33
57
|
|
|
34
58
|
- Sidebar Search opens the command picker, icon controls have accessible names, and the sidebar can be resized with the keyboard.
|