@wenathlan/extension 1.1.37 → 1.1.39

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -2,18 +2,20 @@
2
2
 
3
3
  Devthink is a **consent-first browser-agent bridge** distributed as a TypeScript library and a Chromium Manifest V3 extension. It turns a user-provided browser objective into a bounded, reviewable plan. The user must start the active-tab session and approve the plan before any page action reaches the browser.
4
4
 
5
- Version: **1.1.37**. License: **GPL-3.0-only**. The repository is `wenathlan/extension`; the npm-compatible scoped package identifier is `@wenathlan/extension`.
5
+ Version: **1.1.39**. License: **GPL-3.0-only**. The repository is `wenathlan/extension`; the npm-compatible scoped package identifier is `@wenathlan/extension`.
6
6
 
7
7
  ## What it does
8
8
 
9
- | Capability | Behavior in 1.1.37 |
9
+ | Capability | Behavior in 1.1.39 |
10
10
  | --- | --- |
11
11
  | Active-tab session | The user starts a short-lived session for one HTTPS tab and one origin; the session records its origin grants. |
12
12
  | Page observation | The extension captures the complete semantic inventory: every interactive element, every form control, every select option and the full page text. |
13
13
  | Plan proposal | A local plan can be created immediately; an optional user-configured HTTPS endpoint can return a typed plan proposal of any length. |
14
14
  | Review gate | Every remote proposal starts in `pending`; it cannot reach the page bridge before explicit approval. |
15
- | Browser tools | Reviewed plans cover two hundred one action kinds: pointer paths and coordinate clicks, text, aria, name, xpath, index and point target resolution, timed typing, key holds, sliders, dates, colors, shadow dom piercing, iframe entry, dialog answering, retry rules, a complete read vocabulary with clickable maps and verification reads, page mutation under review, and browser-level tab, window, zoom, snapshot and download commands. |
15
+ | Browser tools | Reviewed plans cover two hundred thirty one action kinds: pointer paths and coordinate clicks, text, aria, name, xpath, index and point target resolution, timed typing, key holds, sliders, dates, colors, shadow dom piercing, iframe entry, dialog answering, retry rules, a complete read vocabulary with clickable maps and verification reads, page mutation under review, and browser-level tab, window, zoom, snapshot and download commands. |
16
16
  | Forms and data | The agent fills forms with review: `fillform` fills from a structured record of field matches, kinds and values, `filllabel` and `fillplaceholder` match controls by label, placeholder or aria label, `detectfields` reports the form map with field kinds, `generatevalues` seeds locale aware values per field kind while real looking card numbers and personal identifiers are refused, `saveprofiles` stores profiles behind origin grants, `asksubmit` opens the submission prompt with the values diff, `submitform` submits only under an approved consent ref, `readerrors` associates inline validation messages with fields, `retryform` retries with reviewed backoff windows, `runwizard`, `selectchain`, `picktypeahead` and `pickdate` drive wizards, dependent selects, typeaheads and calendars, `attachfile` attaches run store artifacts, `fillcard`, `fillcode` and `consentpassword` handle payment and secret fields behind explicit consent, `skiphoneypot` skips trap fields, `detectlogin` and `detecttemplate` flag login, signup and checkout shapes, and `handoffcaptcha` pauses the plan and hands control back to the user. |
17
+ | Files and downloads | The agent moves bytes with review: `batchdownload` downloads reviewed url lists in waves under a user configured concurrent window with per file states and sequence suffixes for filename conflicts, `pausedownload` and `resumedownload` walk the queue through the downloads api, `verifydownload` compares state, size and checksum against reviewed expectations, `interceptmime` arms include, exclude and deny default mime filters that reroute matching downloads into quarantine, `exportnetlog` exports the step correlated network log with every header value redacted, `readclipboard` needs an approved single use consent prompt per read and never persists the payload text, `writeclipboard` writes reviewed text through the page bridge with payload hashes for audit, `copyscreen` copies the visible tab to the clipboard, `quarantinedownload` and `scanvirus` keep files outside the downloads folder until a clean scan verdict releases them, `namecaptures` stamps task, step and sequence filenames with per task counters, and `cleanupartifacts` sweeps artifacts by reviewed age and kind rules while keeping open review references. |
18
+ | Datasets and exports | The agent turns pages into data with audit: `scrapetable` reads tables into normalized column keys with rowspan and colspan expansion and nested child datasets, `paginateextract` follows next controls while waiting for fresh rows, `mergepages` aligns columns across pages, `transformvalues` applies reviewed expressions with per rule error surfacing, `deduperows` deduplicates by reviewed keys, `stamplerows` stamps every row with its source url, timestamp and step ref, `previewgrid` previews the grid before export, `importcsv` parses quoted csv with column mappings for fill loops, `looprows` interpolates `{{column}}` row variables into the wrapped inner step, `exportcsv`, `exportjson` and `exportexcel` write checksummed artifacts into the task artifact store with provenance records, `copytable` uses the clipboard under the optional clipboardWrite capability, `pushsheets` pushes only to a reviewed HTTPS endpoint behind its origin grant and the explicit reviewed flag, `streamdisk` streams chunk by chunk with backpressure and a persisted resume state, `resumeextract` continues an interrupted extraction from its stored cursor, and `logprovenance` logs provenance for audit; exports refuse to leave local memory while the session origin grants do not cover the active origin. |
17
19
  | Tabs and windows command | The agent commands the whole browser surface with review: `querytabs` resolves reviewed url, title, id and pattern matchers against the live tab set, `closepattern` closes matching tabs only under the explicit reviewed flag, `pintab`, `mutetab`, `movetab`, `movetabwindow`, `duplicatetab`, `reloadtabs`, `discardtab`, `zoomin`, `zoomout` and `switchtab` mutate tabs, `grouptabs`, `colorgroup` and `collapsegroup` keep group registries, `savelayout` and `restorelayout` save and restore named tab layouts with window bounds, `snapshotsession` and `reopenrun` capture and reopen whole runs, `watchtab` observes title, activation and closure events, `findclones`, `searchtabs` and `listaudio` enumerate the surface read only, `badgetab` and `attachmeta` route task state per tab, and `maximizewindow`, `minimizewindow`, `restorewindow`, `focuswindow`, `scratchwindow` and `incognitowindow` command windows with incognito separated from grant inheritance. |
18
20
  | Navigation mastery | The agent moves anywhere with review: `openlink`, `openprivate`, `reopentab`, `deeplink` and `openclipboard` open reviewed targets in resolved containers, `followlink`, `spanav`, `spawait`, `waitload` and `waiturl` navigate pages and single page app routes, `rewritequery` and `setfragment` edit the current url, `navlist` walks a reviewed url list with per entry progress, `navprofile` applies per site wait profiles, `checksafe` verifies urls before unreviewed origins open, `batchopen` opens curated lists with per url safety states, `navrate` enforces per domain rate limits with user configured windows and ceilings, and the navigation trail, redirect chains and final urls stay recorded for audit. |
19
21
  | Observation depth | The agent sees the whole page read only: the accessibility tree beside the dom snapshot, reader views, visible text, outlines, selections, open graph and embedded json state, plus detected lists, tables, pagination, infinite scroll, virtualization, lazy images, sticky overlays and scroll locks offered as plan suggestions. |
@@ -51,7 +53,7 @@ The endpoint field intentionally has no default URL. Enter a URL such as `https:
51
53
 
52
54
  ```json
53
55
  {
54
- "version": "1.1.37",
56
+ "version": "1.1.39",
55
57
  "objective": "User supplied objective",
56
58
  "session": { "id": "uuid", "tabid": 1, "origin": "https://example.com" },
57
59
  "observation": { "schemaversion": 3, "url": "https://example.com/path", "interactive": [] },
package/dist/index.js CHANGED
@@ -520,21 +520,235 @@ var sessionmemory = class {
520
520
  async getcodevalue() {
521
521
  return this.adapter.get("codevalue");
522
522
  }
523
+ /** Stores one dataset with its column specs and rows, replacing the previous record of that id. */
524
+ async setdataset(value) {
525
+ await this.adapter.set(`dataset${value.id}`, value);
526
+ const ids = (await this.adapter.get("datasets") ?? []).filter((id) => id !== value.id);
527
+ await this.adapter.set("datasets", [value.id, ...ids]);
528
+ }
529
+ /** Returns one stored dataset by its id with its column specs and row count. */
530
+ async getdataset(id) {
531
+ return this.adapter.get(`dataset${id}`);
532
+ }
533
+ /** Returns every stored dataset id, newest first. */
534
+ async getdatasets() {
535
+ const ids = await this.adapter.get("datasets") ?? [];
536
+ const records = [];
537
+ for (const id of ids) {
538
+ const record2 = await this.adapter.get(`dataset${id}`);
539
+ if (record2) records.push(record2);
540
+ }
541
+ return records;
542
+ }
543
+ /** Stores one imported csv dataset for fill loops beside the dataset store. */
544
+ async addimport(value) {
545
+ await this.setdataset(value);
546
+ const ids = (await this.adapter.get("imports") ?? []).filter((id) => id !== value.id);
547
+ await this.adapter.set("imports", [value.id, ...ids]);
548
+ }
549
+ /** Returns every imported csv dataset for fill loops, newest first. */
550
+ async getimports() {
551
+ const ids = await this.adapter.get("imports") ?? [];
552
+ const records = [];
553
+ for (const id of ids) {
554
+ const record2 = await this.adapter.get(`dataset${id}`);
555
+ if (record2) records.push(record2);
556
+ }
557
+ return records;
558
+ }
559
+ /** Stores one extraction session with its cursor and page history, replacing the previous session of that id. */
560
+ async setextractsession(value) {
561
+ await this.adapter.set(`extract${value.id}`, value);
562
+ const ids = (await this.adapter.get("extracts") ?? []).filter((id) => id !== value.id);
563
+ await this.adapter.set("extracts", [value.id, ...ids]);
564
+ }
565
+ /** Returns every extraction session with its cursor and page history, newest first. */
566
+ async getextractsessions() {
567
+ const ids = await this.adapter.get("extracts") ?? [];
568
+ const records = [];
569
+ for (const id of ids) {
570
+ const record2 = await this.adapter.get(`extract${id}`);
571
+ if (record2) records.push(record2);
572
+ }
573
+ return records;
574
+ }
575
+ /** Records one provenance record of an exported artifact. */
576
+ async addprovenance(record2) {
577
+ const records = await this.getprovenances();
578
+ await this.adapter.set("provenances", [record2, ...records]);
579
+ }
580
+ /** Returns every provenance record per exported artifact, newest first. */
581
+ async getprovenances() {
582
+ return await this.adapter.get("provenances") ?? [];
583
+ }
584
+ /** Stores the transform rules and dedupe keys of one task, replacing the previous record of that task. */
585
+ async settaskrules(value) {
586
+ const records = (await this.adapter.get("taskrules") ?? []).filter((item) => item.taskid !== value.taskid);
587
+ await this.adapter.set("taskrules", [value, ...records]);
588
+ }
589
+ /** Returns the transform rules and dedupe keys per task, newest first. */
590
+ async gettaskrules() {
591
+ return await this.adapter.get("taskrules") ?? [];
592
+ }
593
+ /** Stores one stream chunk state for resume, replacing the previous state of that dataset. */
594
+ async setstream(state) {
595
+ const records = (await this.adapter.get("streams") ?? []).filter((item) => item.datasetid !== state.datasetid);
596
+ await this.adapter.set("streams", [state, ...records]);
597
+ }
598
+ /** Returns every stream chunk state persisted for resume, newest first. */
599
+ async getstreams() {
600
+ return await this.adapter.get("streams") ?? [];
601
+ }
602
+ /** Stores one reviewed sheet endpoint config behind its origin grant, replacing the previous config of that origin. */
603
+ async setsheetendpoint(config) {
604
+ const records = (await this.adapter.get("sheetendpoints") ?? []).filter((item) => item.origin !== config.origin);
605
+ await this.adapter.set("sheetendpoints", [config, ...records]);
606
+ }
607
+ /** Returns every reviewed sheet endpoint config, newest first. */
608
+ async getsheetendpoints() {
609
+ return await this.adapter.get("sheetendpoints") ?? [];
610
+ }
611
+ /** Records one exported data artifact; artifact retention is a user setting and an absent setting keeps every artifact. */
612
+ async addexport(artifact) {
613
+ const records = await this.getexports();
614
+ const combined = [artifact, ...records.filter((item) => item.id !== artifact.id)];
615
+ const retention = (await this.getsettings())?.artifactretention;
616
+ await this.adapter.set("exports", retention === void 0 ? combined : combined.slice(0, retention));
617
+ }
618
+ /** Returns every exported data artifact with its content and checksum, newest first. */
619
+ async getexports() {
620
+ return await this.adapter.get("exports") ?? [];
621
+ }
622
+ /** Removes one exported data artifact by id and reports whether it existed. */
623
+ async removeexport(id) {
624
+ const records = await this.getexports();
625
+ const remaining = records.filter((item) => item.id !== id);
626
+ await this.adapter.set("exports", remaining);
627
+ return remaining.length !== records.length;
628
+ }
629
+ /** Removes one run store artifact by id and reports whether it existed. */
630
+ async removeartifact(id) {
631
+ const records = await this.getartifacts();
632
+ const remaining = records.filter((item) => item.id !== id);
633
+ await this.adapter.set("artifacts", remaining);
634
+ return remaining.length !== records.length;
635
+ }
636
+ /** Stores one batch download file record with its state, path and checksum, replacing the previous record of that id. */
637
+ async setdownload(record2) {
638
+ const records = (await this.adapter.get("downloads") ?? []).filter((item) => item.id !== record2.id);
639
+ await this.adapter.set("downloads", [record2, ...records]);
640
+ }
641
+ /** Returns every batch download file record with its state, path and checksum, newest first. */
642
+ async getdownloads() {
643
+ return await this.adapter.get("downloads") ?? [];
644
+ }
645
+ /** Records one captured network log record; netlog retention is a user setting and an absent value keeps every record. */
646
+ async addnetlog(record2) {
647
+ const records = await this.getnetlog();
648
+ const combined = [record2, ...records];
649
+ const retention = (await this.getsettings())?.netlogretention;
650
+ await this.adapter.set("netlog", retention === void 0 ? combined : combined.slice(0, retention));
651
+ }
652
+ /** Returns the captured network log of the run with its step correlation, newest first. */
653
+ async getnetlog() {
654
+ return await this.adapter.get("netlog") ?? [];
655
+ }
656
+ /** Stores one clipboard consent record with its prompt and origin, replacing the previous record of that id. */
657
+ async setclipconsent(record2) {
658
+ const records = (await this.adapter.get("clipconsents") ?? []).filter((item) => item.id !== record2.id);
659
+ await this.adapter.set("clipconsents", [record2, ...records]);
660
+ }
661
+ /** Returns every clipboard consent record with its prompt and origin, newest first. */
662
+ async getclipconsents() {
663
+ return await this.adapter.get("clipconsents") ?? [];
664
+ }
665
+ /** Records one clipboard entry hash with its origin provenance; the payload text itself never persists. */
666
+ async addclip(entry) {
667
+ const records = await this.getclips();
668
+ await this.adapter.set("clips", [entry, ...records]);
669
+ }
670
+ /** Returns every clipboard entry hash with its kind and origin provenance, newest first. */
671
+ async getclips() {
672
+ return await this.adapter.get("clips") ?? [];
673
+ }
674
+ /** Stores one quarantine entry with its scan verdict, replacing the previous entry of that id. */
675
+ async setquarantine(entry) {
676
+ const records = (await this.adapter.get("quarantines") ?? []).filter((item) => item.id !== entry.id);
677
+ await this.adapter.set("quarantines", [entry, ...records]);
678
+ }
679
+ /** Returns every quarantine entry with its scan verdict and release ref, newest first. */
680
+ async getquarantines() {
681
+ return await this.adapter.get("quarantines") ?? [];
682
+ }
683
+ /** Stores the reviewed cleanup rule set of the run, replacing the previous set. */
684
+ async setcleanuprules(rules) {
685
+ return this.adapter.set("cleanuprules", rules);
686
+ }
687
+ /** Returns the reviewed cleanup rule set of the run. */
688
+ async getcleanuprules() {
689
+ return await this.adapter.get("cleanuprules") ?? [];
690
+ }
691
+ /** Records one cleanup run in the run history. */
692
+ async addcleanuprun(run) {
693
+ const records = await this.getcleanupruns();
694
+ await this.adapter.set("cleanupruns", [run, ...records]);
695
+ }
696
+ /** Returns every cleanup run history record with removed and kept counts, newest first. */
697
+ async getcleanupruns() {
698
+ return await this.adapter.get("cleanupruns") ?? [];
699
+ }
700
+ /** Stores the capture naming counters of one task, replacing the previous counters of that task. */
701
+ async setcapturecounter(counter) {
702
+ const records = (await this.adapter.get("capturecounters") ?? []).filter((item) => item.taskid !== counter.taskid);
703
+ await this.adapter.set("capturecounters", [counter, ...records]);
704
+ }
705
+ /** Returns every stored capture naming counter per task, newest first. */
706
+ async getcapturecounters() {
707
+ return await this.adapter.get("capturecounters") ?? [];
708
+ }
709
+ /** Replaces the artifact inventory the cleanup sweeper plans against. */
710
+ async setinventory(entries) {
711
+ return this.adapter.set("inventory", entries);
712
+ }
713
+ /** Returns the artifact inventory with sizes and ages for the cleanup sweeper. */
714
+ async getinventory() {
715
+ return await this.adapter.get("inventory") ?? [];
716
+ }
717
+ /** Stores one user configured virus scanning hook, replacing the previous hook of that scanner name. */
718
+ async setscanhook(config) {
719
+ const records = (await this.adapter.get("scanhooks") ?? []).filter((item) => item.scanner !== config.scanner);
720
+ await this.adapter.set("scanhooks", [config, ...records]);
721
+ }
722
+ /** Returns every configured virus scanning hook, newest first. */
723
+ async getscanhooks() {
724
+ return await this.adapter.get("scanhooks") ?? [];
725
+ }
726
+ /** Stores the armed mime interception filters of the run, newest first. */
727
+ async setmimefilters(filters) {
728
+ return this.adapter.set("mimefilters", filters);
729
+ }
730
+ /** Returns the armed mime interception filters of the run, newest first. */
731
+ async getmimefilters() {
732
+ return await this.adapter.get("mimefilters") ?? [];
733
+ }
523
734
  };
524
735
  function randomid() {
525
736
  return crypto.randomUUID();
526
737
  }
527
738
 
528
739
  // policy.ts
529
- var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword"]);
740
+ var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select", "presskey", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "reload", "back", "forward", "writestorage", "setattribute", "removeattribute", "evaluate", "tabcreate", "tabactivate", "tabclose", "tabreload", "windowcreate", "windowclose", "windowresize", "downloadfile", "clickpoint", "shiftclick", "dismissdialog", "enterframe", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "openlink", "openprivate", "reloadcache", "stopnav", "followlink", "spanav", "rewritequery", "setfragment", "navlist", "navprofile", "handleauth", "printpdf", "prefetch", "preconnect", "deeplink", "reopentab", "pausenav", "navrate", "openclipboard", "batchopen", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "restorelayout", "reopenrun", "badgetab", "fillform", "filllabel", "fillplaceholder", "submitform", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcard", "fillcode", "consentpassword", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk", "paginateextract", "resumeextract", "batchdownload", "pausedownload", "resumedownload", "interceptmime", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "cleanupartifacts"]);
530
741
  var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
531
- var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha"]);
742
+ var readactions = /* @__PURE__ */ new Set(["observe", "inspect", "extract", "wait", "waitfor", "waittext", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "readlinks", "readimages", "readmeta", "readforms", "readstorage", "highlight", "tablist", "windowlist", "tabsnapshot", "mapclicks", "verifyvisible", "verifyenabled", "resolvexpath", "a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "watchmutate", "waitquiet", "watchbanner", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "diffsnapshots", "readselection", "watchfocus", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector", "waitload", "waiturl", "spawait", "detecthttp", "readredirects", "readfinalurl", "trailaudit", "navintent", "checksafe", "querytabs", "watchtab", "findclones", "searchtabs", "listaudio", "snapshotsession", "savelayout", "attachmeta", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "readerrors", "skiphoneypot", "detectlogin", "detecttemplate", "handoffcaptcha", "scrapetable", "importcsv", "looprows", "transformvalues", "deduperows", "mergepages", "stamplerows", "previewgrid", "logprovenance", "verifydownload", "exportnetlog", "namecaptures"]);
532
743
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
533
744
  var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
534
- var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword"]);
535
- var valueactions = /* @__PURE__ */ new Set(["presskey", "drag", "drop", "upload", "readattribute", "removeattribute", "waittext", "evaluate", "zoomset", "tabactivate", "tabclose", "tabreload", "windowclose", "windowresize", "tabcreate", "windowcreate", "downloadfile", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "chooseradio", "setslider", "setdate", "setcolor", "followlink", "setfragment", "handleauth", "navintent", "openclipboard", "checksafe", "reopentab", "spanav", "duplicatetab", "pintab", "mutetab", "movetab", "movetabwindow", "searchtabs", "badgetab", "attachmeta", "focuswindow", "maximizewindow", "minimizewindow", "restorewindow", "incognitowindow", "asksubmit", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword"]);
745
+ var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover", "clickdeep", "rightclick", "doubleclick", "drag", "drop", "upload", "clear", "check", "uncheck", "toggle", "submit", "readattribute", "readstyle", "readgeometry", "readvalue", "readtext", "readhtml", "countelements", "readtable", "highlight", "setattribute", "removeattribute", "waitfor", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "deriveselector", "fingerprintsection", "submitform", "retryform", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "scrapetable", "paginateextract"]);
746
+ var valueactions = /* @__PURE__ */ new Set(["presskey", "drag", "drop", "upload", "readattribute", "removeattribute", "waittext", "evaluate", "zoomset", "tabactivate", "tabclose", "tabreload", "windowclose", "windowresize", "tabcreate", "windowcreate", "downloadfile", "typetime", "appendtext", "setvalue", "typeedit", "keyhold", "keyrelease", "chooseradio", "setslider", "setdate", "setcolor", "followlink", "setfragment", "handleauth", "navintent", "openclipboard", "checksafe", "reopentab", "spanav", "duplicatetab", "pintab", "mutetab", "movetab", "movetabwindow", "searchtabs", "badgetab", "attachmeta", "focuswindow", "maximizewindow", "minimizewindow", "restorewindow", "incognitowindow", "asksubmit", "selectchain", "picktypeahead", "pickdate", "attachfile", "fillcode", "consentpassword", "pausedownload", "resumedownload", "verifydownload", "writeclipboard", "quarantinedownload", "scanvirus"]);
536
747
  var tabscommandactions = /* @__PURE__ */ new Set(["querytabs", "duplicatetab", "closepattern", "pintab", "mutetab", "movetab", "movetabwindow", "grouptabs", "colorgroup", "collapsegroup", "discardtab", "reloadtabs", "zoomin", "zoomout", "watchtab", "switchtab", "maximizewindow", "minimizewindow", "restorewindow", "focuswindow", "scratchwindow", "incognitowindow", "restoretab", "savelayout", "restorelayout", "findclones", "searchtabs", "badgetab", "attachmeta", "listaudio", "reopenrun", "snapshotsession"]);
537
748
  var formactions = /* @__PURE__ */ new Set(["fillform", "filllabel", "fillplaceholder", "detectfields", "generatevalues", "saveprofiles", "asksubmit", "submitform", "readerrors", "retryform", "runwizard", "selectchain", "picktypeahead", "pickdate", "attachfile", "handoffcaptcha", "fillcard", "fillcode", "consentpassword", "skiphoneypot", "detectlogin", "detecttemplate"]);
749
+ var datasetactions = /* @__PURE__ */ new Set(["scrapetable", "exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "importcsv", "looprows", "transformvalues", "deduperows", "paginateextract", "mergepages", "stamplerows", "previewgrid", "streamdisk", "resumeextract", "logprovenance"]);
750
+ var exportactions = /* @__PURE__ */ new Set(["exportcsv", "exportjson", "exportexcel", "copytable", "pushsheets", "streamdisk"]);
751
+ var filesactions = /* @__PURE__ */ new Set(["batchdownload", "pausedownload", "resumedownload", "verifydownload", "interceptmime", "exportnetlog", "readclipboard", "writeclipboard", "copyscreen", "quarantinedownload", "scanvirus", "namecaptures", "cleanupartifacts"]);
538
752
  var fieldkinds = ["text", "email", "phone", "date", "number", "select", "check", "radio", "file", "password", "card", "code"];
539
753
  var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
540
754
  var groupcolors = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
@@ -582,6 +796,19 @@ function islayoutkind(kind) {
582
796
  function isformkind(kind) {
583
797
  return formactions.has(kind);
584
798
  }
799
+ function isdatasetkind(kind) {
800
+ return datasetactions.has(kind);
801
+ }
802
+ function isexportkind(kind) {
803
+ return exportactions.has(kind);
804
+ }
805
+ function isfileskind(kind) {
806
+ return filesactions.has(kind);
807
+ }
808
+ function exportgranted(session, origin) {
809
+ if (!origingranted(session, origin)) return { allowed: false, reason: `The export of extracted data from ${origin} needs the session origin grants before it leaves local memory.` };
810
+ return { allowed: true };
811
+ }
585
812
  function validatefieldmatch(value) {
586
813
  if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed field match is required in options." };
587
814
  const match = value;
@@ -679,6 +906,164 @@ function validateformgrammar(step, options) {
679
906
  if (kind === "consentpassword" && !isnonempty(options.consentref)) return { allowed: false, reason: "A reviewed consent ref is required in options before any password is filled." };
680
907
  return { allowed: true };
681
908
  }
909
+ function validatetransformrule(value) {
910
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed transform rule with an expression, sources and a target is required in options." };
911
+ const rule = value;
912
+ const expression = rule.expression;
913
+ if (typeof expression !== "string" || !/^(trim|upper|lower|number|prefix|suffix|replace)(?::.+)?$/.test(expression)) return { allowed: false, reason: "The reviewed transform expression must be trim, upper, lower, number, prefix, suffix or replace with an optional argument." };
914
+ if (expression.startsWith("replace") && !expression.slice("replace".length).includes("=>")) return { allowed: false, reason: "The reviewed replace expression needs the from=>to separator." };
915
+ if (expression.startsWith("replace") && expression.slice("replace:".length).split("=>")[0] === "") return { allowed: false, reason: "The reviewed replace expression needs a non-empty from part." };
916
+ if (!Array.isArray(rule.sources) || rule.sources.length === 0 || !rule.sources.every((source) => isnonempty(source))) return { allowed: false, reason: "Every reviewed transform rule needs a non-empty list of source columns." };
917
+ if (!isnonempty(rule.target)) return { allowed: false, reason: "Every reviewed transform rule needs a non-empty target column." };
918
+ return { allowed: true };
919
+ }
920
+ function validatedatasetids(options, key) {
921
+ const ids = options[key];
922
+ if (!Array.isArray(ids) || ids.length === 0 || !ids.every((id) => isnonempty(id))) return { allowed: false, reason: `A reviewed non-empty list of dataset ids is required in options as ${key}.` };
923
+ return { allowed: true };
924
+ }
925
+ function validatedatagrammar(step, options, origin) {
926
+ const kind = step.kind;
927
+ if (kind === "scrapetable") {
928
+ if (options.name !== void 0 && !isnonempty(options.name)) return { allowed: false, reason: "The reviewed dataset name must be a non-empty string." };
929
+ if (options.rowlimit !== void 0 && (typeof options.rowlimit !== "number" || !Number.isInteger(options.rowlimit) || options.rowlimit < 1)) return { allowed: false, reason: "The reviewed row limit must be a positive integer with no code ceiling." };
930
+ }
931
+ if (kind === "paginateextract") {
932
+ if (!isnonempty(options.next)) return { allowed: false, reason: "A reviewed next control selector is required in options." };
933
+ if (options.pages !== void 0 && (typeof options.pages !== "number" || !Number.isInteger(options.pages) || options.pages < 1)) return { allowed: false, reason: "The reviewed page count must be a positive integer with no code ceiling." };
934
+ if (!nonnegativeoption(options, "wait")) return { allowed: false, reason: "The reviewed row freshness wait must be zero or a positive number of milliseconds." };
935
+ }
936
+ if (kind === "exportcsv" || kind === "exportjson" || kind === "exportexcel" || kind === "copytable" || kind === "streamdisk") {
937
+ if (!isnonempty(options.dataset)) return { allowed: false, reason: "A reviewed dataset id is required in options." };
938
+ if (options.name !== void 0 && !isnonempty(options.name)) return { allowed: false, reason: "The reviewed artifact name must be a non-empty string." };
939
+ }
940
+ if (kind === "exportcsv" && options.delimiter !== void 0 && (typeof options.delimiter !== "string" || options.delimiter.length !== 1)) return { allowed: false, reason: "The reviewed csv delimiter must be a single character." };
941
+ if (kind === "streamdisk" && (typeof options.chunk !== "number" || !Number.isInteger(options.chunk) || options.chunk < 1)) return { allowed: false, reason: "The reviewed streaming chunk size must be a positive integer with no code ceiling." };
942
+ if (kind === "pushsheets") {
943
+ if (!isnonempty(options.dataset)) return { allowed: false, reason: "A reviewed dataset id is required in options." };
944
+ if (!isnonempty(options.sheet)) return { allowed: false, reason: "A reviewed sheet endpoint url is required in options." };
945
+ if (!ishttpsurl(options.sheet)) return { allowed: false, reason: "The reviewed sheet endpoint url must use HTTPS." };
946
+ if (options.reviewed !== true) return { allowed: false, reason: "The sheet push needs the explicit reviewed flag before any data leaves local memory." };
947
+ }
948
+ if (kind === "importcsv") {
949
+ if (typeof options.csv !== "string" || !options.csv.trim()) return { allowed: false, reason: "Reviewed csv content is required in options." };
950
+ if (options.name !== void 0 && !isnonempty(options.name)) return { allowed: false, reason: "The reviewed dataset name must be a non-empty string." };
951
+ if (options.mapping !== void 0) {
952
+ const mapping = options.mapping;
953
+ if (!mapping || typeof mapping !== "object" || Array.isArray(mapping) || !Object.values(mapping).every((item) => typeof item === "string")) return { allowed: false, reason: "The reviewed csv column mapping must be an object of string values." };
954
+ }
955
+ }
956
+ if (kind === "looprows") {
957
+ if (!isnonempty(options.dataset)) return { allowed: false, reason: "A reviewed dataset id is required in options." };
958
+ if (options.variable !== void 0 && !isnonempty(options.variable)) return { allowed: false, reason: "The reviewed row variable name must be a non-empty string." };
959
+ const inner = validateinnerstep(options, origin);
960
+ if (!inner.allowed) return inner;
961
+ }
962
+ if (kind === "transformvalues") {
963
+ if (!isnonempty(options.dataset)) return { allowed: false, reason: "A reviewed dataset id is required in options." };
964
+ const rules = options.rules;
965
+ if (!Array.isArray(rules) || rules.length === 0) return { allowed: false, reason: "A reviewed non-empty list of transform rules is required in options." };
966
+ for (const item of rules) {
967
+ const rulecheck = validatetransformrule(item);
968
+ if (!rulecheck.allowed) return rulecheck;
969
+ }
970
+ }
971
+ if (kind === "deduperows") {
972
+ if (!isnonempty(options.dataset)) return { allowed: false, reason: "A reviewed dataset id is required in options." };
973
+ const keys = options.keys;
974
+ if (!Array.isArray(keys) || keys.length === 0 || !keys.every((key) => isnonempty(key))) return { allowed: false, reason: "A reviewed non-empty list of dedupe column keys is required in options." };
975
+ }
976
+ if (kind === "mergepages") {
977
+ const listcheck = validatedatasetids(options, "datasets");
978
+ if (!listcheck.allowed) return listcheck;
979
+ }
980
+ if (kind === "stamplerows") {
981
+ if (!isnonempty(options.dataset)) return { allowed: false, reason: "A reviewed dataset id is required in options." };
982
+ if (options.url !== void 0 && !ishttpsurl(options.url)) return { allowed: false, reason: "The reviewed source url must use HTTPS." };
983
+ }
984
+ if (kind === "previewgrid") {
985
+ if (!isnonempty(options.dataset)) return { allowed: false, reason: "A reviewed dataset id is required in options." };
986
+ if (options.sample !== void 0 && (typeof options.sample !== "number" || !Number.isInteger(options.sample) || options.sample < 1)) return { allowed: false, reason: "The reviewed sample row count must be a positive integer with no code ceiling." };
987
+ }
988
+ if (kind === "resumeextract" && !isnonempty(options.session)) return { allowed: false, reason: "A reviewed extract session id is required in options." };
989
+ if (kind === "logprovenance" && !isnonempty(options.artifact)) return { allowed: false, reason: "A reviewed artifact id or name is required in options." };
990
+ return { allowed: true };
991
+ }
992
+ function validatedownloadspec(value) {
993
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed downloadspec with a url list is required in options." };
994
+ const spec = value;
995
+ if (!Array.isArray(spec.urls) || spec.urls.length === 0 || !spec.urls.every((url) => ishttpsurl(url))) return { allowed: false, reason: "The reviewed downloadspec needs a non-empty list of HTTPS urls." };
996
+ if (spec.filename !== void 0 && !isnonempty(spec.filename)) return { allowed: false, reason: "The reviewed downloadspec filename rule must be a non-empty string." };
997
+ if (spec.complete !== void 0 && spec.complete !== "size" && spec.complete !== "checksum") return { allowed: false, reason: "The reviewed downloadspec completion criterion must be size or checksum." };
998
+ return { allowed: true };
999
+ }
1000
+ function validatemimefilter(value) {
1001
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed mimefilter with include and exclude patterns is required in options." };
1002
+ const filter = value;
1003
+ if (!Array.isArray(filter.include) || filter.include.length === 0 || !filter.include.every((pattern) => isnonempty(pattern))) return { allowed: false, reason: "The reviewed mimefilter needs a non-empty list of include patterns." };
1004
+ if (filter.exclude !== void 0 && (!Array.isArray(filter.exclude) || !filter.exclude.every((pattern) => isnonempty(pattern)))) return { allowed: false, reason: "The reviewed mimefilter exclude patterns must be a list of non-empty strings." };
1005
+ if (filter.default !== "deny" && filter.default !== "allow") return { allowed: false, reason: "The reviewed mimefilter needs the deny or allow default for unlisted mime types." };
1006
+ return { allowed: true };
1007
+ }
1008
+ function validatecleanuprule(value) {
1009
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed cleanuprule with an age, a kind and a keep policy is required." };
1010
+ const rule = value;
1011
+ if (typeof rule.age !== "number" || !Number.isFinite(rule.age) || rule.age <= 0) return { allowed: false, reason: "The reviewed cleanup age window must be a positive number of milliseconds with no code ceiling." };
1012
+ if (!isnonempty(rule.kind)) return { allowed: false, reason: "The reviewed cleanup rule needs a non-empty artifact kind, or any to match every kind." };
1013
+ if (rule.keep !== "none" && rule.keep !== "latest" && rule.keep !== "all") return { allowed: false, reason: "The reviewed cleanup keep policy must be none, latest or all." };
1014
+ return { allowed: true };
1015
+ }
1016
+ function validatefilesgrammar(step, options) {
1017
+ const kind = step.kind;
1018
+ if (kind === "batchdownload") {
1019
+ const speccheck = validatedownloadspec(options.downloadspec);
1020
+ if (!speccheck.allowed) return speccheck;
1021
+ if (options.concurrent !== void 0 && (typeof options.concurrent !== "number" || !Number.isInteger(options.concurrent) || options.concurrent < 1)) return { allowed: false, reason: "The reviewed concurrent download window must be a positive integer with no code ceiling." };
1022
+ }
1023
+ if (kind === "pausedownload" || kind === "resumedownload" || kind === "verifydownload" || kind === "quarantinedownload" || kind === "scanvirus") {
1024
+ if (!isnonempty(step.value)) return { allowed: false, reason: "A reviewed download or quarantine reference is required." };
1025
+ if (kind === "verifydownload") {
1026
+ if (options.checksum !== void 0 && !isnonempty(options.checksum)) return { allowed: false, reason: "The reviewed expected checksum must be a non-empty string." };
1027
+ if (options.bytes !== void 0 && (typeof options.bytes !== "number" || !Number.isFinite(options.bytes) || options.bytes < 0)) return { allowed: false, reason: "The reviewed expected size must be zero or a positive number of bytes." };
1028
+ }
1029
+ if (kind === "scanvirus" && options.scanner !== void 0 && !isnonempty(options.scanner)) return { allowed: false, reason: "The reviewed scanner name must be a non-empty string." };
1030
+ if (kind === "quarantinedownload" && options.reason !== void 0 && !isnonempty(options.reason)) return { allowed: false, reason: "The reviewed quarantine reason must be a non-empty string." };
1031
+ }
1032
+ if (kind === "interceptmime") {
1033
+ const filtercheck = validatemimefilter(options.mimefilter);
1034
+ if (!filtercheck.allowed) return filtercheck;
1035
+ }
1036
+ if (kind === "readclipboard") {
1037
+ if (!isnonempty(options.consentref)) return { allowed: false, reason: "A clipboard read requires a reviewed consent ref of an approved consent prompt in options." };
1038
+ if (options.prompt !== void 0 && !isnonempty(options.prompt)) return { allowed: false, reason: "The reviewed clipboard consent prompt must be a non-empty string." };
1039
+ }
1040
+ if (kind === "exportnetlog" && options.stepid !== void 0 && !isnonempty(options.stepid)) return { allowed: false, reason: "The reviewed netlog step filter must be a non-empty step id." };
1041
+ if (kind === "namecaptures") {
1042
+ if (!isnonempty(options.task)) return { allowed: false, reason: "A reviewed task id is required in options for capture naming." };
1043
+ if (options.steps !== void 0 && (!Array.isArray(options.steps) || options.steps.length === 0 || !options.steps.every((item) => isnonempty(item)))) return { allowed: false, reason: "The reviewed capture steps must be a non-empty list of step ids when present." };
1044
+ if (options.extension !== void 0 && !isnonempty(options.extension)) return { allowed: false, reason: "The reviewed capture extension must be a non-empty string." };
1045
+ }
1046
+ if (kind === "cleanupartifacts" && options.rules !== void 0) {
1047
+ const rules = options.rules;
1048
+ if (!Array.isArray(rules) || rules.length === 0) return { allowed: false, reason: "The reviewed cleanup rules must be a non-empty list when present." };
1049
+ for (const item of rules) {
1050
+ const rulecheck = validatecleanuprule(item);
1051
+ if (!rulecheck.allowed) return rulecheck;
1052
+ }
1053
+ }
1054
+ return { allowed: true };
1055
+ }
1056
+ function clipboardconsentgranted(step) {
1057
+ let options = {};
1058
+ try {
1059
+ options = parseoptions(step);
1060
+ } catch {
1061
+ options = {};
1062
+ }
1063
+ const consentref = options.consentref;
1064
+ if (typeof consentref !== "string" || !consentref.trim()) return { allowed: false, reason: "A clipboard read requires a reviewed consent ref in options." };
1065
+ return { allowed: true };
1066
+ }
682
1067
  function submitreviewgranted(steps, submitid) {
683
1068
  const position = steps.findIndex((candidate) => candidate.id === submitid);
684
1069
  const asked = steps.some((candidate, index) => candidate.kind === "asksubmit" && (position === -1 || index < position));
@@ -813,7 +1198,7 @@ function validateinnerstep(options, origin) {
813
1198
  return { allowed: true };
814
1199
  }
815
1200
  if (typeof kind !== "string" || !kind.trim()) return { allowed: false, reason: "A reviewed step id or inline step kind is required in options." };
816
- if (kind === "retryaction" || kind === "enterframe") return { allowed: false, reason: "The reviewed inner step cannot be another wrapper kind." };
1201
+ if (kind === "retryaction" || kind === "enterframe" || kind === "looprows") return { allowed: false, reason: "The reviewed inner step cannot be another wrapper kind." };
817
1202
  if (!allowedactions.has(kind)) return { allowed: false, reason: "The reviewed inner step kind is unsupported." };
818
1203
  const inneroptions = options.options;
819
1204
  if (inneroptions !== void 0 && (!inneroptions || typeof inneroptions !== "object" || Array.isArray(inneroptions))) return { allowed: false, reason: "The reviewed inner step options must be an object." };
@@ -1197,6 +1582,14 @@ function validatestep(step, origin) {
1197
1582
  const formcheck = validateformgrammar(step, options);
1198
1583
  if (!formcheck.allowed) return formcheck;
1199
1584
  }
1585
+ if (isdatasetkind(step.kind)) {
1586
+ const datacheck = validatedatagrammar(step, options, origin);
1587
+ if (!datacheck.allowed) return datacheck;
1588
+ }
1589
+ if (isfileskind(step.kind)) {
1590
+ const filescheck = validatefilesgrammar(step, options);
1591
+ if (!filescheck.allowed) return filescheck;
1592
+ }
1200
1593
  if (step.kind === "tabcreate") {
1201
1594
  if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
1202
1595
  if (options.window !== void 0 && (typeof options.window !== "number" || !Number.isInteger(options.window) || options.window < 0)) return { allowed: false, reason: "The reviewed target window id must be a non-negative integer." };
@@ -1224,6 +1617,10 @@ function canexecute(input) {
1224
1617
  if (input.plan.expiresat <= now) return { allowed: false, reason: "The approved plan has expired." };
1225
1618
  if ((input.step.kind === "pierceshadow" || input.step.kind === "enterframe") && !origingranted(input.session, input.origin)) return { allowed: false, reason: "The shadow or frame step is outside the session origin grants." };
1226
1619
  if (input.step.kind === "readjson" && !origingranted(input.session, input.origin)) return { allowed: false, reason: "The json state read is outside the session origin grants." };
1620
+ if (isexportkind(input.step.kind)) {
1621
+ const exportgate = exportgranted(input.session, input.origin);
1622
+ if (!exportgate.allowed) return exportgate;
1623
+ }
1227
1624
  if (input.step.kind === "navlist") {
1228
1625
  let options = {};
1229
1626
  try {
@@ -1247,6 +1644,11 @@ function canexecute(input) {
1247
1644
  const consentgate = passwordconsentgranted(input.step);
1248
1645
  if (!consentgate.allowed) return consentgate;
1249
1646
  }
1647
+ if (input.step.kind === "readclipboard") {
1648
+ const clipgate = clipboardconsentgranted(input.step);
1649
+ if (!clipgate.allowed) return clipgate;
1650
+ }
1651
+ if (input.step.kind === "interceptmime" && !origingranted(input.session, input.origin)) return { allowed: false, reason: "The download interception is outside the session origin grants." };
1250
1652
  if (input.step.kind === "openlink" || input.step.kind === "openprivate" || input.step.kind === "batchopen" || input.step.kind === "prefetch" || input.step.kind === "deeplink" || input.step.kind === "reopentab") {
1251
1653
  let options = {};
1252
1654
  try {
@@ -1268,7 +1670,7 @@ function canexecute(input) {
1268
1670
  }
1269
1671
 
1270
1672
  // version.ts
1271
- var packageversion = "1.1.37";
1673
+ var packageversion = "1.1.39";
1272
1674
 
1273
1675
  // types.ts
1274
1676
  var protocolversion = packageversion;
@@ -1305,9 +1707,9 @@ function parseproposal(value, origin) {
1305
1707
  return step;
1306
1708
  });
1307
1709
  for (const step of steps) {
1308
- if (step.kind !== "retryaction" && step.kind !== "enterframe") continue;
1710
+ if (step.kind !== "retryaction" && step.kind !== "enterframe" && step.kind !== "looprows") continue;
1309
1711
  const options = parseoptions(step);
1310
- if (typeof options.stepid === "string" && !steps.some((candidate) => candidate.id === options.stepid)) throw new Error("A retry or frame wrapper references an unknown step id.");
1712
+ if (typeof options.stepid === "string" && !steps.some((candidate) => candidate.id === options.stepid)) throw new Error("A retry, frame or loop wrapper references an unknown step id.");
1311
1713
  }
1312
1714
  for (const step of steps) {
1313
1715
  if (step.kind !== "submitform" && step.kind !== "retryform") continue;
@@ -1386,12 +1788,38 @@ function errorreportresponse(input) {
1386
1788
  function wizardreport(input) {
1387
1789
  return { version: protocolversion, ...input.sessionid ? { sessionid: input.sessionid } : {}, wizards: input.wizards, picks: input.picks };
1388
1790
  }
1791
+ function datasetresponse(input) {
1792
+ const sample = Math.max(0, Math.floor(input.sample ?? 10));
1793
+ const payload = { ...input.dataset, rows: input.dataset.rows.slice(0, sample), totalrows: input.dataset.rows.length };
1794
+ return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, dataset: payload });
1795
+ }
1796
+ function extractionreport(input) {
1797
+ return { version: protocolversion, sessions: input.sessions };
1798
+ }
1799
+ function provenancereport(input) {
1800
+ return { version: protocolversion, records: input.records };
1801
+ }
1802
+ function transformgrammar(rules) {
1803
+ return JSON.stringify({ rules: rules.map((rule) => ({ expression: rule.expression, sources: rule.sources, target: rule.target })) });
1804
+ }
1805
+ function downloadreport(input) {
1806
+ return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, downloads: input.downloads });
1807
+ }
1808
+ function netlogreport(input) {
1809
+ return { version: protocolversion, records: input.records };
1810
+ }
1811
+ function quarantinereport(input) {
1812
+ return { version: protocolversion, entries: input.entries };
1813
+ }
1389
1814
  export {
1390
1815
  canexecute,
1816
+ datasetresponse,
1391
1817
  actionrisk as deriveactionrisk,
1392
1818
  diffresponse,
1819
+ downloadreport,
1393
1820
  errorreportresponse,
1394
1821
  eventresponse,
1822
+ extractionreport,
1395
1823
  formreportresponse,
1396
1824
  generatedvalueallowed,
1397
1825
  heldkeysreport,
@@ -1401,6 +1829,7 @@ export {
1401
1829
  layoutreport,
1402
1830
  mapresponse,
1403
1831
  navstateresponse,
1832
+ netlogreport,
1404
1833
  normalizeendpoint,
1405
1834
  observationmodeof,
1406
1835
  observationresponse,
@@ -1409,6 +1838,8 @@ export {
1409
1838
  passwordconsentgranted,
1410
1839
  profilegrantgranted,
1411
1840
  protocolversion,
1841
+ provenancereport,
1842
+ quarantinereport,
1412
1843
  randomid,
1413
1844
  requestbody,
1414
1845
  resolutionverdict,
@@ -1419,6 +1850,7 @@ export {
1419
1850
  submitreviewgranted,
1420
1851
  tabreportresponse,
1421
1852
  trailreport,
1853
+ transformgrammar,
1422
1854
  validatefieldmatch,
1423
1855
  validateformrecord,
1424
1856
  validatestep,