@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.
@@ -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;AAC9I,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,aAAa,GAAG,WAAW,CAAC;AAC9D,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,SAAS,GAAG,WAAW,GAAG,WAAW,CAAC;AAC9G,MAAM,MAAM,SAAS,GAAG,WAAW,GAAG,SAAS,GAAG,SAAS,GAAG,UAAU,GAAG,UAAU,GAAG,QAAQ,GAAG,OAAO,GAAG,MAAM,GAAG,OAAO,GAAG,QAAQ,GAAG,UAAU,CAAC;AAEtJ,MAAM,WAAW,QAAQ;IACvB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,UAAU,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,SAAS;IACxB,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,KAAK,EAAE,QAAQ,EAAE,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,SAAS,CAAC;IACjB,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,EAAE,EAAE,MAAM,CAAC;IACX,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,MAAM,WAAW,cAAc;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,WAAW;IAC1B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,WAAW,EAAE,MAAM,CAAC;IACpB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,KAAK,CAAC;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;KAAE,CAAC,CAAC;IAChF,WAAW,EAAE,KAAK,CAAC;QAAE,QAAQ,EAAE,MAAM,CAAC;QAAC,IAAI,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;IACtE,UAAU,EAAE,MAAM,CAAC;CACpB;AAED,MAAM,WAAW,UAAU;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,SAAS,CAAC;IAChB,EAAE,EAAE,MAAM,CAAC;IACX,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,EAAE,EAAE,MAAM,CAAC;IACX,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,gBAAgB,EAAE,MAAM,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,MAAM,WAAW,eAAe;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,YAAY,CAAC;IACtB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,OAAO,eAAe,CAAC;IAChC,IAAI,EAAE,SAAS,CAAC;CACjB;AAED,MAAM,WAAW,gBAAgB;IAC/B,OAAO,EAAE,OAAO,CAAC;IACjB,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAED,6EAA6E;AAC7E,MAAM,WAAW,YAAY;IAC3B,MAAM,EAAE,MAAM,CAAC;IACf,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,SAAS,EAAE,MAAM,CAAC;CACnB"}
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../types.ts"],"names":[],"mappings":"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"}
package/dist/version.d.ts CHANGED
@@ -1,3 +1,3 @@
1
1
  /** Canonical package version synchronized from package.json. */
2
- export declare const packageversion: "1.1.31";
2
+ export declare const packageversion: "1.1.32";
3
3
  //# sourceMappingURL=version.d.ts.map
@@ -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
- await this.adapter.set("audit", [event, ...records].slice(0, 100));
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 allowedactions = /* @__PURE__ */ new Set(["observe", "inspect", "focus", "click", "type", "navigate", "scroll", "select", "hover", "extract", "wait"]);
53
- var targetactions = /* @__PURE__ */ new Set(["inspect", "focus", "click", "type", "scroll", "select", "hover"]);
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 Math.min(requested, 1e4);
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 (!["focus", "inspect", "click", "type", "scroll", "select", "hover"].includes(input.step.kind)) return { allowed: false, reason: "Only a target-based action can be previewed." };
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.31";
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 || stepsinput.length > 50) throw new Error("A plan needs between one and fifty steps.");
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: Math.min(typeof planinput.expiresat === "number" ? planinput.expiresat : createdat + 10 * 60 * 1e3, createdat + 30 * 60 * 1e3),
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(tabid) {
219
- await chrome.scripting.executeScript({ target: { tabId: tabid }, files: ["pagebridge.js"] });
220
- const result = await chrome.scripting.executeScript({ target: { tabId: tabid }, func: () => {
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 bounded semantic snapshot of the approved active tab.", risk: "read" }], createdat: now, expiresat: now + sessionduration, state: "pending" };
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
- const fresh = await snapshot(tab.id);
279
- if (step.target && !fresh.interactive.some((item) => item.selector === step.target)) throw new Error("The page changed and the target must be reviewed again.");
280
- const result = await chrome.scripting.executeScript({ target: { tabId: tab.id }, func: (action, expectedorigin) => {
281
- const bridge = globalThis.devthinkbridge;
282
- if (!bridge) throw new Error("Devthink page bridge is unavailable.");
283
- return bridge.performstep(action, expectedorigin);
284
- }, args: [step, origin] });
285
- const output = result[0]?.result;
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
- await audit(output?.ok ? "action" : "error", summary, { ...session ? { sessionid: session.id } : {}, ...plan ? { planid: plan.id } : {}, stepid });
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 progress = recordstep(await memory.getprogress(), plan.id, stepid, Date.now());
290
- await memory.setprogress(progress);
291
- if (iscomplete(progress, plan) && plan.state === "approved") {
292
- const completed = { ...plan, state: "completed", completedat: Date.now() };
293
- await memory.setplan(completed);
294
- await audit("complete", "Every reviewed step of the approved plan has executed.", { ...session ? { sessionid: session.id } : {}, planid: completed.id });
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
- const fresh = await snapshot(tab.id);
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":