@wenathlan/extension 1.1.35 → 1.1.36

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,23 +2,24 @@
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.35**. License: **GPL-3.0-only**. The repository is `wenathlan/extension`; the npm-compatible scoped package identifier is `@wenathlan/extension`.
5
+ Version: **1.1.36**. 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.35 |
9
+ | Capability | Behavior in 1.1.36 |
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 one hundred forty seven 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 one hundred seventy nine 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
+ | 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. |
16
17
  | 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. |
17
18
  | 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. |
18
19
  | Watch vocabulary | `watchmutate`, `watchfocus` and `watchbanner` observe the page across a reviewed lifetime window with batched event records, `waitquiet` waits for network quiet under a reviewed threshold, `diffsnapshots` diffs two stored observation versions into added, removed and changed rows, and `deriveselector` ranks stable selector candidates with stability scores; registrations persist across service worker restarts. |
19
20
  | Target resolution | Steps address elements through reviewed `targetref` modes: css selector, visible text, aria role and name, accessible name, xpath, clickable map index or viewport point; ambiguous matches are refused with candidate lists and every resolution returns a matched element summary for review. |
20
21
  | Optional capabilities | `tabs`, `downloads`, `clipboardRead` and `clipboardWrite` are optional permissions; browser kinds check their capability, the review panel can request a grant and every grant is audited. |
21
- | Unlimited by choice | Wait durations, plan size, plan expiry, audit retention and outcome retention carry no code ceilings; every bound is a user choice. |
22
+ | Unlimited by choice | Wait durations, plan size, plan expiry, audit retention, outcome retention, zoom steps and the concurrent task tab ceiling carry no code ceilings; every bound is a user choice. |
22
23
  | Structured results | Step results carry structured JSON details, rendered in the side panel beside each reviewed step. |
23
24
  | Session pause | The user can pause and resume the active session; a paused session blocks every execution and preview while keeping the reviewed plan alive. |
24
25
  | Plan progress | Each reviewed step is tracked with its outcome history, completed steps are marked, plans close automatically once every step has executed, and prior history survives plan replacement. |
@@ -49,7 +50,7 @@ The endpoint field intentionally has no default URL. Enter a URL such as `https:
49
50
 
50
51
  ```json
51
52
  {
52
- "version": "1.1.35",
53
+ "version": "1.1.36",
53
54
  "objective": "User supplied objective",
54
55
  "session": { "id": "uuid", "tabid": 1, "origin": "https://example.com" },
55
56
  "observation": { "schemaversion": 3, "url": "https://example.com/path", "interactive": [] },
package/dist/index.js CHANGED
@@ -352,19 +352,105 @@ var sessionmemory = class {
352
352
  async setnavstate(tabid, state) {
353
353
  return this.adapter.set(`navstate${tabid}`, state);
354
354
  }
355
+ /** Stores one named tab layout with its window bounds and group states, replacing the previous layout of that name. */
356
+ async setlayout(layout) {
357
+ const records = (await this.getlayouts()).filter((item) => item.name !== layout.name);
358
+ await this.adapter.set("layouts", [layout, ...records]);
359
+ }
360
+ /** Returns one saved tab layout by name with its timestamp. */
361
+ async getlayout(name) {
362
+ return (await this.getlayouts()).find((item) => item.name === name);
363
+ }
364
+ /** Returns every saved tab layout with its window bounds and group states. */
365
+ async getlayouts() {
366
+ return await this.adapter.get("layouts") ?? [];
367
+ }
368
+ /** Stores one tab group definition with its color choice and member tabs, replacing the previous definition of that name. */
369
+ async settabgroup(group) {
370
+ const records = (await this.gettabgroups()).filter((item) => item.name !== group.name);
371
+ await this.adapter.set("tabgroups", [...records, group]);
372
+ }
373
+ /** Returns every stored tab group definition with its color choice, newest first. */
374
+ async gettabgroups() {
375
+ return await this.adapter.get("tabgroups") ?? [];
376
+ }
377
+ /** Records one tabmeta record with task provenance, replacing the previous metadata of that tab. */
378
+ async settabmeta(meta) {
379
+ const records = (await this.gettabmetas()).filter((item) => item.tabid !== meta.tabid);
380
+ await this.adapter.set("tabmetas", [...records, meta]);
381
+ }
382
+ /** Returns every stored tabmeta record with task provenance. */
383
+ async gettabmetas() {
384
+ return await this.adapter.get("tabmetas") ?? [];
385
+ }
386
+ /** Records one session snapshot of tabs and windows for later restore. */
387
+ async addsnapshot(snapshot) {
388
+ const records = await this.getsnapshots();
389
+ await this.adapter.set("snapshots", [snapshot, ...records]);
390
+ }
391
+ /** Returns every stored session snapshot, newest first. */
392
+ async getsnapshots() {
393
+ return await this.adapter.get("snapshots") ?? [];
394
+ }
395
+ /** Records one closed tab in the history kept for restoretab and reopenrun. */
396
+ async addclosedtab(tab) {
397
+ const records = await this.getclosedtabs();
398
+ await this.adapter.set("closedtabs", [tab, ...records]);
399
+ }
400
+ /** Returns the closed tab history, newest first. */
401
+ async getclosedtabs() {
402
+ return await this.adapter.get("closedtabs") ?? [];
403
+ }
404
+ /** Stores one badge state per task, replacing the previous badge of that task. */
405
+ async setbadge(badge) {
406
+ const records = (await this.getbadges()).filter((item) => item.taskid !== badge.taskid);
407
+ await this.adapter.set("badges", [...records, badge]);
408
+ }
409
+ /** Returns every stored badge state per task. */
410
+ async getbadges() {
411
+ return await this.adapter.get("badges") ?? [];
412
+ }
413
+ /** Records one tab event observed inside a reviewed watchtab registration. */
414
+ async addtabwatchevent(event) {
415
+ const records = await this.gettabwatchevents();
416
+ await this.adapter.set("tabwatchevents", [event, ...records]);
417
+ }
418
+ /** Returns the tab event stream of every reviewed watchtab registration, newest first. */
419
+ async gettabwatchevents() {
420
+ return await this.adapter.get("tabwatchevents") ?? [];
421
+ }
422
+ /** Returns the ids of the scratch windows opened for split work. */
423
+ async getscratchwindows() {
424
+ return await this.adapter.get("scratchwindows") ?? [];
425
+ }
426
+ /** Replaces the scratch window id list after one scratch window opens or closes. */
427
+ async setscratchwindows(ids) {
428
+ return this.adapter.set("scratchwindows", ids);
429
+ }
430
+ /** Returns the pinned control tab state with the live task feed. */
431
+ async getcontroltab() {
432
+ return this.adapter.get("controltab");
433
+ }
434
+ /** Replaces the pinned control tab state. */
435
+ async setcontroltab(state) {
436
+ return this.adapter.set("controltab", state);
437
+ }
355
438
  };
356
439
  function randomid() {
357
440
  return crypto.randomUUID();
358
441
  }
359
442
 
360
443
  // policy.ts
361
- 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"]);
444
+ 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"]);
362
445
  var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
363
- 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"]);
446
+ 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"]);
364
447
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
365
- var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus"]);
448
+ var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus", "watchtab"]);
366
449
  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"]);
367
- 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"]);
450
+ 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"]);
451
+ 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"]);
452
+ var layoutmutationactions = /* @__PURE__ */ new Set(["grouptabs", "colorgroup", "collapsegroup", "savelayout", "restorelayout"]);
453
+ var groupcolors = ["grey", "blue", "red", "yellow", "green", "pink", "purple", "cyan", "orange"];
368
454
  function normalizeendpoint(value) {
369
455
  const endpoint = new URL(value.trim());
370
456
  if (endpoint.protocol !== "https:") throw new Error("Devthink accepts HTTPS endpoints only.");
@@ -400,6 +486,16 @@ function parseoptions(step) {
400
486
  if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Step options must be a JSON object.");
401
487
  return parsed;
402
488
  }
489
+ function istabscommandkind(kind) {
490
+ return tabscommandactions.has(kind);
491
+ }
492
+ function islayoutkind(kind) {
493
+ return layoutmutationactions.has(kind);
494
+ }
495
+ function layoutmutationgranted(session, now) {
496
+ if (!session || session.stoppedat || session.expiresat <= now) return { allowed: false, reason: "Group and layout mutations stay inside the active session." };
497
+ return { allowed: true };
498
+ }
403
499
  function waitduration(step) {
404
500
  const requested = step.value ? Number.parseInt(step.value, 10) : 250;
405
501
  if (!Number.isFinite(requested) || requested < 0) throw new Error("Wait duration must be zero or a positive number of milliseconds.");
@@ -566,6 +662,104 @@ function validateratelimit(value) {
566
662
  if (typeof limit.ceiling !== "number" || !Number.isInteger(limit.ceiling) || limit.ceiling < 1) return { allowed: false, reason: "The reviewed ratelimit ceiling must be a positive integer with no code ceiling." };
567
663
  return { allowed: true };
568
664
  }
665
+ function validatetabquery(value) {
666
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed tabquery with at least one matcher is required in options." };
667
+ const query = value;
668
+ const hasmatcher = query.url !== void 0 || query.title !== void 0 || query.id !== void 0 || query.pattern !== void 0;
669
+ if (!hasmatcher) return { allowed: false, reason: "The reviewed tabquery needs a url, title, id or pattern matcher." };
670
+ if (query.url !== void 0 && !isnonempty(query.url)) return { allowed: false, reason: "The reviewed tabquery url matcher must be a non-empty string." };
671
+ if (query.title !== void 0 && !isnonempty(query.title)) return { allowed: false, reason: "The reviewed tabquery title matcher must be a non-empty string." };
672
+ if (query.pattern !== void 0 && !isnonempty(query.pattern)) return { allowed: false, reason: "The reviewed tabquery pattern matcher must be a non-empty string." };
673
+ if (query.id !== void 0 && (typeof query.id !== "number" || !Number.isInteger(query.id) || query.id < 0)) return { allowed: false, reason: "The reviewed tabquery id matcher must be a non-negative integer tab id." };
674
+ return { allowed: true };
675
+ }
676
+ function validategroupcolor(value) {
677
+ return typeof value === "string" && groupcolors.includes(value);
678
+ }
679
+ function validateidlist(options, key) {
680
+ const ids = options[key];
681
+ return Array.isArray(ids) && ids.length > 0 && ids.every((id) => typeof id === "number" && Number.isInteger(id) && id >= 0);
682
+ }
683
+ function validatetabsgrammar(step, options) {
684
+ const kind = step.kind;
685
+ if (kind === "querytabs" || kind === "closepattern") {
686
+ const querycheck = validatetabquery(options.tabquery);
687
+ if (!querycheck.allowed) return querycheck;
688
+ if (kind === "closepattern" && options.reviewed !== true) return { allowed: false, reason: "The close pattern needs the explicit reviewed flag before any tab closes." };
689
+ }
690
+ if (kind === "duplicatetab" || kind === "pintab" || kind === "mutetab" || kind === "movetab" || kind === "movetabwindow" || kind === "badgetab" || kind === "attachmeta") {
691
+ if (!isnumericid(step.value)) return { allowed: false, reason: "A numeric browser tab id is required." };
692
+ }
693
+ if (kind === "focuswindow" || kind === "maximizewindow" || kind === "minimizewindow" || kind === "restorewindow") {
694
+ if (!isnumericid(step.value)) return { allowed: false, reason: "A numeric browser window id is required." };
695
+ }
696
+ if (kind === "pintab" && typeof options.pinned !== "boolean") return { allowed: false, reason: "A reviewed pinned flag is required in options." };
697
+ if (kind === "mutetab" && typeof options.muted !== "boolean") return { allowed: false, reason: "A reviewed muted flag is required in options." };
698
+ if (kind === "movetab") {
699
+ if (typeof options.index !== "number" || !Number.isInteger(options.index) || options.index < 0) return { allowed: false, reason: "A reviewed non-negative target index is required in options." };
700
+ }
701
+ if (kind === "movetabwindow") {
702
+ if (typeof options.windowid !== "number" || !Number.isInteger(options.windowid) || options.windowid < 0) return { allowed: false, reason: "A reviewed target window id is required in options." };
703
+ }
704
+ if (kind === "grouptabs") {
705
+ const group = options.group;
706
+ if (!group || typeof group !== "object" || Array.isArray(group)) return { allowed: false, reason: "A reviewed group with a name is required in options." };
707
+ const spec = group;
708
+ if (!isnonempty(spec.name)) return { allowed: false, reason: "The reviewed group needs a non-empty name." };
709
+ if (!validategroupcolor(spec.color)) return { allowed: false, reason: "The reviewed group color must be a Chromium tab group color." };
710
+ if (!validateidlist(spec, "tabids")) return { allowed: false, reason: "The reviewed group needs a non-empty list of member tab ids." };
711
+ }
712
+ if (kind === "colorgroup") {
713
+ if (!isnonempty(options.name)) return { allowed: false, reason: "A reviewed group name is required in options." };
714
+ if (!validategroupcolor(options.color)) return { allowed: false, reason: "The reviewed group color must be a Chromium tab group color." };
715
+ }
716
+ if (kind === "collapsegroup") {
717
+ if (!isnonempty(options.name)) return { allowed: false, reason: "A reviewed group name is required in options." };
718
+ if (typeof options.collapsed !== "boolean") return { allowed: false, reason: "A reviewed collapsed flag is required in options." };
719
+ }
720
+ if (kind === "discardtab" || kind === "reloadtabs") {
721
+ if (!isnumericid(step.value) && !validateidlist(options, "tabs")) return { allowed: false, reason: "A numeric tab id or a reviewed list of tab ids is required." };
722
+ }
723
+ if (kind === "zoomin" || kind === "zoomout") {
724
+ if (options.step !== void 0 && (typeof options.step !== "number" || !Number.isFinite(options.step) || options.step <= 0)) return { allowed: false, reason: "The reviewed zoom step must be a positive number with no code ceiling." };
725
+ if (step.value !== void 0 && step.value !== "" && !isnumericid(step.value)) return { allowed: false, reason: "The reviewed zoom target must be a numeric tab id." };
726
+ }
727
+ if (kind === "switchtab") {
728
+ if (options.direction !== "next" && options.direction !== "previous") return { allowed: false, reason: "A reviewed switch direction of next or previous is required in options." };
729
+ }
730
+ if (kind === "restorewindow") {
731
+ const bounds = options.bounds;
732
+ if (bounds !== void 0) {
733
+ if (!bounds || typeof bounds !== "object" || Array.isArray(bounds)) return { allowed: false, reason: "The reviewed window bounds must be an object." };
734
+ const shape = bounds;
735
+ for (const field of ["left", "top", "width", "height"]) {
736
+ if (typeof shape[field] !== "number" || !Number.isFinite(shape[field])) return { allowed: false, reason: "The reviewed window bounds need numeric left, top, width and height." };
737
+ }
738
+ }
739
+ }
740
+ if (kind === "scratchwindow") {
741
+ if (step.value !== void 0 && step.value !== "" && !ishttpsurl(step.value)) return { allowed: false, reason: "The reviewed scratch window url must use HTTPS." };
742
+ }
743
+ if (kind === "incognitowindow" && !ishttpsurl(step.value)) return { allowed: false, reason: "A reviewed HTTPS url is required to open an incognito window." };
744
+ if (kind === "restoretab" && step.value !== void 0 && step.value !== "" && !ishttpsurl(step.value)) return { allowed: false, reason: "The reviewed restore url must use HTTPS." };
745
+ if (kind === "savelayout" || kind === "restorelayout") {
746
+ if (!isnonempty(options.name)) return { allowed: false, reason: "A reviewed layout name is required in options." };
747
+ }
748
+ if (kind === "badgetab") {
749
+ if (!isnonempty(options.label)) return { allowed: false, reason: "A reviewed badge label is required in options." };
750
+ if (options.taskid !== void 0 && !isnonempty(options.taskid)) return { allowed: false, reason: "The reviewed badge task id must be a non-empty string." };
751
+ }
752
+ if (kind === "attachmeta") {
753
+ const labels = options.labels;
754
+ const taskrefs = options.taskrefs;
755
+ const haslabels = Array.isArray(labels) && labels.length > 0 && labels.every((label) => isnonempty(label));
756
+ const hastaskrefs = Array.isArray(taskrefs) && taskrefs.length > 0 && taskrefs.every((ref) => isnonempty(ref));
757
+ if (!haslabels && !hastaskrefs) return { allowed: false, reason: "Reviewed labels or task refs are required in options to attach metadata." };
758
+ if (options.provenance !== void 0 && !isnonempty(options.provenance)) return { allowed: false, reason: "The reviewed provenance must be a non-empty string." };
759
+ }
760
+ if (kind === "reopenrun" && !isnonempty(options.run)) return { allowed: false, reason: "A reviewed run id is required in options to reopen its tabs." };
761
+ return { allowed: true };
762
+ }
569
763
  function validatestep(step, origin) {
570
764
  if (!allowedactions.has(step.kind)) return { allowed: false, reason: "Unsupported action kind." };
571
765
  if (!step.summary.trim()) return { allowed: false, reason: "A human-readable action summary is required." };
@@ -767,6 +961,20 @@ function validatestep(step, origin) {
767
961
  const listcheck = validateurllist(options, "urls");
768
962
  if (!listcheck.allowed) return listcheck;
769
963
  }
964
+ if (istabscommandkind(step.kind)) {
965
+ const tabscheck = validatetabsgrammar(step, options);
966
+ if (!tabscheck.allowed) return tabscheck;
967
+ }
968
+ if (step.kind === "tabcreate") {
969
+ if (options.background !== void 0 && typeof options.background !== "boolean") return { allowed: false, reason: "The reviewed background flag must be a boolean." };
970
+ 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." };
971
+ }
972
+ if (step.kind === "windowcreate") {
973
+ for (const field of ["left", "top", "width", "height"]) {
974
+ if (options[field] !== void 0 && (typeof options[field] !== "number" || !Number.isFinite(options[field]))) return { allowed: false, reason: `The reviewed window ${field} must be a number.` };
975
+ }
976
+ if (options.state !== void 0 && !["normal", "maximized", "minimized", "fullscreen"].includes(options.state)) return { allowed: false, reason: "The reviewed window state must be normal, maximized, minimized or fullscreen." };
977
+ }
770
978
  return { allowed: true };
771
979
  }
772
980
  function sessiongate(input) {
@@ -797,6 +1005,7 @@ function canexecute(input) {
797
1005
  if (!navigation.allowed) return navigation;
798
1006
  }
799
1007
  }
1008
+ if (islayoutkind(input.step.kind) && !layoutmutationgranted(input.session, now).allowed) return { allowed: false, reason: "Group and layout mutations stay inside the active session." };
800
1009
  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") {
801
1010
  let options = {};
802
1011
  try {
@@ -818,7 +1027,7 @@ function canexecute(input) {
818
1027
  }
819
1028
 
820
1029
  // version.ts
821
- var packageversion = "1.1.35";
1030
+ var packageversion = "1.1.36";
822
1031
 
823
1032
  // types.ts
824
1033
  var protocolversion = packageversion;
@@ -916,6 +1125,12 @@ function trailreport(input) {
916
1125
  function safetyresponse(input) {
917
1126
  return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, verdicts: input.verdicts });
918
1127
  }
1128
+ function tabreportresponse(input) {
1129
+ return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, report: input.report });
1130
+ }
1131
+ function layoutreport(input) {
1132
+ return { version: protocolversion, layouts: input.layouts };
1133
+ }
919
1134
  export {
920
1135
  canexecute,
921
1136
  actionrisk as deriveactionrisk,
@@ -924,6 +1139,7 @@ export {
924
1139
  heldkeysreport,
925
1140
  hostpattern,
926
1141
  iswatchkind,
1142
+ layoutreport,
927
1143
  mapresponse,
928
1144
  navstateresponse,
929
1145
  normalizeendpoint,
@@ -939,6 +1155,7 @@ export {
939
1155
  selectorresponse,
940
1156
  sessionmemory,
941
1157
  signalsreport,
1158
+ tabreportresponse,
942
1159
  trailreport,
943
1160
  validatestep,
944
1161
  validatetargetref