@wenathlan/extension 1.1.34 → 1.1.35

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,17 +2,18 @@
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.34**. License: **GPL-3.0-only**. The repository is `wenathlan/extension`; the npm-compatible scoped package identifier is `@wenathlan/extension`.
5
+ Version: **1.1.35**. 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.34 |
9
+ | Capability | Behavior in 1.1.35 |
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 eighteen 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 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. |
16
+ | 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. |
16
17
  | 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. |
17
18
  | 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. |
18
19
  | 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. |
@@ -48,7 +49,7 @@ The endpoint field intentionally has no default URL. Enter a URL such as `https:
48
49
 
49
50
  ```json
50
51
  {
51
- "version": "1.1.34",
52
+ "version": "1.1.35",
52
53
  "objective": "User supplied objective",
53
54
  "session": { "id": "uuid", "tabid": 1, "origin": "https://example.com" },
54
55
  "observation": { "schemaversion": 3, "url": "https://example.com/path", "interactive": [] },
package/dist/index.js CHANGED
@@ -238,19 +238,133 @@ var sessionmemory = class {
238
238
  async setsignals(signals) {
239
239
  return this.adapter.set("signals", signals);
240
240
  }
241
+ /** Appends one navigation trail entry of a session with its url, title, step ref and timestamp. */
242
+ async addtrailentry(sessionid, entry) {
243
+ const records = await this.gettrail(sessionid);
244
+ await this.adapter.set(`trail${sessionid}`, [...records, entry]);
245
+ }
246
+ /** Returns the navigation trail of a session, oldest first. */
247
+ async gettrail(sessionid) {
248
+ return await this.adapter.get(`trail${sessionid}`) ?? [];
249
+ }
250
+ /** Stores one wait profile for an origin with user configured values, replacing the previous profile of that origin. */
251
+ async setwaitprofile(record2) {
252
+ const records = (await this.getwaitprofiles()).filter((item) => item.origin !== record2.origin);
253
+ await this.adapter.set("waitprofiles", [...records, record2]);
254
+ }
255
+ /** Returns every stored wait profile with its origin and user configured values, newest first. */
256
+ async getwaitprofiles() {
257
+ return await this.adapter.get("waitprofiles") ?? [];
258
+ }
259
+ /** Records one navigation step with its redirect chain and final url. */
260
+ async addnavrecord(record2) {
261
+ const records = await this.getnavrecords();
262
+ await this.adapter.set("navrecords", [record2, ...records]);
263
+ }
264
+ /** Returns every stored navigation record with redirect chains and final urls, newest first. */
265
+ async getnavrecords() {
266
+ return await this.adapter.get("navrecords") ?? [];
267
+ }
268
+ /** Records one navigation intent detected from a plan for audit review. */
269
+ async addnavintent(record2) {
270
+ const records = await this.getnavintents();
271
+ await this.adapter.set("navintents", [record2, ...records]);
272
+ }
273
+ /** Returns every stored navigation intent record, newest first. */
274
+ async getnavintents() {
275
+ return await this.adapter.get("navintents") ?? [];
276
+ }
277
+ /** Replaces the rate limit window state of one domain. */
278
+ async setratestate(state) {
279
+ const records = (await this.getratestates()).filter((item) => item.domain !== state.domain);
280
+ await this.adapter.set("ratestates", [...records, state]);
281
+ }
282
+ /** Returns every rate limit window state per domain. */
283
+ async getratestates() {
284
+ return await this.adapter.get("ratestates") ?? [];
285
+ }
286
+ /** Records one curated link list with its review state before batch opening. */
287
+ async addcurated(list) {
288
+ const records = await this.getcurateds();
289
+ await this.adapter.set("curated", [list, ...records]);
290
+ }
291
+ /** Returns every stored curated link list, newest first. */
292
+ async getcurateds() {
293
+ return await this.adapter.get("curated") ?? [];
294
+ }
295
+ /** Stores reviewed basic auth credentials for one origin, replacing the previous record of that origin. */
296
+ async setauth(record2) {
297
+ const records = (await this.getauths()).filter((item) => item.origin !== record2.origin);
298
+ await this.adapter.set("auths", [...records, record2]);
299
+ }
300
+ /** Returns every stored reviewed basic auth record per origin. */
301
+ async getauths() {
302
+ return await this.adapter.get("auths") ?? [];
303
+ }
304
+ /** Records one task artifact routed into the artifact store. */
305
+ async addartifact(record2) {
306
+ const records = await this.getartifacts();
307
+ await this.adapter.set("artifacts", [record2, ...records]);
308
+ }
309
+ /** Returns every stored task artifact, newest first. */
310
+ async getartifacts() {
311
+ return await this.adapter.get("artifacts") ?? [];
312
+ }
313
+ /** Returns the navigation control state of paused navigation. */
314
+ async getnavcontrol() {
315
+ return this.adapter.get("navcontrol");
316
+ }
317
+ /** Replaces the navigation control state after a pause or resume transition. */
318
+ async setnavcontrol(control) {
319
+ return this.adapter.set("navcontrol", control);
320
+ }
321
+ /** Records one url safety verdict produced by a checksafe verification. */
322
+ async addsafety(verdict) {
323
+ const records = await this.getsafeties();
324
+ await this.adapter.set("safeties", [verdict, ...records]);
325
+ }
326
+ /** Returns every stored url safety verdict, newest first. */
327
+ async getsafeties() {
328
+ return await this.adapter.get("safeties") ?? [];
329
+ }
330
+ /** Records one recently closed tab so a reopentab step can restore it. */
331
+ async addrecenttab(tab) {
332
+ const records = await this.getrecenttabs();
333
+ await this.adapter.set("recenttabs", [tab, ...records]);
334
+ }
335
+ /** Returns every recently closed tab, newest first. */
336
+ async getrecenttabs() {
337
+ return await this.adapter.get("recenttabs") ?? [];
338
+ }
339
+ /** Returns the queued prefetch and batch open target counts shown in the popup badge. */
340
+ async getnavqueues() {
341
+ return this.adapter.get("navqueues");
342
+ }
343
+ /** Replaces the queued prefetch and batch open target counts. */
344
+ async setnavqueues(queues) {
345
+ return this.adapter.set("navqueues", queues);
346
+ }
347
+ /** Returns the last known navigation state of a tab, kept across service worker restarts. */
348
+ async getnavstate(tabid) {
349
+ return this.adapter.get(`navstate${tabid}`);
350
+ }
351
+ /** Replaces the last known navigation state of a tab. */
352
+ async setnavstate(tabid, state) {
353
+ return this.adapter.set(`navstate${tabid}`, state);
354
+ }
241
355
  };
242
356
  function randomid() {
243
357
  return crypto.randomUUID();
244
358
  }
245
359
 
246
360
  // policy.ts
247
- 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"]);
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"]);
248
362
  var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
249
- 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"]);
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"]);
250
364
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
251
365
  var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus"]);
252
366
  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"]);
253
- 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"]);
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"]);
254
368
  function normalizeendpoint(value) {
255
369
  const endpoint = new URL(value.trim());
256
370
  if (endpoint.protocol !== "https:") throw new Error("Devthink accepts HTTPS endpoints only.");
@@ -338,6 +452,35 @@ function origingranted(session, origin) {
338
452
  const grants = session.grants ?? [session.origin];
339
453
  return grants.includes(origin);
340
454
  }
455
+ function originverified(url, grants, verdicts) {
456
+ let origin = "";
457
+ try {
458
+ origin = new URL(url).origin;
459
+ } catch {
460
+ return { allowed: false, reason: "The reviewed navigation URL is invalid." };
461
+ }
462
+ if (grants.includes(origin)) return { allowed: true };
463
+ const covered = verdicts.find((verdict) => verdict.safe && (verdict.url === url || safeorigin(verdict.url) === origin));
464
+ if (covered) return { allowed: true };
465
+ return { allowed: false, reason: `The origin ${origin} is outside the session grants and has no safe checksafe verdict; run checksafe and review it first.` };
466
+ }
467
+ function safeorigin(url) {
468
+ try {
469
+ return new URL(url).origin;
470
+ } catch {
471
+ return "";
472
+ }
473
+ }
474
+ function navigationgranted(session, url) {
475
+ let origin = "";
476
+ try {
477
+ origin = new URL(url).origin;
478
+ } catch {
479
+ return { allowed: false, reason: "The reviewed navigation URL is invalid." };
480
+ }
481
+ if (origingranted(session, origin)) return { allowed: true };
482
+ return { allowed: false, reason: `Navigation to ${origin} leaves the task tab origins and needs the user consent of a session grant first.` };
483
+ }
341
484
  function validateinnerstep(options, origin) {
342
485
  const stepid = options.stepid;
343
486
  const kind = options.kind;
@@ -361,6 +504,68 @@ function validateinnerstep(options, origin) {
361
504
  };
362
505
  return validatestep(inner, origin);
363
506
  }
507
+ function ishttpsurl(value) {
508
+ if (typeof value !== "string" || !value.trim()) return false;
509
+ try {
510
+ return new URL(value).protocol === "https:";
511
+ } catch {
512
+ return false;
513
+ }
514
+ }
515
+ function validatenavtarget(value, kind) {
516
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed navtarget with a url is required in options." };
517
+ const target = value;
518
+ if (!ishttpsurl(target.url)) return { allowed: false, reason: "The reviewed navtarget url must use HTTPS." };
519
+ const container = target.container ?? "tab";
520
+ if (container !== "current" && container !== "tab" && container !== "window" && container !== "private") return { allowed: false, reason: "The reviewed navtarget container must be current, tab, window or private." };
521
+ if (target.position !== void 0 && target.position !== "adjacent" && target.position !== "end") return { allowed: false, reason: "The reviewed navtarget position must be adjacent or end." };
522
+ if (kind === "openprivate" && container !== "private") return { allowed: false, reason: "The openprivate step requires the private container." };
523
+ if (kind === "openlink" && container === "private") return { allowed: false, reason: "The openlink step cannot open the private container; use openprivate." };
524
+ return { allowed: true };
525
+ }
526
+ function validatewaitprofile(value) {
527
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed waitprofile with load signals is required in options." };
528
+ const profile = value;
529
+ if (!Array.isArray(profile.signals) || profile.signals.length === 0 || !profile.signals.every((signal) => isnonempty(signal))) return { allowed: false, reason: "The reviewed waitprofile needs a non-empty list of load signals." };
530
+ if (!nonnegativeoption(profile, "idle")) return { allowed: false, reason: "The reviewed waitprofile idle threshold must be zero or a positive number of milliseconds." };
531
+ if (!nonnegativeoption(profile, "timeout")) return { allowed: false, reason: "The reviewed waitprofile timeout must be zero or a positive number of milliseconds." };
532
+ if (profile.overrides !== void 0) {
533
+ if (!Array.isArray(profile.overrides) || profile.overrides.length === 0) return { allowed: false, reason: "The reviewed waitprofile overrides must be a non-empty list when present." };
534
+ for (const entry of profile.overrides) {
535
+ if (!entry || typeof entry !== "object" || Array.isArray(entry)) return { allowed: false, reason: "Every reviewed waitprofile override must be an object with an origin." };
536
+ const override = entry;
537
+ if (!ishttpsurl(override.origin)) return { allowed: false, reason: "Every reviewed waitprofile override origin must use HTTPS." };
538
+ if (override.signals !== void 0 && (!Array.isArray(override.signals) || !override.signals.every((signal) => isnonempty(signal)))) return { allowed: false, reason: "The reviewed waitprofile override signals must be a list of non-empty strings." };
539
+ if (!nonnegativeoption(override, "idle") || !nonnegativeoption(override, "timeout")) return { allowed: false, reason: "The reviewed waitprofile override thresholds must be zero or positive numbers." };
540
+ }
541
+ }
542
+ return { allowed: true };
543
+ }
544
+ function validateurlpattern(value) {
545
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed urlpattern is required in options." };
546
+ const pattern = value;
547
+ if (pattern.mode !== "exact" && pattern.mode !== "prefix" && pattern.mode !== "host" && pattern.mode !== "pattern") return { allowed: false, reason: "The reviewed urlpattern mode must be exact, prefix, host or pattern." };
548
+ if (!ishttpsurl(pattern.url)) return { allowed: false, reason: "The reviewed urlpattern url must use HTTPS." };
549
+ if (pattern.query !== void 0) {
550
+ if (!pattern.query || typeof pattern.query !== "object" || Array.isArray(pattern.query)) return { allowed: false, reason: "The reviewed urlpattern query part must be an object of parameter names and values." };
551
+ for (const item of Object.values(pattern.query)) if (typeof item !== "string") return { allowed: false, reason: "The reviewed urlpattern query values must be strings." };
552
+ }
553
+ if (pattern.fragment !== void 0 && !isnonempty(pattern.fragment)) return { allowed: false, reason: "The reviewed urlpattern fragment must be a non-empty string." };
554
+ return { allowed: true };
555
+ }
556
+ function validateurllist(options, key) {
557
+ const urls = options[key];
558
+ if (!Array.isArray(urls) || urls.length === 0 || !urls.every((url) => ishttpsurl(url))) return { allowed: false, reason: `A reviewed non-empty list of HTTPS urls is required in options as ${key}.` };
559
+ return { allowed: true };
560
+ }
561
+ function validateratelimit(value) {
562
+ if (!value || typeof value !== "object" || Array.isArray(value)) return { allowed: false, reason: "A reviewed ratelimit with a window and a ceiling is required in options." };
563
+ const limit = value;
564
+ if (limit.domain !== void 0 && !isnonempty(limit.domain)) return { allowed: false, reason: "The reviewed ratelimit domain must be a non-empty string." };
565
+ if (typeof limit.window !== "number" || !Number.isFinite(limit.window) || limit.window <= 0) return { allowed: false, reason: "The reviewed ratelimit window must be a positive number of milliseconds with no code ceiling." };
566
+ 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
+ return { allowed: true };
568
+ }
364
569
  function validatestep(step, origin) {
365
570
  if (!allowedactions.has(step.kind)) return { allowed: false, reason: "Unsupported action kind." };
366
571
  if (!step.summary.trim()) return { allowed: false, reason: "A human-readable action summary is required." };
@@ -498,6 +703,70 @@ function validatestep(step, origin) {
498
703
  const versions = options.versions;
499
704
  if (!Array.isArray(versions) || versions.length !== 2 || !versions.every((version) => typeof version === "number" && Number.isInteger(version) && version >= 1)) return { allowed: false, reason: "Two reviewed observation version numbers are required in options." };
500
705
  }
706
+ if (step.kind === "openlink" || step.kind === "openprivate" || step.kind === "deeplink") {
707
+ const targetcheck = validatenavtarget(options.navtarget, step.kind);
708
+ if (!targetcheck.allowed) return targetcheck;
709
+ if (step.kind === "deeplink") {
710
+ const app = options.app;
711
+ if (!isnonempty(app)) return { allowed: false, reason: "A reviewed deep link app pattern is required in options." };
712
+ const params = options.params;
713
+ if (params !== void 0 && (!params || typeof params !== "object" || Array.isArray(params) || !Object.values(params).every((item) => typeof item === "string"))) return { allowed: false, reason: "The reviewed deep link params must be an object of string values." };
714
+ }
715
+ }
716
+ if (step.kind === "waitload" && !nonnegativeoption(options, "timeout")) return { allowed: false, reason: "The waitload timeout must be zero or a positive number of milliseconds." };
717
+ if (step.kind === "waiturl" || step.kind === "spawait") {
718
+ if (step.kind === "waiturl") {
719
+ const patterncheck = validateurlpattern(options.urlpattern);
720
+ if (!patterncheck.allowed) return patterncheck;
721
+ }
722
+ if (!nonnegativeoption(options, "timeout")) return { allowed: false, reason: "The wait timeout must be zero or a positive number of milliseconds." };
723
+ if (!nonnegativeoption(options, "poll")) return { allowed: false, reason: "The wait poll interval must be zero or a positive number of milliseconds." };
724
+ }
725
+ if (step.kind === "followlink") {
726
+ if (options.fragment !== void 0 && typeof options.fragment !== "boolean") return { allowed: false, reason: "The reviewed followlink fragment flag must be a boolean." };
727
+ }
728
+ if (step.kind === "spanav") {
729
+ if (options.routepattern !== void 0) {
730
+ const routecheck = validateurlpattern(options.routepattern);
731
+ if (!routecheck.allowed) return routecheck;
732
+ }
733
+ if (!nonnegativeoption(options, "timeout")) return { allowed: false, reason: "The spanav route timeout must be zero or a positive number of milliseconds." };
734
+ }
735
+ if (step.kind === "rewritequery") {
736
+ const set = options.set;
737
+ const remove = options.remove;
738
+ if (set === void 0 && remove === void 0) return { allowed: false, reason: "Reviewed query parameters to set or remove are required in options." };
739
+ if (set !== void 0 && (!set || typeof set !== "object" || Array.isArray(set) || !Object.values(set).every((item) => typeof item === "string"))) return { allowed: false, reason: "The reviewed query parameters to set must be an object of string values." };
740
+ if (remove !== void 0 && (!Array.isArray(remove) || !remove.every((item) => isnonempty(item)))) return { allowed: false, reason: "The reviewed query parameters to remove must be a list of non-empty names." };
741
+ }
742
+ if (step.kind === "navlist") {
743
+ const listcheck = validateurllist(options, "urls");
744
+ if (!listcheck.allowed) return listcheck;
745
+ }
746
+ if (step.kind === "navprofile") {
747
+ const profilecheck = validatewaitprofile(options.waitprofile);
748
+ if (!profilecheck.allowed) return profilecheck;
749
+ }
750
+ if (step.kind === "handleauth" && !ishttpsurl(step.value)) return { allowed: false, reason: "A reviewed HTTPS origin or url is required as the auth target." };
751
+ if (step.kind === "printpdf" && options.name !== void 0 && !isnonempty(options.name)) return { allowed: false, reason: "The reviewed artifact name must be a non-empty string." };
752
+ if (step.kind === "prefetch") {
753
+ const listcheck = validateurllist(options, "urls");
754
+ if (!listcheck.allowed) return listcheck;
755
+ }
756
+ if (step.kind === "preconnect") {
757
+ const origins = options.origins;
758
+ if (!Array.isArray(origins) || origins.length === 0 || !origins.every((originurl) => ishttpsurl(originurl))) return { allowed: false, reason: "A reviewed non-empty list of HTTPS origins is required in options." };
759
+ }
760
+ if (step.kind === "reopentab" && step.value !== void 0 && !ishttpsurl(step.value)) return { allowed: false, reason: "The reviewed reopen url must use HTTPS." };
761
+ if (step.kind === "navrate") {
762
+ const limitcheck = validateratelimit(options.ratelimit);
763
+ if (!limitcheck.allowed) return limitcheck;
764
+ }
765
+ if (step.kind === "checksafe" && !ishttpsurl(step.value)) return { allowed: false, reason: "A reviewed HTTPS url is required for the safety check." };
766
+ if (step.kind === "batchopen") {
767
+ const listcheck = validateurllist(options, "urls");
768
+ if (!listcheck.allowed) return listcheck;
769
+ }
501
770
  return { allowed: true };
502
771
  }
503
772
  function sessiongate(input) {
@@ -515,11 +784,41 @@ function canexecute(input) {
515
784
  if (input.plan.expiresat <= now) return { allowed: false, reason: "The approved plan has expired." };
516
785
  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." };
517
786
  if (input.step.kind === "readjson" && !origingranted(input.session, input.origin)) return { allowed: false, reason: "The json state read is outside the session origin grants." };
787
+ if (input.step.kind === "navlist") {
788
+ let options = {};
789
+ try {
790
+ options = parseoptions(input.step);
791
+ } catch {
792
+ options = {};
793
+ }
794
+ for (const url of Array.isArray(options.urls) ? options.urls : []) {
795
+ if (typeof url !== "string") continue;
796
+ const navigation = navigationgranted(input.session, url);
797
+ if (!navigation.allowed) return navigation;
798
+ }
799
+ }
800
+ 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
+ let options = {};
802
+ try {
803
+ options = parseoptions(input.step);
804
+ } catch {
805
+ options = {};
806
+ }
807
+ const grammar = validatestep(input.step, input.origin);
808
+ if (!grammar.allowed) return grammar;
809
+ const grants = input.session?.grants ?? [input.session?.origin ?? input.origin];
810
+ const targets = input.step.kind === "batchopen" || input.step.kind === "prefetch" ? Array.isArray(options.urls) ? options.urls : [] : input.step.kind === "reopentab" ? [input.step.value] : [options.navtarget?.url];
811
+ for (const target of targets) {
812
+ if (typeof target !== "string" || !target) continue;
813
+ const verified = originverified(target, grants, input.verdicts ?? []);
814
+ if (!verified.allowed) return verified;
815
+ }
816
+ }
518
817
  return validatestep(input.step, input.origin);
519
818
  }
520
819
 
521
820
  // version.ts
522
- var packageversion = "1.1.34";
821
+ var packageversion = "1.1.35";
523
822
 
524
823
  // types.ts
525
824
  var protocolversion = packageversion;
@@ -608,6 +907,15 @@ function signalsreport(input) {
608
907
  function selectorresponse(input) {
609
908
  return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, candidates: input.candidates });
610
909
  }
910
+ function navstateresponse(input) {
911
+ return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, navstate: input.navstate });
912
+ }
913
+ function trailreport(input) {
914
+ return { version: protocolversion, ...input.sessionid ? { sessionid: input.sessionid } : {}, trail: input.trail };
915
+ }
916
+ function safetyresponse(input) {
917
+ return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, verdicts: input.verdicts });
918
+ }
611
919
  export {
612
920
  canexecute,
613
921
  actionrisk as deriveactionrisk,
@@ -617,6 +925,7 @@ export {
617
925
  hostpattern,
618
926
  iswatchkind,
619
927
  mapresponse,
928
+ navstateresponse,
620
929
  normalizeendpoint,
621
930
  observationmodeof,
622
931
  observationresponse,
@@ -626,9 +935,11 @@ export {
626
935
  randomid,
627
936
  requestbody,
628
937
  resolutionverdict,
938
+ safetyresponse,
629
939
  selectorresponse,
630
940
  sessionmemory,
631
941
  signalsreport,
942
+ trailreport,
632
943
  validatestep,
633
944
  validatetargetref
634
945
  };