@wenathlan/extension 1.1.46 → 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) {
@@ -1241,6 +1467,133 @@ var sessionmemory = class {
1241
1467
  async getlevelsummaries() {
1242
1468
  return await this.adapter.get("levelsummaries") ?? [];
1243
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
+ }
1244
1597
  };
1245
1598
  function mediakindof(record2) {
1246
1599
  if ("pages" in record2) return "pdf";
@@ -2460,9 +2813,9 @@ function consolediff(input) {
2460
2813
  }
2461
2814
 
2462
2815
  // policy.ts
2463
- 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"]);
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"]);
2464
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"]);
2465
- 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"]);
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"]);
2466
2819
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
2467
2820
  var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
2468
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"]);
@@ -2480,6 +2833,7 @@ var netwatchactions = /* @__PURE__ */ new Set(["watchrequests", "readheaders", "
2480
2833
  var controlactions = /* @__PURE__ */ new Set(["blockrequest", "mockresponse", "rewriteheaders", "setcookies", "readcookies", "clearcookies", "authflow", "saveapikey", "routeproxy", "postform", "postfiles"]);
2481
2834
  var debugactions = /* @__PURE__ */ new Set(["watchconsole", "watcherrors", "watchtasks"]);
2482
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"]);
2483
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"]);
2484
2838
  var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
2485
2839
  var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
@@ -2501,6 +2855,9 @@ function isdebugkind(kind) {
2501
2855
  function iscdpkind(kind) {
2502
2856
  return cdpactions.has(kind);
2503
2857
  }
2858
+ function isprofilekind(kind) {
2859
+ return profileractions.has(kind);
2860
+ }
2504
2861
  function actionrisk(kind) {
2505
2862
  if (!allowedactions.has(kind)) throw new Error("Unsupported browser action.");
2506
2863
  if (sensitiveactions.has(kind)) return "sensitive";
@@ -3816,6 +4173,28 @@ function debuggerconsentcovers(origin, domains, grants) {
3816
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.` };
3817
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.` };
3818
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
+ }
3819
4198
  function validatebreakpointcondition(condition) {
3820
4199
  const expression = condition.trim();
3821
4200
  if (expression.length === 0) return { allowed: false, reason: "The breakpoint condition must not be empty." };
@@ -3908,6 +4287,66 @@ function validatecdpgrammar(step, options) {
3908
4287
  }
3909
4288
  return { allowed: true };
3910
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
+ }
3911
4350
  function planallowlist(steps) {
3912
4351
  const attach = steps.find((step) => step.kind === "attachcdp");
3913
4352
  if (!attach) return void 0;
@@ -4339,6 +4778,10 @@ function validatestep(step, origin) {
4339
4778
  const cdpcheck = validatecdpgrammar(step, options);
4340
4779
  if (!cdpcheck.allowed) return cdpcheck;
4341
4780
  }
4781
+ if (isprofilekind(step.kind)) {
4782
+ const profilecheck = validateprofilegrammar(step, options);
4783
+ if (!profilecheck.allowed) return profilecheck;
4784
+ }
4342
4785
  if (step.kind === "tabcreate") {
4343
4786
  if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
4344
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." };
@@ -4472,15 +4915,39 @@ function canexecute(input) {
4472
4915
  if (input.step.kind === "setbreakpoint") {
4473
4916
  const breakpoint = breakpointinputof(cdpoptions.breakpoint);
4474
4917
  if (breakpoint) {
4475
- const targetgate = origincheck(input.session, breakpoint.url);
4476
- if (!targetgate.allowed) return targetgate;
4918
+ const targetgate2 = origincheck(input.session, breakpoint.url);
4919
+ if (!targetgate2.allowed) return targetgate2;
4477
4920
  }
4478
4921
  }
4479
4922
  if (input.step.kind === "overridescript") {
4480
4923
  const override = overrideinputof(cdpoptions.override);
4481
4924
  if (override) {
4482
- const targetgate = origincheck(input.session, override.urlpattern);
4483
- if (!targetgate.allowed) return targetgate;
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;
4484
4951
  }
4485
4952
  }
4486
4953
  }
@@ -4529,8 +4996,8 @@ function canexecute(input) {
4529
4996
  }
4530
4997
  const target = controltarget(input.step);
4531
4998
  if (target !== void 0) {
4532
- const targetgate = origincheck(input.session, target);
4533
- if (!targetgate.allowed) return targetgate;
4999
+ const targetgate2 = origincheck(input.session, target);
5000
+ if (!targetgate2.allowed) return targetgate2;
4534
5001
  }
4535
5002
  }
4536
5003
  if (input.step.kind === "extractapi") {
@@ -4723,9 +5190,15 @@ function recordcdp(progress, planid, stepid, entry, now) {
4723
5190
  const outcome = { stepid, ok: entry.errorclass === void 0, summary, details: { cdp: entry }, at: now };
4724
5191
  return recordoutcome(base, planid, outcome, now);
4725
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
+ }
4726
5199
 
4727
5200
  // version.ts
4728
- var packageversion = "1.1.46";
5201
+ var packageversion = "1.1.47";
4729
5202
 
4730
5203
  // types.ts
4731
5204
  var protocolversion = packageversion;
@@ -4844,6 +5317,59 @@ function parseproposal(value, origin, grants) {
4844
5317
  if (!allowlistcovers(planallowlist2, method)) throw new Error(`The raw command ${method} stays outside the enabled domain allowlist of the plan attach.`);
4845
5318
  }
4846
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
+ }
4847
5373
  const evaluation = validatestep(step, origin);
4848
5374
  if (!evaluation.allowed) throw new Error(evaluation.reason);
4849
5375
  const target = outboundtarget(step);
@@ -4918,7 +5444,7 @@ function requestbody(input) {
4918
5444
  return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
4919
5445
  }
4920
5446
  function outcomeresponse(input) {
4921
- 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 } : {} });
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 } : {} });
4922
5448
  }
4923
5449
  function mapresponse(input) {
4924
5450
  return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
@@ -5033,6 +5559,14 @@ function cdpreport(input) {
5033
5559
  });
5034
5560
  return { version: protocolversion, sessions: input.sessions, commands: input.commands, events: input.events, breakpoints: input.breakpoints, pauses: input.pauses, watches: input.watches, overrides, grants };
5035
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
+ }
5036
5570
 
5037
5571
  // capture.ts
5038
5572
  var capturekinds = ["shotview", "shotfullpage", "shotelement", "shotregion", "contactsheet"];
@@ -6721,7 +7255,7 @@ function stepoptions2(step) {
6721
7255
  }
6722
7256
  async function refreshcapabilities() {
6723
7257
  const report = await readcapabilities();
6724
- const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds, ...cdpkinds] };
7258
+ const withmedia = { ...report, captures: [...capturekinds], media: [...mediakinds], http: [...httpkinds], netwatch: [...socketkinds, ...netwatchkinds], control: [...controlkinds], debug: [...timelinekinds, ...cdpkinds], profile: [...profilerkinds] };
6725
7259
  await memory.setcapabilities(withmedia);
6726
7260
  return withmedia;
6727
7261
  }
@@ -6877,6 +7411,7 @@ function stepauditkind(step, ok) {
6877
7411
  if (step.kind === "consentpassword") return "consent";
6878
7412
  if (step.kind === "handoffcaptcha") return "handoff";
6879
7413
  if (iscapturekind(step.kind)) return "capture";
7414
+ if (isprofilekind(step.kind)) return "profile";
6880
7415
  if (ismediakind(step.kind)) return "media";
6881
7416
  if (isfileskind(step.kind)) {
6882
7417
  if (step.kind === "interceptmime") return "intercept";
@@ -7159,6 +7694,8 @@ async function tracktabupdate(tabid2, changeinfo) {
7159
7694
  if (active.session.detachedat !== void 0 || active.session.tabid !== tabid2) continue;
7160
7695
  await detachcdpforrun(runid, `the run tab navigated to ${url}`).catch(() => {
7161
7696
  });
7697
+ await stopprofileinstrumentsforrun(runid, `the run tab navigated to ${url}`).catch(() => {
7698
+ });
7162
7699
  }
7163
7700
  return;
7164
7701
  }
@@ -10001,11 +10538,24 @@ async function executecdpstep(step, session, plan, tabid2, origin) {
10001
10538
  if (!output.ok) return output;
10002
10539
  const record2 = attachcdpsession({ id: sessionid, runid: plan.id, stepid: step.id, tabid: tabid2, origin, domains, now: Date.now(), debuggerversion: cdpderivation });
10003
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
+ }
10004
10554
  await memory.setcdpsession(record2);
10005
10555
  await memory.setprogress(recordcdp(await memory.getprogress(), plan.id, step.id, { family: "attach", domains: domains.length }, Date.now()));
10006
- 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.`, extra);
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);
10007
10557
  await refreshbadge();
10008
- return { ok: true, summary: output.summary, details: { ...output.details ?? {}, session: record2, cdp: { sessionid, state: "attached", commandids: [] } } };
10558
+ return { ok: true, summary: output.summary, details: { ...output.details ?? {}, session: record2, ...flattened.length > 0 ? { targets: flattened } : {}, cdp: { sessionid, state: "attached", commandids: [] } } };
10009
10559
  }
10010
10560
  if (step.kind === "detachcdp") {
10011
10561
  if (!active) throw new Error("No attached devtools session covers this run.");
@@ -10147,6 +10697,275 @@ async function executecdpstep(step, session, plan, tabid2, origin) {
10147
10697
  }
10148
10698
  return { ok: false, summary: "The devtools step is not part of the instrumented family." };
10149
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
+ }
10150
10969
  var activerules = /* @__PURE__ */ new Map();
10151
10970
  var activeauthflows = /* @__PURE__ */ new Map();
10152
10971
  function rulesetof(runid) {
@@ -10616,6 +11435,9 @@ async function executestep(stepid) {
10616
11435
  } else if (iscdpkind(step.kind)) {
10617
11436
  if (!session || !plan || plan.state !== "approved") throw new Error("Devtools protocol kinds refuse to run outside an approved session plan.");
10618
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);
10619
11441
  } else {
10620
11442
  if (step.target && freshcheckkinds.has(step.kind)) {
10621
11443
  const fresh = await snapshot(tab.id);
@@ -10662,6 +11484,9 @@ async function executestep(stepid) {
10662
11484
  const completed = watchwindow ? recordwatchcompletion(base, plan.id, stepid, watchwindow.startedat, watchwindow.lifetime, Date.now()) : recordstep(base, plan.id, stepid, Date.now());
10663
11485
  const tracked = recordoutcome(completed, plan.id, outcome, Date.now());
10664
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
+ });
10665
11490
  await updatetaskbadges(plan, tracked);
10666
11491
  await refreshbadge();
10667
11492
  if (iscomplete(tracked, plan) && plan.state === "approved") {
@@ -10675,6 +11500,8 @@ async function executestep(stepid) {
10675
11500
  });
10676
11501
  await detachcdpforrun(plan.id, "plan completion").catch(() => {
10677
11502
  });
11503
+ await stopprofileinstrumentsforrun(plan.id, "plan completion").catch(() => {
11504
+ });
10678
11505
  const done = { ...plan, state: "completed", completedat: Date.now() };
10679
11506
  await memory.setplan(done);
10680
11507
  await audit("complete", "Every reviewed step of the approved plan has executed.", { ...session ? { sessionid: session.id } : {}, planid: done.id });
@@ -10851,7 +11678,7 @@ async function handlerequest(message, sender) {
10851
11678
  const livetab = session ? await chrome.tabs.get(session.tabid).catch(() => void 0) : void 0;
10852
11679
  const waitprofile = session ? waitprofiles.find((record2) => record2.origin === session.origin) : void 0;
10853
11680
  const livestate = { phase: livetab?.status === "loading" ? "loading" : "complete", ...navrecords[0] ? { finalurl: navrecords[0].finalurl, redirects: navrecords[0].chain } : {} };
10854
- 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, 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()] } : {} };
10855
11682
  }
10856
11683
  case "capabilities":
10857
11684
  return refreshcapabilities();
@@ -11687,6 +12514,74 @@ async function handlerequest(message, sender) {
11687
12514
  if (!plan) throw new Error("No plan is available for a devtools protocol envelope.");
11688
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() });
11689
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
+ }
11690
12585
  case "stop": {
11691
12586
  const session = await memory.getsession();
11692
12587
  for (const [id, controller] of [...activefetches.entries()]) {
@@ -11710,6 +12605,8 @@ async function handlerequest(message, sender) {
11710
12605
  });
11711
12606
  await detachcdpforrun(stoppedplan.id, "run cancel").catch(() => {
11712
12607
  });
12608
+ await stopprofileinstrumentsforrun(stoppedplan.id, "run cancel").catch(() => {
12609
+ });
11713
12610
  } else {
11714
12611
  await closechannelsforrun("none").catch(() => {
11715
12612
  });
@@ -11720,6 +12617,8 @@ async function handlerequest(message, sender) {
11720
12617
  active.cancelled = true;
11721
12618
  await detachcdpforrun(runid, "run cancel").catch(() => {
11722
12619
  });
12620
+ await stopprofileinstrumentsforrun(runid, "run cancel").catch(() => {
12621
+ });
11723
12622
  }
11724
12623
  for (const [id, active] of [...activerecordings.entries()]) {
11725
12624
  const finished = finishrecording(active.record, Date.now());