@wenathlan/extension 1.1.30 → 1.1.32
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -11
- package/dist/cli.js +23 -2
- package/dist/index.js +124 -15
- package/dist/index.js.map +2 -2
- package/dist/memory.d.ts +11 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/policy.d.ts +7 -1
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +7 -2
- package/dist/protocol.d.ts.map +1 -1
- package/dist/types.d.ts +44 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +330 -38
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/manifest.json +7 -1
- package/extension/dist/pagebridge.js +422 -7
- package/extension/dist/pagebridge.js.map +4 -4
- package/extension/dist/popup.html +1 -1
- package/extension/dist/popup.js +36 -1
- package/extension/dist/popup.js.map +2 -2
- package/extension/dist/sidepanel.html +1 -1
- package/extension/dist/sidepanel.js +75 -16
- package/extension/dist/sidepanel.js.map +2 -2
- package/extension/dist/style.css +1 -0
- package/extension/manifest.json +7 -1
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
|
-
"sources": ["../pagebridge.ts"],
|
|
4
|
-
"sourcesContent": ["import type { observation, toolstep } from \"../types.js\";\n\nfunction bounded(value: string, length = 180): string {\n return value.replace(/\\s+/g, \" \").trim().slice(0, length);\n}\n\nfunction label(element: Element): string {\n const aria = element.getAttribute(\"aria-label\");\n const labelledby = element.getAttribute(\"aria-labelledby\");\n const linked = labelledby ? document.getElementById(labelledby)?.textContent : \"\";\n return bounded(aria || linked || element.getAttribute(\"title\") || element.textContent || \"\");\n}\n\nfunction selector(element: Element): string {\n if (element.id) return `#${CSS.escape(element.id)}`;\n const role = element.getAttribute(\"role\");\n const name = element.getAttribute(\"name\");\n if (role && name) return `[role=\"${CSS.escape(role)}\"][name=\"${CSS.escape(name)}\"]`;\n if (name) return `${element.tagName.toLowerCase()}[name=\"${CSS.escape(name)}\"]`;\n const tag = element.tagName.toLowerCase();\n const parent = element.parentElement;\n if (!parent) return tag;\n const peers = [...parent.children].filter(node => node.tagName === element.tagName);\n return `${tag}:nth-of-type(${peers.indexOf(element) + 1})`;\n}\n\nconst previewid = \"devthinktargetpreview\";\n\nfunction clearpreview(): void {\n document.getElementById(previewid)?.remove();\n}\n\n/** Shows an ephemeral outline only; it neither mutates page data nor dispatches page events. */\nexport function previewtarget(targetselector: string, expectedorigin: string): { ok: boolean; summary: string } {\n if (location.origin !== expectedorigin) return { ok: false, summary: \"Page origin changed before preview.\" };\n clearpreview();\n const target = document.querySelector(targetselector);\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Reviewed target is no longer available.\" };\n const rect = target.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return { ok: false, summary: \"Reviewed target is not currently visible.\" };\n const overlay = document.createElement(\"div\");\n overlay.id = previewid;\n overlay.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(overlay.style, { position: \"fixed\", left: `${Math.max(0, rect.left - 3)}px`, top: `${Math.max(0, rect.top - 3)}px`, width: `${rect.width + 6}px`, height: `${rect.height + 6}px`, border: \"3px solid #2f80ed\", borderRadius: \"6px\", boxShadow: \"0 0 0 3px rgba(47,128,237,.28)\", pointerEvents: \"none\", zIndex: \"2147483647\", boxSizing: \"border-box\" });\n document.documentElement.append(overlay);\n window.setTimeout(clearpreview, 5000);\n return { ok: true, summary: `Previewing ${label(target) || target.tagName.toLowerCase()} for five seconds.` };\n}\n\n/** Captures only semantic, bounded page context for a user-approved active tab. */\nexport function capturesnapshot(): observation {\n const candidates = [...document.querySelectorAll(\"a[href], button, input, textarea, select, [role=button], [role=link]\")].slice(0, 80);\n const interactive = candidates.map(element => ({ selector: selector(element), role: element.getAttribute(\"role\") || element.tagName.toLowerCase(), label: label(element) })).filter(item => item.label || item.role);\n const forms = [...document.querySelectorAll(\"input, textarea, select\")].slice(0, 40).map(element => ({ label: label(element), type: element.getAttribute(\"type\") || element.tagName.toLowerCase(), name: element.getAttribute(\"name\") || \"\" }));\n const text = bounded(document.body?.innerText || \"\", 2000);\n return { url: location.href, title: bounded(document.title, 180), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };\n}\n\n/** Performs one local action after the background policy gate and a fresh target check. */\nexport function performstep(step: toolstep, expectedorigin: string): { ok: boolean; summary: string } {\n if (location.origin !== expectedorigin) return { ok: false, summary: \"Page origin changed before action.\" };\n if (step.kind === \"observe\") return { ok: true, summary: \"Observation completed.\" };\n if (step.kind === \"navigate\") {\n if (!step.value || new URL(step.value).origin !== expectedorigin) return { ok: false, summary: \"Navigation target is outside the approved origin.\" };\n location.assign(step.value);\n return { ok: true, summary: \"Navigation request sent.\" };\n }\n if (!step.target) return { ok: false, summary: \"Action target is absent.\" };\n const target = document.querySelector(step.target);\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Action target is no longer available.\" };\n if (step.kind === \"focus\") { target.focus(); return { ok: true, summary: \"Target focused.\" }; }\n if (step.kind === \"inspect\") return { ok: true, summary: `Target: ${label(target) || target.tagName.toLowerCase()}.` };\n if (step.kind === \"click\") { target.click(); return { ok: true, summary: \"Reviewed click completed.\" }; }\n if (step.kind === \"type\") {\n if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return { ok: false, summary: \"Target cannot receive text.\" };\n if (typeof step.value !== \"string\") return { ok: false, summary: \"Approved text is absent.\" };\n target.focus();\n target.value = step.value;\n target.dispatchEvent(new Event(\"input\", { bubbles: true }));\n target.dispatchEvent(new Event(\"change\", { bubbles: true }));\n return { ok: true, summary: \"Approved text entered.\" };\n }\n return { ok: false, summary: \"Unsupported action.\" };\n}\n\nObject.assign(globalThis, { devthinkbridge: { capturesnapshot, previewtarget, performstep } });\n"],
|
|
5
|
-
"mappings": "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",
|
|
6
|
-
"names": []
|
|
3
|
+
"sources": ["../../policy.ts", "../pageactions.ts", "../pagereads.ts", "../pagebridge.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 type { toolstep } from \"../types.js\";\nimport { parseoptions } from \"../policy.js\";\n\n/**\n * Mutating page interactions for reviewed steps.\n * Every correlated rule for pointer, key, drag, upload, form, attribute, storage and evaluation actions lives in this file.\n */\n\nexport type stepresult = { ok: boolean; summary: string; details?: Record<string, unknown> };\n\nfunction events(target: Element): void {\n target.dispatchEvent(new Event(\"input\", { bubbles: true }));\n target.dispatchEvent(new Event(\"change\", { bubbles: true }));\n}\n\nfunction modifiers(options: Record<string, unknown>): string[] {\n return Array.isArray(options.modifiers) ? options.modifiers.filter((item): item is string => typeof item === \"string\") : [];\n}\n\nfunction keyevent(type: \"keydown\" | \"keypress\" | \"keyup\", key: string, mods: string[]): KeyboardEvent {\n const code = key.length === 1 ? `Key${key.toUpperCase()}` : key;\n return new KeyboardEvent(type, { key, code, bubbles: true, cancelable: true, composed: true, ctrlKey: mods.includes(\"ctrl\"), shiftKey: mods.includes(\"shift\"), altKey: mods.includes(\"alt\"), metaKey: mods.includes(\"meta\") });\n}\n\nfunction fieldlike(target: Element | null): HTMLInputElement | HTMLTextAreaElement | null {\n return target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement ? target : null;\n}\n\nfunction stringify(value: unknown): unknown {\n try { return JSON.parse(JSON.stringify(value)) ?? null; } catch { return String(value); }\n}\n\n/** Runs one mutating page action after the background policy gate and a fresh target check. */\nexport function runpageaction(step: toolstep, target: Element | null): stepresult | Promise<stepresult> {\n const options = (() => { try { return parseoptions(step); } catch { return {} as Record<string, unknown>; } })();\n switch (step.kind) {\n case \"presskey\": {\n const receiver = target instanceof HTMLElement ? target : document.activeElement instanceof HTMLElement ? document.activeElement : document.body;\n const key = step.value ?? \"\";\n const mods = modifiers(options);\n receiver.dispatchEvent(keyevent(\"keydown\", key, mods));\n receiver.dispatchEvent(keyevent(\"keypress\", key, mods));\n receiver.dispatchEvent(keyevent(\"keyup\", key, mods));\n return { ok: true, summary: `Key ${key} delivered with ${mods.length} modifier${mods.length === 1 ? \"\" : \"s\"}.` };\n }\n case \"clickdeep\": {\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Action target is no longer available.\" };\n target.dispatchEvent(new PointerEvent(\"pointerdown\", { bubbles: true, cancelable: true, composed: true }));\n target.dispatchEvent(new MouseEvent(\"mousedown\", { bubbles: true, cancelable: true }));\n target.dispatchEvent(new PointerEvent(\"pointerup\", { bubbles: true, cancelable: true, composed: true }));\n target.dispatchEvent(new MouseEvent(\"mouseup\", { bubbles: true, cancelable: true }));\n target.click();\n return { ok: true, summary: \"Full pointer click sequence delivered.\" };\n }\n case \"rightclick\": {\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Action target is no longer available.\" };\n const init: MouseEventInit = { bubbles: true, cancelable: true, button: 2, buttons: 2 };\n target.dispatchEvent(new PointerEvent(\"pointerdown\", { ...init, composed: true }));\n target.dispatchEvent(new MouseEvent(\"mousedown\", init));\n target.dispatchEvent(new MouseEvent(\"contextmenu\", init));\n return { ok: true, summary: \"Context menu events delivered.\" };\n }\n case \"doubleclick\": {\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Action target is no longer available.\" };\n target.click();\n target.click();\n target.dispatchEvent(new MouseEvent(\"dblclick\", { bubbles: true, cancelable: true, detail: 2 }));\n return { ok: true, summary: \"Double click sequence delivered.\" };\n }\n case \"drag\": {\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Drag source is no longer available.\" };\n const destination = document.querySelector(step.value ?? \"\");\n if (!destination) return { ok: false, summary: \"Drag destination is no longer available.\" };\n const transfer = new DataTransfer();\n if (typeof options.data === \"string\") transfer.setData(\"text/plain\", options.data);\n target.dispatchEvent(new DragEvent(\"dragstart\", { bubbles: true, cancelable: true, dataTransfer: transfer }));\n destination.dispatchEvent(new DragEvent(\"dragenter\", { bubbles: true, cancelable: true, dataTransfer: transfer }));\n destination.dispatchEvent(new DragEvent(\"dragover\", { bubbles: true, cancelable: true, dataTransfer: transfer }));\n destination.dispatchEvent(new DragEvent(\"drop\", { bubbles: true, cancelable: true, dataTransfer: transfer }));\n target.dispatchEvent(new DragEvent(\"dragend\", { bubbles: true, cancelable: true, dataTransfer: transfer }));\n return { ok: true, summary: \"Drag and drop sequence delivered.\" };\n }\n case \"drop\": {\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Drop zone is no longer available.\" };\n const transfer = new DataTransfer();\n transfer.setData(\"text/plain\", step.value ?? \"\");\n target.dispatchEvent(new DragEvent(\"dragenter\", { bubbles: true, cancelable: true, dataTransfer: transfer }));\n target.dispatchEvent(new DragEvent(\"dragover\", { bubbles: true, cancelable: true, dataTransfer: transfer }));\n target.dispatchEvent(new DragEvent(\"drop\", { bubbles: true, cancelable: true, dataTransfer: transfer }));\n return { ok: true, summary: \"Drop payload delivered.\" };\n }\n case \"upload\": {\n if (!(target instanceof HTMLInputElement) || target.type !== \"file\") return { ok: false, summary: \"Target is not a file input.\" };\n const transfer = new DataTransfer();\n transfer.items.add(new File([typeof options.content === \"string\" ? options.content : \"\"], step.value ?? \"upload\", { type: typeof options.type === \"string\" ? options.type : \"text/plain\" }));\n target.files = transfer.files;\n events(target);\n return { ok: true, summary: `Uploaded ${step.value ?? \"file\"} into the reviewed input.` };\n }\n case \"clear\": {\n const field = fieldlike(target);\n if (!field) return { ok: false, summary: \"Target cannot hold a value.\" };\n field.value = \"\";\n events(field);\n return { ok: true, summary: \"Field cleared.\" };\n }\n case \"check\":\n case \"uncheck\":\n case \"toggle\": {\n if (!(target instanceof HTMLInputElement) || (target.type !== \"checkbox\" && target.type !== \"radio\")) return { ok: false, summary: \"Target is not a checkbox or radio control.\" };\n if (step.kind === \"uncheck\" && target.type === \"radio\") return { ok: false, summary: \"A radio control cannot be unchecked.\" };\n target.checked = step.kind === \"toggle\" ? !target.checked : step.kind === \"check\";\n events(target);\n return { ok: true, summary: `Control is now ${target.checked ? \"checked\" : \"unchecked\"}.` };\n }\n case \"submit\": {\n const form = target instanceof HTMLFormElement ? target : target instanceof HTMLElement ? target.closest(\"form\") : null;\n if (!form) return { ok: false, summary: \"No form owns the reviewed target.\" };\n try { form.requestSubmit(target instanceof HTMLFormElement ? undefined : (target as HTMLElement)); } catch { form.submit(); }\n return { ok: true, summary: \"Form submission requested.\" };\n }\n case \"setattribute\": {\n if (!target) return { ok: false, summary: \"Action target is no longer available.\" };\n const name = typeof options.name === \"string\" ? options.name : \"\";\n target.setAttribute(name, typeof options.value === \"string\" ? options.value : \"\");\n return { ok: true, summary: `Attribute ${name} set.` };\n }\n case \"removeattribute\": {\n if (!target) return { ok: false, summary: \"Action target is no longer available.\" };\n const name = step.value ?? \"\";\n target.removeAttribute(name);\n return { ok: true, summary: `Attribute ${name} removed.` };\n }\n case \"writestorage\": {\n try {\n localStorage.setItem(typeof options.key === \"string\" ? options.key : \"\", typeof options.value === \"string\" ? options.value : \"\");\n return { ok: true, summary: `Local storage entry ${String(options.key)} written.` };\n } catch (error) { return { ok: false, summary: `Local storage refused the write: ${error instanceof Error ? error.message : String(error)}` }; }\n }\n case \"evaluate\": {\n try {\n let outcome: unknown;\n try { outcome = new Function(`\"use strict\"; return (${step.value ?? \"undefined\"});`)(); } catch { outcome = new Function(`\"use strict\"; ${step.value ?? \"\"}`)(); }\n return { ok: true, summary: `Reviewed expression returned ${outcome === undefined ? \"no value\" : \"a value\"}.`, details: { result: stringify(outcome) } };\n } catch (error) { return { ok: false, summary: `Reviewed expression failed: ${error instanceof Error ? error.message : String(error)}` }; }\n }\n case \"fullscreen\": {\n const element = target instanceof HTMLElement ? target : document.documentElement;\n return document.fullscreenElement === element\n ? document.exitFullscreen().then(() => ({ ok: true, summary: \"Fullscreen state cleared.\" }) as stepresult)\n : element.requestFullscreen().then(() => ({ ok: true, summary: \"Fullscreen state entered.\" }) as stepresult).catch(error => ({ ok: false, summary: `Fullscreen was refused: ${error instanceof Error ? error.message : String(error)}` }));\n }\n default: return { ok: false, summary: \"Unsupported page action.\" };\n }\n}\n", "import type { toolstep } from \"../types.js\";\nimport { parseoptions } from \"../policy.js\";\n\n/**\n * Read-only page observation for reviewed steps.\n * Every correlated rule for attribute, style, geometry, value, content, table, link, image, meta, form, storage reads and bounded-free waits lives in this file.\n */\n\nimport type { stepresult } from \"./pageactions.js\";\n\nconst highlightid = \"devthinkactionhighlight\";\n\nfunction clearhighlight(): void {\n document.getElementById(highlightid)?.remove();\n}\n\n/** Outlines one reviewed target without dispatching page events. */\nfunction highlighttarget(target: Element): stepresult {\n clearhighlight();\n const rect = target.getBoundingClientRect();\n const overlay = document.createElement(\"div\");\n overlay.id = highlightid;\n overlay.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(overlay.style, { position: \"fixed\", left: `${Math.max(0, rect.left - 3)}px`, top: `${Math.max(0, rect.top - 3)}px`, width: `${rect.width + 6}px`, height: `${rect.height + 6}px`, border: \"3px solid #2f9e44\", borderRadius: \"6px\", pointerEvents: \"none\", zIndex: \"2147483647\", boxSizing: \"border-box\" });\n document.documentElement.append(overlay);\n window.setTimeout(clearhighlight, 5000);\n return { ok: true, summary: \"Target outlined for five seconds.\" };\n}\n\nfunction poll(predicate: () => boolean, description: string, timeout: number): Promise<stepresult> {\n return new Promise(resolve => {\n const started = Date.now();\n const check = (): void => {\n if (predicate()) { resolve({ ok: true, summary: `${description} is now present on the page.` }); return; }\n if (timeout > 0 && Date.now() - started >= timeout) { resolve({ ok: false, summary: `${description} did not appear within ${timeout} milliseconds.` }); return; }\n window.setTimeout(check, 100);\n };\n check();\n });\n}\n\nfunction formstate(): Array<Record<string, unknown>> {\n return [...document.querySelectorAll(\"input, textarea, select\")].map(element => ({\n type: element.getAttribute(\"type\") ?? element.tagName.toLowerCase(),\n name: element.getAttribute(\"name\") ?? \"\",\n value: element instanceof HTMLInputElement || element instanceof HTMLTextAreaElement || element instanceof HTMLSelectElement ? element.value : \"\",\n ...(element instanceof HTMLInputElement && (element.type === \"checkbox\" || element.type === \"radio\") ? { checked: element.checked } : {}),\n }));\n}\n\n/** Runs one read-only page observation after the background policy gate. */\nexport function runpageread(step: toolstep, target: Element | null): stepresult | Promise<stepresult> {\n const options = (() => { try { return parseoptions(step); } catch { return {} as Record<string, unknown>; } })();\n switch (step.kind) {\n case \"highlight\": {\n if (!target) return { ok: false, summary: \"Highlight target is no longer available.\" };\n return highlighttarget(target);\n }\n case \"readattribute\": {\n if (!target) return { ok: false, summary: \"Read target is no longer available.\" };\n const value = target.getAttribute(step.value ?? \"\");\n return value === null ? { ok: false, summary: `Attribute ${step.value} is absent.` } : { ok: true, summary: `Attribute ${step.value} read.`, details: { value } };\n }\n case \"readstyle\": {\n if (!target) return { ok: false, summary: \"Read target is no longer available.\" };\n const computed = getComputedStyle(target);\n const styles: Record<string, string> = {};\n for (let index = 0; index < computed.length; index += 1) { const property = computed.item(index); styles[property] = computed.getPropertyValue(property); }\n return { ok: true, summary: `Read ${computed.length} computed style properties.`, details: { styles } };\n }\n case \"readgeometry\": {\n if (!target) return { ok: false, summary: \"Read target is no longer available.\" };\n const rect = target.getBoundingClientRect();\n const geometry = { x: rect.x, y: rect.y, width: rect.width, height: rect.height, top: rect.top, right: rect.right, bottom: rect.bottom, left: rect.left };\n return { ok: true, summary: \"Target geometry read.\", details: { geometry } };\n }\n case \"readvalue\": {\n if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement || target instanceof HTMLSelectElement)) return { ok: false, summary: \"Target does not hold a form value.\" };\n return { ok: true, summary: \"Form value read.\", details: { value: target.value } };\n }\n case \"readtext\": {\n if (!target) return { ok: false, summary: \"Read target is no longer available.\" };\n const text = target.textContent ?? \"\";\n return { ok: true, summary: `Read ${text.length} characters of text.`, details: { text } };\n }\n case \"readhtml\": {\n if (!target) return { ok: false, summary: \"Read target is no longer available.\" };\n return { ok: true, summary: \"Target markup read.\", details: { html: target.outerHTML } };\n }\n case \"countelements\": {\n const count = document.querySelectorAll(step.target ?? \"\").length;\n return { ok: true, summary: `Selector matches ${count} element${count === 1 ? \"\" : \"s\"}.`, details: { count } };\n }\n case \"readtable\": {\n if (!(target instanceof HTMLTableElement)) return { ok: false, summary: \"Read target is not a table element.\" };\n const rows = [...target.querySelectorAll(\"tr\")].map(row => [...row.querySelectorAll(\"th, td\")].map(cell => cell.textContent?.trim() ?? \"\"));\n const headers = rows[0] ?? [];\n const body = rows.slice(1);\n return { ok: true, summary: `Read table with ${headers.length} column${headers.length === 1 ? \"\" : \"s\"} and ${body.length} row${body.length === 1 ? \"\" : \"s\"}.`, details: { headers, rows: body } };\n }\n case \"readlinks\": {\n const links = [...document.querySelectorAll(\"a[href]\")].map(element => ({ text: element.textContent?.trim() ?? \"\", href: element.getAttribute(\"href\") ?? \"\" }));\n return { ok: true, summary: `Read ${links.length} link${links.length === 1 ? \"\" : \"s\"}.`, details: { links } };\n }\n case \"readimages\": {\n const images = [...document.querySelectorAll(\"img\")].map(element => ({ src: element.getAttribute(\"src\") ?? \"\", alt: element.getAttribute(\"alt\") ?? \"\" }));\n return { ok: true, summary: `Read ${images.length} image${images.length === 1 ? \"\" : \"s\"}.`, details: { images } };\n }\n case \"readmeta\": {\n const meta = [...document.querySelectorAll(\"meta\")].map(element => ({ name: element.getAttribute(\"name\") ?? \"\", property: element.getAttribute(\"property\") ?? \"\", content: element.getAttribute(\"content\") ?? \"\" }));\n return { ok: true, summary: `Read ${meta.length} meta entr${meta.length === 1 ? \"y\" : \"ies\"}.`, details: { meta } };\n }\n case \"readforms\": {\n const forms = formstate();\n return { ok: true, summary: `Read ${forms.length} form control${forms.length === 1 ? \"\" : \"s\"}.`, details: { forms } };\n }\n case \"readstorage\": {\n try {\n if (step.value) {\n const value = localStorage.getItem(step.value);\n return { ok: true, summary: `Read local storage entry ${step.value}.`, details: { value } };\n }\n const entries: Record<string, string | null> = {};\n for (let index = 0; index < localStorage.length; index += 1) { const key = localStorage.key(index); if (key !== null) entries[key] = localStorage.getItem(key); }\n return { ok: true, summary: `Read ${Object.keys(entries).length} local storage entr${Object.keys(entries).length === 1 ? \"y\" : \"ies\"}.`, details: { entries } };\n } catch (error) { return { ok: false, summary: `Local storage refused the read: ${error instanceof Error ? error.message : String(error)}` }; }\n }\n case \"waitfor\": {\n const selector = step.target ?? \"\";\n const timeout = typeof options.timeout === \"number\" ? options.timeout : 0;\n return poll(() => Boolean(document.querySelector(selector)), `Selector ${selector}`, timeout);\n }\n case \"waittext\": {\n const text = step.value ?? \"\";\n const timeout = typeof options.timeout === \"number\" ? options.timeout : 0;\n return poll(() => (document.body?.innerText ?? \"\").includes(text), `Text ${text}`, timeout);\n }\n default: return { ok: false, summary: \"Unsupported page read.\" };\n }\n}\n", "import type { observation, toolstep } from \"../types.js\";\nimport { runpageaction, type stepresult } from \"./pageactions.js\";\nimport { runpageread } from \"./pagereads.js\";\n\n/**\n * Page bridge for reviewed steps.\n * Correlated rules for semantic snapshots, target preview, step dispatch and the original action set live in this file.\n */\n\nfunction clean(value: string): string {\n return value.replace(/\\s+/g, \" \").trim();\n}\n\nfunction label(element: Element): string {\n const aria = element.getAttribute(\"aria-label\");\n const labelledby = element.getAttribute(\"aria-labelledby\");\n const linked = labelledby ? document.getElementById(labelledby)?.textContent : \"\";\n return clean(aria || linked || element.getAttribute(\"title\") || element.textContent || \"\");\n}\n\nfunction selector(element: Element): string {\n if (element.id) return `#${CSS.escape(element.id)}`;\n const role = element.getAttribute(\"role\");\n const name = element.getAttribute(\"name\");\n if (role && name) return `[role=\"${CSS.escape(role)}\"][name=\"${CSS.escape(name)}\"]`;\n if (name) return `${element.tagName.toLowerCase()}[name=\"${CSS.escape(name)}\"]`;\n const tag = element.tagName.toLowerCase();\n const parent = element.parentElement;\n if (!parent) return tag;\n const peers = [...parent.children].filter(node => node.tagName === element.tagName);\n return `${tag}:nth-of-type(${peers.indexOf(element) + 1})`;\n}\n\nfunction stepoptions(step: toolstep): Record<string, unknown> {\n if (!step.options) return {};\n try {\n const parsed = JSON.parse(step.options);\n return parsed && typeof parsed === \"object\" && !Array.isArray(parsed) ? (parsed as Record<string, unknown>) : {};\n } catch { return {}; }\n}\n\nconst previewid = \"devthinktargetpreview\";\n\nfunction clearpreview(): void {\n document.getElementById(previewid)?.remove();\n}\n\n/** Shows an ephemeral outline only; it neither mutates page data nor dispatches page events. */\nexport function previewtarget(targetselector: string, expectedorigin: string): { ok: boolean; summary: string } {\n if (location.origin !== expectedorigin) return { ok: false, summary: \"Page origin changed before preview.\" };\n clearpreview();\n const target = document.querySelector(targetselector);\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Reviewed target is no longer available.\" };\n const rect = target.getBoundingClientRect();\n if (rect.width <= 0 || rect.height <= 0) return { ok: false, summary: \"Reviewed target is not currently visible.\" };\n const overlay = document.createElement(\"div\");\n overlay.id = previewid;\n overlay.setAttribute(\"aria-hidden\", \"true\");\n Object.assign(overlay.style, { position: \"fixed\", left: `${Math.max(0, rect.left - 3)}px`, top: `${Math.max(0, rect.top - 3)}px`, width: `${rect.width + 6}px`, height: `${rect.height + 6}px`, border: \"3px solid #2f80ed\", borderRadius: \"6px\", boxShadow: \"0 0 0 3px rgba(47,128,237,.28)\", pointerEvents: \"none\", zIndex: \"2147483647\", boxSizing: \"border-box\" });\n document.documentElement.append(overlay);\n window.setTimeout(clearpreview, 5000);\n return { ok: true, summary: `Previewing ${label(target) || target.tagName.toLowerCase()} for five seconds.` };\n}\n\n/** Captures the complete semantic page context for a user-approved active tab. */\nexport function capturesnapshot(): observation {\n const candidates = [...document.querySelectorAll(\"a[href], button, input, textarea, select, [role=button], [role=link], [role=combobox], [role=option], [role=checkbox], [role=radio], [role=switch], [role=tab], details, summary\")];\n const interactive = candidates.map(element => ({ selector: selector(element), role: element.getAttribute(\"role\") || element.tagName.toLowerCase(), label: label(element) })).filter(item => item.label || item.role);\n const forms = [...document.querySelectorAll(\"input, textarea, select\")].map(element => ({\n label: label(element),\n type: element.getAttribute(\"type\") || element.tagName.toLowerCase(),\n name: element.getAttribute(\"name\") || \"\",\n ...(element instanceof HTMLSelectElement ? { options: [...element.options].map(option => clean(option.textContent || option.value)) } : {}),\n }));\n const text = clean(document.body?.innerText || \"\");\n return { schemaversion: 2, url: location.href, title: clean(document.title), textpreview: text, textlength: document.body?.innerText.length ?? 0, forms, interactive, capturedat: Date.now() };\n}\n\n/** Extracts full, read-only structured content for a reviewed extraction step. */\nfunction extractcontent(targetselector: string | undefined): stepresult {\n if (!targetselector) {\n const links = [...document.querySelectorAll(\"a[href]\")].map(element => {\n const href = element instanceof HTMLAnchorElement ? element.getAttribute(\"href\") ?? \"\" : \"\";\n return { text: element.textContent?.trim() ?? \"\", href };\n });\n return { ok: true, summary: `Extracted ${links.length} link entries.`, details: { links } };\n }\n const target = document.querySelector(targetselector);\n if (!target) return { ok: false, summary: \"Extraction target is no longer available.\" };\n const text = target.textContent ?? \"\";\n return { ok: true, summary: `Extracted ${text.length} characters of content.`, details: { text } };\n}\n\n/** Scrolls one reviewed target into view without reading or changing other page state. */\nfunction scrolltarget(target: HTMLElement): stepresult {\n target.scrollIntoView({ block: \"center\", inline: \"nearest\", behavior: \"auto\" });\n return { ok: true, summary: `Scrolled ${label(target) || target.tagName.toLowerCase()} into view.` };\n}\n\n/** Dispatches hover events on one reviewed target. */\nfunction hovertarget(target: HTMLElement): stepresult {\n for (const type of [\"pointerover\", \"mouseover\", \"pointerenter\"] as const) {\n target.dispatchEvent(new PointerEvent(type, { bubbles: type !== \"pointerenter\", cancelable: true, composed: true }));\n }\n target.dispatchEvent(new MouseEvent(\"mouseenter\", { bubbles: false, cancelable: true }));\n return { ok: true, summary: `Hover events delivered to ${label(target) || target.tagName.toLowerCase()}.` };\n}\n\n/** Selects one reviewed existing option; values outside the declared options are refused. */\nfunction selectoption(target: HTMLElement, value: string): stepresult {\n if (!(target instanceof HTMLSelectElement)) return { ok: false, summary: \"Target is not a select element.\" };\n const option = [...target.options].find(candidate => candidate.value === value || candidate.textContent?.trim() === value);\n if (!option) return { ok: false, summary: \"Reviewed option is not part of the select element.\" };\n target.value = option.value;\n target.dispatchEvent(new Event(\"input\", { bubbles: true }));\n target.dispatchEvent(new Event(\"change\", { bubbles: true }));\n return { ok: true, summary: `Selected ${clean(option.textContent || option.value)}.` };\n}\n\nconst readkinds: ReadonlySet<string> = new Set([\"readattribute\", \"readstyle\", \"readgeometry\", \"readvalue\", \"readtext\", \"readhtml\", \"countelements\", \"readtable\", \"readlinks\", \"readimages\", \"readmeta\", \"readforms\", \"readstorage\", \"waitfor\", \"waittext\", \"highlight\"]);\nconst mutatingkinds: ReadonlySet<string> = new Set([\"presskey\", \"clickdeep\", \"rightclick\", \"doubleclick\", \"drag\", \"drop\", \"upload\", \"clear\", \"check\", \"uncheck\", \"toggle\", \"submit\", \"setattribute\", \"removeattribute\", \"writestorage\", \"evaluate\", \"fullscreen\"]);\n\n/** Performs one local action after the background policy gate and a fresh target check. */\nexport function performstep(step: toolstep, expectedorigin: string): stepresult | Promise<stepresult> {\n if (location.origin !== expectedorigin) return { ok: false, summary: \"Page origin changed before action.\" };\n if (step.kind === \"observe\") return { ok: true, summary: \"Observation completed.\" };\n if (step.kind === \"wait\") {\n const requested = step.value ? Number.parseInt(step.value, 10) : 250;\n const duration = Number.isFinite(requested) && requested > 0 ? requested : 0;\n return new Promise(resolve => window.setTimeout(() => resolve({ ok: true, summary: `Reviewed wait of ${duration} milliseconds completed.` }), duration));\n }\n if (step.kind === \"extract\") return extractcontent(step.target);\n if (step.kind === \"navigate\") {\n if (!step.value || new URL(step.value).origin !== expectedorigin) return { ok: false, summary: \"Navigation target is outside the approved origin.\" };\n location.assign(step.value);\n return { ok: true, summary: \"Navigation request sent.\" };\n }\n if (step.kind === \"reload\") { location.reload(); return { ok: true, summary: \"Page reload requested.\" }; }\n if (step.kind === \"back\") { history.back(); return { ok: true, summary: \"History back requested.\" }; }\n if (step.kind === \"forward\") { history.forward(); return { ok: true, summary: \"History forward requested.\" }; }\n if (step.kind === \"scrollpage\") {\n const options = stepoptions(step);\n window.scrollBy({ left: typeof options.x === \"number\" ? options.x : 0, top: typeof options.y === \"number\" ? options.y : 600, behavior: \"auto\" });\n return { ok: true, summary: \"Window scrolled by the reviewed amounts.\" };\n }\n if (step.kind === \"scrollend\") { window.scrollTo(0, document.documentElement.scrollHeight); return { ok: true, summary: \"Window scrolled to the page end.\" }; }\n if (step.kind === \"scrolltop\") { window.scrollTo(0, 0); return { ok: true, summary: \"Window scrolled to the page top.\" }; }\n if (readkinds.has(step.kind)) return runpageread(step, step.target ? document.querySelector(step.target) : null);\n if (mutatingkinds.has(step.kind)) return runpageaction(step, step.target ? document.querySelector(step.target) : null);\n if (!step.target) return { ok: false, summary: \"Action target is absent.\" };\n const target = document.querySelector(step.target);\n if (!(target instanceof HTMLElement)) return { ok: false, summary: \"Action target is no longer available.\" };\n if (step.kind === \"scrollby\") {\n const options = stepoptions(step);\n target.scrollBy({ left: typeof options.x === \"number\" ? options.x : 0, top: typeof options.y === \"number\" ? options.y : 600, behavior: \"auto\" });\n return { ok: true, summary: \"Container scrolled by the reviewed amounts.\" };\n }\n if (step.kind === \"focus\") { target.focus(); return { ok: true, summary: \"Target focused.\" }; }\n if (step.kind === \"inspect\") return { ok: true, summary: `Target: ${label(target) || target.tagName.toLowerCase()}.` };\n if (step.kind === \"click\") { target.click(); return { ok: true, summary: \"Reviewed click completed.\" }; }\n if (step.kind === \"scroll\") return scrolltarget(target);\n if (step.kind === \"hover\") return hovertarget(target);\n if (step.kind === \"select\") return selectoption(target, step.value ?? \"\");\n if (step.kind === \"type\") {\n if (!(target instanceof HTMLInputElement || target instanceof HTMLTextAreaElement)) return { ok: false, summary: \"Target cannot receive text.\" };\n if (typeof step.value !== \"string\") return { ok: false, summary: \"Approved text is absent.\" };\n target.focus();\n target.value = step.value;\n target.dispatchEvent(new Event(\"input\", { bubbles: true }));\n target.dispatchEvent(new Event(\"change\", { bubbles: true }));\n return { ok: true, summary: \"Approved text entered.\" };\n }\n return { ok: false, summary: \"Unsupported action.\" };\n}\n\nObject.assign(globalThis, { devthinkbridge: { capturesnapshot, previewtarget, performstep } });\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["selector"]
|
|
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</title><link rel="stylesheet" href="style.css"></head>
|
|
4
|
-
<body><main><header><p class="eyebrow">DEVTHINK 1.1.
|
|
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>
|
|
5
5
|
</html>
|
package/extension/dist/popup.js
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
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"]);
|
|
5
|
+
var allowedactions = /* @__PURE__ */ new Set([...sensitiveactions, ...interactionactions, ...readactions]);
|
|
2
6
|
function normalizeendpoint(value) {
|
|
3
7
|
const endpoint = new URL(value.trim());
|
|
4
8
|
if (endpoint.protocol !== "https:") throw new Error("Devthink accepts HTTPS endpoints only.");
|
|
@@ -14,8 +18,10 @@ function hostpattern(origin) {
|
|
|
14
18
|
// extension/popup.ts
|
|
15
19
|
var endpointinput = document.querySelector("#endpoint");
|
|
16
20
|
var statusnode = document.querySelector("#status");
|
|
21
|
+
var capabilitiesnode = document.querySelector("#capabilities");
|
|
17
22
|
var connectbutton = document.querySelector("#connect");
|
|
18
23
|
var sessionbutton = document.querySelector("#session");
|
|
24
|
+
var pausebutton = document.querySelector("#pause");
|
|
19
25
|
var stopbutton = document.querySelector("#stop");
|
|
20
26
|
var openbutton = document.querySelector("#openpanel");
|
|
21
27
|
function status(message, error = false) {
|
|
@@ -29,11 +35,29 @@ async function request(message) {
|
|
|
29
35
|
if (!response.ok) throw new Error(response.error);
|
|
30
36
|
return response.value;
|
|
31
37
|
}
|
|
38
|
+
function pauselabel(paused) {
|
|
39
|
+
return paused ? "Resume session" : "Pause session";
|
|
40
|
+
}
|
|
41
|
+
function rendercapabilities(report) {
|
|
42
|
+
if (!capabilitiesnode) return;
|
|
43
|
+
if (!report) {
|
|
44
|
+
capabilitiesnode.textContent = "Capabilities unknown.";
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
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
|
+
capabilitiesnode.textContent = `Optional capabilities: ${granted.join(" \xB7 ")}.`;
|
|
49
|
+
}
|
|
32
50
|
async function restore() {
|
|
33
51
|
const context = await request({ kind: "context" });
|
|
34
52
|
if (endpointinput && context.config) endpointinput.value = context.config.endpoint;
|
|
53
|
+
rendercapabilities(context.capabilities);
|
|
35
54
|
const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();
|
|
36
|
-
|
|
55
|
+
if (pausebutton) {
|
|
56
|
+
pausebutton.disabled = !active;
|
|
57
|
+
pausebutton.textContent = pauselabel(Boolean(context.session?.pausedat));
|
|
58
|
+
}
|
|
59
|
+
if (active && context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");
|
|
60
|
+
else status(active ? "Session active. Review the plan in the side panel." : "No active browser session.");
|
|
37
61
|
}
|
|
38
62
|
connectbutton?.addEventListener("click", async () => {
|
|
39
63
|
try {
|
|
@@ -50,6 +74,16 @@ sessionbutton?.addEventListener("click", async () => {
|
|
|
50
74
|
try {
|
|
51
75
|
await request({ kind: "startsession" });
|
|
52
76
|
status("Session started for the active HTTPS tab.");
|
|
77
|
+
await restore();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
status(error instanceof Error ? error.message : String(error), true);
|
|
80
|
+
}
|
|
81
|
+
});
|
|
82
|
+
pausebutton?.addEventListener("click", async () => {
|
|
83
|
+
try {
|
|
84
|
+
const context = await request({ kind: "context" });
|
|
85
|
+
await request({ kind: context.session?.pausedat ? "resumesession" : "pausesession" });
|
|
86
|
+
await restore();
|
|
53
87
|
} catch (error) {
|
|
54
88
|
status(error instanceof Error ? error.message : String(error), true);
|
|
55
89
|
}
|
|
@@ -58,6 +92,7 @@ stopbutton?.addEventListener("click", async () => {
|
|
|
58
92
|
try {
|
|
59
93
|
await request({ kind: "stop" });
|
|
60
94
|
status("Session stopped. No action can continue.");
|
|
95
|
+
await restore();
|
|
61
96
|
} catch (error) {
|
|
62
97
|
status(error instanceof Error ? error.message : String(error), true);
|
|
63
98
|
}
|
|
@@ -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\"]);\nconst allowedactions = new Set<actionkind>([\"observe\", \"inspect\", \"focus\", \"click\", \"type\", \"navigate\"]);\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 a 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 kind === \"focus\" ? \"interaction\" : \"read\";\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 ((step.kind === \"click\" || step.kind === \"focus\" || step.kind === \"inspect\" || step.kind === \"type\") && !step.target?.trim()) return { allowed: false, reason: \"A page target is required.\" };\n if (step.kind === \"navigate\") {\n if (!step.value) return { allowed: false, reason: \"A navigation URL is required.\" };\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 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 if (!input.session || input.session.stoppedat) return { allowed: false, reason: \"No active browser session exists.\" };\n if (input.session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired.\" };\n if (input.session.tabid !== input.tabid || input.session.origin !== input.origin) return { allowed: false, reason: \"The action is outside the approved tab or origin.\" };\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 if (!input.session || input.session.stoppedat) return { allowed: false, reason: \"No active browser session exists.\" };\n if (input.session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired.\" };\n if (input.session.tabid !== input.tabid || input.session.origin !== input.origin) return { allowed: false, reason: \"The preview is outside the approved tab or origin.\" };\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 (![\"focus\", \"inspect\", \"click\", \"type\"].includes(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\";\n\nconst endpointinput = document.querySelector<HTMLInputElement>(\"#endpoint\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst connectbutton = document.querySelector<HTMLButtonElement>(\"#connect\");\nconst sessionbutton = document.querySelector<HTMLButtonElement>(\"#session\");\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\nasync function restore(): Promise<void> {\n const context = await request({ kind: \"context\" }) as { config?: { endpoint: string }; session?: { stoppedat?: number; expiresat: number } };\n if (endpointinput && context.config) endpointinput.value = context.config.endpoint;\n const active = context.session && !context.session.stoppedat && context.session.expiresat > Date.now();\n 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.\"); } 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.\"); } 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": ";
|
|
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": "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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><section><h2>Current plan</h2><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>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>
|
|
@@ -7,6 +7,8 @@ var planroot = document.querySelector("#plan");
|
|
|
7
7
|
var auditroot = document.querySelector("#audit");
|
|
8
8
|
var diagnosticroot = document.querySelector("#diagnostics");
|
|
9
9
|
var statusnode = document.querySelector("#status");
|
|
10
|
+
var progressnode = document.querySelector("#planprogress");
|
|
11
|
+
var capabilitiestext = document.querySelector("#capabilitiestext");
|
|
10
12
|
function status(message, error = false) {
|
|
11
13
|
if (statusnode) {
|
|
12
14
|
statusnode.textContent = message;
|
|
@@ -26,32 +28,74 @@ function button(label, action, disabled = false) {
|
|
|
26
28
|
element.addEventListener("click", () => action().catch((error) => status(error instanceof Error ? error.message : String(error), true)));
|
|
27
29
|
return element;
|
|
28
30
|
}
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
31
|
+
var previewkinds = ["focus", "inspect", "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"];
|
|
32
|
+
function renderprogress(plan, completed) {
|
|
33
|
+
if (!progressnode) return;
|
|
34
|
+
const total = plan.steps.length || 1;
|
|
35
|
+
progressnode.max = total;
|
|
36
|
+
progressnode.value = completed.length;
|
|
37
|
+
progressnode.textContent = `${completed.length} of ${plan.steps.length} reviewed steps executed`;
|
|
38
|
+
}
|
|
39
|
+
function renderoutcome(step, outcomes) {
|
|
40
|
+
const outcome = [...outcomes].reverse().find((item) => item.stepid === step.id && item.ok) ?? [...outcomes].reverse().find((item) => item.stepid === step.id);
|
|
41
|
+
if (!outcome) return null;
|
|
42
|
+
const node = document.createElement("details");
|
|
43
|
+
node.className = "outcome";
|
|
44
|
+
const summary = document.createElement("summary");
|
|
45
|
+
summary.textContent = `${outcome.ok ? "result" : "failure"}: ${outcome.summary}`;
|
|
46
|
+
node.append(summary);
|
|
47
|
+
if (outcome.details && Object.keys(outcome.details).length > 0) {
|
|
48
|
+
const payload = document.createElement("pre");
|
|
49
|
+
payload.textContent = JSON.stringify(outcome.details, null, 2).slice(0, 4e3);
|
|
50
|
+
node.append(payload);
|
|
35
51
|
}
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
52
|
+
return node;
|
|
53
|
+
}
|
|
54
|
+
function steplist(plan, steps, completed, outcomes, risk) {
|
|
55
|
+
const group = steps.filter((step) => step.risk === risk);
|
|
56
|
+
if (group.length === 0) return null;
|
|
57
|
+
const section = document.createElement("section");
|
|
58
|
+
const heading = document.createElement("h3");
|
|
59
|
+
heading.textContent = `${risk} steps`;
|
|
60
|
+
section.append(heading);
|
|
39
61
|
const list = document.createElement("ol");
|
|
40
|
-
for (const step of
|
|
62
|
+
for (const step of group) {
|
|
41
63
|
const item = document.createElement("li");
|
|
42
|
-
|
|
43
|
-
|
|
64
|
+
const done = completed.includes(step.id);
|
|
65
|
+
item.textContent = `${done ? "\u2713" : ""} ${step.summary}`;
|
|
66
|
+
const outcome = renderoutcome(step, outcomes);
|
|
67
|
+
if (outcome) item.append(outcome);
|
|
68
|
+
if (!done && step.target && previewkinds.includes(step.kind) && ["pending", "approved"].includes(plan.state)) item.append(" ", button("Preview current target", async () => {
|
|
44
69
|
const result = await request({ kind: "preview", stepid: step.id });
|
|
45
70
|
status(result.summary);
|
|
46
71
|
}));
|
|
47
|
-
if (plan.state === "approved") item.append(" ", button("Run this reviewed step", async () => {
|
|
72
|
+
if (!done && plan.state === "approved") item.append(" ", button("Run this reviewed step", async () => {
|
|
48
73
|
const result = await request({ kind: "execute", stepid: step.id });
|
|
49
74
|
status(result.summary);
|
|
50
75
|
await refresh();
|
|
51
76
|
}));
|
|
52
77
|
list.append(item);
|
|
53
78
|
}
|
|
54
|
-
|
|
79
|
+
section.append(list);
|
|
80
|
+
return section;
|
|
81
|
+
}
|
|
82
|
+
function renderplan(plan, progress, outcomes = []) {
|
|
83
|
+
if (!planroot) return;
|
|
84
|
+
planroot.replaceChildren();
|
|
85
|
+
if (!plan) {
|
|
86
|
+
planroot.textContent = "Start a session, then request a local or endpoint plan. No task runs before review.";
|
|
87
|
+
if (progressnode) progressnode.value = 0;
|
|
88
|
+
return;
|
|
89
|
+
}
|
|
90
|
+
const title = document.createElement("h2");
|
|
91
|
+
title.textContent = `${plan.state}: ${plan.objective}`;
|
|
92
|
+
planroot.append(title);
|
|
93
|
+
const completed = progress?.planid === plan.id ? progress.completedsteps : [];
|
|
94
|
+
renderprogress(plan, completed);
|
|
95
|
+
const sensitive = steplist(plan, plan.steps, completed, outcomes, "sensitive");
|
|
96
|
+
const interaction = steplist(plan, plan.steps, completed, outcomes, "interaction");
|
|
97
|
+
const read = steplist(plan, plan.steps, completed, outcomes, "read");
|
|
98
|
+
for (const group of [sensitive, interaction, read]) if (group) planroot.append(group);
|
|
55
99
|
if (plan.state === "pending") {
|
|
56
100
|
planroot.append(button("Approve reviewed plan", async () => {
|
|
57
101
|
await request({ kind: "approve" });
|
|
@@ -61,6 +105,19 @@ function renderplan(plan) {
|
|
|
61
105
|
await refresh();
|
|
62
106
|
}));
|
|
63
107
|
}
|
|
108
|
+
if (plan.state === "completed" && plan.completedat) {
|
|
109
|
+
const note = document.createElement("p");
|
|
110
|
+
note.textContent = "Every reviewed step has executed and the plan is closed.";
|
|
111
|
+
planroot.append(note);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function rendercapabilities(report) {
|
|
115
|
+
if (!capabilitiestext) return;
|
|
116
|
+
if (!report) {
|
|
117
|
+
capabilitiestext.textContent = "Optional capabilities unknown.";
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
capabilitiestext.textContent = `tabs ${report.tabs ? "granted" : "absent"} \xB7 downloads ${report.downloads ? "granted" : "absent"} \xB7 clipboard read ${report.clipboardread ? "granted" : "absent"} \xB7 clipboard write ${report.clipboardwrite ? "granted" : "absent"}`;
|
|
64
121
|
}
|
|
65
122
|
function renderaudit(events) {
|
|
66
123
|
if (!auditroot) return;
|
|
@@ -87,10 +144,12 @@ function renderdiagnostic(report) {
|
|
|
87
144
|
}
|
|
88
145
|
async function refresh() {
|
|
89
146
|
const context = await request({ kind: "context" });
|
|
90
|
-
renderplan(context.plan);
|
|
147
|
+
renderplan(context.plan, context.progress, context.outcomes ?? []);
|
|
91
148
|
renderdiagnostic(context.diagnostic);
|
|
92
149
|
renderaudit(context.audit);
|
|
93
|
-
|
|
150
|
+
rendercapabilities(context.capabilities);
|
|
151
|
+
if (context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");
|
|
152
|
+
else status(context.session ? "Active session is visible. The extension is waiting for review." : "No active browser session.");
|
|
94
153
|
}
|
|
95
154
|
async function create(kind) {
|
|
96
155
|
await request({ kind, objective: objective?.value ?? "" });
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../sidepanel.ts"],
|
|
4
|
-
"sourcesContent": ["import type { agentplan, auditevent, diagnosticreport } from \"../types.js\";\n\nconst objective = document.querySelector<HTMLTextAreaElement>(\"#objective\");\nconst localbutton = document.querySelector<HTMLButtonElement>(\"#localplan\");\nconst remotebutton = document.querySelector<HTMLButtonElement>(\"#remoteplan\");\nconst diagnosticbutton = document.querySelector<HTMLButtonElement>(\"#diagnostic\");\nconst planroot = document.querySelector<HTMLElement>(\"#plan\");\nconst auditroot = document.querySelector<HTMLElement>(\"#audit\");\nconst diagnosticroot = document.querySelector<HTMLElement>(\"#diagnostics\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\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; }\nfunction button(label: string, action: () => Promise<void>, disabled = false): HTMLButtonElement { const element = document.createElement(\"button\"); element.type = \"button\"; element.textContent = label; element.disabled = disabled; element.addEventListener(\"click\", () => action().catch(error => status(error instanceof Error ? error.message : String(error), true))); return element; }\nfunction
|
|
5
|
-
"mappings": ";AAEA,IAAM,YAAY,SAAS,cAAmC,YAAY;AAC1E,IAAM,cAAc,SAAS,cAAiC,YAAY;AAC1E,IAAM,eAAe,SAAS,cAAiC,aAAa;AAC5E,IAAM,mBAAmB,SAAS,cAAiC,aAAa;AAChF,IAAM,WAAW,SAAS,cAA2B,OAAO;AAC5D,IAAM,YAAY,SAAS,cAA2B,QAAQ;AAC9D,IAAM,iBAAiB,SAAS,cAA2B,cAAc;AACzE,IAAM,aAAa,SAAS,cAA2B,SAAS;
|
|
4
|
+
"sourcesContent": ["import type { agentplan, auditevent, capabilityreport, diagnosticreport, planprogress, stepoutcome, toolstep } from \"../types.js\";\n\nconst objective = document.querySelector<HTMLTextAreaElement>(\"#objective\");\nconst localbutton = document.querySelector<HTMLButtonElement>(\"#localplan\");\nconst remotebutton = document.querySelector<HTMLButtonElement>(\"#remoteplan\");\nconst diagnosticbutton = document.querySelector<HTMLButtonElement>(\"#diagnostic\");\nconst planroot = document.querySelector<HTMLElement>(\"#plan\");\nconst auditroot = document.querySelector<HTMLElement>(\"#audit\");\nconst diagnosticroot = document.querySelector<HTMLElement>(\"#diagnostics\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst progressnode = document.querySelector<HTMLProgressElement>(\"#planprogress\");\nconst capabilitiestext = document.querySelector<HTMLElement>(\"#capabilitiestext\");\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; }\nfunction button(label: string, action: () => Promise<void>, disabled = false): HTMLButtonElement { const element = document.createElement(\"button\"); element.type = \"button\"; element.textContent = label; element.disabled = disabled; element.addEventListener(\"click\", () => action().catch(error => status(error instanceof Error ? error.message : String(error), true))); return element; }\nconst previewkinds = [\"focus\", \"inspect\", \"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\"];\n\n/** Renders plan completion as a live progress ratio. */\nfunction renderprogress(plan: agentplan, completed: string[]): void {\n if (!progressnode) return;\n const total = plan.steps.length || 1;\n progressnode.max = total;\n progressnode.value = completed.length;\n progressnode.textContent = `${completed.length} of ${plan.steps.length} reviewed steps executed`;\n}\n\n/** Renders the latest structured outcome of one step beside its review entry. */\nfunction renderoutcome(step: toolstep, outcomes: stepoutcome[]): HTMLElement | null {\n const outcome = [...outcomes].reverse().find(item => item.stepid === step.id && item.ok) ?? [...outcomes].reverse().find(item => item.stepid === step.id);\n if (!outcome) return null;\n const node = document.createElement(\"details\");\n node.className = \"outcome\";\n const summary = document.createElement(\"summary\");\n summary.textContent = `${outcome.ok ? \"result\" : \"failure\"}: ${outcome.summary}`;\n node.append(summary);\n if (outcome.details && Object.keys(outcome.details).length > 0) {\n const payload = document.createElement(\"pre\");\n payload.textContent = JSON.stringify(outcome.details, null, 2).slice(0, 4000);\n node.append(payload);\n }\n return node;\n}\n\nfunction steplist(plan: agentplan, steps: toolstep[], completed: string[], outcomes: stepoutcome[], risk: toolstep[\"risk\"]): HTMLElement | null {\n const group = steps.filter(step => step.risk === risk);\n if (group.length === 0) return null;\n const section = document.createElement(\"section\");\n const heading = document.createElement(\"h3\");\n heading.textContent = `${risk} steps`;\n section.append(heading);\n const list = document.createElement(\"ol\");\n for (const step of group) {\n const item = document.createElement(\"li\");\n const done = completed.includes(step.id);\n item.textContent = `${done ? \"\u2713\" : \"\"} ${step.summary}`;\n const outcome = renderoutcome(step, outcomes);\n if (outcome) item.append(outcome);\n if (!done && step.target && previewkinds.includes(step.kind) && [\"pending\", \"approved\"].includes(plan.state)) item.append(\" \", button(\"Preview current target\", async () => { const result = await request({ kind: \"preview\", stepid: step.id }) as { summary: string }; status(result.summary); }));\n if (!done && plan.state === \"approved\") item.append(\" \", button(\"Run this reviewed step\", async () => { const result = await request({ kind: \"execute\", stepid: step.id }) as { summary: string }; status(result.summary); await refresh(); }));\n list.append(item);\n }\n section.append(list);\n return section;\n}\n\nfunction renderplan(plan?: agentplan, progress?: planprogress, outcomes: stepoutcome[] = []): void {\n if (!planroot) return;\n planroot.replaceChildren();\n if (!plan) { planroot.textContent = \"Start a session, then request a local or endpoint plan. No task runs before review.\"; if (progressnode) progressnode.value = 0; return; }\n const title = document.createElement(\"h2\"); title.textContent = `${plan.state}: ${plan.objective}`; planroot.append(title);\n const completed = progress?.planid === plan.id ? progress.completedsteps : [];\n renderprogress(plan, completed);\n const sensitive = steplist(plan, plan.steps, completed, outcomes, \"sensitive\");\n const interaction = steplist(plan, plan.steps, completed, outcomes, \"interaction\");\n const read = steplist(plan, plan.steps, completed, outcomes, \"read\");\n for (const group of [sensitive, interaction, read]) if (group) planroot.append(group);\n if (plan.state === \"pending\") { planroot.append(button(\"Approve reviewed plan\", async () => { await request({ kind: \"approve\" }); await refresh(); }), button(\"Reject plan\", async () => { await request({ kind: \"reject\" }); await refresh(); })); }\n if (plan.state === \"completed\" && plan.completedat) { const note = document.createElement(\"p\"); note.textContent = \"Every reviewed step has executed and the plan is closed.\"; planroot.append(note); }\n}\n\nfunction rendercapabilities(report?: capabilityreport): void {\n if (!capabilitiestext) return;\n if (!report) { capabilitiestext.textContent = \"Optional capabilities unknown.\"; return; }\n capabilitiestext.textContent = `tabs ${report.tabs ? \"granted\" : \"absent\"} \u00B7 downloads ${report.downloads ? \"granted\" : \"absent\"} \u00B7 clipboard read ${report.clipboardread ? \"granted\" : \"absent\"} \u00B7 clipboard write ${report.clipboardwrite ? \"granted\" : \"absent\"}`;\n}\n\nfunction renderaudit(events: auditevent[]): void { if (!auditroot) return; auditroot.replaceChildren(); for (const event of events.slice(0, 12)) { const item = document.createElement(\"li\"); item.textContent = `${new Date(event.at).toLocaleTimeString()} \u00B7 ${event.kind} \u00B7 ${event.summary}`; auditroot.append(item); } }\nfunction renderdiagnostic(report?: diagnosticreport): void { if (!diagnosticroot) return; diagnosticroot.replaceChildren(); if (!report) { diagnosticroot.textContent = \"Run a local diagnostic after starting a session to record bridge and page-shape health.\"; return; } const values = [`origin: ${report.origin}`, `title: ${report.title || \"untitled\"}`, `interactive elements: ${report.interactivecount}`, `forms: ${report.formcount}`, `page text length: ${report.textlength}`, `bridge available: ${report.bridgeavailable ? \"yes\" : \"no\"}`]; for (const value of values) { const item = document.createElement(\"li\"); item.textContent = value; diagnosticroot.append(item); } }\nasync function refresh(): Promise<void> { const context = await request({ kind: \"context\" }) as { plan?: agentplan; progress?: planprogress; diagnostic?: diagnosticreport; audit: auditevent[]; outcomes?: stepoutcome[]; capabilities?: capabilityreport; session?: { id: string; pausedat?: number } }; renderplan(context.plan, context.progress, context.outcomes ?? []); renderdiagnostic(context.diagnostic); renderaudit(context.audit); rendercapabilities(context.capabilities); if (context.session?.pausedat) status(\"Session paused. Reviewed actions are blocked until resume.\"); else status(context.session ? \"Active session is visible. The extension is waiting for review.\" : \"No active browser session.\"); }\nasync function create(kind: \"proposelocal\" | \"proposeremote\"): Promise<void> { await request({ kind, objective: objective?.value ?? \"\" }); await refresh(); }\nlocalbutton?.addEventListener(\"click\", () => create(\"proposelocal\").catch(error => status(error instanceof Error ? error.message : String(error), true)));\nremotebutton?.addEventListener(\"click\", () => create(\"proposeremote\").catch(error => status(error instanceof Error ? error.message : String(error), true)));\ndiagnosticbutton?.addEventListener(\"click\", () => request({ kind: \"diagnostic\" }).then(() => refresh()).catch(error => status(error instanceof Error ? error.message : String(error), true)));\nrefresh().catch(error => status(error instanceof Error ? error.message : String(error), true));\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
6
|
"names": []
|
|
7
7
|
}
|
package/extension/dist/style.css
CHANGED
|
@@ -7,3 +7,4 @@ label { display: block; font-size: 12px; color: var(--muted); margin: 14px 0 6px
|
|
|
7
7
|
button { min-height: 40px; margin-top: 10px; border: 1px solid transparent; border-radius: 9px; padding: 0 12px; color: #06120f; background: var(--accent); font-weight: 750; cursor: pointer; } button:focus-visible, input:focus-visible, textarea:focus-visible { outline: 3px solid #8fb9ff; outline-offset: 2px; } button:disabled { cursor: not-allowed; opacity: .5; } button.secondary { background: transparent; color: var(--text); border-color: var(--line); } button.danger { background: transparent; color: var(--danger); border-color: color-mix(in srgb, var(--danger) 50%, transparent); }
|
|
8
8
|
.actions { display: flex; flex-wrap: wrap; gap: 8px; } .actions button { flex: 1 1 135px; } #status { min-height: 44px; border-left: 3px solid var(--accent); padding-left: 10px; font-size: 13px; } #status[data-state="error"] { border-color: var(--danger); color: #ffc2cc; }
|
|
9
9
|
section { margin-top: 20px; } .panel, .audit { margin: 0; border: 1px solid var(--line); background: color-mix(in srgb, var(--panel) 88%, transparent); border-radius: 12px; padding: 14px; } ol { padding-left: 24px; } li { color: var(--muted); line-height: 1.5; margin-bottom: 11px; } li button { margin-left: 7px; min-height: 32px; font-size: 12px; } .audit { list-style: none; padding-left: 14px; }
|
|
10
|
+
.muted { color: var(--muted); font-size: 12px; margin: 8px 0 0; } #planprogress { width: 100%; height: 8px; margin-bottom: 10px; accent-color: var(--accent); } details.outcome { margin: 6px 0 0; font-size: 12px; } details.outcome summary { color: var(--accent); cursor: pointer; } details.outcome pre { margin: 6px 0 0; padding: 8px; border: 1px solid var(--line); border-radius: 8px; background: #0c1321; color: var(--muted); overflow-x: auto; max-height: 220px; overflow-y: auto; font-size: 11px; } section h3 { font-size: 12px; letter-spacing: .08em; text-transform: uppercase; color: var(--muted); margin: 14px 0 6px; }
|
package/extension/manifest.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"manifest_version": 3,
|
|
3
3
|
"key": "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAnOEjO8Z0PDgQyfvawGcaO2j+o0GLCFTLNj7TkYC/Avo9l2NenMRq7gp90Nfd7E9MViv/OMcCKSYZ5unv12QPRtv31C+a5UQWDFAOP/cH5mwMd6hsayElrSoW8ta+FwFqmr9dIFkn7cQEU3YhZr4Gcbs+ycUHOxVgDA4NBKB0rQ6e9VW5LvTw0isRYUrqM+M72vKxHk9zUIYYn/LGPvottKBYi2GLr0PHSeC2UE+Shmq7vcFIXj6hDjvD4kLJ5sKoUllEcZ1TPuBcnHUQ9ndKA5iktXDQOIJCUJmi7a0YJ2PGg7fvpYfT9k0ai/qZ+pIoRfoOEwE01bPoDn7NjeYnNQIDAQAB",
|
|
4
4
|
"name": "Devthink",
|
|
5
|
-
"version": "1.1.
|
|
5
|
+
"version": "1.1.32",
|
|
6
6
|
"description": "A consent-first bridge for reviewed browser-agent tasks.",
|
|
7
7
|
"permissions": [
|
|
8
8
|
"activeTab",
|
|
@@ -10,6 +10,12 @@
|
|
|
10
10
|
"scripting",
|
|
11
11
|
"sidePanel"
|
|
12
12
|
],
|
|
13
|
+
"optional_permissions": [
|
|
14
|
+
"tabs",
|
|
15
|
+
"downloads",
|
|
16
|
+
"clipboardRead",
|
|
17
|
+
"clipboardWrite"
|
|
18
|
+
],
|
|
13
19
|
"optional_host_permissions": [
|
|
14
20
|
"https://*/*"
|
|
15
21
|
],
|