@wenathlan/extension 1.1.45 → 1.1.47

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.
@@ -1,3 +1,229 @@
1
+ // profilers.ts
2
+ var profilerkinds = ["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"];
3
+ var flowmetricnames = ["navigation", "paint", "lcp", "fid", "interaction", "blocking"];
4
+ var tracecategories = ["navigation", "scripting", "rendering", "painting", "loading", "network"];
5
+ function attachtargetof(value) {
6
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
7
+ const entry = value;
8
+ const kinds = ["page", "iframe", "worker", "serviceworker"];
9
+ const kind = typeof entry.kind === "string" && kinds.includes(entry.kind) ? entry.kind : void 0;
10
+ const url = typeof entry.url === "string" && entry.url.trim() ? entry.url.trim() : void 0;
11
+ if (kind === void 0 || url === void 0) return void 0;
12
+ if (kind !== "page" && !/^https:\/\//.test(url)) return void 0;
13
+ return { kind, url };
14
+ }
15
+ function flowspecof(value) {
16
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
17
+ const entry = value;
18
+ const prefix = typeof entry.prefix === "string" && entry.prefix.trim() ? entry.prefix.trim() : void 0;
19
+ const steps = Array.isArray(entry.steps) ? entry.steps.filter((step) => typeof step === "string" && step.trim().length > 0) : [];
20
+ const metrics = Array.isArray(entry.metrics) ? entry.metrics.filter((metric) => typeof metric === "string" && flowmetricnames.includes(metric)) : [];
21
+ if (prefix === void 0 || steps.length === 0 || metrics.length === 0) return void 0;
22
+ return { prefix, steps, metrics };
23
+ }
24
+ function stepwindows(spec, marks) {
25
+ const windows = [];
26
+ for (const stepid of spec.steps) {
27
+ const start = marks.find((mark) => mark.type === "mark" && mark.name === `${spec.prefix}:${stepid}:start`);
28
+ const end = marks.find((mark) => mark.type === "mark" && mark.name === `${spec.prefix}:${stepid}:end`);
29
+ if (start === void 0 || end === void 0) continue;
30
+ windows.push({ stepid, start: start.start, end: Math.max(end.start, start.start) });
31
+ }
32
+ return windows;
33
+ }
34
+ function measure(input) {
35
+ const metrics = [];
36
+ const windows = stepwindows(input.spec, input.entries.filter((entry) => entry.type === "mark"));
37
+ const stepsof = (start, end) => windows.filter((window2) => window2.end >= start && window2.start <= end).map((window2) => window2.stepid);
38
+ const push = (name, start, end, steps) => {
39
+ if (!input.spec.metrics.includes(name)) return;
40
+ metrics.push({ id: `${input.runid}-${input.stepid}-${name}-${metrics.length}`, runid: input.runid, stepid: input.stepid, name, start, end, duration: Math.max(0, end - start), steps, at: input.now });
41
+ };
42
+ const navigation = input.entries.find((entry) => entry.type === "navigation");
43
+ if (navigation !== void 0) push("navigation", navigation.start, navigation.start + navigation.duration, stepsof(navigation.start, navigation.start + navigation.duration));
44
+ for (const paint of input.entries.filter((entry) => entry.type === "paint")) {
45
+ if (!input.spec.metrics.includes("paint")) break;
46
+ push("paint", paint.start, paint.start + paint.duration, stepsof(paint.start, paint.start + paint.duration));
47
+ }
48
+ const lcps = input.entries.filter((entry) => entry.type === "largest-contentful-paint");
49
+ const lcp = lcps.length > 0 ? lcps.reduce((largest, entry) => entry.start > largest.start ? entry : largest) : void 0;
50
+ if (lcp !== void 0) push("lcp", lcp.start, lcp.start + lcp.duration, stepsof(lcp.start, lcp.start + lcp.duration));
51
+ const firstinput = input.entries.find((entry) => entry.type === "first-input");
52
+ if (firstinput !== void 0) push("fid", firstinput.start, firstinput.start + firstinput.duration, stepsof(firstinput.start, firstinput.start + firstinput.duration));
53
+ const interactions = input.entries.filter((entry) => entry.type === "event");
54
+ if (interactions.length > 0) {
55
+ const start = interactions.reduce((earliest, entry) => entry.start < earliest.start ? entry : earliest).start;
56
+ const end = interactions.reduce((latest, entry) => entry.start + entry.duration > latest ? entry.start + entry.duration : latest, start);
57
+ push("interaction", start, end, stepsof(start, end));
58
+ }
59
+ for (const window2 of windows) {
60
+ if (!input.spec.metrics.includes("blocking")) break;
61
+ const blocking = input.entries.filter((entry) => entry.type === "longtask" && entry.start >= window2.start && entry.start <= window2.end).reduce((total, entry) => total + Math.max(0, entry.duration - 50), 0);
62
+ metrics.push({ id: `${input.runid}-${input.stepid}-blocking-${window2.stepid}`, runid: input.runid, stepid: input.stepid, name: "blocking", start: window2.start, end: window2.end, duration: blocking, steps: [window2.stepid], at: input.now });
63
+ }
64
+ return metrics;
65
+ }
66
+ function heapintervalallowed(lastcapturedat, interval, now) {
67
+ if (interval === void 0 || lastcapturedat === void 0) return true;
68
+ return now - lastcapturedat >= interval;
69
+ }
70
+ function heapsnap(input) {
71
+ return { id: input.id, runid: input.runid, stepid: input.stepid, origin: input.origin, bytesize: input.usedbytes, nodecount: input.nodecount, capturedat: input.now };
72
+ }
73
+ function growsampleof(input) {
74
+ return { id: input.id, runid: input.runid, stepid: input.stepid, usedbytes: input.usedbytes, limitbytes: input.limitbytes, at: input.now };
75
+ }
76
+ function growthtrend(input) {
77
+ const ordered = [...input.samples].sort((left, right) => left.at - right.at);
78
+ const first = ordered[0];
79
+ const last = ordered[ordered.length - 1];
80
+ const computed = first !== void 0 && last !== void 0 && last.at > first.at ? (last.usedbytes - first.usedbytes) / (last.at - first.at) : 0;
81
+ const flaggedsteps = [];
82
+ for (let index = 1; index < ordered.length; index += 1) {
83
+ const previous = ordered[index - 1];
84
+ const current = ordered[index];
85
+ if (previous === void 0 || current === void 0) continue;
86
+ const growth = current.at > previous.at ? (current.usedbytes - previous.usedbytes) / (current.at - previous.at) : 0;
87
+ if (growth > input.slope && !flaggedsteps.includes(current.stepid)) flaggedsteps.push(current.stepid);
88
+ }
89
+ return { runid: input.runid, slope: computed, samples: ordered.length, flaggedsteps, at: input.now };
90
+ }
91
+ function cpusnap(input) {
92
+ const ranked = /* @__PURE__ */ new Map();
93
+ for (const sample of input.samples) ranked.set(sample.name, (ranked.get(sample.name) ?? 0) + sample.time);
94
+ const hotfunctions = [...ranked.entries()].sort((left, right) => right[1] - left[1] || left[0].localeCompare(right[0])).map((entry) => entry[0]);
95
+ return { id: input.id, runid: input.runid, stepid: input.stepid, origin: input.origin, duration: input.duration, samplecount: input.samples.length, hotfunctions, at: input.now };
96
+ }
97
+ function shiftentryof(value) {
98
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
99
+ const entry = value;
100
+ const score = typeof entry.score === "number" && Number.isFinite(entry.score) && entry.score >= 0 ? entry.score : void 0;
101
+ const starttime = typeof entry.starttime === "number" && Number.isFinite(entry.starttime) ? entry.starttime : void 0;
102
+ if (score === void 0 || starttime === void 0) return void 0;
103
+ const selectors = Array.isArray(entry.selectors) ? entry.selectors.filter((selector) => typeof selector === "string" && selector.trim().length > 0) : [];
104
+ return { id: typeof entry.id === "string" ? entry.id : "", runid: typeof entry.runid === "string" ? entry.runid : "", stepid: typeof entry.stepid === "string" ? entry.stepid : "", score, starttime, selectors, at: typeof entry.at === "number" ? entry.at : starttime };
105
+ }
106
+ function tracestart(input) {
107
+ return { id: input.id, runid: input.runid, stepid: input.stepid, origin: input.origin, categories: [...new Set(input.categories)], bytesize: 0, events: 0, annotations: [], startedat: input.now, endedat: input.now };
108
+ }
109
+ function tracetofile(trace, events) {
110
+ const payload = { devthinktrace: "1.1.47", runid: trace.runid, origin: trace.origin, categories: trace.categories, startedat: trace.startedat, endedat: trace.endedat, annotations: trace.annotations, traceevents: events.map((event) => ({ name: event.name, cat: event.category, offset: event.offset })) };
111
+ const content = JSON.stringify(payload);
112
+ return { content, bytesize: content.length, events: events.length };
113
+ }
114
+ function annotationof(value) {
115
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
116
+ const entry = value;
117
+ const stepid = typeof entry.stepid === "string" && entry.stepid.trim() ? entry.stepid.trim() : void 0;
118
+ const label = typeof entry.label === "string" && entry.label.trim() ? entry.label.trim() : void 0;
119
+ if (stepid === void 0 || label === void 0) return void 0;
120
+ const offset = typeof entry.offset === "number" && Number.isFinite(entry.offset) && entry.offset >= 0 ? entry.offset : 0;
121
+ return { stepid, label, offset };
122
+ }
123
+ function annotatetrace(input) {
124
+ const aligned = input.annotations.map((annotation) => {
125
+ const entry = input.timeline.find((item) => item.stepid === annotation.stepid);
126
+ if (entry === void 0) return annotation;
127
+ return { ...annotation, offset: Math.max(0, entry.time - input.trace.startedat) };
128
+ });
129
+ return { ...input.trace, annotations: aligned, endedat: Math.max(input.trace.endedat, input.now) };
130
+ }
131
+ function replaytrace(content) {
132
+ let parsed;
133
+ try {
134
+ parsed = JSON.parse(content);
135
+ } catch {
136
+ throw new Error("The trace file is not valid json.");
137
+ }
138
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("The trace file is not a json object.");
139
+ const record2 = parsed;
140
+ const runid = typeof record2.runid === "string" ? record2.runid : "";
141
+ const annotations = Array.isArray(record2.annotations) ? record2.annotations.flatMap((item) => {
142
+ const annotation = annotationof(item);
143
+ return annotation !== void 0 ? [annotation] : [];
144
+ }) : [];
145
+ const rawevents = Array.isArray(record2.traceevents) ? record2.traceevents : [];
146
+ const categories = {};
147
+ const events = rawevents.flatMap((item) => {
148
+ if (!item || typeof item !== "object") return [];
149
+ const entry = item;
150
+ if (typeof entry.name !== "string" || typeof entry.cat !== "string" || typeof entry.offset !== "number") return [];
151
+ const offset = entry.offset;
152
+ categories[entry.cat] = (categories[entry.cat] ?? 0) + 1;
153
+ const annotation = annotations.find((candidate) => Math.abs(candidate.offset - offset) < 1);
154
+ return [{ name: entry.name, category: entry.cat, offset, ...annotation !== void 0 ? { stepid: annotation.stepid } : {} }];
155
+ });
156
+ return { traceid: typeof record2.id === "string" ? record2.id : "", runid, categories, events, annotations };
157
+ }
158
+ function mapurlof(scripturl, source) {
159
+ const match = /[#@]\s*sourceMappingURL=(\S+)/.exec(source);
160
+ if (match === null || match[1] === void 0) return void 0;
161
+ try {
162
+ return new URL(match[1], scripturl).toString();
163
+ } catch {
164
+ return void 0;
165
+ }
166
+ }
167
+ function capturesourcemaps(input) {
168
+ return input.scripts.flatMap((script) => {
169
+ const mapurl = mapurlof(script.url, script.source);
170
+ if (mapurl === void 0) return [];
171
+ return [{ id: `${input.runid}-${script.url}`, runid: input.runid, stepid: input.stepid, origin: input.origin, scripturl: script.url, mapurl, parsed: false, at: input.now }];
172
+ });
173
+ }
174
+ function decodevlq(segment) {
175
+ const values = [];
176
+ let shift = 0;
177
+ let value = 0;
178
+ for (const character of segment) {
179
+ const digit = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/".indexOf(character);
180
+ if (digit < 0) return void 0;
181
+ value += (digit & 31) << shift;
182
+ shift += 5;
183
+ if ((digit & 32) === 0) {
184
+ const negative = (value & 1) === 1;
185
+ values.push(negative ? -(value >>> 1) : value >>> 1);
186
+ value = 0;
187
+ shift = 0;
188
+ }
189
+ }
190
+ return values.length > 0 ? values : void 0;
191
+ }
192
+ function rewritesourcelocation(input, map) {
193
+ const sources = Array.isArray(map.sources) ? map.sources.filter((source2) => typeof source2 === "string") : [];
194
+ if (sources.length === 0 || typeof map.mappings !== "string" || map.mappings.length === 0) return void 0;
195
+ const lines = map.mappings.split(";");
196
+ if (input.line >= lines.length) return void 0;
197
+ let sourceindex = 0;
198
+ let sourceline = 0;
199
+ for (let line = 0; line <= input.line; line += 1) {
200
+ const linemappings = lines[line];
201
+ if (linemappings === void 0) continue;
202
+ const first = linemappings.split(",")[0] ?? "";
203
+ if (first.length === 0) continue;
204
+ const values = decodevlq(first);
205
+ if (values === void 0 || values.length < 4) continue;
206
+ sourceindex = Math.max(0, sourceindex + (values[1] ?? 0));
207
+ sourceline = Math.max(0, sourceline + (values[2] ?? 0));
208
+ }
209
+ const targetmappings = lines[input.line];
210
+ const target = targetmappings !== void 0 ? targetmappings.split(",")[0] ?? "" : "";
211
+ if (target.length === 0) return void 0;
212
+ const source = sources[Math.min(sources.length - 1, sourceindex)];
213
+ if (source === void 0) return void 0;
214
+ return { url: source, line: sourceline };
215
+ }
216
+ function expireprofilerecords(input) {
217
+ const retention = input.retention;
218
+ if (retention === void 0) return { heaps: input.heaps, profiles: input.profiles, traces: input.traces };
219
+ const expired = (at) => input.now - at > retention;
220
+ return {
221
+ heaps: input.heaps.map((heap) => expired(heap.capturedat) && heap.bytesexpired !== true ? { ...heap, bytesexpired: true } : heap),
222
+ profiles: input.profiles.map((profile) => expired(profile.at) && profile.samplesexpired !== true ? { ...profile, samplesexpired: true } : profile),
223
+ traces: input.traces.map((trace) => expired(trace.endedat) && trace.bytesexpired !== true ? { ...trace, bytesexpired: true } : trace)
224
+ };
225
+ }
226
+
1
227
  // memory.ts
2
228
  var sessionmemory = class {
3
229
  constructor(adapter) {
@@ -1126,6 +1352,108 @@ var sessionmemory = class {
1126
1352
  async getconsoleconsents() {
1127
1353
  return await this.adapter.get("consoleconsents") ?? [];
1128
1354
  }
1355
+ /** Stores one devtools session record with its enabled domains and detach state, replacing the previous record of its id. */
1356
+ async setcdpsession(session) {
1357
+ const records = (await this.adapter.get("cdpsessions") ?? []).filter((item) => item.id !== session.id);
1358
+ await this.adapter.set("cdpsessions", [session, ...records]);
1359
+ }
1360
+ /** Returns every stored devtools session record, newest first. */
1361
+ async getcdpsessions() {
1362
+ return await this.adapter.get("cdpsessions") ?? [];
1363
+ }
1364
+ /** Stores one raw command outcome with its duration and error class. */
1365
+ async addcdpcommand(command) {
1366
+ const records = await this.adapter.get("cdpcommands") ?? [];
1367
+ await this.adapter.set("cdpcommands", [command, ...records]);
1368
+ }
1369
+ /** Returns every stored raw command outcome, newest first. */
1370
+ async getcdpcommands() {
1371
+ return await this.adapter.get("cdpcommands") ?? [];
1372
+ }
1373
+ /** Stores one domain event rule with its match filter, replacing the previous rule of its id. */
1374
+ async setcdpeventrule(rule) {
1375
+ const records = (await this.adapter.get("cdpeventrules") ?? []).filter((item) => item.id !== rule.id);
1376
+ await this.adapter.set("cdpeventrules", [rule, ...records]);
1377
+ }
1378
+ /** Returns every stored domain event rule, newest first. */
1379
+ async getcdpeventrules() {
1380
+ return await this.adapter.get("cdpeventrules") ?? [];
1381
+ }
1382
+ /** Stores one breakpoint record with its condition and hit counter, replacing the previous record of its id. */
1383
+ async addbreakpoint(spec) {
1384
+ const records = (await this.adapter.get("breakpoints") ?? []).filter((item) => item.id !== spec.id);
1385
+ await this.adapter.set("breakpoints", [spec, ...records]);
1386
+ }
1387
+ /** Returns every stored breakpoint record, newest first. */
1388
+ async getbreakpoints() {
1389
+ return await this.adapter.get("breakpoints") ?? [];
1390
+ }
1391
+ /** Stores one pause state capture; the user configured pause retention window expires the call frames and the dom snapshot reference of the oldest captures while the pause reason and hit breakpoint survive. */
1392
+ async addpause(pause) {
1393
+ const records = await this.getpauses();
1394
+ const combined = [pause, ...records.filter((item) => item.id !== pause.id)];
1395
+ const retention = (await this.getsettings())?.pauseretention;
1396
+ if (retention === void 0) {
1397
+ await this.adapter.set("pauses", combined);
1398
+ return;
1399
+ }
1400
+ const kept = combined.slice(0, retention);
1401
+ const expired = combined.slice(retention).map((item) => {
1402
+ if (item.framesexpired === true) return item;
1403
+ const faded = { ...item, callframes: [], framesexpired: true };
1404
+ delete faded.domsnapshotid;
1405
+ return faded;
1406
+ });
1407
+ await this.adapter.set("pauses", [...kept, ...expired]);
1408
+ }
1409
+ /** Returns every stored pause state capture, newest first. */
1410
+ async getpauses() {
1411
+ return await this.adapter.get("pauses") ?? [];
1412
+ }
1413
+ /** Returns the pause state captures of one run, newest first. */
1414
+ async listpauses(runid) {
1415
+ const records = await this.getpauses();
1416
+ return records.filter((item) => item.runid === runid);
1417
+ }
1418
+ /** Stores one watch expression with its per pause values, replacing the previous expression of its id. */
1419
+ async setwatchexpression(expression) {
1420
+ const records = (await this.adapter.get("watchexpressions") ?? []).filter((item) => item.id !== expression.id);
1421
+ await this.adapter.set("watchexpressions", [expression, ...records]);
1422
+ }
1423
+ /** Returns every stored watch expression, newest first. */
1424
+ async getwatchexpressions() {
1425
+ return await this.adapter.get("watchexpressions") ?? [];
1426
+ }
1427
+ /** Stores one script override with its review provenance, replacing the previous override of its id. */
1428
+ async addscriptoverride(spec) {
1429
+ const records = (await this.adapter.get("scriptoverrides") ?? []).filter((item) => item.id !== spec.id);
1430
+ await this.adapter.set("scriptoverrides", [spec, ...records]);
1431
+ }
1432
+ /** Returns every stored script override, newest first. */
1433
+ async getscriptoverrides() {
1434
+ return await this.adapter.get("scriptoverrides") ?? [];
1435
+ }
1436
+ /** Stores one debugger consent decision per origin with the consented domain list, replacing the previous decision of its id. */
1437
+ async setdebuggergrant(grant) {
1438
+ const records = (await this.adapter.get("debuggergrants") ?? []).filter((item) => item.id !== grant.id);
1439
+ await this.adapter.set("debuggergrants", [grant, ...records]);
1440
+ }
1441
+ /** Returns every debugger consent decision, newest first. */
1442
+ async getdebuggergrants() {
1443
+ return await this.adapter.get("debuggergrants") ?? [];
1444
+ }
1445
+ /** Revokes every approved debugger consent of one origin: the revoke time stamps the records so the next attach needs a new reviewed prompt. */
1446
+ async revokedebuggergrants(origin, at) {
1447
+ const records = await this.getdebuggergrants();
1448
+ let revoked = 0;
1449
+ const updated = records.map((grant) => {
1450
+ if (grant.origin !== origin || grant.revokedat !== void 0) return grant;
1451
+ revoked += 1;
1452
+ return { ...grant, revokedat: at };
1453
+ });
1454
+ await this.adapter.set("debuggergrants", updated);
1455
+ return revoked;
1456
+ }
1129
1457
  /** Merges expired entry counts into the per run level count summary that survives the retention window. */
1130
1458
  async mergelevelsummary(runid, counts, now) {
1131
1459
  const records = await this.getlevelsummaries();
@@ -1139,6 +1467,133 @@ var sessionmemory = class {
1139
1467
  async getlevelsummaries() {
1140
1468
  return await this.adapter.get("levelsummaries") ?? [];
1141
1469
  }
1470
+ /** Stores one measured flow metric of the run beside its step span; the flow series stays per run. */
1471
+ async addflowmetric(metric) {
1472
+ const records = await this.getflowmetrics();
1473
+ await this.adapter.set("flowmetrics", [metric, ...records]);
1474
+ }
1475
+ /** Returns every stored flow metric, newest first. */
1476
+ async getflowmetrics() {
1477
+ return await this.adapter.get("flowmetrics") ?? [];
1478
+ }
1479
+ /** Returns the flow metrics of one run, newest first. */
1480
+ async listflowmetrics(runid) {
1481
+ const records = await this.getflowmetrics();
1482
+ return records.filter((metric) => metric.runid === runid);
1483
+ }
1484
+ /** Stores one heap snapshot record with its byte and node counts; the user configured profile retention window expires the heavy snapshot bytes while the counts survive. */
1485
+ async setheaprecord(heap) {
1486
+ const records = (await this.adapter.get("heaprecords") ?? []).filter((item) => item.id !== heap.id);
1487
+ const retention = (await this.getsettings())?.profileretention;
1488
+ const { heaps } = expireprofilerecords({ heaps: [heap, ...records], profiles: [], traces: [], retention, now: Date.now() });
1489
+ await this.adapter.set("heaprecords", heaps);
1490
+ }
1491
+ /** Returns every stored heap snapshot record, newest first. */
1492
+ async getheaprecords() {
1493
+ return await this.adapter.get("heaprecords") ?? [];
1494
+ }
1495
+ /** Stores one heap growth sample taken beside a step. */
1496
+ async addgrowsample(sample) {
1497
+ const records = await this.adapter.get("growsamples") ?? [];
1498
+ await this.adapter.set("growsamples", [sample, ...records]);
1499
+ }
1500
+ /** Returns every stored heap growth sample, newest first. */
1501
+ async getgrowsamples() {
1502
+ return await this.adapter.get("growsamples") ?? [];
1503
+ }
1504
+ /** Returns the heap growth samples of one run, newest first. */
1505
+ async listgrowsamples(runid) {
1506
+ const records = await this.getgrowsamples();
1507
+ return records.filter((sample) => sample.runid === runid);
1508
+ }
1509
+ /** Stores one computed heap growth trend of a run with its slope and flagged steps, replacing the previous trend of the run. */
1510
+ async settrend(trend) {
1511
+ const records = await this.adapter.get("memorytrends") ?? [];
1512
+ await this.adapter.set("memorytrends", [trend, ...records.filter((item) => item.runid !== trend.runid)]);
1513
+ }
1514
+ /** Returns every stored heap growth trend, newest first. */
1515
+ async gettrends() {
1516
+ return await this.adapter.get("memorytrends") ?? [];
1517
+ }
1518
+ /** Stores one cpu profile record with its sample count and hot function list; the profile retention window expires the heavy sample payload while the counts and hot functions survive. */
1519
+ async setcpuprofile(profile) {
1520
+ const records = (await this.adapter.get("cpuprofiles") ?? []).filter((item) => item.id !== profile.id);
1521
+ const retention = (await this.getsettings())?.profileretention;
1522
+ const { profiles } = expireprofilerecords({ heaps: [], profiles: [profile, ...records], traces: [], retention, now: Date.now() });
1523
+ await this.adapter.set("cpuprofiles", profiles);
1524
+ }
1525
+ /** Returns every stored cpu profile record, newest first. */
1526
+ async getcpuprofiles() {
1527
+ return await this.adapter.get("cpuprofiles") ?? [];
1528
+ }
1529
+ /** Stores one layout shift entry with its score and impacted selectors. */
1530
+ async addshiftentry(entry) {
1531
+ const records = await this.adapter.get("shiftentries") ?? [];
1532
+ await this.adapter.set("shiftentries", [entry, ...records]);
1533
+ }
1534
+ /** Returns every stored layout shift entry, newest first. */
1535
+ async getshiftentries() {
1536
+ return await this.adapter.get("shiftentries") ?? [];
1537
+ }
1538
+ /** Stores one trace record with its category list, byte size and step annotations; the profile retention window expires the heavy trace bytes while the metadata and annotations survive. */
1539
+ async settracerecord(trace) {
1540
+ const records = (await this.adapter.get("tracerecords") ?? []).filter((item) => item.id !== trace.id);
1541
+ const retention = (await this.getsettings())?.profileretention;
1542
+ const { traces } = expireprofilerecords({ heaps: [], profiles: [], traces: [trace, ...records], retention, now: Date.now() });
1543
+ await this.adapter.set("tracerecords", traces);
1544
+ }
1545
+ /** Returns every stored trace record, newest first. */
1546
+ async gettracerecords() {
1547
+ return await this.adapter.get("tracerecords") ?? [];
1548
+ }
1549
+ /** Returns the trace records filtered by run and applied categories. */
1550
+ async listtraces(filter) {
1551
+ const records = await this.gettracerecords();
1552
+ return records.filter((trace) => (filter.runid === void 0 || trace.runid === filter.runid) && (filter.categories === void 0 || filter.categories.every((category) => trace.categories.includes(category))));
1553
+ }
1554
+ /** Stores the exported file content of one trace beside its record; the retention window drops the file bytes of expired traces while the record survives. */
1555
+ async settracefile(traceid, content) {
1556
+ const records = (await this.adapter.get("tracefiles") ?? []).filter((item) => item.traceid !== traceid);
1557
+ const trace = (await this.gettracerecords()).find((item) => item.id === traceid);
1558
+ const retention = (await this.getsettings())?.profileretention;
1559
+ const kept = retention === void 0 || trace === void 0 || Date.now() - trace.endedat <= retention ? [{ traceid, content, savedat: Date.now() }, ...records] : records;
1560
+ await this.adapter.set("tracefiles", kept);
1561
+ }
1562
+ /** Returns the exported file content of one trace, or undefined when the retention window dropped the bytes. */
1563
+ async gettracefile(traceid) {
1564
+ const records = await this.adapter.get("tracefiles") ?? [];
1565
+ return records.find((item) => item.traceid === traceid)?.content;
1566
+ }
1567
+ /** Stores one source map reference of a run with its script url, map url and parsed state. */
1568
+ async setsourcemapref(ref) {
1569
+ const records = (await this.adapter.get("sourcemaprefs") ?? []).filter((item) => item.id !== ref.id);
1570
+ await this.adapter.set("sourcemaprefs", [ref, ...records]);
1571
+ }
1572
+ /** Returns every stored source map reference, newest first. */
1573
+ async getsourcemaps() {
1574
+ return await this.adapter.get("sourcemaprefs") ?? [];
1575
+ }
1576
+ /** Stores one source map capture consent decision per origin, replacing the previous decision of its id. */
1577
+ async setsourcemapconsent(consent) {
1578
+ const records = (await this.adapter.get("sourcemapconsents") ?? []).filter((item) => item.id !== consent.id);
1579
+ await this.adapter.set("sourcemapconsents", [consent, ...records]);
1580
+ }
1581
+ /** Returns every source map capture consent decision, newest first. */
1582
+ async getsourcemapconsents() {
1583
+ return await this.adapter.get("sourcemapconsents") ?? [];
1584
+ }
1585
+ /** Revokes every approved source map consent of one origin so the next capture needs a new reviewed prompt. */
1586
+ async revokesourcemapconsents(origin, at) {
1587
+ const records = await this.getsourcemapconsents();
1588
+ let revoked = 0;
1589
+ const updated = records.map((consent) => {
1590
+ if (consent.origin !== origin || consent.revokedat !== void 0) return consent;
1591
+ revoked += 1;
1592
+ return { ...consent, revokedat: at };
1593
+ });
1594
+ await this.adapter.set("sourcemapconsents", updated);
1595
+ return revoked;
1596
+ }
1142
1597
  };
1143
1598
  function mediakindof(record2) {
1144
1599
  if ("pages" in record2) return "pdf";
@@ -2005,6 +2460,126 @@ function ratelimitwait(state, now) {
2005
2460
  return Math.max(0, state.resetat - now);
2006
2461
  }
2007
2462
 
2463
+ // cdpbus.ts
2464
+ var cdpkinds = ["attachcdp", "detachcdp", "cdpcmd", "watchcdp", "setbreakpoint", "stepcode", "watchexpr", "overridescript"];
2465
+ var cdpdomains = ["Runtime", "Log", "Debugger", "DOM", "Network", "Page"];
2466
+ function methoddomain(method) {
2467
+ const match = /^([A-Z][A-Za-z]*)\.([a-zA-Z][A-Za-z0-9]*)$/.exec(method.trim());
2468
+ return match?.[1];
2469
+ }
2470
+ function cdpallowlistof(value) {
2471
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2472
+ const entry = value;
2473
+ const domains = Array.isArray(entry.domains) ? entry.domains.filter((domain) => typeof domain === "string" && cdpdomains.includes(domain)) : [];
2474
+ if (domains.length === 0) return void 0;
2475
+ if (entry.methods === void 0) return { domains };
2476
+ const methods = Array.isArray(entry.methods) ? entry.methods.filter((method) => typeof method === "string" && methoddomain(method) !== void 0 && domains.includes(methoddomain(method))) : [];
2477
+ if (methods.length === 0) return void 0;
2478
+ return { domains, methods };
2479
+ }
2480
+ function allowlistcovers(allowlist, method) {
2481
+ const domain = methoddomain(method);
2482
+ if (domain === void 0) return false;
2483
+ if (!allowlist.domains.includes(domain)) return false;
2484
+ if (allowlist.methods !== void 0 && !allowlist.methods.includes(method)) return false;
2485
+ return true;
2486
+ }
2487
+ function attachcdpsession(input) {
2488
+ return { id: input.id, runid: input.runid, stepid: input.stepid, tabid: input.tabid, origin: input.origin, attachedat: input.now, domains: [...new Set(input.domains)], debuggerversion: input.debuggerversion };
2489
+ }
2490
+ function detachcdpsession(session, at, userdetached = false) {
2491
+ return { ...session, detachedat: at, ...userdetached ? { userdetached: true } : {} };
2492
+ }
2493
+ function sendcdpcommand(input) {
2494
+ const domain = methoddomain(input.method);
2495
+ if (domain === void 0) return { ...input, method: input.method.trim(), domain: "", duration: input.duration, ...input.errorclass !== void 0 ? { errorclass: input.errorclass } : { errorclass: "malformedmethod" }, at: input.at };
2496
+ return { ...input, method: input.method.trim(), domain, duration: input.duration, ...input.errorclass !== void 0 ? { errorclass: input.errorclass } : {}, at: input.at };
2497
+ }
2498
+ function serializecdpcommand(queue, command) {
2499
+ return [...queue, command];
2500
+ }
2501
+ function cdpeventruleof(value) {
2502
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2503
+ const entry = value;
2504
+ const domain = typeof entry.domain === "string" && cdpdomains.includes(entry.domain) ? entry.domain : void 0;
2505
+ const event = typeof entry.event === "string" && entry.event.trim() ? entry.event.trim() : void 0;
2506
+ if (domain === void 0 || event === void 0) return void 0;
2507
+ const match = typeof entry.match === "string" && entry.match.trim() ? entry.match.trim() : void 0;
2508
+ return { domain, event, ...match !== void 0 ? { match } : {} };
2509
+ }
2510
+ function watchcdpevents(rules, events) {
2511
+ const matched = [];
2512
+ const counts = {};
2513
+ for (const domain of cdpdomains) counts[domain] = 0;
2514
+ for (const event of events) {
2515
+ for (const rule of rules) {
2516
+ if (rule.domain !== event.domain || rule.event !== event.event) continue;
2517
+ if (rule.match !== void 0 && !(event.payload ?? "").includes(rule.match)) continue;
2518
+ matched.push({ ruleid: rule.id, domain: event.domain, event: event.event, ...event.payload !== void 0 ? { payload: event.payload } : {} });
2519
+ counts[event.domain] = (counts[event.domain] ?? 0) + 1;
2520
+ }
2521
+ }
2522
+ return { matched, counts };
2523
+ }
2524
+ function breakpointinputof(value) {
2525
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2526
+ const entry = value;
2527
+ const url = typeof entry.url === "string" && entry.url.trim() ? entry.url.trim() : void 0;
2528
+ const line = typeof entry.line === "number" && Number.isInteger(entry.line) && entry.line >= 0 ? entry.line : void 0;
2529
+ if (url === void 0 || line === void 0) return void 0;
2530
+ const column = typeof entry.column === "number" && Number.isInteger(entry.column) && entry.column >= 0 ? entry.column : void 0;
2531
+ const condition = typeof entry.condition === "string" && entry.condition.trim() ? entry.condition.trim() : void 0;
2532
+ return { url, line, ...column !== void 0 ? { column } : {}, ...condition !== void 0 ? { condition } : {} };
2533
+ }
2534
+ function capturepause(input) {
2535
+ return { id: input.id, runid: input.runid, stepid: input.stepid, reason: input.reason, callframes: [...input.callframes], ...input.hitbreakpoint !== void 0 ? { hitbreakpoint: input.hitbreakpoint } : {}, ...input.domsnapshotid !== void 0 ? { domsnapshotid: input.domsnapshotid } : {}, at: input.at };
2536
+ }
2537
+ function stepmodeof(value) {
2538
+ const modes = ["stepover", "stepinto", "stepout", "resume"];
2539
+ return typeof value === "string" && modes.includes(value) ? value : void 0;
2540
+ }
2541
+ function watchexpressionof(value) {
2542
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2543
+ const entry = value;
2544
+ const expression = typeof entry.expression === "string" && entry.expression.trim() ? entry.expression.trim() : void 0;
2545
+ if (expression === void 0) return void 0;
2546
+ const scope = typeof entry.scope === "string" && entry.scope.trim() ? entry.scope.trim() : "topframe";
2547
+ return { expression, scope };
2548
+ }
2549
+ function recordwatchvalue(expression, pauseid, value, at) {
2550
+ return { ...expression, values: [...expression.values, { pauseid, value, at }] };
2551
+ }
2552
+ function overrideinputof(value) {
2553
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2554
+ const entry = value;
2555
+ const urlpattern = typeof entry.urlpattern === "string" && entry.urlpattern.trim() ? entry.urlpattern.trim() : void 0;
2556
+ const source = typeof entry.source === "string" ? entry.source : void 0;
2557
+ if (urlpattern === void 0 || source === void 0 || source.trim().length === 0) return void 0;
2558
+ return { urlpattern, source };
2559
+ }
2560
+ function teardownplanof(value) {
2561
+ if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
2562
+ const entry = value;
2563
+ const revertsteps = Array.isArray(entry.revertsteps) ? entry.revertsteps.filter((step) => typeof step === "string" && step.trim().length > 0) : [];
2564
+ const policy = entry.resumepolicy;
2565
+ if (revertsteps.length === 0) return void 0;
2566
+ if (policy !== void 0 && policy !== "resume" && policy !== "pause" && policy !== "ask") return void 0;
2567
+ return { revertsteps, resumepolicy: policy ?? "ask" };
2568
+ }
2569
+ function teardowncdpsession(input) {
2570
+ const revertedbreakpoints = input.breakpoints.filter((spec) => spec.revertedat === void 0).map((spec) => spec.id);
2571
+ const revertedoverrides = input.overrides.filter((spec) => spec.revertedat === void 0).map((spec) => spec.id);
2572
+ const resumepolicy = input.userdetached ? "pause" : input.plan?.resumepolicy ?? "ask";
2573
+ return {
2574
+ session: detachcdpsession(input.session, input.at, input.userdetached),
2575
+ revertedbreakpoints,
2576
+ revertedoverrides,
2577
+ resumepolicy,
2578
+ paused: input.userdetached || resumepolicy === "pause",
2579
+ keepsalive: input.userdetached
2580
+ };
2581
+ }
2582
+
2008
2583
  // netauth.ts
2009
2584
  function oauthflowof(value) {
2010
2585
  if (!value || typeof value !== "object" || Array.isArray(value)) return void 0;
@@ -2143,7 +2718,7 @@ ${file.content}\r
2143
2718
  // runtimeline.ts
2144
2719
  var timelinekinds = ["watchconsole", "watcherrors", "watchtasks"];
2145
2720
  var loglevels = ["error", "warn", "info", "log", "debug", "trace"];
2146
- var timelinesources = ["console", "error", "rejection", "resource", "longtask", "network"];
2721
+ var timelinesources = ["console", "error", "rejection", "resource", "longtask", "network", "cdp"];
2147
2722
  function attachtimeline(input) {
2148
2723
  return { runid: input.runid, origin: input.origin, stepids: [...input.stepids], attachedat: input.now, entries: [] };
2149
2724
  }
@@ -2238,9 +2813,9 @@ function consolediff(input) {
2238
2813
  }
2239
2814
 
2240
2815
  // policy.ts
2241
- var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles"]);
2242
- var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction", "capturebodies"]);
2243
- var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks"]);
2816
+ var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts", "recordscreen", "captureaudio", "downloadimages", "callrest", "callgraphql", "sendmessage", "blockrequest", "mockresponse", "rewriteheaders", "setcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles", "attachcdp", "detachcdp", "cdpcmd", "overridescript", "heapshot", "profilecpu", "capturesourcemaps"]);
2817
+ var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction", "capturebodies", "setbreakpoint", "stepcode", "watchexpr"]);
2818
+ var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures", "shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet", "capturepdf", "captureframe", "readmedia", "readassets", "probestream", "timelapse", "shotcanvas", "convertimage", "makethumbs", "fetchurl", "parsejson", "parsehtml", "opensocket", "waitmessage", "watchrequests", "readheaders", "mapapi", "subscribesse", "longpoll", "extractapi", "readcookies", "watchconsole", "watcherrors", "watchtasks", "watchcdp", "measureflow", "trackmemory", "watchshifts", "traceload", "annotatetrace", "replaytrace"]);
2244
2819
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
2245
2820
  var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
2246
2821
  var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "scrapetable", "paginateextract", "shotelement", "captureframe", "shotcanvas"]);
@@ -2257,6 +2832,8 @@ var socketactions = /* @__PURE__ */ new Set(["opensocket", "sendmessage", "waitm
2257
2832
  var netwatchactions = /* @__PURE__ */ new Set(["watchrequests", "readheaders", "capturebodies", "mapapi", "extractapi"]);
2258
2833
  var controlactions = /* @__PURE__ */ new Set(["blockrequest", "mockresponse", "rewriteheaders", "setcookies", "readcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles"]);
2259
2834
  var debugactions = /* @__PURE__ */ new Set(["watchconsole", "watcherrors", "watchtasks"]);
2835
+ var cdpactions = /* @__PURE__ */ new Set(["attachcdp", "detachcdp", "cdpcmd", "watchcdp", "setbreakpoint", "stepcode", "watchexpr", "overridescript"]);
2836
+ var profileractions = /* @__PURE__ */ new Set(["measureflow", "heapshot", "trackmemory", "profilecpu", "watchshifts", "traceload", "annotatetrace", "replaytrace", "capturesourcemaps"]);
2260
2837
  var credentialheaders = /* @__PURE__ */ new Set(["authorization", "proxy-authorization", "cookie", "cookie2", "set-cookie", "api-key", "x-api-key", "x-auth-token", "x-session-token", "proxy-authorization"]);
2261
2838
  var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
2262
2839
  var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
@@ -2275,6 +2852,12 @@ function hostpattern(origin) {
2275
2852
  function isdebugkind(kind) {
2276
2853
  return debugactions.has(kind);
2277
2854
  }
2855
+ function iscdpkind(kind) {
2856
+ return cdpactions.has(kind);
2857
+ }
2858
+ function isprofilekind(kind) {
2859
+ return profileractions.has(kind);
2860
+ }
2278
2861
  function actionrisk(kind) {
2279
2862
  if (!allowedactions.has(kind)) throw new Error("Unsupported browser action.");
2280
2863
  if (sensitiveactions.has(kind)) return "sensitive";
@@ -3575,6 +4158,209 @@ function rotationruleof(value) {
3575
4158
  if (maxentries === void 0 || overflowtarget === void 0) return void 0;
3576
4159
  return { maxentries, overflowtarget };
3577
4160
  }
4161
+ function debuggate(session, tabid2, origin, now) {
4162
+ if (!session || session.stoppedat) return { allowed: false, reason: "No active browser session exists for the devtools protocol step." };
4163
+ if (session.expiresat <= now) return { allowed: false, reason: "The browser session has expired and cannot run a devtools protocol step." };
4164
+ if (session.pausedat) return { allowed: false, reason: "The browser session is paused and cannot run a devtools protocol step." };
4165
+ if (session.tabid !== tabid2) return { allowed: false, reason: `The devtools protocol step needs the run tab ${session.tabid} and refuses tab ${tabid2}.` };
4166
+ if (!origingranted(session, origin)) return { allowed: false, reason: `The devtools protocol step on ${origin} needs the session origin grants first.` };
4167
+ return { allowed: true };
4168
+ }
4169
+ function debuggerconsentcovers(origin, domains, grants) {
4170
+ const needed = [...new Set(domains)];
4171
+ const covering = grants.find((grant) => grant.origin === origin && grant.approved === true && grant.revokedat === void 0 && needed.every((domain) => grant.domains.includes(domain)));
4172
+ if (covering) return { allowed: true };
4173
+ if (grants.some((grant) => grant.origin === origin && grant.revokedat !== void 0)) return { allowed: false, reason: `The debugger consent on ${origin} was revoked; approve a new prompt before the devtools protocol runs again.` };
4174
+ return { allowed: false, reason: `The devtools protocol on ${origin} needs the reviewed debugger consent for ${needed.join(", ")} first; approve the prompt with the domain allowlist shown in the review panel.` };
4175
+ }
4176
+ function targetgate(input) {
4177
+ const base = debuggate(input.session, input.tabid, input.origin, input.now);
4178
+ if (!base.allowed) return base;
4179
+ for (const target of input.targets) {
4180
+ if (target.kind === "page") continue;
4181
+ const origincheckresult = origincheck(input.session, target.url);
4182
+ if (!origincheckresult.allowed) return { allowed: false, reason: `The ${target.kind} target ${target.url} stays outside the granted origins; profiling refuses to attach.` };
4183
+ }
4184
+ if (input.grants === void 0) return { allowed: true };
4185
+ const consent = debuggerconsentcovers(input.origin, [], input.grants);
4186
+ if (!consent.allowed) return { allowed: false, reason: `The profiling step on ${input.origin} needs the reviewed debugger grant of the origin first; approve the prompt with the profiling derivation shown in the review panel.` };
4187
+ return { allowed: true };
4188
+ }
4189
+ function sourcemapconsentcovers(origin, consents) {
4190
+ const covering = consents.find((consent) => consent.origin === origin && consent.approved === true && consent.revokedat === void 0);
4191
+ if (covering) return { allowed: true };
4192
+ if (consents.some((consent) => consent.origin === origin && consent.revokedat !== void 0)) return { allowed: false, reason: `The source map capture consent on ${origin} was revoked; approve a new prompt before another map file is fetched.` };
4193
+ return { allowed: false, reason: `The source map capture on ${origin} needs the reviewed per origin consent first; approve the prompt shown in the review panel.` };
4194
+ }
4195
+ function traceceilingof(settings) {
4196
+ return settings?.traceceiling;
4197
+ }
4198
+ function validatebreakpointcondition(condition) {
4199
+ const expression = condition.trim();
4200
+ if (expression.length === 0) return { allowed: false, reason: "The breakpoint condition must not be empty." };
4201
+ if (/(?<![=!<>])=(?!=)/.test(expression)) return { allowed: false, reason: "Breakpoint conditions refuse assignment because the reviewed grammar is comparison only." };
4202
+ if (/[A-Za-z_$][\w$]*\s*\(/.test(expression)) return { allowed: false, reason: "Breakpoint conditions refuse calls because the reviewed grammar is comparison only." };
4203
+ const literal = /^(?:-?\d+(?:\.\d+)?|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|true|false|null)$/;
4204
+ const tokens = expression.match(/(?:[A-Za-z_$][\w$]*|-?\d+(?:\.\d+)?|"(?:[^"\\]|\\.)*"|'(?:[^'\\]|\\.)*'|===|!==|==|!=|>=|<=|&&|\|\||[!.<>()+\-*\/%])/g);
4205
+ if (tokens === null || tokens.join("") !== expression.replace(/\s+/g, "")) return { allowed: false, reason: "The breakpoint condition must use the reviewed expression grammar of member chains, literals, comparisons, logic operators, negation and parentheses." };
4206
+ const identifierlike = /^(?:true|false|null)$/;
4207
+ for (const token of tokens) {
4208
+ if (literal.test(token) || identifierlike.test(token)) continue;
4209
+ if (["===", "!==", "==", "!=", ">=", "<=", "&&", "||", "!", ".", "(", ")", "<", ">", "+", "-", "*", "/", "%"].includes(token)) continue;
4210
+ if (/^[A-Za-z_$][\w$]*$/.test(token)) continue;
4211
+ return { allowed: false, reason: `The token ${token} of the breakpoint condition stays outside the reviewed expression grammar.` };
4212
+ }
4213
+ return { allowed: true };
4214
+ }
4215
+ function breakpointbudgetallowed(active, ceiling) {
4216
+ if (ceiling === void 0) return { allowed: true };
4217
+ if (typeof ceiling !== "number" || !Number.isInteger(ceiling) || ceiling < 0) return { allowed: false, reason: "The reviewed breakpoint ceiling must be zero or a positive integer of user configured value with no code ceiling." };
4218
+ if (active >= ceiling) return { allowed: false, reason: `The run already holds ${active} active breakpoint${active === 1 ? "" : "s"} and the reviewed breakpoint ceiling is ${ceiling}; revert one or review a wider ceiling.` };
4219
+ return { allowed: true };
4220
+ }
4221
+ function breakpointceilingof(settings) {
4222
+ return settings?.breakpointceiling;
4223
+ }
4224
+ function validatecdpgrammar(step, options) {
4225
+ const kind = step.kind;
4226
+ if (kind === "attachcdp") {
4227
+ if (!Array.isArray(options.domains) || options.domains.length === 0 || !options.domains.every((domain) => typeof domain === "string" && cdpdomains.includes(domain))) return { allowed: false, reason: `The attach needs a non-empty enabled domain list of the reviewed domain grammar: ${cdpdomains.join(", ")}.` };
4228
+ if (teardownplanof(options.teardown) === void 0) return { allowed: false, reason: "Every attach needs a reviewed teardown plan with its revert steps and resume policy before approval." };
4229
+ if (options.allowlist !== void 0) {
4230
+ const allowlist = cdpallowlistof(options.allowlist);
4231
+ if (!allowlist || !allowlist.domains.every((domain) => options.domains.includes(domain))) return { allowed: false, reason: "The reviewed method allowlist must stay inside the enabled domains of the attach." };
4232
+ }
4233
+ const budgetcheck = debugwaitbudgetallowed(typeof options.wait === "number" ? options.wait : void 0, void 0);
4234
+ if (!budgetcheck.allowed) return budgetcheck;
4235
+ return { allowed: true };
4236
+ }
4237
+ if (kind === "detachcdp") return { allowed: true };
4238
+ if (kind === "cdpcmd") {
4239
+ const command = options.command && typeof options.command === "object" && !Array.isArray(options.command) ? options.command : void 0;
4240
+ if (!command || typeof command.method !== "string" || methoddomain(command.method) === void 0) return { allowed: false, reason: "The raw command needs a reviewed method of the Domain.method form." };
4241
+ if (command.params !== void 0 && (typeof command.params !== "object" || Array.isArray(command.params))) return { allowed: false, reason: "The raw command params must be a JSON object." };
4242
+ if (command.resultpath !== void 0 && typeof command.resultpath !== "string") return { allowed: false, reason: "The reviewed result path must be a dotted path string." };
4243
+ return { allowed: true };
4244
+ }
4245
+ if (kind === "watchcdp") {
4246
+ if (!Array.isArray(options.events) || options.events.length === 0 || !options.events.every((rule) => cdpeventruleof(rule) !== void 0)) return { allowed: false, reason: "The event watch needs a non-empty reviewed list of domain event rules of the reviewed domain grammar." };
4247
+ let watchwindow;
4248
+ if (options.watch !== void 0) {
4249
+ const watch = options.watch;
4250
+ if (!watch || typeof watch !== "object" || Array.isArray(watch)) return { allowed: false, reason: "The reviewed event watch window must be an object." };
4251
+ const reviewed = watch;
4252
+ if (reviewed.window !== void 0) {
4253
+ if (typeof reviewed.window !== "number" || !Number.isFinite(reviewed.window) || reviewed.window < 0) return { allowed: false, reason: "The reviewed event watch window must be zero or a positive number of milliseconds." };
4254
+ watchwindow = reviewed.window;
4255
+ }
4256
+ }
4257
+ if (watchwindow === void 0) return { allowed: false, reason: "The event watch needs a reviewed lifetime window before any domain event is observed." };
4258
+ const budgetcheck = debugwaitbudgetallowed(watchwindow, typeof options.wait === "number" ? options.wait : void 0);
4259
+ if (!budgetcheck.allowed) return budgetcheck;
4260
+ return { allowed: true };
4261
+ }
4262
+ if (kind === "setbreakpoint") {
4263
+ const breakpoint = breakpointinputof(options.breakpoint);
4264
+ if (!breakpoint) return { allowed: false, reason: "The breakpoint needs a reviewed script url and a zero based line." };
4265
+ if (!ishttpsurl(breakpoint.url)) return { allowed: false, reason: "The breakpoint script url must be a reviewed HTTPS url." };
4266
+ if (breakpoint.condition !== void 0) {
4267
+ const conditioncheck = validatebreakpointcondition(breakpoint.condition);
4268
+ if (!conditioncheck.allowed) return conditioncheck;
4269
+ }
4270
+ return { allowed: true };
4271
+ }
4272
+ if (kind === "stepcode") {
4273
+ if (stepmodeof(options.mode) === void 0) return { allowed: false, reason: "The step code mode must be one of stepover, stepinto, stepout or resume." };
4274
+ return { allowed: true };
4275
+ }
4276
+ if (kind === "watchexpr") {
4277
+ if (watchexpressionof(options.expression) === void 0) return { allowed: false, reason: "The watch expression needs the reviewed expression text." };
4278
+ if (options.reviewed !== true) return { allowed: false, reason: "Watch expressions must be reviewed before evaluation; set the explicit reviewed flag on the step." };
4279
+ return { allowed: true };
4280
+ }
4281
+ if (kind === "overridescript") {
4282
+ const override = overrideinputof(options.override);
4283
+ if (!override) return { allowed: false, reason: "The script override needs a reviewed url pattern and its full fixture source." };
4284
+ if (patternorigin(override.urlpattern) === void 0) return { allowed: false, reason: "Script overrides without a named https origin pattern are refused." };
4285
+ if (options.reviewed !== true) return { allowed: false, reason: "The full fixture source must be reviewed before the script override runs; set the explicit reviewed flag on the step." };
4286
+ return { allowed: true };
4287
+ }
4288
+ return { allowed: true };
4289
+ }
4290
+ function validateprofilegrammar(step, options) {
4291
+ const kind = step.kind;
4292
+ if (kind === "measureflow") {
4293
+ if (flowspecof(options.flow) === void 0) return { allowed: false, reason: `The flow measurement needs a reviewed flow spec with its mark prefix, step window and metric list of the reviewed metric set: navigation, paint, lcp, fid, interaction, blocking.` };
4294
+ const watch = options.watch && typeof options.watch === "object" && !Array.isArray(options.watch) ? options.watch : {};
4295
+ if (typeof watch.window !== "number" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: "The flow measurement needs a reviewed watch window of zero or more milliseconds." };
4296
+ const budgetcheck = debugwaitbudgetallowed(watch.window, typeof options.wait === "number" ? options.wait : void 0);
4297
+ if (!budgetcheck.allowed) return budgetcheck;
4298
+ return { allowed: true };
4299
+ }
4300
+ if (kind === "heapshot") {
4301
+ const heap = options.heap && typeof options.heap === "object" && !Array.isArray(options.heap) ? options.heap : {};
4302
+ if (heap.interval !== void 0 && (typeof heap.interval !== "number" || !Number.isFinite(heap.interval) || heap.interval < 0)) return { allowed: false, reason: "The reviewed heap snapshot interval must be zero or a positive number of milliseconds and stays a user choice with no code ceiling." };
4303
+ return { allowed: true };
4304
+ }
4305
+ if (kind === "trackmemory") {
4306
+ const growth = options.growth && typeof options.growth === "object" && !Array.isArray(options.growth) ? options.growth : void 0;
4307
+ if (!growth || typeof growth.slope !== "number" || !Number.isFinite(growth.slope) || growth.slope < 0) return { allowed: false, reason: "Memory growth tracking needs the reviewed slope in bytes per millisecond before any sample is flagged." };
4308
+ if (growth.interval !== void 0 && (typeof growth.interval !== "number" || !Number.isFinite(growth.interval) || growth.interval < 0)) return { allowed: false, reason: "The reviewed sampling interval must be zero or a positive number of milliseconds and stays a user choice with no code ceiling." };
4309
+ return { allowed: true };
4310
+ }
4311
+ if (kind === "profilecpu") {
4312
+ const profile = options.profile && typeof options.profile === "object" && !Array.isArray(options.profile) ? options.profile : void 0;
4313
+ if (!profile || typeof profile.duration !== "number" || !Number.isFinite(profile.duration) || profile.duration < 0) return { allowed: false, reason: "The cpu profile needs a reviewed duration of zero or more milliseconds." };
4314
+ const budgetcheck = debugwaitbudgetallowed(profile.duration, typeof options.wait === "number" ? options.wait : void 0);
4315
+ if (!budgetcheck.allowed) return budgetcheck;
4316
+ return { allowed: true };
4317
+ }
4318
+ if (kind === "watchshifts") {
4319
+ const watch = options.watch && typeof options.watch === "object" && !Array.isArray(options.watch) ? options.watch : {};
4320
+ if (typeof watch.window !== "number" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: "The layout shift watch needs a reviewed observation window of zero or more milliseconds; the window stays a user choice with no code ceiling." };
4321
+ if (options.threshold !== void 0 && (typeof options.threshold !== "number" || !Number.isFinite(options.threshold) || options.threshold < 0)) return { allowed: false, reason: "The reviewed shift score threshold must be zero or a positive number." };
4322
+ const budgetcheck = debugwaitbudgetallowed(watch.window, typeof options.wait === "number" ? options.wait : void 0);
4323
+ if (!budgetcheck.allowed) return budgetcheck;
4324
+ return { allowed: true };
4325
+ }
4326
+ if (kind === "traceload") {
4327
+ const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
4328
+ if (!trace || !Array.isArray(trace.categories) || trace.categories.length === 0 || !trace.categories.every((category) => typeof category === "string" && tracecategories.includes(category))) return { allowed: false, reason: `The trace record needs a non-empty reviewed category list of the reviewed category grammar: ${tracecategories.join(", ")}.` };
4329
+ if (typeof trace.window !== "number" || !Number.isFinite(trace.window) || trace.window < 0) return { allowed: false, reason: "The trace record needs a reviewed window of zero or more milliseconds and stops at the reviewed window end." };
4330
+ if (trace.exporttarget !== void 0 && trace.exporttarget !== "memory" && trace.exporttarget !== "download") return { allowed: false, reason: "The trace export target must be memory or download." };
4331
+ const budgetcheck = debugwaitbudgetallowed(trace.window, typeof options.wait === "number" ? options.wait : void 0);
4332
+ if (!budgetcheck.allowed) return budgetcheck;
4333
+ return { allowed: true };
4334
+ }
4335
+ if (kind === "annotatetrace" || kind === "replaytrace") {
4336
+ const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
4337
+ if (!trace || typeof trace.traceid !== "string" || !trace.traceid.trim()) return { allowed: false, reason: `The ${kind === "annotatetrace" ? "trace annotation" : "trace replay"} needs the stored trace id of a recorded trace.` };
4338
+ if (kind === "replaytrace") return { allowed: true };
4339
+ if (!Array.isArray(options.annotations) || options.annotations.length === 0 || !options.annotations.every((annotation) => annotationof(annotation) !== void 0)) return { allowed: false, reason: "Exported traces carry their step annotations: every annotation needs a step id, a label and an optional offset from the trace start." };
4340
+ return { allowed: true };
4341
+ }
4342
+ if (kind === "capturesourcemaps") {
4343
+ if (options.scripts !== void 0) {
4344
+ if (!Array.isArray(options.scripts) || options.scripts.length === 0 || !options.scripts.every((url) => typeof url === "string" && ishttpsurl(url))) return { allowed: false, reason: "The source map capture scripts must be a non-empty list of reviewed HTTPS urls." };
4345
+ }
4346
+ return { allowed: true };
4347
+ }
4348
+ return { allowed: true };
4349
+ }
4350
+ function planallowlist(steps) {
4351
+ const attach = steps.find((step) => step.kind === "attachcdp");
4352
+ if (!attach) return void 0;
4353
+ let options = {};
4354
+ try {
4355
+ options = parseoptions(attach);
4356
+ } catch {
4357
+ options = {};
4358
+ }
4359
+ const domains = Array.isArray(options.domains) ? options.domains.filter((domain) => typeof domain === "string" && cdpdomains.includes(domain)) : [];
4360
+ if (domains.length === 0) return void 0;
4361
+ const gated = cdpallowlistof(options.allowlist);
4362
+ return { domains, ...gated?.methods !== void 0 ? { methods: gated.methods } : {} };
4363
+ }
3578
4364
  function controltarget(step) {
3579
4365
  let options = {};
3580
4366
  try {
@@ -3988,6 +4774,14 @@ function validatestep(step, origin) {
3988
4774
  const timelinecheck = validatetimelinegrammar(step, options);
3989
4775
  if (!timelinecheck.allowed) return timelinecheck;
3990
4776
  }
4777
+ if (iscdpkind(step.kind)) {
4778
+ const cdpcheck = validatecdpgrammar(step, options);
4779
+ if (!cdpcheck.allowed) return cdpcheck;
4780
+ }
4781
+ if (isprofilekind(step.kind)) {
4782
+ const profilecheck = validateprofilegrammar(step, options);
4783
+ if (!profilecheck.allowed) return profilecheck;
4784
+ }
3991
4785
  if (step.kind === "tabcreate") {
3992
4786
  if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
3993
4787
  if (options.window !== void 0 && (typeof options.window !== "number" || !Number.isInteger(options.window) || options.window < 0)) return { allowed: false, reason: "The reviewed target window id must be a non-negative integer." };
@@ -4093,6 +4887,70 @@ function canexecute(input) {
4093
4887
  const timelinegatecheck = timelinegate(input.session, input.tabid, input.origin, now);
4094
4888
  if (!timelinegatecheck.allowed) return timelinegatecheck;
4095
4889
  }
4890
+ if (iscdpkind(input.step.kind)) {
4891
+ const debuggatecheck = debuggate(input.session, input.tabid, input.origin, now);
4892
+ if (!debuggatecheck.allowed) return debuggatecheck;
4893
+ if (!input.plan) return { allowed: false, reason: "The devtools protocol steps need an approved plan." };
4894
+ const allowlist = planallowlist(input.plan.steps);
4895
+ if (input.step.kind !== "attachcdp") {
4896
+ if (allowlist === void 0) return { allowed: false, reason: "The devtools protocol step needs the attachcdp step of the same plan with its enabled domains first." };
4897
+ if (input.step.kind === "cdpcmd") {
4898
+ let cdpoptions2 = {};
4899
+ try {
4900
+ cdpoptions2 = parseoptions(input.step);
4901
+ } catch {
4902
+ cdpoptions2 = {};
4903
+ }
4904
+ const command = cdpoptions2.command && typeof cdpoptions2.command === "object" && !Array.isArray(cdpoptions2.command) ? cdpoptions2.command : void 0;
4905
+ const method = typeof command?.method === "string" ? command.method : "";
4906
+ if (methoddomain(method) === void 0 || !allowlistcovers(allowlist, method)) return { allowed: false, reason: `The raw command ${method || ""} stays outside the enabled domain allowlist of the plan attach; review the attach domains or the method gates.` };
4907
+ }
4908
+ }
4909
+ let cdpoptions = {};
4910
+ try {
4911
+ cdpoptions = parseoptions(input.step);
4912
+ } catch {
4913
+ cdpoptions = {};
4914
+ }
4915
+ if (input.step.kind === "setbreakpoint") {
4916
+ const breakpoint = breakpointinputof(cdpoptions.breakpoint);
4917
+ if (breakpoint) {
4918
+ const targetgate2 = origincheck(input.session, breakpoint.url);
4919
+ if (!targetgate2.allowed) return targetgate2;
4920
+ }
4921
+ }
4922
+ if (input.step.kind === "overridescript") {
4923
+ const override = overrideinputof(cdpoptions.override);
4924
+ if (override) {
4925
+ const targetgate2 = origincheck(input.session, override.urlpattern);
4926
+ if (!targetgate2.allowed) return targetgate2;
4927
+ }
4928
+ }
4929
+ }
4930
+ if (isprofilekind(input.step.kind)) {
4931
+ let profileoptions = {};
4932
+ try {
4933
+ profileoptions = parseoptions(input.step);
4934
+ } catch {
4935
+ profileoptions = {};
4936
+ }
4937
+ const targets = [
4938
+ ...attachtargetof(profileoptions.target) !== void 0 ? [attachtargetof(profileoptions.target)] : [],
4939
+ ...Array.isArray(profileoptions.attachtargets) ? profileoptions.attachtargets.flatMap((target) => {
4940
+ const parsed = attachtargetof(target);
4941
+ return parsed !== void 0 ? [parsed] : [];
4942
+ }) : []
4943
+ ];
4944
+ const targetgatecheck = targetgate({ session: input.session, tabid: input.tabid, origin: input.origin, targets, grants: void 0, now });
4945
+ if (!targetgatecheck.allowed) return targetgatecheck;
4946
+ if (input.step.kind === "capturesourcemaps") {
4947
+ for (const url of Array.isArray(profileoptions.scripts) ? profileoptions.scripts : []) {
4948
+ if (typeof url !== "string") continue;
4949
+ const scriptgate = origincheck(input.session, url);
4950
+ if (!scriptgate.allowed) return scriptgate;
4951
+ }
4952
+ }
4953
+ }
4096
4954
  if (iscontrolkind(input.step.kind)) {
4097
4955
  const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: "control the network" });
4098
4956
  if (!controlgate.allowed) return controlgate;
@@ -4138,8 +4996,8 @@ function canexecute(input) {
4138
4996
  }
4139
4997
  const target = controltarget(input.step);
4140
4998
  if (target !== void 0) {
4141
- const targetgate = origincheck(input.session, target);
4142
- if (!targetgate.allowed) return targetgate;
4999
+ const targetgate2 = origincheck(input.session, target);
5000
+ if (!targetgate2.allowed) return targetgate2;
4143
5001
  }
4144
5002
  }
4145
5003
  if (input.step.kind === "extractapi") {
@@ -4326,9 +5184,21 @@ function recordtimeline(progress, planid, stepid, entry, now) {
4326
5184
  const outcome = { stepid, ok: true, summary: `Captured ${entry.entries} timeline entr${entry.entries === 1 ? "y" : "ies"} with ${entry.collapsed} collapsed repeat${entry.collapsed === 1 ? "" : "s"}, ${entry.errors} error${entry.errors === 1 ? "" : "s"}, ${entry.rejections} rejection${entry.rejections === 1 ? "" : "s"} and ${entry.longtasks} long task${entry.longtasks === 1 ? "" : "s"}.`, details: { timeline: entry }, at: now };
4327
5185
  return recordoutcome(base, planid, outcome, now);
4328
5186
  }
5187
+ function recordcdp(progress, planid, stepid, entry, now) {
5188
+ const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
5189
+ const summary = entry.family === "command" ? `The reviewed ${entry.method ?? "raw"} command of the ${entry.domain ?? "unknown"} domain returned in ${entry.duration ?? 0} millisecond${(entry.duration ?? 0) === 1 ? "" : "s"}${entry.errorclass !== void 0 ? ` with the ${entry.errorclass} error class` : ""}.` : `The devtools ${entry.family} step ran${entry.domain !== void 0 ? ` on the ${entry.domain} domain` : ""}${entry.events !== void 0 ? ` and matched ${entry.events} event${entry.events === 1 ? "" : "s"}` : ""}${entry.hits !== void 0 ? ` with ${entry.hits} hit${entry.hits === 1 ? "" : "s"}` : ""}${entry.frames !== void 0 ? ` capturing ${entry.frames} call frame${entry.frames === 1 ? "" : "s"}` : ""}.`;
5190
+ const outcome = { stepid, ok: entry.errorclass === void 0, summary, details: { cdp: entry }, at: now };
5191
+ return recordoutcome(base, planid, outcome, now);
5192
+ }
5193
+ function recordprofile(progress, planid, stepid, entry, now) {
5194
+ const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
5195
+ const counts = `${entry.metrics !== void 0 ? `${entry.metrics} metric${entry.metrics === 1 ? "" : "s"}, ` : ""}${entry.samples !== void 0 ? `${entry.samples} sample${entry.samples === 1 ? "" : "s"}, ` : ""}${entry.nodes !== void 0 ? `${entry.nodes} node${entry.nodes === 1 ? "" : "s"}, ` : ""}${entry.events !== void 0 ? `${entry.events} event${entry.events === 1 ? "" : "s"}, ` : ""}${entry.bytes !== void 0 ? `${entry.bytes} byte${entry.bytes === 1 ? "" : "s"}, ` : ""}${entry.flagged !== void 0 ? `${entry.flagged} flagged step${entry.flagged === 1 ? "" : "s"}, ` : ""}`.replace(/, $/, "");
5196
+ const outcome = { stepid, ok: true, summary: `The profiling ${entry.family} capture ran${counts.length > 0 ? ` with ${counts}` : ""}.`, details: { profile: entry }, at: now };
5197
+ return recordoutcome(base, planid, outcome, now);
5198
+ }
4329
5199
 
4330
5200
  // version.ts
4331
- var packageversion = "1.1.45";
5201
+ var packageversion = "1.1.47";
4332
5202
 
4333
5203
  // types.ts
4334
5204
  var protocolversion = packageversion;
@@ -4353,6 +5223,20 @@ function parseproposal(value, origin, grants) {
4353
5223
  const expiresat = typeof planinput.expiresat === "number" ? planinput.expiresat : createdat + 10 * 60 * 1e3;
4354
5224
  if (expiresat <= createdat) throw new Error("Plan expiry must be in the future.");
4355
5225
  const planwindow = expiresat - createdat;
5226
+ const attachinput = stepsinput.map((input) => record(input)).find((candidate) => candidate.kind === "attachcdp");
5227
+ let planallowlist2;
5228
+ if (attachinput !== void 0) {
5229
+ const attachoptions = (() => {
5230
+ try {
5231
+ return parseoptions({ id: "attach", kind: "attachcdp", summary: "attach", risk: "sensitive", ...typeof attachinput.options === "string" ? { options: attachinput.options } : {} });
5232
+ } catch {
5233
+ return {};
5234
+ }
5235
+ })();
5236
+ const domains = Array.isArray(attachoptions.domains) ? attachoptions.domains.filter((domain) => typeof domain === "string" && cdpdomains.includes(domain)) : [];
5237
+ const gated = cdpallowlistof(attachoptions.allowlist);
5238
+ planallowlist2 = domains.length > 0 ? { domains, ...gated?.methods !== void 0 ? { methods: gated.methods } : {} } : void 0;
5239
+ }
4356
5240
  const steps = stepsinput.map((input, index) => {
4357
5241
  const candidate = record(input);
4358
5242
  const kind = text(candidate.kind, `step ${index + 1} kind`);
@@ -4403,6 +5287,89 @@ function parseproposal(value, origin, grants) {
4403
5287
  if (!granted) throw new Error(`The ${step.kind} capture of ${origin} targets an origin outside the grants.`);
4404
5288
  if (debugoptions.level !== void 0 && !loglevels.includes(debugoptions.level)) throw new Error(`The reviewed level floor must be one of ${loglevels.join(", ")}.`);
4405
5289
  }
5290
+ if (iscdpkind(step.kind)) {
5291
+ const granted = covered.some((pattern) => {
5292
+ try {
5293
+ return new URL(origin).origin === new URL(pattern).origin;
5294
+ } catch {
5295
+ return false;
5296
+ }
5297
+ });
5298
+ if (!granted) throw new Error(`The ${step.kind} step of ${origin} targets an origin outside the grants.`);
5299
+ let cdpoptions = {};
5300
+ try {
5301
+ cdpoptions = parseoptions(step);
5302
+ } catch {
5303
+ cdpoptions = {};
5304
+ }
5305
+ if (step.kind === "attachcdp") {
5306
+ if (teardownplanof(cdpoptions.teardown) === void 0) throw new Error("Attach steps without a reviewed teardown plan are refused.");
5307
+ const domains = Array.isArray(cdpoptions.domains) ? cdpoptions.domains.filter((domain) => typeof domain === "string" && cdpdomains.includes(domain)) : [];
5308
+ if (domains.length === 0) throw new Error("Attach steps need a non-empty enabled domain list of the reviewed domain grammar.");
5309
+ const gated = cdpallowlistof(cdpoptions.allowlist);
5310
+ if (cdpoptions.allowlist !== void 0 && (!gated || !gated.domains.every((domain) => domains.includes(domain)))) throw new Error("The reviewed method allowlist must stay inside the enabled domains of the attach.");
5311
+ }
5312
+ if (step.kind === "cdpcmd") {
5313
+ const command = cdpoptions.command && typeof cdpoptions.command === "object" && !Array.isArray(cdpoptions.command) ? cdpoptions.command : void 0;
5314
+ const method = typeof command?.method === "string" ? command.method : "";
5315
+ if (methoddomain(method) === void 0) throw new Error("Raw commands need a reviewed method of the Domain.method form.");
5316
+ if (planallowlist2 === void 0) throw new Error("Raw command steps need the attachcdp step of the same plan with its enabled domains first.");
5317
+ if (!allowlistcovers(planallowlist2, method)) throw new Error(`The raw command ${method} stays outside the enabled domain allowlist of the plan attach.`);
5318
+ }
5319
+ }
5320
+ if (isprofilekind(step.kind)) {
5321
+ const granted = covered.some((pattern) => {
5322
+ try {
5323
+ return new URL(origin).origin === new URL(pattern).origin;
5324
+ } catch {
5325
+ return false;
5326
+ }
5327
+ });
5328
+ if (!granted) throw new Error(`The ${step.kind} step of ${origin} targets an origin outside the grants.`);
5329
+ let profileoptions = {};
5330
+ try {
5331
+ profileoptions = parseoptions(step);
5332
+ } catch {
5333
+ profileoptions = {};
5334
+ }
5335
+ const targets = [
5336
+ ...attachtargetof(profileoptions.target) !== void 0 ? [attachtargetof(profileoptions.target)] : [],
5337
+ ...Array.isArray(profileoptions.attachtargets) ? profileoptions.attachtargets.flatMap((target2) => {
5338
+ const parsed = attachtargetof(target2);
5339
+ return parsed !== void 0 ? [parsed] : [];
5340
+ }) : []
5341
+ ];
5342
+ for (const target2 of targets) {
5343
+ if (target2.kind === "page") continue;
5344
+ const targetgranted = covered.some((pattern) => {
5345
+ try {
5346
+ return new URL(target2.url).origin === new URL(pattern).origin;
5347
+ } catch {
5348
+ return false;
5349
+ }
5350
+ });
5351
+ if (!targetgranted) throw new Error(`The ${target2.kind} target ${target2.url} of the ${step.kind} step stays outside the granted origins.`);
5352
+ }
5353
+ if (step.kind === "traceload") {
5354
+ const trace = profileoptions.trace && typeof profileoptions.trace === "object" && !Array.isArray(profileoptions.trace) ? profileoptions.trace : void 0;
5355
+ const categories = trace !== void 0 && Array.isArray(trace.categories) ? trace.categories : [];
5356
+ if (categories.some((category) => typeof category !== "string" || !tracecategories.includes(category))) throw new Error(`Trace categories outside the reviewed list are refused: ${tracecategories.join(", ")}.`);
5357
+ }
5358
+ if (step.kind === "capturesourcemaps") {
5359
+ for (const url of Array.isArray(profileoptions.scripts) ? profileoptions.scripts : []) {
5360
+ if (typeof url !== "string") continue;
5361
+ const scriptgranted = covered.some((pattern) => {
5362
+ try {
5363
+ return new URL(url).origin === new URL(pattern).origin;
5364
+ } catch {
5365
+ return false;
5366
+ }
5367
+ });
5368
+ if (!scriptgranted) throw new Error(`The source map capture of ${url} targets an origin outside the grants.`);
5369
+ }
5370
+ }
5371
+ if (step.kind === "annotatetrace" && (!Array.isArray(profileoptions.annotations) || profileoptions.annotations.length === 0 || !profileoptions.annotations.every((annotation) => annotationof(annotation) !== void 0))) throw new Error("Trace annotation steps without reviewed step annotations are refused.");
5372
+ }
4406
5373
  const evaluation = validatestep(step, origin);
4407
5374
  if (!evaluation.allowed) throw new Error(evaluation.reason);
4408
5375
  const target = outboundtarget(step);
@@ -4477,7 +5444,7 @@ function requestbody(input) {
4477
5444
  return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
4478
5445
  }
4479
5446
  function outcomeresponse(input) {
4480
- return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {}, ...input.media ? { media: input.media } : {}, ...input.transport ? { transport: input.transport } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {} });
5447
+ return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {}, ...input.capture ? { capture: input.capture } : {}, ...input.media ? { media: input.media } : {}, ...input.transport ? { transport: input.transport } : {}, ...input.network ? { network: input.network } : {}, ...input.control ? { control: input.control } : {}, ...input.timeline ? { timeline: input.timeline } : {}, ...input.cdp ? { cdp: input.cdp } : {}, ...input.profile ? { profile: input.profile } : {} });
4481
5448
  }
4482
5449
  function mapresponse(input) {
4483
5450
  return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
@@ -4579,6 +5546,27 @@ function timelinereport(input) {
4579
5546
  function consolediffreport(input) {
4580
5547
  return { version: protocolversion, diff: input.diff };
4581
5548
  }
5549
+ function cdpreport(input) {
5550
+ const overrides = input.overrides.map((spec) => {
5551
+ const { source, ...metadata } = spec;
5552
+ void source;
5553
+ return metadata;
5554
+ });
5555
+ const grants = input.grants.map((grant) => {
5556
+ const { prompt, ...metadata } = grant;
5557
+ void prompt;
5558
+ return metadata;
5559
+ });
5560
+ return { version: protocolversion, sessions: input.sessions, commands: input.commands, events: input.events, breakpoints: input.breakpoints, pauses: input.pauses, watches: input.watches, overrides, grants };
5561
+ }
5562
+ function profilereport(input) {
5563
+ const consents = input.consents.map((consent) => {
5564
+ const { prompt, ...metadata } = consent;
5565
+ void prompt;
5566
+ return metadata;
5567
+ });
5568
+ return { version: protocolversion, flows: input.flows, heaps: input.heaps, samples: input.samples, trends: input.trends, profiles: input.profiles, shifts: input.shifts, traces: input.traces, sourcemaps: input.sourcemaps, consents };
5569
+ }
4582
5570
 
4583
5571
  // capture.ts
4584
5572
  var capturekinds = ["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"];
@@ -6267,7 +7255,7 @@ function stepoptions2(step) {
6267
7255
  }
6268
7256
  async function refreshcapabilities() {
6269
7257
  const report = await readcapabilities();
6270
- const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds] };
7258
+ const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds, ...cdpkinds], profile: [...profilerkinds] };
6271
7259
  await memory.setcapabilities(withmedia);
6272
7260
  return withmedia;
6273
7261
  }
@@ -6423,6 +7411,7 @@ function stepauditkind(step, ok) {
6423
7411
  if (step.kind === "consentpassword") return "consent";
6424
7412
  if (step.kind === "handoffcaptcha") return "handoff";
6425
7413
  if (iscapturekind(step.kind)) return "capture";
7414
+ if (isprofilekind(step.kind)) return "profile";
6426
7415
  if (ismediakind(step.kind)) return "media";
6427
7416
  if (isfileskind(step.kind)) {
6428
7417
  if (step.kind === "interceptmime") return "intercept";
@@ -6701,6 +7690,13 @@ async function tracktabupdate(tabid2, changeinfo) {
6701
7690
  await audit("timeline", `Watcher ${id} detached when the run tab navigated to ${url} at ${detached.at}; every page hook of the destroyed context is gone with it.`, { sessionid: session.id });
6702
7691
  activetimelinewatchers.delete(id);
6703
7692
  }
7693
+ for (const [runid, active] of [...activecdpsessions.entries()]) {
7694
+ if (active.session.detachedat !== void 0 || active.session.tabid !== tabid2) continue;
7695
+ await detachcdpforrun(runid, `the run tab navigated to ${url}`).catch(() => {
7696
+ });
7697
+ await stopprofileinstrumentsforrun(runid, `the run tab navigated to ${url}`).catch(() => {
7698
+ });
7699
+ }
6704
7700
  return;
6705
7701
  }
6706
7702
  const buffer = navbuffers.get(tabid2) ?? [];
@@ -9460,6 +10456,516 @@ async function executetimelinestep(step, session, plan, tabid2, origin) {
9460
10456
  await audit("timeline", `Captured ${stored.length} timeline entr${stored.length === 1 ? "y" : "ies"} of ${origin} for the reviewed window of ${watchwindow} milliseconds${collapsed > 0 ? ` with ${collapsed} collapsed repeat${collapsed === 1 ? "" : "s"}` : ""}${errorids.length > 0 ? `, ${errorids.length} error${errorids.length === 1 ? "" : "s"}` : ""}${rejectionids.length > 0 ? `, ${rejectionids.length} rejection${rejectionids.length === 1 ? "" : "s"}` : ""}${longtasks.length > 0 ? ` and ${longtasks.length} long task${longtasks.length === 1 ? "" : "s"}` : ""}; console, error and task watching derives from page-injected listeners and the performance buffers.`, extra);
9461
10457
  return { ok: Boolean(output.ok), summary: output.summary, details: { ...output.details ?? {}, timeline: { entries: stored.length, levels: counts, collapsed }, entries: stored, errorids, rejectionids, longtasks: longtasks.map((task) => ({ ...task, blocking: blocking.blocking })), ...flagged.length > 0 ? { flagged } : {}, ...rotationtarget ? { rotation: rotationtarget } : {} } };
9462
10458
  }
10459
+ var cdpderivation = "devthink instrumented harness 1.1.46 (no chrome.debugger permission)";
10460
+ var activecdpsessions = /* @__PURE__ */ new Map();
10461
+ async function detachcdpforrun(runid, reason, userdetached = false) {
10462
+ const active = activecdpsessions.get(runid);
10463
+ if (!active) return;
10464
+ const at = Date.now();
10465
+ const decision = teardowncdpsession({ session: active.session, breakpoints: active.breakpoints, overrides: active.overrides, plan: active.teardown, userdetached, at });
10466
+ for (const id of decision.revertedbreakpoints) {
10467
+ const spec = active.breakpoints.find((candidate) => candidate.id === id);
10468
+ if (!spec) continue;
10469
+ const reverted = { ...spec, revertedat: at };
10470
+ await memory.addbreakpoint(reverted).catch(() => void 0);
10471
+ await audit("debugger", `Reverted the breakpoint ${id} of ${spec.url}:${spec.line} with ${spec.hits} hit${spec.hits === 1 ? "" : "s"} at ${reason}${spec.condition !== void 0 ? "; the condition stays reviewed in the record" : ""}.`, { stepid: spec.stepid }).catch(() => void 0);
10472
+ }
10473
+ for (const id of decision.revertedoverrides) {
10474
+ const spec = active.overrides.find((candidate) => candidate.id === id);
10475
+ if (!spec) continue;
10476
+ const reverted = { ...spec, revertedat: at };
10477
+ await memory.addscriptoverride(reverted).catch(() => void 0);
10478
+ await audit("debugger", `Reverted the script override ${id} of ${spec.urlpattern} with ${spec.hits} applied evaluation${spec.hits === 1 ? "" : "s"} at ${reason}; the fixture source never enters the audit trail.`, { stepid: spec.stepid }).catch(() => void 0);
10479
+ }
10480
+ for (const rule of active.rules) {
10481
+ if (rule.closedat !== void 0) continue;
10482
+ await memory.setcdpeventrule({ ...rule, closedat: at }).catch(() => void 0);
10483
+ }
10484
+ await memory.setcdpsession(decision.session).catch(() => void 0);
10485
+ await audit("debugger", `Session ${active.session.id} of ${active.session.origin} detached at ${reason} with the reviewed domains ${active.session.domains.join(", ")} enabled${decision.keepsalive ? "; the session record stays alive for review because the user detached the debugger" : ""}.`, { sessionid: active.session.id, planid: runid, stepid: active.session.stepid }).catch(() => void 0);
10486
+ if (!userdetached) {
10487
+ const revoked = await memory.revokedebuggergrants(active.session.origin, at).catch(() => 0);
10488
+ if (revoked > 0) await audit("debugger", `Revoked ${revoked} debugger consent record${revoked === 1 ? "" : "s"} of ${active.session.origin} at ${reason}; the next attach needs a new reviewed prompt.`, { planid: runid }).catch(() => void 0);
10489
+ }
10490
+ if (decision.paused) await audit("pause", `The devtools teardown at ${reason} paused the run for review; the resume policy is ${decision.resumepolicy}.`, { planid: runid }).catch(() => void 0);
10491
+ activecdpsessions.delete(runid);
10492
+ await refreshbadge().catch(() => void 0);
10493
+ }
10494
+ function cdpstateof(runid, session, teardown) {
10495
+ const existing = activecdpsessions.get(runid);
10496
+ if (existing) return existing;
10497
+ const created = { session, queue: [], rules: [], breakpoints: [], overrides: [], ...teardown !== void 0 ? { teardown } : {}, cancelled: false };
10498
+ activecdpsessions.set(runid, created);
10499
+ return created;
10500
+ }
10501
+ function pauseframes(entry) {
10502
+ if (!Array.isArray(entry)) return [];
10503
+ return entry.flatMap((frame) => {
10504
+ if (!frame || typeof frame !== "object") return [];
10505
+ const record2 = frame;
10506
+ if (typeof record2.url !== "string" || typeof record2.line !== "number") return [];
10507
+ return [{ url: record2.url, line: record2.line, ...typeof record2.column === "number" ? { column: record2.column } : {}, ...typeof record2.functionname === "string" ? { functionname: record2.functionname } : {} }];
10508
+ });
10509
+ }
10510
+ async function executecdpstep(step, session, plan, tabid2, origin) {
10511
+ const options = stepoptions2(step);
10512
+ const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
10513
+ const debuggatecheck = debuggate(session, tabid2, origin, Date.now());
10514
+ if (!debuggatecheck.allowed) throw new Error(debuggatecheck.reason ?? "The devtools protocol step stays outside the debug gate.");
10515
+ const grants = await memory.getdebuggergrants();
10516
+ const domains = Array.isArray(options.domains) ? options.domains.filter((domain) => typeof domain === "string") : [];
10517
+ if (step.kind === "attachcdp") {
10518
+ const consent = debuggerconsentcovers(origin, domains, grants);
10519
+ if (!consent.allowed) {
10520
+ const pending = grants.find((grant) => grant.origin === origin && grant.approved === void 0 && grant.domains.join(",") === domains.join(","));
10521
+ if (!pending) {
10522
+ const record2 = { id: randomid(), prompt: `Debugger attach on ${origin} with the reviewed domains ${domains.join(", ")} enabled for run ${plan.id}.`, origin, domains, consentedat: Date.now() };
10523
+ await memory.setdebuggergrant(record2);
10524
+ await refreshbadge();
10525
+ }
10526
+ throw new Error(`${consent.reason} The prompt is open in the review panel with the domain allowlist shown; approve it and run the step again.`);
10527
+ }
10528
+ } else {
10529
+ const consent = debuggerconsentcovers(origin, planallowlist(plan.steps)?.domains ?? [], grants);
10530
+ if (!consent.allowed) throw new Error(consent.reason ?? "The reviewed debugger consent is missing.");
10531
+ }
10532
+ const active = activecdpsessions.get(plan.id);
10533
+ if (step.kind !== "attachcdp" && (!active || active.session.detachedat !== void 0)) throw new Error("No attached devtools session covers this run; run the attachcdp step first.");
10534
+ if (step.kind === "attachcdp") {
10535
+ const teardown = teardownplanof(options.teardown);
10536
+ const sessionid = randomid();
10537
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The attach returned no result." };
10538
+ if (!output.ok) return output;
10539
+ const record2 = attachcdpsession({ id: sessionid, runid: plan.id, stepid: step.id, tabid: tabid2, origin, domains, now: Date.now(), debuggerversion: cdpderivation });
10540
+ cdpstateof(plan.id, record2, teardown);
10541
+ const attachtargets = profiletargetsof(options);
10542
+ const flattened = [];
10543
+ if (attachtargets.length > 0) {
10544
+ const targetgatecheck = targetgate({ session, tabid: tabid2, origin, targets: attachtargets, grants, now: Date.now() });
10545
+ if (!targetgatecheck.allowed) throw new Error(targetgatecheck.reason ?? "The attach target stays outside the profiling target gate.");
10546
+ attachtargets.forEach((target, index) => {
10547
+ flattened.push({ kind: target.kind, url: target.url, sessionid: `${sessionid}-${index + 1}`, attachedat: Date.now() });
10548
+ if (target.kind === "serviceworker") {
10549
+ void memory.addtimelineentry({ id: randomid(), runid: plan.id, stepid: step.id, time: Date.now(), level: "info", source: "cdp", message: `Service worker target ${target.url} attached through the page controller state; the worker console and network flow into the run timeline as derived entries because no debugger permission exists in the manifest.` });
10550
+ }
10551
+ });
10552
+ activeprofiletargets.set(plan.id, { runid: plan.id, targets: flattened });
10553
+ }
10554
+ await memory.setcdpsession(record2);
10555
+ await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "attach", domains: domains.length }, Date.now()));
10556
+ await audit("debugger", `Attached the devtools session ${sessionid} to the run tab ${tabid2} of ${origin} with the reviewed domains ${domains.join(", ")} enabled and the debugger version ${cdpderivation}; the teardown plan reverts ${teardown?.revertsteps.length ?? 0} step${(teardown?.revertsteps.length ?? 0) === 1 ? "" : "s"} with the ${teardown?.resumepolicy ?? "ask"} resume policy.${flattened.length > 0 ? ` The attach flattened ${flattened.length} sub session${flattened.length === 1 ? "" : "s"} for nested target access of ${flattened.map((target) => `${target.kind} ${target.url}`).join(", ")}.` : ""}`, extra);
10557
+ await refreshbadge();
10558
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, session: record2, ...flattened.length > 0 ? { targets: flattened } : {}, cdp: { sessionid, state: "attached", commandids: [] } } };
10559
+ }
10560
+ if (step.kind === "detachcdp") {
10561
+ if (!active) throw new Error("No attached devtools session covers this run.");
10562
+ const before = { breakpoints: active.breakpoints.filter((spec) => spec.revertedat === void 0).length, overrides: active.overrides.filter((spec) => spec.revertedat === void 0).length };
10563
+ await detachcdpforrun(plan.id, "the reviewed detachcdp step");
10564
+ await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "detach", hits: before.breakpoints + before.overrides }, Date.now()));
10565
+ return { ok: true, summary: `Detached the devtools session ${active.session.id} cleanly after reverting ${before.breakpoints} breakpoint${before.breakpoints === 1 ? "" : "s"} and ${before.overrides} override${before.overrides === 1 ? "" : "s"}; the detach is confirmed.`, details: { detached: true, ...before, cdp: { sessionid: active.session.id, state: "detached", commandids: active.queue.map((command) => command.id) } } };
10566
+ }
10567
+ if (step.kind === "cdpcmd") {
10568
+ if (!active) throw new Error("No attached devtools session covers this run.");
10569
+ const command = options.command && typeof options.command === "object" && !Array.isArray(options.command) ? options.command : {};
10570
+ const method = typeof command.method === "string" ? command.method : "";
10571
+ const planlist = planallowlist(plan.steps);
10572
+ const allowlist = { domains: active.session.domains, ...planlist?.methods !== void 0 ? { methods: planlist.methods } : {} };
10573
+ if (!allowlistcovers(allowlist, method)) throw new Error(`The raw command ${method} stays outside the enabled domain allowlist of the run attach.`);
10574
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The command returned no result." };
10575
+ const duration = typeof output.details?.duration === "number" ? output.details.duration : 0;
10576
+ const errorclass = typeof output.details?.errorclass === "string" ? output.details.errorclass : output.ok ? void 0 : "protocolerror";
10577
+ const record2 = sendcdpcommand({ id: randomid(), sessionid: active.session.id, runid: plan.id, stepid: step.id, method, ...command.params && typeof command.params === "object" && !Array.isArray(command.params) ? { params: command.params } : {}, ...typeof command.resultpath === "string" ? { resultpath: command.resultpath } : {}, duration, ...errorclass !== void 0 ? { errorclass } : {}, at: Date.now() });
10578
+ active.queue = serializecdpcommand(active.queue, record2);
10579
+ await memory.addcdpcommand(record2);
10580
+ await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "command", method, domain: record2.domain, duration, ...errorclass !== void 0 ? { errorclass } : {} }, Date.now()));
10581
+ const pausedetails = output.details?.paused;
10582
+ let pauseid;
10583
+ if (pausedetails && typeof pausedetails === "object") {
10584
+ pauseid = randomid();
10585
+ const pause = capturepause({ id: pauseid, runid: plan.id, stepid: step.id, reason: typeof pausedetails.reason === "string" ? pausedetails.reason : "breakpoint", callframes: pauseframes(pausedetails.frames), ...typeof pausedetails.hitbreakpoint === "string" ? { hitbreakpoint: pausedetails.hitbreakpoint } : {}, at: Date.now() });
10586
+ await memory.addpause(pause);
10587
+ await audit("debugger", `The run paused on breakpoint ${pause.hitbreakpoint ?? "unknown"} with ${pause.callframes.length} call frame${pause.callframes.length === 1 ? "" : "s"} captured.`, extra);
10588
+ }
10589
+ await audit("debugger", `Sent the reviewed command ${method} of the ${record2.domain} domain to session ${active.session.id}; it returned in ${duration} millisecond${duration === 1 ? "" : "s"}${errorclass !== void 0 ? ` with the ${errorclass} error class` : ""}; the params never enter the audit trail.`, extra);
10590
+ await refreshbadge();
10591
+ if (!output.ok) return { ok: false, summary: output.summary, details: { ...output.details ?? {}, command: record2, cdp: { sessionid: active.session.id, state: "attached", commandids: active.queue.map((item) => item.id) } } };
10592
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, command: record2, ...pauseid !== void 0 ? { pauseid } : {}, cdp: { sessionid: active.session.id, state: "attached", commandids: active.queue.map((item) => item.id) } } };
10593
+ }
10594
+ if (step.kind === "watchcdp") {
10595
+ if (!active) throw new Error("No attached devtools session covers this run.");
10596
+ const rules = [];
10597
+ for (const rule of Array.isArray(options.events) ? options.events : []) {
10598
+ const parsed = rule && typeof rule === "object" && !Array.isArray(rule) ? rule : void 0;
10599
+ const normalized = parsed !== void 0 ? await Promise.resolve(parsed) : void 0;
10600
+ const base = normalized !== void 0 ? { domain: String(normalized.domain ?? ""), event: String(normalized.event ?? ""), ...typeof normalized.match === "string" ? { match: normalized.match } : {} } : void 0;
10601
+ if (!base || !base.domain || !base.event) continue;
10602
+ const record2 = { id: randomid(), sessionid: active.session.id, runid: plan.id, stepid: step.id, domain: base.domain, event: base.event, ...base.match !== void 0 ? { match: base.match } : {}, events: 0, registeredat: Date.now() };
10603
+ rules.push(record2);
10604
+ active.rules.push(record2);
10605
+ await memory.setcdpeventrule(record2);
10606
+ }
10607
+ if (rules.length === 0) throw new Error("The event watch needs at least one reviewed domain event rule.");
10608
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The event watch returned no result." };
10609
+ const observed = detailarray(output.details, "events").flatMap((event) => {
10610
+ if (!event || typeof event !== "object") return [];
10611
+ const record2 = event;
10612
+ if (typeof record2.domain !== "string" || typeof record2.event !== "string") return [];
10613
+ return [{ domain: record2.domain, event: record2.event, ...typeof record2.payload === "string" ? { payload: record2.payload } : {} }];
10614
+ });
10615
+ const matched = watchcdpevents(rules, observed);
10616
+ for (const rule of active.rules) {
10617
+ const count = matched.matched.filter((item) => item.ruleid === rule.id).length;
10618
+ if (count > 0) rule.events += count;
10619
+ await memory.setcdpeventrule({ ...rule });
10620
+ }
10621
+ for (const item of matched.matched) {
10622
+ await memory.addtimelineentry({ id: randomid(), runid: plan.id, stepid: step.id, time: Date.now(), level: "info", source: "cdp", message: `${item.domain}.${item.event}${item.payload !== void 0 ? `: ${item.payload}` : ""}` });
10623
+ }
10624
+ await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "watch", events: matched.matched.length, ...rules[0] !== void 0 ? { domain: rules[0].domain } : {} }, Date.now()));
10625
+ await audit("debugger", `Watched ${rules.length} reviewed domain event rule${rules.length === 1 ? "" : "s"} of session ${active.session.id} for the reviewed window and forwarded ${matched.matched.length} matched event${matched.matched.length === 1 ? "" : "s"} into the run timeline; the subscription closes at run end or on step cancel.`, extra);
10626
+ await refreshbadge();
10627
+ return { ok: Boolean(output.ok), summary: output.summary, details: { ...output.details ?? {}, eventcounts: matched.counts, matched: matched.matched, cdp: { sessionid: active.session.id, state: "attached", commandids: [] } } };
10628
+ }
10629
+ if (step.kind === "setbreakpoint") {
10630
+ if (!active) throw new Error("No attached devtools session covers this run.");
10631
+ const input = breakpointinputof(options.breakpoint);
10632
+ if (!input) throw new Error("The breakpoint input is absent.");
10633
+ const settings = await memory.getsettings();
10634
+ const budgetcheck = breakpointbudgetallowed(active.breakpoints.filter((spec) => spec.revertedat === void 0).length, breakpointceilingof(settings));
10635
+ if (!budgetcheck.allowed) throw new Error(budgetcheck.reason ?? "The breakpoint ceiling refused the registration.");
10636
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The breakpoint registration returned no result." };
10637
+ if (!output.ok) return output;
10638
+ const registered = output.details?.breakpoint;
10639
+ const id = typeof registered?.id === "string" ? registered.id : randomid();
10640
+ const record2 = { id, runid: plan.id, stepid: step.id, url: input.url, line: input.line, ...input.column !== void 0 ? { column: input.column } : {}, ...input.condition !== void 0 ? { condition: input.condition } : {}, hits: 0, registeredat: Date.now() };
10641
+ active.breakpoints.push(record2);
10642
+ await memory.addbreakpoint(record2);
10643
+ await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "breakpoint", hits: 0 }, Date.now()));
10644
+ await audit("debugger", `Registered the reviewed breakpoint ${id} at ${input.url}:${input.line}${input.condition !== void 0 ? ` under the reviewed condition` : ""} of session ${active.session.id}; the run pauses when an instrumented probe of the run hits it.`, extra);
10645
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, breakpoint: record2, cdp: { sessionid: active.session.id, state: "attached", commandids: [] } } };
10646
+ }
10647
+ if (step.kind === "stepcode") {
10648
+ if (!active) throw new Error("No attached devtools session covers this run.");
10649
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The step returned no result." };
10650
+ const pausedetails = output.details?.pausestate;
10651
+ let pauseid;
10652
+ if (pausedetails && typeof pausedetails === "object") {
10653
+ pauseid = randomid();
10654
+ const domversion = await memory.nextobservationversion().catch(() => void 0);
10655
+ const pause = capturepause({ id: pauseid, runid: plan.id, stepid: step.id, reason: typeof pausedetails.reason === "string" ? pausedetails.reason : "step", callframes: pauseframes(pausedetails.frames), ...typeof pausedetails.hitbreakpoint === "string" ? { hitbreakpoint: pausedetails.hitbreakpoint } : {}, ...domversion !== void 0 ? { domsnapshotid: String(domversion) } : {}, at: Date.now() });
10656
+ await memory.addpause(pause);
10657
+ }
10658
+ await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "step", frames: pauseframes(pausedetails?.frames).length }, Date.now()));
10659
+ await audit("debugger", `Stepped the paused probe of session ${active.session.id} with the ${stepmodeof(options.mode) ?? "reviewed"} mode and captured the pause state${pauseid !== void 0 ? ` as ${pauseid} with the dom snapshot through the page bridge` : ""}.`, extra);
10660
+ return { ok: Boolean(output.ok), summary: output.summary, details: { ...output.details ?? {}, ...pauseid !== void 0 ? { pauseid } : {}, cdp: { sessionid: active.session.id, state: "attached", commandids: [] } } };
10661
+ }
10662
+ if (step.kind === "watchexpr") {
10663
+ if (!active) throw new Error("No attached devtools session covers this run.");
10664
+ const input = watchexpressionof(options.expression);
10665
+ if (!input) throw new Error("The watch expression input is absent.");
10666
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The watch expression returned no result." };
10667
+ const stored = (await memory.getwatchexpressions()).find((item) => item.stepid === step.id);
10668
+ const base = stored ?? { id: randomid(), runid: plan.id, stepid: step.id, expression: input.expression, scope: input.scope, reviewed: true, values: [], at: Date.now() };
10669
+ if (output.ok) {
10670
+ const value = typeof output.details?.value === "string" ? output.details.value : "";
10671
+ const pauses = await memory.listpauses(plan.id);
10672
+ const pauseid = pauses[0]?.id ?? "nopause";
10673
+ const updated = recordwatchvalue(base, pauseid, value, Date.now());
10674
+ await memory.setwatchexpression(updated);
10675
+ await memory.addtimelineentry({ id: randomid(), runid: plan.id, stepid: step.id, time: Date.now(), level: "debug", source: "cdp", message: `Watch expression ${input.expression} evaluated to ${value} at pause ${pauseid} in the ${input.scope} scope.` });
10676
+ }
10677
+ await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "watch", events: base.values.length }, Date.now()));
10678
+ await audit("debugger", `Evaluated the reviewed watch expression of step ${step.id} at the pause in the ${input.scope} scope; the value stores with its pause scope in the timeline and the expression text stays reviewed.`, extra);
10679
+ return { ok: Boolean(output.ok), summary: output.summary, details: { ...output.details ?? {}, cdp: { sessionid: active.session.id, state: "attached", commandids: [] } } };
10680
+ }
10681
+ if (step.kind === "overridescript") {
10682
+ if (!active) throw new Error("No attached devtools session covers this run.");
10683
+ const override = options.override && typeof options.override === "object" && !Array.isArray(options.override) ? options.override : void 0;
10684
+ const urlpattern = typeof override?.urlpattern === "string" ? override.urlpattern : "";
10685
+ const source = typeof override?.source === "string" ? override.source : "";
10686
+ if (!urlpattern || !source) throw new Error("The script override input is absent.");
10687
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The override returned no result." };
10688
+ if (!output.ok) return output;
10689
+ const applied = output.details?.override;
10690
+ const id = typeof applied?.id === "string" ? applied.id : randomid();
10691
+ const record2 = { id, runid: plan.id, stepid: step.id, urlpattern, source, reviewed: true, reviewedat: Date.now(), hits: 0, appliedat: Date.now() };
10692
+ active.overrides.push(record2);
10693
+ await memory.addscriptoverride(record2);
10694
+ await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "override", hits: 0 }, Date.now()));
10695
+ await audit("debugger", `Applied the reviewed script override ${id} of ${urlpattern} on new document evaluation through the page instrumentation; the fixture reverts at run end and the source never enters the audit trail.`, extra);
10696
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, override: { id, urlpattern, appliedat: record2.appliedat, reviewed: true }, cdp: { sessionid: active.session.id, state: "attached", commandids: [] } } };
10697
+ }
10698
+ return { ok: false, summary: "The devtools step is not part of the instrumented family." };
10699
+ }
10700
+ var activeprofiletargets = /* @__PURE__ */ new Map();
10701
+ var activememorytrackers = /* @__PURE__ */ new Map();
10702
+ var lastheapshots = /* @__PURE__ */ new Map();
10703
+ function profiletargetsof(options) {
10704
+ const single = attachtargetof(options.target);
10705
+ const listed = Array.isArray(options.attachtargets) ? options.attachtargets.flatMap((target) => {
10706
+ const parsed = attachtargetof(target);
10707
+ return parsed !== void 0 ? [parsed] : [];
10708
+ }) : [];
10709
+ return [...single !== void 0 ? [single] : [], ...listed];
10710
+ }
10711
+ async function stopprofileinstrumentsforrun(runid, reason) {
10712
+ const targets = activeprofiletargets.get(runid);
10713
+ if (targets) {
10714
+ activeprofiletargets.delete(runid);
10715
+ await audit("profile", `Detached ${targets.targets.length} profiling target${targets.targets.length === 1 ? "" : "s"} at ${reason}; the flattened sub sessions close with the parent session.`, { planid: runid }).catch(() => void 0);
10716
+ }
10717
+ const tracker = activememorytrackers.get(runid);
10718
+ if (tracker) {
10719
+ activememorytrackers.delete(runid);
10720
+ const samples = await memory.listgrowsamples(runid).catch(() => []);
10721
+ if (samples.length > 0) {
10722
+ const trend = growthtrend({ runid, samples, slope: tracker.slope, now: Date.now() });
10723
+ await memory.settrend(trend).catch(() => void 0);
10724
+ }
10725
+ await audit("profile", `Closed the memory growth tracker of ${samples.length} sample${samples.length === 1 ? "" : "s"} at ${reason}; the computed trend stays stored with its flagged steps.`, { planid: runid }).catch(() => void 0);
10726
+ }
10727
+ lastheapshots.delete(runid);
10728
+ }
10729
+ async function sampleheapforstep(tracker, stepid, tabid2, origin, plan) {
10730
+ const now = Date.now();
10731
+ if (now - tracker.lastsampleat < tracker.interval) return;
10732
+ const samplestep = { id: stepid, kind: "heapshot", summary: "Heap sample beside the step", risk: "read" };
10733
+ const output = await dispatchpagestep(samplestep, tabid2, origin, plan);
10734
+ const details = output?.details;
10735
+ if (!output?.ok || typeof details?.usedbytes !== "number") return;
10736
+ const sample = growsampleof({ id: randomid(), runid: tracker.runid, stepid, usedbytes: details.usedbytes, limitbytes: typeof details.limitbytes === "number" ? details.limitbytes : 0, now });
10737
+ await memory.addgrowsample(sample);
10738
+ tracker.lastsampleat = now;
10739
+ const samples = await memory.listgrowsamples(tracker.runid);
10740
+ const trend = growthtrend({ runid: tracker.runid, samples, slope: tracker.slope, now });
10741
+ await memory.settrend(trend);
10742
+ if (trend.flaggedsteps.includes(stepid)) await memory.addtimelineentry({ id: randomid(), runid: tracker.runid, stepid, time: now, level: "warn", source: "longtask", message: `Heap growth of step ${stepid} exceeds the reviewed slope of ${tracker.slope} bytes per millisecond; the step is flagged in the memory trend.` });
10743
+ }
10744
+ async function executeprofilestep(step, session, plan, tabid2, origin) {
10745
+ const options = stepoptions2(step);
10746
+ const extra = { sessionid: session.id, planid: plan.id, stepid: step.id };
10747
+ const targets = profiletargetsof(options);
10748
+ const grants = await memory.getdebuggergrants();
10749
+ const targetgatecheck = targetgate({ session, tabid: tabid2, origin, targets, grants, now: Date.now() });
10750
+ if (!targetgatecheck.allowed) {
10751
+ const consent = debuggerconsentcovers(origin, [], grants);
10752
+ if (!consent.allowed) {
10753
+ const pending = grants.find((grant) => grant.origin === origin && grant.approved === void 0);
10754
+ if (!pending) {
10755
+ const record2 = { id: randomid(), prompt: `Profiling instrumentation on ${origin} for run ${plan.id} through the performance timeline buffers and the injected probes of the page bridge.`, origin, domains: [], consentedat: Date.now() };
10756
+ await memory.setdebuggergrant(record2);
10757
+ await refreshbadge();
10758
+ }
10759
+ throw new Error(`${consent.reason} The prompt is open in the review panel with the profiling derivation shown; approve it and run the step again.`);
10760
+ }
10761
+ throw new Error(targetgatecheck.reason ?? "The profiling step stays outside the profiling target gate.");
10762
+ }
10763
+ if (step.kind === "measureflow") {
10764
+ const spec = flowspecof(options.flow);
10765
+ if (!spec) throw new Error("A reviewed flow spec is required.");
10766
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The flow measurement returned no result." };
10767
+ if (!output.ok) return output;
10768
+ const entries = detailarray(output.details, "entries").flatMap((entry) => {
10769
+ if (!entry || typeof entry !== "object") return [];
10770
+ const record2 = entry;
10771
+ if (typeof record2.name !== "string" || typeof record2.type !== "string" || typeof record2.start !== "number" || typeof record2.duration !== "number") return [];
10772
+ return [{ name: record2.name, type: record2.type, start: record2.start, duration: record2.duration }];
10773
+ });
10774
+ const metrics = measure({ runid: plan.id, stepid: step.id, spec, entries, now: Date.now() });
10775
+ for (const metric of metrics) await memory.addflowmetric(metric);
10776
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "flow", metrics: metrics.length, recordids: metrics.map((metric) => metric.id) }, Date.now()));
10777
+ await audit("profile", `Measured the flow ${spec.prefix} of ${origin} over ${spec.steps.length} step${spec.steps.length === 1 ? "" : "s"} with ${metrics.length} metric${metrics.length === 1 ? "" : "s"} of the reviewed set ${spec.metrics.join(", ")}; the marks and performance buffers derive the durations because no debugger permission exists in the manifest.`, extra);
10778
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, flow: { metrics: metrics.map((metric) => ({ name: metric.name, duration: metric.duration, steps: metric.steps })) }, profile: { metrics: metrics.length, samples: 0 } } };
10779
+ }
10780
+ if (step.kind === "heapshot") {
10781
+ const heap = options.heap && typeof options.heap === "object" && !Array.isArray(options.heap) ? options.heap : {};
10782
+ const interval = typeof heap.interval === "number" ? heap.interval : void 0;
10783
+ if (!heapintervalallowed(lastheapshots.get(plan.id), interval, Date.now())) throw new Error(`The reviewed heap snapshot interval of ${interval} milliseconds has not elapsed since the last snapshot of this run; the frequency bound stays the user choice only.`);
10784
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The heap snapshot returned no result." };
10785
+ if (!output.ok) return output;
10786
+ const details = output.details;
10787
+ const record2 = heapsnap({ id: randomid(), runid: plan.id, stepid: step.id, origin, usedbytes: typeof details.usedbytes === "number" ? details.usedbytes : 0, limitbytes: typeof details.limitbytes === "number" ? details.limitbytes : 0, nodecount: typeof details.nodecount === "number" ? details.nodecount : 0, now: Date.now() });
10788
+ lastheapshots.set(plan.id, record2.capturedat);
10789
+ await memory.setheaprecord(record2);
10790
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "heap", nodes: record2.nodecount, bytes: record2.bytesize, recordids: [record2.id] }, Date.now()));
10791
+ await audit("profile", `Captured the on demand heap snapshot ${record2.id} of ${origin} with ${record2.bytesize} used bytes and ${record2.nodecount} dom nodes${interval !== void 0 ? ` under the user chosen interval of ${interval} milliseconds` : ""}; the bytes derive from the page performance memory buffer because no heap profiler exists without the debugger permission.`, extra);
10792
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, heapid: record2.id, heap: record2, profile: { metrics: 0, samples: 0 } } };
10793
+ }
10794
+ if (step.kind === "trackmemory") {
10795
+ const growth = options.growth && typeof options.growth === "object" && !Array.isArray(options.growth) ? options.growth : void 0;
10796
+ const slope = typeof growth?.slope === "number" ? growth.slope : void 0;
10797
+ if (growth === void 0 || slope === void 0) throw new Error("The reviewed growth slope is required.");
10798
+ const interval = typeof growth.interval === "number" ? growth.interval : 0;
10799
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The memory sample returned no result." };
10800
+ if (!output.ok) return output;
10801
+ const details = output.details;
10802
+ const sample = growsampleof({ id: randomid(), runid: plan.id, stepid: step.id, usedbytes: typeof details.usedbytes === "number" ? details.usedbytes : 0, limitbytes: typeof details.limitbytes === "number" ? details.limitbytes : 0, now: Date.now() });
10803
+ await memory.addgrowsample(sample);
10804
+ activememorytrackers.set(plan.id, { runid: plan.id, slope, interval, lastsampleat: sample.at });
10805
+ const samples = await memory.listgrowsamples(plan.id);
10806
+ const trend = growthtrend({ runid: plan.id, samples, slope, now: Date.now() });
10807
+ await memory.settrend(trend);
10808
+ if (trend.flaggedsteps.length > 0) await memory.addtimelineentry({ id: randomid(), runid: plan.id, stepid: step.id, time: Date.now(), level: "warn", source: "longtask", message: `Memory growth tracking flagged ${trend.flaggedsteps.length} step${trend.flaggedsteps.length === 1 ? "" : "s"} above the reviewed slope of ${slope} bytes per millisecond: ${trend.flaggedsteps.join(", ")}.` });
10809
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "memory", samples: trend.samples, flagged: trend.flaggedsteps.length, recordids: [sample.id] }, Date.now()));
10810
+ await audit("profile", `Started the memory growth tracking of ${origin} with the reviewed slope of ${slope} bytes per millisecond and ${trend.samples} stored sample${trend.samples === 1 ? "" : "s"}${trend.flaggedsteps.length > 0 ? `; the steps ${trend.flaggedsteps.join(", ")} exceed the slope` : ""}; a sample is taken beside every following step of the run.`, extra);
10811
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, trend, profile: { metrics: 0, samples: trend.samples } } };
10812
+ }
10813
+ if (step.kind === "profilecpu") {
10814
+ const duration = options.profile && typeof options.profile === "object" && !Array.isArray(options.profile) ? options.profile.duration : void 0;
10815
+ if (typeof duration !== "number") throw new Error("The reviewed cpu profile duration is required.");
10816
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The cpu profile returned no result." };
10817
+ if (!output.ok) return output;
10818
+ const samples = detailarray(output.details, "samples").flatMap((sample) => {
10819
+ if (!sample || typeof sample !== "object") return [];
10820
+ const record3 = sample;
10821
+ if (typeof record3.name !== "string" || typeof record3.time !== "number") return [];
10822
+ return [{ name: record3.name, time: record3.time }];
10823
+ });
10824
+ const record2 = cpusnap({ id: randomid(), runid: plan.id, stepid: step.id, origin, duration, samples, now: Date.now() });
10825
+ await memory.setcpuprofile(record2);
10826
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "cpu", samples: record2.samplecount, recordids: [record2.id] }, Date.now()));
10827
+ await audit("profile", `Profiled the cpu window of ${duration} milliseconds of ${origin} with ${record2.samplecount} sample${record2.samplecount === 1 ? "" : "s"} and ${record2.hotfunctions.length} hot function${record2.hotfunctions.length === 1 ? "" : "s"}${record2.hotfunctions.length > 0 ? ` led by ${record2.hotfunctions[0]}` : ""}; the samples derive from the long task attribution and event timing buffers because no sampling profiler exists without the debugger permission.`, extra);
10828
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, profileid: record2.id, cpu: { duration: record2.duration, samplecount: record2.samplecount, hotfunctions: record2.hotfunctions }, profile: { metrics: 0, samples: record2.samplecount } } };
10829
+ }
10830
+ if (step.kind === "watchshifts") {
10831
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The layout shift watch returned no result." };
10832
+ if (!output.ok) return output;
10833
+ const entries = [];
10834
+ for (const shift of detailarray(output.details, "shifts")) {
10835
+ const parsed = shiftentryof({ ...shift && typeof shift === "object" && !Array.isArray(shift) ? shift : {}, id: randomid(), runid: plan.id, stepid: step.id, at: Date.now() });
10836
+ if (parsed !== void 0) entries.push(parsed);
10837
+ }
10838
+ for (const entry of entries) await memory.addshiftentry(entry);
10839
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "shift", events: entries.length, recordids: entries.map((entry) => entry.id) }, Date.now()));
10840
+ await audit("profile", `Watched the layout shifts of ${origin} for the reviewed window and recorded ${entries.length} shift${entries.length === 1 ? "" : "s"} with scores and impacted selectors from the performance layout-shift buffer.`, extra);
10841
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, shifts: entries, profile: { metrics: 0, samples: 0 } } };
10842
+ }
10843
+ if (step.kind === "traceload") {
10844
+ const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
10845
+ const categories = trace !== void 0 && Array.isArray(trace.categories) ? trace.categories.filter((category) => typeof category === "string") : [];
10846
+ if (trace === void 0 || categories.length === 0) throw new Error("The reviewed trace categories are required.");
10847
+ const exporttarget = trace.exporttarget === "download" ? "download" : "memory";
10848
+ const started = Date.now();
10849
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The trace record returned no result." };
10850
+ if (!output.ok) return output;
10851
+ const events = detailarray(output.details, "events").flatMap((event) => {
10852
+ if (!event || typeof event !== "object") return [];
10853
+ const record3 = event;
10854
+ if (typeof record3.name !== "string" || typeof record3.category !== "string" || typeof record3.offset !== "number") return [];
10855
+ return [{ name: record3.name, category: record3.category, offset: record3.offset }];
10856
+ });
10857
+ const record2 = tracestart({ id: randomid(), runid: plan.id, stepid: step.id, origin, categories, now: started });
10858
+ const file = tracetofile(record2, events);
10859
+ const ceiling = traceceilingof(await memory.getsettings());
10860
+ if (ceiling !== void 0 && file.bytesize > ceiling) throw new Error(`The derived trace file of ${file.bytesize} bytes exceeds the user configured trace byte ceiling of ${ceiling} bytes; review a wider ceiling or a narrower category list.`);
10861
+ const ended = { ...record2, endedat: Date.now(), bytesize: file.bytesize, events: file.events };
10862
+ await memory.settracerecord(ended);
10863
+ await memory.settracefile(record2.id, file.content);
10864
+ let exported = false;
10865
+ if (exporttarget === "download") {
10866
+ const granted = await chrome.permissions.contains({ permissions: ["downloads"] }).catch(() => false);
10867
+ if (!granted) throw new Error("The trace export needs the downloads capability; request it from the review panel.");
10868
+ const dataurl = `data:application/json;base64,${btoa(file.content)}`;
10869
+ await chrome.downloads.download({ url: dataurl, filename: `devthink-trace-${record2.id}.json` });
10870
+ exported = true;
10871
+ await memory.settracerecord({ ...ended, exportedat: Date.now() });
10872
+ }
10873
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "trace", events: file.events, bytes: file.bytesize, recordids: [record2.id] }, Date.now()));
10874
+ await audit("profile", `Recorded the trace ${record2.id} of ${origin} with the reviewed categories ${categories.join(", ")} over ${file.events} event${file.events === 1 ? "" : "s"} and ${file.bytesize} bytes${exported ? " exported through the reviewed download flow" : " kept in memory"}; the file derives from the performance timeline entries and is not the devtools binary trace format.`, extra);
10875
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, traceid: record2.id, trace: { categories, events: file.events, bytesize: file.bytesize, exported }, profile: { metrics: 0, samples: 0 } } };
10876
+ }
10877
+ if (step.kind === "annotatetrace") {
10878
+ const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
10879
+ const traceid = typeof trace?.traceid === "string" ? trace.traceid : "";
10880
+ const stored = traceid ? (await memory.gettracerecords()).find((item) => item.id === traceid && item.runid === plan.id) : void 0;
10881
+ if (!stored) throw new Error(`No stored trace of this run matches ${traceid || "the given id"}.`);
10882
+ const annotations = (Array.isArray(options.annotations) ? options.annotations : []).flatMap((input) => {
10883
+ const parsed = annotationof(input);
10884
+ return parsed !== void 0 ? [parsed] : [];
10885
+ });
10886
+ if (annotations.length === 0) throw new Error("Exported traces carry their step annotations: at least one reviewed annotation is required.");
10887
+ const timeline = await memory.listtimeline({ runid: plan.id });
10888
+ const annotated = annotatetrace({ trace: stored, annotations, timeline: timeline.map((entry) => ({ stepid: entry.stepid, time: entry.time })), now: Date.now() });
10889
+ await memory.settracerecord(annotated);
10890
+ const content = await memory.gettracefile(traceid);
10891
+ if (content !== void 0) {
10892
+ try {
10893
+ const replay = replaytrace(content);
10894
+ const updated = tracetofile(annotated, replay.events.map((event) => ({ name: event.name, category: event.category, offset: event.offset })));
10895
+ await memory.settracefile(traceid, updated.content);
10896
+ await memory.settracerecord({ ...annotated, bytesize: updated.bytesize });
10897
+ } catch {
10898
+ }
10899
+ }
10900
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "annotate", events: annotated.annotations.length, recordids: [traceid] }, Date.now()));
10901
+ await audit("profile", `Annotated the trace ${traceid} with ${annotated.annotations.length} step annotation${annotated.annotations.length === 1 ? "" : "s"} aligned with the run timeline entries of steps ${annotated.annotations.map((annotation) => annotation.stepid).join(", ")}.`, extra);
10902
+ return { ok: true, summary: `Annotated the stored trace ${traceid} with ${annotated.annotations.length} step annotation${annotated.annotations.length === 1 ? "" : "s"} aligned with the run timeline.`, details: { traceid, annotations: annotated.annotations, profile: { metrics: 0, samples: 0 } } };
10903
+ }
10904
+ if (step.kind === "replaytrace") {
10905
+ const trace = options.trace && typeof options.trace === "object" && !Array.isArray(options.trace) ? options.trace : void 0;
10906
+ const traceid = typeof trace?.traceid === "string" ? trace.traceid : "";
10907
+ const stored = traceid ? (await memory.gettracerecords()).find((item) => item.id === traceid) : void 0;
10908
+ if (!stored) throw new Error(`No stored trace matches ${traceid || "the given id"}.`);
10909
+ const content = await memory.gettracefile(traceid);
10910
+ const replay = content !== void 0 ? replaytrace(content) : { traceid, runid: stored.runid, categories: Object.fromEntries(stored.categories.map((category) => [category, 0])), events: [], annotations: stored.annotations };
10911
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "replay", events: replay.events.length, recordids: [traceid] }, Date.now()));
10912
+ await audit("profile", `Replayed the stored trace ${traceid} of ${stored.runid} offline with ${replay.events.length} event${replay.events.length === 1 ? "" : "s"} grouped by category and step${content === void 0 ? "; the heavy file bytes had expired after the retention window so the replay renders the surviving metadata and annotations only" : ""}.`, extra);
10913
+ return { ok: true, summary: `Replayed the stored trace ${traceid} offline with ${replay.events.length} event${replay.events.length === 1 ? "" : "s"} grouped by category and step.`, details: { traceid, replay, ...content === void 0 ? { fileexpired: true } : {}, profile: { metrics: 0, samples: 0 } } };
10914
+ }
10915
+ if (step.kind === "capturesourcemaps") {
10916
+ const consents = await memory.getsourcemapconsents();
10917
+ const consent = sourcemapconsentcovers(origin, consents);
10918
+ if (!consent.allowed) {
10919
+ const pending = consents.find((item) => item.origin === origin && item.approved === void 0);
10920
+ if (!pending) {
10921
+ const record2 = { id: randomid(), prompt: `Source map capture on ${origin} for run ${plan.id}: the map files of the loaded same origin scripts are fetched and parsed locally.`, origin, consentedat: Date.now() };
10922
+ await memory.setsourcemapconsent(record2);
10923
+ await refreshbadge();
10924
+ }
10925
+ throw new Error(`${consent.reason} The prompt is open in the review panel; approve it and run the step again.`);
10926
+ }
10927
+ const approved = consents.find((item) => item.origin === origin && item.approved === true && item.revokedat === void 0);
10928
+ if (approved) await memory.setsourcemapconsent({ ...approved, usedat: Date.now() });
10929
+ const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The source map capture returned no result." };
10930
+ if (!output.ok) return output;
10931
+ const scripts = detailarray(output.details, "scripts").flatMap((script) => {
10932
+ if (!script || typeof script !== "object") return [];
10933
+ const record2 = script;
10934
+ if (typeof record2.url !== "string" || typeof record2.mapurl !== "string") return [];
10935
+ return [{ url: record2.url, source: `//# sourceMappingURL=${record2.mapurl}` }];
10936
+ });
10937
+ const refs = capturesourcemaps({ runid: plan.id, stepid: step.id, origin, scripts, now: Date.now() });
10938
+ const parsedmaps = /* @__PURE__ */ new Map();
10939
+ for (const ref of refs) {
10940
+ let parsed = false;
10941
+ try {
10942
+ const response = await fetch(ref.mapurl, { credentials: "omit" });
10943
+ if (response.ok) {
10944
+ const map = await response.json();
10945
+ if (Array.isArray(map.sources) && typeof map.mappings === "string") {
10946
+ parsed = true;
10947
+ parsedmaps.set(ref.scripturl, { sources: map.sources.filter((source) => typeof source === "string"), mappings: map.mappings });
10948
+ }
10949
+ }
10950
+ } catch {
10951
+ }
10952
+ await memory.setsourcemapref({ ...ref, parsed });
10953
+ }
10954
+ const stack = (Array.isArray(options.stack) ? options.stack : []).flatMap((location2) => {
10955
+ if (!location2 || typeof location2 !== "object") return [];
10956
+ const record2 = location2;
10957
+ if (typeof record2.url !== "string" || typeof record2.line !== "number") return [];
10958
+ const map = parsedmaps.get(record2.url);
10959
+ if (map === void 0) return [];
10960
+ const rewritten = rewritesourcelocation({ url: record2.url, line: record2.line, ...typeof record2.column === "number" ? { column: record2.column } : {} }, map);
10961
+ return rewritten !== void 0 ? [{ from: `${record2.url}:${record2.line}`, to: `${rewritten.url}:${rewritten.line}` }] : [];
10962
+ });
10963
+ await memory.setprogress(recordprofile(await memory.getprogress(), plan.id, step.id, { family: "sourcemap", events: refs.length, recordids: refs.map((ref) => ref.id) }, Date.now()));
10964
+ await audit("profile", `Captured ${refs.length} source map reference${refs.length === 1 ? "" : "s"} of ${origin} with ${[...parsedmaps.keys()].length} parsed map${[...parsedmaps.keys()].length === 1 ? "" : "s"}${stack.length > 0 ? ` and rewrote ${stack.length} stack location${stack.length === 1 ? "" : "s"}` : ""}; the script sources stayed in the page bridge and only the map urls and parsed state entered the record.`, extra);
10965
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, sourcemaps: refs.map((ref) => ({ scripturl: ref.scripturl, mapurl: ref.mapurl, parsed: ref.parsed })), ...stack.length > 0 ? { rewritten: stack } : {}, profile: { metrics: 0, samples: 0 } } };
10966
+ }
10967
+ return { ok: false, summary: "The profiling step is not part of the instrumented family." };
10968
+ }
9463
10969
  var activerules = /* @__PURE__ */ new Map();
9464
10970
  var activeauthflows = /* @__PURE__ */ new Map();
9465
10971
  function rulesetof(runid) {
@@ -9850,11 +11356,12 @@ async function refreshbadge() {
9850
11356
  const recordingprompts = (await memory.getrecordingconsents()).filter((record2) => record2.approved === void 0).length;
9851
11357
  const fetchprompts = (await memory.getfetchconsents()).filter((consent) => consent.approved === void 0).length;
9852
11358
  const consoleprompts = (await memory.getconsoleconsents()).filter((consent) => consent.approved === void 0).length;
11359
+ const debuggerprompts = (await memory.getdebuggergrants()).filter((grant) => grant.approved === void 0).length;
9853
11360
  const observedrequests = (await memory.getexchanges()).length;
9854
11361
  const livechannels = (await memory.getchannels()).filter((channel) => channel.state === "open" || channel.state === "connecting").length;
9855
11362
  const activerulescount = [...activerules.values()].reduce((total2, ruleset) => total2 + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0);
9856
11363
  const tasktabs2 = new Set(badges.map((badge) => badge.tabid)).size;
9857
- const total = (queues?.prefetch ?? 0) + (queues?.batchopen ?? 0) + tasktabs2 + prompts + consents + quarantined + datasets + captures + media + recordingprompts + fetchprompts + consoleprompts + observedrequests + livechannels + activerulescount;
11364
+ const total = (queues?.prefetch ?? 0) + (queues?.batchopen ?? 0) + tasktabs2 + prompts + consents + quarantined + datasets + captures + media + recordingprompts + fetchprompts + consoleprompts + debuggerprompts + observedrequests + livechannels + activerulescount;
9858
11365
  await chrome.action.setBadgeText({ text: total > 0 ? String(total) : "" }).catch(() => {
9859
11366
  });
9860
11367
  }
@@ -9925,6 +11432,12 @@ async function executestep(stepid) {
9925
11432
  } else if (isdebugkind(step.kind)) {
9926
11433
  if (!session || !plan || plan.state !== "approved") throw new Error("Debugging kinds refuse to run outside an approved session plan.");
9927
11434
  output = await executetimelinestep(step, session, plan, tab.id, origin);
11435
+ } else if (iscdpkind(step.kind)) {
11436
+ if (!session || !plan || plan.state !== "approved") throw new Error("Devtools protocol kinds refuse to run outside an approved session plan.");
11437
+ output = await executecdpstep(step, session, plan, tab.id, origin);
11438
+ } else if (isprofilekind(step.kind)) {
11439
+ if (!session || !plan || plan.state !== "approved") throw new Error("Profiling kinds refuse to run outside an approved session plan.");
11440
+ output = await executeprofilestep(step, session, plan, tab.id, origin);
9928
11441
  } else {
9929
11442
  if (step.target && freshcheckkinds.has(step.kind)) {
9930
11443
  const fresh = await snapshot(tab.id);
@@ -9971,6 +11484,9 @@ async function executestep(stepid) {
9971
11484
  const completed = watchwindow ? recordwatchcompletion(base, plan.id, stepid, watchwindow.startedat, watchwindow.lifetime, Date.now()) : recordstep(base, plan.id, stepid, Date.now());
9972
11485
  const tracked = recordoutcome(completed, plan.id, outcome, Date.now());
9973
11486
  await memory.setprogress(tracked);
11487
+ const tracker = activememorytrackers.get(plan.id);
11488
+ if (tracker) await sampleheapforstep(tracker, stepid, tab.id, origin, plan).catch(() => {
11489
+ });
9974
11490
  await updatetaskbadges(plan, tracked);
9975
11491
  await refreshbadge();
9976
11492
  if (iscomplete(tracked, plan) && plan.state === "approved") {
@@ -9982,6 +11498,10 @@ async function executestep(stepid) {
9982
11498
  });
9983
11499
  await revertcontrolsforrun(plan.id, "plan completion").catch(() => {
9984
11500
  });
11501
+ await detachcdpforrun(plan.id, "plan completion").catch(() => {
11502
+ });
11503
+ await stopprofileinstrumentsforrun(plan.id, "plan completion").catch(() => {
11504
+ });
9985
11505
  const done = { ...plan, state: "completed", completedat: Date.now() };
9986
11506
  await memory.setplan(done);
9987
11507
  await audit("complete", "Every reviewed step of the approved plan has executed.", { ...session ? { sessionid: session.id } : {}, planid: done.id });
@@ -10158,7 +11678,7 @@ async function handlerequest(message, sender) {
10158
11678
  const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
10159
11679
  const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
10160
11680
  const livestate = { phase: livetab?.status === "loading" ? "loading" : "complete", ...navrecords[0] ? { finalurl: navrecords[0].finalurl, redirects: navrecords[0].chain } : {} };
10161
- return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), socketsactive: activesockets.size, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {} };
11681
+ return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {}, trail: trailreport({ ...session ? { sessionid: session.id } : {}, trail }), navrecords, ratestates, safeties, curated, waitprofiles, auths, navcontrol, navqueues, artifacts, navstate: livestate, ...waitprofile ? { waitprofile } : {}, offline: !navigator.onLine, tabs, windows, layouts: layoutreport({ layouts }), tabgroups, tabmetas, badges, snapshots, closedtabs, tabwatchevents, clones, tasktabgauge: taskgauge, ...controltab ? { controltab } : {}, tabreport: report, profiles, tickets, wizards: wizardreport({ ...session ? { sessionid: session.id } : {}, wizards, picks }), picks, errorreports, captchas, detections, ...codeentry !== void 0 ? { codeentry: true } : {}, datasets, imports, extractsessions, streams, exports, provenances, taskrules, sheetendpoints: sheetgrants, downloads, netlogs, clipconsents, clips, quarantines, cleanuprules, cleanupruns, capturecounters, inventory, mimefilters, scanhooks, captures: capturemetadata, capturepairs, capturepolicy: runsettings?.capturepolicy ?? "manual", media: mediarecords, imagebatches, recordingconsents, recordingactive: [...activerecordings.values()].map((active) => ({ id: active.record.id, kind: active.record.kind, scope: active.record.scope, startedat: active.record.startedat, stopat: active.stopat })), recordingwindow: runsettings?.recordingwindow, calls, endpoints, fetchconsents, apikeys, callretention: runsettings?.callretention, fetchesactive: activefetches.size, exchanges, channels, subscriptions, apimap, messages: messagecount, webrequestgrant: runsettings?.webrequestgrant === true, bodyretention: runsettings?.bodyretention, timelineretention: runsettings?.timelineretention, timeline, consoleconsents: await memory.getconsoleconsents(), rotationtargets: await memory.getrotationtargets(), levelsummaries: await memory.getlevelsummaries(), cdpsessions: await memory.getcdpsessions(), cdpcommands: await memory.getcdpcommands(), cdpeventrules: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watchexpressions: await memory.getwatchexpressions(), scriptoverrides: await memory.getscriptoverrides(), debuggergrants: await memory.getdebuggergrants(), pauseretention: runsettings?.pauseretention, breakpointceiling: runsettings?.breakpointceiling, cdpattached: [...activecdpsessions.values()].filter((active) => active.session.detachedat === void 0).length, profileretention: runsettings?.profileretention, traceceiling: runsettings?.traceceiling, profile: profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() }), profileactive: activememorytrackers.size + activeprofiletargets.size, profiletargets: [...activeprofiletargets.values()].flatMap((entry) => entry.targets), socketsactive: activesockets.size, traffic, tokens, authflows, activerules: [...activerules.values()].reduce((total, ruleset) => total + ruleset.blocks.filter((rule) => rule.revertedat === void 0).length + ruleset.mocks.filter((rule) => rule.revertedat === void 0).length + ruleset.rewrites.filter((rule) => rule.revertedat === void 0).length + (ruleset.proxy !== void 0 && ruleset.proxy.revertedat === void 0 ? 1 : 0), 0), ...stitchprogress.size > 0 ? { stitchprogress: [...stitchprogress.values()] } : {} };
10162
11682
  }
10163
11683
  case "capabilities":
10164
11684
  return refreshcapabilities();
@@ -10932,6 +12452,136 @@ async function handlerequest(message, sender) {
10932
12452
  await refreshbadge();
10933
12453
  return { closed: true, id: inputclose.id ?? "" };
10934
12454
  }
12455
+ case "setpauseretention": {
12456
+ const inputretention = message;
12457
+ const settings = await memory.getsettings();
12458
+ const retention = typeof inputretention.retention === "number" && Number.isInteger(inputretention.retention) && inputretention.retention >= 0 ? inputretention.retention : void 0;
12459
+ await memory.setsettings({ ...settings, ...retention !== void 0 ? { pauseretention: retention } : {} });
12460
+ await audit("configure", `The user set the pause capture retention to ${retention === void 0 ? "keep every capture" : `${retention} capture${retention === 1 ? "" : "s"}`}; the pause reason and hit breakpoint always survive.`);
12461
+ return { pauseretention: retention };
12462
+ }
12463
+ case "setbreakpointceiling": {
12464
+ const inputceiling = message;
12465
+ const settings = await memory.getsettings();
12466
+ const ceiling = typeof inputceiling.ceiling === "number" && Number.isInteger(inputceiling.ceiling) && inputceiling.ceiling >= 0 ? inputceiling.ceiling : void 0;
12467
+ await memory.setsettings({ ...settings, ...ceiling !== void 0 ? { breakpointceiling: ceiling } : {} });
12468
+ await audit("configure", `The user set the breakpoint ceiling to ${ceiling === void 0 ? "no ceiling" : `${ceiling} breakpoint${ceiling === 1 ? "" : "s"} per run`}; an absent value never refuses a breakpoint.`);
12469
+ return { breakpointceiling: ceiling };
12470
+ }
12471
+ case "approvedebuggerconsent": {
12472
+ const inputapprove = message;
12473
+ const records = await memory.getdebuggergrants();
12474
+ const record2 = records.find((item) => item.id === inputapprove.id);
12475
+ if (!record2) throw new Error("No debugger consent prompt matches the id.");
12476
+ const decided = { ...record2, approved: true, usedat: Date.now() };
12477
+ await memory.setdebuggergrant(decided);
12478
+ await audit("debugger", `Debugger consent on ${record2.origin} approved from the review panel with the domain allowlist ${record2.domains.join(", ")} shown; the decision covers those domains only.`, { stepid: record2.id });
12479
+ await refreshbadge();
12480
+ return { approved: true, origin: record2.origin, domains: record2.domains };
12481
+ }
12482
+ case "revokedebuggerconsent": {
12483
+ const session = await memory.getsession();
12484
+ const plan = await memory.getplan();
12485
+ const origin = session?.origin;
12486
+ if (!origin) throw new Error("No active session origin covers a debugger consent revoke.");
12487
+ const revoked = await memory.revokedebuggergrants(origin, Date.now());
12488
+ if (plan) await detachcdpforrun(plan.id, "the user detached the debugger", true);
12489
+ if (session) await memory.setsession({ ...session, pausedat: session.pausedat ?? Date.now() });
12490
+ await audit("debugger", `The user detached the debugger: ${revoked} consent record${revoked === 1 ? "" : "s"} of ${origin} revoked, every breakpoint and override reverted, the session record stays alive for review and the run paused for review before continuing.`, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {} });
12491
+ await refreshbadge();
12492
+ return { revoked, paused: true };
12493
+ }
12494
+ case "revertcdpoverride": {
12495
+ const inputrevert = message;
12496
+ const plan = await memory.getplan();
12497
+ if (!plan) throw new Error("No plan is available for a script override revert.");
12498
+ const active = activecdpsessions.get(plan.id);
12499
+ const stored = (await memory.getscriptoverrides()).find((spec) => spec.id === inputrevert.id);
12500
+ if (!stored) throw new Error(`No script override matches ${inputrevert.id ?? ""}.`);
12501
+ if (stored.revertedat !== void 0) throw new Error("The script override already reverted.");
12502
+ const reverted = { ...stored, revertedat: Date.now() };
12503
+ if (active) {
12504
+ const index = active.overrides.findIndex((spec) => spec.id === stored.id);
12505
+ if (index >= 0) active.overrides[index] = reverted;
12506
+ }
12507
+ await memory.addscriptoverride(reverted);
12508
+ await audit("debugger", `Reverted the script override ${reverted.id} of ${reverted.urlpattern} from the review panel; later evaluations run the original source again and the fixture source never entered the audit trail.`, { planid: plan.id, stepid: reverted.stepid });
12509
+ await refreshbadge();
12510
+ return { reverted: true, id: reverted.id };
12511
+ }
12512
+ case "cdpreport": {
12513
+ const plan = await memory.getplan();
12514
+ if (!plan) throw new Error("No plan is available for a devtools protocol envelope.");
12515
+ return cdpreport({ sessions: await memory.getcdpsessions(), commands: await memory.getcdpcommands(), events: await memory.getcdpeventrules(), breakpoints: await memory.getbreakpoints(), pauses: await memory.getpauses(), watches: await memory.getwatchexpressions(), overrides: await memory.getscriptoverrides(), grants: await memory.getdebuggergrants() });
12516
+ }
12517
+ case "profilereport": {
12518
+ return profilereport({ flows: await memory.getflowmetrics(), heaps: await memory.getheaprecords(), samples: await memory.getgrowsamples(), trends: await memory.gettrends(), profiles: await memory.getcpuprofiles(), shifts: await memory.getshiftentries(), traces: await memory.gettracerecords(), sourcemaps: await memory.getsourcemaps(), consents: await memory.getsourcemapconsents() });
12519
+ }
12520
+ case "approvesourcemapconsent": {
12521
+ const inputapprove = message;
12522
+ const records = await memory.getsourcemapconsents();
12523
+ const record2 = records.find((item) => item.id === inputapprove.id);
12524
+ if (!record2) throw new Error("No source map capture prompt matches the id.");
12525
+ const decided = { ...record2, approved: true, usedat: Date.now() };
12526
+ await memory.setsourcemapconsent(decided);
12527
+ await audit("consent", `Source map capture on ${record2.origin} approved from the review panel; the decision covers the map files of that origin only.`, { stepid: record2.id });
12528
+ await refreshbadge();
12529
+ return { approved: true, origin: record2.origin };
12530
+ }
12531
+ case "revokesourcemapconsent": {
12532
+ const session = await memory.getsession();
12533
+ const origin = session?.origin;
12534
+ if (!origin) throw new Error("No active session origin covers a source map consent revoke.");
12535
+ const revoked = await memory.revokesourcemapconsents(origin, Date.now());
12536
+ await audit("consent", `The user revoked ${revoked} source map capture consent record${revoked === 1 ? "" : "s"} of ${origin}; the next capture needs a new reviewed prompt.`, { ...session ? { sessionid: session.id } : {} });
12537
+ await refreshbadge();
12538
+ return { revoked };
12539
+ }
12540
+ case "exporttrace": {
12541
+ const session = await memory.getsession();
12542
+ if (!session || session.stoppedat || session.expiresat <= Date.now()) throw new Error("Trace exports stay behind the consent gate of an active session.");
12543
+ const inputtrace = message;
12544
+ const traceid = inputtrace.traceid?.trim();
12545
+ if (!traceid) throw new Error("Trace exports need the stored trace id.");
12546
+ const content = await memory.gettracefile(traceid);
12547
+ if (content === void 0) throw new Error("The trace file bytes expired after the retention window; only the metadata survives.");
12548
+ const granted = await chrome.permissions.contains({ permissions: ["downloads"] }).catch(() => false);
12549
+ if (!granted) throw new Error("The trace export needs the downloads capability; request it from the review panel.");
12550
+ const dataurl = `data:application/json;base64,${btoa(content)}`;
12551
+ await chrome.downloads.download({ url: dataurl, filename: `devthink-trace-${traceid}.json` });
12552
+ const stored = (await memory.gettracerecords()).find((trace) => trace.id === traceid);
12553
+ if (stored) await memory.settracerecord({ ...stored, exportedat: Date.now() });
12554
+ await audit("export", `The review panel exported the trace ${traceid} through the reviewed download flow with its ${stored?.annotations.length ?? 0} step annotation${(stored?.annotations.length ?? 0) === 1 ? "" : "s"}.`, { ...session ? { sessionid: session.id } : {} });
12555
+ return { exported: true, traceid };
12556
+ }
12557
+ case "tracereplay": {
12558
+ const inputreplay = message;
12559
+ const traceid = inputreplay.traceid?.trim();
12560
+ if (!traceid) throw new Error("The trace replay needs the stored trace id.");
12561
+ const stored = (await memory.gettracerecords()).find((trace) => trace.id === traceid);
12562
+ if (!stored) throw new Error(`No stored trace matches ${traceid}.`);
12563
+ const content = await memory.gettracefile(traceid);
12564
+ if (content === void 0) throw new Error("The trace file bytes expired after the retention window; only the metadata survives.");
12565
+ const replay = replaytrace(content);
12566
+ await audit("profile", `The review panel replayed the stored trace ${traceid} offline with ${replay.events.length} event${replay.events.length === 1 ? "" : "s"} grouped by category and step.`, { planid: stored.runid });
12567
+ return replay;
12568
+ }
12569
+ case "setprofileretention": {
12570
+ const inputretention = message;
12571
+ const settings = await memory.getsettings();
12572
+ const retention = typeof inputretention.retention === "number" && Number.isInteger(inputretention.retention) && inputretention.retention >= 0 ? inputretention.retention : void 0;
12573
+ await memory.setsettings({ ...settings, ...retention !== void 0 ? { profileretention: retention } : {} });
12574
+ await audit("configure", `The user set the profile retention to ${retention === void 0 ? "keep every heavy artifact" : `${retention} millisecond${retention === 1 ? "" : "s"}`}; the byte and node counts, sample counts, hot functions and step annotations always survive.`);
12575
+ return { profileretention: retention };
12576
+ }
12577
+ case "settraceceiling": {
12578
+ const inputceiling = message;
12579
+ const settings = await memory.getsettings();
12580
+ const ceiling = typeof inputceiling.ceiling === "number" && Number.isInteger(inputceiling.ceiling) && inputceiling.ceiling >= 0 ? inputceiling.ceiling : void 0;
12581
+ await memory.setsettings({ ...settings, ...ceiling !== void 0 ? { traceceiling: ceiling } : {} });
12582
+ await audit("configure", `The user set the trace byte ceiling to ${ceiling === void 0 ? "no ceiling" : `${ceiling} byte${ceiling === 1 ? "" : "s"}`}; an absent value never refuses a trace export.`);
12583
+ return { traceceiling: ceiling };
12584
+ }
10935
12585
  case "stop": {
10936
12586
  const session = await memory.getsession();
10937
12587
  for (const [id, controller] of [...activefetches.entries()]) {
@@ -10953,12 +12603,23 @@ async function handlerequest(message, sender) {
10953
12603
  });
10954
12604
  await revertcontrolsforrun(stoppedplan.id, "run cancel").catch(() => {
10955
12605
  });
12606
+ await detachcdpforrun(stoppedplan.id, "run cancel").catch(() => {
12607
+ });
12608
+ await stopprofileinstrumentsforrun(stoppedplan.id, "run cancel").catch(() => {
12609
+ });
10956
12610
  } else {
10957
12611
  await closechannelsforrun("none").catch(() => {
10958
12612
  });
10959
12613
  await revertcontrolsforrun("none", "run cancel").catch(() => {
10960
12614
  });
10961
12615
  }
12616
+ for (const [runid, active] of [...activecdpsessions.entries()]) {
12617
+ active.cancelled = true;
12618
+ await detachcdpforrun(runid, "run cancel").catch(() => {
12619
+ });
12620
+ await stopprofileinstrumentsforrun(runid, "run cancel").catch(() => {
12621
+ });
12622
+ }
10962
12623
  for (const [id, active] of [...activerecordings.entries()]) {
10963
12624
  const finished = finishrecording(active.record, Date.now());
10964
12625
  await memory.addmedia(finished).catch(() => {