@wenathlan/extension 1.1.31 → 1.1.33

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,36 +1,1162 @@
1
1
  "use strict";
2
2
  (() => {
3
- // extension/pagebridge.ts
4
- function bounded(value, length = 180) {
5
- return value.replace(/\s+/g, " ").trim().slice(0, length);
3
+ // policy.ts
4
+ 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"]);
5
+ var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
6
+ 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"]);
7
+ var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
8
+ function parseoptions(step) {
9
+ if (step.options === void 0) return {};
10
+ let parsed;
11
+ try {
12
+ parsed = JSON.parse(step.options);
13
+ } catch {
14
+ throw new Error("Step options must be a JSON object.");
15
+ }
16
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Step options must be a JSON object.");
17
+ return parsed;
18
+ }
19
+ function resolutionverdict(count) {
20
+ if (!Number.isFinite(count) || count <= 0) return "absent";
21
+ return count === 1 ? "resolved" : "ambiguous";
22
+ }
23
+
24
+ // extension/pageactions.ts
25
+ function events(target) {
26
+ target.dispatchEvent(new Event("input", { bubbles: true }));
27
+ target.dispatchEvent(new Event("change", { bubbles: true }));
28
+ }
29
+ function modifiers(options) {
30
+ return Array.isArray(options.modifiers) ? options.modifiers.filter((item) => typeof item === "string") : [];
31
+ }
32
+ function keyevent(type, key, mods) {
33
+ const code = key.length === 1 ? `Key${key.toUpperCase()}` : key;
34
+ return new KeyboardEvent(type, { key, code, bubbles: true, cancelable: true, composed: true, ctrlKey: mods.includes("ctrl"), shiftKey: mods.includes("shift"), altKey: mods.includes("alt"), metaKey: mods.includes("meta") });
35
+ }
36
+ function fieldlike(target) {
37
+ return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement ? target : null;
38
+ }
39
+ function stringify(value) {
40
+ try {
41
+ return JSON.parse(JSON.stringify(value)) ?? null;
42
+ } catch {
43
+ return String(value);
44
+ }
45
+ }
46
+ function runpageaction(step, target) {
47
+ const options = (() => {
48
+ try {
49
+ return parseoptions(step);
50
+ } catch {
51
+ return {};
52
+ }
53
+ })();
54
+ switch (step.kind) {
55
+ case "presskey": {
56
+ const receiver2 = target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
57
+ const key = step.value ?? "";
58
+ const mods = modifiers(options);
59
+ receiver2.dispatchEvent(keyevent("keydown", key, mods));
60
+ receiver2.dispatchEvent(keyevent("keypress", key, mods));
61
+ receiver2.dispatchEvent(keyevent("keyup", key, mods));
62
+ return { ok: true, summary: `Key ${key} delivered with ${mods.length} modifier${mods.length === 1 ? "" : "s"}.` };
63
+ }
64
+ case "clickdeep": {
65
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
66
+ target.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true, composed: true }));
67
+ target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
68
+ target.dispatchEvent(new PointerEvent("pointerup", { bubbles: true, cancelable: true, composed: true }));
69
+ target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }));
70
+ target.click();
71
+ return { ok: true, summary: "Full pointer click sequence delivered." };
72
+ }
73
+ case "rightclick": {
74
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
75
+ const init = { bubbles: true, cancelable: true, button: 2, buttons: 2 };
76
+ target.dispatchEvent(new PointerEvent("pointerdown", { ...init, composed: true }));
77
+ target.dispatchEvent(new MouseEvent("mousedown", init));
78
+ target.dispatchEvent(new MouseEvent("contextmenu", init));
79
+ return { ok: true, summary: "Context menu events delivered." };
80
+ }
81
+ case "doubleclick": {
82
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
83
+ target.click();
84
+ target.click();
85
+ target.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, cancelable: true, detail: 2 }));
86
+ return { ok: true, summary: "Double click sequence delivered." };
87
+ }
88
+ case "drag": {
89
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Drag source is no longer available." };
90
+ const destination = document.querySelector(step.value ?? "");
91
+ if (!destination) return { ok: false, summary: "Drag destination is no longer available." };
92
+ const transfer = new DataTransfer();
93
+ if (typeof options.data === "string") transfer.setData("text/plain", options.data);
94
+ target.dispatchEvent(new DragEvent("dragstart", { bubbles: true, cancelable: true, dataTransfer: transfer }));
95
+ destination.dispatchEvent(new DragEvent("dragenter", { bubbles: true, cancelable: true, dataTransfer: transfer }));
96
+ destination.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true, dataTransfer: transfer }));
97
+ destination.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer }));
98
+ target.dispatchEvent(new DragEvent("dragend", { bubbles: true, cancelable: true, dataTransfer: transfer }));
99
+ return { ok: true, summary: "Drag and drop sequence delivered." };
100
+ }
101
+ case "drop": {
102
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Drop zone is no longer available." };
103
+ const transfer = new DataTransfer();
104
+ transfer.setData("text/plain", step.value ?? "");
105
+ target.dispatchEvent(new DragEvent("dragenter", { bubbles: true, cancelable: true, dataTransfer: transfer }));
106
+ target.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true, dataTransfer: transfer }));
107
+ target.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer }));
108
+ return { ok: true, summary: "Drop payload delivered." };
109
+ }
110
+ case "upload": {
111
+ if (!(target instanceof HTMLInputElement) || target.type !== "file") return { ok: false, summary: "Target is not a file input." };
112
+ const transfer = new DataTransfer();
113
+ transfer.items.add(new File([typeof options.content === "string" ? options.content : ""], step.value ?? "upload", { type: typeof options.type === "string" ? options.type : "text/plain" }));
114
+ target.files = transfer.files;
115
+ events(target);
116
+ return { ok: true, summary: `Uploaded ${step.value ?? "file"} into the reviewed input.` };
117
+ }
118
+ case "clear": {
119
+ const field = fieldlike(target);
120
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
121
+ field.value = "";
122
+ events(field);
123
+ return { ok: true, summary: "Field cleared." };
124
+ }
125
+ case "check":
126
+ case "uncheck":
127
+ case "toggle": {
128
+ if (!(target instanceof HTMLInputElement) || target.type !== "checkbox" && target.type !== "radio") return { ok: false, summary: "Target is not a checkbox or radio control." };
129
+ if (step.kind === "uncheck" && target.type === "radio") return { ok: false, summary: "A radio control cannot be unchecked." };
130
+ target.checked = step.kind === "toggle" ? !target.checked : step.kind === "check";
131
+ events(target);
132
+ return { ok: true, summary: `Control is now ${target.checked ? "checked" : "unchecked"}.` };
133
+ }
134
+ case "submit": {
135
+ const form = target instanceof HTMLFormElement ? target : target instanceof HTMLElement ? target.closest("form") : null;
136
+ if (!form) return { ok: false, summary: "No form owns the reviewed target." };
137
+ try {
138
+ form.requestSubmit(target instanceof HTMLFormElement ? void 0 : target);
139
+ } catch {
140
+ form.submit();
141
+ }
142
+ return { ok: true, summary: "Form submission requested." };
143
+ }
144
+ case "setattribute": {
145
+ if (!target) return { ok: false, summary: "Action target is no longer available." };
146
+ const name = typeof options.name === "string" ? options.name : "";
147
+ target.setAttribute(name, typeof options.value === "string" ? options.value : "");
148
+ return { ok: true, summary: `Attribute ${name} set.` };
149
+ }
150
+ case "removeattribute": {
151
+ if (!target) return { ok: false, summary: "Action target is no longer available." };
152
+ const name = step.value ?? "";
153
+ target.removeAttribute(name);
154
+ return { ok: true, summary: `Attribute ${name} removed.` };
155
+ }
156
+ case "writestorage": {
157
+ try {
158
+ localStorage.setItem(typeof options.key === "string" ? options.key : "", typeof options.value === "string" ? options.value : "");
159
+ return { ok: true, summary: `Local storage entry ${String(options.key)} written.` };
160
+ } catch (error) {
161
+ return { ok: false, summary: `Local storage refused the write: ${error instanceof Error ? error.message : String(error)}` };
162
+ }
163
+ }
164
+ case "evaluate": {
165
+ try {
166
+ let outcome;
167
+ try {
168
+ outcome = new Function(`"use strict"; return (${step.value ?? "undefined"});`)();
169
+ } catch {
170
+ outcome = new Function(`"use strict"; ${step.value ?? ""}`)();
171
+ }
172
+ return { ok: true, summary: `Reviewed expression returned ${outcome === void 0 ? "no value" : "a value"}.`, details: { result: stringify(outcome) } };
173
+ } catch (error) {
174
+ return { ok: false, summary: `Reviewed expression failed: ${error instanceof Error ? error.message : String(error)}` };
175
+ }
176
+ }
177
+ case "fullscreen": {
178
+ const element = target instanceof HTMLElement ? target : document.documentElement;
179
+ return document.fullscreenElement === element ? document.exitFullscreen().then(() => ({ ok: true, summary: "Fullscreen state cleared." })) : element.requestFullscreen().then(() => ({ ok: true, summary: "Fullscreen state entered." })).catch((error) => ({ ok: false, summary: `Fullscreen was refused: ${error instanceof Error ? error.message : String(error)}` }));
180
+ }
181
+ default:
182
+ return { ok: false, summary: "Unsupported page action." };
183
+ }
184
+ }
185
+
186
+ // extension/pagexpath.ts
187
+ function owntext(element) {
188
+ let combined = "";
189
+ for (const node of element.childNodes) if (node.nodeType === Node.TEXT_NODE) combined += node.textContent ?? "";
190
+ return combined.replace(/\s+/g, " ").trim();
191
+ }
192
+ function attributesof(element) {
193
+ const attributes = {};
194
+ for (const attribute of [...element.attributes]) attributes[attribute.name] = attribute.value;
195
+ return attributes;
196
+ }
197
+ function wrap(element) {
198
+ return { tag: element.tagName.toLowerCase(), attributes: attributesof(element), text: owntext(element), children: [...element.children].map(wrap), element };
199
+ }
200
+ function buildxtree(root) {
201
+ return { tag: "#document", attributes: {}, text: "", children: root.documentElement ? [wrap(root.documentElement)] : [] };
202
+ }
203
+ function parsepredicate(raw) {
204
+ const body = raw.trim();
205
+ let match = /^@([\w-]+)$/.exec(body);
206
+ if (match) return { kind: "attr", name: match[1] };
207
+ match = /^@([\w-]+)\s*=\s*['"]([^'"]*)['"]$/.exec(body);
208
+ if (match) return { kind: "attr", name: match[1], value: match[2] };
209
+ match = /^contains\(\s*@([\w-]+)\s*,\s*['"]([^'"]*)['"]\s*\)$/.exec(body);
210
+ if (match) return { kind: "attr", name: match[1], value: match[2], contains: true };
211
+ match = /^text\(\)\s*=\s*['"]([^'"]*)['"]$/.exec(body);
212
+ if (match) return { kind: "text", value: match[1] };
213
+ match = /^contains\(\s*text\(\)\s*,\s*['"]([^'"]*)['"]\s*\)$/.exec(body);
214
+ if (match) return { kind: "text", value: match[1], contains: true };
215
+ match = /^(\d+)$/.exec(body);
216
+ if (match) return { kind: "position", index: Number.parseInt(match[1], 10) };
217
+ return null;
6
218
  }
7
- function label(element) {
219
+ function parsexpath(expression) {
220
+ const trimmed = expression.trim();
221
+ if (!trimmed.startsWith("/")) throw new Error("The reviewed xpath expression must start with a slash.");
222
+ const steps = [];
223
+ let index = 0;
224
+ while (index < trimmed.length) {
225
+ if (trimmed[index] !== "/") throw new Error("The reviewed xpath expression contains an unsupported segment.");
226
+ let slashes = 0;
227
+ while (index < trimmed.length && trimmed[index] === "/") {
228
+ slashes += 1;
229
+ index += 1;
230
+ }
231
+ const start = index;
232
+ let quote = "";
233
+ while (index < trimmed.length) {
234
+ const character = trimmed[index];
235
+ if (quote) {
236
+ if (character === quote) quote = "";
237
+ } else if (character === "'" || character === '"') quote = character;
238
+ else if (character === "/") break;
239
+ index += 1;
240
+ }
241
+ const body = trimmed.slice(start, index);
242
+ if (!body) throw new Error("The reviewed xpath expression contains an empty step.");
243
+ const parsed = /^(\*|[a-zA-Z][\w-]*)((?:\[[^\]]*\])*)$/.exec(body);
244
+ if (!parsed) throw new Error(`The reviewed xpath step ${body} is not supported.`);
245
+ const predicates = [];
246
+ const pattern = /\[([^\]]*)\]/g;
247
+ let predicate;
248
+ while ((predicate = pattern.exec(parsed[2] ?? "")) !== null) {
249
+ const parsedpredicate = parsepredicate(predicate[1]);
250
+ if (!parsedpredicate) throw new Error(`The reviewed xpath predicate [${predicate[1]}] is not supported.`);
251
+ predicates.push(parsedpredicate);
252
+ }
253
+ steps.push({ descendant: slashes > 1, tag: parsed[1].toLowerCase(), predicates });
254
+ }
255
+ return steps;
256
+ }
257
+ function descendants(node, includeself) {
258
+ const result = includeself ? [node] : [];
259
+ for (const child of node.children) {
260
+ result.push(child);
261
+ result.push(...descendants(child, false));
262
+ }
263
+ return result;
264
+ }
265
+ function applypredicates(nodes, predicates) {
266
+ let result = nodes;
267
+ for (const predicate of predicates) {
268
+ if (predicate.kind === "position") {
269
+ const entry = result[predicate.index - 1];
270
+ result = entry ? [entry] : [];
271
+ continue;
272
+ }
273
+ result = result.filter((node) => {
274
+ if (predicate.kind === "attr") {
275
+ const value = node.attributes[predicate.name];
276
+ if (value === void 0) return false;
277
+ if (predicate.value === void 0) return true;
278
+ return predicate.contains ? value.includes(predicate.value) : value === predicate.value;
279
+ }
280
+ return predicate.contains ? node.text.includes(predicate.value) : node.text === predicate.value;
281
+ });
282
+ }
283
+ return result;
284
+ }
285
+ function evaluatexpath(root, expression) {
286
+ const steps = parsexpath(expression);
287
+ let current = [root];
288
+ let first = true;
289
+ for (const step of steps) {
290
+ let matched = [];
291
+ for (const node of current) {
292
+ const pool = step.descendant ? descendants(node, first) : node.children;
293
+ matched = matched.concat(pool.filter((candidate) => candidate.tag === step.tag || step.tag === "*"));
294
+ }
295
+ current = applypredicates(matched, step.predicates);
296
+ first = false;
297
+ }
298
+ return current;
299
+ }
300
+
301
+ // extension/pageresolve.ts
302
+ function clean(value) {
303
+ return value.replace(/\s+/g, " ").trim();
304
+ }
305
+ function cssescape(value) {
306
+ return typeof CSS !== "undefined" && typeof CSS.escape === "function" ? CSS.escape(value) : value.replace(/[^a-zA-Z0-9_-]/g, "\\$&");
307
+ }
308
+ function elementlabel(element) {
8
309
  const aria = element.getAttribute("aria-label");
310
+ let linked = "";
9
311
  const labelledby = element.getAttribute("aria-labelledby");
10
- const linked = labelledby ? document.getElementById(labelledby)?.textContent : "";
11
- return bounded(aria || linked || element.getAttribute("title") || element.textContent || "");
312
+ if (labelledby) {
313
+ try {
314
+ const owner = element.ownerDocument?.getElementById(labelledby);
315
+ if (owner) linked = owner.textContent ?? "";
316
+ } catch {
317
+ }
318
+ }
319
+ let forlabel = "";
320
+ if (element.id) {
321
+ try {
322
+ const label = element.ownerDocument?.querySelector(`label[for="${cssescape(element.id)}"]`);
323
+ if (label instanceof HTMLElement) forlabel = label.textContent ?? "";
324
+ } catch {
325
+ }
326
+ }
327
+ return clean(aria || linked || forlabel || element.getAttribute("title") || element.textContent || "");
12
328
  }
13
- function selector(element) {
14
- if (element.id) return `#${CSS.escape(element.id)}`;
329
+ function implicitrole(element) {
330
+ const tag = element.tagName.toLowerCase();
331
+ if (tag === "button") return "button";
332
+ if (tag === "a" && element.getAttribute("href")) return "link";
333
+ if (tag === "select") return "combobox";
334
+ if (tag === "textarea") return "textbox";
335
+ if (tag === "details") return "group";
336
+ if (tag === "input") {
337
+ const type = element.getAttribute("type") ?? "text";
338
+ if (type === "checkbox") return "checkbox";
339
+ if (type === "radio") return "radio";
340
+ if (type === "button" || type === "submit" || type === "reset") return "button";
341
+ if (type === "range") return "slider";
342
+ return "textbox";
343
+ }
344
+ return "";
345
+ }
346
+ function elementselector(element) {
347
+ if (element.id) return `#${cssescape(element.id)}`;
15
348
  const role = element.getAttribute("role");
16
349
  const name = element.getAttribute("name");
17
- if (role && name) return `[role="${CSS.escape(role)}"][name="${CSS.escape(name)}"]`;
18
- if (name) return `${element.tagName.toLowerCase()}[name="${CSS.escape(name)}"]`;
350
+ if (role && name) return `[role="${cssescape(role)}"][name="${cssescape(name)}"]`;
351
+ if (name) return `${element.tagName.toLowerCase()}[name="${cssescape(name)}"]`;
19
352
  const tag = element.tagName.toLowerCase();
20
353
  const parent = element.parentElement;
21
354
  if (!parent) return tag;
22
355
  const peers = [...parent.children].filter((node) => node.tagName === element.tagName);
23
356
  return `${tag}:nth-of-type(${peers.indexOf(element) + 1})`;
24
357
  }
358
+ function owntext2(element) {
359
+ let combined = "";
360
+ for (const node of element.childNodes) if (node.nodeType === Node.TEXT_NODE) combined += node.textContent ?? "";
361
+ return clean(combined);
362
+ }
363
+ function summarize(element) {
364
+ return {
365
+ tag: element.tagName.toLowerCase(),
366
+ id: element.id,
367
+ role: element.getAttribute("role")?.toLowerCase() || implicitrole(element),
368
+ name: element.getAttribute("name") ?? "",
369
+ label: elementlabel(element),
370
+ text: owntext2(element),
371
+ selector: elementselector(element),
372
+ element
373
+ };
374
+ }
375
+ var clickableselector = "a[href], button, input, textarea, select, summary, [role=button], [role=link], [role=combobox], [role=option], [role=checkbox], [role=radio], [role=switch], [role=tab]";
376
+ function collectclickable(root) {
377
+ return [...root.querySelectorAll(clickableselector)].map(summarize);
378
+ }
379
+ function collectcandidates(root) {
380
+ return [...root.querySelectorAll("*")].map(summarize);
381
+ }
382
+ function matchtext(candidates, text) {
383
+ const wanted = clean(text).toLowerCase();
384
+ if (!wanted) return [];
385
+ const exact = candidates.filter((candidate) => candidate.text.toLowerCase() === wanted || candidate.label.toLowerCase() === wanted);
386
+ if (exact.length > 0) return exact;
387
+ return candidates.filter((candidate) => candidate.text.toLowerCase().includes(wanted) || candidate.label.toLowerCase().includes(wanted));
388
+ }
389
+ function matcharia(candidates, role, name) {
390
+ const wantedrole = clean(role).toLowerCase();
391
+ const wantedname = clean(name).toLowerCase();
392
+ if (!wantedrole || !wantedname) return [];
393
+ return candidates.filter((candidate) => candidate.role.toLowerCase() === wantedrole && (candidate.label.toLowerCase() === wantedname || candidate.name.toLowerCase() === wantedname));
394
+ }
395
+ function matchname(candidates, name, clickable) {
396
+ const wanted = clean(name).toLowerCase();
397
+ if (!wanted) return [];
398
+ const matches = candidates.filter((candidate) => candidate.label.toLowerCase() === wanted || candidate.name.toLowerCase() === wanted);
399
+ if (matches.length > 1 && clickable) {
400
+ const interactive = matches.filter(clickable);
401
+ if (interactive.length === 1) return interactive;
402
+ }
403
+ return matches;
404
+ }
405
+ function matchindex(candidates, index) {
406
+ if (!Number.isInteger(index) || index < 1) return [];
407
+ const entry = candidates[index - 1];
408
+ return entry ? [entry] : [];
409
+ }
410
+ function buildclickablemap(candidates, version, builtat = 0) {
411
+ const entries = candidates.map((candidate, position) => ({ number: position + 1, selector: candidate.selector, role: candidate.role || candidate.tag, label: candidate.label, mode: "selector" }));
412
+ return { version, entries, builtat };
413
+ }
414
+ function describeframes(root) {
415
+ const frames = [...root.querySelectorAll("iframe")].map((frame) => {
416
+ let content = null;
417
+ try {
418
+ content = frame.contentDocument;
419
+ } catch {
420
+ content = null;
421
+ }
422
+ let sameorigin = false;
423
+ try {
424
+ sameorigin = content !== null && frame.contentWindow?.location.origin === location.origin;
425
+ } catch {
426
+ sameorigin = false;
427
+ }
428
+ return sameorigin && content ? { sameorigin: true, document: describeframes(content) } : { sameorigin: false };
429
+ });
430
+ return { frames, live: root };
431
+ }
432
+ function walkframepath(root, path) {
433
+ let current = root;
434
+ for (const index of path) {
435
+ if (!Number.isInteger(index) || index < 0) return { ok: false, reason: "The reviewed frame path contains an invalid frame index." };
436
+ const entry = current.frames[index];
437
+ if (!entry) return { ok: false, reason: `Frame ${index} of the reviewed frame path is absent.` };
438
+ if (!entry.sameorigin || !entry.document) return { ok: false, reason: `Frame ${index} of the reviewed frame path is cross origin and was refused.` };
439
+ current = entry.document;
440
+ }
441
+ return { ok: true, document: current };
442
+ }
443
+ function queryshadowchain(root, selectors) {
444
+ let scope = root;
445
+ for (let position = 0; position < selectors.length; position += 1) {
446
+ const found = scope.querySelector(selectors[position]);
447
+ if (!found) return null;
448
+ if (position === selectors.length - 1) return found;
449
+ const shadow = found.shadowRoot;
450
+ if (!shadow) return null;
451
+ scope = shadow;
452
+ }
453
+ return null;
454
+ }
455
+ function queryscoped(root, selector) {
456
+ const direct = root.querySelector(selector);
457
+ if (direct) return direct;
458
+ for (const element of [...root.querySelectorAll("*")]) {
459
+ const shadow = element.shadowRoot;
460
+ if (shadow) {
461
+ const found = queryscoped(shadow, selector);
462
+ if (found) return found;
463
+ }
464
+ }
465
+ return null;
466
+ }
467
+ function parseoptionssafe(step) {
468
+ try {
469
+ return parseoptions(step);
470
+ } catch {
471
+ return {};
472
+ }
473
+ }
474
+ function targetsummary(mode, element) {
475
+ const rect = element.getBoundingClientRect();
476
+ return { mode, selector: elementselector(element), tag: element.tagName.toLowerCase(), label: elementlabel(element), geometry: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } };
477
+ }
478
+ function singleresolution(mode, matches) {
479
+ const verdict = resolutionverdict(matches.length);
480
+ if (verdict === "resolved") {
481
+ const winner = matches[0];
482
+ if (winner && winner.element instanceof HTMLElement) return { status: "resolved", element: winner.element, target: targetsummary(mode, winner.element) };
483
+ return { status: "absent", mode };
484
+ }
485
+ if (verdict === "ambiguous") return { status: "ambiguous", mode, candidates: matches.slice(0, 8).map((candidate) => candidate.label || candidate.selector) };
486
+ return { status: "absent", mode };
487
+ }
488
+ function resolvetargetref(reference, root) {
489
+ const mode = reference.mode;
490
+ if (mode === "selector") {
491
+ const selector = typeof reference.selector === "string" ? reference.selector : "";
492
+ const element = selector ? root.querySelector(selector) : null;
493
+ return element instanceof HTMLElement ? { status: "resolved", element, target: targetsummary("selector", element) } : { status: "absent", mode: "selector" };
494
+ }
495
+ if (mode === "point") {
496
+ const x = Number(reference.x);
497
+ const y = Number(reference.y);
498
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return { status: "absent", mode: "point" };
499
+ const element = root.elementFromPoint(x, y);
500
+ return element instanceof HTMLElement ? { status: "resolved", element, target: targetsummary("point", element) } : { status: "absent", mode: "point" };
501
+ }
502
+ if (mode === "xpath") {
503
+ const expression = typeof reference.xpath === "string" ? reference.xpath : "";
504
+ if (!expression) return { status: "absent", mode: "xpath" };
505
+ const matches = evaluatexpath(buildxtree(root), expression);
506
+ const first = matches[0];
507
+ return first?.element instanceof HTMLElement ? { status: "resolved", element: first.element, target: targetsummary("xpath", first.element) } : { status: "absent", mode: "xpath" };
508
+ }
509
+ if (mode === "index") {
510
+ const matches = matchindex(collectclickable(root), Number(reference.index));
511
+ return singleresolution("index", matches);
512
+ }
513
+ const candidates = collectcandidates(root);
514
+ if (mode === "text") return singleresolution("text", matchtext(candidates, typeof reference.text === "string" ? reference.text : ""));
515
+ if (mode === "aria") return singleresolution("aria", matcharia(candidates, typeof reference.role === "string" ? reference.role : "", typeof reference.name === "string" ? reference.name : ""));
516
+ if (mode === "name") return singleresolution("name", matchname(candidates, typeof reference.name === "string" ? reference.name : ""));
517
+ return { status: "absent" };
518
+ }
519
+ function resolvestep(step, root) {
520
+ const reference = parseoptionssafe(step).targetref;
521
+ if (reference && typeof reference === "object" && !Array.isArray(reference)) return resolvetargetref(reference, root);
522
+ if (!step.target?.trim()) return { status: "none" };
523
+ const element = root.querySelector(step.target);
524
+ if (element instanceof HTMLElement) return { status: "resolved", element, target: targetsummary("selector", element) };
525
+ return { status: "absent", mode: "selector" };
526
+ }
527
+
528
+ // extension/pagereads.ts
529
+ var highlightid = "devthinkactionhighlight";
530
+ function clearhighlight() {
531
+ document.getElementById(highlightid)?.remove();
532
+ }
533
+ function highlighttarget(target) {
534
+ clearhighlight();
535
+ const rect = target.getBoundingClientRect();
536
+ const overlay = document.createElement("div");
537
+ overlay.id = highlightid;
538
+ overlay.setAttribute("aria-hidden", "true");
539
+ Object.assign(overlay.style, { position: "fixed", left: `${Math.max(0, rect.left - 3)}px`, top: `${Math.max(0, rect.top - 3)}px`, width: `${rect.width + 6}px`, height: `${rect.height + 6}px`, border: "3px solid #2f9e44", borderRadius: "6px", pointerEvents: "none", zIndex: "2147483647", boxSizing: "border-box" });
540
+ document.documentElement.append(overlay);
541
+ window.setTimeout(clearhighlight, 5e3);
542
+ return { ok: true, summary: "Target outlined for five seconds." };
543
+ }
544
+ function poll(root, predicate, description, timeout) {
545
+ return new Promise((resolve) => {
546
+ const started = Date.now();
547
+ const check = () => {
548
+ if (predicate()) {
549
+ resolve({ ok: true, summary: `${description} is now present on the page.` });
550
+ return;
551
+ }
552
+ if (timeout > 0 && Date.now() - started >= timeout) {
553
+ resolve({ ok: false, summary: `${description} did not appear within ${timeout} milliseconds.` });
554
+ return;
555
+ }
556
+ window.setTimeout(check, 100);
557
+ };
558
+ check();
559
+ });
560
+ }
561
+ function formstate(root) {
562
+ return [...root.querySelectorAll("input, textarea, select")].map((element) => ({
563
+ type: element.getAttribute("type") ?? element.tagName.toLowerCase(),
564
+ name: element.getAttribute("name") ?? "",
565
+ value: element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement ? element.value : "",
566
+ ...element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio") ? { checked: element.checked } : {}
567
+ }));
568
+ }
569
+ function runpageread(step, target, root = document) {
570
+ const options = (() => {
571
+ try {
572
+ return parseoptions(step);
573
+ } catch {
574
+ return {};
575
+ }
576
+ })();
577
+ switch (step.kind) {
578
+ case "highlight": {
579
+ if (!target) return { ok: false, summary: "Highlight target is no longer available." };
580
+ return highlighttarget(target);
581
+ }
582
+ case "readattribute": {
583
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
584
+ const value = target.getAttribute(step.value ?? "");
585
+ return value === null ? { ok: false, summary: `Attribute ${step.value} is absent.` } : { ok: true, summary: `Attribute ${step.value} read.`, details: { value } };
586
+ }
587
+ case "readstyle": {
588
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
589
+ const computed = getComputedStyle(target);
590
+ const styles = {};
591
+ for (let index = 0; index < computed.length; index += 1) {
592
+ const property = computed.item(index);
593
+ styles[property] = computed.getPropertyValue(property);
594
+ }
595
+ return { ok: true, summary: `Read ${computed.length} computed style properties.`, details: { styles } };
596
+ }
597
+ case "readgeometry": {
598
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
599
+ const rect = target.getBoundingClientRect();
600
+ const geometry = { x: rect.x, y: rect.y, width: rect.width, height: rect.height, top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left };
601
+ return { ok: true, summary: "Target geometry read.", details: { geometry } };
602
+ }
603
+ case "readvalue": {
604
+ if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) return { ok: false, summary: "Target does not hold a form value." };
605
+ return { ok: true, summary: "Form value read.", details: { value: target.value } };
606
+ }
607
+ case "readtext": {
608
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
609
+ const text = target.textContent ?? "";
610
+ return { ok: true, summary: `Read ${text.length} characters of text.`, details: { text } };
611
+ }
612
+ case "readhtml": {
613
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
614
+ return { ok: true, summary: "Target markup read.", details: { html: target.outerHTML } };
615
+ }
616
+ case "countelements": {
617
+ const count = root.querySelectorAll(step.target ?? "").length;
618
+ return { ok: true, summary: `Selector matches ${count} element${count === 1 ? "" : "s"}.`, details: { count } };
619
+ }
620
+ case "readtable": {
621
+ if (!(target instanceof HTMLTableElement)) return { ok: false, summary: "Read target is not a table element." };
622
+ const rows = [...target.querySelectorAll("tr")].map((row) => [...row.querySelectorAll("th, td")].map((cell) => cell.textContent?.trim() ?? ""));
623
+ const headers = rows[0] ?? [];
624
+ const body = rows.slice(1);
625
+ return { ok: true, summary: `Read table with ${headers.length} column${headers.length === 1 ? "" : "s"} and ${body.length} row${body.length === 1 ? "" : "s"}.`, details: { headers, rows: body } };
626
+ }
627
+ case "readlinks": {
628
+ const links = [...root.querySelectorAll("a[href]")].map((element) => ({ text: element.textContent?.trim() ?? "", href: element.getAttribute("href") ?? "" }));
629
+ return { ok: true, summary: `Read ${links.length} link${links.length === 1 ? "" : "s"}.`, details: { links } };
630
+ }
631
+ case "readimages": {
632
+ const images = [...root.querySelectorAll("img")].map((element) => ({ src: element.getAttribute("src") ?? "", alt: element.getAttribute("alt") ?? "" }));
633
+ return { ok: true, summary: `Read ${images.length} image${images.length === 1 ? "" : "s"}.`, details: { images } };
634
+ }
635
+ case "readmeta": {
636
+ const meta = [...root.querySelectorAll("meta")].map((element) => ({ name: element.getAttribute("name") ?? "", property: element.getAttribute("property") ?? "", content: element.getAttribute("content") ?? "" }));
637
+ return { ok: true, summary: `Read ${meta.length} meta entr${meta.length === 1 ? "y" : "ies"}.`, details: { meta } };
638
+ }
639
+ case "readforms": {
640
+ const forms = formstate(root);
641
+ return { ok: true, summary: `Read ${forms.length} form control${forms.length === 1 ? "" : "s"}.`, details: { forms } };
642
+ }
643
+ case "readstorage": {
644
+ try {
645
+ if (step.value) {
646
+ const value = localStorage.getItem(step.value);
647
+ return { ok: true, summary: `Read local storage entry ${step.value}.`, details: { value } };
648
+ }
649
+ const entries = {};
650
+ for (let index = 0; index < localStorage.length; index += 1) {
651
+ const key = localStorage.key(index);
652
+ if (key !== null) entries[key] = localStorage.getItem(key);
653
+ }
654
+ return { ok: true, summary: `Read ${Object.keys(entries).length} local storage entr${Object.keys(entries).length === 1 ? "y" : "ies"}.`, details: { entries } };
655
+ } catch (error) {
656
+ return { ok: false, summary: `Local storage refused the read: ${error instanceof Error ? error.message : String(error)}` };
657
+ }
658
+ }
659
+ case "waitfor": {
660
+ const selector = step.target ?? "";
661
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
662
+ return poll(root, () => Boolean(root.querySelector(selector)), `Selector ${selector}`, timeout);
663
+ }
664
+ case "waittext": {
665
+ const text = step.value ?? "";
666
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
667
+ return poll(root, () => (root.body?.innerText ?? "").includes(text), `Text ${text}`, timeout);
668
+ }
669
+ case "mapclicks": {
670
+ const candidates = collectclickable(root);
671
+ const map = buildclickablemap(candidates, 0, 0);
672
+ return { ok: true, summary: `Mapped ${map.entries.length} clickable element${map.entries.length === 1 ? "" : "s"}.`, details: { entries: map.entries } };
673
+ }
674
+ case "verifyvisible": {
675
+ if (!target) return { ok: false, summary: "Verify target is no longer available." };
676
+ const rect = target.getBoundingClientRect();
677
+ const rendered = rect.width > 0 && rect.height > 0;
678
+ return { ok: rendered, summary: rendered ? `Target is rendered at ${Math.round(rect.x)},${Math.round(rect.y)} with size ${Math.round(rect.width)}x${Math.round(rect.height)}.` : "Target is not rendered.", details: { visible: rendered, geometry: { x: rect.x, y: rect.y, width: rect.width, height: rect.height } } };
679
+ }
680
+ case "verifyenabled": {
681
+ if (!target) return { ok: false, summary: "Verify target is no longer available." };
682
+ const control = target;
683
+ const disabled = control.disabled === true || target.hasAttribute("disabled");
684
+ const readonly = control.readOnly === true || target.hasAttribute("readonly");
685
+ const enabled = !disabled && !readonly;
686
+ return { ok: enabled, summary: enabled ? "Target is enabled and writable." : disabled ? "Target is disabled." : "Target is readonly.", details: { enabled, disabled, readonly } };
687
+ }
688
+ case "resolvexpath": {
689
+ const reference = options.targetref;
690
+ const expression = typeof reference?.xpath === "string" ? reference.xpath : "";
691
+ if (!expression) return { ok: false, summary: "The reviewed xpath expression is absent." };
692
+ let matches = [];
693
+ try {
694
+ matches = evaluatexpath(buildxtree(root), expression);
695
+ } catch (error) {
696
+ return { ok: false, summary: `The reviewed xpath expression failed: ${error instanceof Error ? error.message : String(error)}` };
697
+ }
698
+ const summaries = matches.map((node) => ({ tag: node.tag, ...node.element ? { selector: elementselector(node.element), label: elementlabel(node.element) } : {} }));
699
+ return { ok: matches.length > 0, summary: matches.length > 0 ? `Resolved ${matches.length} element${matches.length === 1 ? "" : "s"} for the reviewed xpath.` : "The reviewed xpath matched no elements.", details: { mode: "xpath", matches: summaries } };
700
+ }
701
+ default:
702
+ return { ok: false, summary: "Unsupported page read." };
703
+ }
704
+ }
705
+
706
+ // extension/pagecontrols.ts
707
+ function typetimeschedule(text, delay) {
708
+ return [...text].map((character, position) => ({ key: character, delay: position === 0 ? 0 : delay }));
709
+ }
710
+ function appendvalue(current, addition) {
711
+ return current + addition;
712
+ }
713
+ function valueevents() {
714
+ return ["input", "change"];
715
+ }
716
+ function multichoices(values, options) {
717
+ const present = [];
718
+ const missing = [];
719
+ for (const value of values) {
720
+ const option = options.find((candidate) => candidate.value === value || candidate.label === value);
721
+ if (option) present.push(option.value);
722
+ else missing.push(value);
723
+ }
724
+ return { present, missing };
725
+ }
726
+ function radiochoice(inputs, choice) {
727
+ return inputs.findIndex((candidate) => candidate.value === choice || candidate.label === choice);
728
+ }
729
+ function slidervalue(requested, min, max, step) {
730
+ const lower = Math.min(min, max);
731
+ const upper = Math.max(min, max);
732
+ const clamped = Math.min(upper, Math.max(lower, requested));
733
+ if (!Number.isFinite(step) || step <= 0) return clamped;
734
+ return Math.round((clamped - lower) / step) * step + lower;
735
+ }
736
+ function datevalue(requested) {
737
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(requested)) return null;
738
+ const parts = requested.split("-").map((part) => Number.parseInt(part, 10));
739
+ const year = parts[0];
740
+ const month = parts[1];
741
+ const day = parts[2];
742
+ if (!year || !month || !day || month < 1 || month > 12 || day < 1 || day > 31) return null;
743
+ return requested;
744
+ }
745
+ function colorvalue(requested) {
746
+ if (!/^#[0-9a-fA-F]{6}$/.test(requested)) return null;
747
+ return requested.toLowerCase();
748
+ }
749
+ function expandstate(open) {
750
+ return open ? { open: true, changed: false } : { open: true, changed: true };
751
+ }
752
+ function events2(target) {
753
+ target.dispatchEvent(new Event("input", { bubbles: true }));
754
+ target.dispatchEvent(new Event("change", { bubbles: true }));
755
+ }
756
+ function modifiers2(options) {
757
+ return Array.isArray(options.modifiers) ? options.modifiers.filter((item) => typeof item === "string") : [];
758
+ }
759
+ function keyevent2(type, key, mods) {
760
+ const code = key.length === 1 ? `Key${key.toUpperCase()}` : key;
761
+ return new KeyboardEvent(type, { key, code, bubbles: true, cancelable: true, composed: true, ctrlKey: mods.includes("ctrl"), shiftKey: mods.includes("shift"), altKey: mods.includes("alt"), metaKey: mods.includes("meta") });
762
+ }
763
+ function fieldlike2(target) {
764
+ return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement ? target : null;
765
+ }
766
+ function receiver(target) {
767
+ return target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
768
+ }
769
+ function wait(delay) {
770
+ return new Promise((resolve) => window.setTimeout(resolve, delay));
771
+ }
772
+ function focuswhenneeded(target, options) {
773
+ if (!(target instanceof HTMLElement)) return;
774
+ if (options.focus === false) return;
775
+ if (options.focus === true || document.activeElement !== target) target.focus();
776
+ }
777
+ function pollfor(predicate, description, timeout) {
778
+ return new Promise((resolve) => {
779
+ const started = Date.now();
780
+ const check = () => {
781
+ if (predicate()) {
782
+ resolve({ ok: true, summary: `${description} is now present on the page.` });
783
+ return;
784
+ }
785
+ if (timeout > 0 && Date.now() - started >= timeout) {
786
+ resolve({ ok: false, summary: `${description} did not appear within ${timeout} milliseconds.` });
787
+ return;
788
+ }
789
+ window.setTimeout(check, 100);
790
+ };
791
+ check();
792
+ });
793
+ }
794
+ function runpagecontrol(step, target, root = document) {
795
+ let options = {};
796
+ try {
797
+ options = parseoptions(step);
798
+ } catch {
799
+ options = {};
800
+ }
801
+ switch (step.kind) {
802
+ case "typetime": {
803
+ const field = fieldlike2(target);
804
+ if (!field) return { ok: false, summary: "Target cannot receive timed text." };
805
+ const text = step.value ?? "";
806
+ const delay = typeof options.delay === "number" && options.delay > 0 ? options.delay : 0;
807
+ focuswhenneeded(field, options);
808
+ const schedule = typetimeschedule(text, delay);
809
+ return (async () => {
810
+ for (const entry of schedule) {
811
+ await wait(entry.delay);
812
+ field.dispatchEvent(keyevent2("keydown", entry.key, []));
813
+ field.dispatchEvent(new KeyboardEvent("keypress", { key: entry.key, bubbles: true, cancelable: true }));
814
+ field.value = `${field.value}${entry.key}`;
815
+ field.dispatchEvent(new Event("input", { bubbles: true }));
816
+ }
817
+ field.dispatchEvent(new Event("change", { bubbles: true }));
818
+ return { ok: true, summary: `Typed ${text.length} character${text.length === 1 ? "" : "s"} with a per keystroke delay of ${delay} milliseconds.` };
819
+ })();
820
+ }
821
+ case "appendtext": {
822
+ const field = fieldlike2(target);
823
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
824
+ focuswhenneeded(field, options);
825
+ field.value = appendvalue(field.value, step.value ?? "");
826
+ events2(field);
827
+ return { ok: true, summary: "Reviewed text appended to the current field value." };
828
+ }
829
+ case "setvalue": {
830
+ const field = fieldlike2(target);
831
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
832
+ focuswhenneeded(field, options);
833
+ field.value = step.value ?? "";
834
+ events2(field);
835
+ return { ok: true, summary: `Field value set through the dom property with ${valueevents().join(" and ")} events.` };
836
+ }
837
+ case "typeedit": {
838
+ if (!(target instanceof HTMLElement) || !target.isContentEditable) return { ok: false, summary: "Target is not a content editable region." };
839
+ focuswhenneeded(target, options);
840
+ const text = step.value ?? "";
841
+ return (async () => {
842
+ for (const character of [...text]) {
843
+ target.dispatchEvent(new InputEvent("beforeinput", { bubbles: true, cancelable: true, data: character, inputType: "insertText" }));
844
+ target.append(document.createTextNode(character));
845
+ target.dispatchEvent(new InputEvent("input", { bubbles: true, data: character, inputType: "insertText" }));
846
+ }
847
+ return { ok: true, summary: `Typed ${text.length} character${text.length === 1 ? "" : "s"} into the content editable region.` };
848
+ })();
849
+ }
850
+ case "keyhold": {
851
+ const key = step.value ?? "";
852
+ const mods = modifiers2(options);
853
+ receiver(target).dispatchEvent(keyevent2("keydown", key, mods));
854
+ const holdid = typeof options.holdid === "string" && options.holdid ? options.holdid : "";
855
+ return { ok: true, summary: `Key ${key} pressed and held${holdid ? ` under hold id ${holdid}` : ""}.`, details: { ...holdid ? { holdid } : {}, modifiers: mods } };
856
+ }
857
+ case "keyrelease": {
858
+ const key = step.value ?? "";
859
+ const mods = modifiers2(options);
860
+ receiver(target).dispatchEvent(keyevent2("keyup", key, mods));
861
+ return { ok: true, summary: `Key ${key} released.`, details: { modifiers: mods } };
862
+ }
863
+ case "submitsearch": {
864
+ const field = fieldlike2(target);
865
+ if (!field) return { ok: false, summary: "Target is not a search field." };
866
+ const results = typeof options.results === "string" ? options.results : "";
867
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
868
+ focuswhenneeded(field, options);
869
+ field.dispatchEvent(keyevent2("keydown", "Enter", []));
870
+ field.dispatchEvent(new KeyboardEvent("keypress", { key: "Enter", bubbles: true, cancelable: true }));
871
+ field.dispatchEvent(keyevent2("keyup", "Enter", []));
872
+ return pollfor(() => Boolean(document.querySelector(results)), `Results region ${results}`, timeout);
873
+ }
874
+ case "selectmulti": {
875
+ if (!(target instanceof HTMLSelectElement) || !target.multiple) return { ok: false, summary: "Target is not a multi select control." };
876
+ const choices = [...target.options].map((option) => ({ value: option.value, label: clean(option.textContent || option.value) }));
877
+ const requested = Array.isArray(options.values) ? options.values.filter((item) => typeof item === "string") : [];
878
+ const outcome = multichoices(requested, choices);
879
+ if (outcome.missing.length > 0) return { ok: false, summary: `Reviewed option${outcome.missing.length === 1 ? "" : "s"} ${outcome.missing.join(", ")} ${outcome.missing.length === 1 ? "is" : "are"} not part of the select control.` };
880
+ for (const option of target.options) option.selected = outcome.present.includes(option.value);
881
+ events2(target);
882
+ return { ok: true, summary: `Selected ${outcome.present.length} reviewed option${outcome.present.length === 1 ? "" : "s"} in the multi select control.`, details: { selected: outcome.present } };
883
+ }
884
+ case "chooseradio": {
885
+ const radios = target instanceof HTMLInputElement && target.type === "radio" ? [...root.querySelectorAll(`input[type=radio][name="${CSS.escape(target.name)}"]`)] : target ? [...target.querySelectorAll("input[type=radio]")] : [];
886
+ if (radios.length === 0) return { ok: false, summary: "No radio group owns the reviewed target." };
887
+ const inputs = radios.map((radio) => ({ value: radio.value, label: radio.labels && radio.labels.length > 0 ? clean(radio.labels[0]?.textContent || "") || radio.value : radio.value }));
888
+ const index = radiochoice(inputs, step.value ?? "");
889
+ const chosen = radios[index];
890
+ if (!chosen) return { ok: false, summary: "The reviewed radio option is not part of the group." };
891
+ chosen.checked = true;
892
+ events2(chosen);
893
+ return { ok: true, summary: `Picked reviewed radio option ${step.value}.`, details: { value: chosen.value } };
894
+ }
895
+ case "setslider": {
896
+ if (!(target instanceof HTMLInputElement) || target.type !== "range") return { ok: false, summary: "Target is not a range slider." };
897
+ const requested = Number(step.value);
898
+ if (!Number.isFinite(requested)) return { ok: false, summary: "The reviewed slider value is not a number." };
899
+ focuswhenneeded(target, options);
900
+ const value = slidervalue(requested, Number(target.min), Number(target.max), Number(target.step));
901
+ target.value = String(value);
902
+ events2(target);
903
+ return { ok: true, summary: `Slider dragged to the reviewed value ${value}.`, details: { value } };
904
+ }
905
+ case "setdate": {
906
+ if (!(target instanceof HTMLInputElement) || target.type !== "date") return { ok: false, summary: "Target is not a date input." };
907
+ const value = datevalue(step.value ?? "");
908
+ if (value === null) return { ok: false, summary: "The reviewed date is invalid." };
909
+ focuswhenneeded(target, options);
910
+ target.value = value;
911
+ events2(target);
912
+ return { ok: true, summary: `Date input set to ${value}.`, details: { value } };
913
+ }
914
+ case "setcolor": {
915
+ if (!(target instanceof HTMLInputElement) || target.type !== "color") return { ok: false, summary: "Target is not a color input." };
916
+ const value = colorvalue(step.value ?? "");
917
+ if (value === null) return { ok: false, summary: "The reviewed color is invalid." };
918
+ focuswhenneeded(target, options);
919
+ target.value = value;
920
+ events2(target);
921
+ return { ok: true, summary: `Color input set to ${value}.`, details: { value } };
922
+ }
923
+ case "expanddetails": {
924
+ const details = target instanceof HTMLElement ? target.closest("details") : null;
925
+ if (!details) return { ok: false, summary: "Target is not inside a details section." };
926
+ const outcome = expandstate(details.open);
927
+ details.open = outcome.open;
928
+ return { ok: true, summary: outcome.changed ? "Collapsed details section opened." : "Details section was already open.", details: { changed: outcome.changed } };
929
+ }
930
+ default:
931
+ return { ok: false, summary: "Unsupported control action." };
932
+ }
933
+ }
934
+
935
+ // extension/pagepointer.ts
936
+ var basecadence = 16;
937
+ function distance(a, b) {
938
+ return Math.hypot(b.x - a.x, b.y - a.y);
939
+ }
940
+ function ease(easing, progress) {
941
+ if (easing === "easeinout") return progress * progress * (3 - 2 * progress);
942
+ return progress;
943
+ }
944
+ function ispointref(value) {
945
+ if (!value || typeof value !== "object" || Array.isArray(value)) return false;
946
+ const point = value;
947
+ return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
948
+ }
949
+ function pathhops(path, profile, random = Math.random, cadence = basecadence) {
950
+ const easing = profile?.easing === "easeinout" ? "easeinout" : "linear";
951
+ const peak = typeof profile?.peak === "number" && profile.peak > 0 ? profile.peak : void 0;
952
+ const jitter = typeof profile?.jitter === "number" && profile.jitter > 0 ? profile.jitter : 0;
953
+ const points = [path.start, ...path.waypoints ?? [], path.end];
954
+ const lengths = [];
955
+ let total = 0;
956
+ for (let index = 1; index < points.length; index += 1) {
957
+ const length = distance(points[index - 1], points[index]);
958
+ lengths.push(length);
959
+ total += length;
960
+ }
961
+ const reviewedduration = typeof path.duration === "number" && Number.isFinite(path.duration) && path.duration > 0 ? path.duration : void 0;
962
+ const duration = reviewedduration ?? (peak !== void 0 && total > 0 ? total / peak * 1e3 : 300);
963
+ const hops = [];
964
+ let previous = points[0];
965
+ for (let index = 1; index < points.length; index += 1) {
966
+ const from = points[index - 1];
967
+ const to = points[index];
968
+ const length = lengths[index - 1] ?? 0;
969
+ if (total <= 0 || length <= 0) {
970
+ hops.push({ x: to.x, y: to.y, delay: 0 });
971
+ previous = to;
972
+ continue;
973
+ }
974
+ const segmentduration = duration * length / total;
975
+ const count = Math.max(1, Math.ceil(segmentduration / Math.max(1, cadence)));
976
+ for (let hop = 1; hop <= count; hop += 1) {
977
+ const progress = hop / count;
978
+ const eased = ease(easing, progress);
979
+ const position = { x: from.x + (to.x - from.x) * eased, y: from.y + (to.y - from.y) * eased };
980
+ const step = distance(previous, position);
981
+ const base = segmentduration / count;
982
+ const capped = peak !== void 0 ? Math.max(base, step / peak * 1e3) : base;
983
+ hops.push({ x: position.x, y: position.y, delay: Math.max(0, capped + (jitter > 0 ? random() * jitter : 0)) });
984
+ previous = position;
985
+ }
986
+ }
987
+ return hops;
988
+ }
989
+ function clickplan(x, y, modifiers3) {
990
+ const shift = modifiers3.includes("shift");
991
+ const pointer = (type) => ({ type, eventkind: "pointer", x, y, shift });
992
+ const mouse = (type) => ({ type, eventkind: "mouse", x, y, shift });
993
+ return [pointer("pointerover"), pointer("pointermove"), pointer("pointerdown"), mouse("mousedown"), pointer("pointerup"), mouse("mouseup"), mouse("click")];
994
+ }
995
+ function dispatchplanned(element, event) {
996
+ const init = { bubbles: true, cancelable: true, composed: true, clientX: event.x, clientY: event.y, shiftKey: event.shift };
997
+ if (event.eventkind === "pointer") element.dispatchEvent(new PointerEvent(event.type, init));
998
+ else element.dispatchEvent(new MouseEvent(event.type, init));
999
+ }
1000
+ function dispatchclick(element, modifiers3 = []) {
1001
+ const rect = element.getBoundingClientRect();
1002
+ const x = rect.left + rect.width / 2;
1003
+ const y = rect.top + rect.height / 2;
1004
+ for (const event of clickplan(x, y, modifiers3)) dispatchplanned(element, event);
1005
+ }
1006
+ function ensurevisible(element) {
1007
+ try {
1008
+ element.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
1009
+ } catch {
1010
+ }
1011
+ }
1012
+ function settle(delay) {
1013
+ return new Promise((resolve) => window.setTimeout(resolve, delay));
1014
+ }
1015
+ function dispatchmove(x, y) {
1016
+ const element = document.elementFromPoint(x, y);
1017
+ const receiver2 = element ?? document.documentElement;
1018
+ receiver2.dispatchEvent(new PointerEvent("pointermove", { bubbles: true, cancelable: true, composed: true, clientX: x, clientY: y }));
1019
+ }
1020
+ async function travel(path, profile) {
1021
+ const hops = pathhops(path, profile);
1022
+ const startelement = document.elementFromPoint(path.start.x, path.start.y) ?? document.documentElement;
1023
+ startelement.dispatchEvent(new PointerEvent("pointerover", { bubbles: true, cancelable: true, composed: true, clientX: path.start.x, clientY: path.start.y }));
1024
+ for (const hop of hops) {
1025
+ await settle(hop.delay);
1026
+ dispatchmove(hop.x, hop.y);
1027
+ }
1028
+ const endelement = document.elementFromPoint(path.end.x, path.end.y) ?? document.documentElement;
1029
+ endelement.dispatchEvent(new PointerEvent("pointerout", { bubbles: true, cancelable: true, composed: true, clientX: path.end.x, clientY: path.end.y }));
1030
+ return { ok: true, summary: `Pointer traveled ${hops.length} hop${hops.length === 1 ? "" : "s"} to the reviewed end point.` };
1031
+ }
1032
+ function runpointerstep(step, resolution) {
1033
+ let options = {};
1034
+ try {
1035
+ options = parseoptions(step);
1036
+ } catch {
1037
+ options = {};
1038
+ }
1039
+ if (step.kind === "movepointer") {
1040
+ const path = options.pointpath;
1041
+ if (!path || !ispointref(path.start) || !ispointref(path.end)) return { ok: false, summary: "The reviewed pointer path is absent." };
1042
+ const waypoints = Array.isArray(path.waypoints) && path.waypoints.every((item) => ispointref(item)) ? path.waypoints : void 0;
1043
+ const fullpath = { start: path.start, end: path.end, ...waypoints ? { waypoints } : {}, ...typeof path.duration === "number" && Number.isFinite(path.duration) ? { duration: path.duration } : {} };
1044
+ return travel(fullpath, options.speedprofile);
1045
+ }
1046
+ if (step.kind === "clickpoint") {
1047
+ const reference = options.targetref;
1048
+ const x = Number(reference?.x);
1049
+ const y = Number(reference?.y);
1050
+ if (!Number.isFinite(x) || !Number.isFinite(y)) return { ok: false, summary: "The reviewed click coordinates are absent." };
1051
+ const element = document.elementFromPoint(x, y);
1052
+ if (!(element instanceof HTMLElement)) return { ok: false, summary: "No element is rendered at the reviewed coordinates." };
1053
+ ensurevisible(element);
1054
+ for (const event of clickplan(x, y, [])) dispatchplanned(element, event);
1055
+ return { ok: true, summary: `Clicked the element at the reviewed coordinates ${x},${y}.` };
1056
+ }
1057
+ if (step.kind === "shiftclick") {
1058
+ if (resolution.status === "ambiguous") return { ok: false, summary: `The reviewed reference matched ${resolution.candidates.length} elements; choose one candidate.`, details: { mode: resolution.mode, candidates: resolution.candidates } };
1059
+ if (resolution.status !== "resolved") return { ok: false, summary: "Action target is no longer available." };
1060
+ ensurevisible(resolution.element);
1061
+ dispatchclick(resolution.element, ["shift"]);
1062
+ return { ok: true, summary: `Shift click delivered to ${resolution.target.label || resolution.target.tag}.`, details: { mode: resolution.target.mode, resolvedtarget: resolution.target } };
1063
+ }
1064
+ return { ok: false, summary: "Unsupported pointer action." };
1065
+ }
1066
+
1067
+ // extension/pageinteract.ts
1068
+ function optionsof(step) {
1069
+ try {
1070
+ return parseoptions(step);
1071
+ } catch {
1072
+ return {};
1073
+ }
1074
+ }
1075
+ function innerstep(step) {
1076
+ const options = optionsof(step);
1077
+ const kind = options.kind;
1078
+ if (typeof kind !== "string" || !kind.trim()) return null;
1079
+ const inneroptions = options.options;
1080
+ return {
1081
+ id: `${step.id}inner`,
1082
+ kind,
1083
+ summary: step.summary,
1084
+ risk: step.risk,
1085
+ ...typeof options.target === "string" ? { target: options.target } : {},
1086
+ ...typeof options.value === "string" ? { value: options.value } : {},
1087
+ ...inneroptions && typeof inneroptions === "object" && !Array.isArray(inneroptions) ? { options: JSON.stringify(inneroptions) } : {}
1088
+ };
1089
+ }
1090
+ function clickresolved(stepkind, resolution) {
1091
+ if (resolution.status === "ambiguous") return { ok: false, summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`, details: { mode: resolution.mode, candidates: resolution.candidates } };
1092
+ if (resolution.status !== "resolved") return { ok: false, summary: "The reviewed target is no longer available." };
1093
+ ensurevisible(resolution.element);
1094
+ dispatchclick(resolution.element);
1095
+ return { ok: true, summary: `Clicked ${resolution.target.label || resolution.target.tag} resolved by ${stepkind} ${resolution.target.mode} mode.`, details: { mode: resolution.target.mode, resolvedtarget: resolution.target } };
1096
+ }
1097
+ function runinteractstep(step, expectedorigin, dispatch) {
1098
+ if (step.kind === "clicktext" || step.kind === "clickaria" || step.kind === "clickname") {
1099
+ return clickresolved(step.kind, resolvestep(step, document));
1100
+ }
1101
+ if (step.kind === "pierceshadow") {
1102
+ const options = optionsof(step);
1103
+ const shadow = Array.isArray(options.shadow) ? options.shadow.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
1104
+ const element = shadow.length > 0 ? queryshadowchain(document, shadow) : queryscoped(document, step.target ?? "");
1105
+ if (!(element instanceof HTMLElement)) return { ok: false, summary: "The reviewed shadow target is not available." };
1106
+ ensurevisible(element);
1107
+ dispatchclick(element);
1108
+ const summary = targetsummary("selector", element);
1109
+ return { ok: true, summary: `Clicked ${summary.label || summary.tag} resolved through ${shadow.length > 0 ? "the reviewed shadow path" : "open shadow roots"}.`, details: { mode: "selector", resolvedtarget: summary } };
1110
+ }
1111
+ if (step.kind === "enterframe") {
1112
+ const options = optionsof(step);
1113
+ const path = Array.isArray(options.framepath) ? options.framepath.filter((item) => typeof item === "number" && Number.isInteger(item) && item >= 0) : [];
1114
+ const walk = walkframepath(describeframes(document), path);
1115
+ if (!walk.ok) return { ok: false, summary: walk.reason };
1116
+ const framedocument = walk.document.live;
1117
+ if (!framedocument) return { ok: false, summary: "The reviewed frame document is not available." };
1118
+ const inner = innerstep(step);
1119
+ if (!inner) return { ok: false, summary: "The reviewed inner step is absent." };
1120
+ return dispatch(inner, expectedorigin, framedocument);
1121
+ }
1122
+ return { ok: false, summary: "Unsupported interaction action." };
1123
+ }
1124
+
1125
+ // extension/pagedialogs.ts
1126
+ function harvestdialoglog(root) {
1127
+ const raw = root.documentElement.dataset.devthinkdialoglog;
1128
+ if (!raw) return [];
1129
+ delete root.documentElement.dataset.devthinkdialoglog;
1130
+ try {
1131
+ const parsed = JSON.parse(raw);
1132
+ if (!Array.isArray(parsed)) return [];
1133
+ return parsed.filter((item) => Boolean(item) && typeof item === "object" && typeof item.dialog === "string");
1134
+ } catch {
1135
+ return [];
1136
+ }
1137
+ }
1138
+
1139
+ // extension/pagebridge.ts
1140
+ function stepoptions(step) {
1141
+ if (!step.options) return {};
1142
+ try {
1143
+ const parsed = JSON.parse(step.options);
1144
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
1145
+ } catch {
1146
+ return {};
1147
+ }
1148
+ }
25
1149
  var previewid = "devthinktargetpreview";
26
1150
  function clearpreview() {
27
1151
  document.getElementById(previewid)?.remove();
28
1152
  }
29
- function previewtarget(targetselector, expectedorigin) {
1153
+ function previewtarget(step, expectedorigin) {
30
1154
  if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before preview." };
31
1155
  clearpreview();
32
- const target = document.querySelector(targetselector);
33
- if (!(target instanceof HTMLElement)) return { ok: false, summary: "Reviewed target is no longer available." };
1156
+ const resolution = resolvestep(step, document);
1157
+ if (resolution.status === "ambiguous") return { ok: false, summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`, candidates: resolution.candidates };
1158
+ if (resolution.status !== "resolved") return { ok: false, summary: "Reviewed target is no longer available." };
1159
+ const target = resolution.element;
34
1160
  const rect = target.getBoundingClientRect();
35
1161
  if (rect.width <= 0 || rect.height <= 0) return { ok: false, summary: "Reviewed target is not currently visible." };
36
1162
  const overlay = document.createElement("div");
@@ -39,42 +1165,46 @@
39
1165
  Object.assign(overlay.style, { position: "fixed", left: `${Math.max(0, rect.left - 3)}px`, top: `${Math.max(0, rect.top - 3)}px`, width: `${rect.width + 6}px`, height: `${rect.height + 6}px`, border: "3px solid #2f80ed", borderRadius: "6px", boxShadow: "0 0 0 3px rgba(47,128,237,.28)", pointerEvents: "none", zIndex: "2147483647", boxSizing: "border-box" });
40
1166
  document.documentElement.append(overlay);
41
1167
  window.setTimeout(clearpreview, 5e3);
42
- return { ok: true, summary: `Previewing ${label(target) || target.tagName.toLowerCase()} for five seconds.` };
1168
+ return { ok: true, summary: `Previewing ${elementlabel(target) || target.tagName.toLowerCase()} for five seconds.`, resolvedtarget: resolution.target };
43
1169
  }
44
1170
  function capturesnapshot() {
45
- const candidates = [...document.querySelectorAll("a[href], button, input, textarea, select, [role=button], [role=link], [role=combobox], [role=option], details, summary")].slice(0, 80);
46
- const interactive = candidates.map((element) => ({ selector: selector(element), role: element.getAttribute("role") || element.tagName.toLowerCase(), label: label(element) })).filter((item) => item.label || item.role);
47
- const forms = [...document.querySelectorAll("input, textarea, select")].slice(0, 40).map((element) => ({
48
- label: label(element),
1171
+ const candidates = [...document.querySelectorAll("a[href], button, input, textarea, select, [role=button], [role=link], [role=combobox], [role=option], [role=checkbox], [role=radio], [role=switch], [role=tab], details, summary")];
1172
+ const interactive = candidates.map((element) => ({ selector: elementselector(element), role: element.getAttribute("role") || element.tagName.toLowerCase(), label: elementlabel(element) })).filter((item) => item.label || item.role);
1173
+ const forms = [...document.querySelectorAll("input, textarea, select")].map((element) => ({
1174
+ label: elementlabel(element),
49
1175
  type: element.getAttribute("type") || element.tagName.toLowerCase(),
50
1176
  name: element.getAttribute("name") || "",
51
- ...element instanceof HTMLSelectElement ? { options: [...element.options].slice(0, 12).map((option) => bounded(option.textContent || option.value, 60)) } : {}
1177
+ ...element instanceof HTMLSelectElement ? { options: [...element.options].map((option) => clean(option.textContent || option.value)) } : {}
52
1178
  }));
53
- const text = bounded(document.body?.innerText || "", 2e3);
54
- return { url: location.href, title: bounded(document.title, 180), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };
1179
+ const text = clean(document.body?.innerText || "");
1180
+ return { schemaversion: 2, url: location.href, title: clean(document.title), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };
1181
+ }
1182
+ function readdialogs() {
1183
+ return harvestdialoglog(document);
55
1184
  }
56
- function extractcontent(targetselector) {
1185
+ function extractcontent(targetselector, root) {
57
1186
  if (!targetselector) {
58
- const links = [...document.querySelectorAll("a[href]")].slice(0, 25).map((element) => {
1187
+ const links = [...root.querySelectorAll("a[href]")].map((element) => {
59
1188
  const href = element instanceof HTMLAnchorElement ? element.getAttribute("href") ?? "" : "";
60
- return `${bounded(element.textContent || "")} (${bounded(href, 120)})`;
1189
+ return { text: element.textContent?.trim() ?? "", href };
61
1190
  });
62
- return { ok: true, summary: `Extracted ${links.length} bounded link entries: ${links.join("; ").slice(0, 180) || "no links present"}.` };
1191
+ return { ok: true, summary: `Extracted ${links.length} link entries.`, details: { links } };
63
1192
  }
64
- const target = document.querySelector(targetselector);
1193
+ const target = root.querySelector(targetselector);
65
1194
  if (!target) return { ok: false, summary: "Extraction target is no longer available." };
66
- return { ok: true, summary: `Extracted: ${bounded(target.textContent || "", 180) || "empty target"}.` };
1195
+ const text = target.textContent ?? "";
1196
+ return { ok: true, summary: `Extracted ${text.length} characters of content.`, details: { text } };
67
1197
  }
68
1198
  function scrolltarget(target) {
69
1199
  target.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
70
- return { ok: true, summary: `Scrolled ${label(target) || target.tagName.toLowerCase()} into view.` };
1200
+ return { ok: true, summary: `Scrolled ${elementlabel(target) || target.tagName.toLowerCase()} into view.` };
71
1201
  }
72
1202
  function hovertarget(target) {
73
1203
  for (const type of ["pointerover", "mouseover", "pointerenter"]) {
74
1204
  target.dispatchEvent(new PointerEvent(type, { bubbles: type !== "pointerenter", cancelable: true, composed: true }));
75
1205
  }
76
1206
  target.dispatchEvent(new MouseEvent("mouseenter", { bubbles: false, cancelable: true }));
77
- return { ok: true, summary: `Hover events delivered to ${label(target) || target.tagName.toLowerCase()}.` };
1207
+ return { ok: true, summary: `Hover events delivered to ${elementlabel(target) || target.tagName.toLowerCase()}.` };
78
1208
  }
79
1209
  function selectoption(target, value) {
80
1210
  if (!(target instanceof HTMLSelectElement)) return { ok: false, summary: "Target is not a select element." };
@@ -83,48 +1213,95 @@
83
1213
  target.value = option.value;
84
1214
  target.dispatchEvent(new Event("input", { bubbles: true }));
85
1215
  target.dispatchEvent(new Event("change", { bubbles: true }));
86
- return { ok: true, summary: `Selected ${bounded(option.textContent || option.value, 60)}.` };
1216
+ return { ok: true, summary: `Selected ${clean(option.textContent || option.value)}.` };
87
1217
  }
88
- function performstep(step, expectedorigin) {
1218
+ var readkinds = /* @__PURE__ */ new Set(["readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "waitfor", "waittext", "highlight", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath"]);
1219
+ var mutatingkinds = /* @__PURE__ */ new Set(["presskey", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "setattribute", "removeattribute", "writestorage", "evaluate", "fullscreen"]);
1220
+ var controlkinds = /* @__PURE__ */ new Set(["typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails"]);
1221
+ var interactkinds = /* @__PURE__ */ new Set(["clicktext", "clickaria", "clickname", "pierceshadow", "enterframe"]);
1222
+ var pointerkinds = /* @__PURE__ */ new Set(["movepointer", "clickpoint", "shiftclick"]);
1223
+ async function performstep(step, expectedorigin, rootdocument = document) {
89
1224
  if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before action." };
90
1225
  if (step.kind === "observe") return { ok: true, summary: "Observation completed." };
91
1226
  if (step.kind === "wait") {
92
1227
  const requested = step.value ? Number.parseInt(step.value, 10) : 250;
93
- const duration = Number.isFinite(requested) && requested > 0 ? Math.min(requested, 1e4) : 0;
94
- return new Promise((resolve) => window.setTimeout(() => resolve({ ok: true, summary: `Bounded wait of ${duration} milliseconds completed.` }), duration));
1228
+ const duration = Number.isFinite(requested) && requested > 0 ? requested : 0;
1229
+ return new Promise((resolve) => window.setTimeout(() => resolve({ ok: true, summary: `Reviewed wait of ${duration} milliseconds completed.` }), duration));
95
1230
  }
96
- if (step.kind === "extract") return extractcontent(step.target);
1231
+ if (step.kind === "extract") return extractcontent(step.target, rootdocument);
97
1232
  if (step.kind === "navigate") {
98
1233
  if (!step.value || new URL(step.value).origin !== expectedorigin) return { ok: false, summary: "Navigation target is outside the approved origin." };
99
1234
  location.assign(step.value);
100
1235
  return { ok: true, summary: "Navigation request sent." };
101
1236
  }
102
- if (!step.target) return { ok: false, summary: "Action target is absent." };
103
- const target = document.querySelector(step.target);
104
- if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
105
- if (step.kind === "focus") {
106
- target.focus();
107
- return { ok: true, summary: "Target focused." };
108
- }
109
- if (step.kind === "inspect") return { ok: true, summary: `Target: ${label(target) || target.tagName.toLowerCase()}.` };
110
- if (step.kind === "click") {
111
- target.click();
112
- return { ok: true, summary: "Reviewed click completed." };
113
- }
114
- if (step.kind === "scroll") return scrolltarget(target);
115
- if (step.kind === "hover") return hovertarget(target);
116
- if (step.kind === "select") return selectoption(target, step.value ?? "");
117
- if (step.kind === "type") {
118
- if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return { ok: false, summary: "Target cannot receive text." };
119
- if (typeof step.value !== "string") return { ok: false, summary: "Approved text is absent." };
120
- target.focus();
121
- target.value = step.value;
122
- target.dispatchEvent(new Event("input", { bubbles: true }));
123
- target.dispatchEvent(new Event("change", { bubbles: true }));
124
- return { ok: true, summary: "Approved text entered." };
125
- }
126
- return { ok: false, summary: "Unsupported action." };
127
- }
128
- Object.assign(globalThis, { devthinkbridge: { capturesnapshot, previewtarget, performstep } });
1237
+ if (step.kind === "reload") {
1238
+ location.reload();
1239
+ return { ok: true, summary: "Page reload requested." };
1240
+ }
1241
+ if (step.kind === "back") {
1242
+ history.back();
1243
+ return { ok: true, summary: "History back requested." };
1244
+ }
1245
+ if (step.kind === "forward") {
1246
+ history.forward();
1247
+ return { ok: true, summary: "History forward requested." };
1248
+ }
1249
+ if (step.kind === "scrollpage") {
1250
+ const options = stepoptions(step);
1251
+ window.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
1252
+ return { ok: true, summary: "Window scrolled by the reviewed amounts." };
1253
+ }
1254
+ if (step.kind === "scrollend") {
1255
+ window.scrollTo(0, document.documentElement.scrollHeight);
1256
+ return { ok: true, summary: "Window scrolled to the page end." };
1257
+ }
1258
+ if (step.kind === "scrolltop") {
1259
+ window.scrollTo(0, 0);
1260
+ return { ok: true, summary: "Window scrolled to the page top." };
1261
+ }
1262
+ const resolution = resolvestep(step, rootdocument);
1263
+ if (resolution.status === "ambiguous") {
1264
+ return { ok: false, summary: `The reviewed ${resolution.mode} reference matched ${resolution.candidates.length} elements: ${resolution.candidates.join("; ")}.`, details: { mode: resolution.mode, candidates: resolution.candidates } };
1265
+ }
1266
+ const element = resolution.status === "resolved" ? resolution.element : null;
1267
+ let result;
1268
+ if (readkinds.has(step.kind)) result = runpageread(step, element, rootdocument);
1269
+ else if (controlkinds.has(step.kind)) result = runpagecontrol(step, element, rootdocument);
1270
+ else if (interactkinds.has(step.kind)) return await runinteractstep(step, expectedorigin, performstep);
1271
+ else if (pointerkinds.has(step.kind)) result = runpointerstep(step, resolution);
1272
+ else if (mutatingkinds.has(step.kind)) result = runpageaction(step, element);
1273
+ else {
1274
+ if (!element) return { ok: false, summary: "Action target is no longer available." };
1275
+ if (step.kind === "scrollby") {
1276
+ const options = stepoptions(step);
1277
+ element.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
1278
+ result = { ok: true, summary: "Container scrolled by the reviewed amounts." };
1279
+ } else if (step.kind === "focus") {
1280
+ element.focus();
1281
+ result = { ok: true, summary: "Target focused." };
1282
+ } else if (step.kind === "inspect") result = { ok: true, summary: `Target: ${elementlabel(element) || element.tagName.toLowerCase()}.` };
1283
+ else if (step.kind === "click") {
1284
+ element.click();
1285
+ result = { ok: true, summary: "Reviewed click completed." };
1286
+ } else if (step.kind === "scroll") result = scrolltarget(element);
1287
+ else if (step.kind === "hover") result = hovertarget(element);
1288
+ else if (step.kind === "select") result = selectoption(element, step.value ?? "");
1289
+ else if (step.kind === "type") {
1290
+ if (!(element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement)) return { ok: false, summary: "Target cannot receive text." };
1291
+ if (typeof step.value !== "string") return { ok: false, summary: "Approved text is absent." };
1292
+ element.focus();
1293
+ element.value = step.value;
1294
+ element.dispatchEvent(new Event("input", { bubbles: true }));
1295
+ element.dispatchEvent(new Event("change", { bubbles: true }));
1296
+ result = { ok: true, summary: "Approved text entered." };
1297
+ } else return { ok: false, summary: "Unsupported action." };
1298
+ }
1299
+ const output = await result;
1300
+ if (resolution.status === "resolved") {
1301
+ return { ...output, details: { ...output.details ?? {}, mode: resolution.target.mode, resolvedtarget: resolution.target } };
1302
+ }
1303
+ return output;
1304
+ }
1305
+ Object.assign(globalThis, { devthinkbridge: { capturesnapshot, previewtarget, performstep, readdialogs } });
129
1306
  })();
130
1307
  //# sourceMappingURL=pagebridge.js.map