@wenathlan/extension 1.1.30 → 1.1.32

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,14 +1,339 @@
1
1
  "use strict";
2
2
  (() => {
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"]);
5
+ var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset"]);
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"]);
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
+
20
+ // extension/pageactions.ts
21
+ function events(target) {
22
+ target.dispatchEvent(new Event("input", { bubbles: true }));
23
+ target.dispatchEvent(new Event("change", { bubbles: true }));
24
+ }
25
+ function modifiers(options) {
26
+ return Array.isArray(options.modifiers) ? options.modifiers.filter((item) => typeof item === "string") : [];
27
+ }
28
+ function keyevent(type, key, mods) {
29
+ const code = key.length === 1 ? `Key${key.toUpperCase()}` : key;
30
+ 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") });
31
+ }
32
+ function fieldlike(target) {
33
+ return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement ? target : null;
34
+ }
35
+ function stringify(value) {
36
+ try {
37
+ return JSON.parse(JSON.stringify(value)) ?? null;
38
+ } catch {
39
+ return String(value);
40
+ }
41
+ }
42
+ function runpageaction(step, target) {
43
+ const options = (() => {
44
+ try {
45
+ return parseoptions(step);
46
+ } catch {
47
+ return {};
48
+ }
49
+ })();
50
+ switch (step.kind) {
51
+ case "presskey": {
52
+ const receiver = target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;
53
+ const key = step.value ?? "";
54
+ const mods = modifiers(options);
55
+ receiver.dispatchEvent(keyevent("keydown", key, mods));
56
+ receiver.dispatchEvent(keyevent("keypress", key, mods));
57
+ receiver.dispatchEvent(keyevent("keyup", key, mods));
58
+ return { ok: true, summary: `Key ${key} delivered with ${mods.length} modifier${mods.length === 1 ? "" : "s"}.` };
59
+ }
60
+ case "clickdeep": {
61
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
62
+ target.dispatchEvent(new PointerEvent("pointerdown", { bubbles: true, cancelable: true, composed: true }));
63
+ target.dispatchEvent(new MouseEvent("mousedown", { bubbles: true, cancelable: true }));
64
+ target.dispatchEvent(new PointerEvent("pointerup", { bubbles: true, cancelable: true, composed: true }));
65
+ target.dispatchEvent(new MouseEvent("mouseup", { bubbles: true, cancelable: true }));
66
+ target.click();
67
+ return { ok: true, summary: "Full pointer click sequence delivered." };
68
+ }
69
+ case "rightclick": {
70
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
71
+ const init = { bubbles: true, cancelable: true, button: 2, buttons: 2 };
72
+ target.dispatchEvent(new PointerEvent("pointerdown", { ...init, composed: true }));
73
+ target.dispatchEvent(new MouseEvent("mousedown", init));
74
+ target.dispatchEvent(new MouseEvent("contextmenu", init));
75
+ return { ok: true, summary: "Context menu events delivered." };
76
+ }
77
+ case "doubleclick": {
78
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
79
+ target.click();
80
+ target.click();
81
+ target.dispatchEvent(new MouseEvent("dblclick", { bubbles: true, cancelable: true, detail: 2 }));
82
+ return { ok: true, summary: "Double click sequence delivered." };
83
+ }
84
+ case "drag": {
85
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Drag source is no longer available." };
86
+ const destination = document.querySelector(step.value ?? "");
87
+ if (!destination) return { ok: false, summary: "Drag destination is no longer available." };
88
+ const transfer = new DataTransfer();
89
+ if (typeof options.data === "string") transfer.setData("text/plain", options.data);
90
+ target.dispatchEvent(new DragEvent("dragstart", { bubbles: true, cancelable: true, dataTransfer: transfer }));
91
+ destination.dispatchEvent(new DragEvent("dragenter", { bubbles: true, cancelable: true, dataTransfer: transfer }));
92
+ destination.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true, dataTransfer: transfer }));
93
+ destination.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer }));
94
+ target.dispatchEvent(new DragEvent("dragend", { bubbles: true, cancelable: true, dataTransfer: transfer }));
95
+ return { ok: true, summary: "Drag and drop sequence delivered." };
96
+ }
97
+ case "drop": {
98
+ if (!(target instanceof HTMLElement)) return { ok: false, summary: "Drop zone is no longer available." };
99
+ const transfer = new DataTransfer();
100
+ transfer.setData("text/plain", step.value ?? "");
101
+ target.dispatchEvent(new DragEvent("dragenter", { bubbles: true, cancelable: true, dataTransfer: transfer }));
102
+ target.dispatchEvent(new DragEvent("dragover", { bubbles: true, cancelable: true, dataTransfer: transfer }));
103
+ target.dispatchEvent(new DragEvent("drop", { bubbles: true, cancelable: true, dataTransfer: transfer }));
104
+ return { ok: true, summary: "Drop payload delivered." };
105
+ }
106
+ case "upload": {
107
+ if (!(target instanceof HTMLInputElement) || target.type !== "file") return { ok: false, summary: "Target is not a file input." };
108
+ const transfer = new DataTransfer();
109
+ transfer.items.add(new File([typeof options.content === "string" ? options.content : ""], step.value ?? "upload", { type: typeof options.type === "string" ? options.type : "text/plain" }));
110
+ target.files = transfer.files;
111
+ events(target);
112
+ return { ok: true, summary: `Uploaded ${step.value ?? "file"} into the reviewed input.` };
113
+ }
114
+ case "clear": {
115
+ const field = fieldlike(target);
116
+ if (!field) return { ok: false, summary: "Target cannot hold a value." };
117
+ field.value = "";
118
+ events(field);
119
+ return { ok: true, summary: "Field cleared." };
120
+ }
121
+ case "check":
122
+ case "uncheck":
123
+ case "toggle": {
124
+ if (!(target instanceof HTMLInputElement) || target.type !== "checkbox" && target.type !== "radio") return { ok: false, summary: "Target is not a checkbox or radio control." };
125
+ if (step.kind === "uncheck" && target.type === "radio") return { ok: false, summary: "A radio control cannot be unchecked." };
126
+ target.checked = step.kind === "toggle" ? !target.checked : step.kind === "check";
127
+ events(target);
128
+ return { ok: true, summary: `Control is now ${target.checked ? "checked" : "unchecked"}.` };
129
+ }
130
+ case "submit": {
131
+ const form = target instanceof HTMLFormElement ? target : target instanceof HTMLElement ? target.closest("form") : null;
132
+ if (!form) return { ok: false, summary: "No form owns the reviewed target." };
133
+ try {
134
+ form.requestSubmit(target instanceof HTMLFormElement ? void 0 : target);
135
+ } catch {
136
+ form.submit();
137
+ }
138
+ return { ok: true, summary: "Form submission requested." };
139
+ }
140
+ case "setattribute": {
141
+ if (!target) return { ok: false, summary: "Action target is no longer available." };
142
+ const name = typeof options.name === "string" ? options.name : "";
143
+ target.setAttribute(name, typeof options.value === "string" ? options.value : "");
144
+ return { ok: true, summary: `Attribute ${name} set.` };
145
+ }
146
+ case "removeattribute": {
147
+ if (!target) return { ok: false, summary: "Action target is no longer available." };
148
+ const name = step.value ?? "";
149
+ target.removeAttribute(name);
150
+ return { ok: true, summary: `Attribute ${name} removed.` };
151
+ }
152
+ case "writestorage": {
153
+ try {
154
+ localStorage.setItem(typeof options.key === "string" ? options.key : "", typeof options.value === "string" ? options.value : "");
155
+ return { ok: true, summary: `Local storage entry ${String(options.key)} written.` };
156
+ } catch (error) {
157
+ return { ok: false, summary: `Local storage refused the write: ${error instanceof Error ? error.message : String(error)}` };
158
+ }
159
+ }
160
+ case "evaluate": {
161
+ try {
162
+ let outcome;
163
+ try {
164
+ outcome = new Function(`"use strict"; return (${step.value ?? "undefined"});`)();
165
+ } catch {
166
+ outcome = new Function(`"use strict"; ${step.value ?? ""}`)();
167
+ }
168
+ return { ok: true, summary: `Reviewed expression returned ${outcome === void 0 ? "no value" : "a value"}.`, details: { result: stringify(outcome) } };
169
+ } catch (error) {
170
+ return { ok: false, summary: `Reviewed expression failed: ${error instanceof Error ? error.message : String(error)}` };
171
+ }
172
+ }
173
+ case "fullscreen": {
174
+ const element = target instanceof HTMLElement ? target : document.documentElement;
175
+ 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)}` }));
176
+ }
177
+ default:
178
+ return { ok: false, summary: "Unsupported page action." };
179
+ }
180
+ }
181
+
182
+ // extension/pagereads.ts
183
+ var highlightid = "devthinkactionhighlight";
184
+ function clearhighlight() {
185
+ document.getElementById(highlightid)?.remove();
186
+ }
187
+ function highlighttarget(target) {
188
+ clearhighlight();
189
+ const rect = target.getBoundingClientRect();
190
+ const overlay = document.createElement("div");
191
+ overlay.id = highlightid;
192
+ overlay.setAttribute("aria-hidden", "true");
193
+ 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" });
194
+ document.documentElement.append(overlay);
195
+ window.setTimeout(clearhighlight, 5e3);
196
+ return { ok: true, summary: "Target outlined for five seconds." };
197
+ }
198
+ function poll(predicate, description, timeout) {
199
+ return new Promise((resolve) => {
200
+ const started = Date.now();
201
+ const check = () => {
202
+ if (predicate()) {
203
+ resolve({ ok: true, summary: `${description} is now present on the page.` });
204
+ return;
205
+ }
206
+ if (timeout > 0 && Date.now() - started >= timeout) {
207
+ resolve({ ok: false, summary: `${description} did not appear within ${timeout} milliseconds.` });
208
+ return;
209
+ }
210
+ window.setTimeout(check, 100);
211
+ };
212
+ check();
213
+ });
214
+ }
215
+ function formstate() {
216
+ return [...document.querySelectorAll("input, textarea, select")].map((element) => ({
217
+ type: element.getAttribute("type") ?? element.tagName.toLowerCase(),
218
+ name: element.getAttribute("name") ?? "",
219
+ value: element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement ? element.value : "",
220
+ ...element instanceof HTMLInputElement && (element.type === "checkbox" || element.type === "radio") ? { checked: element.checked } : {}
221
+ }));
222
+ }
223
+ function runpageread(step, target) {
224
+ const options = (() => {
225
+ try {
226
+ return parseoptions(step);
227
+ } catch {
228
+ return {};
229
+ }
230
+ })();
231
+ switch (step.kind) {
232
+ case "highlight": {
233
+ if (!target) return { ok: false, summary: "Highlight target is no longer available." };
234
+ return highlighttarget(target);
235
+ }
236
+ case "readattribute": {
237
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
238
+ const value = target.getAttribute(step.value ?? "");
239
+ return value === null ? { ok: false, summary: `Attribute ${step.value} is absent.` } : { ok: true, summary: `Attribute ${step.value} read.`, details: { value } };
240
+ }
241
+ case "readstyle": {
242
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
243
+ const computed = getComputedStyle(target);
244
+ const styles = {};
245
+ for (let index = 0; index < computed.length; index += 1) {
246
+ const property = computed.item(index);
247
+ styles[property] = computed.getPropertyValue(property);
248
+ }
249
+ return { ok: true, summary: `Read ${computed.length} computed style properties.`, details: { styles } };
250
+ }
251
+ case "readgeometry": {
252
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
253
+ const rect = target.getBoundingClientRect();
254
+ 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 };
255
+ return { ok: true, summary: "Target geometry read.", details: { geometry } };
256
+ }
257
+ case "readvalue": {
258
+ if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) return { ok: false, summary: "Target does not hold a form value." };
259
+ return { ok: true, summary: "Form value read.", details: { value: target.value } };
260
+ }
261
+ case "readtext": {
262
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
263
+ const text = target.textContent ?? "";
264
+ return { ok: true, summary: `Read ${text.length} characters of text.`, details: { text } };
265
+ }
266
+ case "readhtml": {
267
+ if (!target) return { ok: false, summary: "Read target is no longer available." };
268
+ return { ok: true, summary: "Target markup read.", details: { html: target.outerHTML } };
269
+ }
270
+ case "countelements": {
271
+ const count = document.querySelectorAll(step.target ?? "").length;
272
+ return { ok: true, summary: `Selector matches ${count} element${count === 1 ? "" : "s"}.`, details: { count } };
273
+ }
274
+ case "readtable": {
275
+ if (!(target instanceof HTMLTableElement)) return { ok: false, summary: "Read target is not a table element." };
276
+ const rows = [...target.querySelectorAll("tr")].map((row) => [...row.querySelectorAll("th, td")].map((cell) => cell.textContent?.trim() ?? ""));
277
+ const headers = rows[0] ?? [];
278
+ const body = rows.slice(1);
279
+ 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 } };
280
+ }
281
+ case "readlinks": {
282
+ const links = [...document.querySelectorAll("a[href]")].map((element) => ({ text: element.textContent?.trim() ?? "", href: element.getAttribute("href") ?? "" }));
283
+ return { ok: true, summary: `Read ${links.length} link${links.length === 1 ? "" : "s"}.`, details: { links } };
284
+ }
285
+ case "readimages": {
286
+ const images = [...document.querySelectorAll("img")].map((element) => ({ src: element.getAttribute("src") ?? "", alt: element.getAttribute("alt") ?? "" }));
287
+ return { ok: true, summary: `Read ${images.length} image${images.length === 1 ? "" : "s"}.`, details: { images } };
288
+ }
289
+ case "readmeta": {
290
+ const meta = [...document.querySelectorAll("meta")].map((element) => ({ name: element.getAttribute("name") ?? "", property: element.getAttribute("property") ?? "", content: element.getAttribute("content") ?? "" }));
291
+ return { ok: true, summary: `Read ${meta.length} meta entr${meta.length === 1 ? "y" : "ies"}.`, details: { meta } };
292
+ }
293
+ case "readforms": {
294
+ const forms = formstate();
295
+ return { ok: true, summary: `Read ${forms.length} form control${forms.length === 1 ? "" : "s"}.`, details: { forms } };
296
+ }
297
+ case "readstorage": {
298
+ try {
299
+ if (step.value) {
300
+ const value = localStorage.getItem(step.value);
301
+ return { ok: true, summary: `Read local storage entry ${step.value}.`, details: { value } };
302
+ }
303
+ const entries = {};
304
+ for (let index = 0; index < localStorage.length; index += 1) {
305
+ const key = localStorage.key(index);
306
+ if (key !== null) entries[key] = localStorage.getItem(key);
307
+ }
308
+ return { ok: true, summary: `Read ${Object.keys(entries).length} local storage entr${Object.keys(entries).length === 1 ? "y" : "ies"}.`, details: { entries } };
309
+ } catch (error) {
310
+ return { ok: false, summary: `Local storage refused the read: ${error instanceof Error ? error.message : String(error)}` };
311
+ }
312
+ }
313
+ case "waitfor": {
314
+ const selector2 = step.target ?? "";
315
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
316
+ return poll(() => Boolean(document.querySelector(selector2)), `Selector ${selector2}`, timeout);
317
+ }
318
+ case "waittext": {
319
+ const text = step.value ?? "";
320
+ const timeout = typeof options.timeout === "number" ? options.timeout : 0;
321
+ return poll(() => (document.body?.innerText ?? "").includes(text), `Text ${text}`, timeout);
322
+ }
323
+ default:
324
+ return { ok: false, summary: "Unsupported page read." };
325
+ }
326
+ }
327
+
3
328
  // extension/pagebridge.ts
4
- function bounded(value, length = 180) {
5
- return value.replace(/\s+/g, " ").trim().slice(0, length);
329
+ function clean(value) {
330
+ return value.replace(/\s+/g, " ").trim();
6
331
  }
7
332
  function label(element) {
8
333
  const aria = element.getAttribute("aria-label");
9
334
  const labelledby = element.getAttribute("aria-labelledby");
10
335
  const linked = labelledby ? document.getElementById(labelledby)?.textContent : "";
11
- return bounded(aria || linked || element.getAttribute("title") || element.textContent || "");
336
+ return clean(aria || linked || element.getAttribute("title") || element.textContent || "");
12
337
  }
13
338
  function selector(element) {
14
339
  if (element.id) return `#${CSS.escape(element.id)}`;
@@ -22,6 +347,15 @@
22
347
  const peers = [...parent.children].filter((node) => node.tagName === element.tagName);
23
348
  return `${tag}:nth-of-type(${peers.indexOf(element) + 1})`;
24
349
  }
350
+ function stepoptions(step) {
351
+ if (!step.options) return {};
352
+ try {
353
+ const parsed = JSON.parse(step.options);
354
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
355
+ } catch {
356
+ return {};
357
+ }
358
+ }
25
359
  var previewid = "devthinktargetpreview";
26
360
  function clearpreview() {
27
361
  document.getElementById(previewid)?.remove();
@@ -42,23 +376,101 @@
42
376
  return { ok: true, summary: `Previewing ${label(target) || target.tagName.toLowerCase()} for five seconds.` };
43
377
  }
44
378
  function capturesnapshot() {
45
- const candidates = [...document.querySelectorAll("a[href], button, input, textarea, select, [role=button], [role=link]")].slice(0, 80);
379
+ 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")];
46
380
  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) => ({ label: label(element), type: element.getAttribute("type") || element.tagName.toLowerCase(), name: element.getAttribute("name") || "" }));
48
- const text = bounded(document.body?.innerText || "", 2e3);
49
- return { url: location.href, title: bounded(document.title, 180), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };
381
+ const forms = [...document.querySelectorAll("input, textarea, select")].map((element) => ({
382
+ label: label(element),
383
+ type: element.getAttribute("type") || element.tagName.toLowerCase(),
384
+ name: element.getAttribute("name") || "",
385
+ ...element instanceof HTMLSelectElement ? { options: [...element.options].map((option) => clean(option.textContent || option.value)) } : {}
386
+ }));
387
+ const text = clean(document.body?.innerText || "");
388
+ return { schemaversion: 2, url: location.href, title: clean(document.title), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };
389
+ }
390
+ function extractcontent(targetselector) {
391
+ if (!targetselector) {
392
+ const links = [...document.querySelectorAll("a[href]")].map((element) => {
393
+ const href = element instanceof HTMLAnchorElement ? element.getAttribute("href") ?? "" : "";
394
+ return { text: element.textContent?.trim() ?? "", href };
395
+ });
396
+ return { ok: true, summary: `Extracted ${links.length} link entries.`, details: { links } };
397
+ }
398
+ const target = document.querySelector(targetselector);
399
+ if (!target) return { ok: false, summary: "Extraction target is no longer available." };
400
+ const text = target.textContent ?? "";
401
+ return { ok: true, summary: `Extracted ${text.length} characters of content.`, details: { text } };
402
+ }
403
+ function scrolltarget(target) {
404
+ target.scrollIntoView({ block: "center", inline: "nearest", behavior: "auto" });
405
+ return { ok: true, summary: `Scrolled ${label(target) || target.tagName.toLowerCase()} into view.` };
406
+ }
407
+ function hovertarget(target) {
408
+ for (const type of ["pointerover", "mouseover", "pointerenter"]) {
409
+ target.dispatchEvent(new PointerEvent(type, { bubbles: type !== "pointerenter", cancelable: true, composed: true }));
410
+ }
411
+ target.dispatchEvent(new MouseEvent("mouseenter", { bubbles: false, cancelable: true }));
412
+ return { ok: true, summary: `Hover events delivered to ${label(target) || target.tagName.toLowerCase()}.` };
50
413
  }
414
+ function selectoption(target, value) {
415
+ if (!(target instanceof HTMLSelectElement)) return { ok: false, summary: "Target is not a select element." };
416
+ const option = [...target.options].find((candidate) => candidate.value === value || candidate.textContent?.trim() === value);
417
+ if (!option) return { ok: false, summary: "Reviewed option is not part of the select element." };
418
+ target.value = option.value;
419
+ target.dispatchEvent(new Event("input", { bubbles: true }));
420
+ target.dispatchEvent(new Event("change", { bubbles: true }));
421
+ return { ok: true, summary: `Selected ${clean(option.textContent || option.value)}.` };
422
+ }
423
+ var readkinds = /* @__PURE__ */ new Set(["readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "waitfor", "waittext", "highlight"]);
424
+ var mutatingkinds = /* @__PURE__ */ new Set(["presskey", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "setattribute", "removeattribute", "writestorage", "evaluate", "fullscreen"]);
51
425
  function performstep(step, expectedorigin) {
52
426
  if (location.origin !== expectedorigin) return { ok: false, summary: "Page origin changed before action." };
53
427
  if (step.kind === "observe") return { ok: true, summary: "Observation completed." };
428
+ if (step.kind === "wait") {
429
+ const requested = step.value ? Number.parseInt(step.value, 10) : 250;
430
+ const duration = Number.isFinite(requested) && requested > 0 ? requested : 0;
431
+ return new Promise((resolve) => window.setTimeout(() => resolve({ ok: true, summary: `Reviewed wait of ${duration} milliseconds completed.` }), duration));
432
+ }
433
+ if (step.kind === "extract") return extractcontent(step.target);
54
434
  if (step.kind === "navigate") {
55
435
  if (!step.value || new URL(step.value).origin !== expectedorigin) return { ok: false, summary: "Navigation target is outside the approved origin." };
56
436
  location.assign(step.value);
57
437
  return { ok: true, summary: "Navigation request sent." };
58
438
  }
439
+ if (step.kind === "reload") {
440
+ location.reload();
441
+ return { ok: true, summary: "Page reload requested." };
442
+ }
443
+ if (step.kind === "back") {
444
+ history.back();
445
+ return { ok: true, summary: "History back requested." };
446
+ }
447
+ if (step.kind === "forward") {
448
+ history.forward();
449
+ return { ok: true, summary: "History forward requested." };
450
+ }
451
+ if (step.kind === "scrollpage") {
452
+ const options = stepoptions(step);
453
+ window.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
454
+ return { ok: true, summary: "Window scrolled by the reviewed amounts." };
455
+ }
456
+ if (step.kind === "scrollend") {
457
+ window.scrollTo(0, document.documentElement.scrollHeight);
458
+ return { ok: true, summary: "Window scrolled to the page end." };
459
+ }
460
+ if (step.kind === "scrolltop") {
461
+ window.scrollTo(0, 0);
462
+ return { ok: true, summary: "Window scrolled to the page top." };
463
+ }
464
+ if (readkinds.has(step.kind)) return runpageread(step, step.target ? document.querySelector(step.target) : null);
465
+ if (mutatingkinds.has(step.kind)) return runpageaction(step, step.target ? document.querySelector(step.target) : null);
59
466
  if (!step.target) return { ok: false, summary: "Action target is absent." };
60
467
  const target = document.querySelector(step.target);
61
468
  if (!(target instanceof HTMLElement)) return { ok: false, summary: "Action target is no longer available." };
469
+ if (step.kind === "scrollby") {
470
+ const options = stepoptions(step);
471
+ target.scrollBy({ left: typeof options.x === "number" ? options.x : 0, top: typeof options.y === "number" ? options.y : 600, behavior: "auto" });
472
+ return { ok: true, summary: "Container scrolled by the reviewed amounts." };
473
+ }
62
474
  if (step.kind === "focus") {
63
475
  target.focus();
64
476
  return { ok: true, summary: "Target focused." };
@@ -68,6 +480,9 @@
68
480
  target.click();
69
481
  return { ok: true, summary: "Reviewed click completed." };
70
482
  }
483
+ if (step.kind === "scroll") return scrolltarget(target);
484
+ if (step.kind === "hover") return hovertarget(target);
485
+ if (step.kind === "select") return selectoption(target, step.value ?? "");
71
486
  if (step.kind === "type") {
72
487
  if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return { ok: false, summary: "Target cannot receive text." };
73
488
  if (typeof step.value !== "string") return { ok: false, summary: "Approved text is absent." };