@wenathlan/extension 1.1.31 → 1.1.32
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 +16 -12
- package/dist/cli.js +23 -2
- package/dist/index.js +95 -13
- package/dist/index.js.map +2 -2
- package/dist/memory.d.ts +9 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +6 -2
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +7 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +35 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +250 -40
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +7 -1
- package/extension/dist/pagebridge.js +384 -15
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +15 -0
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +65 -15
- package/extension/dist/sidepanel.js.map +2 -2
- package/extension/dist/style.css +1 -0
- package/extension/manifest.json +7 -1
- package/package.json +1 -1
package/dist/types.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"AAGA,eAAO,MAAM,eAAe,UAAiB,CAAC;AAE9C,MAAM,MAAM,UAAU,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,OAAO,GAAG,MAAM,GAAG,UAAU,GAAG,QAAQ,GAAG,QAAQ,GAAG,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"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"}
|
package/dist/version.d.ts
CHANGED
|
@@ -34,12 +34,37 @@ var sessionmemory = class {
|
|
|
34
34
|
async setprogress(value) {
|
|
35
35
|
return this.adapter.set("progress", value);
|
|
36
36
|
}
|
|
37
|
+
async getcapabilities() {
|
|
38
|
+
return this.adapter.get("capabilities");
|
|
39
|
+
}
|
|
40
|
+
async setcapabilities(value) {
|
|
41
|
+
return this.adapter.set("capabilities", value);
|
|
42
|
+
}
|
|
43
|
+
async getsettings() {
|
|
44
|
+
return this.adapter.get("settings");
|
|
45
|
+
}
|
|
46
|
+
async setsettings(value) {
|
|
47
|
+
return this.adapter.set("settings", value);
|
|
48
|
+
}
|
|
37
49
|
async getaudit() {
|
|
38
50
|
return await this.adapter.get("audit") ?? [];
|
|
39
51
|
}
|
|
52
|
+
async getoutcomes() {
|
|
53
|
+
return await this.adapter.get("outcomes") ?? [];
|
|
54
|
+
}
|
|
55
|
+
/** Records one audit event; retention is a user setting and an absent setting keeps every event. */
|
|
40
56
|
async addaudi(event) {
|
|
41
57
|
const records = await this.getaudit();
|
|
42
|
-
|
|
58
|
+
const combined = [event, ...records];
|
|
59
|
+
const retention = (await this.getsettings())?.auditretention;
|
|
60
|
+
await this.adapter.set("audit", retention === void 0 ? combined : combined.slice(0, retention));
|
|
61
|
+
}
|
|
62
|
+
/** Records one step outcome; retention is a user setting and an absent setting keeps every outcome. */
|
|
63
|
+
async addoutcome(outcome) {
|
|
64
|
+
const records = await this.getoutcomes();
|
|
65
|
+
const combined = [outcome, ...records];
|
|
66
|
+
const retention = (await this.getsettings())?.outcomeretention;
|
|
67
|
+
await this.adapter.set("outcomes", retention === void 0 ? combined : combined.slice(0, retention));
|
|
43
68
|
}
|
|
44
69
|
};
|
|
45
70
|
function randomid() {
|
|
@@ -47,10 +72,12 @@ function randomid() {
|
|
|
47
72
|
}
|
|
48
73
|
|
|
49
74
|
// policy.ts
|
|
50
|
-
var sensitiveactions = /* @__PURE__ */ new Set(["click", "type", "navigate", "select"]);
|
|
51
|
-
var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover"]);
|
|
52
|
-
var
|
|
53
|
-
var
|
|
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"]);
|
|
78
|
+
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
79
|
+
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"]);
|
|
80
|
+
var valueactions = /* @__PURE__ */ new Set(["presskey", "drag", "drop", "upload", "readattribute", "removeattribute", "waittext", "evaluate", "zoomset", "tabactivate", "tabclose", "tabreload", "windowclose", "windowresize", "tabcreate", "windowcreate", "downloadfile"]);
|
|
54
81
|
function normalizeendpoint(value) {
|
|
55
82
|
const endpoint = new URL(value.trim());
|
|
56
83
|
if (endpoint.protocol !== "https:") throw new Error("Devthink accepts HTTPS endpoints only.");
|
|
@@ -67,16 +94,46 @@ function actionrisk(kind) {
|
|
|
67
94
|
if (sensitiveactions.has(kind)) return "sensitive";
|
|
68
95
|
return interactionactions.has(kind) ? "interaction" : "read";
|
|
69
96
|
}
|
|
97
|
+
function parseoptions(step) {
|
|
98
|
+
if (step.options === void 0) return {};
|
|
99
|
+
let parsed;
|
|
100
|
+
try {
|
|
101
|
+
parsed = JSON.parse(step.options);
|
|
102
|
+
} catch {
|
|
103
|
+
throw new Error("Step options must be a JSON object.");
|
|
104
|
+
}
|
|
105
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) throw new Error("Step options must be a JSON object.");
|
|
106
|
+
return parsed;
|
|
107
|
+
}
|
|
108
|
+
function requiredcapability(kind) {
|
|
109
|
+
if (kind === "tablist") return "tabs";
|
|
110
|
+
if (kind === "downloadfile") return "downloads";
|
|
111
|
+
return void 0;
|
|
112
|
+
}
|
|
70
113
|
function waitduration(step) {
|
|
71
114
|
const requested = step.value ? Number.parseInt(step.value, 10) : 250;
|
|
72
115
|
if (!Number.isFinite(requested) || requested < 0) throw new Error("Wait duration must be zero or a positive number of milliseconds.");
|
|
73
|
-
return
|
|
116
|
+
return requested;
|
|
117
|
+
}
|
|
118
|
+
function isnumericid(value) {
|
|
119
|
+
return typeof value === "string" && /^\d+$/.test(value);
|
|
120
|
+
}
|
|
121
|
+
function numericoption(options, key) {
|
|
122
|
+
return options[key] === void 0 || typeof options[key] === "number" && Number.isFinite(options[key]);
|
|
74
123
|
}
|
|
75
124
|
function validatestep(step, origin) {
|
|
76
125
|
if (!allowedactions.has(step.kind)) return { allowed: false, reason: "Unsupported action kind." };
|
|
77
126
|
if (!step.summary.trim()) return { allowed: false, reason: "A human-readable action summary is required." };
|
|
78
127
|
if (targetactions.has(step.kind) && !step.target?.trim()) return { allowed: false, reason: "A page target is required." };
|
|
128
|
+
if (valueactions.has(step.kind) && !step.value?.trim()) return { allowed: false, reason: "A reviewed value is required." };
|
|
79
129
|
if (step.kind === "select" && !step.value?.trim()) return { allowed: false, reason: "A reviewed option value is required." };
|
|
130
|
+
if (step.kind === "navigate" && !step.value) return { allowed: false, reason: "A navigation URL is required." };
|
|
131
|
+
let options;
|
|
132
|
+
try {
|
|
133
|
+
options = parseoptions(step);
|
|
134
|
+
} catch {
|
|
135
|
+
return { allowed: false, reason: "Step options must be a JSON object." };
|
|
136
|
+
}
|
|
80
137
|
if (step.kind === "wait") {
|
|
81
138
|
try {
|
|
82
139
|
waitduration(step);
|
|
@@ -85,13 +142,37 @@ function validatestep(step, origin) {
|
|
|
85
142
|
}
|
|
86
143
|
}
|
|
87
144
|
if (step.kind === "navigate") {
|
|
88
|
-
if (!step.value) return { allowed: false, reason: "A navigation URL is required." };
|
|
89
145
|
try {
|
|
90
|
-
if (new URL(step.value).origin !== origin) return { allowed: false, reason: "Navigation must remain within the approved origin." };
|
|
146
|
+
if (new URL(step.value ?? "").origin !== origin) return { allowed: false, reason: "Navigation must remain within the approved origin." };
|
|
91
147
|
} catch {
|
|
92
148
|
return { allowed: false, reason: "Navigation URL is invalid." };
|
|
93
149
|
}
|
|
94
150
|
}
|
|
151
|
+
if (step.kind === "tabcreate" || step.kind === "windowcreate" || step.kind === "downloadfile") {
|
|
152
|
+
try {
|
|
153
|
+
const url = new URL(step.value ?? "");
|
|
154
|
+
if (url.protocol !== "https:") return { allowed: false, reason: "The reviewed URL must use HTTPS." };
|
|
155
|
+
} catch {
|
|
156
|
+
return { allowed: false, reason: "The reviewed URL is invalid." };
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
if (step.kind === "tabactivate" || step.kind === "tabclose" || step.kind === "tabreload" || step.kind === "windowclose" || step.kind === "windowresize") {
|
|
160
|
+
if (!isnumericid(step.value)) return { allowed: false, reason: "A numeric browser id is required." };
|
|
161
|
+
}
|
|
162
|
+
if (step.kind === "zoomset") {
|
|
163
|
+
const zoom = Number(step.value);
|
|
164
|
+
if (!Number.isFinite(zoom) || zoom <= 0) return { allowed: false, reason: "The reviewed zoom must be a positive number." };
|
|
165
|
+
}
|
|
166
|
+
if (step.kind === "setattribute" || step.kind === "writestorage") {
|
|
167
|
+
const keyname = step.kind === "setattribute" ? "name" : "key";
|
|
168
|
+
if (typeof options[keyname] !== "string" || !options[keyname].trim()) return { allowed: false, reason: `A reviewed ${keyname} is required in options.` };
|
|
169
|
+
if (typeof options.value !== "string") return { allowed: false, reason: "A reviewed value is required in options." };
|
|
170
|
+
}
|
|
171
|
+
if (step.kind === "windowresize") {
|
|
172
|
+
if (typeof options.width !== "number" || typeof options.height !== "number" || !Number.isFinite(options.width) || !Number.isFinite(options.height)) return { allowed: false, reason: "Reviewed width and height numbers are required in options." };
|
|
173
|
+
}
|
|
174
|
+
if ((step.kind === "scrollpage" || step.kind === "scrollby") && (!numericoption(options, "x") || !numericoption(options, "y"))) return { allowed: false, reason: "Scroll amounts must be numbers in options." };
|
|
175
|
+
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." };
|
|
95
176
|
return { allowed: true };
|
|
96
177
|
}
|
|
97
178
|
function sessiongate(input) {
|
|
@@ -115,7 +196,7 @@ function canpreview(input) {
|
|
|
115
196
|
if (!gate.allowed) return gate;
|
|
116
197
|
if (!input.plan || !["pending", "approved"].includes(input.plan.state)) return { allowed: false, reason: "Only a reviewed pending or approved plan can be previewed." };
|
|
117
198
|
if (input.plan.expiresat <= now) return { allowed: false, reason: "The reviewed plan has expired." };
|
|
118
|
-
if (!
|
|
199
|
+
if (!targetactions.has(input.step.kind)) return { allowed: false, reason: "Only a target-based action can be previewed." };
|
|
119
200
|
return validatestep(input.step, input.origin);
|
|
120
201
|
}
|
|
121
202
|
|
|
@@ -128,6 +209,10 @@ function recordstep(progress, planid, stepid, now) {
|
|
|
128
209
|
if (base.completedsteps.includes(stepid)) return { ...base, updatedat: now };
|
|
129
210
|
return { planid, completedsteps: [...base.completedsteps, stepid], updatedat: now };
|
|
130
211
|
}
|
|
212
|
+
function recordoutcome(progress, planid, outcome, now) {
|
|
213
|
+
const base = progress && progress.planid === planid ? progress : emptyprogress(planid, now);
|
|
214
|
+
return { ...base, outcomes: [...base.outcomes ?? [], outcome], updatedat: now };
|
|
215
|
+
}
|
|
131
216
|
function iscomplete(progress, plan) {
|
|
132
217
|
if (!progress || progress.planid !== plan.id) return false;
|
|
133
218
|
const required = plan.steps.map((step) => step.id);
|
|
@@ -135,11 +220,13 @@ function iscomplete(progress, plan) {
|
|
|
135
220
|
}
|
|
136
221
|
function resetforplan(progress, plan, now) {
|
|
137
222
|
if (progress && progress.planid === plan.id) return progress;
|
|
138
|
-
return emptyprogress(plan.id, now);
|
|
223
|
+
if (!progress) return emptyprogress(plan.id, now);
|
|
224
|
+
const snapshot2 = { planid: progress.planid, completedsteps: progress.completedsteps, ...progress.outcomes ? { outcomes: progress.outcomes } : {}, updatedat: progress.updatedat };
|
|
225
|
+
return { planid: plan.id, completedsteps: [], outcomes: [], prior: [...progress.prior ?? [], snapshot2], updatedat: now };
|
|
139
226
|
}
|
|
140
227
|
|
|
141
228
|
// version.ts
|
|
142
|
-
var packageversion = "1.1.
|
|
229
|
+
var packageversion = "1.1.32";
|
|
143
230
|
|
|
144
231
|
// types.ts
|
|
145
232
|
var protocolversion = packageversion;
|
|
@@ -158,7 +245,7 @@ function parseproposal(value, origin) {
|
|
|
158
245
|
if (root.version !== protocolversion) throw new Error("Unsupported protocol version.");
|
|
159
246
|
const planinput = record(root.plan);
|
|
160
247
|
const stepsinput = planinput.steps;
|
|
161
|
-
if (!Array.isArray(stepsinput) || stepsinput.length === 0
|
|
248
|
+
if (!Array.isArray(stepsinput) || stepsinput.length === 0) throw new Error("A plan needs at least one step.");
|
|
162
249
|
const steps = stepsinput.map((input, index) => {
|
|
163
250
|
const candidate = record(input);
|
|
164
251
|
const kind = text(candidate.kind, `step ${index + 1} kind`);
|
|
@@ -168,31 +255,117 @@ function parseproposal(value, origin) {
|
|
|
168
255
|
summary: text(candidate.summary, `step ${index + 1} summary`),
|
|
169
256
|
risk: actionrisk(kind),
|
|
170
257
|
...typeof candidate.target === "string" ? { target: candidate.target } : {},
|
|
171
|
-
...typeof candidate.value === "string" ? { value: candidate.value } : {}
|
|
258
|
+
...typeof candidate.value === "string" ? { value: candidate.value } : {},
|
|
259
|
+
...typeof candidate.options === "string" ? { options: candidate.options } : {}
|
|
172
260
|
};
|
|
173
261
|
const evaluation = validatestep(step, origin);
|
|
174
262
|
if (!evaluation.allowed) throw new Error(evaluation.reason);
|
|
175
263
|
return step;
|
|
176
264
|
});
|
|
177
265
|
const createdat = Date.now();
|
|
266
|
+
const expiresat = typeof planinput.expiresat === "number" ? planinput.expiresat : createdat + 10 * 60 * 1e3;
|
|
178
267
|
const plan = {
|
|
179
268
|
id: typeof planinput.id === "string" ? planinput.id : crypto.randomUUID(),
|
|
180
269
|
objective: text(planinput.objective, "objective"),
|
|
181
270
|
origin,
|
|
182
271
|
steps,
|
|
183
272
|
createdat,
|
|
184
|
-
expiresat
|
|
273
|
+
expiresat,
|
|
185
274
|
state: "pending"
|
|
186
275
|
};
|
|
187
276
|
if (plan.expiresat <= createdat) throw new Error("Plan expiry must be in the future.");
|
|
188
277
|
return { version: protocolversion, plan };
|
|
189
278
|
}
|
|
190
279
|
function requestbody(input) {
|
|
191
|
-
return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation });
|
|
280
|
+
return JSON.stringify({ version: protocolversion, objective: input.objective, session: input.session, observation: input.observation, capabilities: input.capabilities });
|
|
281
|
+
}
|
|
282
|
+
|
|
283
|
+
// extension/browsertabs.ts
|
|
284
|
+
var browserkinds = /* @__PURE__ */ new Set(["tablist", "tabcreate", "tabactivate", "tabclose", "tabreload", "tabsnapshot", "windowlist", "windowcreate", "windowclose", "zoomset", "windowresize", "downloadfile"]);
|
|
285
|
+
function isbrowserkind(kind) {
|
|
286
|
+
return browserkinds.has(kind);
|
|
287
|
+
}
|
|
288
|
+
async function readcapabilities() {
|
|
289
|
+
const [tabs, downloads, clipboardread, clipboardwrite] = await Promise.all([
|
|
290
|
+
chrome.permissions.contains({ permissions: ["tabs"] }),
|
|
291
|
+
chrome.permissions.contains({ permissions: ["downloads"] }),
|
|
292
|
+
chrome.permissions.contains({ permissions: ["clipboardRead"] }),
|
|
293
|
+
chrome.permissions.contains({ permissions: ["clipboardWrite"] })
|
|
294
|
+
]);
|
|
295
|
+
return { tabs, downloads, clipboardread, clipboardwrite, reportedat: Date.now() };
|
|
296
|
+
}
|
|
297
|
+
function stepoptions(step) {
|
|
298
|
+
if (!step.options) return {};
|
|
299
|
+
try {
|
|
300
|
+
const parsed = JSON.parse(step.options);
|
|
301
|
+
return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
|
|
302
|
+
} catch {
|
|
303
|
+
return {};
|
|
304
|
+
}
|
|
305
|
+
}
|
|
306
|
+
function tabid(step) {
|
|
307
|
+
return Number.parseInt(step.value ?? "", 10);
|
|
308
|
+
}
|
|
309
|
+
async function runbrowseraction(step, sessiontabid, windowid) {
|
|
310
|
+
const options = stepoptions(step);
|
|
311
|
+
switch (step.kind) {
|
|
312
|
+
case "tablist": {
|
|
313
|
+
const tabs = await chrome.tabs.query({});
|
|
314
|
+
return { ok: true, summary: `Listed ${tabs.length} open tab${tabs.length === 1 ? "" : "s"}.`, details: { tabs: tabs.map((tab) => ({ id: tab.id ?? 0, index: tab.index, title: tab.title ?? "", url: tab.url ?? "", active: tab.active, pinned: tab.pinned, audible: tab.audible ?? false })) } };
|
|
315
|
+
}
|
|
316
|
+
case "tabcreate": {
|
|
317
|
+
const created = await chrome.tabs.create({ url: step.value, active: options.active !== false, pinned: options.pinned === true });
|
|
318
|
+
return { ok: true, summary: `Opened a new tab for ${step.value}.`, details: { tabid: created?.id ?? 0 } };
|
|
319
|
+
}
|
|
320
|
+
case "tabactivate": {
|
|
321
|
+
await chrome.tabs.update(tabid(step), { active: true });
|
|
322
|
+
return { ok: true, summary: `Activated tab ${tabid(step)}.` };
|
|
323
|
+
}
|
|
324
|
+
case "tabclose": {
|
|
325
|
+
await chrome.tabs.remove(tabid(step));
|
|
326
|
+
return { ok: true, summary: `Closed tab ${tabid(step)}.` };
|
|
327
|
+
}
|
|
328
|
+
case "tabreload": {
|
|
329
|
+
await chrome.tabs.reload(tabid(step), { bypassCache: options.bypasscache === true });
|
|
330
|
+
return { ok: true, summary: `Reloaded tab ${tabid(step)}.` };
|
|
331
|
+
}
|
|
332
|
+
case "tabsnapshot": {
|
|
333
|
+
const shot = await chrome.tabs.captureVisibleTab(windowid, { format: "png" });
|
|
334
|
+
return { ok: true, summary: "Captured the visible area of the active tab.", details: { shot } };
|
|
335
|
+
}
|
|
336
|
+
case "windowlist": {
|
|
337
|
+
const windows = await chrome.windows.getAll();
|
|
338
|
+
return { ok: true, summary: `Listed ${windows.length} open window${windows.length === 1 ? "" : "s"}.`, details: { windows: windows.map((item) => ({ id: item.id ?? 0, type: item.type, state: item.state ?? "", focused: item.focused })) } };
|
|
339
|
+
}
|
|
340
|
+
case "windowcreate": {
|
|
341
|
+
const created = await chrome.windows.create({ url: step.value ?? "about:blank", ...typeof options.width === "number" ? { width: options.width } : {}, ...typeof options.height === "number" ? { height: options.height } : {} });
|
|
342
|
+
return { ok: true, summary: `Opened a new window for ${step.value}.`, details: { windowid: created?.id ?? 0 } };
|
|
343
|
+
}
|
|
344
|
+
case "windowclose": {
|
|
345
|
+
await chrome.windows.remove(tabid(step));
|
|
346
|
+
return { ok: true, summary: `Closed window ${tabid(step)}.` };
|
|
347
|
+
}
|
|
348
|
+
case "zoomset": {
|
|
349
|
+
const zoom = Number(step.value);
|
|
350
|
+
await chrome.tabs.setZoom(sessiontabid, zoom);
|
|
351
|
+
return { ok: true, summary: `Set the tab zoom to ${zoom}.` };
|
|
352
|
+
}
|
|
353
|
+
case "windowresize": {
|
|
354
|
+
await chrome.windows.update(tabid(step), { width: options.width, height: options.height });
|
|
355
|
+
return { ok: true, summary: `Resized window ${tabid(step)}.` };
|
|
356
|
+
}
|
|
357
|
+
case "downloadfile": {
|
|
358
|
+
const downloadid = await chrome.downloads.download({ url: step.value ?? "" });
|
|
359
|
+
return { ok: true, summary: `Started the download of ${step.value}.`, details: { downloadid } };
|
|
360
|
+
}
|
|
361
|
+
default:
|
|
362
|
+
return { ok: false, summary: "Unsupported browser action." };
|
|
363
|
+
}
|
|
192
364
|
}
|
|
193
365
|
|
|
194
366
|
// extension/background.ts
|
|
195
367
|
var sessionduration = 15 * 60 * 1e3;
|
|
368
|
+
var freshcheckkinds = /* @__PURE__ */ new Set(["focus", "inspect", "click", "type", "scroll", "select", "hover"]);
|
|
196
369
|
var chromestorage = {
|
|
197
370
|
async get(key) {
|
|
198
371
|
return (await chrome.storage.local.get(key))[key];
|
|
@@ -208,6 +381,11 @@ function extensionpage(sender) {
|
|
|
208
381
|
async function audit(kind, summary, extra = {}) {
|
|
209
382
|
await memory.addaudi({ id: randomid(), kind, at: Date.now(), summary, ...extra });
|
|
210
383
|
}
|
|
384
|
+
async function refreshcapabilities() {
|
|
385
|
+
const report = await readcapabilities();
|
|
386
|
+
await memory.setcapabilities(report);
|
|
387
|
+
return report;
|
|
388
|
+
}
|
|
211
389
|
async function activecontext() {
|
|
212
390
|
const [tab] = await chrome.tabs.query({ active: true, lastFocusedWindow: true });
|
|
213
391
|
if (!tab?.id || !tab.url) throw new Error("No active web tab is available.");
|
|
@@ -215,9 +393,9 @@ async function activecontext() {
|
|
|
215
393
|
if (!origin.startsWith("https://")) throw new Error("Devthink can work with HTTPS pages only.");
|
|
216
394
|
return { tab, origin };
|
|
217
395
|
}
|
|
218
|
-
async function snapshot(
|
|
219
|
-
await chrome.scripting.executeScript({ target: { tabId:
|
|
220
|
-
const result = await chrome.scripting.executeScript({ target: { tabId:
|
|
396
|
+
async function snapshot(tabid2) {
|
|
397
|
+
await chrome.scripting.executeScript({ target: { tabId: tabid2 }, files: ["pagebridge.js"] });
|
|
398
|
+
const result = await chrome.scripting.executeScript({ target: { tabId: tabid2 }, func: () => {
|
|
221
399
|
const bridge = globalThis.devthinkbridge;
|
|
222
400
|
if (!bridge) throw new Error("Devthink page bridge is unavailable.");
|
|
223
401
|
return bridge.capturesnapshot();
|
|
@@ -228,7 +406,7 @@ async function snapshot(tabid) {
|
|
|
228
406
|
}
|
|
229
407
|
async function startsession() {
|
|
230
408
|
const { tab, origin } = await activecontext();
|
|
231
|
-
const session = { id: randomid(), tabid: tab.id, origin, startedat: Date.now(), expiresat: Date.now() + sessionduration };
|
|
409
|
+
const session = { id: randomid(), tabid: tab.id, origin, startedat: Date.now(), expiresat: Date.now() + sessionduration, grants: [origin] };
|
|
232
410
|
await memory.setsession(session);
|
|
233
411
|
await audit("session", `Session started for ${origin}.`, { sessionid: session.id });
|
|
234
412
|
return session;
|
|
@@ -245,7 +423,7 @@ async function diagnostic() {
|
|
|
245
423
|
}
|
|
246
424
|
function localplan(objective, session) {
|
|
247
425
|
const now = Date.now();
|
|
248
|
-
return { id: randomid(), objective, origin: session.origin, steps: [{ id: randomid(), kind: "observe", summary: "Capture a
|
|
426
|
+
return { id: randomid(), objective, origin: session.origin, steps: [{ id: randomid(), kind: "observe", summary: "Capture a complete semantic snapshot of the approved active tab.", risk: "read" }], createdat: now, expiresat: now + sessionduration, state: "pending" };
|
|
249
427
|
}
|
|
250
428
|
async function propose(objective, remote) {
|
|
251
429
|
if (!objective.trim()) throw new Error("An objective is required.");
|
|
@@ -254,11 +432,12 @@ async function propose(objective, remote) {
|
|
|
254
432
|
const { tab, origin } = await activecontext();
|
|
255
433
|
if (session.tabid !== tab.id || session.origin !== origin) throw new Error("The selected tab or origin no longer matches the approved session.");
|
|
256
434
|
const observation = await snapshot(session.tabid);
|
|
435
|
+
const capabilities = await refreshcapabilities();
|
|
257
436
|
const config = await memory.getconfig();
|
|
258
437
|
let plan = localplan(objective.trim(), session);
|
|
259
438
|
if (remote) {
|
|
260
439
|
if (!config) throw new Error("Configure an approved HTTPS endpoint before requesting a remote proposal.");
|
|
261
|
-
const response = await fetch(config.endpoint, { method: "POST", headers: { "content-type": "application/json" }, credentials: "omit", body: requestbody({ objective: objective.trim(), session, observation }) });
|
|
440
|
+
const response = await fetch(config.endpoint, { method: "POST", headers: { "content-type": "application/json" }, credentials: "omit", body: requestbody({ objective: objective.trim(), session, observation, capabilities }) });
|
|
262
441
|
if (!response.ok) throw new Error(`Proposal endpoint returned ${response.status}.`);
|
|
263
442
|
plan = parseproposal(await response.json(), session.origin).plan;
|
|
264
443
|
}
|
|
@@ -267,6 +446,11 @@ async function propose(objective, remote) {
|
|
|
267
446
|
await audit("proposal", `Plan proposed with ${plan.steps.length} reviewed step${plan.steps.length === 1 ? "" : "s"}.`, { sessionid: session.id, planid: plan.id });
|
|
268
447
|
return plan;
|
|
269
448
|
}
|
|
449
|
+
function browserauditkind(step) {
|
|
450
|
+
if (step.kind === "downloadfile") return "download";
|
|
451
|
+
if (step.kind.startsWith("window")) return "window";
|
|
452
|
+
return "tab";
|
|
453
|
+
}
|
|
270
454
|
async function executestep(stepid) {
|
|
271
455
|
const session = await memory.getsession();
|
|
272
456
|
const plan = await memory.getplan();
|
|
@@ -275,23 +459,39 @@ async function executestep(stepid) {
|
|
|
275
459
|
if (!step) throw new Error("Reviewed step was not found.");
|
|
276
460
|
const gate = canexecute({ session, plan, step, tabid: tab.id, origin });
|
|
277
461
|
if (!gate.allowed) throw new Error(gate.reason);
|
|
278
|
-
|
|
279
|
-
if (
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
462
|
+
let output;
|
|
463
|
+
if (isbrowserkind(step.kind)) {
|
|
464
|
+
const capability = requiredcapability(step.kind);
|
|
465
|
+
if (capability) {
|
|
466
|
+
const granted = await chrome.permissions.contains({ permissions: [capability] });
|
|
467
|
+
if (!granted) throw new Error(`The ${capability} capability has not been granted; request it from the review panel.`);
|
|
468
|
+
}
|
|
469
|
+
output = await runbrowseraction(step, tab.id, tab.windowId ?? chrome.windows.WINDOW_ID_CURRENT);
|
|
470
|
+
} else {
|
|
471
|
+
if (step.target && freshcheckkinds.has(step.kind)) {
|
|
472
|
+
const fresh = await snapshot(tab.id);
|
|
473
|
+
if (!fresh.interactive.some((item) => item.selector === step.target)) throw new Error("The page changed and the target must be reviewed again.");
|
|
474
|
+
}
|
|
475
|
+
const result = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: (action, expectedorigin) => {
|
|
476
|
+
const bridge = globalThis.devthinkbridge;
|
|
477
|
+
if (!bridge) throw new Error("Devthink page bridge is unavailable.");
|
|
478
|
+
return bridge.performstep(action, expectedorigin);
|
|
479
|
+
}, args: [step, origin] });
|
|
480
|
+
output = result[0]?.result;
|
|
481
|
+
}
|
|
286
482
|
const summary = output?.summary ?? "The page action returned no result.";
|
|
287
|
-
|
|
483
|
+
const outcome = { stepid, ok: Boolean(output?.ok), summary, ...output?.details ? { details: output.details } : {}, at: Date.now() };
|
|
484
|
+
const auditkind = isbrowserkind(step.kind) ? browserauditkind(step) : output?.ok ? "action" : "error";
|
|
485
|
+
await audit(auditkind, summary, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid });
|
|
486
|
+
await memory.addoutcome(outcome);
|
|
288
487
|
if (output?.ok && plan) {
|
|
289
|
-
const
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
await
|
|
488
|
+
const completed = recordstep(await memory.getprogress(), plan.id, stepid, Date.now());
|
|
489
|
+
const tracked = recordoutcome(completed, plan.id, outcome, Date.now());
|
|
490
|
+
await memory.setprogress(tracked);
|
|
491
|
+
if (iscomplete(tracked, plan) && plan.state === "approved") {
|
|
492
|
+
const done = { ...plan, state: "completed", completedat: Date.now() };
|
|
493
|
+
await memory.setplan(done);
|
|
494
|
+
await audit("complete", "Every reviewed step of the approved plan has executed.", { ...session ? { sessionid: session.id } : {}, planid: done.id });
|
|
295
495
|
}
|
|
296
496
|
}
|
|
297
497
|
return output ?? { ok: false, summary };
|
|
@@ -304,8 +504,7 @@ async function previewstep(stepid) {
|
|
|
304
504
|
if (!step) throw new Error("Reviewed step was not found.");
|
|
305
505
|
const gate = canpreview({ session, plan, step, tabid: tab.id, origin });
|
|
306
506
|
if (!gate.allowed) throw new Error(gate.reason);
|
|
307
|
-
|
|
308
|
-
if (!step.target || !fresh.interactive.some((item) => item.selector === step.target)) throw new Error("The page changed and the target must be reviewed again.");
|
|
507
|
+
if (!step.target) throw new Error("Only a target-based step can be previewed.");
|
|
309
508
|
const result = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: (target, expectedorigin) => {
|
|
310
509
|
const bridge = globalThis.devthinkbridge;
|
|
311
510
|
if (!bridge) throw new Error("Devthink page bridge is unavailable.");
|
|
@@ -331,11 +530,18 @@ async function resumesession() {
|
|
|
331
530
|
if (!session || session.stoppedat) throw new Error("No active browser session exists.");
|
|
332
531
|
if (session.expiresat <= Date.now()) throw new Error("The browser session has expired and cannot be resumed.");
|
|
333
532
|
if (!session.pausedat) throw new Error("The browser session is not paused.");
|
|
334
|
-
const resumed = { id: session.id, tabid: session.tabid, origin: session.origin, startedat: session.startedat, expiresat: session.expiresat };
|
|
533
|
+
const resumed = { id: session.id, tabid: session.tabid, origin: session.origin, startedat: session.startedat, expiresat: session.expiresat, ...session.grants ? { grants: session.grants } : {} };
|
|
335
534
|
await memory.setsession(resumed);
|
|
336
535
|
await audit("resume", "The user resumed the browser session; reviewed actions can run again.", { sessionid: session.id });
|
|
337
536
|
return resumed;
|
|
338
537
|
}
|
|
538
|
+
async function grantcapability(permission) {
|
|
539
|
+
if (!["tabs", "downloads", "clipboardRead", "clipboardWrite"].includes(permission)) throw new Error("Unknown capability.");
|
|
540
|
+
const granted = await chrome.permissions.request({ permissions: [permission] });
|
|
541
|
+
if (!granted) throw new Error("The capability grant was declined.");
|
|
542
|
+
await audit("capability", `Capability ${permission} granted by the user.`);
|
|
543
|
+
return refreshcapabilities();
|
|
544
|
+
}
|
|
339
545
|
async function handlerequest(message, sender) {
|
|
340
546
|
if (!extensionpage(sender)) throw new Error("Requests are accepted only from Devthink extension pages.");
|
|
341
547
|
const input = message;
|
|
@@ -353,8 +559,12 @@ async function handlerequest(message, sender) {
|
|
|
353
559
|
case "context": {
|
|
354
560
|
const plan = await memory.getplan();
|
|
355
561
|
const progress = await memory.getprogress();
|
|
356
|
-
return { config: await memory.getconfig(), session: await memory.getsession(), plan, progress: plan && progress?.planid === plan.id ? progress : void 0, diagnostic: await memory.getdiagnostic(), audit: await memory.getaudit() };
|
|
562
|
+
return { config: await memory.getconfig(), session: await memory.getsession(), 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() };
|
|
357
563
|
}
|
|
564
|
+
case "capabilities":
|
|
565
|
+
return refreshcapabilities();
|
|
566
|
+
case "grantcapability":
|
|
567
|
+
return grantcapability(input.permission ?? "");
|
|
358
568
|
case "diagnostic":
|
|
359
569
|
return diagnostic();
|
|
360
570
|
case "proposelocal":
|