@wenathlan/extension 1.1.32 → 1.1.34

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,5 +1,5 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
3
  <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Devthink</title><link rel="stylesheet" href="style.css"></head>
4
- <body><main><header><p class="eyebrow">DEVTHINK 1.1.32</p><h1>Browser bridge</h1><p>Consent is required before page access or action.</p></header><label for="endpoint">Optional agent endpoint</label><input id="endpoint" type="url" inputmode="url" placeholder="https://agent.example/proposal" autocomplete="off"><button id="connect">Approve endpoint origin</button><div class="actions"><button id="session">Start active tab session</button><button id="pause" class="secondary">Pause session</button><button id="openpanel" class="secondary">Review plans</button><button id="stop" class="danger">Stop</button></div><p id="capabilities" role="status" class="muted">Optional capabilities: loading.</p><p id="status" role="status">Loading session state.</p></main><script type="module" src="popup.js"></script></body>
4
+ <body><main><header><p class="eyebrow">DEVTHINK 1.1.34</p><h1>Browser bridge</h1><p>Consent is required before page access or action.</p></header><label for="endpoint">Optional agent endpoint</label><input id="endpoint" type="url" inputmode="url" placeholder="https://agent.example/proposal" autocomplete="off"><button id="connect">Approve endpoint origin</button><div class="actions"><button id="session">Start active tab session</button><button id="pause" class="secondary">Pause session</button><button id="openpanel" class="secondary">Review plans</button><button id="stop" class="danger">Stop</button></div><p id="capabilities" role="status" class="muted">Optional capabilities: loading.</p><p id="livelogic" role="status" class="muted">Held keys: none · Open dialogs answered: none.</p><p id="status" role="status">Loading session state.</p></main><script type="module" src="popup.js"></script></body>
5
5
  </html>
@@ -1,7 +1,7 @@
1
1
  // policy.ts
2
- 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"]);
3
- var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset"]);
4
- 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"]);
2
+ 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"]);
3
+ var interactionactions = /* @__PURE__ */ new Set(["focus", "scroll", "hover", "clickdeep", "rightclick", "doubleclick", "scrollpage", "scrollby", "scrollend", "scrolltop", "fullscreen", "zoomset", "movepointer", "clicktext", "clickaria", "clickname", "expanddetails", "pierceshadow", "retryaction"]);
4
+ 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"]);
5
5
  var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
6
6
  function normalizeendpoint(value) {
7
7
  const endpoint = new URL(value.trim());
@@ -19,6 +19,7 @@ function hostpattern(origin) {
19
19
  var endpointinput = document.querySelector("#endpoint");
20
20
  var statusnode = document.querySelector("#status");
21
21
  var capabilitiesnode = document.querySelector("#capabilities");
22
+ var livenode = document.querySelector("#livelogic");
22
23
  var connectbutton = document.querySelector("#connect");
23
24
  var sessionbutton = document.querySelector("#session");
24
25
  var pausebutton = document.querySelector("#pause");
@@ -47,10 +48,19 @@ function rendercapabilities(report) {
47
48
  const granted = [`tabs ${report.tabs ? "granted" : "absent"}`, `downloads ${report.downloads ? "granted" : "absent"}`, `clipboard read ${report.clipboardread ? "granted" : "absent"}`, `clipboard write ${report.clipboardwrite ? "granted" : "absent"}`];
48
49
  capabilitiesnode.textContent = `Optional capabilities: ${granted.join(" \xB7 ")}.`;
49
50
  }
51
+ function renderlivestate(holds, dialogs, signals) {
52
+ if (!livenode) return;
53
+ const held = (holds ?? []).filter((hold) => hold.releasedat === void 0);
54
+ const heldsummary = held.length > 0 ? held.map((hold) => `${hold.key}${hold.holdid ? ` (${hold.holdid})` : ""}`).join(", ") : "none";
55
+ const dialogsummary = (dialogs ?? []).length > 0 ? `${dialogs?.length} answered` : "none";
56
+ const pages = signals && (signals.language !== void 0 || signals.template !== void 0 || signals.scrolllocked !== void 0) ? [signals.language !== void 0 ? `language ${signals.language || "unknown"}` : "", signals.template !== void 0 ? `template ${signals.template}` : "", signals.scrolllocked !== void 0 ? `scroll lock ${signals.scrolllocked ? "on" : "off"}` : ""].filter(Boolean).join(" \xB7 ") : "unknown";
57
+ livenode.textContent = `Held keys: ${heldsummary} \xB7 Open dialogs answered: ${dialogsummary} \xB7 Page signals: ${pages}.`;
58
+ }
50
59
  async function restore() {
51
60
  const context = await request({ kind: "context" });
52
61
  if (endpointinput && context.config) endpointinput.value = context.config.endpoint;
53
62
  rendercapabilities(context.capabilities);
63
+ renderlivestate(context.holds?.heldkeys, context.dialogs, context.signals);
54
64
  const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
55
65
  if (pausebutton) {
56
66
  pausebutton.disabled = !active;
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../policy.ts", "../popup.ts"],
4
- "sourcesContent": ["import type { actionkind, agentplan, agentsession, endpointconfig, policyevaluation, toolstep } from \"./types.js\";\n\nconst sensitiveactions = new Set<actionkind>([\"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\"]);\nconst interactionactions = new Set<actionkind>([\"focus\", \"scroll\", \"hover\", \"clickdeep\", \"rightclick\", \"doubleclick\", \"scrollpage\", \"scrollby\", \"scrollend\", \"scrolltop\", \"fullscreen\", \"zoomset\"]);\nconst readactions = new Set<actionkind>([\"observe\", \"inspect\", \"extract\", \"wait\", \"waitfor\", \"waittext\", \"readattribute\", \"readstyle\", \"readgeometry\", \"readvalue\", \"readtext\", \"readhtml\", \"countelements\", \"readtable\", \"readlinks\", \"readimages\", \"readmeta\", \"readforms\", \"readstorage\", \"highlight\", \"tablist\", \"windowlist\", \"tabsnapshot\"]);\nconst allowedactions = new Set<actionkind>([...sensitiveactions, ...interactionactions, ...readactions]);\nconst targetactions = new Set<actionkind>([\"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\"]);\nconst valueactions = new Set<actionkind>([\"presskey\", \"drag\", \"drop\", \"upload\", \"readattribute\", \"removeattribute\", \"waittext\", \"evaluate\", \"zoomset\", \"tabactivate\", \"tabclose\", \"tabreload\", \"windowclose\", \"windowresize\", \"tabcreate\", \"windowcreate\", \"downloadfile\"]);\n\n/** Normalizes a user supplied HTTPS endpoint without preserving a provider lock-in. */\nexport function normalizeendpoint(value: string): endpointconfig {\n const endpoint = new URL(value.trim());\n if (endpoint.protocol !== \"https:\") throw new Error(\"Devthink accepts HTTPS endpoints only.\");\n if (endpoint.username || endpoint.password) throw new Error(\"Endpoint credentials are not allowed in the URL.\");\n return { endpoint: endpoint.toString(), origin: endpoint.origin, configuredat: Date.now() };\n}\n\n/** Creates the exact optional host pattern requested from Chromium. */\nexport function hostpattern(origin: string): string {\n const parsed = new URL(origin);\n if (parsed.protocol !== \"https:\") throw new Error(\"Only HTTPS origins can be granted.\");\n return `${parsed.origin}/*`;\n}\n\n/** Defines action risk from the fixed local allowlist. */\nexport function actionrisk(kind: actionkind): \"read\" | \"interaction\" | \"sensitive\" {\n if (!allowedactions.has(kind)) throw new Error(\"Unsupported browser action.\");\n if (sensitiveactions.has(kind)) return \"sensitive\";\n return interactionactions.has(kind) ? \"interaction\" : \"read\";\n}\n\n/** Parses the reviewed JSON options of a step; malformed payloads are rejected early. */\nexport function parseoptions(step: toolstep): Record<string, unknown> {\n if (step.options === undefined) return {};\n let parsed: unknown;\n try { parsed = JSON.parse(step.options); } catch { throw new Error(\"Step options must be a JSON object.\"); }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) throw new Error(\"Step options must be a JSON object.\");\n return parsed as Record<string, unknown>;\n}\n\n/** Maps an action kind to the optional browser permission it requires, if any. */\nexport function requiredcapability(kind: actionkind): string | undefined {\n if (kind === \"tablist\") return \"tabs\";\n if (kind === \"downloadfile\") return \"downloads\";\n return undefined;\n}\n\n/** Parses the reviewed wait duration of a wait step with no upper bound. */\nexport function waitduration(step: toolstep): number {\n const requested = step.value ? Number.parseInt(step.value, 10) : 250;\n if (!Number.isFinite(requested) || requested < 0) throw new Error(\"Wait duration must be zero or a positive number of milliseconds.\");\n return requested;\n}\n\nfunction isnumericid(value: unknown): value is string {\n return typeof value === \"string\" && /^\\d+$/.test(value);\n}\n\nfunction numericoption(options: Record<string, unknown>, key: string): boolean {\n return options[key] === undefined || (typeof options[key] === \"number\" && Number.isFinite(options[key] as number));\n}\n\n/** Validates a single proposal against the active tab origin and local policy. */\nexport function validatestep(step: toolstep, origin: string): policyevaluation {\n if (!allowedactions.has(step.kind)) return { allowed: false, reason: \"Unsupported action kind.\" };\n if (!step.summary.trim()) return { allowed: false, reason: \"A human-readable action summary is required.\" };\n if (targetactions.has(step.kind) && !step.target?.trim()) return { allowed: false, reason: \"A page target is required.\" };\n if (valueactions.has(step.kind) && !step.value?.trim()) return { allowed: false, reason: \"A reviewed value is required.\" };\n if (step.kind === \"select\" && !step.value?.trim()) return { allowed: false, reason: \"A reviewed option value is required.\" };\n if (step.kind === \"navigate\" && !step.value) return { allowed: false, reason: \"A navigation URL is required.\" };\n let options: Record<string, unknown>;\n try { options = parseoptions(step); } catch { return { allowed: false, reason: \"Step options must be a JSON object.\" }; }\n if (step.kind === \"wait\") {\n try { waitduration(step); } catch { return { allowed: false, reason: \"Wait duration must be zero or a positive number of milliseconds.\" }; }\n }\n if (step.kind === \"navigate\") {\n try {\n if (new URL(step.value ?? \"\").origin !== origin) return { allowed: false, reason: \"Navigation must remain within the approved origin.\" };\n } catch {\n return { allowed: false, reason: \"Navigation URL is invalid.\" };\n }\n }\n if (step.kind === \"tabcreate\" || step.kind === \"windowcreate\" || step.kind === \"downloadfile\") {\n try {\n const url = new URL(step.value ?? \"\");\n if (url.protocol !== \"https:\") return { allowed: false, reason: \"The reviewed URL must use HTTPS.\" };\n } catch {\n return { allowed: false, reason: \"The reviewed URL is invalid.\" };\n }\n }\n if (step.kind === \"tabactivate\" || step.kind === \"tabclose\" || step.kind === \"tabreload\" || step.kind === \"windowclose\" || step.kind === \"windowresize\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser id is required.\" };\n }\n if (step.kind === \"zoomset\") {\n const zoom = Number(step.value);\n if (!Number.isFinite(zoom) || zoom <= 0) return { allowed: false, reason: \"The reviewed zoom must be a positive number.\" };\n }\n if (step.kind === \"setattribute\" || step.kind === \"writestorage\") {\n const keyname = step.kind === \"setattribute\" ? \"name\" : \"key\";\n if (typeof options[keyname] !== \"string\" || !(options[keyname] as string).trim()) return { allowed: false, reason: `A reviewed ${keyname} is required in options.` };\n if (typeof options.value !== \"string\") return { allowed: false, reason: \"A reviewed value is required in options.\" };\n }\n if (step.kind === \"windowresize\") {\n 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.\" };\n }\n if ((step.kind === \"scrollpage\" || step.kind === \"scrollby\") && (!numericoption(options, \"x\") || !numericoption(options, \"y\"))) return { allowed: false, reason: \"Scroll amounts must be numbers in options.\" };\n if (step.kind === \"waitfor\" && options.timeout !== undefined && (typeof options.timeout !== \"number\" || options.timeout < 0)) return { allowed: false, reason: \"The waitfor timeout must be zero or a positive number of milliseconds.\" };\n return { allowed: true };\n}\n\n/** Shared session gate: a live, unpaused session that still matches the active tab. */\nfunction sessiongate(input: { session: agentsession | undefined; tabid: number; origin: string; now: number; action: string }): policyevaluation {\n if (!input.session || input.session.stoppedat) return { allowed: false, reason: \"No active browser session exists.\" };\n if (input.session.expiresat <= input.now) return { allowed: false, reason: \"The browser session has expired.\" };\n if (input.session.pausedat) return { allowed: false, reason: `The browser session is paused and cannot ${input.action}.` };\n if (input.session.tabid !== input.tabid || input.session.origin !== input.origin) return { allowed: false, reason: `The ${input.action} is outside the approved tab or origin.` };\n return { allowed: true };\n}\n\n/** Applies the consent gate immediately before an action reaches the page bridge. */\nexport function canexecute(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now?: number }): policyevaluation {\n const now = input.now ?? Date.now();\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"execute an action\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"The plan has not received explicit approval.\" };\n if (input.plan.expiresat <= now) return { allowed: false, reason: \"The approved plan has expired.\" };\n return validatestep(input.step, input.origin);\n}\n\n/** Allows a non-mutating, temporary target preview during plan review. */\nexport function canpreview(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now?: number }): policyevaluation {\n const now = input.now ?? Date.now();\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"preview a target\" });\n if (!gate.allowed) return gate;\n if (!input.plan || ![\"pending\", \"approved\"].includes(input.plan.state)) return { allowed: false, reason: \"Only a reviewed pending or approved plan can be previewed.\" };\n if (input.plan.expiresat <= now) return { allowed: false, reason: \"The reviewed plan has expired.\" };\n if (!targetactions.has(input.step.kind)) return { allowed: false, reason: \"Only a target-based action can be previewed.\" };\n return validatestep(input.step, input.origin);\n}\n", "import { hostpattern, normalizeendpoint } from \"../policy.js\";\nimport type { capabilityreport } from \"../types.js\";\n\nconst endpointinput = document.querySelector<HTMLInputElement>(\"#endpoint\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst capabilitiesnode = document.querySelector<HTMLElement>(\"#capabilities\");\nconst connectbutton = document.querySelector<HTMLButtonElement>(\"#connect\");\nconst sessionbutton = document.querySelector<HTMLButtonElement>(\"#session\");\nconst pausebutton = document.querySelector<HTMLButtonElement>(\"#pause\");\nconst stopbutton = document.querySelector<HTMLButtonElement>(\"#stop\");\nconst openbutton = document.querySelector<HTMLButtonElement>(\"#openpanel\");\n\nfunction status(message: string, error = false): void { if (statusnode) { statusnode.textContent = message; statusnode.dataset.state = error ? \"error\" : \"ready\"; } }\nasync function request(message: unknown): Promise<unknown> { const response = await chrome.runtime.sendMessage(message) as { ok: boolean; value?: unknown; error?: string }; if (!response.ok) throw new Error(response.error); return response.value; }\n\nfunction pauselabel(paused: boolean): string { return paused ? \"Resume session\" : \"Pause session\"; }\n\n/** Renders the live optional capability set the user has granted. */\nfunction rendercapabilities(report?: capabilityreport): void {\n if (!capabilitiesnode) return;\n if (!report) { capabilitiesnode.textContent = \"Capabilities unknown.\"; return; }\n const granted = [`tabs ${report.tabs ? \"granted\" : \"absent\"}`, `downloads ${report.downloads ? \"granted\" : \"absent\"}`, `clipboard read ${report.clipboardread ? \"granted\" : \"absent\"}`, `clipboard write ${report.clipboardwrite ? \"granted\" : \"absent\"}`];\n capabilitiesnode.textContent = `Optional capabilities: ${granted.join(\" \u00B7 \")}.`;\n}\n\nasync function restore(): Promise<void> {\n const context = await request({ kind: \"context\" }) as { config?: { endpoint: string }; session?: { stoppedat?: number; pausedat?: number; expiresat: number }; capabilities?: capabilityreport };\n if (endpointinput && context.config) endpointinput.value = context.config.endpoint;\n rendercapabilities(context.capabilities);\n const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();\n if (pausebutton) { pausebutton.disabled = !active; pausebutton.textContent = pauselabel(Boolean(context.session?.pausedat)); }\n if (active && context.session?.pausedat) status(\"Session paused. Reviewed actions are blocked until resume.\");\n else status(active ? \"Session active. Review the plan in the side panel.\" : \"No active browser session.\");\n}\n\nconnectbutton?.addEventListener(\"click\", async () => {\n try {\n const config = normalizeendpoint(endpointinput?.value ?? \"\");\n const granted = await chrome.permissions.request({ origins: [hostpattern(config.origin)] });\n if (!granted) throw new Error(\"Origin permission was not granted.\");\n await request({ kind: \"configure\", endpoint: config.endpoint });\n status(`Endpoint approved for ${config.origin}.`);\n } catch (error) { status(error instanceof Error ? error.message : String(error), true); }\n});\nsessionbutton?.addEventListener(\"click\", async () => { try { await request({ kind: \"startsession\" }); status(\"Session started for the active HTTPS tab.\"); await restore(); } catch (error) { status(error instanceof Error ? error.message : String(error), true); } });\npausebutton?.addEventListener(\"click\", async () => { try { const context = await request({ kind: \"context\" }) as { session?: { pausedat?: number } }; await request({ kind: context.session?.pausedat ? \"resumesession\" : \"pausesession\" }); await restore(); } catch (error) { status(error instanceof Error ? error.message : String(error), true); } });\nstopbutton?.addEventListener(\"click\", async () => { try { await request({ kind: \"stop\" }); status(\"Session stopped. No action can continue.\"); await restore(); } catch (error) { status(error instanceof Error ? error.message : String(error), true); } });\nopenbutton?.addEventListener(\"click\", () => chrome.sidePanel.open({ windowId: chrome.windows.WINDOW_ID_CURRENT }));\nrestore().catch(error => status(error instanceof Error ? error.message : String(error), true));\n"],
5
- "mappings": ";AAEA,IAAM,mBAAmB,oBAAI,IAAgB,CAAC,SAAS,QAAQ,YAAY,UAAU,YAAY,QAAQ,QAAQ,UAAU,SAAS,SAAS,WAAW,UAAU,UAAU,UAAU,QAAQ,WAAW,gBAAgB,gBAAgB,mBAAmB,YAAY,aAAa,eAAe,YAAY,aAAa,gBAAgB,eAAe,gBAAgB,cAAc,CAAC;AAC3X,IAAM,qBAAqB,oBAAI,IAAgB,CAAC,SAAS,UAAU,SAAS,aAAa,cAAc,eAAe,cAAc,YAAY,aAAa,aAAa,cAAc,SAAS,CAAC;AAClM,IAAM,cAAc,oBAAI,IAAgB,CAAC,WAAW,WAAW,WAAW,QAAQ,WAAW,YAAY,iBAAiB,aAAa,gBAAgB,aAAa,YAAY,YAAY,iBAAiB,aAAa,aAAa,cAAc,YAAY,aAAa,eAAe,aAAa,WAAW,cAAc,aAAa,CAAC;AACjV,IAAM,iBAAiB,oBAAI,IAAgB,CAAC,GAAG,kBAAkB,GAAG,oBAAoB,GAAG,WAAW,CAAC;AAKhG,SAAS,kBAAkB,OAA+B;AAC/D,QAAM,WAAW,IAAI,IAAI,MAAM,KAAK,CAAC;AACrC,MAAI,SAAS,aAAa,SAAU,OAAM,IAAI,MAAM,wCAAwC;AAC5F,MAAI,SAAS,YAAY,SAAS,SAAU,OAAM,IAAI,MAAM,kDAAkD;AAC9G,SAAO,EAAE,UAAU,SAAS,SAAS,GAAG,QAAQ,SAAS,QAAQ,cAAc,KAAK,IAAI,EAAE;AAC5F;AAGO,SAAS,YAAY,QAAwB;AAClD,QAAM,SAAS,IAAI,IAAI,MAAM;AAC7B,MAAI,OAAO,aAAa,SAAU,OAAM,IAAI,MAAM,oCAAoC;AACtF,SAAO,GAAG,OAAO,MAAM;AACzB;;;ACnBA,IAAM,gBAAgB,SAAS,cAAgC,WAAW;AAC1E,IAAM,aAAa,SAAS,cAA2B,SAAS;AAChE,IAAM,mBAAmB,SAAS,cAA2B,eAAe;AAC5E,IAAM,gBAAgB,SAAS,cAAiC,UAAU;AAC1E,IAAM,gBAAgB,SAAS,cAAiC,UAAU;AAC1E,IAAM,cAAc,SAAS,cAAiC,QAAQ;AACtE,IAAM,aAAa,SAAS,cAAiC,OAAO;AACpE,IAAM,aAAa,SAAS,cAAiC,YAAY;AAEzE,SAAS,OAAO,SAAiB,QAAQ,OAAa;AAAE,MAAI,YAAY;AAAE,eAAW,cAAc;AAAS,eAAW,QAAQ,QAAQ,QAAQ,UAAU;AAAA,EAAS;AAAE;AACpK,eAAe,QAAQ,SAAoC;AAAE,QAAM,WAAW,MAAM,OAAO,QAAQ,YAAY,OAAO;AAAuD,MAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,SAAS,KAAK;AAAG,SAAO,SAAS;AAAO;AAEvP,SAAS,WAAW,QAAyB;AAAE,SAAO,SAAS,mBAAmB;AAAiB;AAGnG,SAAS,mBAAmB,QAAiC;AAC3D,MAAI,CAAC,iBAAkB;AACvB,MAAI,CAAC,QAAQ;AAAE,qBAAiB,cAAc;AAAyB;AAAA,EAAQ;AAC/E,QAAM,UAAU,CAAC,QAAQ,OAAO,OAAO,YAAY,QAAQ,IAAI,aAAa,OAAO,YAAY,YAAY,QAAQ,IAAI,kBAAkB,OAAO,gBAAgB,YAAY,QAAQ,IAAI,mBAAmB,OAAO,iBAAiB,YAAY,QAAQ,EAAE;AACzP,mBAAiB,cAAc,0BAA0B,QAAQ,KAAK,QAAK,CAAC;AAC9E;AAEA,eAAe,UAAyB;AACtC,QAAM,UAAU,MAAM,QAAQ,EAAE,MAAM,UAAU,CAAC;AACjD,MAAI,iBAAiB,QAAQ,OAAQ,eAAc,QAAQ,QAAQ,OAAO;AAC1E,qBAAmB,QAAQ,YAAY;AACvC,QAAM,SAAS,QAAQ,WAAW,CAAC,QAAQ,QAAQ,aAAa,QAAQ,QAAQ,YAAY,KAAK,IAAI;AACrG,MAAI,aAAa;AAAE,gBAAY,WAAW,CAAC;AAAQ,gBAAY,cAAc,WAAW,QAAQ,QAAQ,SAAS,QAAQ,CAAC;AAAA,EAAG;AAC7H,MAAI,UAAU,QAAQ,SAAS,SAAU,QAAO,4DAA4D;AAAA,MACvG,QAAO,SAAS,uDAAuD,4BAA4B;AAC1G;AAEA,eAAe,iBAAiB,SAAS,YAAY;AACnD,MAAI;AACF,UAAM,SAAS,kBAAkB,eAAe,SAAS,EAAE;AAC3D,UAAM,UAAU,MAAM,OAAO,YAAY,QAAQ,EAAE,SAAS,CAAC,YAAY,OAAO,MAAM,CAAC,EAAE,CAAC;AAC1F,QAAI,CAAC,QAAS,OAAM,IAAI,MAAM,oCAAoC;AAClE,UAAM,QAAQ,EAAE,MAAM,aAAa,UAAU,OAAO,SAAS,CAAC;AAC9D,WAAO,yBAAyB,OAAO,MAAM,GAAG;AAAA,EAClD,SAAS,OAAO;AAAE,WAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI;AAAA,EAAG;AAC1F,CAAC;AACD,eAAe,iBAAiB,SAAS,YAAY;AAAE,MAAI;AAAE,UAAM,QAAQ,EAAE,MAAM,eAAe,CAAC;AAAG,WAAO,2CAA2C;AAAG,UAAM,QAAQ;AAAA,EAAG,SAAS,OAAO;AAAE,WAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI;AAAA,EAAG;AAAE,CAAC;AACvQ,aAAa,iBAAiB,SAAS,YAAY;AAAE,MAAI;AAAE,UAAM,UAAU,MAAM,QAAQ,EAAE,MAAM,UAAU,CAAC;AAA0C,UAAM,QAAQ,EAAE,MAAM,QAAQ,SAAS,WAAW,kBAAkB,eAAe,CAAC;AAAG,UAAM,QAAQ;AAAA,EAAG,SAAS,OAAO;AAAE,WAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI;AAAA,EAAG;AAAE,CAAC;AACzV,YAAY,iBAAiB,SAAS,YAAY;AAAE,MAAI;AAAE,UAAM,QAAQ,EAAE,MAAM,OAAO,CAAC;AAAG,WAAO,0CAA0C;AAAG,UAAM,QAAQ;AAAA,EAAG,SAAS,OAAO;AAAE,WAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI;AAAA,EAAG;AAAE,CAAC;AAC3P,YAAY,iBAAiB,SAAS,MAAM,OAAO,UAAU,KAAK,EAAE,UAAU,OAAO,QAAQ,kBAAkB,CAAC,CAAC;AACjH,QAAQ,EAAE,MAAM,WAAS,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI,CAAC;",
4
+ "sourcesContent": ["import type { actionkind, agentplan, agentsession, endpointconfig, observationmode, policyevaluation, toolstep } from \"./types.js\";\n\nconst sensitiveactions = new Set<actionkind>([\"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\"]);\nconst interactionactions = new Set<actionkind>([\"focus\", \"scroll\", \"hover\", \"clickdeep\", \"rightclick\", \"doubleclick\", \"scrollpage\", \"scrollby\", \"scrollend\", \"scrolltop\", \"fullscreen\", \"zoomset\", \"movepointer\", \"clicktext\", \"clickaria\", \"clickname\", \"expanddetails\", \"pierceshadow\", \"retryaction\"]);\nconst readactions = new Set<actionkind>([\"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\"]);\nconst allowedactions = new Set<actionkind>([...sensitiveactions, ...interactionactions, ...readactions]);\nconst watchactions = new Set<actionkind>([\"watchmutate\", \"watchbanner\", \"watchfocus\"]);\nconst targetactions = new Set<actionkind>([\"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\"]);\nconst valueactions = new Set<actionkind>([\"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\"]);\n\n/** Normalizes a user supplied HTTPS endpoint without preserving a provider lock-in. */\nexport function normalizeendpoint(value: string): endpointconfig {\n const endpoint = new URL(value.trim());\n if (endpoint.protocol !== \"https:\") throw new Error(\"Devthink accepts HTTPS endpoints only.\");\n if (endpoint.username || endpoint.password) throw new Error(\"Endpoint credentials are not allowed in the URL.\");\n return { endpoint: endpoint.toString(), origin: endpoint.origin, configuredat: Date.now() };\n}\n\n/** Creates the exact optional host pattern requested from Chromium. */\nexport function hostpattern(origin: string): string {\n const parsed = new URL(origin);\n if (parsed.protocol !== \"https:\") throw new Error(\"Only HTTPS origins can be granted.\");\n return `${parsed.origin}/*`;\n}\n\n/** True when the action kind observes the page over a reviewed lifetime window. */\nexport function iswatchkind(kind: actionkind): boolean {\n return watchactions.has(kind);\n}\n\n/** Grades the observation mode of a kind: passive capture, watched lifetimes or diffing passes. */\nexport function observationmodeof(kind: actionkind): observationmode {\n if (watchactions.has(kind) || kind === \"waitquiet\") return \"watching\";\n if (kind === \"diffsnapshots\") return \"diffing\";\n return \"passive\";\n}\n\n/** Defines action risk from the fixed local allowlist. */\nexport function actionrisk(kind: actionkind): \"read\" | \"interaction\" | \"sensitive\" {\n if (!allowedactions.has(kind)) throw new Error(\"Unsupported browser action.\");\n if (sensitiveactions.has(kind)) return \"sensitive\";\n return interactionactions.has(kind) ? \"interaction\" : \"read\";\n}\n\n/** True when the action kind accepts a css selector target or a reviewed targetref. */\nexport function needstarget(kind: actionkind): boolean {\n return targetactions.has(kind);\n}\n\n/** Parses the reviewed JSON options of a step; malformed payloads are rejected early. */\nexport function parseoptions(step: toolstep): Record<string, unknown> {\n if (step.options === undefined) return {};\n let parsed: unknown;\n try { parsed = JSON.parse(step.options); } catch { throw new Error(\"Step options must be a JSON object.\"); }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) throw new Error(\"Step options must be a JSON object.\");\n return parsed as Record<string, unknown>;\n}\n\n/** Maps an action kind to the optional browser permission it requires, if any. */\nexport function requiredcapability(kind: actionkind): string | undefined {\n if (kind === \"tablist\") return \"tabs\";\n if (kind === \"downloadfile\") return \"downloads\";\n return undefined;\n}\n\n/** Parses the reviewed wait duration of a wait step with no upper bound. */\nexport function waitduration(step: toolstep): number {\n const requested = step.value ? Number.parseInt(step.value, 10) : 250;\n if (!Number.isFinite(requested) || requested < 0) throw new Error(\"Wait duration must be zero or a positive number of milliseconds.\");\n return requested;\n}\n\nfunction isnumericid(value: unknown): value is string {\n return typeof value === \"string\" && /^\\d+$/.test(value);\n}\n\nfunction numericoption(options: Record<string, unknown>, key: string): boolean {\n return options[key] === undefined || (typeof options[key] === \"number\" && Number.isFinite(options[key] as number));\n}\n\n/** True when an optional numeric option is absent or a finite number of zero or more. */\nfunction nonnegativeoption(options: Record<string, unknown>, key: string): boolean {\n return numericoption(options, key) && !(typeof options[key] === \"number\" && (options[key] as number) < 0);\n}\n\nfunction isnonempty(value: unknown): value is string {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction ispoint(value: unknown): boolean {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const point = value as Record<string, unknown>;\n return typeof point.x === \"number\" && Number.isFinite(point.x) && typeof point.y === \"number\" && Number.isFinite(point.y);\n}\n\n/** Grades a resolution match count: zero is absent, one is resolved and more than one is refused as ambiguous. */\nexport function resolutionverdict(count: number): \"absent\" | \"resolved\" | \"ambiguous\" {\n if (!Number.isFinite(count) || count <= 0) return \"absent\";\n return count === 1 ? \"resolved\" : \"ambiguous\";\n}\n\n/** Validates the reviewed targetref grammar of every resolution mode and rejects empty references. */\nexport function validatetargetref(reference: unknown): policyevaluation {\n if (!reference || typeof reference !== \"object\" || Array.isArray(reference)) return { allowed: false, reason: \"The reviewed target reference must be an object.\" };\n const ref = reference as Record<string, unknown>;\n if (ref.mode === \"selector\") return isnonempty(ref.selector) ? { allowed: true } : { allowed: false, reason: \"The selector target reference needs a non-empty selector.\" };\n if (ref.mode === \"text\") return isnonempty(ref.text) ? { allowed: true } : { allowed: false, reason: \"The text target reference needs non-empty text.\" };\n if (ref.mode === \"aria\") {\n if (!isnonempty(ref.role)) return { allowed: false, reason: \"The aria target reference needs a non-empty role.\" };\n return isnonempty(ref.name) ? { allowed: true } : { allowed: false, reason: \"The aria target reference needs a non-empty name.\" };\n }\n if (ref.mode === \"name\") return isnonempty(ref.name) ? { allowed: true } : { allowed: false, reason: \"The name target reference needs a non-empty name.\" };\n if (ref.mode === \"xpath\") return isnonempty(ref.xpath) ? { allowed: true } : { allowed: false, reason: \"The xpath target reference needs a non-empty expression.\" };\n if (ref.mode === \"index\") {\n const index = ref.index;\n return typeof index === \"number\" && Number.isInteger(index) && index >= 1 ? { allowed: true } : { allowed: false, reason: \"The index target reference needs a positive integer map number.\" };\n }\n if (ref.mode === \"point\") {\n const pointok = typeof ref.x === \"number\" && Number.isFinite(ref.x) && typeof ref.y === \"number\" && Number.isFinite(ref.y);\n return pointok ? { allowed: true } : { allowed: false, reason: \"The point target reference needs numeric x and y coordinates.\" };\n }\n return { allowed: false, reason: \"The target reference mode must be selector, text, aria, name, xpath, index or point.\" };\n}\n\n/** True when the session origin grants cover the given origin; a session without grants only allows its own origin. */\nexport function origingranted(session: agentsession | undefined, origin: string): boolean {\n if (!session) return false;\n const grants = session.grants ?? [session.origin];\n return grants.includes(origin);\n}\n\n/** Validates the reviewed inner step of a retry or frame wrapper against the same rules as a top-level step. */\nfunction validateinnerstep(options: Record<string, unknown>, origin: string): policyevaluation {\n const stepid = options.stepid;\n const kind = options.kind;\n if (isnonempty(stepid)) {\n if (kind !== undefined) return { allowed: false, reason: \"The reviewed wrapper must reference a step id or an inline step, not both.\" };\n return { allowed: true };\n }\n if (typeof kind !== \"string\" || !kind.trim()) return { allowed: false, reason: \"A reviewed step id or inline step kind is required in options.\" };\n if (kind === \"retryaction\" || kind === \"enterframe\") return { allowed: false, reason: \"The reviewed inner step cannot be another wrapper kind.\" };\n if (!allowedactions.has(kind as actionkind)) return { allowed: false, reason: \"The reviewed inner step kind is unsupported.\" };\n const inneroptions = options.options;\n if (inneroptions !== undefined && (!inneroptions || typeof inneroptions !== \"object\" || Array.isArray(inneroptions))) return { allowed: false, reason: \"The reviewed inner step options must be an object.\" };\n const inner: toolstep = {\n id: \"inner\",\n kind: kind as actionkind,\n summary: \"Reviewed inner step.\",\n risk: actionrisk(kind as actionkind),\n ...(isnonempty(options.target) ? { target: options.target } : {}),\n ...(isnonempty(options.value) ? { value: options.value } : {}),\n ...(inneroptions !== undefined ? { options: JSON.stringify(inneroptions) } : {}),\n };\n return validatestep(inner, origin);\n}\n\n/** Validates a single proposal against the active tab origin and local policy. */\nexport function validatestep(step: toolstep, origin: string): policyevaluation {\n if (!allowedactions.has(step.kind)) return { allowed: false, reason: \"Unsupported action kind.\" };\n if (!step.summary.trim()) return { allowed: false, reason: \"A human-readable action summary is required.\" };\n let options: Record<string, unknown>;\n try { options = parseoptions(step); } catch { return { allowed: false, reason: \"Step options must be a JSON object.\" }; }\n const hastargetref = options.targetref !== undefined;\n if (targetactions.has(step.kind) && !step.target?.trim() && !hastargetref) return { allowed: false, reason: \"A page target is required.\" };\n if (valueactions.has(step.kind) && !step.value?.trim()) return { allowed: false, reason: \"A reviewed value is required.\" };\n if (step.kind === \"select\" && !step.value?.trim()) return { allowed: false, reason: \"A reviewed option value is required.\" };\n if (step.kind === \"navigate\" && !step.value) return { allowed: false, reason: \"A navigation URL is required.\" };\n if (hastargetref) {\n const reference = validatetargetref(options.targetref);\n if (!reference.allowed) return reference;\n }\n if (step.kind === \"wait\") {\n try { waitduration(step); } catch { return { allowed: false, reason: \"Wait duration must be zero or a positive number of milliseconds.\" }; }\n }\n if (step.kind === \"navigate\") {\n try {\n if (new URL(step.value ?? \"\").origin !== origin) return { allowed: false, reason: \"Navigation must remain within the approved origin.\" };\n } catch {\n return { allowed: false, reason: \"Navigation URL is invalid.\" };\n }\n }\n if (step.kind === \"tabcreate\" || step.kind === \"windowcreate\" || step.kind === \"downloadfile\") {\n try {\n const url = new URL(step.value ?? \"\");\n if (url.protocol !== \"https:\") return { allowed: false, reason: \"The reviewed URL must use HTTPS.\" };\n } catch {\n return { allowed: false, reason: \"The reviewed URL is invalid.\" };\n }\n }\n if (step.kind === \"tabactivate\" || step.kind === \"tabclose\" || step.kind === \"tabreload\" || step.kind === \"windowclose\" || step.kind === \"windowresize\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser id is required.\" };\n }\n if (step.kind === \"zoomset\") {\n const zoom = Number(step.value);\n if (!Number.isFinite(zoom) || zoom <= 0) return { allowed: false, reason: \"The reviewed zoom must be a positive number.\" };\n }\n if (step.kind === \"setattribute\" || step.kind === \"writestorage\") {\n const keyname = step.kind === \"setattribute\" ? \"name\" : \"key\";\n if (typeof options[keyname] !== \"string\" || !(options[keyname] as string).trim()) return { allowed: false, reason: `A reviewed ${keyname} is required in options.` };\n if (typeof options.value !== \"string\") return { allowed: false, reason: \"A reviewed value is required in options.\" };\n }\n if (step.kind === \"windowresize\") {\n 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.\" };\n }\n if ((step.kind === \"scrollpage\" || step.kind === \"scrollby\") && (!numericoption(options, \"x\") || !numericoption(options, \"y\"))) return { allowed: false, reason: \"Scroll amounts must be numbers in options.\" };\n if (step.kind === \"waitfor\" && options.timeout !== undefined && (typeof options.timeout !== \"number\" || options.timeout < 0)) return { allowed: false, reason: \"The waitfor timeout must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"movepointer\") {\n const path = options.pointpath;\n if (!path || typeof path !== \"object\" || Array.isArray(path)) return { allowed: false, reason: \"A reviewed pointpath with start and end points is required in options.\" };\n const points = path as Record<string, unknown>;\n if (!ispoint(points.start) || !ispoint(points.end)) return { allowed: false, reason: \"The reviewed pointpath needs numeric start and end points.\" };\n if (points.waypoints !== undefined && (!Array.isArray(points.waypoints) || !points.waypoints.every(waypoint => ispoint(waypoint)))) return { allowed: false, reason: \"The reviewed pointpath waypoints must be numeric points.\" };\n if (!nonnegativeoption(points, \"duration\")) return { allowed: false, reason: \"The reviewed pointpath duration must be zero or a positive number of milliseconds.\" };\n const speed = options.speedprofile;\n if (speed !== undefined) {\n if (!speed || typeof speed !== \"object\" || Array.isArray(speed)) return { allowed: false, reason: \"The reviewed speed profile must be an object.\" };\n const profile = speed as Record<string, unknown>;\n if (profile.easing !== undefined && profile.easing !== \"linear\" && profile.easing !== \"easeinout\") return { allowed: false, reason: \"The reviewed easing must be linear or easeinout.\" };\n if (!nonnegativeoption(profile, \"peak\")) return { allowed: false, reason: \"The reviewed peak velocity must be zero or a positive number.\" };\n if (!nonnegativeoption(profile, \"jitter\")) return { allowed: false, reason: \"The reviewed jitter window must be zero or a positive number of milliseconds.\" };\n }\n }\n if (step.kind === \"clickpoint\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"point\")) return { allowed: false, reason: \"A reviewed point target reference is required in options.\" };\n if (step.kind === \"clicktext\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"text\")) return { allowed: false, reason: \"A reviewed text target reference is required in options.\" };\n if (step.kind === \"clickaria\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"aria\")) return { allowed: false, reason: \"A reviewed aria target reference is required in options.\" };\n if (step.kind === \"clickname\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"name\")) return { allowed: false, reason: \"A reviewed name target reference is required in options.\" };\n if (step.kind === \"resolvexpath\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"xpath\")) return { allowed: false, reason: \"A reviewed xpath target reference is required in options.\" };\n if (step.kind === \"typetime\" && options.delay !== undefined && (typeof options.delay !== \"number\" || !Number.isFinite(options.delay) || options.delay < 0)) return { allowed: false, reason: \"The reviewed per keystroke delay must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"submitsearch\") {\n if (!isnonempty(options.results)) return { allowed: false, reason: \"A reviewed results region selector is required in options.\" };\n if (options.timeout !== undefined && (typeof options.timeout !== \"number\" || !Number.isFinite(options.timeout) || options.timeout < 0)) return { allowed: false, reason: \"The submitsearch timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"selectmulti\") {\n const values = options.values;\n if (!Array.isArray(values) || values.length === 0 || !values.every(value => isnonempty(value))) return { allowed: false, reason: \"A reviewed list of option values is required in options.\" };\n }\n if (step.kind === \"setslider\") {\n const slider = Number(step.value);\n if (!Number.isFinite(slider)) return { allowed: false, reason: \"The reviewed slider value must be a number.\" };\n }\n if (step.kind === \"setdate\" && !/^\\d{4}-\\d{2}-\\d{2}$/.test(step.value ?? \"\")) return { allowed: false, reason: \"The reviewed date must use the yyyy-mm-dd form.\" };\n if (step.kind === \"setcolor\" && !/^#[0-9a-fA-F]{6}$/.test(step.value ?? \"\")) return { allowed: false, reason: \"The reviewed color must use the #rrggbb form.\" };\n if (step.kind === \"keyhold\" && options.holdid !== undefined && !isnonempty(options.holdid)) return { allowed: false, reason: \"The reviewed hold id must be a non-empty string.\" };\n if (step.kind === \"dismissdialog\") {\n const accept = options.accept;\n const answer = options.answer;\n if (accept === undefined && !isnonempty(answer)) return { allowed: false, reason: \"A reviewed accept flag or prompt answer is required in options.\" };\n if (accept !== undefined && typeof accept !== \"boolean\") return { allowed: false, reason: \"The reviewed dialog accept flag must be a boolean.\" };\n if (answer !== undefined && !isnonempty(answer)) return { allowed: false, reason: \"The reviewed prompt answer must be a non-empty string.\" };\n }\n if (step.kind === \"pierceshadow\" && options.shadow !== undefined) {\n if (!Array.isArray(options.shadow) || !options.shadow.every(item => isnonempty(item))) return { allowed: false, reason: \"The reviewed shadow path must be a list of non-empty selectors.\" };\n }\n if (step.kind === \"enterframe\") {\n const path = options.framepath;\n if (!Array.isArray(path) || path.length === 0 || !path.every(item => typeof item === \"number\" && Number.isInteger(item) && item >= 0)) return { allowed: false, reason: \"A reviewed frame path of frame indexes is required in options.\" };\n return validateinnerstep(options, origin);\n }\n if (step.kind === \"retryaction\") {\n const inner = validateinnerstep(options, origin);\n if (!inner.allowed) return inner;\n const rule = options.retryrule;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule)) return { allowed: false, reason: \"A reviewed retry rule with attempts is required in options.\" };\n const retry = rule as Record<string, unknown>;\n if (typeof retry.attempts !== \"number\" || !Number.isInteger(retry.attempts) || retry.attempts < 1) return { allowed: false, reason: \"The reviewed retry attempts must be a positive integer with no code ceiling.\" };\n if (!nonnegativeoption(retry, \"settle\")) return { allowed: false, reason: \"The reviewed retry settle window must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(retry, \"tolerance\")) return { allowed: false, reason: \"The reviewed retry movement tolerance must be zero or a positive number of pixels.\" };\n }\n if (watchactions.has(step.kind)) {\n 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.\" };\n if (options.scopes !== undefined && (!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.\" };\n if (options.events !== undefined && (!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.\" };\n if (!nonnegativeoption(options, \"poll\")) return { allowed: false, reason: \"The reviewed watch poll interval must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"waitquiet\") {\n const rule = options.quietrule;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule)) return { allowed: false, reason: \"A reviewed quietrule with an idle threshold is required in options.\" };\n const quiet = rule as Record<string, unknown>;\n 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.\" };\n if (!nonnegativeoption(quiet, \"poll\")) return { allowed: false, reason: \"The reviewed quiet poll interval must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(quiet, \"timeout\")) return { allowed: false, reason: \"The reviewed quiet timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"diffsnapshots\") {\n const versions = options.versions;\n 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.\" };\n }\n return { allowed: true };\n}\n\n/** Shared session gate: a live, unpaused session that still matches the active tab. */\nfunction sessiongate(input: { session: agentsession | undefined; tabid: number; origin: string; now: number; action: string }): policyevaluation {\n if (!input.session || input.session.stoppedat) return { allowed: false, reason: \"No active browser session exists.\" };\n if (input.session.expiresat <= input.now) return { allowed: false, reason: \"The browser session has expired.\" };\n if (input.session.pausedat) return { allowed: false, reason: `The browser session is paused and cannot ${input.action}.` };\n if (input.session.tabid !== input.tabid || input.session.origin !== input.origin) return { allowed: false, reason: `The ${input.action} is outside the approved tab or origin.` };\n return { allowed: true };\n}\n\n/** Applies the consent gate immediately before an action reaches the page bridge. */\nexport function canexecute(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now?: number }): policyevaluation {\n const now = input.now ?? Date.now();\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"execute an action\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"The plan has not received explicit approval.\" };\n if (input.plan.expiresat <= now) return { allowed: false, reason: \"The approved plan has expired.\" };\n 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.\" };\n if (input.step.kind === \"readjson\" && !origingranted(input.session, input.origin)) return { allowed: false, reason: \"The json state read is outside the session origin grants.\" };\n return validatestep(input.step, input.origin);\n}\n\n/** Allows a non-mutating, temporary target preview during plan review. */\nexport function canpreview(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now?: number }): policyevaluation {\n const now = input.now ?? Date.now();\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"preview a target\" });\n if (!gate.allowed) return gate;\n if (!input.plan || ![\"pending\", \"approved\"].includes(input.plan.state)) return { allowed: false, reason: \"Only a reviewed pending or approved plan can be previewed.\" };\n if (input.plan.expiresat <= now) return { allowed: false, reason: \"The reviewed plan has expired.\" };\n let options: Record<string, unknown> = {};\n try { options = parseoptions(input.step); } catch { options = {}; }\n if (!targetactions.has(input.step.kind) && options.targetref === undefined) return { allowed: false, reason: \"Only a target-based action can be previewed.\" };\n return validatestep(input.step, input.origin);\n}\n", "import { hostpattern, normalizeendpoint } from \"../policy.js\";\nimport type { capabilityreport, dialogdecision, keyholdstate } from \"../types.js\";\n\ntype pagesignallike = { language?: string; template?: string; scrolllocked?: boolean; banner?: string };\n\nconst endpointinput = document.querySelector<HTMLInputElement>(\"#endpoint\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst capabilitiesnode = document.querySelector<HTMLElement>(\"#capabilities\");\nconst livenode = document.querySelector<HTMLElement>(\"#livelogic\");\nconst connectbutton = document.querySelector<HTMLButtonElement>(\"#connect\");\nconst sessionbutton = document.querySelector<HTMLButtonElement>(\"#session\");\nconst pausebutton = document.querySelector<HTMLButtonElement>(\"#pause\");\nconst stopbutton = document.querySelector<HTMLButtonElement>(\"#stop\");\nconst openbutton = document.querySelector<HTMLButtonElement>(\"#openpanel\");\n\nfunction status(message: string, error = false): void { if (statusnode) { statusnode.textContent = message; statusnode.dataset.state = error ? \"error\" : \"ready\"; } }\nasync function request(message: unknown): Promise<unknown> { const response = await chrome.runtime.sendMessage(message) as { ok: boolean; value?: unknown; error?: string }; if (!response.ok) throw new Error(response.error); return response.value; }\n\nfunction pauselabel(paused: boolean): string { return paused ? \"Resume session\" : \"Pause session\"; }\n\n/** Renders the live optional capability set the user has granted. */\nfunction rendercapabilities(report?: capabilityreport): void {\n if (!capabilitiesnode) return;\n if (!report) { capabilitiesnode.textContent = \"Capabilities unknown.\"; return; }\n const granted = [`tabs ${report.tabs ? \"granted\" : \"absent\"}`, `downloads ${report.downloads ? \"granted\" : \"absent\"}`, `clipboard read ${report.clipboardread ? \"granted\" : \"absent\"}`, `clipboard write ${report.clipboardwrite ? \"granted\" : \"absent\"}`];\n capabilitiesnode.textContent = `Optional capabilities: ${granted.join(\" \u00B7 \")}.`;\n}\n\n/** Renders the currently held keys and the dialogs answered by the reviewed policy. */\nfunction renderlivestate(holds?: keyholdstate[], dialogs?: dialogdecision[], signals?: pagesignallike): void {\n if (!livenode) return;\n const held = (holds ?? []).filter(hold => hold.releasedat === undefined);\n const heldsummary = held.length > 0 ? held.map(hold => `${hold.key}${hold.holdid ? ` (${hold.holdid})` : \"\"}`).join(\", \") : \"none\";\n const dialogsummary = (dialogs ?? []).length > 0 ? `${dialogs?.length} answered` : \"none\";\n const pages = signals && (signals.language !== undefined || signals.template !== undefined || signals.scrolllocked !== undefined)\n ? [signals.language !== undefined ? `language ${signals.language || \"unknown\"}` : \"\", signals.template !== undefined ? `template ${signals.template}` : \"\", signals.scrolllocked !== undefined ? `scroll lock ${signals.scrolllocked ? \"on\" : \"off\"}` : \"\"].filter(Boolean).join(\" \u00B7 \")\n : \"unknown\";\n livenode.textContent = `Held keys: ${heldsummary} \u00B7 Open dialogs answered: ${dialogsummary} \u00B7 Page signals: ${pages}.`;\n}\n\nasync function restore(): Promise<void> {\n const context = await request({ kind: \"context\" }) as { config?: { endpoint: string }; session?: { stoppedat?: number; pausedat?: number; expiresat: number }; capabilities?: capabilityreport; holds?: { heldkeys: keyholdstate[] }; dialogs?: dialogdecision[]; signals?: pagesignallike };\n if (endpointinput && context.config) endpointinput.value = context.config.endpoint;\n rendercapabilities(context.capabilities);\n renderlivestate(context.holds?.heldkeys, context.dialogs, context.signals);\n const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();\n if (pausebutton) { pausebutton.disabled = !active; pausebutton.textContent = pauselabel(Boolean(context.session?.pausedat)); }\n if (active && context.session?.pausedat) status(\"Session paused. Reviewed actions are blocked until resume.\");\n else status(active ? \"Session active. Review the plan in the side panel.\" : \"No active browser session.\");\n}\n\nconnectbutton?.addEventListener(\"click\", async () => {\n try {\n const config = normalizeendpoint(endpointinput?.value ?? \"\");\n const granted = await chrome.permissions.request({ origins: [hostpattern(config.origin)] });\n if (!granted) throw new Error(\"Origin permission was not granted.\");\n await request({ kind: \"configure\", endpoint: config.endpoint });\n status(`Endpoint approved for ${config.origin}.`);\n } catch (error) { status(error instanceof Error ? error.message : String(error), true); }\n});\nsessionbutton?.addEventListener(\"click\", async () => { try { await request({ kind: \"startsession\" }); status(\"Session started for the active HTTPS tab.\"); await restore(); } catch (error) { status(error instanceof Error ? error.message : String(error), true); } });\npausebutton?.addEventListener(\"click\", async () => { try { const context = await request({ kind: \"context\" }) as { session?: { pausedat?: number } }; await request({ kind: context.session?.pausedat ? \"resumesession\" : \"pausesession\" }); await restore(); } catch (error) { status(error instanceof Error ? error.message : String(error), true); } });\nstopbutton?.addEventListener(\"click\", async () => { try { await request({ kind: \"stop\" }); status(\"Session stopped. No action can continue.\"); await restore(); } catch (error) { status(error instanceof Error ? error.message : String(error), true); } });\nopenbutton?.addEventListener(\"click\", () => chrome.sidePanel.open({ windowId: chrome.windows.WINDOW_ID_CURRENT }));\nrestore().catch(error => status(error instanceof Error ? error.message : String(error), true));\n"],
5
+ "mappings": "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",
6
6
  "names": []
7
7
  }
@@ -1,5 +1,5 @@
1
1
  <!doctype html>
2
2
  <html lang="en">
3
3
  <head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Devthink review</title><link rel="stylesheet" href="style.css"></head>
4
- <body><main class="wide"><header><p class="eyebrow">REVIEW GATE</p><h1>Plan before action</h1><p>Every browser operation remains blocked until the user reviews and approves it here.</p></header><label for="objective">Objective</label><textarea id="objective" rows="4" placeholder="Describe a browser task to turn into a reviewable plan."></textarea><div class="actions"><button id="localplan">Create local observation plan</button><button id="remoteplan" class="secondary">Ask configured endpoint</button><button id="diagnostic" class="secondary">Capture local diagnostics</button></div><p id="status" role="status">Loading session state.</p><p id="capabilitiestext" role="status" class="muted">Optional capabilities unknown.</p><section><h2>Current plan</h2><progress id="planprogress" max="1" value="0"></progress><div id="plan" class="panel"></div></section><section><h2>Structured diagnostics</h2><ul id="diagnostics" class="audit"></ul></section><section><h2>Local audit</h2><ol id="audit" class="audit"></ol></section></main><script type="module" src="sidepanel.js"></script></body>
4
+ <body><main class="wide"><header><p class="eyebrow">REVIEW GATE</p><h1>Plan before action</h1><p>Every browser operation remains blocked until the user reviews and approves it here.</p></header><label for="objective">Objective</label><textarea id="objective" rows="4" placeholder="Describe a browser task to turn into a reviewable plan."></textarea><div class="actions"><button id="localplan">Create local observation plan</button><button id="remoteplan" class="secondary">Ask configured endpoint</button><button id="diagnostic" class="secondary">Capture local diagnostics</button></div><p id="status" role="status">Loading session state.</p><p id="capabilitiestext" role="status" class="muted">Optional capabilities unknown.</p><section><h2>Current plan</h2><progress id="planprogress" max="1" value="0"></progress><div id="plan" class="panel"></div></section><section><h2>Clickable map</h2><ol id="map" class="audit maplist"></ol></section><section><h2>Consent banners</h2><div id="banners" class="panel"></div></section><section><h2>Accessibility tree</h2><div id="a11y" class="panel"></div></section><section><h2>Reader view</h2><div id="reader" class="panel"></div></section><section><h2>Detected shapes</h2><ol id="detections" class="audit maplist"></ol></section><section><h2>Watch stream</h2><ol id="stream" class="audit"></ol></section><section><h2>Snapshot diffs</h2><div id="diffs" class="panel"></div></section><section><h2>Derived selectors</h2><ol id="selectors" class="audit maplist"></ol></section><section><h2>Structured diagnostics</h2><ul id="diagnostics" class="audit"></ul></section><section><h2>Local audit</h2><ol id="audit" class="audit"></ol></section></main><script type="module" src="sidepanel.js"></script></body>
5
5
  </html>