@wenathlan/extension 1.1.32 → 1.1.34
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 +8 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +388 -12
- package/dist/index.js.map +3 -3
- package/dist/memory.d.ts +81 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +13 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +67 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +288 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +892 -25
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +1826 -88
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +13 -3
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +322 -25
- package/extension/dist/sidepanel.js.map +2 -2
- package/extension/dist/style.css +2 -1
- package/extension/manifest.json +1 -1
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -2,17 +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.
|
|
5
|
+
Version: **1.1.34**. 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.
|
|
9
|
+
| Capability | Behavior in 1.1.34 |
|
|
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
|
|
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. |
|
|
16
|
+
| 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
|
+
| 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
|
+
| 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. |
|
|
16
19
|
| 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. |
|
|
17
20
|
| Unlimited by choice | Wait durations, plan size, plan expiry, audit retention and outcome retention carry no code ceilings; every bound is a user choice. |
|
|
18
21
|
| Structured results | Step results carry structured JSON details, rendered in the side panel beside each reviewed step. |
|
|
@@ -45,10 +48,10 @@ The endpoint field intentionally has no default URL. Enter a URL such as `https:
|
|
|
45
48
|
|
|
46
49
|
```json
|
|
47
50
|
{
|
|
48
|
-
"version": "1.1.
|
|
51
|
+
"version": "1.1.34",
|
|
49
52
|
"objective": "User supplied objective",
|
|
50
53
|
"session": { "id": "uuid", "tabid": 1, "origin": "https://example.com" },
|
|
51
|
-
"observation": { "schemaversion":
|
|
54
|
+
"observation": { "schemaversion": 3, "url": "https://example.com/path", "interactive": [] },
|
|
52
55
|
"capabilities": { "tabs": false, "downloads": false, "clipboardread": false, "clipboardwrite": false }
|
|
53
56
|
}
|
|
54
57
|
```
|
package/dist/index.d.ts
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
export * from "./memory.js";
|
|
2
|
-
export { actionrisk as deriveactionrisk, canexecute, hostpattern, normalizeendpoint, validatestep } from "./policy.js";
|
|
2
|
+
export { actionrisk as deriveactionrisk, canexecute, hostpattern, iswatchkind, normalizeendpoint, observationmodeof, resolutionverdict, validatestep, validatetargetref } from "./policy.js";
|
|
3
3
|
export * from "./protocol.js";
|
|
4
4
|
export * from "./types.js";
|
|
5
5
|
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,iBAAiB,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../index.ts"],"names":[],"mappings":"AAAA,cAAc,aAAa,CAAC;AAC5B,OAAO,EAAE,UAAU,IAAI,gBAAgB,EAAE,UAAU,EAAE,WAAW,EAAE,WAAW,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,iBAAiB,EAAE,YAAY,EAAE,iBAAiB,EAAE,MAAM,aAAa,CAAC;AAC7L,cAAc,eAAe,CAAC;AAC9B,cAAc,YAAY,CAAC"}
|
package/dist/index.js
CHANGED
|
@@ -66,18 +66,191 @@ var sessionmemory = class {
|
|
|
66
66
|
const retention = (await this.getsettings())?.outcomeretention;
|
|
67
67
|
await this.adapter.set("outcomes", retention === void 0 ? combined : combined.slice(0, retention));
|
|
68
68
|
}
|
|
69
|
+
/** Stores one clickable map under its observation version so every captured map stays available. */
|
|
70
|
+
async setmap(map) {
|
|
71
|
+
return this.adapter.set(`map${map.version}`, map);
|
|
72
|
+
}
|
|
73
|
+
/** Returns one stored clickable map by its observation version. */
|
|
74
|
+
async getmap(version) {
|
|
75
|
+
return this.adapter.get(`map${version}`);
|
|
76
|
+
}
|
|
77
|
+
/** Advances and persists the observation version counter used to stamp clickable maps. */
|
|
78
|
+
async nextobservationversion() {
|
|
79
|
+
const current = await this.adapter.get("observationversion") ?? 0;
|
|
80
|
+
const next = current + 1;
|
|
81
|
+
await this.adapter.set("observationversion", next);
|
|
82
|
+
return next;
|
|
83
|
+
}
|
|
84
|
+
/** Returns the latest observation version used to stamp a clickable map. */
|
|
85
|
+
async getobservationversion() {
|
|
86
|
+
return this.adapter.get("observationversion");
|
|
87
|
+
}
|
|
88
|
+
/** Returns the key hold registry, persisted so holds survive service worker restarts. */
|
|
89
|
+
async getholds() {
|
|
90
|
+
return await this.adapter.get("holds") ?? [];
|
|
91
|
+
}
|
|
92
|
+
/** Replaces the key hold registry after one press or release transition. */
|
|
93
|
+
async setholds(holds) {
|
|
94
|
+
return this.adapter.set("holds", holds);
|
|
95
|
+
}
|
|
96
|
+
/** Returns every dialog decision recorded for the audit trail. */
|
|
97
|
+
async getdialogs() {
|
|
98
|
+
return await this.adapter.get("dialogs") ?? [];
|
|
99
|
+
}
|
|
100
|
+
/** Records one dialog decision with the reviewed answer and the observed dialog text. */
|
|
101
|
+
async adddialog(decision) {
|
|
102
|
+
const records = await this.getdialogs();
|
|
103
|
+
await this.adapter.set("dialogs", [decision, ...records]);
|
|
104
|
+
}
|
|
105
|
+
/** Returns every retry outcome recorded with attempts and movement deltas. */
|
|
106
|
+
async getretries() {
|
|
107
|
+
return await this.adapter.get("retries") ?? [];
|
|
108
|
+
}
|
|
109
|
+
/** Records one retry outcome with the attempts made and the movement delta observed. */
|
|
110
|
+
async addretry(outcome) {
|
|
111
|
+
const records = await this.getretries();
|
|
112
|
+
await this.adapter.set("retries", [outcome, ...records]);
|
|
113
|
+
}
|
|
114
|
+
/** Returns every resolution summary stored per target mode. */
|
|
115
|
+
async getresolutions() {
|
|
116
|
+
return await this.adapter.get("resolutions") ?? [];
|
|
117
|
+
}
|
|
118
|
+
/** Records one resolution summary for later selector derivation. */
|
|
119
|
+
async addresolution(summary) {
|
|
120
|
+
const records = await this.getresolutions();
|
|
121
|
+
await this.adapter.set("resolutions", [summary, ...records]);
|
|
122
|
+
}
|
|
123
|
+
/** Returns the reviewed default dialog policy kept for the session auto handler. */
|
|
124
|
+
async getdialogpolicy() {
|
|
125
|
+
return this.adapter.get("dialogpolicy");
|
|
126
|
+
}
|
|
127
|
+
/** Stores the reviewed default dialog policy of the latest approved plan. */
|
|
128
|
+
async setdialogpolicy(policy) {
|
|
129
|
+
return this.adapter.set("dialogpolicy", policy);
|
|
130
|
+
}
|
|
131
|
+
/** Stores one observation capture under its version so every observation version stays available. */
|
|
132
|
+
async setobservation(record2) {
|
|
133
|
+
return this.adapter.set(`observation${record2.version}`, record2);
|
|
134
|
+
}
|
|
135
|
+
/** Returns one stored observation version. */
|
|
136
|
+
async getobservation(version) {
|
|
137
|
+
return this.adapter.get(`observation${version}`);
|
|
138
|
+
}
|
|
139
|
+
/** Returns the observation retention window; an absent setting keeps every capture. */
|
|
140
|
+
async observationretention() {
|
|
141
|
+
return (await this.getsettings())?.observationretention;
|
|
142
|
+
}
|
|
143
|
+
/** Stores one accessibility tree capture; retention is a user setting and an absent setting keeps every tree. */
|
|
144
|
+
async adda11ytree(capture) {
|
|
145
|
+
const records = await this.geta11ytrees();
|
|
146
|
+
const combined = [capture, ...records];
|
|
147
|
+
const retention = await this.observationretention();
|
|
148
|
+
await this.adapter.set("a11ytrees", retention === void 0 ? combined : combined.slice(0, retention));
|
|
149
|
+
}
|
|
150
|
+
/** Returns every stored accessibility tree capture, newest first. */
|
|
151
|
+
async geta11ytrees() {
|
|
152
|
+
return await this.adapter.get("a11ytrees") ?? [];
|
|
153
|
+
}
|
|
154
|
+
/** Stores one reader article capture; retention is a user setting and an absent setting keeps every article. */
|
|
155
|
+
async addreaderarticle(capture) {
|
|
156
|
+
const records = await this.getreaderarticles();
|
|
157
|
+
const combined = [capture, ...records];
|
|
158
|
+
const retention = await this.observationretention();
|
|
159
|
+
await this.adapter.set("readerarticles", retention === void 0 ? combined : combined.slice(0, retention));
|
|
160
|
+
}
|
|
161
|
+
/** Returns every stored reader article capture, newest first. */
|
|
162
|
+
async getreaderarticles() {
|
|
163
|
+
return await this.adapter.get("readerarticles") ?? [];
|
|
164
|
+
}
|
|
165
|
+
/** Records one dom mutation observed inside a reviewed watch. */
|
|
166
|
+
async addmutationevent(event) {
|
|
167
|
+
const records = await this.getmutationevents();
|
|
168
|
+
await this.adapter.set("mutationevents", [event, ...records]);
|
|
169
|
+
}
|
|
170
|
+
/** Returns the mutation event stream of every reviewed watch. */
|
|
171
|
+
async getmutationevents() {
|
|
172
|
+
return await this.adapter.get("mutationevents") ?? [];
|
|
173
|
+
}
|
|
174
|
+
/** Records one focus change observed inside a reviewed watch. */
|
|
175
|
+
async addfocusevent(event) {
|
|
176
|
+
const records = await this.getfocusevents();
|
|
177
|
+
await this.adapter.set("focusevents", [event, ...records]);
|
|
178
|
+
}
|
|
179
|
+
/** Returns the focus event stream of every reviewed watch. */
|
|
180
|
+
async getfocusevents() {
|
|
181
|
+
return await this.adapter.get("focusevents") ?? [];
|
|
182
|
+
}
|
|
183
|
+
/** Records one consent banner observed by a reviewed banner watch. */
|
|
184
|
+
async addbanner(event) {
|
|
185
|
+
const records = await this.getbanners();
|
|
186
|
+
await this.adapter.set("banners", [event, ...records]);
|
|
187
|
+
}
|
|
188
|
+
/** Returns every consent banner report observed so far. */
|
|
189
|
+
async getbanners() {
|
|
190
|
+
return await this.adapter.get("banners") ?? [];
|
|
191
|
+
}
|
|
192
|
+
/** Records one snapshot diff between two observation versions. */
|
|
193
|
+
async adddiff(diff) {
|
|
194
|
+
const records = await this.getdiffs();
|
|
195
|
+
await this.adapter.set("diffs", [diff, ...records]);
|
|
196
|
+
}
|
|
197
|
+
/** Returns every stored snapshot diff, newest first. */
|
|
198
|
+
async getdiffs() {
|
|
199
|
+
return await this.adapter.get("diffs") ?? [];
|
|
200
|
+
}
|
|
201
|
+
/** Records one derived selector with its stability score for reuse. */
|
|
202
|
+
async addselector(selector) {
|
|
203
|
+
const records = await this.getselectors();
|
|
204
|
+
await this.adapter.set("selectors", [selector, ...records]);
|
|
205
|
+
}
|
|
206
|
+
/** Returns every stored derived selector with its stability score, newest first. */
|
|
207
|
+
async getselectors() {
|
|
208
|
+
return await this.adapter.get("selectors") ?? [];
|
|
209
|
+
}
|
|
210
|
+
/** Records one detected template class or section fingerprint for its origin. */
|
|
211
|
+
async addtemplate(profile) {
|
|
212
|
+
const records = await this.gettemplates();
|
|
213
|
+
await this.adapter.set("templates", [profile, ...records]);
|
|
214
|
+
}
|
|
215
|
+
/** Returns every stored template class and section fingerprint, newest first. */
|
|
216
|
+
async gettemplates() {
|
|
217
|
+
return await this.adapter.get("templates") ?? [];
|
|
218
|
+
}
|
|
219
|
+
/** Records one watch registration so it survives service worker restarts. */
|
|
220
|
+
async addwatch(watch) {
|
|
221
|
+
const records = await this.getwatches();
|
|
222
|
+
await this.adapter.set("watches", [watch, ...records]);
|
|
223
|
+
}
|
|
224
|
+
/** Returns every watch registration, newest first, including closed windows. */
|
|
225
|
+
async getwatches() {
|
|
226
|
+
return await this.adapter.get("watches") ?? [];
|
|
227
|
+
}
|
|
228
|
+
/** Closes one watch registration by watch id once its reviewed lifetime window ends. */
|
|
229
|
+
async closewatch(watchid, closedat) {
|
|
230
|
+
const records = await this.getwatches();
|
|
231
|
+
await this.adapter.set("watches", records.map((watch) => watch.watchid === watchid && watch.closedat === void 0 ? { ...watch, closedat } : watch));
|
|
232
|
+
}
|
|
233
|
+
/** Returns the live page signals of language, template, scroll lock and banner state. */
|
|
234
|
+
async getsignals() {
|
|
235
|
+
return this.adapter.get("signals");
|
|
236
|
+
}
|
|
237
|
+
/** Replaces the live page signals after an observation step refreshes them. */
|
|
238
|
+
async setsignals(signals) {
|
|
239
|
+
return this.adapter.set("signals", signals);
|
|
240
|
+
}
|
|
69
241
|
};
|
|
70
242
|
function randomid() {
|
|
71
243
|
return crypto.randomUUID();
|
|
72
244
|
}
|
|
73
245
|
|
|
74
246
|
// policy.ts
|
|
75
|
-
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"]);
|
|
76
|
-
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset"]);
|
|
77
|
-
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"]);
|
|
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"]);
|
|
248
|
+
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"]);
|
|
78
250
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
79
|
-
var
|
|
80
|
-
var
|
|
251
|
+
var watchactions = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus"]);
|
|
252
|
+
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"]);
|
|
81
254
|
function normalizeendpoint(value) {
|
|
82
255
|
const endpoint = new URL(value.trim());
|
|
83
256
|
if (endpoint.protocol !== "https:") throw new Error("Devthink accepts HTTPS endpoints only.");
|
|
@@ -89,6 +262,14 @@ function hostpattern(origin) {
|
|
|
89
262
|
if (parsed.protocol !== "https:") throw new Error("Only HTTPS origins can be granted.");
|
|
90
263
|
return `${parsed.origin}/*`;
|
|
91
264
|
}
|
|
265
|
+
function iswatchkind(kind) {
|
|
266
|
+
return watchactions.has(kind);
|
|
267
|
+
}
|
|
268
|
+
function observationmodeof(kind) {
|
|
269
|
+
if (watchactions.has(kind) || kind === "waitquiet") return "watching";
|
|
270
|
+
if (kind === "diffsnapshots") return "diffing";
|
|
271
|
+
return "passive";
|
|
272
|
+
}
|
|
92
273
|
function actionrisk(kind) {
|
|
93
274
|
if (!allowedactions.has(kind)) throw new Error("Unsupported browser action.");
|
|
94
275
|
if (sensitiveactions.has(kind)) return "sensitive";
|
|
@@ -116,19 +297,88 @@ function isnumericid(value) {
|
|
|
116
297
|
function numericoption(options, key) {
|
|
117
298
|
return options[key] === void 0 || typeof options[key] === "number" && Number.isFinite(options[key]);
|
|
118
299
|
}
|
|
300
|
+
function nonnegativeoption(options, key) {
|
|
301
|
+
return numericoption(options, key) && !(typeof options[key] === "number" && options[key] < 0);
|
|
302
|
+
}
|
|
303
|
+
function isnonempty(value) {
|
|
304
|
+
return typeof value === "string" && value.trim().length > 0;
|
|
305
|
+
}
|
|
306
|
+
function ispoint(value) {
|
|
307
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) return false;
|
|
308
|
+
const point = value;
|
|
309
|
+
return typeof point.x === "number" && Number.isFinite(point.x) && typeof point.y === "number" && Number.isFinite(point.y);
|
|
310
|
+
}
|
|
311
|
+
function resolutionverdict(count) {
|
|
312
|
+
if (!Number.isFinite(count) || count <= 0) return "absent";
|
|
313
|
+
return count === 1 ? "resolved" : "ambiguous";
|
|
314
|
+
}
|
|
315
|
+
function validatetargetref(reference) {
|
|
316
|
+
if (!reference || typeof reference !== "object" || Array.isArray(reference)) return { allowed: false, reason: "The reviewed target reference must be an object." };
|
|
317
|
+
const ref = reference;
|
|
318
|
+
if (ref.mode === "selector") return isnonempty(ref.selector) ? { allowed: true } : { allowed: false, reason: "The selector target reference needs a non-empty selector." };
|
|
319
|
+
if (ref.mode === "text") return isnonempty(ref.text) ? { allowed: true } : { allowed: false, reason: "The text target reference needs non-empty text." };
|
|
320
|
+
if (ref.mode === "aria") {
|
|
321
|
+
if (!isnonempty(ref.role)) return { allowed: false, reason: "The aria target reference needs a non-empty role." };
|
|
322
|
+
return isnonempty(ref.name) ? { allowed: true } : { allowed: false, reason: "The aria target reference needs a non-empty name." };
|
|
323
|
+
}
|
|
324
|
+
if (ref.mode === "name") return isnonempty(ref.name) ? { allowed: true } : { allowed: false, reason: "The name target reference needs a non-empty name." };
|
|
325
|
+
if (ref.mode === "xpath") return isnonempty(ref.xpath) ? { allowed: true } : { allowed: false, reason: "The xpath target reference needs a non-empty expression." };
|
|
326
|
+
if (ref.mode === "index") {
|
|
327
|
+
const index = ref.index;
|
|
328
|
+
return typeof index === "number" && Number.isInteger(index) && index >= 1 ? { allowed: true } : { allowed: false, reason: "The index target reference needs a positive integer map number." };
|
|
329
|
+
}
|
|
330
|
+
if (ref.mode === "point") {
|
|
331
|
+
const pointok = typeof ref.x === "number" && Number.isFinite(ref.x) && typeof ref.y === "number" && Number.isFinite(ref.y);
|
|
332
|
+
return pointok ? { allowed: true } : { allowed: false, reason: "The point target reference needs numeric x and y coordinates." };
|
|
333
|
+
}
|
|
334
|
+
return { allowed: false, reason: "The target reference mode must be selector, text, aria, name, xpath, index or point." };
|
|
335
|
+
}
|
|
336
|
+
function origingranted(session, origin) {
|
|
337
|
+
if (!session) return false;
|
|
338
|
+
const grants = session.grants ?? [session.origin];
|
|
339
|
+
return grants.includes(origin);
|
|
340
|
+
}
|
|
341
|
+
function validateinnerstep(options, origin) {
|
|
342
|
+
const stepid = options.stepid;
|
|
343
|
+
const kind = options.kind;
|
|
344
|
+
if (isnonempty(stepid)) {
|
|
345
|
+
if (kind !== void 0) return { allowed: false, reason: "The reviewed wrapper must reference a step id or an inline step, not both." };
|
|
346
|
+
return { allowed: true };
|
|
347
|
+
}
|
|
348
|
+
if (typeof kind !== "string" || !kind.trim()) return { allowed: false, reason: "A reviewed step id or inline step kind is required in options." };
|
|
349
|
+
if (kind === "retryaction" || kind === "enterframe") return { allowed: false, reason: "The reviewed inner step cannot be another wrapper kind." };
|
|
350
|
+
if (!allowedactions.has(kind)) return { allowed: false, reason: "The reviewed inner step kind is unsupported." };
|
|
351
|
+
const inneroptions = options.options;
|
|
352
|
+
if (inneroptions !== void 0 && (!inneroptions || typeof inneroptions !== "object" || Array.isArray(inneroptions))) return { allowed: false, reason: "The reviewed inner step options must be an object." };
|
|
353
|
+
const inner = {
|
|
354
|
+
id: "inner",
|
|
355
|
+
kind,
|
|
356
|
+
summary: "Reviewed inner step.",
|
|
357
|
+
risk: actionrisk(kind),
|
|
358
|
+
...isnonempty(options.target) ? { target: options.target } : {},
|
|
359
|
+
...isnonempty(options.value) ? { value: options.value } : {},
|
|
360
|
+
...inneroptions !== void 0 ? { options: JSON.stringify(inneroptions) } : {}
|
|
361
|
+
};
|
|
362
|
+
return validatestep(inner, origin);
|
|
363
|
+
}
|
|
119
364
|
function validatestep(step, origin) {
|
|
120
365
|
if (!allowedactions.has(step.kind)) return { allowed: false, reason: "Unsupported action kind." };
|
|
121
366
|
if (!step.summary.trim()) return { allowed: false, reason: "A human-readable action summary is required." };
|
|
122
|
-
if (targetactions.has(step.kind) && !step.target?.trim()) return { allowed: false, reason: "A page target is required." };
|
|
123
|
-
if (valueactions.has(step.kind) && !step.value?.trim()) return { allowed: false, reason: "A reviewed value is required." };
|
|
124
|
-
if (step.kind === "select" && !step.value?.trim()) return { allowed: false, reason: "A reviewed option value is required." };
|
|
125
|
-
if (step.kind === "navigate" && !step.value) return { allowed: false, reason: "A navigation URL is required." };
|
|
126
367
|
let options;
|
|
127
368
|
try {
|
|
128
369
|
options = parseoptions(step);
|
|
129
370
|
} catch {
|
|
130
371
|
return { allowed: false, reason: "Step options must be a JSON object." };
|
|
131
372
|
}
|
|
373
|
+
const hastargetref = options.targetref !== void 0;
|
|
374
|
+
if (targetactions.has(step.kind) && !step.target?.trim() && !hastargetref) return { allowed: false, reason: "A page target is required." };
|
|
375
|
+
if (valueactions.has(step.kind) && !step.value?.trim()) return { allowed: false, reason: "A reviewed value is required." };
|
|
376
|
+
if (step.kind === "select" && !step.value?.trim()) return { allowed: false, reason: "A reviewed option value is required." };
|
|
377
|
+
if (step.kind === "navigate" && !step.value) return { allowed: false, reason: "A navigation URL is required." };
|
|
378
|
+
if (hastargetref) {
|
|
379
|
+
const reference = validatetargetref(options.targetref);
|
|
380
|
+
if (!reference.allowed) return reference;
|
|
381
|
+
}
|
|
132
382
|
if (step.kind === "wait") {
|
|
133
383
|
try {
|
|
134
384
|
waitduration(step);
|
|
@@ -168,6 +418,86 @@ function validatestep(step, origin) {
|
|
|
168
418
|
}
|
|
169
419
|
if ((step.kind === "scrollpage" || step.kind === "scrollby") && (!numericoption(options, "x") || !numericoption(options, "y"))) return { allowed: false, reason: "Scroll amounts must be numbers in options." };
|
|
170
420
|
if (step.kind === "waitfor" && options.timeout !== void 0 && (typeof options.timeout !== "number" || options.timeout < 0)) return { allowed: false, reason: "The waitfor timeout must be zero or a positive number of milliseconds." };
|
|
421
|
+
if (step.kind === "movepointer") {
|
|
422
|
+
const path = options.pointpath;
|
|
423
|
+
if (!path || typeof path !== "object" || Array.isArray(path)) return { allowed: false, reason: "A reviewed pointpath with start and end points is required in options." };
|
|
424
|
+
const points = path;
|
|
425
|
+
if (!ispoint(points.start) || !ispoint(points.end)) return { allowed: false, reason: "The reviewed pointpath needs numeric start and end points." };
|
|
426
|
+
if (points.waypoints !== void 0 && (!Array.isArray(points.waypoints) || !points.waypoints.every((waypoint) => ispoint(waypoint)))) return { allowed: false, reason: "The reviewed pointpath waypoints must be numeric points." };
|
|
427
|
+
if (!nonnegativeoption(points, "duration")) return { allowed: false, reason: "The reviewed pointpath duration must be zero or a positive number of milliseconds." };
|
|
428
|
+
const speed = options.speedprofile;
|
|
429
|
+
if (speed !== void 0) {
|
|
430
|
+
if (!speed || typeof speed !== "object" || Array.isArray(speed)) return { allowed: false, reason: "The reviewed speed profile must be an object." };
|
|
431
|
+
const profile = speed;
|
|
432
|
+
if (profile.easing !== void 0 && profile.easing !== "linear" && profile.easing !== "easeinout") return { allowed: false, reason: "The reviewed easing must be linear or easeinout." };
|
|
433
|
+
if (!nonnegativeoption(profile, "peak")) return { allowed: false, reason: "The reviewed peak velocity must be zero or a positive number." };
|
|
434
|
+
if (!nonnegativeoption(profile, "jitter")) return { allowed: false, reason: "The reviewed jitter window must be zero or a positive number of milliseconds." };
|
|
435
|
+
}
|
|
436
|
+
}
|
|
437
|
+
if (step.kind === "clickpoint" && (!hastargetref || options.targetref.mode !== "point")) return { allowed: false, reason: "A reviewed point target reference is required in options." };
|
|
438
|
+
if (step.kind === "clicktext" && (!hastargetref || options.targetref.mode !== "text")) return { allowed: false, reason: "A reviewed text target reference is required in options." };
|
|
439
|
+
if (step.kind === "clickaria" && (!hastargetref || options.targetref.mode !== "aria")) return { allowed: false, reason: "A reviewed aria target reference is required in options." };
|
|
440
|
+
if (step.kind === "clickname" && (!hastargetref || options.targetref.mode !== "name")) return { allowed: false, reason: "A reviewed name target reference is required in options." };
|
|
441
|
+
if (step.kind === "resolvexpath" && (!hastargetref || options.targetref.mode !== "xpath")) return { allowed: false, reason: "A reviewed xpath target reference is required in options." };
|
|
442
|
+
if (step.kind === "typetime" && options.delay !== void 0 && (typeof options.delay !== "number" || !Number.isFinite(options.delay) || options.delay < 0)) return { allowed: false, reason: "The reviewed per keystroke delay must be zero or a positive number of milliseconds." };
|
|
443
|
+
if (step.kind === "submitsearch") {
|
|
444
|
+
if (!isnonempty(options.results)) return { allowed: false, reason: "A reviewed results region selector is required in options." };
|
|
445
|
+
if (options.timeout !== void 0 && (typeof options.timeout !== "number" || !Number.isFinite(options.timeout) || options.timeout < 0)) return { allowed: false, reason: "The submitsearch timeout must be zero or a positive number of milliseconds." };
|
|
446
|
+
}
|
|
447
|
+
if (step.kind === "selectmulti") {
|
|
448
|
+
const values = options.values;
|
|
449
|
+
if (!Array.isArray(values) || values.length === 0 || !values.every((value) => isnonempty(value))) return { allowed: false, reason: "A reviewed list of option values is required in options." };
|
|
450
|
+
}
|
|
451
|
+
if (step.kind === "setslider") {
|
|
452
|
+
const slider = Number(step.value);
|
|
453
|
+
if (!Number.isFinite(slider)) return { allowed: false, reason: "The reviewed slider value must be a number." };
|
|
454
|
+
}
|
|
455
|
+
if (step.kind === "setdate" && !/^\d{4}-\d{2}-\d{2}$/.test(step.value ?? "")) return { allowed: false, reason: "The reviewed date must use the yyyy-mm-dd form." };
|
|
456
|
+
if (step.kind === "setcolor" && !/^#[0-9a-fA-F]{6}$/.test(step.value ?? "")) return { allowed: false, reason: "The reviewed color must use the #rrggbb form." };
|
|
457
|
+
if (step.kind === "keyhold" && options.holdid !== void 0 && !isnonempty(options.holdid)) return { allowed: false, reason: "The reviewed hold id must be a non-empty string." };
|
|
458
|
+
if (step.kind === "dismissdialog") {
|
|
459
|
+
const accept = options.accept;
|
|
460
|
+
const answer = options.answer;
|
|
461
|
+
if (accept === void 0 && !isnonempty(answer)) return { allowed: false, reason: "A reviewed accept flag or prompt answer is required in options." };
|
|
462
|
+
if (accept !== void 0 && typeof accept !== "boolean") return { allowed: false, reason: "The reviewed dialog accept flag must be a boolean." };
|
|
463
|
+
if (answer !== void 0 && !isnonempty(answer)) return { allowed: false, reason: "The reviewed prompt answer must be a non-empty string." };
|
|
464
|
+
}
|
|
465
|
+
if (step.kind === "pierceshadow" && options.shadow !== void 0) {
|
|
466
|
+
if (!Array.isArray(options.shadow) || !options.shadow.every((item) => isnonempty(item))) return { allowed: false, reason: "The reviewed shadow path must be a list of non-empty selectors." };
|
|
467
|
+
}
|
|
468
|
+
if (step.kind === "enterframe") {
|
|
469
|
+
const path = options.framepath;
|
|
470
|
+
if (!Array.isArray(path) || path.length === 0 || !path.every((item) => typeof item === "number" && Number.isInteger(item) && item >= 0)) return { allowed: false, reason: "A reviewed frame path of frame indexes is required in options." };
|
|
471
|
+
return validateinnerstep(options, origin);
|
|
472
|
+
}
|
|
473
|
+
if (step.kind === "retryaction") {
|
|
474
|
+
const inner = validateinnerstep(options, origin);
|
|
475
|
+
if (!inner.allowed) return inner;
|
|
476
|
+
const rule = options.retryrule;
|
|
477
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return { allowed: false, reason: "A reviewed retry rule with attempts is required in options." };
|
|
478
|
+
const retry = rule;
|
|
479
|
+
if (typeof retry.attempts !== "number" || !Number.isInteger(retry.attempts) || retry.attempts < 1) return { allowed: false, reason: "The reviewed retry attempts must be a positive integer with no code ceiling." };
|
|
480
|
+
if (!nonnegativeoption(retry, "settle")) return { allowed: false, reason: "The reviewed retry settle window must be zero or a positive number of milliseconds." };
|
|
481
|
+
if (!nonnegativeoption(retry, "tolerance")) return { allowed: false, reason: "The reviewed retry movement tolerance must be zero or a positive number of pixels." };
|
|
482
|
+
}
|
|
483
|
+
if (watchactions.has(step.kind)) {
|
|
484
|
+
if (typeof options.lifetime !== "number" || !Number.isFinite(options.lifetime) || options.lifetime <= 0) return { allowed: false, reason: "A reviewed watch lifetime window in milliseconds is required in options." };
|
|
485
|
+
if (options.scopes !== void 0 && (!Array.isArray(options.scopes) || !options.scopes.every((scope) => isnonempty(scope)))) return { allowed: false, reason: "The reviewed watch scopes must be a list of non-empty selectors." };
|
|
486
|
+
if (options.events !== void 0 && (!Array.isArray(options.events) || !options.events.every((event) => isnonempty(event)))) return { allowed: false, reason: "The reviewed watch event kinds must be a list of non-empty strings." };
|
|
487
|
+
if (!nonnegativeoption(options, "poll")) return { allowed: false, reason: "The reviewed watch poll interval must be zero or a positive number of milliseconds." };
|
|
488
|
+
}
|
|
489
|
+
if (step.kind === "waitquiet") {
|
|
490
|
+
const rule = options.quietrule;
|
|
491
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return { allowed: false, reason: "A reviewed quietrule with an idle threshold is required in options." };
|
|
492
|
+
const quiet = rule;
|
|
493
|
+
if (typeof quiet.idle !== "number" || !Number.isFinite(quiet.idle) || quiet.idle <= 0) return { allowed: false, reason: "The reviewed quiet idle threshold must be a positive number of milliseconds with no code ceiling." };
|
|
494
|
+
if (!nonnegativeoption(quiet, "poll")) return { allowed: false, reason: "The reviewed quiet poll interval must be zero or a positive number of milliseconds." };
|
|
495
|
+
if (!nonnegativeoption(quiet, "timeout")) return { allowed: false, reason: "The reviewed quiet timeout must be zero or a positive number of milliseconds." };
|
|
496
|
+
}
|
|
497
|
+
if (step.kind === "diffsnapshots") {
|
|
498
|
+
const versions = options.versions;
|
|
499
|
+
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
|
+
}
|
|
171
501
|
return { allowed: true };
|
|
172
502
|
}
|
|
173
503
|
function sessiongate(input) {
|
|
@@ -183,11 +513,13 @@ function canexecute(input) {
|
|
|
183
513
|
if (!gate.allowed) return gate;
|
|
184
514
|
if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "The plan has not received explicit approval." };
|
|
185
515
|
if (input.plan.expiresat <= now) return { allowed: false, reason: "The approved plan has expired." };
|
|
516
|
+
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
|
+
if (input.step.kind === "readjson" && !origingranted(input.session, input.origin)) return { allowed: false, reason: "The json state read is outside the session origin grants." };
|
|
186
518
|
return validatestep(input.step, input.origin);
|
|
187
519
|
}
|
|
188
520
|
|
|
189
521
|
// version.ts
|
|
190
|
-
var packageversion = "1.1.
|
|
522
|
+
var packageversion = "1.1.34";
|
|
191
523
|
|
|
192
524
|
// types.ts
|
|
193
525
|
var protocolversion = packageversion;
|
|
@@ -223,6 +555,11 @@ function parseproposal(value, origin) {
|
|
|
223
555
|
if (!evaluation.allowed) throw new Error(evaluation.reason);
|
|
224
556
|
return step;
|
|
225
557
|
});
|
|
558
|
+
for (const step of steps) {
|
|
559
|
+
if (step.kind !== "retryaction" && step.kind !== "enterframe") continue;
|
|
560
|
+
const options = parseoptions(step);
|
|
561
|
+
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.");
|
|
562
|
+
}
|
|
226
563
|
const createdat = Date.now();
|
|
227
564
|
const expiresat = typeof planinput.expiresat === "number" ? planinput.expiresat : createdat + 10 * 60 * 1e3;
|
|
228
565
|
const plan = {
|
|
@@ -241,19 +578,58 @@ function requestbody(input) {
|
|
|
241
578
|
return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
|
|
242
579
|
}
|
|
243
580
|
function outcomeresponse(input) {
|
|
244
|
-
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome });
|
|
581
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, outcome: input.outcome, ...input.resolvedtarget ? { resolvedtarget: input.resolvedtarget } : {} });
|
|
582
|
+
}
|
|
583
|
+
function mapresponse(input) {
|
|
584
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, map: input.map });
|
|
585
|
+
}
|
|
586
|
+
function heldkeysreport(input) {
|
|
587
|
+
return { version: protocolversion, tabid: input.tabid, heldkeys: input.holds };
|
|
588
|
+
}
|
|
589
|
+
function observationresponse(input) {
|
|
590
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, observation: input.observation });
|
|
591
|
+
}
|
|
592
|
+
function eventresponse(input) {
|
|
593
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, events: input.events });
|
|
594
|
+
}
|
|
595
|
+
function diffresponse(input) {
|
|
596
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, diff: input.diff });
|
|
597
|
+
}
|
|
598
|
+
function signalsreport(input) {
|
|
599
|
+
const signals = input.signals;
|
|
600
|
+
return {
|
|
601
|
+
version: protocolversion,
|
|
602
|
+
...signals && signals.language !== void 0 ? { language: signals.language } : {},
|
|
603
|
+
...signals && signals.template !== void 0 ? { template: signals.template } : {},
|
|
604
|
+
...signals && signals.scrolllocked !== void 0 ? { scrolllocked: signals.scrolllocked } : {},
|
|
605
|
+
...signals && signals.banner !== void 0 ? { banner: signals.banner } : {}
|
|
606
|
+
};
|
|
607
|
+
}
|
|
608
|
+
function selectorresponse(input) {
|
|
609
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, candidates: input.candidates });
|
|
245
610
|
}
|
|
246
611
|
export {
|
|
247
612
|
canexecute,
|
|
248
613
|
actionrisk as deriveactionrisk,
|
|
614
|
+
diffresponse,
|
|
615
|
+
eventresponse,
|
|
616
|
+
heldkeysreport,
|
|
249
617
|
hostpattern,
|
|
618
|
+
iswatchkind,
|
|
619
|
+
mapresponse,
|
|
250
620
|
normalizeendpoint,
|
|
621
|
+
observationmodeof,
|
|
622
|
+
observationresponse,
|
|
251
623
|
outcomeresponse,
|
|
252
624
|
parseproposal,
|
|
253
625
|
protocolversion,
|
|
254
626
|
randomid,
|
|
255
627
|
requestbody,
|
|
628
|
+
resolutionverdict,
|
|
629
|
+
selectorresponse,
|
|
256
630
|
sessionmemory,
|
|
257
|
-
|
|
631
|
+
signalsreport,
|
|
632
|
+
validatestep,
|
|
633
|
+
validatetargetref
|
|
258
634
|
};
|
|
259
635
|
//# sourceMappingURL=index.js.map
|