@wenathlan/extension 1.1.33 → 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 +7 -5
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +170 -3
- package/dist/index.js.map +3 -3
- package/dist/memory.d.ts +49 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +5 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +35 -1
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +177 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +331 -6
- package/extension/dist/background.js.map +3 -3
- package/extension/dist/manifest.json +1 -1
- package/extension/dist/pagebridge.js +935 -5
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +5 -4
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +191 -3
- 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
|
@@ -128,6 +128,116 @@ var sessionmemory = class {
|
|
|
128
128
|
async setdialogpolicy(policy) {
|
|
129
129
|
return this.adapter.set("dialogpolicy", policy);
|
|
130
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
|
+
}
|
|
131
241
|
};
|
|
132
242
|
function randomid() {
|
|
133
243
|
return crypto.randomUUID();
|
|
@@ -136,9 +246,10 @@ function randomid() {
|
|
|
136
246
|
// policy.ts
|
|
137
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"]);
|
|
138
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"]);
|
|
139
|
-
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"]);
|
|
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"]);
|
|
140
250
|
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
141
|
-
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"]);
|
|
142
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"]);
|
|
143
254
|
function normalizeendpoint(value) {
|
|
144
255
|
const endpoint = new URL(value.trim());
|
|
@@ -362,6 +473,24 @@ function validatestep(step, origin) {
|
|
|
362
473
|
if (!nonnegativeoption(retry, "settle")) return { allowed: false, reason: "The reviewed retry settle window must be zero or a positive number of milliseconds." };
|
|
363
474
|
if (!nonnegativeoption(retry, "tolerance")) return { allowed: false, reason: "The reviewed retry movement tolerance must be zero or a positive number of pixels." };
|
|
364
475
|
}
|
|
476
|
+
if (watchactions.has(step.kind)) {
|
|
477
|
+
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." };
|
|
478
|
+
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." };
|
|
479
|
+
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." };
|
|
480
|
+
if (!nonnegativeoption(options, "poll")) return { allowed: false, reason: "The reviewed watch poll interval must be zero or a positive number of milliseconds." };
|
|
481
|
+
}
|
|
482
|
+
if (step.kind === "waitquiet") {
|
|
483
|
+
const rule = options.quietrule;
|
|
484
|
+
if (!rule || typeof rule !== "object" || Array.isArray(rule)) return { allowed: false, reason: "A reviewed quietrule with an idle threshold is required in options." };
|
|
485
|
+
const quiet = rule;
|
|
486
|
+
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." };
|
|
487
|
+
if (!nonnegativeoption(quiet, "poll")) return { allowed: false, reason: "The reviewed quiet poll interval must be zero or a positive number of milliseconds." };
|
|
488
|
+
if (!nonnegativeoption(quiet, "timeout")) return { allowed: false, reason: "The reviewed quiet timeout must be zero or a positive number of milliseconds." };
|
|
489
|
+
}
|
|
490
|
+
if (step.kind === "diffsnapshots") {
|
|
491
|
+
const versions = options.versions;
|
|
492
|
+
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." };
|
|
493
|
+
}
|
|
365
494
|
return { allowed: true };
|
|
366
495
|
}
|
|
367
496
|
function sessiongate(input) {
|
|
@@ -378,6 +507,7 @@ function canexecute(input) {
|
|
|
378
507
|
if (!input.plan || input.plan.state !== "approved") return { allowed: false, reason: "The plan has not received explicit approval." };
|
|
379
508
|
if (input.plan.expiresat <= now) return { allowed: false, reason: "The approved plan has expired." };
|
|
380
509
|
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." };
|
|
510
|
+
if (input.step.kind === "readjson" && !origingranted(input.session, input.origin)) return { allowed: false, reason: "The json state read is outside the session origin grants." };
|
|
381
511
|
return validatestep(input.step, input.origin);
|
|
382
512
|
}
|
|
383
513
|
function canpreview(input) {
|
|
@@ -420,9 +550,17 @@ function resetforplan(progress, plan, now) {
|
|
|
420
550
|
const snapshot2 = { planid: progress.planid, completedsteps: progress.completedsteps, ...progress.outcomes ? { outcomes: progress.outcomes } : {}, updatedat: progress.updatedat };
|
|
421
551
|
return { planid: plan.id, completedsteps: [], outcomes: [], prior: [...progress.prior ?? [], snapshot2], updatedat: now };
|
|
422
552
|
}
|
|
553
|
+
function watchclosed(startedat, lifetime, now) {
|
|
554
|
+
return now >= startedat + lifetime;
|
|
555
|
+
}
|
|
556
|
+
function recordwatchcompletion(progress, planid, stepid, startedat, lifetime, now) {
|
|
557
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
558
|
+
if (!watchclosed(startedat, lifetime, now)) return base;
|
|
559
|
+
return recordstep(base, planid, stepid, now);
|
|
560
|
+
}
|
|
423
561
|
|
|
424
562
|
// version.ts
|
|
425
|
-
var packageversion = "1.1.
|
|
563
|
+
var packageversion = "1.1.34";
|
|
426
564
|
|
|
427
565
|
// types.ts
|
|
428
566
|
var protocolversion = packageversion;
|
|
@@ -489,6 +627,19 @@ function mapresponse(input) {
|
|
|
489
627
|
function heldkeysreport(input) {
|
|
490
628
|
return { version: protocolversion, tabid: input.tabid, heldkeys: input.holds };
|
|
491
629
|
}
|
|
630
|
+
function observationresponse(input) {
|
|
631
|
+
return JSON.stringify({ version: protocolversion, planid: input.plan.id, planstate: input.plan.state, observation: input.observation });
|
|
632
|
+
}
|
|
633
|
+
function signalsreport(input) {
|
|
634
|
+
const signals = input.signals;
|
|
635
|
+
return {
|
|
636
|
+
version: protocolversion,
|
|
637
|
+
...signals && signals.language !== void 0 ? { language: signals.language } : {},
|
|
638
|
+
...signals && signals.template !== void 0 ? { template: signals.template } : {},
|
|
639
|
+
...signals && signals.scrolllocked !== void 0 ? { scrolllocked: signals.scrolllocked } : {},
|
|
640
|
+
...signals && signals.banner !== void 0 ? { banner: signals.banner } : {}
|
|
641
|
+
};
|
|
642
|
+
}
|
|
492
643
|
|
|
493
644
|
// extension/browsertabs.ts
|
|
494
645
|
var browserkinds = /* @__PURE__ */ new Set(["tablist", "tabcreate", "tabactivate", "tabclose", "tabreload", "tabsnapshot", "windowlist", "windowcreate", "windowclose", "zoomset", "windowresize", "downloadfile"]);
|
|
@@ -710,6 +861,8 @@ function heldkeys(holds, tabid2) {
|
|
|
710
861
|
var sessionduration = 15 * 60 * 1e3;
|
|
711
862
|
var freshcheckkinds = /* @__PURE__ */ new Set(["focus", "inspect", "click", "type", "scroll", "select", "hover"]);
|
|
712
863
|
var pointerkinds = /* @__PURE__ */ new Set(["movepointer", "clickpoint", "shiftclick", "clicktext", "clickaria", "clickname", "pierceshadow"]);
|
|
864
|
+
var watchstepkinds = /* @__PURE__ */ new Set(["watchmutate", "watchbanner", "watchfocus"]);
|
|
865
|
+
var observationstepkinds = /* @__PURE__ */ new Set(["a11ytree", "readvisible", "readertree", "detectlists", "detecttables", "readjson", "detectinfinitescroll", "detectvirtual", "detectlazy", "readscrollpos", "readlang", "readoutline", "countpages", "listshadow", "listframes", "classifypage", "fingerprintsection", "readselection", "detectsticky", "detectscrolllock", "readopengraph", "detectlanguage", "deriveselector"]);
|
|
713
866
|
var chromestorage = {
|
|
714
867
|
async get(key) {
|
|
715
868
|
return (await chrome.storage.local.get(key))[key];
|
|
@@ -753,7 +906,10 @@ async function snapshot(tabid2) {
|
|
|
753
906
|
} });
|
|
754
907
|
const value = result[0]?.result;
|
|
755
908
|
if (!value) throw new Error("The page did not return an observation.");
|
|
756
|
-
|
|
909
|
+
const observationcapture = value;
|
|
910
|
+
const version = await memory.nextobservationversion();
|
|
911
|
+
await memory.setobservation({ version, observation: observationcapture });
|
|
912
|
+
return observationcapture;
|
|
757
913
|
}
|
|
758
914
|
function plandialogpolicy(plan) {
|
|
759
915
|
const step = plan?.steps.find((candidate) => candidate.kind === "dismissdialog");
|
|
@@ -874,6 +1030,9 @@ function stepauditkind(step, ok) {
|
|
|
874
1030
|
if (step.kind === "keyhold" || step.kind === "keyrelease") return "hold";
|
|
875
1031
|
if (step.kind === "retryaction") return "retry";
|
|
876
1032
|
if (pointerkinds.has(step.kind)) return "pointer";
|
|
1033
|
+
if (watchstepkinds.has(step.kind)) return "watch";
|
|
1034
|
+
if (step.kind === "diffsnapshots") return "diff";
|
|
1035
|
+
if (observationstepkinds.has(step.kind)) return "observation";
|
|
877
1036
|
return ok ? "action" : "error";
|
|
878
1037
|
}
|
|
879
1038
|
function resolvedinnerstep(step, plan) {
|
|
@@ -960,6 +1119,141 @@ async function executeenterframe(step, plan, tabid2, origin) {
|
|
|
960
1119
|
const derived = { ...step, options: JSON.stringify({ ...options, kind: inner.kind, ...inner.target ? { target: inner.target } : {}, ...inner.value ? { value: inner.value } : {}, ...inneroptions ? { options: inneroptions } : {} }) };
|
|
961
1120
|
return dispatchpagestep(derived, tabid2, origin, plan);
|
|
962
1121
|
}
|
|
1122
|
+
function observationnodes(observationcapture) {
|
|
1123
|
+
return observationcapture.interactive.map((entry) => ({ selector: entry.selector, tag: entry.role, text: entry.label, attributes: {} }));
|
|
1124
|
+
}
|
|
1125
|
+
function detailarray(details, key) {
|
|
1126
|
+
const value = details?.[key];
|
|
1127
|
+
return Array.isArray(value) ? value : [];
|
|
1128
|
+
}
|
|
1129
|
+
async function executediffsnapshots(step, session, plan, tabid2, origin) {
|
|
1130
|
+
const options = stepoptions2(step);
|
|
1131
|
+
const versions = Array.isArray(options.versions) ? options.versions.filter((item) => typeof item === "number") : [];
|
|
1132
|
+
const baseversion = versions[0];
|
|
1133
|
+
const targetversion = versions[1];
|
|
1134
|
+
if (baseversion === void 0 || targetversion === void 0) throw new Error("Two reviewed observation versions are required.");
|
|
1135
|
+
const base = await memory.getobservation(baseversion);
|
|
1136
|
+
const target = await memory.getobservation(targetversion);
|
|
1137
|
+
if (!base || !target) throw new Error("A reviewed observation version has not been captured yet.");
|
|
1138
|
+
const derived = { ...step, options: JSON.stringify({ ...options, base: observationnodes(base.observation), target: observationnodes(target.observation) }) };
|
|
1139
|
+
const output = await dispatchpagestep(derived, tabid2, origin, plan) ?? { ok: false, summary: "The snapshot diff returned no result." };
|
|
1140
|
+
const diff = {
|
|
1141
|
+
baseversion,
|
|
1142
|
+
targetversion,
|
|
1143
|
+
added: detailarray(output.details, "added"),
|
|
1144
|
+
removed: detailarray(output.details, "removed"),
|
|
1145
|
+
changed: detailarray(output.details, "changed"),
|
|
1146
|
+
at: Date.now()
|
|
1147
|
+
};
|
|
1148
|
+
await memory.adddiff(diff);
|
|
1149
|
+
await audit("diff", `Diffed observation versions ${baseversion} and ${targetversion}: ${diff.added.length} added, ${diff.removed.length} removed and ${diff.changed.length} changed nodes.`, { ...session ? { sessionid: session.id } : {}, planid: plan.id, stepid: step.id });
|
|
1150
|
+
return { ok: Boolean(output.ok), summary: output.summary, details: { ...output.details ?? {}, versions: [baseversion, targetversion] } };
|
|
1151
|
+
}
|
|
1152
|
+
async function executewatchstep(step, session, plan, tabid2, origin) {
|
|
1153
|
+
const options = stepoptions2(step);
|
|
1154
|
+
const watchid = typeof options.watchid === "string" && options.watchid.trim() ? options.watchid : randomid();
|
|
1155
|
+
const scopes = Array.isArray(options.scopes) ? options.scopes.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
1156
|
+
const events = Array.isArray(options.events) ? options.events.filter((item) => typeof item === "string" && item.trim().length > 0) : [];
|
|
1157
|
+
const lifetime = typeof options.lifetime === "number" && Number.isFinite(options.lifetime) && options.lifetime > 0 ? options.lifetime : 0;
|
|
1158
|
+
const watch = { watchid, kind: step.kind, stepid: step.id, sessionid: session.id, origin, scopes, events, startedat: Date.now(), lifetime };
|
|
1159
|
+
await memory.addwatch(watch);
|
|
1160
|
+
await audit("watch", `Watch ${step.kind} registered under id ${watchid} for the reviewed lifetime of ${lifetime} milliseconds inside ${scopes.length > 0 ? `scopes ${scopes.join(", ")}` : "the whole document"}.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
|
|
1161
|
+
const output = await dispatchpagestep(step, tabid2, origin, plan) ?? { ok: false, summary: "The watch returned no result." };
|
|
1162
|
+
await memory.closewatch(watchid, Date.now());
|
|
1163
|
+
if (step.kind === "watchmutate") {
|
|
1164
|
+
for (const entry of detailarray(output.details, "events")) {
|
|
1165
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1166
|
+
const record2 = entry;
|
|
1167
|
+
await memory.addmutationevent({ ...record2, sessionid: session.id });
|
|
1168
|
+
}
|
|
1169
|
+
}
|
|
1170
|
+
if (step.kind === "watchfocus") {
|
|
1171
|
+
for (const entry of detailarray(output.details, "events")) {
|
|
1172
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1173
|
+
const record2 = entry;
|
|
1174
|
+
await memory.addfocusevent({ ...record2, sessionid: session.id });
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
if (step.kind === "watchbanner") {
|
|
1178
|
+
for (const entry of detailarray(output.details, "banners")) {
|
|
1179
|
+
if (!entry || typeof entry !== "object") continue;
|
|
1180
|
+
const record2 = entry;
|
|
1181
|
+
await memory.addbanner({ ...record2, sessionid: session.id });
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
await audit("watch", `Watch ${watchid} closed after its reviewed lifetime of ${lifetime} milliseconds.`, { sessionid: session.id, planid: plan.id, stepid: step.id });
|
|
1185
|
+
return { output, watch };
|
|
1186
|
+
}
|
|
1187
|
+
async function refreshsignals(step, output) {
|
|
1188
|
+
const current = await memory.getsignals();
|
|
1189
|
+
const next = current ? { ...current, refreshedat: Date.now() } : { refreshedat: Date.now() };
|
|
1190
|
+
let changed = false;
|
|
1191
|
+
if (step.kind === "readlang" || step.kind === "detectlanguage") {
|
|
1192
|
+
const language = output?.details?.language;
|
|
1193
|
+
if (typeof language === "string" && language) {
|
|
1194
|
+
next.language = language;
|
|
1195
|
+
changed = true;
|
|
1196
|
+
}
|
|
1197
|
+
}
|
|
1198
|
+
if (step.kind === "classifypage") {
|
|
1199
|
+
const template = output?.details?.template;
|
|
1200
|
+
if (typeof template === "string" && template) {
|
|
1201
|
+
next.template = template;
|
|
1202
|
+
changed = true;
|
|
1203
|
+
}
|
|
1204
|
+
}
|
|
1205
|
+
if (step.kind === "detectscrolllock") {
|
|
1206
|
+
const locked = output?.details?.locked;
|
|
1207
|
+
if (typeof locked === "boolean") {
|
|
1208
|
+
next.scrolllocked = locked;
|
|
1209
|
+
changed = true;
|
|
1210
|
+
}
|
|
1211
|
+
}
|
|
1212
|
+
if (step.kind === "watchbanner") {
|
|
1213
|
+
const banners = detailarray(output?.details, "banners");
|
|
1214
|
+
const first = banners[0];
|
|
1215
|
+
if (first && typeof first.kind === "string") {
|
|
1216
|
+
next.banner = first.kind;
|
|
1217
|
+
changed = true;
|
|
1218
|
+
}
|
|
1219
|
+
}
|
|
1220
|
+
if (changed) await memory.setsignals(next);
|
|
1221
|
+
}
|
|
1222
|
+
async function recordevidence(step, output, session, plan, origin) {
|
|
1223
|
+
const extra = { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid: step.id };
|
|
1224
|
+
if (step.kind === "a11ytree" && output?.details?.tree && typeof output.details.tree === "object") {
|
|
1225
|
+
const version = await memory.nextobservationversion();
|
|
1226
|
+
await memory.adda11ytree({ version, tree: output.details.tree, capturedat: Date.now() });
|
|
1227
|
+
await audit("observation", `Accessibility tree captured under observation version ${version} with ${output.details.nodecount ?? "an unknown count of"} nodes.`, extra);
|
|
1228
|
+
}
|
|
1229
|
+
if (step.kind === "readertree" && output?.details?.article && typeof output.details.article === "object") {
|
|
1230
|
+
const version = await memory.nextobservationversion();
|
|
1231
|
+
const article = output.details.article;
|
|
1232
|
+
await memory.addreaderarticle({ version, article, capturedat: Date.now() });
|
|
1233
|
+
await audit("observation", `Reader article captured under observation version ${version} with ${article.blocks.length} blocks and ${article.words} words.`, extra);
|
|
1234
|
+
}
|
|
1235
|
+
if (step.kind === "classifypage" && typeof output?.details?.template === "string") {
|
|
1236
|
+
const fingerprint = typeof output.details.fingerprint === "string" ? output.details.fingerprint : "";
|
|
1237
|
+
const profile = { origin, template: output.details.template, fingerprint, at: Date.now() };
|
|
1238
|
+
await memory.addtemplate(profile);
|
|
1239
|
+
await audit("observation", `Page template ${profile.template} classified for ${origin}${fingerprint ? ` with fingerprint ${fingerprint}` : ""}.`, extra);
|
|
1240
|
+
}
|
|
1241
|
+
if (step.kind === "fingerprintsection" && typeof output?.details?.fingerprint === "string") {
|
|
1242
|
+
const profile = { origin, template: "", fingerprint: output.details.fingerprint, ...typeof output.details.section === "string" ? { section: output.details.section } : {}, at: Date.now() };
|
|
1243
|
+
await memory.addtemplate(profile);
|
|
1244
|
+
await audit("observation", `Section fingerprint ${profile.fingerprint} computed for ${origin}.`, extra);
|
|
1245
|
+
}
|
|
1246
|
+
if (step.kind === "deriveselector") {
|
|
1247
|
+
const candidates = detailarray(output?.details, "candidates");
|
|
1248
|
+
const best = candidates[0];
|
|
1249
|
+
if (best && typeof best.selector === "string" && best.selector) {
|
|
1250
|
+
const record2 = { stepid: step.id, selector: best.selector, strategy: best.strategy, score: best.score, at: Date.now() };
|
|
1251
|
+
await memory.addselector(record2);
|
|
1252
|
+
await audit("observation", `Derived selector ${record2.selector} through the ${record2.strategy} strategy with stability ${record2.score}.`, extra);
|
|
1253
|
+
}
|
|
1254
|
+
}
|
|
1255
|
+
await refreshsignals(step, output);
|
|
1256
|
+
}
|
|
963
1257
|
async function executestep(stepid) {
|
|
964
1258
|
const session = await memory.getsession();
|
|
965
1259
|
const plan = await memory.getplan();
|
|
@@ -969,6 +1263,7 @@ async function executestep(stepid) {
|
|
|
969
1263
|
const gate = canexecute({ session, plan, step, tabid: tab.id, origin });
|
|
970
1264
|
if (!gate.allowed) throw new Error(gate.reason);
|
|
971
1265
|
let output;
|
|
1266
|
+
let watchwindow;
|
|
972
1267
|
if (isbrowserkind(step.kind)) {
|
|
973
1268
|
const capability = requiredcapability(step.kind);
|
|
974
1269
|
if (capability) {
|
|
@@ -988,6 +1283,13 @@ async function executestep(stepid) {
|
|
|
988
1283
|
output = await executemapclicks(step, plan, tab.id, origin);
|
|
989
1284
|
} else if (step.kind === "enterframe") {
|
|
990
1285
|
output = await executeenterframe(step, plan, tab.id, origin);
|
|
1286
|
+
} else if (watchstepkinds.has(step.kind)) {
|
|
1287
|
+
if (!session || !plan || plan.state !== "approved") throw new Error("Watch kinds refuse to run outside an approved session plan.");
|
|
1288
|
+
const watched = await executewatchstep(step, session, plan, tab.id, origin);
|
|
1289
|
+
output = watched.output;
|
|
1290
|
+
watchwindow = { startedat: watched.watch.startedat, lifetime: watched.watch.lifetime };
|
|
1291
|
+
} else if (step.kind === "diffsnapshots") {
|
|
1292
|
+
output = await executediffsnapshots(step, session, plan, tab.id, origin);
|
|
991
1293
|
} else {
|
|
992
1294
|
if (step.target && freshcheckkinds.has(step.kind)) {
|
|
993
1295
|
const fresh = await snapshot(tab.id);
|
|
@@ -995,6 +1297,7 @@ async function executestep(stepid) {
|
|
|
995
1297
|
}
|
|
996
1298
|
output = await dispatchpagestep(step, tab.id, origin, plan);
|
|
997
1299
|
}
|
|
1300
|
+
await recordevidence(step, output, session, plan, origin);
|
|
998
1301
|
const summary = output?.summary ?? "The page action returned no result.";
|
|
999
1302
|
const resolved = output?.details?.resolvedtarget;
|
|
1000
1303
|
if (resolved) {
|
|
@@ -1005,7 +1308,8 @@ async function executestep(stepid) {
|
|
|
1005
1308
|
await audit(auditkind, summary, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid });
|
|
1006
1309
|
await memory.addoutcome(outcome);
|
|
1007
1310
|
if (output?.ok && plan) {
|
|
1008
|
-
const
|
|
1311
|
+
const base = await memory.getprogress();
|
|
1312
|
+
const completed = watchwindow ? recordwatchcompletion(base, plan.id, stepid, watchwindow.startedat, watchwindow.lifetime, Date.now()) : recordstep(base, plan.id, stepid, Date.now());
|
|
1009
1313
|
const tracked = recordoutcome(completed, plan.id, outcome, Date.now());
|
|
1010
1314
|
await memory.setprogress(tracked);
|
|
1011
1315
|
if (iscomplete(tracked, plan) && plan.state === "approved") {
|
|
@@ -1083,7 +1387,10 @@ async function handlerequest(message, sender) {
|
|
|
1083
1387
|
const holds = heldkeys(await memory.getholds());
|
|
1084
1388
|
const observationversion = await memory.getobservationversion();
|
|
1085
1389
|
const map = observationversion !== void 0 && observationversion > 0 ? await memory.getmap(observationversion) : void 0;
|
|
1086
|
-
|
|
1390
|
+
const a11y = (await memory.geta11ytrees())[0];
|
|
1391
|
+
const reader = (await memory.getreaderarticles())[0];
|
|
1392
|
+
const signals = await memory.getsignals();
|
|
1393
|
+
return { config: await memory.getconfig(), session, plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit(), capabilities: await refreshcapabilities(), outcomes: await memory.getoutcomes(), holds: heldkeysreport({ tabid: session?.tabid ?? 0, holds }), dialogs: await memory.getdialogs(), retries: await memory.getretries(), ...signals ? { signals: signalsreport({ signals }) } : { signals: signalsreport({}) }, banners: await memory.getbanners(), mutationevents: await memory.getmutationevents(), focusevents: await memory.getfocusevents(), diffs: await memory.getdiffs(), selectors: await memory.getselectors(), ...a11y ? { a11y } : {}, ...reader ? { reader } : {}, ...map ? { map } : {} };
|
|
1087
1394
|
}
|
|
1088
1395
|
case "capabilities":
|
|
1089
1396
|
return refreshcapabilities();
|
|
@@ -1135,6 +1442,14 @@ async function handlerequest(message, sender) {
|
|
|
1135
1442
|
if (!map) throw new Error("No clickable map has been captured yet.");
|
|
1136
1443
|
return JSON.parse(mapresponse({ map, plan }));
|
|
1137
1444
|
}
|
|
1445
|
+
case "observation": {
|
|
1446
|
+
const plan = await memory.getplan();
|
|
1447
|
+
if (!plan) throw new Error("No plan is available for an observation envelope.");
|
|
1448
|
+
const version = await memory.getobservationversion();
|
|
1449
|
+
const record2 = version !== void 0 ? await memory.getobservation(version) : void 0;
|
|
1450
|
+
if (!record2) throw new Error("No observation has been captured yet.");
|
|
1451
|
+
return JSON.parse(observationresponse({ observation: record2.observation, plan }));
|
|
1452
|
+
}
|
|
1138
1453
|
case "pausesession":
|
|
1139
1454
|
return pausesession();
|
|
1140
1455
|
case "resumesession":
|
|
@@ -1155,6 +1470,16 @@ chrome.runtime.onMessage.addListener((message, sender, sendresponse) => {
|
|
|
1155
1470
|
handlerequest(message, sender).then((value) => sendresponse({ ok: true, value })).catch((error) => sendresponse({ ok: false, error: error instanceof Error ? error.message : String(error) }));
|
|
1156
1471
|
return true;
|
|
1157
1472
|
});
|
|
1473
|
+
async function reconcilewatches() {
|
|
1474
|
+
for (const watch of await memory.getwatches()) {
|
|
1475
|
+
if (watch.closedat !== void 0) continue;
|
|
1476
|
+
if (!watchclosed(watch.startedat, watch.lifetime, Date.now())) continue;
|
|
1477
|
+
await memory.closewatch(watch.watchid, Date.now());
|
|
1478
|
+
await audit("watch", `Watch ${watch.watchid} of ${watch.kind} closed on service worker restart after its reviewed lifetime of ${watch.lifetime} milliseconds.`, { sessionid: watch.sessionid, stepid: watch.stepid });
|
|
1479
|
+
}
|
|
1480
|
+
}
|
|
1481
|
+
reconcilewatches().catch(() => {
|
|
1482
|
+
});
|
|
1158
1483
|
chrome.runtime.onConnect.addListener((port) => {
|
|
1159
1484
|
if (port.name !== "devthinksidepanel" || port.sender?.id !== chrome.runtime.id || !port.sender.url?.startsWith(chrome.runtime.getURL(""))) return port.disconnect();
|
|
1160
1485
|
port.onMessage.addListener((message) => {
|