@wenathlan/extension 1.1.31 → 1.1.33

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../../policy.ts", "../popup.ts"],
4
- "sourcesContent": ["import type { actionkind, agentplan, agentsession, endpointconfig, policyevaluation, toolstep } from \"./types.js\";\n\nconst sensitiveactions = new Set<actionkind>([\"click\", \"type\", \"navigate\", \"select\"]);\nconst interactionactions = new Set<actionkind>([\"focus\", \"scroll\", \"hover\"]);\nconst allowedactions = new Set<actionkind>([\"observe\", \"inspect\", \"focus\", \"click\", \"type\", \"navigate\", \"scroll\", \"select\", \"hover\", \"extract\", \"wait\"]);\nconst targetactions = new Set<actionkind>([\"inspect\", \"focus\", \"click\", \"type\", \"scroll\", \"select\", \"hover\"]);\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 interactionactions.has(kind) ? \"interaction\" : \"read\";\n}\n\n/** Parses the bounded pause duration of a wait step. */\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 Math.min(requested, 10_000);\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 (step.kind === \"select\" && !step.value?.trim()) return { allowed: false, reason: \"A reviewed option value is required.\" };\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 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/** 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 (![\"focus\", \"inspect\", \"click\", \"type\", \"scroll\", \"select\", \"hover\"].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 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\nasync function restore(): Promise<void> {\n const context = await request({ kind: \"context\" }) as { config?: { endpoint: string }; session?: { stoppedat?: number; pausedat?: 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 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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",
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\", \"clickpoint\", \"shiftclick\", \"dismissdialog\", \"enterframe\", \"typetime\", \"appendtext\", \"setvalue\", \"typeedit\", \"keyhold\", \"keyrelease\", \"submitsearch\", \"selectmulti\", \"chooseradio\", \"setslider\", \"setdate\", \"setcolor\"]);\nconst interactionactions = new Set<actionkind>([\"focus\", \"scroll\", \"hover\", \"clickdeep\", \"rightclick\", \"doubleclick\", \"scrollpage\", \"scrollby\", \"scrollend\", \"scrolltop\", \"fullscreen\", \"zoomset\", \"movepointer\", \"clicktext\", \"clickaria\", \"clickname\", \"expanddetails\", \"pierceshadow\", \"retryaction\"]);\nconst readactions = new Set<actionkind>([\"observe\", \"inspect\", \"extract\", \"wait\", \"waitfor\", \"waittext\", \"readattribute\", \"readstyle\", \"readgeometry\", \"readvalue\", \"readtext\", \"readhtml\", \"countelements\", \"readtable\", \"readlinks\", \"readimages\", \"readmeta\", \"readforms\", \"readstorage\", \"highlight\", \"tablist\", \"windowlist\", \"tabsnapshot\", \"mapclicks\", \"verifyvisible\", \"verifyenabled\", \"resolvexpath\"]);\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\", \"shiftclick\", \"typetime\", \"appendtext\", \"setvalue\", \"typeedit\", \"submitsearch\", \"selectmulti\", \"chooseradio\", \"setslider\", \"setdate\", \"setcolor\", \"expanddetails\", \"verifyvisible\", \"verifyenabled\", \"pierceshadow\"]);\nconst valueactions = new Set<actionkind>([\"presskey\", \"drag\", \"drop\", \"upload\", \"readattribute\", \"removeattribute\", \"waittext\", \"evaluate\", \"zoomset\", \"tabactivate\", \"tabclose\", \"tabreload\", \"windowclose\", \"windowresize\", \"tabcreate\", \"windowcreate\", \"downloadfile\", \"typetime\", \"appendtext\", \"setvalue\", \"typeedit\", \"keyhold\", \"keyrelease\", \"chooseradio\", \"setslider\", \"setdate\", \"setcolor\"]);\n\n/** Normalizes a user supplied HTTPS endpoint without preserving a provider lock-in. */\nexport function normalizeendpoint(value: string): endpointconfig {\n const endpoint = new URL(value.trim());\n if (endpoint.protocol !== \"https:\") throw new Error(\"Devthink accepts HTTPS endpoints only.\");\n if (endpoint.username || endpoint.password) throw new Error(\"Endpoint credentials are not allowed in the URL.\");\n return { endpoint: endpoint.toString(), origin: endpoint.origin, configuredat: Date.now() };\n}\n\n/** Creates the exact optional host pattern requested from Chromium. */\nexport function hostpattern(origin: string): string {\n const parsed = new URL(origin);\n if (parsed.protocol !== \"https:\") throw new Error(\"Only HTTPS origins can be granted.\");\n return `${parsed.origin}/*`;\n}\n\n/** Defines action risk from the fixed local allowlist. */\nexport function actionrisk(kind: actionkind): \"read\" | \"interaction\" | \"sensitive\" {\n if (!allowedactions.has(kind)) throw new Error(\"Unsupported browser action.\");\n if (sensitiveactions.has(kind)) return \"sensitive\";\n return interactionactions.has(kind) ? \"interaction\" : \"read\";\n}\n\n/** True when the action kind accepts a css selector target or a reviewed targetref. */\nexport function needstarget(kind: actionkind): boolean {\n return targetactions.has(kind);\n}\n\n/** Parses the reviewed JSON options of a step; malformed payloads are rejected early. */\nexport function parseoptions(step: toolstep): Record<string, unknown> {\n if (step.options === undefined) return {};\n let parsed: unknown;\n try { parsed = JSON.parse(step.options); } catch { throw new Error(\"Step options must be a JSON object.\"); }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) throw new Error(\"Step options must be a JSON object.\");\n return parsed as Record<string, unknown>;\n}\n\n/** Maps an action kind to the optional browser permission it requires, if any. */\nexport function requiredcapability(kind: actionkind): string | undefined {\n if (kind === \"tablist\") return \"tabs\";\n if (kind === \"downloadfile\") return \"downloads\";\n return undefined;\n}\n\n/** Parses the reviewed wait duration of a wait step with no upper bound. */\nexport function waitduration(step: toolstep): number {\n const requested = step.value ? Number.parseInt(step.value, 10) : 250;\n if (!Number.isFinite(requested) || requested < 0) throw new Error(\"Wait duration must be zero or a positive number of milliseconds.\");\n return requested;\n}\n\nfunction isnumericid(value: unknown): value is string {\n return typeof value === \"string\" && /^\\d+$/.test(value);\n}\n\nfunction numericoption(options: Record<string, unknown>, key: string): boolean {\n return options[key] === undefined || (typeof options[key] === \"number\" && Number.isFinite(options[key] as number));\n}\n\n/** True when an optional numeric option is absent or a finite number of zero or more. */\nfunction nonnegativeoption(options: Record<string, unknown>, key: string): boolean {\n return numericoption(options, key) && !(typeof options[key] === \"number\" && (options[key] as number) < 0);\n}\n\nfunction isnonempty(value: unknown): value is string {\n return typeof value === \"string\" && value.trim().length > 0;\n}\n\nfunction ispoint(value: unknown): boolean {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return false;\n const point = value as Record<string, unknown>;\n return typeof point.x === \"number\" && Number.isFinite(point.x) && typeof point.y === \"number\" && Number.isFinite(point.y);\n}\n\n/** Grades a resolution match count: zero is absent, one is resolved and more than one is refused as ambiguous. */\nexport function resolutionverdict(count: number): \"absent\" | \"resolved\" | \"ambiguous\" {\n if (!Number.isFinite(count) || count <= 0) return \"absent\";\n return count === 1 ? \"resolved\" : \"ambiguous\";\n}\n\n/** Validates the reviewed targetref grammar of every resolution mode and rejects empty references. */\nexport function validatetargetref(reference: unknown): policyevaluation {\n if (!reference || typeof reference !== \"object\" || Array.isArray(reference)) return { allowed: false, reason: \"The reviewed target reference must be an object.\" };\n const ref = reference as Record<string, unknown>;\n if (ref.mode === \"selector\") return isnonempty(ref.selector) ? { allowed: true } : { allowed: false, reason: \"The selector target reference needs a non-empty selector.\" };\n if (ref.mode === \"text\") return isnonempty(ref.text) ? { allowed: true } : { allowed: false, reason: \"The text target reference needs non-empty text.\" };\n if (ref.mode === \"aria\") {\n if (!isnonempty(ref.role)) return { allowed: false, reason: \"The aria target reference needs a non-empty role.\" };\n return isnonempty(ref.name) ? { allowed: true } : { allowed: false, reason: \"The aria target reference needs a non-empty name.\" };\n }\n if (ref.mode === \"name\") return isnonempty(ref.name) ? { allowed: true } : { allowed: false, reason: \"The name target reference needs a non-empty name.\" };\n if (ref.mode === \"xpath\") return isnonempty(ref.xpath) ? { allowed: true } : { allowed: false, reason: \"The xpath target reference needs a non-empty expression.\" };\n if (ref.mode === \"index\") {\n const index = ref.index;\n return typeof index === \"number\" && Number.isInteger(index) && index >= 1 ? { allowed: true } : { allowed: false, reason: \"The index target reference needs a positive integer map number.\" };\n }\n if (ref.mode === \"point\") {\n const pointok = typeof ref.x === \"number\" && Number.isFinite(ref.x) && typeof ref.y === \"number\" && Number.isFinite(ref.y);\n return pointok ? { allowed: true } : { allowed: false, reason: \"The point target reference needs numeric x and y coordinates.\" };\n }\n return { allowed: false, reason: \"The target reference mode must be selector, text, aria, name, xpath, index or point.\" };\n}\n\n/** True when the session origin grants cover the given origin; a session without grants only allows its own origin. */\nexport function origingranted(session: agentsession | undefined, origin: string): boolean {\n if (!session) return false;\n const grants = session.grants ?? [session.origin];\n return grants.includes(origin);\n}\n\n/** Validates the reviewed inner step of a retry or frame wrapper against the same rules as a top-level step. */\nfunction validateinnerstep(options: Record<string, unknown>, origin: string): policyevaluation {\n const stepid = options.stepid;\n const kind = options.kind;\n if (isnonempty(stepid)) {\n if (kind !== undefined) return { allowed: false, reason: \"The reviewed wrapper must reference a step id or an inline step, not both.\" };\n return { allowed: true };\n }\n if (typeof kind !== \"string\" || !kind.trim()) return { allowed: false, reason: \"A reviewed step id or inline step kind is required in options.\" };\n if (kind === \"retryaction\" || kind === \"enterframe\") return { allowed: false, reason: \"The reviewed inner step cannot be another wrapper kind.\" };\n if (!allowedactions.has(kind as actionkind)) return { allowed: false, reason: \"The reviewed inner step kind is unsupported.\" };\n const inneroptions = options.options;\n if (inneroptions !== undefined && (!inneroptions || typeof inneroptions !== \"object\" || Array.isArray(inneroptions))) return { allowed: false, reason: \"The reviewed inner step options must be an object.\" };\n const inner: toolstep = {\n id: \"inner\",\n kind: kind as actionkind,\n summary: \"Reviewed inner step.\",\n risk: actionrisk(kind as actionkind),\n ...(isnonempty(options.target) ? { target: options.target } : {}),\n ...(isnonempty(options.value) ? { value: options.value } : {}),\n ...(inneroptions !== undefined ? { options: JSON.stringify(inneroptions) } : {}),\n };\n return validatestep(inner, origin);\n}\n\n/** Validates a single proposal against the active tab origin and local policy. */\nexport function validatestep(step: toolstep, origin: string): policyevaluation {\n if (!allowedactions.has(step.kind)) return { allowed: false, reason: \"Unsupported action kind.\" };\n if (!step.summary.trim()) return { allowed: false, reason: \"A human-readable action summary is required.\" };\n let options: Record<string, unknown>;\n try { options = parseoptions(step); } catch { return { allowed: false, reason: \"Step options must be a JSON object.\" }; }\n const hastargetref = options.targetref !== undefined;\n if (targetactions.has(step.kind) && !step.target?.trim() && !hastargetref) return { allowed: false, reason: \"A page target is required.\" };\n if (valueactions.has(step.kind) && !step.value?.trim()) return { allowed: false, reason: \"A reviewed value is required.\" };\n if (step.kind === \"select\" && !step.value?.trim()) return { allowed: false, reason: \"A reviewed option value is required.\" };\n if (step.kind === \"navigate\" && !step.value) return { allowed: false, reason: \"A navigation URL is required.\" };\n if (hastargetref) {\n const reference = validatetargetref(options.targetref);\n if (!reference.allowed) return reference;\n }\n if (step.kind === \"wait\") {\n try { waitduration(step); } catch { return { allowed: false, reason: \"Wait duration must be zero or a positive number of milliseconds.\" }; }\n }\n if (step.kind === \"navigate\") {\n try {\n if (new URL(step.value ?? \"\").origin !== origin) return { allowed: false, reason: \"Navigation must remain within the approved origin.\" };\n } catch {\n return { allowed: false, reason: \"Navigation URL is invalid.\" };\n }\n }\n if (step.kind === \"tabcreate\" || step.kind === \"windowcreate\" || step.kind === \"downloadfile\") {\n try {\n const url = new URL(step.value ?? \"\");\n if (url.protocol !== \"https:\") return { allowed: false, reason: \"The reviewed URL must use HTTPS.\" };\n } catch {\n return { allowed: false, reason: \"The reviewed URL is invalid.\" };\n }\n }\n if (step.kind === \"tabactivate\" || step.kind === \"tabclose\" || step.kind === \"tabreload\" || step.kind === \"windowclose\" || step.kind === \"windowresize\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser id is required.\" };\n }\n if (step.kind === \"zoomset\") {\n const zoom = Number(step.value);\n if (!Number.isFinite(zoom) || zoom <= 0) return { allowed: false, reason: \"The reviewed zoom must be a positive number.\" };\n }\n if (step.kind === \"setattribute\" || step.kind === \"writestorage\") {\n const keyname = step.kind === \"setattribute\" ? \"name\" : \"key\";\n if (typeof options[keyname] !== \"string\" || !(options[keyname] as string).trim()) return { allowed: false, reason: `A reviewed ${keyname} is required in options.` };\n if (typeof options.value !== \"string\") return { allowed: false, reason: \"A reviewed value is required in options.\" };\n }\n if (step.kind === \"windowresize\") {\n if (typeof options.width !== \"number\" || typeof options.height !== \"number\" || !Number.isFinite(options.width) || !Number.isFinite(options.height)) return { allowed: false, reason: \"Reviewed width and height numbers are required in options.\" };\n }\n if ((step.kind === \"scrollpage\" || step.kind === \"scrollby\") && (!numericoption(options, \"x\") || !numericoption(options, \"y\"))) return { allowed: false, reason: \"Scroll amounts must be numbers in options.\" };\n if (step.kind === \"waitfor\" && options.timeout !== undefined && (typeof options.timeout !== \"number\" || options.timeout < 0)) return { allowed: false, reason: \"The waitfor timeout must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"movepointer\") {\n const path = options.pointpath;\n if (!path || typeof path !== \"object\" || Array.isArray(path)) return { allowed: false, reason: \"A reviewed pointpath with start and end points is required in options.\" };\n const points = path as Record<string, unknown>;\n if (!ispoint(points.start) || !ispoint(points.end)) return { allowed: false, reason: \"The reviewed pointpath needs numeric start and end points.\" };\n if (points.waypoints !== undefined && (!Array.isArray(points.waypoints) || !points.waypoints.every(waypoint => ispoint(waypoint)))) return { allowed: false, reason: \"The reviewed pointpath waypoints must be numeric points.\" };\n if (!nonnegativeoption(points, \"duration\")) return { allowed: false, reason: \"The reviewed pointpath duration must be zero or a positive number of milliseconds.\" };\n const speed = options.speedprofile;\n if (speed !== undefined) {\n if (!speed || typeof speed !== \"object\" || Array.isArray(speed)) return { allowed: false, reason: \"The reviewed speed profile must be an object.\" };\n const profile = speed as Record<string, unknown>;\n if (profile.easing !== undefined && profile.easing !== \"linear\" && profile.easing !== \"easeinout\") return { allowed: false, reason: \"The reviewed easing must be linear or easeinout.\" };\n if (!nonnegativeoption(profile, \"peak\")) return { allowed: false, reason: \"The reviewed peak velocity must be zero or a positive number.\" };\n if (!nonnegativeoption(profile, \"jitter\")) return { allowed: false, reason: \"The reviewed jitter window must be zero or a positive number of milliseconds.\" };\n }\n }\n if (step.kind === \"clickpoint\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"point\")) return { allowed: false, reason: \"A reviewed point target reference is required in options.\" };\n if (step.kind === \"clicktext\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"text\")) return { allowed: false, reason: \"A reviewed text target reference is required in options.\" };\n if (step.kind === \"clickaria\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"aria\")) return { allowed: false, reason: \"A reviewed aria target reference is required in options.\" };\n if (step.kind === \"clickname\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"name\")) return { allowed: false, reason: \"A reviewed name target reference is required in options.\" };\n if (step.kind === \"resolvexpath\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"xpath\")) return { allowed: false, reason: \"A reviewed xpath target reference is required in options.\" };\n if (step.kind === \"typetime\" && options.delay !== undefined && (typeof options.delay !== \"number\" || !Number.isFinite(options.delay) || options.delay < 0)) return { allowed: false, reason: \"The reviewed per keystroke delay must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"submitsearch\") {\n if (!isnonempty(options.results)) return { allowed: false, reason: \"A reviewed results region selector is required in options.\" };\n if (options.timeout !== undefined && (typeof options.timeout !== \"number\" || !Number.isFinite(options.timeout) || options.timeout < 0)) return { allowed: false, reason: \"The submitsearch timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"selectmulti\") {\n const values = options.values;\n if (!Array.isArray(values) || values.length === 0 || !values.every(value => isnonempty(value))) return { allowed: false, reason: \"A reviewed list of option values is required in options.\" };\n }\n if (step.kind === \"setslider\") {\n const slider = Number(step.value);\n if (!Number.isFinite(slider)) return { allowed: false, reason: \"The reviewed slider value must be a number.\" };\n }\n if (step.kind === \"setdate\" && !/^\\d{4}-\\d{2}-\\d{2}$/.test(step.value ?? \"\")) return { allowed: false, reason: \"The reviewed date must use the yyyy-mm-dd form.\" };\n if (step.kind === \"setcolor\" && !/^#[0-9a-fA-F]{6}$/.test(step.value ?? \"\")) return { allowed: false, reason: \"The reviewed color must use the #rrggbb form.\" };\n if (step.kind === \"keyhold\" && options.holdid !== undefined && !isnonempty(options.holdid)) return { allowed: false, reason: \"The reviewed hold id must be a non-empty string.\" };\n if (step.kind === \"dismissdialog\") {\n const accept = options.accept;\n const answer = options.answer;\n if (accept === undefined && !isnonempty(answer)) return { allowed: false, reason: \"A reviewed accept flag or prompt answer is required in options.\" };\n if (accept !== undefined && typeof accept !== \"boolean\") return { allowed: false, reason: \"The reviewed dialog accept flag must be a boolean.\" };\n if (answer !== undefined && !isnonempty(answer)) return { allowed: false, reason: \"The reviewed prompt answer must be a non-empty string.\" };\n }\n if (step.kind === \"pierceshadow\" && options.shadow !== undefined) {\n if (!Array.isArray(options.shadow) || !options.shadow.every(item => isnonempty(item))) return { allowed: false, reason: \"The reviewed shadow path must be a list of non-empty selectors.\" };\n }\n if (step.kind === \"enterframe\") {\n const path = options.framepath;\n if (!Array.isArray(path) || path.length === 0 || !path.every(item => typeof item === \"number\" && Number.isInteger(item) && item >= 0)) return { allowed: false, reason: \"A reviewed frame path of frame indexes is required in options.\" };\n return validateinnerstep(options, origin);\n }\n if (step.kind === \"retryaction\") {\n const inner = validateinnerstep(options, origin);\n if (!inner.allowed) return inner;\n const rule = options.retryrule;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule)) return { allowed: false, reason: \"A reviewed retry rule with attempts is required in options.\" };\n const retry = rule as Record<string, unknown>;\n if (typeof retry.attempts !== \"number\" || !Number.isInteger(retry.attempts) || retry.attempts < 1) return { allowed: false, reason: \"The reviewed retry attempts must be a positive integer with no code ceiling.\" };\n if (!nonnegativeoption(retry, \"settle\")) return { allowed: false, reason: \"The reviewed retry settle window must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(retry, \"tolerance\")) return { allowed: false, reason: \"The reviewed retry movement tolerance must be zero or a positive number of pixels.\" };\n }\n return { allowed: true };\n}\n\n/** Shared session gate: a live, unpaused session that still matches the active tab. */\nfunction sessiongate(input: { session: agentsession | undefined; tabid: number; origin: string; now: number; action: string }): policyevaluation {\n if (!input.session || input.session.stoppedat) return { allowed: false, reason: \"No active browser session exists.\" };\n if (input.session.expiresat <= input.now) return { allowed: false, reason: \"The browser session has expired.\" };\n if (input.session.pausedat) return { allowed: false, reason: `The browser session is paused and cannot ${input.action}.` };\n if (input.session.tabid !== input.tabid || input.session.origin !== input.origin) return { allowed: false, reason: `The ${input.action} is outside the approved tab or origin.` };\n return { allowed: true };\n}\n\n/** Applies the consent gate immediately before an action reaches the page bridge. */\nexport function canexecute(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now?: number }): policyevaluation {\n const now = input.now ?? Date.now();\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"execute an action\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"The plan has not received explicit approval.\" };\n if (input.plan.expiresat <= now) return { allowed: false, reason: \"The approved plan has expired.\" };\n if ((input.step.kind === \"pierceshadow\" || input.step.kind === \"enterframe\") && !origingranted(input.session, input.origin)) return { allowed: false, reason: \"The shadow or frame step is outside the session origin grants.\" };\n return validatestep(input.step, input.origin);\n}\n\n/** Allows a non-mutating, temporary target preview during plan review. */\nexport function canpreview(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now?: number }): policyevaluation {\n const now = input.now ?? Date.now();\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"preview a target\" });\n if (!gate.allowed) return gate;\n if (!input.plan || ![\"pending\", \"approved\"].includes(input.plan.state)) return { allowed: false, reason: \"Only a reviewed pending or approved plan can be previewed.\" };\n if (input.plan.expiresat <= now) return { allowed: false, reason: \"The reviewed plan has expired.\" };\n let options: Record<string, unknown> = {};\n try { options = parseoptions(input.step); } catch { options = {}; }\n if (!targetactions.has(input.step.kind) && options.targetref === undefined) return { allowed: false, reason: \"Only a target-based action can be previewed.\" };\n return validatestep(input.step, input.origin);\n}\n", "import { hostpattern, normalizeendpoint } from \"../policy.js\";\nimport type { capabilityreport, dialogdecision, keyholdstate } from \"../types.js\";\n\nconst endpointinput = document.querySelector<HTMLInputElement>(\"#endpoint\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst capabilitiesnode = document.querySelector<HTMLElement>(\"#capabilities\");\nconst livenode = document.querySelector<HTMLElement>(\"#livelogic\");\nconst connectbutton = document.querySelector<HTMLButtonElement>(\"#connect\");\nconst sessionbutton = document.querySelector<HTMLButtonElement>(\"#session\");\nconst pausebutton = document.querySelector<HTMLButtonElement>(\"#pause\");\nconst stopbutton = document.querySelector<HTMLButtonElement>(\"#stop\");\nconst openbutton = document.querySelector<HTMLButtonElement>(\"#openpanel\");\n\nfunction status(message: string, error = false): void { if (statusnode) { statusnode.textContent = message; statusnode.dataset.state = error ? \"error\" : \"ready\"; } }\nasync function request(message: unknown): Promise<unknown> { const response = await chrome.runtime.sendMessage(message) as { ok: boolean; value?: unknown; error?: string }; if (!response.ok) throw new Error(response.error); return response.value; }\n\nfunction pauselabel(paused: boolean): string { return paused ? \"Resume session\" : \"Pause session\"; }\n\n/** Renders the live optional capability set the user has granted. */\nfunction rendercapabilities(report?: capabilityreport): void {\n if (!capabilitiesnode) return;\n if (!report) { capabilitiesnode.textContent = \"Capabilities unknown.\"; return; }\n const granted = [`tabs ${report.tabs ? \"granted\" : \"absent\"}`, `downloads ${report.downloads ? \"granted\" : \"absent\"}`, `clipboard read ${report.clipboardread ? \"granted\" : \"absent\"}`, `clipboard write ${report.clipboardwrite ? \"granted\" : \"absent\"}`];\n capabilitiesnode.textContent = `Optional capabilities: ${granted.join(\" \u00B7 \")}.`;\n}\n\n/** Renders the currently held keys and the dialogs answered by the reviewed policy. */\nfunction renderlivestate(holds?: keyholdstate[], dialogs?: dialogdecision[]): void {\n if (!livenode) return;\n const held = (holds ?? []).filter(hold => hold.releasedat === undefined);\n const heldsummary = held.length > 0 ? held.map(hold => `${hold.key}${hold.holdid ? ` (${hold.holdid})` : \"\"}`).join(\", \") : \"none\";\n const dialogsummary = (dialogs ?? []).length > 0 ? `${dialogs?.length} answered` : \"none\";\n livenode.textContent = `Held keys: ${heldsummary} \u00B7 Open dialogs answered: ${dialogsummary}.`;\n}\n\nasync function restore(): Promise<void> {\n const context = await request({ kind: \"context\" }) as { config?: { endpoint: string }; session?: { stoppedat?: number; pausedat?: number; expiresat: number }; capabilities?: capabilityreport; holds?: { heldkeys: keyholdstate[] }; dialogs?: dialogdecision[] };\n if (endpointinput && context.config) endpointinput.value = context.config.endpoint;\n rendercapabilities(context.capabilities);\n renderlivestate(context.holds?.heldkeys, context.dialogs);\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>Clickable map</h2><ol id="map" class="audit maplist"></ol></section><section><h2>Structured diagnostics</h2><ul id="diagnostics" class="audit"></ul></section><section><h2>Local audit</h2><ol id="audit" class="audit"></ol></section></main><script type="module" src="sidepanel.js"></script></body>
5
5
  </html>
@@ -6,7 +6,20 @@ var diagnosticbutton = document.querySelector("#diagnostic");
6
6
  var planroot = document.querySelector("#plan");
7
7
  var auditroot = document.querySelector("#audit");
8
8
  var diagnosticroot = document.querySelector("#diagnostics");
9
+ var maproot = document.querySelector("#map");
9
10
  var statusnode = document.querySelector("#status");
11
+ var progressnode = document.querySelector("#planprogress");
12
+ var capabilitiestext = document.querySelector("#capabilitiestext");
13
+ var previews = /* @__PURE__ */ new Map();
14
+ function options(step) {
15
+ if (!step.options) return {};
16
+ try {
17
+ const parsed = JSON.parse(step.options);
18
+ return parsed && typeof parsed === "object" && !Array.isArray(parsed) ? parsed : {};
19
+ } catch {
20
+ return {};
21
+ }
22
+ }
10
23
  function status(message, error = false) {
11
24
  if (statusnode) {
12
25
  statusnode.textContent = message;
@@ -26,35 +39,149 @@ function button(label, action, disabled = false) {
26
39
  element.addEventListener("click", () => action().catch((error) => status(error instanceof Error ? error.message : String(error), true)));
27
40
  return element;
28
41
  }
29
- var previewkinds = ["focus", "inspect", "click", "type", "scroll", "select", "hover"];
30
- function renderplan(plan, progress) {
42
+ 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", "shiftclick", "typetime", "appendtext", "setvalue", "typeedit", "submitsearch", "selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails", "verifyvisible", "verifyenabled", "pierceshadow", "clickpoint", "clicktext", "clickaria", "clickname", "resolvexpath"];
43
+ var topictags = [
44
+ { topic: "pointer", kinds: ["movepointer", "clickpoint", "shiftclick", "clicktext", "clickaria", "clickname", "pierceshadow"] },
45
+ { topic: "typing", kinds: ["typetime", "appendtext", "setvalue", "typeedit", "submitsearch"] },
46
+ { topic: "keys", kinds: ["keyhold", "keyrelease"] },
47
+ { topic: "controls", kinds: ["selectmulti", "chooseradio", "setslider", "setdate", "setcolor", "expanddetails"] },
48
+ { topic: "dialogs", kinds: ["dismissdialog"] },
49
+ { topic: "frames", kinds: ["enterframe"] },
50
+ { topic: "retry", kinds: ["retryaction"] },
51
+ { topic: "reads", kinds: ["mapclicks", "verifyvisible", "verifyenabled", "resolvexpath"] }
52
+ ];
53
+ function steptopic(kind) {
54
+ return topictags.find((tag) => tag.kinds.includes(kind))?.topic;
55
+ }
56
+ function renderprogress(plan, completed) {
57
+ if (!progressnode) return;
58
+ const total = plan.steps.length || 1;
59
+ progressnode.max = total;
60
+ progressnode.value = completed.length;
61
+ progressnode.textContent = `${completed.length} of ${plan.steps.length} reviewed steps executed`;
62
+ }
63
+ function renderoutcome(step, outcomes) {
64
+ const outcome = [...outcomes].reverse().find((item) => item.stepid === step.id && item.ok) ?? [...outcomes].reverse().find((item) => item.stepid === step.id);
65
+ if (!outcome) return null;
66
+ const node = document.createElement("details");
67
+ node.className = "outcome";
68
+ const summary = document.createElement("summary");
69
+ summary.textContent = `${outcome.ok ? "result" : "failure"}: ${outcome.summary}`;
70
+ node.append(summary);
71
+ if (outcome.details && Object.keys(outcome.details).length > 0) {
72
+ const payload = document.createElement("pre");
73
+ payload.textContent = JSON.stringify(outcome.details, null, 2).slice(0, 4e3);
74
+ node.append(payload);
75
+ }
76
+ return node;
77
+ }
78
+ function holddetail(step) {
79
+ if (step.kind === "keyhold") {
80
+ const holdid = options(step).holdid;
81
+ return typeof holdid === "string" && holdid ? ` \xB7 hold id ${holdid}` : "";
82
+ }
83
+ if (step.kind === "keyrelease") return step.value ? ` \xB7 releases hold id ${step.value}` : "";
84
+ return "";
85
+ }
86
+ function retrydetail(step, retries) {
87
+ const latest = [...retries].reverse().find((item) => item.stepid === step.id);
88
+ if (!latest) return null;
89
+ const node = document.createElement("p");
90
+ node.className = "timeline";
91
+ node.textContent = `retry timeline: ${latest.attempts} attempt${latest.attempts === 1 ? "" : "s"} \xB7 ${latest.movement.toFixed(1)} px movement \xB7 ${latest.ok ? "succeeded" : "failed"}`;
92
+ return node;
93
+ }
94
+ function renderpreview(step) {
95
+ const preview = previews.get(step.id);
96
+ if (!preview) return null;
97
+ const node = document.createElement("details");
98
+ node.className = "outcome";
99
+ const summary = document.createElement("summary");
100
+ summary.textContent = `preview: ${preview.summary}`;
101
+ node.append(summary);
102
+ if (preview.resolvedtarget) {
103
+ const payload = document.createElement("pre");
104
+ payload.textContent = JSON.stringify(preview.resolvedtarget, null, 2);
105
+ node.append(payload);
106
+ }
107
+ if (preview.candidates && preview.candidates.length > 1) {
108
+ const chooser = document.createElement("p");
109
+ chooser.textContent = "Ambiguous resolution; choose one candidate as the target hint:";
110
+ node.append(chooser);
111
+ for (const candidate of preview.candidates) {
112
+ node.append(" ", button(`Choose "${candidate}"`, async () => {
113
+ pickhint(`target hint: ${candidate}`);
114
+ }));
115
+ }
116
+ }
117
+ return node;
118
+ }
119
+ function stepitem(plan, step, completed, outcomes, retries) {
120
+ const item = document.createElement("li");
121
+ const done = completed.includes(step.id);
122
+ item.textContent = `${done ? "\u2713" : ""} ${step.summary}${holddetail(step)}`;
123
+ const outcome = renderoutcome(step, outcomes);
124
+ if (outcome) item.append(outcome);
125
+ const timeline = step.kind === "retryaction" ? retrydetail(step, retries) : null;
126
+ if (timeline) item.append(timeline);
127
+ const preview = renderpreview(step);
128
+ if (preview) item.append(preview);
129
+ const hastarget = Boolean(step.target) || options(step).targetref !== void 0;
130
+ if (!done && hastarget && previewkinds.includes(step.kind) && ["pending", "approved"].includes(plan.state)) item.append(" ", button("Preview current target", async () => {
131
+ const result = await request({ kind: "preview", stepid: step.id });
132
+ previews.set(step.id, result);
133
+ status(result.summary);
134
+ await refresh();
135
+ }));
136
+ if (!done && plan.state === "approved") item.append(" ", button("Run this reviewed step", async () => {
137
+ const result = await request({ kind: "execute", stepid: step.id });
138
+ status(result.summary);
139
+ await refresh();
140
+ }));
141
+ return item;
142
+ }
143
+ function steplist(plan, steps, completed, outcomes, retries, risk) {
144
+ const group = steps.filter((step) => step.risk === risk);
145
+ if (group.length === 0) return null;
146
+ const section = document.createElement("section");
147
+ const heading = document.createElement("h3");
148
+ heading.textContent = `${risk} steps`;
149
+ section.append(heading);
150
+ const general = group.filter((step) => steptopic(step.kind) === void 0);
151
+ if (general.length > 0) {
152
+ const list = document.createElement("ol");
153
+ for (const step of general) list.append(stepitem(plan, step, completed, outcomes, retries));
154
+ section.append(list);
155
+ }
156
+ for (const tag of topictags) {
157
+ const tagged = group.filter((step) => steptopic(step.kind) === tag.topic);
158
+ if (tagged.length === 0) continue;
159
+ const row = document.createElement("h4");
160
+ row.textContent = `${tag.topic} steps`;
161
+ section.append(row);
162
+ const list = document.createElement("ol");
163
+ for (const step of tagged) list.append(stepitem(plan, step, completed, outcomes, retries));
164
+ section.append(list);
165
+ }
166
+ return section;
167
+ }
168
+ function renderplan(plan, progress, outcomes = [], retries = []) {
31
169
  if (!planroot) return;
32
170
  planroot.replaceChildren();
33
171
  if (!plan) {
34
172
  planroot.textContent = "Start a session, then request a local or endpoint plan. No task runs before review.";
173
+ if (progressnode) progressnode.value = 0;
35
174
  return;
36
175
  }
37
176
  const title = document.createElement("h2");
38
177
  title.textContent = `${plan.state}: ${plan.objective}`;
39
178
  planroot.append(title);
40
179
  const completed = progress?.planid === plan.id ? progress.completedsteps : [];
41
- const list = document.createElement("ol");
42
- for (const step of plan.steps) {
43
- const item = document.createElement("li");
44
- const done = completed.includes(step.id);
45
- item.textContent = `${done ? "\u2713" : step.risk} \u2014 ${step.summary}`;
46
- if (!done && step.target && previewkinds.includes(step.kind) && ["pending", "approved"].includes(plan.state)) item.append(" ", button("Preview current target", async () => {
47
- const result = await request({ kind: "preview", stepid: step.id });
48
- status(result.summary);
49
- }));
50
- if (!done && plan.state === "approved") item.append(" ", button("Run this reviewed step", async () => {
51
- const result = await request({ kind: "execute", stepid: step.id });
52
- status(result.summary);
53
- await refresh();
54
- }));
55
- list.append(item);
56
- }
57
- planroot.append(list);
180
+ renderprogress(plan, completed);
181
+ const sensitive = steplist(plan, plan.steps, completed, outcomes, retries, "sensitive");
182
+ const interaction = steplist(plan, plan.steps, completed, outcomes, retries, "interaction");
183
+ const read = steplist(plan, plan.steps, completed, outcomes, retries, "read");
184
+ for (const group of [sensitive, interaction, read]) if (group) planroot.append(group);
58
185
  if (plan.state === "pending") {
59
186
  planroot.append(button("Approve reviewed plan", async () => {
60
187
  await request({ kind: "approve" });
@@ -70,6 +197,36 @@ function renderplan(plan, progress) {
70
197
  planroot.append(note);
71
198
  }
72
199
  }
200
+ function pickhint(hint) {
201
+ if (objective) objective.value = objective.value ? `${objective.value}
202
+ ${hint}` : hint;
203
+ status(`${hint} recorded as the target hint for the next plan.`);
204
+ }
205
+ function rendermap(map) {
206
+ if (!maproot) return;
207
+ maproot.replaceChildren();
208
+ if (!map || map.entries.length === 0) {
209
+ maproot.textContent = "Run a mapclicks step to number every clickable element on the page.";
210
+ return;
211
+ }
212
+ for (const entry of map.entries) {
213
+ const item = document.createElement("li");
214
+ const pick = document.createElement("button");
215
+ pick.type = "button";
216
+ pick.textContent = `${entry.number}. ${entry.label || entry.selector} (${entry.role})`;
217
+ pick.addEventListener("click", () => pickhint(`target hint: ${entry.selector} (map entry ${entry.number}, ${entry.label || entry.role})`));
218
+ item.append(pick);
219
+ maproot.append(item);
220
+ }
221
+ }
222
+ function rendercapabilities(report) {
223
+ if (!capabilitiestext) return;
224
+ if (!report) {
225
+ capabilitiestext.textContent = "Optional capabilities unknown.";
226
+ return;
227
+ }
228
+ 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"}`;
229
+ }
73
230
  function renderaudit(events) {
74
231
  if (!auditroot) return;
75
232
  auditroot.replaceChildren();
@@ -95,9 +252,11 @@ function renderdiagnostic(report) {
95
252
  }
96
253
  async function refresh() {
97
254
  const context = await request({ kind: "context" });
98
- renderplan(context.plan, context.progress);
255
+ renderplan(context.plan, context.progress, context.outcomes ?? [], context.retries ?? []);
99
256
  renderdiagnostic(context.diagnostic);
257
+ rendermap(context.map);
100
258
  renderaudit(context.audit);
259
+ rendercapabilities(context.capabilities);
101
260
  if (context.session?.pausedat) status("Session paused. Reviewed actions are blocked until resume.");
102
261
  else status(context.session ? "Active session is visible. The extension is waiting for review." : "No active browser session.");
103
262
  }
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": 3,
3
3
  "sources": ["../sidepanel.ts"],
4
- "sourcesContent": ["import type { agentplan, auditevent, diagnosticreport, planprogress } 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; }\nconst previewkinds = [\"focus\", \"inspect\", \"click\", \"type\", \"scroll\", \"select\", \"hover\"];\n\nfunction renderplan(plan?: agentplan, progress?: planprogress): 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.\"; 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 const list = document.createElement(\"ol\");\n for (const step of plan.steps) {\n const item = document.createElement(\"li\");\n const done = completed.includes(step.id);\n item.textContent = `${done ? \"\u2713\" : step.risk} \u2014 ${step.summary}`;\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 planroot.append(list);\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}\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[]; session?: { id: string; pausedat?: number } }; renderplan(context.plan, context.progress); renderdiagnostic(context.diagnostic); renderaudit(context.audit); 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": ";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;AAEhE,SAAS,OAAO,SAAiB,QAAQ,OAAa;AAAE,MAAI,YAAY;AAAE,eAAW,cAAc;AAAS,eAAW,QAAQ,QAAQ,QAAQ,UAAU;AAAA,EAAS;AAAE;AACpK,eAAe,QAAQ,SAAoC;AAAE,QAAM,WAAW,MAAM,OAAO,QAAQ,YAAY,OAAO;AAAuD,MAAI,CAAC,SAAS,GAAI,OAAM,IAAI,MAAM,SAAS,KAAK;AAAG,SAAO,SAAS;AAAO;AACvP,SAAS,OAAO,OAAe,QAA6B,WAAW,OAA0B;AAAE,QAAM,UAAU,SAAS,cAAc,QAAQ;AAAG,UAAQ,OAAO;AAAU,UAAQ,cAAc;AAAO,UAAQ,WAAW;AAAU,UAAQ,iBAAiB,SAAS,MAAM,OAAO,EAAE,MAAM,WAAS,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI,CAAC,CAAC;AAAG,SAAO;AAAS;AAChY,IAAM,eAAe,CAAC,SAAS,WAAW,SAAS,QAAQ,UAAU,UAAU,OAAO;AAEtF,SAAS,WAAW,MAAkB,UAA+B;AACnE,MAAI,CAAC,SAAU;AACf,WAAS,gBAAgB;AACzB,MAAI,CAAC,MAAM;AAAE,aAAS,cAAc;AAAuF;AAAA,EAAQ;AACnI,QAAM,QAAQ,SAAS,cAAc,IAAI;AAAG,QAAM,cAAc,GAAG,KAAK,KAAK,KAAK,KAAK,SAAS;AAAI,WAAS,OAAO,KAAK;AACzH,QAAM,YAAY,UAAU,WAAW,KAAK,KAAK,SAAS,iBAAiB,CAAC;AAC5E,QAAM,OAAO,SAAS,cAAc,IAAI;AACxC,aAAW,QAAQ,KAAK,OAAO;AAC7B,UAAM,OAAO,SAAS,cAAc,IAAI;AACxC,UAAM,OAAO,UAAU,SAAS,KAAK,EAAE;AACvC,SAAK,cAAc,GAAG,OAAO,WAAM,KAAK,IAAI,WAAM,KAAK,OAAO;AAC9D,QAAI,CAAC,QAAQ,KAAK,UAAU,aAAa,SAAS,KAAK,IAAI,KAAK,CAAC,WAAW,UAAU,EAAE,SAAS,KAAK,KAAK,EAAG,MAAK,OAAO,KAAK,OAAO,0BAA0B,YAAY;AAAE,YAAM,SAAS,MAAM,QAAQ,EAAE,MAAM,WAAW,QAAQ,KAAK,GAAG,CAAC;AAA0B,aAAO,OAAO,OAAO;AAAA,IAAG,CAAC,CAAC;AACnS,QAAI,CAAC,QAAQ,KAAK,UAAU,WAAY,MAAK,OAAO,KAAK,OAAO,0BAA0B,YAAY;AAAE,YAAM,SAAS,MAAM,QAAQ,EAAE,MAAM,WAAW,QAAQ,KAAK,GAAG,CAAC;AAA0B,aAAO,OAAO,OAAO;AAAG,YAAM,QAAQ;AAAA,IAAG,CAAC,CAAC;AAC9O,SAAK,OAAO,IAAI;AAAA,EAClB;AACA,WAAS,OAAO,IAAI;AACpB,MAAI,KAAK,UAAU,WAAW;AAAE,aAAS,OAAO,OAAO,yBAAyB,YAAY;AAAE,YAAM,QAAQ,EAAE,MAAM,UAAU,CAAC;AAAG,YAAM,QAAQ;AAAA,IAAG,CAAC,GAAG,OAAO,eAAe,YAAY;AAAE,YAAM,QAAQ,EAAE,MAAM,SAAS,CAAC;AAAG,YAAM,QAAQ;AAAA,IAAG,CAAC,CAAC;AAAA,EAAG;AACpP,MAAI,KAAK,UAAU,eAAe,KAAK,aAAa;AAAE,UAAM,OAAO,SAAS,cAAc,GAAG;AAAG,SAAK,cAAc;AAA4D,aAAS,OAAO,IAAI;AAAA,EAAG;AACxM;AACA,SAAS,YAAY,QAA4B;AAAE,MAAI,CAAC,UAAW;AAAQ,YAAU,gBAAgB;AAAG,aAAW,SAAS,OAAO,MAAM,GAAG,EAAE,GAAG;AAAE,UAAM,OAAO,SAAS,cAAc,IAAI;AAAG,SAAK,cAAc,GAAG,IAAI,KAAK,MAAM,EAAE,EAAE,mBAAmB,CAAC,SAAM,MAAM,IAAI,SAAM,MAAM,OAAO;AAAI,cAAU,OAAO,IAAI;AAAA,EAAG;AAAE;AAC5T,SAAS,iBAAiB,QAAiC;AAAE,MAAI,CAAC,eAAgB;AAAQ,iBAAe,gBAAgB;AAAG,MAAI,CAAC,QAAQ;AAAE,mBAAe,cAAc;AAA2F;AAAA,EAAQ;AAAE,QAAM,SAAS,CAAC,WAAW,OAAO,MAAM,IAAI,UAAU,OAAO,SAAS,UAAU,IAAI,yBAAyB,OAAO,gBAAgB,IAAI,UAAU,OAAO,SAAS,IAAI,qBAAqB,OAAO,UAAU,IAAI,qBAAqB,OAAO,kBAAkB,QAAQ,IAAI,EAAE;AAAG,aAAW,SAAS,QAAQ;AAAE,UAAM,OAAO,SAAS,cAAc,IAAI;AAAG,SAAK,cAAc;AAAO,mBAAe,OAAO,IAAI;AAAA,EAAG;AAAE;AAC9pB,eAAe,UAAyB;AAAE,QAAM,UAAU,MAAM,QAAQ,EAAE,MAAM,UAAU,CAAC;AAAqJ,aAAW,QAAQ,MAAM,QAAQ,QAAQ;AAAG,mBAAiB,QAAQ,UAAU;AAAG,cAAY,QAAQ,KAAK;AAAG,MAAI,QAAQ,SAAS,SAAU,QAAO,4DAA4D;AAAA,MAAQ,QAAO,QAAQ,UAAU,oEAAoE,4BAA4B;AAAG;AACpkB,eAAe,OAAO,MAAuD;AAAE,QAAM,QAAQ,EAAE,MAAM,WAAW,WAAW,SAAS,GAAG,CAAC;AAAG,QAAM,QAAQ;AAAG;AAC5J,aAAa,iBAAiB,SAAS,MAAM,OAAO,cAAc,EAAE,MAAM,WAAS,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI,CAAC,CAAC;AACxJ,cAAc,iBAAiB,SAAS,MAAM,OAAO,eAAe,EAAE,MAAM,WAAS,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI,CAAC,CAAC;AAC1J,kBAAkB,iBAAiB,SAAS,MAAM,QAAQ,EAAE,MAAM,aAAa,CAAC,EAAE,KAAK,MAAM,QAAQ,CAAC,EAAE,MAAM,WAAS,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI,CAAC,CAAC;AAC5L,QAAQ,EAAE,MAAM,WAAS,OAAO,iBAAiB,QAAQ,MAAM,UAAU,OAAO,KAAK,GAAG,IAAI,CAAC;",
4
+ "sourcesContent": ["import type { agentplan, auditevent, capabilityreport, clickablemap, diagnosticreport, planprogress, resolvedtarget, retryoutcome, 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 maproot = document.querySelector<HTMLElement>(\"#map\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst progressnode = document.querySelector<HTMLProgressElement>(\"#planprogress\");\nconst capabilitiestext = document.querySelector<HTMLElement>(\"#capabilitiestext\");\n\ntype previewresult = { ok: boolean; summary: string; resolvedtarget?: resolvedtarget; candidates?: string[] };\nconst previews = new Map<string, previewresult>();\n\n/** Safely reads the reviewed options object of one step. */\nfunction options(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\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; }\n\n/** Kinds addressable by a css target or a reviewed targetref; only these can be previewed. */\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\", \"shiftclick\", \"typetime\", \"appendtext\", \"setvalue\", \"typeedit\", \"submitsearch\", \"selectmulti\", \"chooseradio\", \"setslider\", \"setdate\", \"setcolor\", \"expanddetails\", \"verifyvisible\", \"verifyenabled\", \"pierceshadow\", \"clickpoint\", \"clicktext\", \"clickaria\", \"clickname\", \"resolvexpath\"];\n\n/** Topic rows that group the new interaction kinds inside each risk class. */\nconst topictags: Array<{ topic: string; kinds: string[] }> = [\n { topic: \"pointer\", kinds: [\"movepointer\", \"clickpoint\", \"shiftclick\", \"clicktext\", \"clickaria\", \"clickname\", \"pierceshadow\"] },\n { topic: \"typing\", kinds: [\"typetime\", \"appendtext\", \"setvalue\", \"typeedit\", \"submitsearch\"] },\n { topic: \"keys\", kinds: [\"keyhold\", \"keyrelease\"] },\n { topic: \"controls\", kinds: [\"selectmulti\", \"chooseradio\", \"setslider\", \"setdate\", \"setcolor\", \"expanddetails\"] },\n { topic: \"dialogs\", kinds: [\"dismissdialog\"] },\n { topic: \"frames\", kinds: [\"enterframe\"] },\n { topic: \"retry\", kinds: [\"retryaction\"] },\n { topic: \"reads\", kinds: [\"mapclicks\", \"verifyvisible\", \"verifyenabled\", \"resolvexpath\"] },\n];\n\nfunction steptopic(kind: string): string | undefined {\n return topictags.find(tag => tag.kinds.includes(kind))?.topic;\n}\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\n/** Renders the hold id of a key hold or release step beside its summary. */\nfunction holddetail(step: toolstep): string {\n if (step.kind === \"keyhold\") {\n const holdid = options(step).holdid;\n return typeof holdid === \"string\" && holdid ? ` \u00B7 hold id ${holdid}` : \"\";\n }\n if (step.kind === \"keyrelease\") return step.value ? ` \u00B7 releases hold id ${step.value}` : \"\";\n return \"\";\n}\n\n/** Renders the retry attempts of one retry step on the step timeline. */\nfunction retrydetail(step: toolstep, retries: retryoutcome[]): HTMLElement | null {\n const latest = [...retries].reverse().find(item => item.stepid === step.id);\n if (!latest) return null;\n const node = document.createElement(\"p\");\n node.className = \"timeline\";\n node.textContent = `retry timeline: ${latest.attempts} attempt${latest.attempts === 1 ? \"\" : \"s\"} \u00B7 ${latest.movement.toFixed(1)} px movement \u00B7 ${latest.ok ? \"succeeded\" : \"failed\"}`;\n return node;\n}\n\n/** Renders the resolved target details of one previewed interaction step before approval. */\nfunction renderpreview(step: toolstep): HTMLElement | null {\n const preview = previews.get(step.id);\n if (!preview) return null;\n const node = document.createElement(\"details\");\n node.className = \"outcome\";\n const summary = document.createElement(\"summary\");\n summary.textContent = `preview: ${preview.summary}`;\n node.append(summary);\n if (preview.resolvedtarget) {\n const payload = document.createElement(\"pre\");\n payload.textContent = JSON.stringify(preview.resolvedtarget, null, 2);\n node.append(payload);\n }\n if (preview.candidates && preview.candidates.length > 1) {\n const chooser = document.createElement(\"p\");\n chooser.textContent = \"Ambiguous resolution; choose one candidate as the target hint:\";\n node.append(chooser);\n for (const candidate of preview.candidates) {\n node.append(\" \", button(`Choose \"${candidate}\"`, async () => { pickhint(`target hint: ${candidate}`); }));\n }\n }\n return node;\n}\n\nfunction stepitem(plan: agentplan, step: toolstep, completed: string[], outcomes: stepoutcome[], retries: retryoutcome[]): HTMLLIElement {\n const item = document.createElement(\"li\");\n const done = completed.includes(step.id);\n item.textContent = `${done ? \"\u2713\" : \"\"} ${step.summary}${holddetail(step)}`;\n const outcome = renderoutcome(step, outcomes);\n if (outcome) item.append(outcome);\n const timeline = step.kind === \"retryaction\" ? retrydetail(step, retries) : null;\n if (timeline) item.append(timeline);\n const preview = renderpreview(step);\n if (preview) item.append(preview);\n const hastarget = Boolean(step.target) || options(step).targetref !== undefined;\n if (!done && hastarget && 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 previewresult; previews.set(step.id, result); status(result.summary); await refresh(); }));\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 return item;\n}\n\nfunction steplist(plan: agentplan, steps: toolstep[], completed: string[], outcomes: stepoutcome[], retries: retryoutcome[], 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 general = group.filter(step => steptopic(step.kind) === undefined);\n if (general.length > 0) {\n const list = document.createElement(\"ol\");\n for (const step of general) list.append(stepitem(plan, step, completed, outcomes, retries));\n section.append(list);\n }\n for (const tag of topictags) {\n const tagged = group.filter(step => steptopic(step.kind) === tag.topic);\n if (tagged.length === 0) continue;\n const row = document.createElement(\"h4\");\n row.textContent = `${tag.topic} steps`;\n section.append(row);\n const list = document.createElement(\"ol\");\n for (const step of tagged) list.append(stepitem(plan, step, completed, outcomes, retries));\n section.append(list);\n }\n return section;\n}\n\nfunction renderplan(plan?: agentplan, progress?: planprogress, outcomes: stepoutcome[] = [], retries: retryoutcome[] = []): 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, retries, \"sensitive\");\n const interaction = steplist(plan, plan.steps, completed, outcomes, retries, \"interaction\");\n const read = steplist(plan, plan.steps, completed, outcomes, retries, \"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\n/** Records one clickable map entry or candidate as the target hint for the next plan. */\nfunction pickhint(hint: string): void {\n if (objective) objective.value = objective.value ? `${objective.value}\\n${hint}` : hint;\n status(`${hint} recorded as the target hint for the next plan.`);\n}\n\n/** Renders the clickable map as a numbered list beside the plan and lets the user pick entries. */\nfunction rendermap(map?: clickablemap): void {\n if (!maproot) return;\n maproot.replaceChildren();\n if (!map || map.entries.length === 0) { maproot.textContent = \"Run a mapclicks step to number every clickable element on the page.\"; return; }\n for (const entry of map.entries) {\n const item = document.createElement(\"li\");\n const pick = document.createElement(\"button\");\n pick.type = \"button\";\n pick.textContent = `${entry.number}. ${entry.label || entry.selector} (${entry.role})`;\n pick.addEventListener(\"click\", () => pickhint(`target hint: ${entry.selector} (map entry ${entry.number}, ${entry.label || entry.role})`));\n item.append(pick);\n maproot.append(item);\n }\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 }; map?: clickablemap; retries?: retryoutcome[] }; renderplan(context.plan, context.progress, context.outcomes ?? [], context.retries ?? []); renderdiagnostic(context.diagnostic); rendermap(context.map); 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
  }
@@ -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; } section h4 { font-size: 11px; letter-spacing: .08em; text-transform: uppercase; color: var(--accent); margin: 12px 0 4px; } p.timeline { margin: 4px 0 0; font-size: 11px; color: var(--accent); } ol.maplist li { margin-bottom: 4px; } ol.maplist li button { margin: 0; min-height: 28px; font-size: 11px; font-weight: 600; background: transparent; color: var(--text); border-color: var(--line); text-align: left; }
@@ -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.31",
5
+ "version": "1.1.33",
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
  ],
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@wenathlan/extension",
3
- "version": "1.1.31",
3
+ "version": "1.1.33",
4
4
  "description": "Consent-first browser agent bridge and Manifest V3 extension.",
5
5
  "type": "module",
6
6
  "license": "GPL-3.0-only",