@wenathlan/extension 1.1.62 → 1.1.64
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 +5 -3
- package/dist/environments.d.ts +10 -0
- package/dist/environments.d.ts.map +1 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +956 -1
- package/dist/index.js.map +4 -4
- package/dist/memory.d.ts +126 -1
- package/dist/memory.d.ts.map +1 -1
- package/dist/planreview.d.ts +87 -0
- package/dist/planreview.d.ts.map +1 -0
- package/dist/policy.d.ts +101 -0
- package/dist/policy.d.ts.map +1 -1
- package/dist/protocol.d.ts +135 -0
- package/dist/protocol.d.ts.map +1 -1
- package/dist/sessioninterface.d.ts +213 -0
- package/dist/sessioninterface.d.ts.map +1 -0
- package/dist/surfaces.d.ts +59 -0
- package/dist/surfaces.d.ts.map +1 -0
- package/dist/types.d.ts +376 -3
- package/dist/types.d.ts.map +1 -1
- package/dist/version.d.ts +1 -1
- package/extension/dist/background.js +1394 -2
- package/extension/dist/background.js.map +4 -4
- package/extension/dist/dashboardpage.html +13 -0
- package/extension/dist/dashboardpage.js +129 -0
- package/extension/dist/dashboardpage.js.map +7 -0
- package/extension/dist/manifest.json +5 -2
- package/extension/dist/offscreen.js +5 -0
- package/extension/dist/offscreen.js.map +2 -2
- package/extension/dist/optionspage.html +14 -0
- package/extension/dist/optionspage.js +118 -0
- package/extension/dist/optionspage.js.map +7 -0
- package/extension/dist/pagebridge.js.map +1 -1
- package/extension/dist/popup.html +4 -1
- package/extension/dist/popup.js +193 -0
- package/extension/dist/popup.js.map +3 -3
- package/extension/dist/sidepanel.html +7 -2
- package/extension/dist/sidepanel.js +693 -219
- package/extension/dist/sidepanel.js.map +3 -3
- package/extension/dist/transparencypage.html +1 -0
- package/extension/dist/transparencypage.js +42 -0
- package/extension/dist/transparencypage.js.map +2 -2
- package/extension/manifest.json +5 -2
- package/package.json +1 -1
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"version": 3,
|
|
3
3
|
"sources": ["../../policy.ts", "../popup.ts"],
|
|
4
|
-
"sourcesContent": ["import type { actionkind, agentbudget, agentidentity, agentmessage, agentplan, agentscope, agentsession, agentusage, allowlistentry, approvaltimeout, arbitrationrule, attachtarget, blackboardentry, callratelimit, captureexport, automationallowlistentry, classconsent, conflictwriter, connectallowentry, consentwindow, costbudget, criticreview, draftstep, environmentkind, environmentrequirement, escalationrecord, handoffrecord, lockkind, mergerule, modeloutput, origincheckverdict, originprofile, phishverdict, plandraft, providerconfig, replanrecord, resourcelock, resultreport, reviewrequest, revokerunevent, ratelimitbucket, sensitiveclass, schemaerror, spawnrequest, swarmaction, taskqueue, captureformat, capturenaming, captureoptions, cdpallowlist, cleanuprule, clientidentity, clientrecord, consoleconsentrecord, debuggergrant, delaystep, downloadspec, editormodel, endpointconfig, fieldkind, formprofile, locationconsent, loglevel, loglevelset, mimefilter, mcpserverconfig, observationmode, permissionstate, policyevaluation, protocoleventsubscription, quarantineentry, regionrect, rotationrule, runsettings, safetyverdict, samplingrequest, siteoverride, sourcemapconsent, spamrule, steptemplate, toolcallrecord, toolcatalog, tooldef, toolmock, toolnamespace, toolstep, tooldryrun, transformrule, verifiercheck, waitstep, watchdogconfig, workerassignment, workflowrecord, workflowstep } from \"./types.js\";\nimport { allowlistcheck, missingclassconsents, profilegrade, sensitiveclassesof, windowgatesstep } from \"./originpolicy.js\";\nimport { defaultenvironment, environmentrequirementsof, environmentsof } from \"./environments.js\";\nimport { domainkinds, toolnamespaces } from \"./toolcatalog.js\";\nimport { channeloptionsof, channelorigin, pollcursorof, subscriptionoptionsof } from \"./socketbus.js\";\nimport { apireplayspecof, privatemime } from \"./netwatch.js\";\nimport { blockruleof, cookiedomaingranted, cookierecordof, mockspecof, patternorigin, proxyrouteof, headeruleof } from \"./netcontrol.js\";\nimport { allowlistcovers, breakpointinputof, cdpallowlistof, cdpdomains, cdpeventruleof, methoddomain, overrideinputof, stepmodeof, teardownplanof, watchexpressionof } from \"./cdpbus.js\";\nimport { annotationof, attachtargetof, flowspecof, tracecategories } from \"./profilers.js\";\nimport { agentgrammarvalid, agentpresetof, blackboxruleof, browserpermissions, devicepresetof, familyofkind, locationconsentcovers, locationpresetof, locationrangevalid, networkpresetof, permissiongrantof, permissiongrade, permissionstates, revertplanof } from \"./emulation.js\";\nimport { autointervalof, importsessionfile, restoreplanof, searchqueryof, sessionkinds, snapshotplanof } from \"./sessions.js\";\nimport { composeworkflow, expressionof, expressionoperators, regexruleof, steptemplateof, validateworkflow, workflowblockof, workflowstepof } from \"./workflow.js\";\nimport { branchof, conditionof, controlsteps, foreachof, iscontrolflowkind, loopof, parallelof, repeatuntilof, tryof, whileof } from \"./controlflow.js\";\nimport { armrule, cronparse, triggerfamilyof, triggerpayloadof, triggereventcatalog, webhooksecretok } from \"./trigger.js\";\nimport { formpayloadof, multipartpayloadof, oauthflowof } from \"./netauth.js\";\nimport { loglevels, timelinesources } from \"./runtimeline.js\";\nimport { bucketboundsvalid, origincheckof } from \"./inboundguard.js\";\nimport { gatekindfor } from \"./confirmgates.js\";\nimport { phishthresholdvalid } from \"./phishguard.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\", \"openlink\", \"openprivate\", \"reloadcache\", \"stopnav\", \"followlink\", \"spanav\", \"rewritequery\", \"setfragment\", \"navlist\", \"navprofile\", \"handleauth\", \"printpdf\", \"prefetch\", \"preconnect\", \"deeplink\", \"reopentab\", \"pausenav\", \"navrate\", \"openclipboard\", \"batchopen\", \"duplicatetab\", \"closepattern\", \"pintab\", \"mutetab\", \"movetab\", \"movetabwindow\", \"grouptabs\", \"colorgroup\", \"collapsegroup\", \"discardtab\", \"reloadtabs\", \"zoomin\", \"zoomout\", \"switchtab\", \"maximizewindow\", \"minimizewindow\", \"restorewindow\", \"focuswindow\", \"scratchwindow\", \"incognitowindow\", \"restoretab\", \"restorelayout\", \"reopenrun\", \"badgetab\", \"fillform\", \"filllabel\", \"fillplaceholder\", \"submitform\", \"retryform\", \"runwizard\", \"selectchain\", \"picktypeahead\", \"pickdate\", \"attachfile\", \"fillcard\", \"fillcode\", \"consentpassword\", \"exportcsv\", \"exportjson\", \"exportexcel\", \"copytable\", \"pushsheets\", \"streamdisk\", \"paginateextract\", \"resumeextract\", \"batchdownload\", \"pausedownload\", \"resumedownload\", \"interceptmime\", \"readclipboard\", \"writeclipboard\", \"copyscreen\", \"quarantinedownload\", \"scanvirus\", \"cleanupartifacts\", \"recordscreen\", \"captureaudio\", \"downloadimages\", \"callrest\", \"callgraphql\", \"sendmessage\", \"blockrequest\", \"mockresponse\", \"rewriteheaders\", \"setcookies\", \"clearcookies\", \"authflow\", \"saveapikey\", \"routeproxy\", \"postform\", \"postfiles\", \"attachcdp\", \"detachcdp\", \"cdpcmd\", \"overridescript\", \"heapshot\", \"profilecpu\", \"capturesourcemaps\", \"emulatedevice\", \"emulatenetwork\", \"emulatelocate\", \"setuseragent\", \"overridepermission\", \"restoresession\", \"exportsessions\", \"importsessions\", \"runworkflow\", \"visitrule\", \"urlrule\", \"menurule\", \"keyrule\", \"buttonrule\", \"cronrule\", \"intervalrule\", \"urllistrule\", \"webhookrule\", \"eventrule\"]);\nconst interactionactions = new Set<actionkind>([\"focus\", \"scroll\", \"hover\", \"clickdeep\", \"rightclick\", \"doubleclick\", \"scrollpage\", \"scrollby\", \"scrollend\", \"scrolltop\", \"fullscreen\", \"zoomset\", \"movepointer\", \"clicktext\", \"clickaria\", \"clickname\", \"expanddetails\", \"pierceshadow\", \"retryaction\", \"capturebodies\", \"setbreakpoint\", \"stepcode\", \"watchexpr\", \"loop\", \"repeatuntil\", \"whileloop\", \"foreach\", \"parallel\", \"trycatch\"]);\nconst readactions = new Set<actionkind>([\"observe\", \"inspect\", \"extract\", \"wait\", \"waitfor\", \"waittext\", \"readattribute\", \"readstyle\", \"readgeometry\", \"readvalue\", \"readtext\", \"readhtml\", \"countelements\", \"readtable\", \"readlinks\", \"readimages\", \"readmeta\", \"readforms\", \"readstorage\", \"highlight\", \"tablist\", \"windowlist\", \"tabsnapshot\", \"mapclicks\", \"verifyvisible\", \"verifyenabled\", \"resolvexpath\", \"a11ytree\", \"readvisible\", \"readertree\", \"detectlists\", \"detecttables\", \"readjson\", \"watchmutate\", \"waitquiet\", \"watchbanner\", \"detectinfinitescroll\", \"detectvirtual\", \"detectlazy\", \"readscrollpos\", \"readlang\", \"readoutline\", \"countpages\", \"listshadow\", \"listframes\", \"classifypage\", \"fingerprintsection\", \"diffsnapshots\", \"readselection\", \"watchfocus\", \"detectsticky\", \"detectscrolllock\", \"readopengraph\", \"detectlanguage\", \"deriveselector\", \"waitload\", \"waiturl\", \"spawait\", \"detecthttp\", \"readredirects\", \"readfinalurl\", \"trailaudit\", \"navintent\", \"checksafe\", \"querytabs\", \"watchtab\", \"findclones\", \"searchtabs\", \"listaudio\", \"snapshotsession\", \"savelayout\", \"attachmeta\", \"detectfields\", \"generatevalues\", \"saveprofiles\", \"asksubmit\", \"readerrors\", \"skiphoneypot\", \"detectlogin\", \"detecttemplate\", \"handoffcaptcha\", \"scrapetable\", \"importcsv\", \"looprows\", \"transformvalues\", \"deduperows\", \"mergepages\", \"stamplerows\", \"previewgrid\", \"logprovenance\", \"verifydownload\", \"exportnetlog\", \"namecaptures\", \"shotview\", \"shotfullpage\", \"shotelement\", \"shotregion\", \"contactsheet\", \"capturepdf\", \"captureframe\", \"readmedia\", \"readassets\", \"probestream\", \"timelapse\", \"shotcanvas\", \"convertimage\", \"makethumbs\", \"fetchurl\", \"parsejson\", \"parsehtml\", \"opensocket\", \"waitmessage\", \"watchrequests\", \"readheaders\", \"mapapi\", \"subscribesse\", \"longpoll\", \"extractapi\", \"readcookies\", \"watchconsole\", \"watcherrors\", \"watchtasks\", \"watchcdp\", \"measureflow\", \"trackmemory\", \"watchshifts\", \"traceload\", \"annotatetrace\", \"replaytrace\", \"blackboxscripts\", \"persiststate\", \"capturesession\", \"namedsessions\", \"diffsessions\", \"searchsessions\", \"composeworkflow\", \"savetemplate\", \"dryrun\", \"delay\", \"waitelement\", \"compute\", \"extractvars\", \"listruns\", \"condition\", \"branch\"]);\nconst allowedactions = new Set<actionkind>([...sensitiveactions, ...interactionactions, ...readactions]);\nconst watchactions = new Set<actionkind>([\"watchmutate\", \"watchbanner\", \"watchfocus\", \"watchtab\"]);\nconst targetactions = new Set<actionkind>([\"inspect\", \"focus\", \"click\", \"type\", \"scroll\", \"select\", \"hover\", \"clickdeep\", \"rightclick\", \"doubleclick\", \"drag\", \"drop\", \"upload\", \"clear\", \"check\", \"uncheck\", \"toggle\", \"submit\", \"readattribute\", \"readstyle\", \"readgeometry\", \"readvalue\", \"readtext\", \"readhtml\", \"countelements\", \"readtable\", \"highlight\", \"setattribute\", \"removeattribute\", \"waitfor\", \"shiftclick\", \"typetime\", \"appendtext\", \"setvalue\", \"typeedit\", \"submitsearch\", \"selectmulti\", \"chooseradio\", \"setslider\", \"setdate\", \"setcolor\", \"expanddetails\", \"verifyvisible\", \"verifyenabled\", \"pierceshadow\", \"deriveselector\", \"fingerprintsection\", \"submitform\", \"retryform\", \"selectchain\", \"picktypeahead\", \"pickdate\", \"attachfile\", \"fillcode\", \"consentpassword\", \"scrapetable\", \"paginateextract\", \"shotelement\", \"captureframe\", \"shotcanvas\"]);\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\", \"followlink\", \"setfragment\", \"handleauth\", \"navintent\", \"openclipboard\", \"checksafe\", \"reopentab\", \"spanav\", \"duplicatetab\", \"pintab\", \"mutetab\", \"movetab\", \"movetabwindow\", \"searchtabs\", \"badgetab\", \"attachmeta\", \"focuswindow\", \"maximizewindow\", \"minimizewindow\", \"restorewindow\", \"incognitowindow\", \"asksubmit\", \"selectchain\", \"picktypeahead\", \"pickdate\", \"attachfile\", \"fillcode\", \"consentpassword\", \"pausedownload\", \"resumedownload\", \"verifydownload\", \"writeclipboard\", \"quarantinedownload\", \"scanvirus\"]);\nconst tabscommandactions = new Set<actionkind>([\"querytabs\", \"duplicatetab\", \"closepattern\", \"pintab\", \"mutetab\", \"movetab\", \"movetabwindow\", \"grouptabs\", \"colorgroup\", \"collapsegroup\", \"discardtab\", \"reloadtabs\", \"zoomin\", \"zoomout\", \"watchtab\", \"switchtab\", \"maximizewindow\", \"minimizewindow\", \"restorewindow\", \"focuswindow\", \"scratchwindow\", \"incognitowindow\", \"restoretab\", \"savelayout\", \"restorelayout\", \"findclones\", \"searchtabs\", \"badgetab\", \"attachmeta\", \"listaudio\", \"reopenrun\", \"snapshotsession\"]);\nconst formactions = new Set<actionkind>([\"fillform\", \"filllabel\", \"fillplaceholder\", \"detectfields\", \"generatevalues\", \"saveprofiles\", \"asksubmit\", \"submitform\", \"readerrors\", \"retryform\", \"runwizard\", \"selectchain\", \"picktypeahead\", \"pickdate\", \"attachfile\", \"handoffcaptcha\", \"fillcard\", \"fillcode\", \"consentpassword\", \"skiphoneypot\", \"detectlogin\", \"detecttemplate\"]);\n/** Extraction, transform, export and provenance kinds of the forms and data part two family. */\nconst datasetactions = new Set<actionkind>([\"scrapetable\", \"exportcsv\", \"exportjson\", \"exportexcel\", \"copytable\", \"pushsheets\", \"importcsv\", \"looprows\", \"transformvalues\", \"deduperows\", \"paginateextract\", \"mergepages\", \"stamplerows\", \"previewgrid\", \"streamdisk\", \"resumeextract\", \"logprovenance\"]);\n/** Export kinds that move extracted data out of local memory to disk, the clipboard or a reviewed sheet endpoint. */\nconst exportactions = new Set<actionkind>([\"exportcsv\", \"exportjson\", \"exportexcel\", \"copytable\", \"pushsheets\", \"streamdisk\"]);\n/** Files, clipboard and downloads kinds of the batch queue, interception, clipboard, quarantine, naming and cleanup family. */\nconst filesactions = new Set<actionkind>([\"batchdownload\", \"pausedownload\", \"resumedownload\", \"verifydownload\", \"interceptmime\", \"exportnetlog\", \"readclipboard\", \"writeclipboard\", \"copyscreen\", \"quarantinedownload\", \"scanvirus\", \"namecaptures\", \"cleanupartifacts\"]);\nconst captureactions = new Set<actionkind>([\"shotview\", \"shotfullpage\", \"shotelement\", \"shotregion\", \"contactsheet\"]);\n/** Media capture part two kinds of the pdf, recording, image, canvas, stream, asset, lapse, conversion and thumbnail family. */\nconst mediaactions = new Set<actionkind>([\"capturepdf\", \"recordscreen\", \"captureaudio\", \"captureframe\", \"downloadimages\", \"shotcanvas\", \"probestream\", \"readmedia\", \"readassets\", \"timelapse\", \"convertimage\", \"makethumbs\"]);\n\nconst httpactions = new Set<actionkind>([\"fetchurl\", \"parsejson\", \"parsehtml\", \"callrest\", \"callgraphql\"]);\n\nconst socketactions = new Set<actionkind>([\"opensocket\", \"sendmessage\", \"waitmessage\", \"subscribesse\", \"longpoll\"]);\n\nconst netwatchactions = new Set<actionkind>([\"watchrequests\", \"readheaders\", \"capturebodies\", \"mapapi\", \"extractapi\"]);\n\n/** Network control kinds of the 1.1.44 family: blocking, mocking, header rewriting, cookies, auth, api keys, proxy routing and uploads. */\nconst controlactions = new Set<actionkind>([\"blockrequest\", \"mockresponse\", \"rewriteheaders\", \"setcookies\", \"readcookies\", \"clearcookies\", \"authflow\", \"saveapikey\", \"routeproxy\", \"postform\", \"postfiles\"]);\n\n/** Debugging kinds of the 1.1.45 family: console, error and task watching stays read only timeline capture. */\nconst debugactions = new Set<actionkind>([\"watchconsole\", \"watcherrors\", \"watchtasks\"]);\n\n/** The devtools protocol kinds of the 1.1.46 debugging family: attach, detach, raw commands, event watches, breakpoints, stepping, watch expressions and script overrides. */\nconst cdpactions = new Set<actionkind>([\"attachcdp\", \"detachcdp\", \"cdpcmd\", \"watchcdp\", \"setbreakpoint\", \"stepcode\", \"watchexpr\", \"overridescript\"]);\n\n/** The profiling kinds of the 1.1.47 debugging part three family: flow measurement, heap snapshots, memory growth tracking, cpu profiles, layout shift watches, trace records, trace annotation, offline trace replay and source map capture. */\nconst profileractions = new Set<actionkind>([\"measureflow\", \"heapshot\", \"trackmemory\", \"profilecpu\", \"watchshifts\", \"traceload\", \"annotatetrace\", \"replaytrace\", \"capturesourcemaps\"]);\n\nconst emulationactions = new Set<actionkind>([\"emulatedevice\", \"emulatenetwork\", \"emulatelocate\", \"setuseragent\", \"overridepermission\", \"blackboxscripts\"]);\n\n/** The session memory kinds of the 1.1.49 family: task state persistence, session capture, restore, naming, diffing, search, export and import. */\nconst sessionactions = new Set<actionkind>([\"persiststate\", \"capturesession\", \"restoresession\", \"namedsessions\", \"diffsessions\", \"searchsessions\", \"exportsessions\", \"importsessions\"]);\n\n/** The workflow kinds of the 1.1.50 and 1.1.51 families: composition, templates, runs, dry runs, jittered delays, element waits, expressions, variable extraction, and the control flow family of conditionals, branching, loops, parallel branches with joins and try catch with retries and timeouts. */\nconst workflowactions = new Set<actionkind>([\"composeworkflow\", \"savetemplate\", \"runworkflow\", \"dryrun\", \"delay\", \"waitelement\", \"compute\", \"extractvars\", \"condition\", \"branch\", \"loop\", \"repeatuntil\", \"whileloop\", \"foreach\", \"parallel\", \"trycatch\"]);\n\n/** The trigger kinds of the 1.1.52 family: page visit, url pattern, context menu, keyboard shortcut, toolbar button, cron, interval, url list, webhook and page event rules that launch reviewed workflows; every rule arms behind the explicit arm review and grades sensitive because it launches runs automatically. */\nconst triggeractions = new Set<actionkind>([\"visitrule\", \"urlrule\", \"menurule\", \"keyrule\", \"buttonrule\", \"cronrule\", \"intervalrule\", \"urllistrule\", \"webhookrule\", \"eventrule\"]);\n\n/** Header names that carry credentials; sending any of them needs the explicit consent that names the header. */\nconst credentialheaders = new Set([\"authorization\", \"proxy-authorization\", \"cookie\", \"cookie2\", \"set-cookie\", \"api-key\", \"x-api-key\", \"x-auth-token\", \"x-session-token\", \"proxy-authorization\"]);\n/** Field kinds the form grammar accepts inside records, profiles and value rules. */\nconst fieldkinds: fieldkind[] = [\"text\", \"email\", \"phone\", \"date\", \"number\", \"select\", \"check\", \"radio\", \"file\", \"password\", \"card\", \"code\"];\nconst layoutmutationactions = new Set<actionkind>([\"grouptabs\", \"colorgroup\", \"collapsegroup\", \"savelayout\", \"restorelayout\"]);\n/** Chromium tab group colors accepted as reviewed group color choices. */\nconst groupcolors = [\"grey\", \"blue\", \"red\", \"yellow\", \"green\", \"pink\", \"purple\", \"cyan\", \"orange\"];\n\n/** Normalizes a user supplied HTTPS endpoint without preserving a provider lock-in. */\nexport function normalizeendpoint(value: string): endpointconfig {\n const endpoint = new URL(value.trim());\n if (endpoint.protocol !== \"https:\") throw new Error(\"Devthink accepts HTTPS endpoints only.\");\n if (endpoint.username || endpoint.password) throw new Error(\"Endpoint credentials are not allowed in the URL.\");\n return { endpoint: endpoint.toString(), origin: endpoint.origin, configuredat: Date.now() };\n}\n\n/** Creates the exact optional host pattern requested from Chromium. */\nexport function hostpattern(origin: string): string {\n const parsed = new URL(origin);\n if (parsed.protocol !== \"https:\") throw new Error(\"Only HTTPS origins can be granted.\");\n return `${parsed.origin}/*`;\n}\n\n/** True when the kind belongs to the session memory family of the 1.1.49 release. */\nexport function issessionkind(kind: actionkind): boolean {\n return sessionactions.has(kind);\n}\n\n/** True when the kind belongs to the workflow family of the 1.1.50 release. */\nexport function isworkflowkind(kind: actionkind): boolean {\n return workflowactions.has(kind);\n}\n\n/** True when the kind belongs to the trigger family of the 1.1.52 release: every trigger kind arms an automatic launcher and needs the explicit arm review. */\nexport function istriggeraction(kind: actionkind): boolean {\n return triggeractions.has(kind);\n}\n\n/** True when the action kind observes the page over a reviewed lifetime window. */\nexport function iswatchkind(kind: actionkind): boolean {\n return watchactions.has(kind);\n}\n\n/** True when the kind belongs to the debugging family of console, error and task watching. */\nexport function isdebugkind(kind: actionkind): boolean {\n return debugactions.has(kind);\n}\n\n/** True when the kind belongs to the devtools protocol family of attaches, raw commands, event watches, breakpoints, stepping, watch expressions and script overrides. */\nexport function iscdpkind(kind: actionkind): boolean {\n return cdpactions.has(kind);\n}\n\n/** True when the kind belongs to the profiling family of flow, heap, cpu, shift, trace and source map instruments. */\nexport function isprofilekind(kind: actionkind): boolean {\n return profileractions.has(kind);\n}\n\n/** True when the kind belongs to the emulation family of device, network, location, agent and permission layers plus blackbox trace shaping. */\nexport function isemulationkind(kind: actionkind): boolean {\n return emulationactions.has(kind);\n}\n\n/** Grades the observation mode of a kind: passive capture, watched lifetimes or diffing passes. */\nexport function observationmodeof(kind: actionkind): observationmode {\n if (watchactions.has(kind) || debugactions.has(kind) || profileractions.has(kind) && kind !== \"heapshot\" && kind !== \"replaytrace\" && kind !== \"annotatetrace\" && kind !== \"capturesourcemaps\" && kind !== \"profilecpu\" || cdpactions.has(kind) && kind === \"watchcdp\" || kind === \"waitquiet\") return \"watching\";\n if (kind === \"diffsnapshots\") return \"diffing\";\n return \"passive\";\n}\n\n/** Defines action risk from the fixed local allowlist. */\nexport function actionrisk(kind: actionkind): \"read\" | \"interaction\" | \"sensitive\" {\n if (!allowedactions.has(kind)) throw new Error(\"Unsupported browser action.\");\n if (sensitiveactions.has(kind)) return \"sensitive\";\n return interactionactions.has(kind) ? \"interaction\" : \"read\";\n}\n\n/** True when the action kind accepts a css selector target or a reviewed targetref. */\nexport function needstarget(kind: actionkind): boolean {\n return targetactions.has(kind);\n}\n\n/** Parses the reviewed JSON options of a step; malformed payloads are rejected early. */\nexport function parseoptions(step: toolstep): Record<string, unknown> {\n if (step.options === undefined) return {};\n let parsed: unknown;\n try { parsed = JSON.parse(step.options); } catch { throw new Error(\"Step options must be a JSON object.\"); }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) throw new Error(\"Step options must be a JSON object.\");\n return parsed as Record<string, unknown>;\n}\n\n/** Maps an action kind to the optional browser permission it requires, if any. */\nexport function requiredcapability(kind: actionkind): string | undefined {\n if (kind === \"tablist\") return \"tabs\";\n if (kind === \"downloadfile\") return \"downloads\";\n if (kind === \"openclipboard\") return \"clipboardRead\";\n if (kind === \"copytable\") return \"clipboardWrite\";\n if (kind === \"batchdownload\" || kind === \"pausedownload\" || kind === \"resumedownload\" || kind === \"verifydownload\" || kind === \"interceptmime\" || kind === \"quarantinedownload\" || kind === \"scanvirus\") return \"downloads\";\n if (kind === \"readclipboard\") return \"clipboardRead\";\n if (kind === \"writeclipboard\" || kind === \"copyscreen\") return \"clipboardWrite\";\n if (kind === \"downloadimages\") return \"downloads\";\n if (kind === \"authflow\") return \"tabs\";\n if (kind === \"capturesession\" || kind === \"restoresession\") return \"tabs\";\n if (kind === \"exportsessions\") return \"downloads\";\n if (kind === \"openlink\" || kind === \"openprivate\" || kind === \"navlist\" || kind === \"batchopen\" || kind === \"reopentab\" || kind === \"deeplink\") return \"tabs\";\n if (tabscommandactions.has(kind)) return \"tabs\";\n return undefined;\n}\n\n/** True when the kind commands tabs or windows beyond the active tab and needs the optional tabs capability. */\nexport function istabscommandkind(kind: actionkind): boolean {\n return tabscommandactions.has(kind);\n}\n\n/** True when the kind mutates tab groups or layouts and therefore stays inside the active session. */\nexport function islayoutkind(kind: actionkind): boolean {\n return layoutmutationactions.has(kind);\n}\n\n/** True when the kind belongs to the forms and data family. */\nexport function isformkind(kind: actionkind): boolean {\n return formactions.has(kind);\n}\n\n/** True when the kind belongs to the extraction, transform, export and provenance family. */\nexport function isdatasetkind(kind: actionkind): boolean {\n return datasetactions.has(kind);\n}\n\n/** True when the kind exports extracted data out of local memory to disk, the clipboard or a reviewed sheet endpoint. */\nexport function isexportkind(kind: actionkind): boolean {\n return exportactions.has(kind);\n}\n\n/** True when the kind belongs to the files, clipboard and downloads family. */\nexport function isfileskind(kind: actionkind): boolean {\n return filesactions.has(kind);\n}\n\n/** True when the kind belongs to the media capture family of viewport, full page, element, region and contact sheet shots. */\nexport function iscapturekind(kind: actionkind): boolean {\n return captureactions.has(kind);\n}\n\n/** Requires the active tab grant of the live session before any capture kind runs: the session tab and origin must match and the origin grant must cover the active origin. */\nexport function capturegate(session: agentsession | undefined, tabid: number, origin: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the capture.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot capture.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot capture.\" };\n if (session.tabid !== tabid) return { allowed: false, reason: `The capture needs the active tab grant of session tab ${session.tabid} and refuses tab ${tabid}.` };\n if (!origingranted(session, origin)) return { allowed: false, reason: `The capture of ${origin} needs the session origin grants first.` };\n return { allowed: true };\n}\n\n/** Validates one reviewed capture options payload: format inside the png, jpeg and webp set, quality bounded only by the format range, pixel ratio from one up with no code ceiling, and a known export target. */\nexport function validatecaptureoptions(value: unknown): policyevaluation {\n if (value === undefined) return { allowed: true };\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"The reviewed capture options must be an object in options.capture.\" };\n const options = value as Record<string, unknown>;\n if (options.format !== undefined && options.format !== \"png\" && options.format !== \"jpeg\" && options.format !== \"webp\") return { allowed: false, reason: \"The reviewed capture format must be png, jpeg or webp.\" };\n if (options.quality !== undefined && (typeof options.quality !== \"number\" || !Number.isFinite(options.quality) || options.quality < 0 || options.quality > 100)) return { allowed: false, reason: \"The reviewed capture quality must stay between zero and one hundred; any value in that range is the user choice with no code cap.\" };\n if (options.pixelratio !== undefined && (typeof options.pixelratio !== \"number\" || !Number.isFinite(options.pixelratio) || options.pixelratio < 1)) return { allowed: false, reason: \"The reviewed pixel ratio starts at one and climbs to any user configured ceiling with no code ceiling.\" };\n if (options.annotate !== undefined && typeof options.annotate !== \"boolean\") return { allowed: false, reason: \"The reviewed capture annotation flag must be a boolean.\" };\n if (options.exporttarget !== undefined && options.exporttarget !== \"memory\" && options.exporttarget !== \"download\" && options.exporttarget !== \"clipboard\") return { allowed: false, reason: \"The reviewed capture export target must be memory, download or clipboard.\" };\n return { allowed: true };\n}\n\n/** Validates one reviewed region rectangle in css pixels; negative coordinates and non positive sizes are refused. */\nexport function validateregionrect(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed regionrect with x, y, width and height in css pixels is required in options.\" };\n const rect = value as Record<string, unknown>;\n for (const field of [\"x\", \"y\", \"width\", \"height\"]) {\n if (typeof rect[field] !== \"number\" || !Number.isFinite(rect[field] as number)) return { allowed: false, reason: `The reviewed regionrect needs a numeric ${field} in css pixels.` };\n }\n if ((rect.x as number) < 0 || (rect.y as number) < 0) return { allowed: false, reason: \"The reviewed regionrect refuses negative coordinates.\" };\n if ((rect.width as number) <= 0 || (rect.height as number) <= 0) return { allowed: false, reason: \"The reviewed regionrect needs positive width and height values.\" };\n return { allowed: true };\n}\n\n/** Validates one reviewed capture naming rule against the allowed segment set: run, step, sequence and kind flags only. */\nexport function validatecapturenaming(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed capturenaming rule with run, step, sequence and kind flags is required.\" };\n const rule = value as Record<string, unknown>;\n const segments = [\"run\", \"step\", \"sequence\", \"kind\"];\n for (const key of Object.keys(rule)) {\n if (!segments.includes(key)) return { allowed: false, reason: `The reviewed capturenaming rule refuses the unknown ${key} segment; only run, step, sequence and kind participate.` };\n }\n for (const segment of segments) {\n if (rule[segment] !== undefined && typeof rule[segment] !== \"boolean\") return { allowed: false, reason: `The reviewed capturenaming ${segment} flag must be a boolean.` };\n }\n if (!segments.some(segment => rule[segment] === true)) return { allowed: false, reason: \"The reviewed capturenaming rule needs at least one enabled segment of run, step, sequence and kind.\" };\n return { allowed: true };\n}\n\n/** Routes the capture export target: memory stays local, clipboard needs the clipboardwrite grant and disk writes only run through the reviewed download flow. */\nexport function captureexportgranted(target: captureexport | undefined): policyevaluation {\n if (target === undefined || target === \"memory\") return { allowed: true };\n if (target === \"clipboard\") return { allowed: true, reason: \"The clipboard capture export runs behind the optional clipboardwrite capability, negotiated through the permissions api before the copy.\" };\n if (target === \"download\") return { allowed: true, reason: \"The download capture export runs only through the reviewed download flow behind the optional downloads capability.\" };\n return { allowed: false, reason: \"The capture export target must be memory, download or clipboard; no other disk route exists.\" };\n}\n\n/** Keeps the stitching scroll budget inside the reviewed wait window: the settle windows of every tile must fit the reviewed wait window with no code ceiling on either side. */\nexport function stitchbudgetallowed(tiles: number, settle: number, wait: number): policyevaluation {\n if (tiles <= 0) return { allowed: false, reason: \"The stitch budget needs at least one tile.\" };\n if (settle < 0 || wait < 0) return { allowed: false, reason: \"The reviewed settle and wait windows must be zero or positive milliseconds.\" };\n if (tiles * settle > wait) return { allowed: false, reason: `The stitching scroll budget of ${tiles} tiles at ${settle} milliseconds exceeds the reviewed wait window of ${wait} milliseconds; review a wider window or a smaller settle.` };\n return { allowed: true };\n}\n\n/** Allows beforeafter state capture to wrap any existing action kind except the capture kinds themselves; pixel evidence around sensitive actions grades as reviewable evidence. */\nexport function beforeafterwrapallowed(kind: actionkind): boolean {\n return allowedactions.has(kind) && !captureactions.has(kind);\n}\n\n/** Exposes the capture retention window as a user configured choice; an absent value keeps every capture byte forever with no code ceiling. */\nexport function captureretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.captureretention;\n}\n\n/** Validates the reviewed media capture parameter grammar; pixel ratios, quality values, cell counts and retention windows stay user choices with no code ceilings. */\nfunction validatecapturegrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n const optioncheck = validatecaptureoptions(options.capture);\n if (!optioncheck.allowed) return optioncheck;\n if (options.settle !== undefined && (typeof options.settle !== \"number\" || !Number.isFinite(options.settle) || options.settle < 0)) return { allowed: false, reason: \"The reviewed capture settle window must be zero or a positive number of milliseconds.\" };\n if (options.overlap !== undefined && (typeof options.overlap !== \"number\" || !Number.isInteger(options.overlap) || options.overlap < 0)) return { allowed: false, reason: \"The reviewed stitch overlap must be zero or a positive number of rows.\" };\n if (options.wait !== undefined && (typeof options.wait !== \"number\" || !Number.isFinite(options.wait) || options.wait < 0)) return { allowed: false, reason: \"The reviewed capture wait window must be zero or a positive number of milliseconds.\" };\n if (options.naming !== undefined) {\n const namingcheck = validatecapturenaming(options.naming);\n if (!namingcheck.allowed) return namingcheck;\n }\n if (kind === \"shotregion\") {\n const rectcheck = validateregionrect(options.regionrect);\n if (!rectcheck.allowed) return rectcheck;\n if (options.reviewed !== true) return { allowed: false, reason: \"Every reviewed regionrect needs the explicit reviewed flag before shotregion runs.\" };\n if (options.container !== undefined && !isnonempty(options.container)) return { allowed: false, reason: \"The reviewed scrollable container selector must be a non-empty string.\" };\n if (options.steps !== undefined && (typeof options.steps !== \"number\" || !Number.isInteger(options.steps) || options.steps < 1)) return { allowed: false, reason: \"The reviewed container scroll steps must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"contactsheet\") {\n const elements = options.elements;\n if (!Array.isArray(elements) || elements.length === 0 || !elements.every(item => isnonempty(item))) return { allowed: false, reason: \"A reviewed non-empty list of element selectors is required in options for the contact sheet; the cell count stays the user choice.\" };\n const layout = options.sheet;\n if (layout !== undefined) {\n if (!layout || typeof layout !== \"object\" || Array.isArray(layout)) return { allowed: false, reason: \"The reviewed sheetlayout must be an object with cellsize, columns and label.\" };\n const sheet = layout as Record<string, unknown>;\n if (typeof sheet.cellsize !== \"number\" || !Number.isFinite(sheet.cellsize) || sheet.cellsize <= 0) return { allowed: false, reason: \"The reviewed contact sheet cell size must be a positive number of pixels.\" };\n if (typeof sheet.columns !== \"number\" || !Number.isInteger(sheet.columns) || sheet.columns < 1) return { allowed: false, reason: \"The reviewed contact sheet column count must be a positive integer with no code ceiling.\" };\n if (sheet.label !== undefined && sheet.label !== \"none\" && sheet.label !== \"index\" && sheet.label !== \"selector\" && sheet.label !== \"both\") return { allowed: false, reason: \"The reviewed contact sheet label style must be none, index, selector or both.\" };\n }\n }\n return { allowed: true };\n}\n\n/** Refuses any export that would leave local memory while the session origin grants do not cover the active origin. */\nexport function exportgranted(session: agentsession | undefined, origin: string): policyevaluation {\n if (!origingranted(session, origin)) return { allowed: false, reason: `The export of extracted data from ${origin} needs the session origin grants before it leaves local memory.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed fieldmatch grammar of one form field entry. */\nexport function validatefieldmatch(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed field match is required in options.\" };\n const match = value as Record<string, unknown>;\n if (match.mode !== \"label\" && match.mode !== \"placeholder\" && match.mode !== \"arialabel\" && match.mode !== \"name\") return { allowed: false, reason: \"The reviewed field match mode must be label, placeholder, arialabel or name.\" };\n const key = match.mode === \"label\" ? \"label\" : match.mode === \"placeholder\" ? \"placeholder\" : match.mode === \"arialabel\" ? \"arialabel\" : \"name\";\n if (!isnonempty(match[key])) return { allowed: false, reason: `The reviewed ${match.mode} field match needs a non-empty ${key}.` };\n return { allowed: true };\n}\n\n/** Validates a reviewed structured form record; password entries are refused because passwords need the explicit consentpassword consent. */\nexport function validateformrecord(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed form record with entries is required in options.\" };\n const record = value as Record<string, unknown>;\n if (record.form !== undefined && !isnonempty(record.form)) return { allowed: false, reason: \"The reviewed form record form selector must be a non-empty string.\" };\n if (!Array.isArray(record.entries) || record.entries.length === 0) return { allowed: false, reason: \"The reviewed form record needs a non-empty list of entries.\" };\n for (const item of record.entries) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every reviewed form record entry must be an object.\" };\n const entry = item as Record<string, unknown>;\n const matchcheck = validatefieldmatch(entry.match);\n if (!matchcheck.allowed) return matchcheck;\n if (typeof entry.kind !== \"string\" || !fieldkinds.includes(entry.kind as fieldkind)) return { allowed: false, reason: \"Every reviewed form record entry needs a known field kind.\" };\n if (typeof entry.value !== \"string\") return { allowed: false, reason: \"Every reviewed form record entry needs a string value.\" };\n if (entry.kind === \"password\") return { allowed: false, reason: \"Password entries are refused inside form records; use consentpassword with a reviewed consent ref.\" };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed valuegen grammar of a generatevalues step. */\nexport function validatevaluegen(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed valuegen rule with a field kind is required in options.\" };\n const rule = value as Record<string, unknown>;\n if (typeof rule.kind !== \"string\" || !fieldkinds.includes(rule.kind as fieldkind)) return { allowed: false, reason: \"The reviewed valuegen kind must be a known field kind.\" };\n if (rule.locale !== undefined && !isnonempty(rule.locale)) return { allowed: false, reason: \"The reviewed valuegen locale must be a non-empty string.\" };\n if (rule.seed !== undefined && (typeof rule.seed !== \"number\" || !Number.isFinite(rule.seed))) return { allowed: false, reason: \"The reviewed valuegen seed must be a finite number.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed list of label or placeholder value pairs for filllabel and fillplaceholder steps. */\nfunction validatefieldpairs(options: Record<string, unknown>, mode: \"label\" | \"placeholder\"): policyevaluation {\n const pairs = options.fields;\n if (!Array.isArray(pairs) || pairs.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of field pairs is required in options.\" };\n for (const item of pairs) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every reviewed field pair must be an object.\" };\n const pair = item as Record<string, unknown>;\n if (!isnonempty(pair[mode])) return { allowed: false, reason: `Every reviewed field pair needs a non-empty ${mode}.` };\n if (typeof pair.value !== \"string\" || !pair.value.trim()) return { allowed: false, reason: \"Every reviewed field pair needs a non-empty value.\" };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed card segment grammar of a fillcard step. */\nfunction validatecardsegments(value: unknown): policyevaluation {\n if (!Array.isArray(value) || value.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of card segments is required in options.\" };\n for (const item of value) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every reviewed card segment must be an object.\" };\n const segment = item as Record<string, unknown>;\n const matchcheck = validatefieldmatch(segment.match);\n if (!matchcheck.allowed) return matchcheck;\n if (typeof segment.value !== \"string\" || !segment.value.trim()) return { allowed: false, reason: \"Every reviewed card segment needs a non-empty value.\" };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed forms and data parameter grammar of the form family. */\nfunction validateformgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"fillform\" || (kind === \"saveprofiles\" && options.formrecord !== undefined)) {\n const recordcheck = validateformrecord(options.formrecord);\n if (!recordcheck.allowed) return recordcheck;\n }\n if (kind === \"filllabel\" || kind === \"fillplaceholder\") {\n const paircheck = validatefieldpairs(options, kind === \"filllabel\" ? \"label\" : \"placeholder\");\n if (!paircheck.allowed) return paircheck;\n }\n if (kind === \"generatevalues\" && options.valuegen !== undefined) {\n const rulecheck = validatevaluegen(options.valuegen);\n if (!rulecheck.allowed) return rulecheck;\n }\n if (kind === \"saveprofiles\" && !isnonempty(options.name)) return { allowed: false, reason: \"A reviewed profile name is required in options.\" };\n if (kind === \"submitform\" && !isnonempty(options.consentref)) return { allowed: false, reason: \"A reviewed consent ref of an approved asksubmit ticket is required in options.\" };\n if (kind === \"retryform\") {\n const backoff = options.backoff;\n if (!backoff || typeof backoff !== \"object\" || Array.isArray(backoff)) return { allowed: false, reason: \"A reviewed backoff rule with wait and factor is required in options.\" };\n const rule = backoff as Record<string, unknown>;\n if (typeof rule.wait !== \"number\" || !Number.isFinite(rule.wait) || rule.wait <= 0) return { allowed: false, reason: \"The reviewed retry backoff wait must be a positive number of milliseconds with no code ceiling.\" };\n if (typeof rule.factor !== \"number\" || !Number.isFinite(rule.factor) || rule.factor < 1) return { allowed: false, reason: \"The reviewed retry backoff factor must be one or greater with no code ceiling.\" };\n if (options.attempts !== undefined && (typeof options.attempts !== \"number\" || !Number.isInteger(options.attempts) || options.attempts < 1)) return { allowed: false, reason: \"The reviewed retry attempts must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"runwizard\" && options.steps !== undefined && (typeof options.steps !== \"number\" || !Number.isInteger(options.steps) || options.steps < 1)) return { allowed: false, reason: \"The reviewed wizard step count must be a positive integer with no code ceiling.\" };\n if (kind === \"selectchain\") {\n if (!isnonempty(options.child)) return { allowed: false, reason: \"A reviewed child selector of the dependent control is required in options.\" };\n if (!nonnegativeoption(options, \"wait\")) return { allowed: false, reason: \"The reviewed dependent wait must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"picktypeahead\") {\n if (!isnonempty(options.pick)) return { allowed: false, reason: \"A reviewed suggestion entry to pick is required in options.\" };\n if (!nonnegativeoption(options, \"timeout\")) return { allowed: false, reason: \"The reviewed typeahead timeout must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"pickdate\" && !/^\\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 (kind === \"fillcard\") {\n const segmentcheck = validatecardsegments(options.segments);\n if (!segmentcheck.allowed) return segmentcheck;\n if (!nonnegativeoption(options, \"pause\")) return { allowed: false, reason: \"The reviewed card typing pause must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"fillcode\" && !isnonempty(options.source)) return { allowed: false, reason: \"A reviewed one time code source is required in options.\" };\n if (kind === \"consentpassword\" && !isnonempty(options.consentref)) return { allowed: false, reason: \"A reviewed consent ref is required in options before any password is filled.\" };\n return { allowed: true };\n}\n\n/** Validates one reviewed transform rule: a supported expression, source columns and a target column. */\nexport function validatetransformrule(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed transform rule with an expression, sources and a target is required in options.\" };\n const rule = value as Record<string, unknown>;\n const expression = rule.expression;\n if (typeof expression !== \"string\" || !/^(trim|upper|lower|number|prefix|suffix|replace)(?::.+)?$/.test(expression)) return { allowed: false, reason: \"The reviewed transform expression must be trim, upper, lower, number, prefix, suffix or replace with an optional argument.\" };\n if (expression.startsWith(\"replace\") && !expression.slice(\"replace\".length).includes(\"=>\")) return { allowed: false, reason: \"The reviewed replace expression needs the from=>to separator.\" };\n if (expression.startsWith(\"replace\") && expression.slice(\"replace:\".length).split(\"=>\")[0] === \"\") return { allowed: false, reason: \"The reviewed replace expression needs a non-empty from part.\" };\n if (!Array.isArray(rule.sources) || rule.sources.length === 0 || !rule.sources.every(source => isnonempty(source))) return { allowed: false, reason: \"Every reviewed transform rule needs a non-empty list of source columns.\" };\n if (!isnonempty(rule.target)) return { allowed: false, reason: \"Every reviewed transform rule needs a non-empty target column.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed dataset id list in options. */\nfunction validatedatasetids(options: Record<string, unknown>, key: string): policyevaluation {\n const ids = options[key];\n if (!Array.isArray(ids) || ids.length === 0 || !ids.every(id => isnonempty(id))) return { allowed: false, reason: `A reviewed non-empty list of dataset ids is required in options as ${key}.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed extraction, transform, export and provenance parameter grammar of the data family. */\nfunction validatedatagrammar(step: toolstep, options: Record<string, unknown>, origin: string): policyevaluation {\n const kind = step.kind;\n if (kind === \"scrapetable\") {\n if (options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed dataset name must be a non-empty string.\" };\n if (options.rowlimit !== undefined && (typeof options.rowlimit !== \"number\" || !Number.isInteger(options.rowlimit) || options.rowlimit < 1)) return { allowed: false, reason: \"The reviewed row limit must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"paginateextract\") {\n if (!isnonempty(options.next)) return { allowed: false, reason: \"A reviewed next control selector is required in options.\" };\n if (options.pages !== undefined && (typeof options.pages !== \"number\" || !Number.isInteger(options.pages) || options.pages < 1)) return { allowed: false, reason: \"The reviewed page count must be a positive integer with no code ceiling.\" };\n if (!nonnegativeoption(options, \"wait\")) return { allowed: false, reason: \"The reviewed row freshness wait must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"exportcsv\" || kind === \"exportjson\" || kind === \"exportexcel\" || kind === \"copytable\" || kind === \"streamdisk\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed artifact name must be a non-empty string.\" };\n }\n if (kind === \"exportcsv\" && options.delimiter !== undefined && (typeof options.delimiter !== \"string\" || options.delimiter.length !== 1)) return { allowed: false, reason: \"The reviewed csv delimiter must be a single character.\" };\n if (kind === \"streamdisk\" && (typeof options.chunk !== \"number\" || !Number.isInteger(options.chunk) || options.chunk < 1)) return { allowed: false, reason: \"The reviewed streaming chunk size must be a positive integer with no code ceiling.\" };\n if (kind === \"pushsheets\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (!isnonempty(options.sheet)) return { allowed: false, reason: \"A reviewed sheet endpoint url is required in options.\" };\n if (!ishttpsurl(options.sheet)) return { allowed: false, reason: \"The reviewed sheet endpoint url must use HTTPS.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"The sheet push needs the explicit reviewed flag before any data leaves local memory.\" };\n }\n if (kind === \"importcsv\") {\n if (typeof options.csv !== \"string\" || !options.csv.trim()) return { allowed: false, reason: \"Reviewed csv content is required in options.\" };\n if (options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed dataset name must be a non-empty string.\" };\n if (options.mapping !== undefined) {\n const mapping = options.mapping;\n if (!mapping || typeof mapping !== \"object\" || Array.isArray(mapping) || !Object.values(mapping).every(item => typeof item === \"string\")) return { allowed: false, reason: \"The reviewed csv column mapping must be an object of string values.\" };\n }\n }\n if (kind === \"looprows\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (options.variable !== undefined && !isnonempty(options.variable)) return { allowed: false, reason: \"The reviewed row variable name must be a non-empty string.\" };\n const inner = validateinnerstep(options, origin);\n if (!inner.allowed) return inner;\n }\n if (kind === \"transformvalues\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n const rules = options.rules;\n if (!Array.isArray(rules) || rules.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of transform rules is required in options.\" };\n for (const item of rules) {\n const rulecheck = validatetransformrule(item);\n if (!rulecheck.allowed) return rulecheck;\n }\n }\n if (kind === \"deduperows\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n const keys = options.keys;\n if (!Array.isArray(keys) || keys.length === 0 || !keys.every(key => isnonempty(key))) return { allowed: false, reason: \"A reviewed non-empty list of dedupe column keys is required in options.\" };\n }\n if (kind === \"mergepages\") {\n const listcheck = validatedatasetids(options, \"datasets\");\n if (!listcheck.allowed) return listcheck;\n }\n if (kind === \"stamplerows\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (options.url !== undefined && !ishttpsurl(options.url)) return { allowed: false, reason: \"The reviewed source url must use HTTPS.\" };\n }\n if (kind === \"previewgrid\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (options.sample !== undefined && (typeof options.sample !== \"number\" || !Number.isInteger(options.sample) || options.sample < 1)) return { allowed: false, reason: \"The reviewed sample row count must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"resumeextract\" && !isnonempty(options.session)) return { allowed: false, reason: \"A reviewed extract session id is required in options.\" };\n if (kind === \"logprovenance\" && !isnonempty(options.artifact)) return { allowed: false, reason: \"A reviewed artifact id or name is required in options.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed batch download specification: a non-empty HTTPS url list, an optional filename rule and an optional completion criterion. */\nexport function validatedownloadspec(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed downloadspec with a url list is required in options.\" };\n const spec = value as Record<string, unknown>;\n if (!Array.isArray(spec.urls) || spec.urls.length === 0 || !spec.urls.every(url => ishttpsurl(url))) return { allowed: false, reason: \"The reviewed downloadspec needs a non-empty list of HTTPS urls.\" };\n if (spec.filename !== undefined && !isnonempty(spec.filename)) return { allowed: false, reason: \"The reviewed downloadspec filename rule must be a non-empty string.\" };\n if (spec.complete !== undefined && spec.complete !== \"size\" && spec.complete !== \"checksum\") return { allowed: false, reason: \"The reviewed downloadspec completion criterion must be size or checksum.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed mime interception filter: include and exclude patterns plus the deny default for unlisted mime types. */\nexport function validatemimefilter(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed mimefilter with include and exclude patterns is required in options.\" };\n const filter = value as Record<string, unknown>;\n if (!Array.isArray(filter.include) || filter.include.length === 0 || !filter.include.every(pattern => isnonempty(pattern))) return { allowed: false, reason: \"The reviewed mimefilter needs a non-empty list of include patterns.\" };\n if (filter.exclude !== undefined && (!Array.isArray(filter.exclude) || !filter.exclude.every(pattern => isnonempty(pattern)))) return { allowed: false, reason: \"The reviewed mimefilter exclude patterns must be a list of non-empty strings.\" };\n if (filter.default !== \"deny\" && filter.default !== \"allow\") return { allowed: false, reason: \"The reviewed mimefilter needs the deny or allow default for unlisted mime types.\" };\n return { allowed: true };\n}\n\n/** Validates one reviewed cleanup rule: a positive age window with no code ceiling, an artifact kind and a keep policy. */\nexport function validatecleanuprule(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed cleanuprule with an age, a kind and a keep policy is required.\" };\n const rule = value as Record<string, unknown>;\n if (typeof rule.age !== \"number\" || !Number.isFinite(rule.age) || rule.age <= 0) return { allowed: false, reason: \"The reviewed cleanup age window must be a positive number of milliseconds with no code ceiling.\" };\n if (!isnonempty(rule.kind)) return { allowed: false, reason: \"The reviewed cleanup rule needs a non-empty artifact kind, or any to match every kind.\" };\n if (rule.keep !== \"none\" && rule.keep !== \"latest\" && rule.keep !== \"all\") return { allowed: false, reason: \"The reviewed cleanup keep policy must be none, latest or all.\" };\n return { allowed: true };\n}\n\n/** Validates the reviewed files, clipboard and downloads parameter grammar; batch sizes, concurrent windows and cleanup ages stay user configured with no code ceilings. */\nfunction validatefilesgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"batchdownload\") {\n const speccheck = validatedownloadspec(options.downloadspec);\n if (!speccheck.allowed) return speccheck;\n if (options.concurrent !== undefined && (typeof options.concurrent !== \"number\" || !Number.isInteger(options.concurrent) || options.concurrent < 1)) return { allowed: false, reason: \"The reviewed concurrent download window must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"pausedownload\" || kind === \"resumedownload\" || kind === \"verifydownload\" || kind === \"quarantinedownload\" || kind === \"scanvirus\") {\n if (!isnonempty(step.value)) return { allowed: false, reason: \"A reviewed download or quarantine reference is required.\" };\n if (kind === \"verifydownload\") {\n if (options.checksum !== undefined && !isnonempty(options.checksum)) return { allowed: false, reason: \"The reviewed expected checksum must be a non-empty string.\" };\n if (options.bytes !== undefined && (typeof options.bytes !== \"number\" || !Number.isFinite(options.bytes) || options.bytes < 0)) return { allowed: false, reason: \"The reviewed expected size must be zero or a positive number of bytes.\" };\n }\n if (kind === \"scanvirus\" && options.scanner !== undefined && !isnonempty(options.scanner)) return { allowed: false, reason: \"The reviewed scanner name must be a non-empty string.\" };\n if (kind === \"quarantinedownload\" && options.reason !== undefined && !isnonempty(options.reason)) return { allowed: false, reason: \"The reviewed quarantine reason must be a non-empty string.\" };\n }\n if (kind === \"interceptmime\") {\n const filtercheck = validatemimefilter(options.mimefilter);\n if (!filtercheck.allowed) return filtercheck;\n }\n if (kind === \"readclipboard\") {\n if (!isnonempty(options.consentref)) return { allowed: false, reason: \"A clipboard read requires a reviewed consent ref of an approved consent prompt in options.\" };\n if (options.prompt !== undefined && !isnonempty(options.prompt)) return { allowed: false, reason: \"The reviewed clipboard consent prompt must be a non-empty string.\" };\n }\n if (kind === \"exportnetlog\" && options.stepid !== undefined && !isnonempty(options.stepid)) return { allowed: false, reason: \"The reviewed netlog step filter must be a non-empty step id.\" };\n if (kind === \"namecaptures\") {\n if (!isnonempty(options.task)) return { allowed: false, reason: \"A reviewed task id is required in options for capture naming.\" };\n if (options.steps !== undefined && (!Array.isArray(options.steps) || options.steps.length === 0 || !options.steps.every(item => isnonempty(item)))) return { allowed: false, reason: \"The reviewed capture steps must be a non-empty list of step ids when present.\" };\n if (options.extension !== undefined && !isnonempty(options.extension)) return { allowed: false, reason: \"The reviewed capture extension must be a non-empty string.\" };\n }\n if (kind === \"cleanupartifacts\" && options.rules !== undefined) {\n const rules = options.rules;\n if (!Array.isArray(rules) || rules.length === 0) return { allowed: false, reason: \"The reviewed cleanup rules must be a non-empty list when present.\" };\n for (const item of rules) {\n const rulecheck = validatecleanuprule(item);\n if (!rulecheck.allowed) return rulecheck;\n }\n }\n return { allowed: true };\n}\n\n/** Requires an approved consent prompt before any clipboard read; every read consumes its own prompt. */\nexport function clipboardconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"A clipboard read requires a reviewed consent ref in options.\" };\n return { allowed: true };\n}\n\n/** Refuses to release any quarantined file before a clean scan verdict exists. */\nexport function quarantinereleasegranted(entry: quarantineentry): policyevaluation {\n if (entry.scan !== \"clean\") return { allowed: false, reason: `The quarantined file ${entry.path} cannot leave quarantine with the ${entry.scan} scan verdict; only a clean verdict releases it.` };\n return { allowed: true };\n}\n\n/** Refuses downloads and download interception that fall outside the session origin grants. */\nexport function downloadgranted(session: agentsession | undefined, url: string): policyevaluation {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { return { allowed: false, reason: \"The reviewed download URL is invalid.\" }; }\n if (!origingranted(session, origin)) return { allowed: false, reason: `The download from ${origin} leaves the session origin grants and needs a session grant first.` };\n return { allowed: true };\n}\n\n/** Masks a clipboard payload for every log line; the full text never persists anywhere. */\nexport function maskclipboard(payload: string): string {\n return `[clipboard payload of ${payload.length} character${payload.length === 1 ? \"\" : \"s\"}]`;\n}\n\n/** Requires an asksubmit review step before every form submission step. */\nexport function submitreviewgranted(steps: toolstep[], submitid: string): policyevaluation {\n const position = steps.findIndex(candidate => candidate.id === submitid);\n const asked = steps.some((candidate, index) => candidate.kind === \"asksubmit\" && (position === -1 || index < position));\n return asked ? { allowed: true } : { allowed: false, reason: \"Form submission requires an asksubmit review step before it.\" };\n}\n\n/** Requires a reviewed consent ref before any password field is filled. */\nexport function passwordconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"A password fill requires a reviewed consent ref in options.\" };\n return { allowed: true };\n}\n\n/** True when a numeric value passes the Luhn checksum used by card networks. */\nfunction luhnvalid(digits: string): boolean {\n let sum = 0;\n let double = false;\n for (let index = digits.length - 1; index >= 0; index -= 1) {\n let value = Number.parseInt(digits[index] ?? \"\", 10);\n if (!Number.isFinite(value)) return false;\n if (double) { value *= 2; if (value > 9) value -= 9; }\n sum += value;\n double = !double;\n }\n return sum % 10 === 0;\n}\n\n/** Refuses generated values that look like real card numbers or personal identifiers; test prefixed card values stay allowed. */\nexport function generatedvalueallowed(value: string): policyevaluation {\n const compact = value.replace(/[\\s-]/g, \"\");\n if (/^\\d{13,19}$/.test(compact) && luhnvalid(compact) && !compact.startsWith(\"4111\")) return { allowed: false, reason: \"The generated value looks like a real card number and is refused; generated card values use the 4111 test prefix.\" };\n if (/^\\d{3}-\\d{2}-\\d{4}$/.test(value.trim())) return { allowed: false, reason: \"The generated value looks like a personal identifier and is refused.\" };\n return { allowed: true };\n}\n\n/** Requires the origin grants of a saved profile to cover the origin before its values fill a page. */\nexport function profilegrantgranted(profile: formprofile, origin: string): policyevaluation {\n if (!profile.grants.includes(origin)) return { allowed: false, reason: `The saved profile ${profile.name} is not granted to ${origin}; add the origin to the profile grants first.` };\n return { allowed: true };\n}\n\n/** Restricts group and layout mutations to the active session: they refuse without a live session. */\nexport function layoutmutationgranted(session: agentsession | undefined, now: number): policyevaluation {\n if (!session || session.stoppedat || session.expiresat <= now) return { allowed: false, reason: \"Group and layout mutations stay inside the active session.\" };\n return { allowed: true };\n}\n\n/** Requires explicit review before closing a window that holds more than one task tab. */\nexport function windowclosegate(tasktabcount: number, reviewed: boolean): policyevaluation {\n if (tasktabcount > 1 && !reviewed) return { allowed: false, reason: `The window holds ${tasktabcount} task tabs and needs explicit review before it closes.` };\n return { allowed: true };\n}\n\n/** Reads the user configured concurrent task tab ceiling; an absent value never refuses a tab. */\nexport function tasktabceiling(settings: runsettings | undefined): number | undefined {\n const ceiling = settings?.tasktabceiling;\n return typeof ceiling === \"number\" && Number.isFinite(ceiling) && ceiling >= 0 ? ceiling : 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/** Decides whether an unreviewed origin may open: the session grants cover it or a safe checksafe verdict vouches for it. */\nexport function originverified(url: string, grants: string[], verdicts: safetyverdict[]): policyevaluation {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { return { allowed: false, reason: \"The reviewed navigation URL is invalid.\" }; }\n if (grants.includes(origin)) return { allowed: true };\n const covered = verdicts.find(verdict => verdict.safe && (verdict.url === url || (safeorigin(verdict.url) === origin)));\n if (covered) return { allowed: true };\n return { allowed: false, reason: `The origin ${origin} is outside the session grants and has no safe checksafe verdict; run checksafe and review it first.` };\n}\n\nfunction safeorigin(url: string): string {\n try { return new URL(url).origin; } catch { return \"\"; }\n}\n\n/** Refuses navigation that would move a granted task tab outside the session origin grants until the user consents. */\nexport function navigationgranted(session: agentsession | undefined, url: string): policyevaluation {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { return { allowed: false, reason: \"The reviewed navigation URL is invalid.\" }; }\n if (origingranted(session, origin)) return { allowed: true };\n return { allowed: false, reason: `Navigation to ${origin} leaves the task tab origins and needs the user consent of a session grant first.` };\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\" || kind === \"looprows\") 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/** True when a reviewed https url parses. */\nfunction ishttpsurl(value: unknown): value is string {\n if (typeof value !== \"string\" || !value.trim()) return false;\n try { return new URL(value).protocol === \"https:\"; } catch { return false; }\n}\n\n/** Validates the reviewed navtarget grammar of a navigation step. */\nfunction validatenavtarget(value: unknown, kind: string): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed navtarget with a url is required in options.\" };\n const target = value as Record<string, unknown>;\n if (!ishttpsurl(target.url)) return { allowed: false, reason: \"The reviewed navtarget url must use HTTPS.\" };\n const container = target.container ?? \"tab\";\n if (container !== \"current\" && container !== \"tab\" && container !== \"window\" && container !== \"private\") return { allowed: false, reason: \"The reviewed navtarget container must be current, tab, window or private.\" };\n if (target.position !== undefined && target.position !== \"adjacent\" && target.position !== \"end\") return { allowed: false, reason: \"The reviewed navtarget position must be adjacent or end.\" };\n if (kind === \"openprivate\" && container !== \"private\") return { allowed: false, reason: \"The openprivate step requires the private container.\" };\n if (kind === \"openlink\" && container === \"private\") return { allowed: false, reason: \"The openlink step cannot open the private container; use openprivate.\" };\n return { allowed: true };\n}\n\n/** Validates the reviewed waitprofile grammar with its load signals, thresholds and per origin overrides. */\nfunction validatewaitprofile(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed waitprofile with load signals is required in options.\" };\n const profile = value as Record<string, unknown>;\n if (!Array.isArray(profile.signals) || profile.signals.length === 0 || !profile.signals.every(signal => isnonempty(signal))) return { allowed: false, reason: \"The reviewed waitprofile needs a non-empty list of load signals.\" };\n if (!nonnegativeoption(profile, \"idle\")) return { allowed: false, reason: \"The reviewed waitprofile idle threshold must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(profile, \"timeout\")) return { allowed: false, reason: \"The reviewed waitprofile timeout must be zero or a positive number of milliseconds.\" };\n if (profile.overrides !== undefined) {\n if (!Array.isArray(profile.overrides) || profile.overrides.length === 0) return { allowed: false, reason: \"The reviewed waitprofile overrides must be a non-empty list when present.\" };\n for (const entry of profile.overrides) {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return { allowed: false, reason: \"Every reviewed waitprofile override must be an object with an origin.\" };\n const override = entry as Record<string, unknown>;\n if (!ishttpsurl(override.origin)) return { allowed: false, reason: \"Every reviewed waitprofile override origin must use HTTPS.\" };\n if (override.signals !== undefined && (!Array.isArray(override.signals) || !override.signals.every(signal => isnonempty(signal)))) return { allowed: false, reason: \"The reviewed waitprofile override signals must be a list of non-empty strings.\" };\n if (!nonnegativeoption(override, \"idle\") || !nonnegativeoption(override, \"timeout\")) return { allowed: false, reason: \"The reviewed waitprofile override thresholds must be zero or positive numbers.\" };\n }\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed urlpattern grammar with its match mode plus query and fragment parts. */\nexport function validateurlpattern(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed urlpattern is required in options.\" };\n const pattern = value as Record<string, unknown>;\n if (pattern.mode !== \"exact\" && pattern.mode !== \"prefix\" && pattern.mode !== \"host\" && pattern.mode !== \"pattern\") return { allowed: false, reason: \"The reviewed urlpattern mode must be exact, prefix, host or pattern.\" };\n if (!ishttpsurl(pattern.url)) return { allowed: false, reason: \"The reviewed urlpattern url must use HTTPS.\" };\n if (pattern.query !== undefined) {\n if (!pattern.query || typeof pattern.query !== \"object\" || Array.isArray(pattern.query)) return { allowed: false, reason: \"The reviewed urlpattern query part must be an object of parameter names and values.\" };\n for (const item of Object.values(pattern.query)) if (typeof item !== \"string\") return { allowed: false, reason: \"The reviewed urlpattern query values must be strings.\" };\n }\n if (pattern.fragment !== undefined && !isnonempty(pattern.fragment)) return { allowed: false, reason: \"The reviewed urlpattern fragment must be a non-empty string.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed non-empty list of HTTPS urls in options. */\nfunction validateurllist(options: Record<string, unknown>, key: string): policyevaluation {\n const urls = options[key];\n if (!Array.isArray(urls) || urls.length === 0 || !urls.every(url => ishttpsurl(url))) return { allowed: false, reason: `A reviewed non-empty list of HTTPS urls is required in options as ${key}.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed ratelimit grammar of a navrate step; the window and ceiling stay user configured with no hardcoded cap. */\nfunction validateratelimit(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed ratelimit with a window and a ceiling is required in options.\" };\n const limit = value as Record<string, unknown>;\n if (limit.domain !== undefined && !isnonempty(limit.domain)) return { allowed: false, reason: \"The reviewed ratelimit domain must be a non-empty string.\" };\n if (typeof limit.window !== \"number\" || !Number.isFinite(limit.window) || limit.window <= 0) return { allowed: false, reason: \"The reviewed ratelimit window must be a positive number of milliseconds with no code ceiling.\" };\n if (typeof limit.ceiling !== \"number\" || !Number.isInteger(limit.ceiling) || limit.ceiling < 1) return { allowed: false, reason: \"The reviewed ratelimit ceiling must be a positive integer with no code ceiling.\" };\n return { allowed: true };\n}\n\n/** Validates the reviewed tabquery grammar with url, title, id and pattern matchers; at least one matcher is required. */\nexport function validatetabquery(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed tabquery with at least one matcher is required in options.\" };\n const query = value as Record<string, unknown>;\n const hasmatcher = query.url !== undefined || query.title !== undefined || query.id !== undefined || query.pattern !== undefined;\n if (!hasmatcher) return { allowed: false, reason: \"The reviewed tabquery needs a url, title, id or pattern matcher.\" };\n if (query.url !== undefined && !isnonempty(query.url)) return { allowed: false, reason: \"The reviewed tabquery url matcher must be a non-empty string.\" };\n if (query.title !== undefined && !isnonempty(query.title)) return { allowed: false, reason: \"The reviewed tabquery title matcher must be a non-empty string.\" };\n if (query.pattern !== undefined && !isnonempty(query.pattern)) return { allowed: false, reason: \"The reviewed tabquery pattern matcher must be a non-empty string.\" };\n if (query.id !== undefined && (typeof query.id !== \"number\" || !Number.isInteger(query.id) || query.id < 0)) return { allowed: false, reason: \"The reviewed tabquery id matcher must be a non-negative integer tab id.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed group color choice against the Chromium tab group palette. */\nfunction validategroupcolor(value: unknown): boolean {\n return typeof value === \"string\" && (groupcolors as string[]).includes(value);\n}\n\n/** Validates a reviewed list of numeric browser ids in options. */\nfunction validateidlist(options: Record<string, unknown>, key: string): boolean {\n const ids = options[key];\n return Array.isArray(ids) && ids.length > 0 && ids.every(id => typeof id === \"number\" && Number.isInteger(id) && id >= 0);\n}\n\n/** Validates the reviewed tab and window parameter grammar of the tabs and windows command family. */\nfunction validatetabsgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"querytabs\" || kind === \"closepattern\") {\n const querycheck = validatetabquery(options.tabquery);\n if (!querycheck.allowed) return querycheck;\n if (kind === \"closepattern\" && options.reviewed !== true) return { allowed: false, reason: \"The close pattern needs the explicit reviewed flag before any tab closes.\" };\n }\n if (kind === \"duplicatetab\" || kind === \"pintab\" || kind === \"mutetab\" || kind === \"movetab\" || kind === \"movetabwindow\" || kind === \"badgetab\" || kind === \"attachmeta\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser tab id is required.\" };\n }\n if (kind === \"focuswindow\" || kind === \"maximizewindow\" || kind === \"minimizewindow\" || kind === \"restorewindow\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser window id is required.\" };\n }\n if (kind === \"pintab\" && typeof options.pinned !== \"boolean\") return { allowed: false, reason: \"A reviewed pinned flag is required in options.\" };\n if (kind === \"mutetab\" && typeof options.muted !== \"boolean\") return { allowed: false, reason: \"A reviewed muted flag is required in options.\" };\n if (kind === \"movetab\") {\n if (typeof options.index !== \"number\" || !Number.isInteger(options.index) || options.index < 0) return { allowed: false, reason: \"A reviewed non-negative target index is required in options.\" };\n }\n if (kind === \"movetabwindow\") {\n if (typeof options.windowid !== \"number\" || !Number.isInteger(options.windowid) || options.windowid < 0) return { allowed: false, reason: \"A reviewed target window id is required in options.\" };\n }\n if (kind === \"grouptabs\") {\n const group = options.group;\n if (!group || typeof group !== \"object\" || Array.isArray(group)) return { allowed: false, reason: \"A reviewed group with a name is required in options.\" };\n const spec = group as Record<string, unknown>;\n if (!isnonempty(spec.name)) return { allowed: false, reason: \"The reviewed group needs a non-empty name.\" };\n if (!validategroupcolor(spec.color)) return { allowed: false, reason: \"The reviewed group color must be a Chromium tab group color.\" };\n if (!validateidlist(spec, \"tabids\")) return { allowed: false, reason: \"The reviewed group needs a non-empty list of member tab ids.\" };\n }\n if (kind === \"colorgroup\") {\n if (!isnonempty(options.name)) return { allowed: false, reason: \"A reviewed group name is required in options.\" };\n if (!validategroupcolor(options.color)) return { allowed: false, reason: \"The reviewed group color must be a Chromium tab group color.\" };\n }\n if (kind === \"collapsegroup\") {\n if (!isnonempty(options.name)) return { allowed: false, reason: \"A reviewed group name is required in options.\" };\n if (typeof options.collapsed !== \"boolean\") return { allowed: false, reason: \"A reviewed collapsed flag is required in options.\" };\n }\n if (kind === \"discardtab\" || kind === \"reloadtabs\") {\n if (!isnumericid(step.value) && !validateidlist(options, \"tabs\")) return { allowed: false, reason: \"A numeric tab id or a reviewed list of tab ids is required.\" };\n }\n if (kind === \"zoomin\" || kind === \"zoomout\") {\n if (options.step !== undefined && (typeof options.step !== \"number\" || !Number.isFinite(options.step) || options.step <= 0)) return { allowed: false, reason: \"The reviewed zoom step must be a positive number with no code ceiling.\" };\n if (step.value !== undefined && step.value !== \"\" && !isnumericid(step.value)) return { allowed: false, reason: \"The reviewed zoom target must be a numeric tab id.\" };\n }\n if (kind === \"switchtab\") {\n if (options.direction !== \"next\" && options.direction !== \"previous\") return { allowed: false, reason: \"A reviewed switch direction of next or previous is required in options.\" };\n }\n if (kind === \"restorewindow\") {\n const bounds = options.bounds;\n if (bounds !== undefined) {\n if (!bounds || typeof bounds !== \"object\" || Array.isArray(bounds)) return { allowed: false, reason: \"The reviewed window bounds must be an object.\" };\n const shape = bounds as Record<string, unknown>;\n for (const field of [\"left\", \"top\", \"width\", \"height\"]) {\n if (typeof shape[field] !== \"number\" || !Number.isFinite(shape[field])) return { allowed: false, reason: \"The reviewed window bounds need numeric left, top, width and height.\" };\n }\n }\n }\n if (kind === \"scratchwindow\") {\n if (step.value !== undefined && step.value !== \"\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"The reviewed scratch window url must use HTTPS.\" };\n }\n if (kind === \"incognitowindow\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"A reviewed HTTPS url is required to open an incognito window.\" };\n if (kind === \"restoretab\" && step.value !== undefined && step.value !== \"\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"The reviewed restore url must use HTTPS.\" };\n if (kind === \"savelayout\" || kind === \"restorelayout\") {\n if (!isnonempty(options.name)) return { allowed: false, reason: \"A reviewed layout name is required in options.\" };\n }\n if (kind === \"badgetab\") {\n if (!isnonempty(options.label)) return { allowed: false, reason: \"A reviewed badge label is required in options.\" };\n if (options.taskid !== undefined && !isnonempty(options.taskid)) return { allowed: false, reason: \"The reviewed badge task id must be a non-empty string.\" };\n }\n if (kind === \"attachmeta\") {\n const labels = options.labels;\n const taskrefs = options.taskrefs;\n const haslabels = Array.isArray(labels) && labels.length > 0 && labels.every(label => isnonempty(label));\n const hastaskrefs = Array.isArray(taskrefs) && taskrefs.length > 0 && taskrefs.every(ref => isnonempty(ref));\n if (!haslabels && !hastaskrefs) return { allowed: false, reason: \"Reviewed labels or task refs are required in options to attach metadata.\" };\n if (options.provenance !== undefined && !isnonempty(options.provenance)) return { allowed: false, reason: \"The reviewed provenance must be a non-empty string.\" };\n }\n if (kind === \"reopenrun\" && !isnonempty(options.run)) return { allowed: false, reason: \"A reviewed run id is required in options to reopen its tabs.\" };\n return { allowed: true };\n}\n\n/** True when the kind belongs to the media capture family of pdf documents, recordings, images, canvases, streams, assets, lapses, conversions and thumbnails. */\nexport function ismediakind(kind: actionkind): boolean {\n return mediaactions.has(kind);\n}\n\n/** True when the kind belongs to the network observation family of fetching, parsing and typed calls. */\nexport function ishttpkind(kind: actionkind): boolean {\n return httpactions.has(kind);\n}\n\n/** True when the kind belongs to the socket and stream family of channels, messages, subscriptions and poll loops. */\nexport function issocketkind(kind: actionkind): boolean {\n return socketactions.has(kind);\n}\n\n/** True when the kind belongs to the request observation family of watches, headers, bodies and page api discovery. */\nexport function isnetwatchkind(kind: actionkind): boolean {\n return netwatchactions.has(kind);\n}\n\n/** True when the kind belongs to the network control family of blocking, mocking, header rewriting, cookies, auth, api keys, proxy routing and uploads. */\nexport function iscontrolkind(kind: actionkind): boolean {\n return controlactions.has(kind);\n}\n\n/** Resolves the reviewed risk of one step: capturebodies grades sensitive when the reviewed mime list carries private payload types and extractapi grades sensitive when the replay verb mutates, while every other kind keeps its risk table grade. */\nexport function resolvedrisk(step: toolstep): \"read\" | \"interaction\" | \"sensitive\" {\n if (step.kind === \"capturebodies\") {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const body = options.body;\n const mimes = body && typeof body === \"object\" && !Array.isArray(body) ? (body as Record<string, unknown>).mimes : undefined;\n if (Array.isArray(mimes) && mimes.some(mime => typeof mime === \"string\" && privatemime(mime))) return \"sensitive\";\n return \"interaction\";\n }\n if (step.kind === \"extractapi\") {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const replay = options.replay;\n const verb = replay && typeof replay === \"object\" && !Array.isArray(replay) ? (replay as Record<string, unknown>).verb : undefined;\n if (typeof verb === \"string\" && ![\"GET\", \"HEAD\", \"OPTIONS\"].includes(verb.trim().toUpperCase())) return \"sensitive\";\n return \"read\";\n }\n return actionrisk(step.kind);\n}\n\n/** Restricts every outbound channel to a granted origin: wss websocket and https event stream urls map onto their https origin, carry no embedded credentials and stay inside the session origin grants. */\nexport function socketgate(session: agentsession | undefined, url: string): policyevaluation {\n let parsed: URL;\n try { parsed = new URL(url); } catch { return { allowed: false, reason: \"The channel needs a valid url before it can be reviewed.\" }; }\n if (parsed.protocol !== \"wss:\" && parsed.protocol !== \"https:\") return { allowed: false, reason: \"Channels use wss websocket urls or https event stream urls only.\" };\n if (parsed.username || parsed.password) return { allowed: false, reason: \"Channel credentials are not allowed in the url.\" };\n const origin = channelorigin(url);\n if (!origingranted(session, origin)) return { allowed: false, reason: `The channel to ${origin} stays outside the session origin grants.` };\n return { allowed: true };\n}\n\n/** Requires the user granted request watching before any watchrequests step runs; the observation derives from the page timing buffers and the grant adds no manifest permission. */\nexport function watchgate(session: agentsession | undefined, settings: runsettings | undefined, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the request watch.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot watch requests.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot watch requests.\" };\n if (settings?.webrequestgrant !== true) return { allowed: false, reason: \"Request watching needs the webrequest grant in the review panel first; the observation derives from the page timing buffers and adds no manifest permission.\" };\n return { allowed: true };\n}\n\n/** Requires the host grant for every observed origin before header reads, body captures and endpoint replays touch an exchange. */\nexport function observedorigingranted(session: agentsession | undefined, url: string): policyevaluation {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { return { allowed: false, reason: \"The observed exchange url does not parse for an origin check.\" }; }\n if (!origingranted(session, origin)) return { allowed: false, reason: `The observed origin ${origin} stays outside the session origin grants; grant it before reading headers, bodies or replays.` };\n return { allowed: true };\n}\n\n/** Restricts every outbound request to a granted origin: the url must be a reviewed HTTPS url inside the session origin grants. */\nexport function origincheck(session: agentsession | undefined, url: string): policyevaluation {\n let parsed: URL;\n try { parsed = new URL(url); } catch { return { allowed: false, reason: \"The outbound request needs a valid url before it can be reviewed.\" }; }\n if (parsed.protocol !== \"https:\") return { allowed: false, reason: \"Outbound requests use HTTPS urls only.\" };\n if (parsed.username || parsed.password) return { allowed: false, reason: \"Endpoint credentials are not allowed in the url.\" };\n if (!origingranted(session, parsed.origin)) return { allowed: false, reason: `The outbound request to ${parsed.origin} stays outside the session origin grants.` };\n return { allowed: true };\n}\n\n/** True when a header name carries credentials and therefore needs the explicit consent that names it. */\nexport function credentialheadername(name: string): boolean {\n return credentialheaders.has(name.trim().toLowerCase());\n}\n\n/** Requires a reviewed consent ref before any custom header leaves the extension; requests without custom headers need no prompt. */\nexport function fetchconsentrefgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const request = options.fetch;\n const headers = request && typeof request === \"object\" && !Array.isArray(request) ? (request as Record<string, unknown>).headers : undefined;\n const names = headers && typeof headers === \"object\" && !Array.isArray(headers) ? Object.keys(headers as Record<string, unknown>) : [];\n if (names.length === 0) return { allowed: true };\n const empty = names.some(name => !name.trim());\n if (empty) return { allowed: false, reason: \"Header allowlists with empty names are refused.\" };\n const credential = names.find(name => credentialheadername(name));\n if (credential !== undefined && !isnonempty(options.consentref)) return { allowed: false, reason: `The credential bearing header ${credential} needs the explicit reviewed consent that names it before it is sent.` };\n if (!isnonempty(options.consentref)) return { allowed: false, reason: `The ${names.length} reviewed custom header${names.length === 1 ? \"\" : \"s\"} need a reviewed consent ref in options before any send.` };\n return { allowed: true };\n}\n\n/** True when one stored fetch consent still covers the origin and every header name inside its expiry window. */\nexport function fetchconsentcovers(consent: { origin: string; headers: Array<{ name: string }>; approved?: boolean; expiresat: number }, origin: string, headernames: string[], now: number): boolean {\n if (consent.approved !== true) return false;\n if (consent.expiresat <= now) return false;\n if (consent.origin !== origin) return false;\n const covered = new Set(consent.headers.map(header => header.name.trim().toLowerCase()));\n return headernames.every(name => covered.has(name.trim().toLowerCase()));\n}\n\n/** True when the reviewed call mutates: rest verbs beyond get, head and options or a graphql mutation; mutating calls grade sensitive. */\nexport function mutationcallof(step: toolstep): boolean {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n if (step.kind === \"callgraphql\") {\n const request = options.graphql;\n return Boolean(request && typeof request === \"object\" && !Array.isArray(request) && (request as Record<string, unknown>).operationkind === \"mutation\");\n }\n if (step.kind === \"callrest\") {\n const method = typeof options.method === \"string\" ? options.method.trim().toUpperCase() : undefined;\n if (method !== undefined) return ![\"GET\", \"HEAD\", \"OPTIONS\"].includes(method);\n }\n return false;\n}\n\n/** Keeps the reviewed fetch waits inside the reviewed wait budget: the worst case of every timeout plus every backoff wait must fit; every bound itself stays a user choice with no code ceiling. */\nexport function fetchbudgetallowed(timeout: number | undefined, retries: number | undefined, backoff: number | undefined, wait: number | undefined): policyevaluation {\n for (const [label, value] of [[\"timeout\", timeout], [\"retries\", retries], [\"backoff\", backoff]] as Array<[string, number | undefined]>) {\n if (value !== undefined && (typeof value !== \"number\" || !Number.isFinite(value) || value < 0)) return { allowed: false, reason: `The reviewed fetch ${label} must be zero or a positive number with no code ceiling.` };\n }\n if (wait !== undefined && (typeof wait !== \"number\" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: \"The reviewed fetch wait budget must be zero or a positive number of milliseconds.\" };\n if (wait === undefined || timeout === undefined) return { allowed: true };\n const attempts = Math.max(1, Math.floor((retries ?? 0)) + 1);\n const waits = (backoff ?? 0) * (attempts * (attempts - 1)) / 2;\n const worstcase = timeout * attempts + waits;\n if (worstcase > wait) return { allowed: false, reason: `The fetch worst case of ${worstcase} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or fewer retries.` };\n return { allowed: true };\n}\n\n/** Resolves the outbound url of an http step at review time: the fetch request url of a fetchurl step and nothing for typed calls whose endpoints resolve at execution. */\nexport function outboundtarget(step: toolstep): string | undefined {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const request = options.fetch;\n if (request && typeof request === \"object\" && !Array.isArray(request)) {\n const url = (request as Record<string, unknown>).url;\n if (typeof url === \"string\" && url.trim()) return url.trim();\n }\n return undefined;\n}\n\n/** Validates one reviewed typed endpoint definition: name, method, HTTPS url template with variables, header allowlist with non-empty names and a payload schema with kinds, required flags and defaults. */\nexport function validateendpointrecord(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed endpoint record is required.\" };\n const record = value as Record<string, unknown>;\n if (!isnonempty(record.name)) return { allowed: false, reason: \"The endpoint record needs a reviewed non-empty name.\" };\n if (!isnonempty(record.method)) return { allowed: false, reason: \"The endpoint record needs a reviewed method.\" };\n if (!ishttpsurl(record.url)) return { allowed: false, reason: \"The endpoint record url template must be an HTTPS url.\" };\n if (record.headers !== undefined) {\n if (!record.headers || typeof record.headers !== \"object\" || Array.isArray(record.headers)) return { allowed: false, reason: \"The endpoint header allowlist must be an object of reviewed headers.\" };\n for (const name of Object.keys(record.headers as Record<string, unknown>)) {\n if (!name.trim()) return { allowed: false, reason: \"Endpoint header allowlists with empty names are refused.\" };\n const headervalue = (record.headers as Record<string, unknown>)[name];\n if (typeof headervalue !== \"string\") return { allowed: false, reason: `The endpoint header ${name} needs a reviewed string value.` };\n }\n }\n const schema = record.schema;\n if (!schema || typeof schema !== \"object\" || Array.isArray(schema)) return { allowed: false, reason: \"Every typed endpoint call needs a reviewed payload schema; endpoint records without schemas are refused.\" };\n const fields = (schema as Record<string, unknown>).fields;\n if (!Array.isArray(fields) || fields.length === 0) return { allowed: false, reason: \"The endpoint payload schema needs a non-empty field list.\" };\n for (const item of fields) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every payload schema field must be an object.\" };\n const field = item as Record<string, unknown>;\n if (!isnonempty(field.name)) return { allowed: false, reason: \"Every payload schema field needs a non-empty name.\" };\n if (field.kind !== \"string\" && field.kind !== \"number\" && field.kind !== \"boolean\") return { allowed: false, reason: `The payload schema field ${field.name} must be a string, number or boolean kind.` };\n if (field.required !== undefined && typeof field.required !== \"boolean\") return { allowed: false, reason: `The payload schema field ${field.name} required flag must be a boolean.` };\n if (field.default !== undefined && typeof field.default !== \"string\" && typeof field.default !== \"number\" && typeof field.default !== \"boolean\") return { allowed: false, reason: `The payload schema field ${field.name} default must match its kind.` };\n }\n return { allowed: true };\n}\n\n/** Validates one dotted json path against the path grammar: non-empty segments of names, digits, underscores or hyphens. */\nfunction validpath(path: string): boolean {\n return path.split(\".\").every(segment => /^[A-Za-z0-9_-]+$/.test(segment));\n}\n\n/** Validates the reviewed network observation parameter grammar of the 1.1.42 family: fetch requests with header allowlists, fetch policies with timeout, retries, backoff and follow limit, stream budgets, dotted json paths, html queries, graphql operations and typed endpoint references; every bound stays a user choice with no code ceiling. */\nfunction validatehttpgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"fetchurl\") {\n const request = options.fetch;\n if (!request || typeof request !== \"object\" || Array.isArray(request)) return { allowed: false, reason: \"A reviewed fetch request with a url is required in options.fetch.\" };\n const fetchrequest = request as Record<string, unknown>;\n if (typeof fetchrequest.url !== \"string\" || !fetchrequest.url.trim()) return { allowed: false, reason: \"The reviewed fetch request needs a non-empty url.\" };\n if (fetchrequest.method !== undefined && (typeof fetchrequest.method !== \"string\" || ![\"GET\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"].includes(fetchrequest.method.trim().toUpperCase()))) return { allowed: false, reason: \"The reviewed fetch method must be a known HTTP verb.\" };\n if (fetchrequest.headers !== undefined) {\n if (!fetchrequest.headers || typeof fetchrequest.headers !== \"object\" || Array.isArray(fetchrequest.headers)) return { allowed: false, reason: \"The reviewed header allowlist must be an object of custom headers.\" };\n for (const name of Object.keys(fetchrequest.headers as Record<string, unknown>)) {\n if (!name.trim()) return { allowed: false, reason: \"Header allowlists with empty names are refused.\" };\n if (typeof (fetchrequest.headers as Record<string, unknown>)[name] !== \"string\") return { allowed: false, reason: `The reviewed header ${name} needs a string value.` };\n }\n }\n if (fetchrequest.body !== undefined && typeof fetchrequest.body !== \"string\") return { allowed: false, reason: \"The reviewed fetch body must be a string.\" };\n if (fetchrequest.mode !== undefined && fetchrequest.mode !== \"cors\" && fetchrequest.mode !== \"no-cors\" && fetchrequest.mode !== \"same-origin\") return { allowed: false, reason: \"The reviewed fetch mode must be cors, no-cors or same-origin.\" };\n const consentgate = fetchconsentrefgranted(step);\n if (!consentgate.allowed) return consentgate;\n const policycheck = validatefetchoptions(options.fetchoptions);\n if (!policycheck.allowed) return policycheck;\n const fetchpolicy = fetchoptionsvalues(options.fetchoptions);\n const budget = fetchbudgetallowed(fetchpolicy.timeout, fetchpolicy.retries, fetchpolicy.backoff, fetchnumeric(options, \"wait\"));\n if (!budget.allowed) return budget;\n if (options.stream !== undefined) {\n if (!options.stream || typeof options.stream !== \"object\" || Array.isArray(options.stream)) return { allowed: false, reason: \"The reviewed stream window must be an object with an optional byte budget.\" };\n const streambudget = (options.stream as Record<string, unknown>).budget;\n if (streambudget !== undefined && (typeof streambudget !== \"number\" || !Number.isFinite(streambudget) || streambudget < 0)) return { allowed: false, reason: \"The reviewed stream byte budget must be zero or a positive number of bytes with no code ceiling.\" };\n }\n }\n if (kind === \"parsejson\") {\n if (!isnonempty(options.call)) return { allowed: false, reason: \"A reviewed stored call id is required in options.call before the body parses.\" };\n const fields = options.fields;\n if (!Array.isArray(fields) || fields.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of json path rules is required in options.fields.\" };\n for (const item of fields) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every json path rule must be an object.\" };\n const rule = item as Record<string, unknown>;\n if (!isnonempty(rule.name)) return { allowed: false, reason: \"Every json path rule needs a non-empty field name.\" };\n if (typeof rule.path !== \"string\" || !rule.path.trim() || !validpath(rule.path.trim())) return { allowed: false, reason: `The json path of ${rule.name} must be a dotted path of non-empty segments.` };\n if (rule.kind !== undefined && rule.kind !== \"text\" && rule.kind !== \"number\" && rule.kind !== \"boolean\" && rule.kind !== \"json\") return { allowed: false, reason: `The json path kind of ${rule.name} must be text, number, boolean or json.` };\n }\n }\n if (kind === \"parsehtml\") {\n if (!isnonempty(options.call)) return { allowed: false, reason: \"A reviewed stored call id is required in options.call before the markup parses.\" };\n const queries = options.queries;\n if (!Array.isArray(queries) || queries.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of html queries is required in options.queries.\" };\n for (const item of queries) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every html query must be an object.\" };\n const query = item as Record<string, unknown>;\n if (!isnonempty(query.selector)) return { allowed: false, reason: \"Every html query needs a selector from the reviewed selector grammar.\" };\n if (query.attribute !== undefined && !isnonempty(query.attribute)) return { allowed: false, reason: \"The reviewed html query attribute must be a non-empty attribute name.\" };\n if (query.multi !== undefined && typeof query.multi !== \"boolean\") return { allowed: false, reason: \"The reviewed html query multi flag must be a boolean.\" };\n }\n }\n if (kind === \"callrest\" || kind === \"callgraphql\") {\n if (!isnonempty(options.endpoint)) return { allowed: false, reason: \"A reviewed typed endpoint name is required in options.endpoint.\" };\n if (kind === \"callrest\") {\n if (options.payload !== undefined && (!options.payload || typeof options.payload !== \"object\" || Array.isArray(options.payload))) return { allowed: false, reason: \"The reviewed rest payload must be an object of reviewed values.\" };\n if (options.method !== undefined && (typeof options.method !== \"string\" || ![\"GET\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"].includes(options.method.trim().toUpperCase()))) return { allowed: false, reason: \"The reviewed endpoint method override must be a known HTTP verb.\" };\n if (options.success !== undefined && (!Array.isArray(options.success) || !options.success.every(code => typeof code === \"number\" && Number.isInteger(code)))) return { allowed: false, reason: \"The reviewed success status list must be a list of integer status codes.\" };\n }\n if (kind === \"callgraphql\") {\n const request = options.graphql;\n if (!request || typeof request !== \"object\" || Array.isArray(request)) return { allowed: false, reason: \"A reviewed graphql request with an operation is required in options.graphql.\" };\n const graphql = request as Record<string, unknown>;\n if (typeof graphql.query !== \"string\" || !graphql.query.trim()) return { allowed: false, reason: \"The reviewed graphql operation text must be a non-empty string.\" };\n if (graphql.operationkind !== \"query\" && graphql.operationkind !== \"mutation\") return { allowed: false, reason: \"The reviewed graphql operation kind must be query or mutation; unknown operation kinds are refused.\" };\n if (graphql.variables !== undefined && (!graphql.variables || typeof graphql.variables !== \"object\" || Array.isArray(graphql.variables))) return { allowed: false, reason: \"The reviewed graphql variables must be an object of reviewed values.\" };\n if (graphql.operationname !== undefined && !isnonempty(graphql.operationname)) return { allowed: false, reason: \"The reviewed graphql operation name must be a non-empty string.\" };\n }\n if (options.apikeys !== undefined && (!Array.isArray(options.apikeys) || !options.apikeys.every(name => isnonempty(name)))) return { allowed: false, reason: \"The reviewed api key reference list must be a list of non-empty stored names.\" };\n const policycheck = validatefetchoptions(options.fetchoptions);\n if (!policycheck.allowed) return policycheck;\n const fetchpolicy = fetchoptionsvalues(options.fetchoptions);\n const budget = fetchbudgetallowed(fetchpolicy.timeout, fetchpolicy.retries, fetchpolicy.backoff, fetchnumeric(options, \"wait\"));\n if (!budget.allowed) return budget;\n }\n return { allowed: true };\n}\n\n/** Validates one reviewed fetch policy object: timeout, retries, backoff base and redirect follow limit stay user choices with no code ceiling. */\nfunction validatefetchoptions(value: unknown): policyevaluation {\n if (value === undefined) return { allowed: true };\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"The reviewed fetch options must be an object with timeout, retries, backoff and follow.\" };\n const options = value as Record<string, unknown>;\n for (const key of [\"timeout\", \"backoff\"]) {\n if (options[key] !== undefined && (typeof options[key] !== \"number\" || !Number.isFinite(options[key]) || options[key] < 0)) return { allowed: false, reason: `The reviewed fetch ${key} must be zero or a positive number with no code ceiling.` };\n }\n for (const key of [\"retries\", \"follow\"]) {\n if (options[key] !== undefined && (typeof options[key] !== \"number\" || !Number.isInteger(options[key]) || options[key] < 0)) return { allowed: false, reason: `The reviewed fetch ${key} must be zero or a positive integer with no code ceiling.` };\n }\n return { allowed: true };\n}\n\n/** Reads the numeric fetch policy fields of one reviewed fetch options object. */\nfunction fetchoptionsvalues(value: unknown): { timeout?: number | undefined; retries?: number | undefined; backoff?: number | undefined } {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return {};\n const options = value as Record<string, unknown>;\n return { timeout: fetchnumeric(options, \"timeout\"), retries: fetchnumeric(options, \"retries\"), backoff: fetchnumeric(options, \"backoff\") };\n}\n\n/** Reads one numeric fetch policy field from the step options. */\nfunction fetchnumeric(options: Record<string, unknown>, key: string): number | undefined {\n const value = options[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\n/** True when the kind records user activity and needs the reviewed recording consent before it starts. */\nexport function isrecordingkind(kind: actionkind): boolean {\n return kind === \"recordscreen\" || kind === \"captureaudio\";\n}\n\n/** Validates the reviewed socket and stream parameter grammar of the 1.1.43 family: channel urls with protocols, reconnect budgets and backoff ceilings, multiplexed message payloads, message filters with dotted paths and match limits, event subscriptions with cancellation paths and long poll cursors with intervals kept inside the reviewed wait budget; every bound stays a user choice with no code ceiling. */\nfunction validatesocketgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"opensocket\") {\n const channel = channeloptionsof(options.socket);\n if (!channel) return { allowed: false, reason: \"A reviewed socket with a url is required in options.socket.\" };\n if (channel.options.reconnect !== undefined && !Number.isInteger(channel.options.reconnect)) return { allowed: false, reason: \"The reviewed socket reconnect budget must be an integer attempt count with no code ceiling.\" };\n for (const label of [\"backoff\", \"backoffceiling\"] as const) {\n const value = channel.options[label];\n if (value !== undefined && (typeof value !== \"number\" || !Number.isFinite(value) || value < 0)) return { allowed: false, reason: `The reviewed socket ${label} must be zero or a positive number of milliseconds with no code ceiling.` };\n }\n if (channel.options.lifetime !== undefined && (typeof channel.options.lifetime !== \"number\" || !Number.isFinite(channel.options.lifetime) || channel.options.lifetime <= 0)) return { allowed: false, reason: \"The reviewed socket lifetime window must be a positive number of milliseconds.\" };\n }\n if (kind === \"sendmessage\") {\n const message = options.message;\n if (!message || typeof message !== \"object\" || Array.isArray(message)) return { allowed: false, reason: \"A reviewed message with a channel, stream and payload is required in options.message.\" };\n const envelope = message as Record<string, unknown>;\n if (!isnonempty(envelope.channel)) return { allowed: false, reason: \"The reviewed message needs the open channel id in options.message.channel.\" };\n if (envelope.stream !== undefined && !isnonempty(envelope.stream)) return { allowed: false, reason: \"The reviewed message stream name must be a non-empty string.\" };\n if (typeof envelope.payload !== \"string\") return { allowed: false, reason: \"The reviewed message payload must be a string.\" };\n }\n if (kind === \"waitmessage\") {\n if (options.filter !== undefined) {\n const filter = options.filter;\n if (!filter || typeof filter !== \"object\" || Array.isArray(filter)) return { allowed: false, reason: \"The reviewed message filter must be an object of stream, path and limit.\" };\n const reviewed = filter as Record<string, unknown>;\n if (reviewed.stream !== undefined && !isnonempty(reviewed.stream)) return { allowed: false, reason: \"The reviewed message filter stream name must be a non-empty string.\" };\n if (reviewed.path !== undefined && (typeof reviewed.path !== \"string\" || !validpath(reviewed.path.trim()))) return { allowed: false, reason: \"The reviewed message filter path must be a dotted path of non-empty segments.\" };\n if (reviewed.limit !== undefined && (typeof reviewed.limit !== \"number\" || !Number.isInteger(reviewed.limit) || reviewed.limit < 1)) return { allowed: false, reason: \"The reviewed message match limit must be a positive integer with no code ceiling.\" };\n }\n if (options.wait !== undefined && (typeof options.wait !== \"number\" || !Number.isFinite(options.wait) || options.wait < 0)) return { allowed: false, reason: \"The reviewed message wait budget must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"subscribesse\") {\n const subscription = subscriptionoptionsof(options.subscription);\n if (!subscription) return { allowed: false, reason: \"A reviewed subscription with an event stream url and a cancellation path is required in options.subscription.\" };\n const rawlifetime = options.subscription && typeof options.subscription === \"object\" && !Array.isArray(options.subscription) ? (options.subscription as Record<string, unknown>).lifetime : undefined;\n if (rawlifetime !== undefined && (typeof rawlifetime !== \"number\" || !Number.isFinite(rawlifetime) || rawlifetime <= 0)) return { allowed: false, reason: \"The reviewed subscription lifetime window must be a positive number of milliseconds.\" };\n }\n if (kind === \"longpoll\") {\n const cursor = pollcursorof(options.poll);\n if (!cursor) return { allowed: false, reason: \"A reviewed poll cursor with a url, cursor field, interval and stop condition is required in options.poll.\" };\n const wait = options.wait;\n if (wait !== undefined && (typeof wait !== \"number\" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: \"The reviewed long poll wait budget must be zero or a positive number of milliseconds.\" };\n if (wait !== undefined && cursor.interval > wait) return { allowed: false, reason: `The long poll interval of ${cursor.interval} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or a shorter interval.` };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed request observation parameter grammar of the 1.1.43 family: watch windows with user configured match limits, header filters whose redaction list is required before any header value is stored, body filters with url patterns, mime lists and byte ceilings and api replay specs with known verbs and dotted extraction paths. */\nfunction validatenetwatchgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"watchrequests\") {\n if (options.watch !== undefined) {\n const watch = options.watch;\n if (!watch || typeof watch !== \"object\" || Array.isArray(watch)) return { allowed: false, reason: \"The reviewed watch window must be an object.\" };\n const reviewed = watch as Record<string, unknown>;\n if (reviewed.window !== undefined && (typeof reviewed.window !== \"number\" || !Number.isFinite(reviewed.window) || reviewed.window < 0)) return { allowed: false, reason: \"The reviewed watch window must be zero or a positive number of milliseconds.\" };\n }\n if (options.limit !== undefined && (typeof options.limit !== \"number\" || !Number.isInteger(options.limit) || options.limit < 1)) return { allowed: false, reason: \"The reviewed watch match limit must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"readheaders\") {\n const headers = options.headers;\n if (!headers || typeof headers !== \"object\" || Array.isArray(headers)) return { allowed: false, reason: \"A reviewed header filter with a name allowlist and a redaction list is required in options.headers.\" };\n const reviewed = headers as Record<string, unknown>;\n if (!Array.isArray(reviewed.allow) || reviewed.allow.length === 0 || !reviewed.allow.every((name): name is string => isnonempty(name))) return { allowed: false, reason: \"The reviewed header allowlist must be a non-empty list of header names.\" };\n if (!Array.isArray(reviewed.redact) || reviewed.redact.length === 0 || !reviewed.redact.every((name): name is string => isnonempty(name))) return { allowed: false, reason: \"Header capture requires a reviewed redaction list before any header value is stored.\" };\n }\n if (kind === \"capturebodies\") {\n const body = options.body;\n if (!body || typeof body !== \"object\" || Array.isArray(body)) return { allowed: false, reason: \"A reviewed body filter with a url pattern, mime list and byte ceiling is required in options.body.\" };\n const reviewed = body as Record<string, unknown>;\n if (reviewed.urlpattern !== undefined && !isnonempty(reviewed.urlpattern)) return { allowed: false, reason: \"The reviewed body url pattern must be a non-empty string.\" };\n if (reviewed.mimes !== undefined && (!Array.isArray(reviewed.mimes) || reviewed.mimes.length === 0 || !reviewed.mimes.every((mime): mime is string => isnonempty(mime)))) return { allowed: false, reason: \"The reviewed body mime list must be a non-empty list of mime types.\" };\n if (reviewed.ceiling !== undefined && (typeof reviewed.ceiling !== \"number\" || !Number.isFinite(reviewed.ceiling) || reviewed.ceiling < 0)) return { allowed: false, reason: \"The reviewed body byte ceiling must be zero or a positive number of bytes with no code ceiling.\" };\n }\n if (kind === \"mapapi\") {\n if (options.limit !== undefined && (typeof options.limit !== \"number\" || !Number.isInteger(options.limit) || options.limit < 1)) return { allowed: false, reason: \"The reviewed mapapi match limit must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"extractapi\") {\n const replay = apireplayspecof(options.replay);\n if (!replay) return { allowed: false, reason: \"A reviewed replay spec with an endpoint is required in options.replay.\" };\n if (!ishttpsurl(replay.endpoint)) return { allowed: false, reason: \"The reviewed replay endpoint must be an HTTPS url.\" };\n if (replay.verb !== undefined && ![\"GET\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"].includes(replay.verb)) return { allowed: false, reason: \"The reviewed replay verb must be a known HTTP verb.\" };\n for (const path of replay.paths ?? []) {\n if (!validpath(path.trim())) return { allowed: false, reason: `The reviewed replay extraction path ${path} must be a dotted path of non-empty segments.` };\n }\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed network control parameter grammar of the 1.1.44 family: block rules with url patterns that name their origin, mock fixtures reviewed with their full body, header rewrite rules with named origin patterns and set, append and remove operations, cookie records scoped to granted domains, oauth flows with provider consent refs, api key entries behind explicit consent, proxy routes with required bypass lists, urlencoded form payloads and multipart uploads whose every file carries the explicit reviewed flag; every bound stays a user choice with no code ceiling. */\nfunction validatecontrolgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"blockrequest\") {\n const rule = blockruleof(options.block);\n if (!rule) return { allowed: false, reason: \"A reviewed block rule with a url pattern is required in options.block.\" };\n if (patternorigin(rule.urlpattern) === undefined) return { allowed: false, reason: \"Block rules need an https origin pattern; patterns without a named origin are refused.\" };\n if ((options.block as Record<string, unknown>).reviewed !== true) return { allowed: false, reason: \"The block rule carries the explicit reviewed flag before any request is blocked.\" };\n }\n if (kind === \"mockresponse\") {\n const spec = mockspecof(options.mock);\n if (!spec) return { allowed: false, reason: \"A reviewed mock fixture with a url pattern, status and its reviewed body or a captured body ref is required in options.mock.\" };\n if (patternorigin(spec.urlpattern) === undefined) return { allowed: false, reason: \"Mock fixtures need an https origin pattern; patterns without a named origin are refused.\" };\n if (spec.reviewed !== true) return { allowed: false, reason: \"Every mock fixture is reviewed with its full body or the referenced captured body through the explicit reviewed flag before it serves.\" };\n }\n if (kind === \"rewriteheaders\") {\n const rules = options.rules;\n if (!Array.isArray(rules) || rules.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of header rewrite rules is required in options.rules.\" };\n for (const item of rules) {\n const rule = headeruleof(item);\n if (!rule) return { allowed: false, reason: \"Every header rewrite rule needs a url pattern, header name, a set, append or remove operation and its value.\" };\n if (patternorigin(rule.urlpattern) === undefined) return { allowed: false, reason: \"Header rewrite rules must name their origin pattern explicitly; patterns without a named origin are refused.\" };\n }\n }\n if (kind === \"setcookies\") {\n const cookies = options.cookies;\n if (!Array.isArray(cookies) || cookies.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of cookie records is required in options.cookies.\" };\n for (const item of cookies) {\n if (!cookierecordof(item)) return { allowed: false, reason: \"Every cookie record needs a name, domain, path and reviewed string value with an optional expiry.\" };\n }\n }\n if (kind === \"readcookies\" && options.domain !== undefined && !isnonempty(options.domain)) return { allowed: false, reason: \"The reviewed cookie read domain must be a non-empty host.\" };\n if (kind === \"clearcookies\") {\n if (!isnonempty(options.domain)) return { allowed: false, reason: \"A reviewed cookie domain is required before cookies are cleared.\" };\n if (options.names !== undefined && (!Array.isArray(options.names) || options.names.length === 0 || !options.names.every((name): name is string => isnonempty(name)))) return { allowed: false, reason: \"The reviewed cookie clear list must be a non-empty list of cookie names when present.\" };\n }\n if (kind === \"authflow\") {\n const flow = oauthflowof(options.oauth);\n if (!flow) return { allowed: false, reason: \"A reviewed oauth flow with provider, authorize url, token url, scopes and redirect origin is required in options.oauth.\" };\n if (!ishttpsurl(flow.authorizeurl) || !ishttpsurl(flow.tokenurl)) return { allowed: false, reason: \"The oauth authorize and token urls must use HTTPS.\" };\n if (!ishttpsurl(flow.redirectorigin) && !/^https:\\/\\/[^/]+\\/?$/.test(flow.redirectorigin)) return { allowed: false, reason: \"The oauth redirect origin must be an HTTPS origin inside the grants.\" };\n const consent = authconsentgranted(step);\n if (!consent.allowed) return consent;\n }\n if (kind === \"saveapikey\") {\n const key = options.key;\n if (!key || typeof key !== \"object\" || Array.isArray(key)) return { allowed: false, reason: \"A reviewed api key entry with name, origin scopes and header is required in options.key.\" };\n const entry = key as Record<string, unknown>;\n if (!isnonempty(entry.name)) return { allowed: false, reason: \"The api key entry needs a reviewed non-empty name.\" };\n if (!Array.isArray(entry.origins) || entry.origins.length === 0 || !entry.origins.every((item): item is string => ishttpsurl(item))) return { allowed: false, reason: \"The api key needs a reviewed non-empty list of HTTPS origin scopes.\" };\n if (!isnonempty(entry.header)) return { allowed: false, reason: \"The api key entry needs a reviewed non-empty header name.\" };\n if (typeof entry.value !== \"string\" || !entry.value) return { allowed: false, reason: \"The api key needs its secret value in the reviewed options; it never enters the audit trail.\" };\n const consent = apikeyconsentgranted(step);\n if (!consent.allowed) return consent;\n }\n if (kind === \"routeproxy\") {\n if (!proxyrouteof(options.proxy)) return { allowed: false, reason: \"A reviewed proxy route with scheme, host, port and a non-empty bypass list is required in options.proxy.\" };\n if (!isnonempty(options.consentref)) return { allowed: false, reason: \"Proxy routing needs the explicit reviewed consent ref before any route applies.\" };\n }\n if (kind === \"postform\") {\n const form = formpayloadof(options.form);\n if (!form) return { allowed: false, reason: \"A reviewed form payload with a url and a non-empty field list is required in options.form.\" };\n if (!ishttpsurl(form.url)) return { allowed: false, reason: \"The form submission target must use HTTPS.\" };\n if (options.wait !== undefined && (typeof options.wait !== \"number\" || !Number.isFinite(options.wait) || options.wait < 0)) return { allowed: false, reason: \"The reviewed rate limit wait budget must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"postfiles\") {\n const upload = multipartpayloadof(options.upload);\n if (!upload) return { allowed: false, reason: \"A reviewed multipart upload with a url and reviewed files is required in options.upload; every file carries the explicit reviewed flag.\" };\n if (!ishttpsurl(upload.url)) return { allowed: false, reason: \"The multipart upload target must use HTTPS.\" };\n if (options.wait !== undefined && (typeof options.wait !== \"number\" || !Number.isFinite(options.wait) || options.wait < 0)) return { allowed: false, reason: \"The reviewed rate limit wait budget must be zero or a positive number of milliseconds.\" };\n }\n return { allowed: true };\n}\n\n/** Requires the reviewed block rule of a live session before any blockrequest runs: the rule must carry the explicit reviewed flag and the session must stay active, unpaused and unexpired; every rule applies for the run only and reverts at run end. */\nexport function blockgate(session: agentsession | undefined, step: toolstep, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the request block.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot block requests.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot block requests.\" };\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const rule = options.block;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule) || (rule as Record<string, unknown>).reviewed !== true) return { allowed: false, reason: \"Request blocking needs its reviewed block rule with the explicit reviewed flag before any rule applies.\" };\n if (!blockruleof(rule)) return { allowed: false, reason: \"The block rule needs a url pattern and an optional resource type list.\" };\n return { allowed: true };\n}\n\n/** Scopes every cookie kind to a granted domain of a live session: the domain must equal a granted origin host or sit beneath it, and every other domain is refused. */\nexport function cookiegate(session: agentsession | undefined, domain: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the cookie operation.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot touch cookies.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot touch cookies.\" };\n const grants = session.grants ?? [session.origin];\n if (!cookiedomaingranted(domain, grants)) return { allowed: false, reason: `The cookie domain ${domain} stays outside the session origin grants; cookie control refuses domains beyond the grants.` };\n return { allowed: true };\n}\n\n/** Requires the explicit reviewed consent before routeproxy changes routing: a live session, a reviewed consent ref and a valid route with its bypass list; the route applies for the run only and restores the previous state at run end. */\nexport function proxygate(session: agentsession | undefined, step: toolstep, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the proxy route.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot change routing.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot change routing.\" };\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n if (!isnonempty(options.consentref)) return { allowed: false, reason: \"Proxy routing needs the explicit reviewed consent ref before any route applies.\" };\n if (!proxyrouteof(options.proxy)) return { allowed: false, reason: \"The proxy route needs a scheme, host, port and a non-empty bypass list of origins that stay direct.\" };\n return { allowed: true };\n}\n\n/** Requires the reviewed provider consent prompt ref before any authflow runs. */\nexport function authconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"An oauth flow requires the reviewed provider consent prompt ref in options before it starts.\" };\n return { allowed: true };\n}\n\n/** Requires the explicit consent prompt ref before saveapikey stores a key. */\nexport function apikeyconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"Storing an api key requires the explicit reviewed consent prompt ref in options before anything is stored.\" };\n return { allowed: true };\n}\n\n/** Keeps the rate limit wait inside the reviewed wait budget as user configured behavior: the wait until the reset window passes must fit when a budget was reviewed; both bounds stay user choices with no code ceiling. */\nexport function ratelimitbudgetallowed(wait: number | undefined, budget: number | undefined): policyevaluation {\n if (wait !== undefined && (typeof wait !== \"number\" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: \"The rate limit wait must be zero or a positive number of milliseconds.\" };\n if (budget !== undefined && (typeof budget !== \"number\" || !Number.isFinite(budget) || budget < 0)) return { allowed: false, reason: \"The reviewed rate limit budget must be zero or a positive number of milliseconds.\" };\n if (wait !== undefined && budget !== undefined && wait > budget) return { allowed: false, reason: `The rate limit wait of ${wait} milliseconds exceeds the reviewed budget of ${budget} milliseconds; review a wider budget or submit later.` };\n return { allowed: true };\n}\n\n/** Requires the active tab grant of the live session for every debugging kind: the timeline gate scopes console, error and task capture to the run tab only and refuses every other tab. */\nexport function timelinegate(session: agentsession | undefined, tabid: number, origin: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the timeline capture.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot capture the timeline.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot capture the timeline.\" };\n if (session.tabid !== tabid) return { allowed: false, reason: `The timeline capture needs the run tab ${session.tabid} and refuses tab ${tabid}.` };\n if (!origingranted(session, origin)) return { allowed: false, reason: `The timeline capture of ${origin} needs the session origin grants first.` };\n return { allowed: true };\n}\n\n/** True when one approved console capture consent of that origin exists; console capture on a new origin prompts once and the approved decision persists. */\nexport function consoleconsentcovers(origin: string, consents: consoleconsentrecord[]): policyevaluation {\n if (consents.some(consent => consent.origin === origin && consent.approved === true)) return { allowed: true };\n return { allowed: false, reason: `Console capture on ${origin} needs the reviewed console consent first; approve the prompt in the review panel.` };\n}\n\n/** Requires the granted origin before stack frames are captured; stack capture outside the granted origin is refused. */\nexport function stackgate(session: agentsession | undefined, origin: string): policyevaluation {\n if (!origingranted(session, origin)) return { allowed: false, reason: `Stack capture of ${origin} stays outside the session origin grants.` };\n return { allowed: true };\n}\n\n/** Keeps the debug watch window inside the reviewed wait budget: the watch wait must fit the reviewed budget when one was reviewed; both bounds stay user choices with no code ceiling. */\nexport function debugwaitbudgetallowed(watchwindow: number | undefined, wait: number | undefined): policyevaluation {\n if (watchwindow !== undefined && (typeof watchwindow !== \"number\" || !Number.isFinite(watchwindow) || watchwindow < 0)) return { allowed: false, reason: \"The debug watch window must be zero or a positive number of milliseconds.\" };\n if (wait !== undefined && (typeof wait !== \"number\" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: \"The reviewed debug wait budget must be zero or a positive number of milliseconds.\" };\n if (watchwindow !== undefined && wait !== undefined && watchwindow > wait) return { allowed: false, reason: `The debug watch window of ${watchwindow} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or a shorter window.` };\n return { allowed: true };\n}\n\n/** Exposes the timeline retention window as a user configured choice; an absent value keeps every timeline entry forever while the level count summaries always survive. */\nexport function timelineretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.timelineretention;\n}\n\n/** Grades console diffing as read only comparison evidence: the diff compares two stored console outputs and touches no page or browser state. */\nexport function diffreviewgrade(): { risk: \"read\"; mode: \"diffing\"; evidence: \"comparison\" } {\n return { risk: \"read\", mode: \"diffing\", evidence: \"comparison\" };\n}\n\n/** Validates the reviewed debugging parameter grammar of the 1.1.45 family: a watch window inside the reviewed wait budget, level floors from the reviewed level set, source filters from the reviewed source grammar, spam rules with user configured thresholds, serialization depth bounds, rotation rules with no hardcoded entry ceiling and the required redaction pattern list before any console text is captured. */\nfunction validatetimelinegrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n let watchwindow: number | undefined;\n if (options.watch !== undefined) {\n const watch = options.watch;\n if (!watch || typeof watch !== \"object\" || Array.isArray(watch)) return { allowed: false, reason: \"The reviewed debug watch window must be an object.\" };\n const reviewed = watch as Record<string, unknown>;\n if (reviewed.window !== undefined) {\n if (typeof reviewed.window !== \"number\" || !Number.isFinite(reviewed.window) || reviewed.window < 0) return { allowed: false, reason: \"The reviewed debug watch window must be zero or a positive number of milliseconds.\" };\n watchwindow = reviewed.window;\n }\n }\n const budgetcheck = debugwaitbudgetallowed(watchwindow, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n if (options.level !== undefined && !loglevels.includes(options.level as loglevel)) return { allowed: false, reason: `The reviewed level floor must be one of ${loglevels.join(\", \")}.` };\n if (options.sources !== undefined) {\n if (!Array.isArray(options.sources) || options.sources.length === 0 || !options.sources.every(source => timelinesources.includes(source as never))) return { allowed: false, reason: `The reviewed source filters must be a non-empty list of the reviewed timeline sources: ${timelinesources.join(\", \")}.` };\n }\n if (kind === \"watchconsole\") {\n if (options.redact === undefined || !Array.isArray(options.redact) || options.redact.length === 0 || !options.redact.every(pattern => isnonempty(pattern))) return { allowed: false, reason: \"Console capture requires a reviewed non-empty redaction pattern list before any console text is captured.\" };\n if (options.depth !== undefined && (typeof options.depth !== \"number\" || !Number.isInteger(options.depth) || options.depth < 1)) return { allowed: false, reason: \"The reviewed serialization depth bound must be a positive integer with no code ceiling.\" };\n if (options.spam !== undefined) {\n const rule = spamruleof(options.spam);\n if (!rule) return { allowed: false, reason: \"The reviewed spam rule needs a pattern, a window size and a collapse threshold.\" };\n if (rule.collapse < 1) return { allowed: false, reason: \"The reviewed spam collapse threshold must be a positive integer of user configured value with no code ceiling.\" };\n }\n if (options.rotation !== undefined) {\n const rule = rotationruleof(options.rotation);\n if (!rule) return { allowed: false, reason: \"The reviewed rotation rule needs a max entry count and an overflow target.\" };\n }\n }\n if (kind === \"watchtasks\") {\n if (options.threshold !== undefined && (typeof options.threshold !== \"number\" || !Number.isFinite(options.threshold) || options.threshold < 0)) return { allowed: false, reason: \"The reviewed long task threshold must be zero or a positive number of milliseconds with no code ceiling.\" };\n }\n return { allowed: true };\n}\n\n/** Normalizes a reviewed spam rule: the pattern, the window size and the collapse threshold as user configured values. */\nexport function spamruleof(value: unknown): spamrule | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const entry = value as Record<string, unknown>;\n const pattern = typeof entry.pattern === \"string\" ? entry.pattern : \"\";\n const windowsize = typeof entry.windowsize === \"number\" && Number.isFinite(entry.windowsize) && entry.windowsize >= 0 ? entry.windowsize : undefined;\n const collapse = typeof entry.collapse === \"number\" && Number.isInteger(entry.collapse) ? entry.collapse : undefined;\n if (windowsize === undefined || collapse === undefined) return undefined;\n return { pattern, windowsize, collapse };\n}\n\n/** Normalizes a reviewed log rotation rule: the max entries per run and the overflow target store with no hardcoded entry ceiling. */\nexport function rotationruleof(value: unknown): rotationrule | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const entry = value as Record<string, unknown>;\n const maxentries = typeof entry.maxentries === \"number\" && Number.isInteger(entry.maxentries) && entry.maxentries >= 1 ? entry.maxentries : undefined;\n const overflowtarget = typeof entry.overflowtarget === \"string\" && entry.overflowtarget.trim() ? entry.overflowtarget.trim() : undefined;\n if (maxentries === undefined || overflowtarget === undefined) return undefined;\n return { maxentries, overflowtarget };\n}\n\n/** Requires the active run tab grant of the live session for every devtools protocol kind: the debug gate scopes attaches, commands, watches, breakpoints, steps and overrides to the run tab only and refuses every other tab. */\nexport function debuggate(session: agentsession | undefined, tabid: number, origin: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the devtools protocol step.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot run a devtools protocol step.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot run a devtools protocol step.\" };\n if (session.tabid !== tabid) return { allowed: false, reason: `The devtools protocol step needs the run tab ${session.tabid} and refuses tab ${tabid}.` };\n if (!origingranted(session, origin)) return { allowed: false, reason: `The devtools protocol step on ${origin} needs the session origin grants first.` };\n return { allowed: true };\n}\n\n/** True when one approved debugger consent of that origin covers every requested domain; the first attachcdp of a run needs the approved record and revocation removes the coverage. */\nexport function debuggerconsentcovers(origin: string, domains: string[], grants: debuggergrant[]): policyevaluation {\n const needed = [...new Set(domains)];\n const covering = grants.find(grant => grant.origin === origin && grant.approved === true && grant.revokedat === undefined && needed.every(domain => grant.domains.includes(domain)));\n if (covering) return { allowed: true };\n if (grants.some(grant => grant.origin === origin && grant.revokedat !== undefined)) return { allowed: false, reason: `The debugger consent on ${origin} was revoked; approve a new prompt before the devtools protocol runs again.` };\n return { allowed: false, reason: `The devtools protocol on ${origin} needs the reviewed debugger consent for ${needed.join(\", \")} first; approve the prompt with the domain allowlist shown in the review panel.` };\n}\n\n/** The profiling target gate of every 1.1.47 kind: the live session run tab and origin grants come first, every iframe, worker and service worker target stays inside the granted origins, and the reviewed debugger grant of the origin covers every profiling instrument because profiling is debugger grade instrumentation. */\nexport function targetgate(input: { session: agentsession | undefined; tabid: number; origin: string; targets: attachtarget[]; grants: debuggergrant[] | undefined; now: number }): policyevaluation {\n const base = debuggate(input.session, input.tabid, input.origin, input.now);\n if (!base.allowed) return base;\n for (const target of input.targets) {\n if (target.kind === \"page\") continue;\n const origincheckresult = origincheck(input.session, target.url);\n if (!origincheckresult.allowed) return { allowed: false, reason: `The ${target.kind} target ${target.url} stays outside the granted origins; profiling refuses to attach.` };\n }\n if (input.grants === undefined) return { allowed: true };\n const consent = debuggerconsentcovers(input.origin, [], input.grants);\n if (!consent.allowed) return { allowed: false, reason: `The profiling step on ${input.origin} needs the reviewed debugger grant of the origin first; approve the prompt with the profiling derivation shown in the review panel.` };\n return { allowed: true };\n}\n\n/** True when one approved source map capture consent of that origin covers the capture; revocation removes the coverage and the next capture needs a new reviewed prompt. */\nexport function sourcemapconsentcovers(origin: string, consents: sourcemapconsent[]): policyevaluation {\n const covering = consents.find(consent => consent.origin === origin && consent.approved === true && consent.revokedat === undefined);\n if (covering) return { allowed: true };\n if (consents.some(consent => consent.origin === origin && consent.revokedat !== undefined)) return { allowed: false, reason: `The source map capture consent on ${origin} was revoked; approve a new prompt before another map file is fetched.` };\n return { allowed: false, reason: `The source map capture on ${origin} needs the reviewed per origin consent first; approve the prompt shown in the review panel.` };\n}\n\n/** Exposes the user configured retention window for the heavy profile bytes; an absent window keeps every snapshot, sample and trace file. */\nexport function profileretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.profileretention;\n}\n\n/** Exposes the user configured trace byte ceiling; an absent value never refuses a trace export because the cap stays a user choice only. */\nexport function traceceilingof(settings: runsettings | undefined): number | undefined {\n return settings?.traceceiling;\n}\n\n/** Validates one breakpoint condition against the reviewed expression grammar: member chains, literals of number, string, boolean and null, comparison and logic operators, negation and parentheses; assignments, calls and statements are refused. */\nexport function validatebreakpointcondition(condition: string): policyevaluation {\n const expression = condition.trim();\n if (expression.length === 0) return { allowed: false, reason: \"The breakpoint condition must not be empty.\" };\n if (/(?<![=!<>])=(?!=)/.test(expression)) return { allowed: false, reason: \"Breakpoint conditions refuse assignment because the reviewed grammar is comparison only.\" };\n if (/[A-Za-z_$][\\w$]*\\s*\\(/.test(expression)) return { allowed: false, reason: \"Breakpoint conditions refuse calls because the reviewed grammar is comparison only.\" };\n const literal = /^(?:-?\\d+(?:\\.\\d+)?|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|true|false|null)$/;\n const tokens = expression.match(/(?:[A-Za-z_$][\\w$]*|-?\\d+(?:\\.\\d+)?|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|===|!==|==|!=|>=|<=|&&|\\|\\||[!.<>()+\\-*\\/%])/g);\n if (tokens === null || tokens.join(\"\") !== expression.replace(/\\s+/g, \"\")) return { allowed: false, reason: \"The breakpoint condition must use the reviewed expression grammar of member chains, literals, comparisons, logic operators, negation and parentheses.\" };\n const identifierlike = /^(?:true|false|null)$/;\n for (const token of tokens) {\n if (literal.test(token) || identifierlike.test(token)) continue;\n if ([\"===\", \"!==\", \"==\", \"!=\", \">=\", \"<=\", \"&&\", \"||\", \"!\", \".\", \"(\", \")\", \"<\", \">\", \"+\", \"-\", \"*\", \"/\", \"%\"].includes(token)) continue;\n if (/^[A-Za-z_$][\\w$]*$/.test(token)) continue;\n return { allowed: false, reason: `The token ${token} of the breakpoint condition stays outside the reviewed expression grammar.` };\n }\n return { allowed: true };\n}\n\n/** Keeps the breakpoint count of one run inside the user configured ceiling: an absent ceiling never refuses a breakpoint because the cap stays a user choice only. */\nexport function breakpointbudgetallowed(active: number, ceiling: number | undefined): policyevaluation {\n if (ceiling === undefined) return { allowed: true };\n if (typeof ceiling !== \"number\" || !Number.isInteger(ceiling) || ceiling < 0) return { allowed: false, reason: \"The reviewed breakpoint ceiling must be zero or a positive integer of user configured value with no code ceiling.\" };\n if (active >= ceiling) return { allowed: false, reason: `The run already holds ${active} active breakpoint${active === 1 ? \"\" : \"s\"} and the reviewed breakpoint ceiling is ${ceiling}; revert one or review a wider ceiling.` };\n return { allowed: true };\n}\n\n/** Exposes the pause capture retention window as a user configured choice; an absent value keeps every pause capture with its call frames. */\nexport function pauseretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.pauseretention;\n}\n\n/** Exposes the user configured breakpoint ceiling; an absent value never refuses a breakpoint because the cap stays a user choice only. */\nexport function breakpointceilingof(settings: runsettings | undefined): number | undefined {\n return settings?.breakpointceiling;\n}\n\n/** Requires the review of every emulation layer before it applies: a live session on the run tab, an approved plan, the explicit reviewed flag on the layer options and the reviewed revert plan beside it. */\nexport function emugate(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: \"emulate the run tab\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Emulation layers need an approved plan before they apply.\" };\n let options: Record<string, unknown> = {};\n try { options = parseoptions(input.step); } catch { options = {}; }\n if (options.reviewed !== true) return { allowed: false, reason: `The ${input.step.kind} layer needs the explicit reviewed flag before any mask applies.` };\n if (revertplanof(options.revertplan) === undefined) return { allowed: false, reason: `Every ${input.step.kind} layer needs a reviewed revert plan beside it before any mask applies.` };\n return { allowed: true };\n}\n\n/** Allows layer stacking only when the reviewed plan lists the steps: a second layer of one family needs at least two reviewed steps of that family in the same plan because the last applied layer wins conflicts. */\nexport function emulationstackallowed(plan: agentplan | undefined, kind: actionkind, active: number): policyevaluation {\n if (!plan) return { allowed: false, reason: \"Layer stacking needs the reviewed plan first.\" };\n const listed = plan.steps.filter(step => step.kind === kind).length;\n if (active >= listed) return { allowed: false, reason: `The plan lists ${listed} reviewed ${kind} step${listed === 1 ? \"\" : \"s\"} and ${active} layer${active === 1 ? \"\" : \"s\"} of that family are already active; stacking beyond the reviewed plan is refused.` };\n return { allowed: true };\n}\n\n/** True when one approved location consent of that origin covers the reviewed coordinates; the prompt shows the exact latitude and longitude before emulatelocate applies. */\nexport function locationconsentgate(origin: string, latitude: number, longitude: number, consents: locationconsent[]): policyevaluation {\n if (consents.some(consent => consent.origin === origin && consent.revokedat !== undefined)) return { allowed: false, reason: `The location consent on ${origin} was revoked; approve a new prompt before the location override runs again.` };\n if (locationconsentcovers(origin, latitude, longitude, consents)) return { allowed: true };\n return { allowed: false, reason: `The location override of ${latitude}, ${longitude} on ${origin} needs the reviewed location consent first; approve the prompt with the coordinates shown in the review panel.` };\n}\n\n/** Exposes the user configured retention window for reverted emulation layer states; an absent window keeps every prior state while the layer history always survives. */\nexport function emulationretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.emulationretention;\n}\n\n/** Validates the reviewed emulation parameter grammar of the 1.1.48 family: device presets with width, height, pixel ratio and the mobile flag plus the reviewed reload flag, network presets with latency, download and upload bounds and the offline window, location presets inside the latitude and longitude ranges behind the location consent, agent presets of the reviewed user agent grammar with platform and brand list, permission overrides of the reviewed browser permission set graded by name, blackbox rules of explicit origin patterns with their trace scope, and the reviewed revert plan beside every layer. */\nfunction validateemulationgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (revertplanof(options.revertplan) === undefined) return { allowed: false, reason: `Every ${kind} layer needs a reviewed revert plan before any mask applies.` };\n if (kind === \"emulatedevice\") {\n const preset = devicepresetof(options.device);\n if (!preset) return { allowed: false, reason: \"The device layer needs a reviewed preset with a name, positive integer width and height and a positive pixel ratio.\" };\n if (options.reload !== undefined && typeof options.reload !== \"boolean\") return { allowed: false, reason: \"The reviewed reload flag must be a boolean; the page reloads only when the reviewed plan asks.\" };\n return { allowed: true };\n }\n if (kind === \"emulatenetwork\") {\n const preset = networkpresetof(options.network);\n if (!preset) return { allowed: false, reason: \"The network layer needs a reviewed preset with a name and zero or positive latency, download and upload bounds.\" };\n if (options.window !== undefined && (typeof options.window !== \"number\" || !Number.isFinite(options.window) || options.window < 0)) return { allowed: false, reason: \"The reviewed offline window must be zero or a positive number of milliseconds with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"emulatelocate\") {\n const preset = locationpresetof(options.location);\n if (!preset) return { allowed: false, reason: \"The location layer needs a reviewed preset with a name, a latitude inside -90 and 90, a longitude inside -180 and 180 and a zero or positive accuracy radius.\" };\n if (!locationrangevalid(preset.latitude, preset.longitude)) return { allowed: false, reason: \"The reviewed latitude must stay inside -90 and 90 degrees and the longitude inside -180 and 180 degrees.\" };\n return { allowed: true };\n }\n if (kind === \"setuseragent\") {\n const preset = agentpresetof(options.agent);\n if (!preset) return { allowed: false, reason: \"The agent layer needs a reviewed preset with a user agent string of the reviewed grammar, a platform and a non-empty brand list.\" };\n if (!agentgrammarvalid(preset.useragent)) return { allowed: false, reason: \"The reviewed user agent string must use the reviewed grammar of tokens, separators and version marks without line breaks.\" };\n return { allowed: true };\n }\n if (kind === \"overridepermission\") {\n const grant = permissiongrantof(options.permission);\n if (!grant) return { allowed: false, reason: `The permission override needs a reviewed name of the browser permission set (${browserpermissions.join(\", \")}) and a state of ${permissionstates.join(\", \")}.` };\n void permissiongrade(grant.name);\n return { allowed: true };\n }\n if (kind === \"blackboxscripts\") {\n const rules = Array.isArray(options.rules) ? options.rules.flatMap(rule => { const parsed = blackboxruleof(rule); return parsed !== undefined ? [parsed] : []; }) : [];\n if (rules.length === 0) return { allowed: false, reason: \"The blackbox layer needs a reviewed non-empty rule list where every pattern names its origin explicitly and carries a trace scope.\" };\n return { allowed: true };\n }\n return { allowed: true };\n}\n\n/** Validates one permission override name against the reviewed browser permission set. */\nexport function permissionnamevalid(name: string): policyevaluation {\n if (!browserpermissions.includes(name)) return { allowed: false, reason: `The permission ${name} stays outside the reviewed browser permission set: ${browserpermissions.join(\", \")}.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed session parameter grammar of the 1.1.49 memory family: snapshot plans with the scope, the section toggles of the reviewed grammar and the optional auto interval whose period, maximum snapshot count and expiry stay user choices with no code ceiling, restore plans with their tab, form and capture policies behind the explicit restore review, session filings with unique reviewed names and folders, diffs of two saved records, searches with the term grammar and the field set, exports behind the explicit export review and imports of the known file format behind the full record review. */\nfunction validatesessiongrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"persiststate\") {\n if (options.resume !== undefined && typeof options.resume !== \"boolean\") return { allowed: false, reason: \"The reviewed resume flag must be a boolean.\" };\n return { allowed: true };\n }\n if (kind === \"capturesession\") {\n const plan = snapshotplanof(options.snapshot);\n if (!plan) return { allowed: false, reason: \"The session capture needs a reviewed snapshot plan with its scope, a non-empty section list of the reviewed grammar (tabs, scroll, forms, storage, cookies) and the capture link flag.\" };\n if (plan.auto !== undefined) {\n const interval = autointervalof((options.snapshot as Record<string, unknown>).auto);\n if (interval === undefined) return { allowed: false, reason: \"The reviewed auto snapshot interval needs a positive period, a positive maximum snapshot count and a zero or positive expiry window with no code ceiling.\" };\n }\n return { allowed: true };\n }\n if (kind === \"restoresession\") {\n if (typeof options.sessionid !== \"string\" || !options.sessionid.trim()) return { allowed: false, reason: \"The session restore needs the reviewed session id of the saved record.\" };\n if (restoreplanof(options.restore) === undefined) return { allowed: false, reason: \"The session restore needs a reviewed restore plan with its tab, form and capture policies.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"Every session restore needs the explicit restore review with its tabs, form state and captures listed before it reopens anything.\" };\n return { allowed: true };\n }\n if (kind === \"namedsessions\") {\n if (typeof options.sessionid !== \"string\" || !options.sessionid.trim()) return { allowed: false, reason: \"The session filing needs the reviewed session id of the saved record.\" };\n if (typeof options.name !== \"string\" || !options.name.trim()) return { allowed: false, reason: \"The session filing needs a reviewed non-empty session name.\" };\n if (options.folder !== undefined && (typeof options.folder !== \"string\" || !options.folder.trim())) return { allowed: false, reason: \"The reviewed folder name must be a non-empty string.\" };\n if (options.tags !== undefined && (!Array.isArray(options.tags) || !options.tags.every(tag => typeof tag === \"string\" && tag.trim()))) return { allowed: false, reason: \"The reviewed tag list must be a list of non-empty strings.\" };\n return { allowed: true };\n }\n if (kind === \"diffsessions\") {\n if (typeof options.left !== \"string\" || !options.left.trim() || typeof options.right !== \"string\" || !options.right.trim()) return { allowed: false, reason: \"The session diff needs the reviewed ids of both saved sessions.\" };\n return { allowed: true };\n }\n if (kind === \"searchsessions\") {\n if (searchqueryof(options.query) === undefined) return { allowed: false, reason: \"The session search needs a reviewed query with a non-empty term list, fields of the reviewed grammar (urls, titles, names, text) and an optional time window.\" };\n return { allowed: true };\n }\n if (kind === \"exportsessions\") {\n if (options.reviewed !== true) return { allowed: false, reason: \"Session exports need the explicit export review before any session file leaves the device.\" };\n if (options.ids !== undefined && (!Array.isArray(options.ids) || options.ids.length === 0 || !options.ids.every(id => typeof id === \"string\" && id.trim()))) return { allowed: false, reason: \"The reviewed export id list must be a non-empty list of saved session ids.\" };\n return { allowed: true };\n }\n if (kind === \"importsessions\") {\n if (options.reviewed !== true) return { allowed: false, reason: \"Session imports need the explicit full record review before any record joins the library.\" };\n if (importsessionfile(options.file) === undefined) return { allowed: false, reason: \"The session import needs a reviewed file of the known format version with an intact checksum.\" };\n return { allowed: true };\n }\n return { allowed: true };\n}\n\n/** Requires the explicit restore review flag and the reviewed restore plan before any session restore reopens a tab; the review lists every tab, form state and capture first. */\nexport function restorereviewgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n if (restoreplanof(options.restore) === undefined) return { allowed: false, reason: \"Every session restore needs a reviewed restore plan with its tab, form and capture policies.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"The session restore needs the explicit restore review of its tabs, form state and captures before it reopens anything.\" };\n return { allowed: true };\n}\n\n/** The session consent gate of every session memory step: a live session, an approved plan and the restore review of every restore; crash restore prompts stay inside the same consent model. */\nexport function sessionrestoregate(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: \"run the session memory step\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Session memory steps need an approved plan before they run.\" };\n if (input.step.kind === \"restoresession\") return restorereviewgranted(input.step);\n return { allowed: true };\n}\n\n/** Returns the origins a restore reopens outside the grants so the restore skips and reports them; captures and cookies restore only with their origin grants. */\nexport function restoreoriginsgranted(urls: string[], grants: string[]): { allowed: boolean; skippedorigins: string[] } {\n const covered = new Set(grants);\n const skippedorigins: string[] = [];\n for (const url of urls) {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { origin = \"\"; }\n if (!origin || !covered.has(origin)) skippedorigins.push(origin || url);\n }\n return { allowed: skippedorigins.length === 0, skippedorigins: [...new Set(skippedorigins)] };\n}\n\n/** Requires session names to stay unique inside the library so a filing never shadows another saved session. */\nexport function sessionnameunique(name: string, records: Array<{ id: string; name: string }>, recordid?: string): policyevaluation {\n if (records.some(record => record.name === name && record.id !== recordid)) return { allowed: false, reason: `The session name ${name} already exists in the library; review a unique name.` };\n return { allowed: true };\n}\n\n/** Requires folder names to stay unique inside the folder tree so one folder never shadows another. */\nexport function sessionfolderunique(name: string, folders: Array<{ name: string }>): policyevaluation {\n if (folders.some(folder => folder.name === name)) return { allowed: false, reason: `The folder name ${name} already exists in the library; review a unique folder name.` };\n return { allowed: true };\n}\n\n/** Exposes the user configured retention window for saved session sections; an absent window keeps every section and no code ceiling applies. */\nexport function snapshotretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.sessionretention;\n}\n\n/** Validates the reviewed workflow parameter grammar of the 1.1.50 and 1.1.51 families: composition with the expanded block list so no step stays hidden, shareable step templates, workflow runs behind the explicit run review, dry runs of the known workflow, jittered delays and element waits of user configured bounds with no code ceiling, expressions whose operators match the operand kinds and result kinds, regex rules of bounded backtracking shapes applied to reviewed text, and the control flow payloads of conditionals, branching, loops with user configured safety bounds, foreach selectors, parallel branches with join policies and try catch with retry and timeout policies whose child kinds all stay inside the reviewed vocabulary. */\nfunction validateworkflowgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"composeworkflow\") {\n const payload = options.workflow;\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) return { allowed: false, reason: \"The workflow composition needs the reviewed workflow payload with its name, version, origins, steps and blocks.\" };\n const candidate = payload as Record<string, unknown>;\n if (typeof candidate.name !== \"string\" || !candidate.name.trim()) return { allowed: false, reason: \"The workflow composition needs a reviewed non-empty name.\" };\n if (typeof candidate.version !== \"number\" || !Number.isInteger(candidate.version) || candidate.version < 1) return { allowed: false, reason: \"The workflow version must be a positive integer.\" };\n if (!Array.isArray(candidate.origins) || candidate.origins.length === 0 || !candidate.origins.every(origin => typeof origin === \"string\" && origin.startsWith(\"https://\"))) return { allowed: false, reason: \"The workflow needs at least one granted HTTPS origin so every step stays inside the grants.\" };\n if (!Array.isArray(candidate.steps) || candidate.steps.length === 0 || !candidate.steps.every(entry => workflowstepof(entry) !== undefined || (entry && typeof entry === \"object\" && typeof (entry as Record<string, unknown>).block === \"string\"))) return { allowed: false, reason: \"The workflow needs a non-empty reviewed step list of the workflow step grammar or block invocations.\" };\n const blocks = Array.isArray(candidate.blocks) ? candidate.blocks.flatMap(block => { const parsed = workflowblockof(block); return parsed !== undefined ? [parsed] : []; }) : [];\n if (Array.isArray(candidate.blocks) && blocks.length !== (candidate.blocks as unknown[]).length) return { allowed: false, reason: \"The reviewed block list must carry unique lowercase names, labels and valid child steps.\" };\n try {\n const record = composeworkflow({ name: candidate.name, version: candidate.version, origins: candidate.origins as string[], steps: (candidate.steps as Array<Record<string, unknown>>).map(entry => \"block\" in entry ? { block: entry.block as string, label: typeof entry.label === \"string\" ? entry.label : entry.block as string } : workflowstepof(entry) as workflowstep), blocks, now: 0, kindallowed: candidatekind => { try { actionrisk(candidatekind as actionkind); return true; } catch { return false; } }, riskof: candidatekind => actionrisk(candidatekind as actionkind) });\n const inputs = Array.isArray(candidate.inputs) ? candidate.inputs.flatMap(name => typeof name === \"string\" ? [name] : []) : undefined;\n const checked = validateworkflow(record, { kindallowed: workflowkind => { try { actionrisk(workflowkind as actionkind); return true; } catch { return false; } }, ...(inputs !== undefined ? { inputs } : {}) });\n if (!checked.allowed) return checked;\n } catch (error) {\n return { allowed: false, reason: error instanceof Error ? error.message : \"The workflow payload failed its composition validation.\" };\n }\n return { allowed: true };\n }\n if (kind === \"savetemplate\") {\n const payload = options.template && typeof options.template === \"object\" && !Array.isArray(options.template) ? options.template as Record<string, unknown> : {};\n const template = steptemplateof({ id: \"templatereview\", origin: \"https://example.com\", sharedat: 0, ...payload });\n if (!template) return { allowed: false, reason: \"The step template needs a reviewed name and a valid workflow step it shares across workflows.\" };\n return { allowed: true };\n }\n if (kind === \"runworkflow\") {\n if (typeof options.workflowid !== \"string\" || !options.workflowid.trim()) return { allowed: false, reason: \"The workflow run needs the reviewed id of the composed workflow.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"Every real workflow run needs the explicit run review with its expanded step list shown before the first step executes.\" };\n if (options.variables !== undefined && (!options.variables || typeof options.variables !== \"object\" || Array.isArray(options.variables) || !Object.values(options.variables).every(value => typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\"))) return { allowed: false, reason: \"The reviewed run variables must be an object of string, number or boolean values.\" };\n return { allowed: true };\n }\n if (kind === \"dryrun\") {\n if (typeof options.workflowid !== \"string\" || !options.workflowid.trim()) return { allowed: false, reason: \"The dry run needs the reviewed id of the composed workflow.\" };\n return { allowed: true };\n }\n if (kind === \"delay\") {\n const delay = options.delay;\n if (!delay || typeof delay !== \"object\" || Array.isArray(delay)) return { allowed: false, reason: \"The delay needs a reviewed base and jitter window in options.\" };\n const reviewed = delay as Record<string, unknown>;\n if (typeof reviewed.base !== \"number\" || !Number.isFinite(reviewed.base) || reviewed.base < 0) return { allowed: false, reason: \"The reviewed delay base must be zero or a positive number of milliseconds.\" };\n if (typeof reviewed.jitter !== \"number\" || !Number.isFinite(reviewed.jitter) || reviewed.jitter < 0) return { allowed: false, reason: \"The reviewed delay jitter window must be zero or a positive number of milliseconds with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"waitelement\") {\n const wait = options.wait;\n if (!wait || typeof wait !== \"object\" || Array.isArray(wait)) return { allowed: false, reason: \"The element wait needs a reviewed selector, timeout and poll interval in options.\" };\n const reviewed = wait as Record<string, unknown>;\n if (typeof reviewed.selector !== \"string\" || !reviewed.selector.trim()) return { allowed: false, reason: \"The element wait needs a reviewed non-empty selector.\" };\n if (typeof reviewed.timeout !== \"number\" || !Number.isFinite(reviewed.timeout) || reviewed.timeout < 0) return { allowed: false, reason: \"The reviewed element wait timeout must be zero or a positive number of milliseconds with no code ceiling.\" };\n if (typeof reviewed.poll !== \"number\" || !Number.isFinite(reviewed.poll) || reviewed.poll < 0) return { allowed: false, reason: \"The reviewed element wait poll interval must be zero or a positive number of milliseconds with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"compute\") {\n const expression = expressionof(options.expression);\n if (!expression) return { allowed: false, reason: `The expression step needs a reviewed expression with operands, an operator of the reviewed set (${expressionoperators.join(\", \")}) and a result variable of a reviewed kind.` };\n const operatorcheck = validatexpressionoperators(expression);\n if (!operatorcheck.allowed) return operatorcheck;\n return { allowed: true };\n }\n if (kind === \"extractvars\") {\n const rule = regexruleof(options.rule);\n if (!rule) return { allowed: false, reason: \"The variable extraction needs a reviewed regex rule with its pattern, flags and named capture groups.\" };\n const shapecheck = validateregexrule(rule.pattern);\n if (!shapecheck.allowed) return shapecheck;\n if (typeof options.text !== \"string\") return { allowed: false, reason: \"The variable extraction needs the reviewed text the regex rule applies to.\" };\n return { allowed: true };\n }\n if (kind === \"condition\") {\n const condition = conditionof(options.condition);\n if (!condition) return { allowed: false, reason: \"The condition step needs a reviewed boolean expression in its options.\" };\n const operatorcheck = validatexpressionoperators(condition.expression);\n if (!operatorcheck.allowed) return operatorcheck;\n return { allowed: true };\n }\n if (kind === \"branch\") {\n const branch = branchof(options.branch);\n if (!branch) return { allowed: false, reason: \"The branch step needs reviewed unique paths with boolean match expressions and an else path in its options so every branch terminates.\" };\n for (const path of [...branch.paths, branch.else]) {\n if (path.when === undefined) continue;\n const operatorcheck = validatexpressionoperators(path.when);\n if (!operatorcheck.allowed) return operatorcheck;\n }\n return controlchildkinds(step);\n }\n if (kind === \"loop\") {\n const loop = loopof(options.loop);\n if (!loop) return { allowed: false, reason: \"The loop step needs a reviewed list variable, distinct item and index variables, an optional positive safety bound and a non-empty body in its options; an absent bound keeps the documented default.\" };\n return controlchildkinds(step);\n }\n if (kind === \"repeatuntil\") {\n const repeat = repeatuntilof(options.repeatuntil);\n if (!repeat) return { allowed: false, reason: \"The repeat until step needs a reviewed convergence expression, an optional positive safety bound and a non-empty body in its options.\" };\n const operatorcheck = validatexpressionoperators(repeat.until);\n if (!operatorcheck.allowed) return operatorcheck;\n return controlchildkinds(step);\n }\n if (kind === \"whileloop\") {\n const condition = whileof(options.while);\n if (!condition) return { allowed: false, reason: \"The while step needs a reviewed condition, a mandatory positive safety bound and a non-empty body in its options; a while loop without a safety bound is refused.\" };\n const operatorcheck = validatexpressionoperators(condition.while);\n if (!operatorcheck.allowed) return operatorcheck;\n return controlchildkinds(step);\n }\n if (kind === \"foreach\") {\n const foreach = foreachof(options.foreach);\n if (!foreach) return { allowed: false, reason: \"The foreach step needs a reviewed non-empty selector, distinct item and index variables and a non-empty body in its options.\" };\n return controlchildkinds(step);\n }\n if (kind === \"parallel\") {\n const parallel = parallelof(options.parallel);\n if (!parallel) return { allowed: false, reason: \"The parallel step needs uniquely identified branches with bodies and a join policy of the first, last or fail strategy with cancel or continue on branch failure in its options.\" };\n return controlchildkinds(step);\n }\n if (kind === \"trycatch\") {\n const fragile = tryof(options.try);\n if (!fragile) return { allowed: false, reason: \"The try step needs a fragile body, a catch handler and optional retry and timeout policies in its options: attempts stay user configured with no code ceiling, backoff is fixed or exponential and budgets are positive.\" };\n return controlchildkinds(step);\n }\n return { allowed: true };\n}\n\n/** Checks every child step of a control payload against the reviewed action vocabulary so no control construct hides an unreviewed kind behind its body. */\nfunction controlchildkinds(step: toolstep): policyevaluation {\n const children = controlsteps({ id: step.id, kind: step.kind, label: step.summary, ...(step.options !== undefined ? { options: step.options } : {}) });\n for (const child of children) {\n try { actionrisk(child.kind); } catch { return { allowed: false, reason: `The ${child.kind} step inside the control payload of the ${step.kind} step is not a reviewed action kind.` }; }\n }\n return { allowed: true };\n}\n\n/** Rejects unbounded backtracking shapes of reviewed regex patterns: a quantified group whose body itself ends with an unbounded quantifier can explode on adversarial text, so the shape is refused while bounded repetitions stay user choices. */\nexport function validateregexrule(pattern: string): policyevaluation {\n try { new RegExp(pattern); } catch { return { allowed: false, reason: \"The reviewed regex pattern does not compile.\" }; }\n const nestedquantifier = /\\((?:[^()\\\\]|\\\\.)*[+*}]\\)[+*{]/.test(pattern) || /\\(\\)[+*{]/.test(pattern);\n if (nestedquantifier) return { allowed: false, reason: \"The reviewed regex pattern nests an unbounded quantifier inside a quantified group and is refused because adversarial text could explode the backtracking.\" };\n const unboundedrepeat = /\\{\\d+,\\}/.test(pattern);\n if (unboundedrepeat && /\\([^)]*\\{\\d+,\\}[^)]*\\)[+*{]/.test(pattern)) return { allowed: false, reason: \"The reviewed regex pattern repeats an unbounded group and is refused because adversarial text could explode the backtracking.\" };\n return { allowed: true };\n}\n\n/** Validates the reviewed expression operators against the operand kinds and the result kind: arithmetic needs numbers and returns numbers, logic needs booleans and returns booleans, comparison needs numbers and returns booleans, text operators return strings or booleans and length returns a number. */\nfunction validatexpressionoperators(expression: import(\"./types.js\").expressiontype): policyevaluation {\n const numeric = new Set([\"add\", \"subtract\", \"multiply\", \"divide\", \"modulo\"]);\n const logic = new Set([\"and\", \"or\", \"not\"]);\n const comparison = new Set([\"less\", \"greater\", \"lessequal\", \"greaterequal\"]);\n const text = new Set([\"concat\", \"contains\"]);\n const operator = expression.operator;\n if (numeric.has(operator)) {\n for (const operand of [expression.left, expression.right]) {\n if (operand === undefined) continue;\n if (operand.literal !== undefined && typeof operand.literal === \"boolean\") return { allowed: false, reason: `The ${operator} operator needs numeric operands; boolean literals are refused.` };\n }\n if (expression.resultkind !== \"number\" && expression.resultkind !== \"string\") return { allowed: false, reason: `The ${operator} operator needs a number result kind.` };\n }\n if (logic.has(operator)) {\n for (const operand of [expression.left, expression.right]) {\n if (operand === undefined) continue;\n if (operand.literal !== undefined && typeof operand.literal !== \"boolean\") return { allowed: false, reason: `The ${operator} operator needs boolean operands; non boolean literals are refused.` };\n }\n if (expression.resultkind !== \"boolean\") return { allowed: false, reason: `The ${operator} operator needs a boolean result kind.` };\n if (operator === \"not\" && expression.right !== undefined) return { allowed: false, reason: \"The not operator takes one operand only.\" };\n }\n if (comparison.has(operator) && expression.resultkind !== \"boolean\") return { allowed: false, reason: `The ${operator} operator needs a boolean result kind.` };\n if (text.has(operator) && expression.resultkind !== \"boolean\" && expression.resultkind !== \"string\") return { allowed: false, reason: `The ${operator} operator needs a string or boolean result kind.` };\n if (operator === \"contains\" && expression.resultkind !== \"boolean\") return { allowed: false, reason: \"The contains operator needs a boolean result kind.\" };\n if (operator === \"length\") {\n if (expression.right !== undefined) return { allowed: false, reason: \"The length operator takes one operand only.\" };\n if (expression.resultkind !== \"number\") return { allowed: false, reason: \"The length operator needs a number result kind.\" };\n }\n if ((operator === \"equal\" || operator === \"notequal\") && !new Set([\"boolean\", \"string\", \"number\"]).has(expression.resultkind)) return { allowed: false, reason: \"The equality operator needs a primitive result kind.\" };\n return { allowed: true };\n}\n\n/** The workflow consent gate: a live session, an approved plan and the explicit run review of every real run; dry runs stay read only inside the same session and plan gates. */\nexport function workflowgate(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: \"run the workflow step\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Workflow steps need the approved plan review before they run.\" };\n if (input.step.kind === \"runworkflow\") {\n let runoptions: Record<string, unknown> = {};\n try { runoptions = parseoptions(input.step); } catch { runoptions = {}; }\n if (runoptions.reviewed !== true) return { allowed: false, reason: \"Every real workflow run needs the explicit run review with its expanded step list shown before the first step executes.\" };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed trigger parameter grammar of the 1.1.52 family: every kind arms exactly one rule behind the explicit arm review, the workflow reference must name a composed workflow, the match payloads follow their family grammar \u2014 visit origins and url list entries must be HTTPS urls, url patterns must parse as HTTPS globs, cron expressions must parse as five field schedules with named weekdays and months and a resolvable timezone, interval periods stay positive with zero or positive jitter, webhook secrets must clear the documented entropy floor with a non-empty payload schema, event names must come from the observed event catalog and context menu titles stay non-empty \u2014 while cooldown windows stay user configured positive values with the documented default of the webhook and event families winning only when the review configures none. */\nfunction validatetriggergrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const family = triggerfamilyof(step.kind);\n if (family === undefined) return { allowed: false, reason: \"The trigger step is not a reviewed trigger kind.\" };\n if (typeof options.workflowid !== \"string\" || !options.workflowid.trim()) return { allowed: false, reason: \"Every trigger rule needs the reviewed id of the composed workflow it launches.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"Every trigger rule needs the explicit arm review with its match fields and bound workflow shown before it arms.\" };\n if (options.label !== undefined && (typeof options.label !== \"string\" || !options.label.trim())) return { allowed: false, reason: \"The reviewed trigger label must be a non-empty string.\" };\n if (options.cooldown !== undefined && (typeof options.cooldown !== \"number\" || !Number.isFinite(options.cooldown) || options.cooldown <= 0)) return { allowed: false, reason: \"The reviewed cooldown window must be a positive number of milliseconds with no code ceiling; the webhook and event families keep the documented default when the review configures none.\" };\n const payload = options.rule;\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) return { allowed: false, reason: `The ${step.kind} step needs its reviewed rule payload in options.` };\n if (triggerpayloadof(family, payload) === undefined) {\n if (family === \"visit\") return { allowed: false, reason: \"The visit rule needs a non-empty reviewed list of HTTPS origins it fires on.\" };\n if (family === \"url\") return { allowed: false, reason: \"The url rule needs a reviewed HTTPS glob url pattern; `*` spans one path segment and `**` spans across segments.\" };\n if (family === \"menu\") return { allowed: false, reason: \"The menu rule needs a reviewed non-empty context menu entry title.\" };\n if (family === \"key\") return { allowed: false, reason: \"The keyboard shortcut rule needs a reviewed lowercase command name and an optional suggested key binding.\" };\n if (family === \"cron\") return { allowed: false, reason: \"The cron rule needs a reviewed five field cron expression of minutes, hours, days, months and weekdays with named weekdays and months and an optional resolvable timezone; unparseable schedules are refused.\" };\n if (family === \"interval\") return { allowed: false, reason: \"The interval rule needs a reviewed positive period in milliseconds with an optional zero or positive jitter window.\" };\n if (family === \"urllist\") return { allowed: false, reason: \"The url list rule needs a reviewed non-empty list of HTTPS urls its workflow runs across.\" };\n if (family === \"webhook\") return { allowed: false, reason: `The webhook rule needs a reviewed shared secret of at least twenty four characters mixing letters and digits and a non-empty payload schema of named string, number or boolean fields.` };\n if (family === \"event\") return { allowed: false, reason: `The page event rule needs a reviewed non-empty list of event names of the observed event catalog: ${triggereventcatalog.join(\", \")}.` };\n return { allowed: false, reason: \"The trigger rule payload does not follow its family grammar.\" };\n }\n if (family === \"cron\") {\n const candidate = payload as Record<string, unknown>;\n if (typeof candidate.cron === \"string\" && cronparse(candidate.cron) === undefined) return { allowed: false, reason: \"The cron expression does not parse as a five field schedule and is refused.\" };\n }\n if (family === \"webhook\") {\n const candidate = payload as Record<string, unknown>;\n if (typeof candidate.secret === \"string\" && !webhooksecretok(candidate.secret)) return { allowed: false, reason: \"The webhook shared secret must hold at least twenty four characters mixing letters and digits; the entropy floor is a floor, never a cap.\" };\n }\n const armed = armrule({ family, workflowid: options.workflowid, ...(typeof options.label === \"string\" && options.label.trim() ? { label: options.label } : {}), payload, ...(typeof options.cooldown === \"number\" ? { cooldown: options.cooldown } : {}), now: 0 });\n if (armed === undefined) return { allowed: false, reason: \"The trigger rule payload does not arm as a reviewed rule.\" };\n return { allowed: true };\n}\n\n/** The trigger consent gate: a live session, an approved plan and the explicit arm review of every rule; automatic launchers never arm outside the consent gates. */\nexport function triggergate(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: \"arm the trigger rule\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Trigger rules need the approved plan review before they arm.\" };\n let triggeroptions: Record<string, unknown> = {};\n try { triggeroptions = parseoptions(input.step); } catch { triggeroptions = {}; }\n if (triggeroptions.reviewed !== true) return { allowed: false, reason: \"Every trigger rule needs the explicit arm review with its match fields and bound workflow shown before it arms.\" };\n return { allowed: true };\n}\n\n/** Returns the match origins of one reviewed trigger rule so callers can keep every rule inside the workflow grant list; triggers on origins outside the grants are refused. */\nexport function triggerorigins(step: toolstep): string[] {\n let triggeroptions: Record<string, unknown> = {};\n try { triggeroptions = parseoptions(step); } catch { return []; }\n const family = triggerfamilyof(step.kind);\n if (family === undefined) return [];\n const armed = armrule({ family, workflowid: typeof triggeroptions.workflowid === \"string\" ? triggeroptions.workflowid : \"\", payload: triggeroptions.rule, ...(typeof triggeroptions.cooldown === \"number\" ? { cooldown: triggeroptions.cooldown } : {}), now: 0 });\n if (armed === undefined) return [];\n const origins: string[] = [];\n for (const origin of armed.origins ?? []) origins.push(origin);\n if (armed.pattern !== undefined) { try { origins.push(new URL(armed.pattern).origin); } catch { /* the pattern grammar already refused unparseable patterns */ } }\n for (const url of armed.urls ?? []) { try { origins.push(new URL(url).origin); } catch { /* the url list grammar already refused unparseable urls */ } }\n return [...new Set(origins)];\n}\n\n/** Returns the read only projection of one workflow step for dry runs: read class steps report their would be outcome while interaction and mutation steps carry no projection and the dry run refuses them; a control step projects only when every child step of its payload grades read. */\nexport function dryrunprojection(step: workflowstep): string | undefined {\n if (iscontrolflowkind(step.kind)) {\n for (const child of controlsteps(step)) {\n const childrisk = resolvedrisk({ id: child.id, kind: child.kind, summary: child.label, risk: \"read\", ...(child.target !== undefined ? { target: child.target } : {}), ...(child.value !== undefined ? { value: child.value } : {}), ...(child.options !== undefined ? { options: child.options } : {}) });\n if (childrisk !== \"read\") return undefined;\n }\n if (step.kind === \"condition\") return \"The condition step would evaluate its reviewed expression over the extracted values with no page side effect.\";\n if (step.kind === \"branch\") return \"The branch step would choose one reviewed path by page state and only the chosen path would run.\";\n if (step.kind === \"loop\") return \"The loop step would iterate its reviewed list binding the item and index variables per iteration inside the safety bound.\";\n if (step.kind === \"repeatuntil\") return \"The repeat until step would rerun its body until the convergence expression holds inside the safety bound.\";\n if (step.kind === \"whileloop\") return \"The while step would loop while its condition holds inside the reviewed safety bound.\";\n if (step.kind === \"foreach\") return \"The foreach step would iterate the elements of its reviewed selector binding the item and index variables per iteration.\";\n if (step.kind === \"parallel\") return \"The parallel step would run its branches concurrently and join their outcomes under the reviewed strategy.\";\n return \"The try step would run its fragile body and only the catch handler on failure.\";\n }\n const risk = resolvedrisk({ id: step.id, kind: step.kind, summary: step.label, risk: \"read\", ...(step.target !== undefined ? { target: step.target } : {}), ...(step.value !== undefined ? { value: step.value } : {}), ...(step.options !== undefined ? { options: step.options } : {}) });\n if (risk !== \"read\") return undefined;\n if (step.kind === \"delay\") return `The delay step would sleep its reviewed base inside the jitter window.`;\n if (step.kind === \"waitelement\") return `The element wait step would poll ${step.target ?? \"the reviewed selector\"} until appearance or the reviewed timeout.`;\n if (step.kind === \"compute\") return `The compute step would evaluate its reviewed expression into the result variable.`;\n if (step.kind === \"extractvars\") return `The variable extraction step would apply its reviewed regex rule and store the named captures.`;\n return `The ${step.kind} step would run read only and mutate nothing.`;\n}\n\n/** Validates one reviewed permission state of an override. */\nexport function permissionstatevalid(state: string): policyevaluation {\n if (!permissionstates.includes(state as permissionstate)) return { allowed: false, reason: `The reviewed permission state must be one of ${permissionstates.join(\", \")}.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed devtools parameter grammar of the 1.1.46 family: enabled domains bounded by the reviewed domain grammar, the required teardown plan of every attach, raw commands of the Domain.method form, domain event rules with match filters inside the reviewed watch window, breakpoints with conditions of the reviewed expression grammar, step modes, reviewed watch expressions, and script overrides with the explicit reviewed flag and a url pattern that names its origin. */\nfunction validatecdpgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"attachcdp\") {\n if (!Array.isArray(options.domains) || options.domains.length === 0 || !options.domains.every((domain): domain is string => typeof domain === \"string\" && cdpdomains.includes(domain))) return { allowed: false, reason: `The attach needs a non-empty enabled domain list of the reviewed domain grammar: ${cdpdomains.join(\", \")}.` };\n if (teardownplanof(options.teardown) === undefined) return { allowed: false, reason: \"Every attach needs a reviewed teardown plan with its revert steps and resume policy before approval.\" };\n if (options.allowlist !== undefined) {\n const allowlist = cdpallowlistof(options.allowlist);\n if (!allowlist || !allowlist.domains.every(domain => (options.domains as string[]).includes(domain))) return { allowed: false, reason: \"The reviewed method allowlist must stay inside the enabled domains of the attach.\" };\n }\n const budgetcheck = debugwaitbudgetallowed(typeof options.wait === \"number\" ? options.wait : undefined, undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"detachcdp\") return { allowed: true };\n if (kind === \"cdpcmd\") {\n const command = options.command && typeof options.command === \"object\" && !Array.isArray(options.command) ? options.command as Record<string, unknown> : undefined;\n if (!command || typeof command.method !== \"string\" || methoddomain(command.method) === undefined) return { allowed: false, reason: \"The raw command needs a reviewed method of the Domain.method form.\" };\n if (command.params !== undefined && (typeof command.params !== \"object\" || Array.isArray(command.params))) return { allowed: false, reason: \"The raw command params must be a JSON object.\" };\n if (command.resultpath !== undefined && typeof command.resultpath !== \"string\") return { allowed: false, reason: \"The reviewed result path must be a dotted path string.\" };\n return { allowed: true };\n }\n if (kind === \"watchcdp\") {\n if (!Array.isArray(options.events) || options.events.length === 0 || !options.events.every(rule => cdpeventruleof(rule) !== undefined)) return { allowed: false, reason: \"The event watch needs a non-empty reviewed list of domain event rules of the reviewed domain grammar.\" };\n let watchwindow: number | undefined;\n if (options.watch !== undefined) {\n const watch = options.watch;\n if (!watch || typeof watch !== \"object\" || Array.isArray(watch)) return { allowed: false, reason: \"The reviewed event watch window must be an object.\" };\n const reviewed = watch as Record<string, unknown>;\n if (reviewed.window !== undefined) {\n if (typeof reviewed.window !== \"number\" || !Number.isFinite(reviewed.window) || reviewed.window < 0) return { allowed: false, reason: \"The reviewed event watch window must be zero or a positive number of milliseconds.\" };\n watchwindow = reviewed.window;\n }\n }\n if (watchwindow === undefined) return { allowed: false, reason: \"The event watch needs a reviewed lifetime window before any domain event is observed.\" };\n const budgetcheck = debugwaitbudgetallowed(watchwindow, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"setbreakpoint\") {\n const breakpoint = breakpointinputof(options.breakpoint);\n if (!breakpoint) return { allowed: false, reason: \"The breakpoint needs a reviewed script url and a zero based line.\" };\n if (!ishttpsurl(breakpoint.url)) return { allowed: false, reason: \"The breakpoint script url must be a reviewed HTTPS url.\" };\n if (breakpoint.condition !== undefined) {\n const conditioncheck = validatebreakpointcondition(breakpoint.condition);\n if (!conditioncheck.allowed) return conditioncheck;\n }\n return { allowed: true };\n }\n if (kind === \"stepcode\") {\n if (stepmodeof(options.mode) === undefined) return { allowed: false, reason: \"The step code mode must be one of stepover, stepinto, stepout or resume.\" };\n return { allowed: true };\n }\n if (kind === \"watchexpr\") {\n if (watchexpressionof(options.expression) === undefined) return { allowed: false, reason: \"The watch expression needs the reviewed expression text.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"Watch expressions must be reviewed before evaluation; set the explicit reviewed flag on the step.\" };\n return { allowed: true };\n }\n if (kind === \"overridescript\") {\n const override = overrideinputof(options.override);\n if (!override) return { allowed: false, reason: \"The script override needs a reviewed url pattern and its full fixture source.\" };\n if (patternorigin(override.urlpattern) === undefined) return { allowed: false, reason: \"Script overrides without a named https origin pattern are refused.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"The full fixture source must be reviewed before the script override runs; set the explicit reviewed flag on the step.\" };\n return { allowed: true };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed profiling parameter grammar of the 1.1.47 family: flow specs of the reviewed metric set inside a reviewed watch window, heap snapshots with the user chosen interval only, growth tracking with the reviewed slope, cpu profiles bounded by the reviewed wait budget, layout shift watches with the user chosen window only, trace records bounded by the reviewed category list and byte ceiling, trace annotations that carry step ids, offline replays of stored traces and source map capture scripts of explicit https urls. */\nfunction validateprofilegrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"measureflow\") {\n if (flowspecof(options.flow) === undefined) return { allowed: false, reason: `The flow measurement needs a reviewed flow spec with its mark prefix, step window and metric list of the reviewed metric set: navigation, paint, lcp, fid, interaction, blocking.` };\n const watch = options.watch && typeof options.watch === \"object\" && !Array.isArray(options.watch) ? options.watch as Record<string, unknown> : {};\n if (typeof watch.window !== \"number\" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: \"The flow measurement needs a reviewed watch window of zero or more milliseconds.\" };\n const budgetcheck = debugwaitbudgetallowed(watch.window, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"heapshot\") {\n const heap = options.heap && typeof options.heap === \"object\" && !Array.isArray(options.heap) ? options.heap as Record<string, unknown> : {};\n if (heap.interval !== undefined && (typeof heap.interval !== \"number\" || !Number.isFinite(heap.interval) || heap.interval < 0)) return { allowed: false, reason: \"The reviewed heap snapshot interval must be zero or a positive number of milliseconds and stays a user choice with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"trackmemory\") {\n const growth = options.growth && typeof options.growth === \"object\" && !Array.isArray(options.growth) ? options.growth as Record<string, unknown> : undefined;\n if (!growth || typeof growth.slope !== \"number\" || !Number.isFinite(growth.slope) || growth.slope < 0) return { allowed: false, reason: \"Memory growth tracking needs the reviewed slope in bytes per millisecond before any sample is flagged.\" };\n if (growth.interval !== undefined && (typeof growth.interval !== \"number\" || !Number.isFinite(growth.interval) || growth.interval < 0)) return { allowed: false, reason: \"The reviewed sampling interval must be zero or a positive number of milliseconds and stays a user choice with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"profilecpu\") {\n const profile = options.profile && typeof options.profile === \"object\" && !Array.isArray(options.profile) ? options.profile as Record<string, unknown> : undefined;\n if (!profile || typeof profile.duration !== \"number\" || !Number.isFinite(profile.duration) || profile.duration < 0) return { allowed: false, reason: \"The cpu profile needs a reviewed duration of zero or more milliseconds.\" };\n const budgetcheck = debugwaitbudgetallowed(profile.duration, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"watchshifts\") {\n const watch = options.watch && typeof options.watch === \"object\" && !Array.isArray(options.watch) ? options.watch as Record<string, unknown> : {};\n if (typeof watch.window !== \"number\" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: \"The layout shift watch needs a reviewed observation window of zero or more milliseconds; the window stays a user choice with no code ceiling.\" };\n if (options.threshold !== undefined && (typeof options.threshold !== \"number\" || !Number.isFinite(options.threshold) || options.threshold < 0)) return { allowed: false, reason: \"The reviewed shift score threshold must be zero or a positive number.\" };\n const budgetcheck = debugwaitbudgetallowed(watch.window, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"traceload\") {\n const trace = options.trace && typeof options.trace === \"object\" && !Array.isArray(options.trace) ? options.trace as Record<string, unknown> : undefined;\n if (!trace || !Array.isArray(trace.categories) || trace.categories.length === 0 || !trace.categories.every((category): category is string => typeof category === \"string\" && tracecategories.includes(category))) return { allowed: false, reason: `The trace record needs a non-empty reviewed category list of the reviewed category grammar: ${tracecategories.join(\", \")}.` };\n if (typeof trace.window !== \"number\" || !Number.isFinite(trace.window) || trace.window < 0) return { allowed: false, reason: \"The trace record needs a reviewed window of zero or more milliseconds and stops at the reviewed window end.\" };\n if (trace.exporttarget !== undefined && trace.exporttarget !== \"memory\" && trace.exporttarget !== \"download\") return { allowed: false, reason: \"The trace export target must be memory or download.\" };\n const budgetcheck = debugwaitbudgetallowed(trace.window, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"annotatetrace\" || kind === \"replaytrace\") {\n const trace = options.trace && typeof options.trace === \"object\" && !Array.isArray(options.trace) ? options.trace as Record<string, unknown> : undefined;\n if (!trace || typeof trace.traceid !== \"string\" || !trace.traceid.trim()) return { allowed: false, reason: `The ${kind === \"annotatetrace\" ? \"trace annotation\" : \"trace replay\"} needs the stored trace id of a recorded trace.` };\n if (kind === \"replaytrace\") return { allowed: true };\n if (!Array.isArray(options.annotations) || options.annotations.length === 0 || !options.annotations.every(annotation => annotationof(annotation) !== undefined)) return { allowed: false, reason: \"Exported traces carry their step annotations: every annotation needs a step id, a label and an optional offset from the trace start.\" };\n return { allowed: true };\n }\n if (kind === \"capturesourcemaps\") {\n if (options.scripts !== undefined) {\n if (!Array.isArray(options.scripts) || options.scripts.length === 0 || !options.scripts.every((url): url is string => typeof url === \"string\" && ishttpsurl(url))) return { allowed: false, reason: \"The source map capture scripts must be a non-empty list of reviewed HTTPS urls.\" };\n }\n return { allowed: true };\n }\n return { allowed: true };\n}\n\n/** Resolves the reviewed cdp allowlist of one plan: the enabled domains and method gates of its attachcdp step, the reviewable contract every later cdp kind of the plan must stay inside. */\nexport function planallowlist(steps: toolstep[]): cdpallowlist | undefined {\n const attach = steps.find(step => step.kind === \"attachcdp\");\n if (!attach) return undefined;\n let options: Record<string, unknown> = {};\n try { options = parseoptions(attach); } catch { options = {}; }\n const domains = Array.isArray(options.domains) ? options.domains.filter((domain): domain is string => typeof domain === \"string\" && cdpdomains.includes(domain)) : [];\n if (domains.length === 0) return undefined;\n const gated = cdpallowlistof(options.allowlist);\n return { domains, ...(gated?.methods !== undefined ? { methods: gated.methods } : {}) };\n}\n\n/** Resolves the reviewed outbound url of a network control step at review time: the form url of postform, the upload url of postfiles and the token url of authflow. */\nexport function controltarget(step: toolstep): string | undefined {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n for (const key of [\"form\", \"upload\"] as const) {\n const value = options[key];\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const url = (value as Record<string, unknown>).url;\n if (typeof url === \"string\" && url.trim()) return url.trim();\n }\n }\n if (step.kind === \"authflow\") {\n const flow = oauthflowof(options.oauth);\n if (flow) return flow.tokenurl;\n }\n return undefined;\n}\n\n/** Resolves the reviewed channel url of a socket step at review time: the socket url of opensocket, the event stream url of subscribesse and the poll url of longpoll. */\nexport function sockettarget(step: toolstep): string | undefined {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n for (const key of [\"socket\", \"subscription\", \"poll\"] as const) {\n const value = options[key];\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const url = (value as Record<string, unknown>).url;\n if (typeof url === \"string\" && url.trim()) return url.trim();\n }\n }\n return undefined;\n}\n\n/** Requires the active tab grant of the live session for every media kind: the session tab and origin must match and the origin grant must cover the active origin. */\nexport function mediagate(session: agentsession | undefined, tabid: number, origin: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the media capture.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot capture media.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot capture media.\" };\n if (session.tabid !== tabid) return { allowed: false, reason: `The media capture needs the active tab grant of session tab ${session.tabid} and refuses tab ${tabid}.` };\n if (!origingranted(session, origin)) return { allowed: false, reason: `The media capture of ${origin} needs the session origin grants first.` };\n return { allowed: true };\n}\n\n/** Requires an approved recording consent prompt before any recording of user activity starts; every start consumes its own prompt. */\nexport function recordingconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"A recording of user activity requires a reviewed consent ref in options before it starts.\" };\n return { allowed: true };\n}\n\n/** Exposes the recording duration window as a user configured choice in milliseconds; an absent window leaves the duration to the reviewed step options with no code ceiling. */\nexport function recordingwindow(settings: runsettings | undefined): number | undefined {\n const window = settings?.recordingwindow;\n return typeof window === \"number\" && Number.isFinite(window) && window > 0 ? window : undefined;\n}\n\n/** Keeps the reviewed lapse plan inside the reviewed wait budget: the whole lapse duration must fit the wait window with no code ceiling on either side. */\nexport function lapsebudgetallowed(interval: number, duration: number, wait: number | undefined): policyevaluation {\n if (!(interval > 0)) return { allowed: false, reason: \"The reviewed lapse interval must be a positive number of milliseconds.\" };\n if (!(duration > 0)) return { allowed: false, reason: \"The reviewed lapse duration must be a positive number of milliseconds.\" };\n if (wait !== undefined && !(wait >= 0)) return { allowed: false, reason: \"The reviewed wait budget must be zero or a positive number of milliseconds.\" };\n if (wait !== undefined && duration > wait) return { allowed: false, reason: `The lapse duration of ${duration} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or a shorter duration.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed media capture parameter grammar of the 1.1.41 family: pdf paper sizes, recording scopes and windows, image filters, lapse plans, conversion targets and thumbnail directives stay user choices with no code ceilings. */\nfunction validatemediagrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"capturepdf\") {\n const pdf = options.pdf;\n if (pdf !== undefined) {\n if (!pdf || typeof pdf !== \"object\" || Array.isArray(pdf)) return { allowed: false, reason: \"The reviewed pdf options must be an object in options.pdf.\" };\n const pdfoptions = pdf as Record<string, unknown>;\n if (pdfoptions.paperwidth !== undefined && (typeof pdfoptions.paperwidth !== \"number\" || !Number.isFinite(pdfoptions.paperwidth) || pdfoptions.paperwidth <= 0)) return { allowed: false, reason: \"The reviewed pdf paper width must be a positive number of inches with no code cap.\" };\n if (pdfoptions.paperheight !== undefined && (typeof pdfoptions.paperheight !== \"number\" || !Number.isFinite(pdfoptions.paperheight) || pdfoptions.paperheight <= 0)) return { allowed: false, reason: \"The reviewed pdf paper height must be a positive number of inches with no code cap.\" };\n if (pdfoptions.margins !== undefined) {\n const margins = pdfoptions.margins;\n if (!margins || typeof margins !== \"object\" || Array.isArray(margins)) return { allowed: false, reason: \"The reviewed pdf margins must be an object with top, right, bottom and left inches.\" };\n for (const side of [\"top\", \"right\", \"bottom\", \"left\"]) {\n const value = (margins as Record<string, unknown>)[side];\n if (value === undefined) continue;\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) return { allowed: false, reason: `The reviewed pdf ${side} margin must be zero or a positive number of inches; negative margins are refused.` };\n }\n }\n if (pdfoptions.scale !== undefined && (typeof pdfoptions.scale !== \"number\" || !Number.isFinite(pdfoptions.scale) || pdfoptions.scale <= 0)) return { allowed: false, reason: \"The reviewed pdf scale must be a positive number with no code cap.\" };\n if (pdfoptions.landscape !== undefined && typeof pdfoptions.landscape !== \"boolean\") return { allowed: false, reason: \"The reviewed pdf landscape flag must be a boolean.\" };\n if (pdfoptions.paginate !== undefined && typeof pdfoptions.paginate !== \"boolean\") return { allowed: false, reason: \"The reviewed pdf paginate flag must be a boolean.\" };\n }\n if (options.breakpoints !== undefined && (!Array.isArray(options.breakpoints) || options.breakpoints.length === 0 || !options.breakpoints.every(item => isnonempty(item)))) return { allowed: false, reason: \"The reviewed pdf break points must be a non-empty list of selectors when present.\" };\n if (options.exporttarget !== undefined && options.exporttarget !== \"memory\" && options.exporttarget !== \"download\") return { allowed: false, reason: \"The reviewed pdf export target must be memory or download; pdf documents do not route to the clipboard.\" };\n if (options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed pdf artifact name must be a non-empty string.\" };\n }\n if (kind === \"recordscreen\" || kind === \"captureaudio\") {\n const recording = options.recording;\n if (recording !== undefined) {\n if (!recording || typeof recording !== \"object\" || Array.isArray(recording)) return { allowed: false, reason: \"The reviewed recording options must be an object in options.recording.\" };\n const recordoptions = recording as Record<string, unknown>;\n if (recordoptions.scope !== undefined && recordoptions.scope !== \"tab\" && recordoptions.scope !== \"run\") return { allowed: false, reason: \"The reviewed recording scope must be tab or run.\" };\n if (recordoptions.fps !== undefined && (typeof recordoptions.fps !== \"number\" || !Number.isFinite(recordoptions.fps) || recordoptions.fps <= 0)) return { allowed: false, reason: \"The reviewed recording fps must be a positive number with no code ceiling.\" };\n if (recordoptions.bitrate !== undefined && (typeof recordoptions.bitrate !== \"number\" || !Number.isFinite(recordoptions.bitrate) || recordoptions.bitrate <= 0)) return { allowed: false, reason: \"The reviewed recording bitrate must be a positive number with no code ceiling.\" };\n if (recordoptions.audio !== undefined && typeof recordoptions.audio !== \"boolean\") return { allowed: false, reason: \"The reviewed recording audio flag must be a boolean.\" };\n }\n if (options.duration !== undefined && (typeof options.duration !== \"number\" || !Number.isFinite(options.duration) || options.duration <= 0)) return { allowed: false, reason: \"The reviewed recording duration must be a positive number of milliseconds with no code ceiling.\" };\n const consent = recordingconsentgranted(step);\n if (!consent.allowed) return consent;\n }\n if (kind === \"captureframe\") {\n if (options.timestamp !== undefined && (typeof options.timestamp !== \"number\" || !Number.isFinite(options.timestamp) || options.timestamp < 0)) return { allowed: false, reason: \"The reviewed frame timestamp must be zero or a positive number of seconds.\" };\n if (options.poster !== undefined && typeof options.poster !== \"boolean\") return { allowed: false, reason: \"The reviewed poster flag must be a boolean.\" };\n const capturecheck = validatecaptureoptions(options.capture);\n if (!capturecheck.allowed) return capturecheck;\n }\n if (kind === \"downloadimages\") {\n const filter = options.imagefilter;\n if (!filter || typeof filter !== \"object\" || Array.isArray(filter)) return { allowed: false, reason: \"A reviewed imagefilter is required in options before any image downloads.\" };\n const imagefilter = filter as Record<string, unknown>;\n if (imagefilter.selector !== undefined && !isnonempty(imagefilter.selector)) return { allowed: false, reason: \"The reviewed imagefilter selector must be a non-empty selector from the reviewed selector grammar.\" };\n if (imagefilter.minwidth !== undefined && (typeof imagefilter.minwidth !== \"number\" || !Number.isFinite(imagefilter.minwidth) || imagefilter.minwidth < 0)) return { allowed: false, reason: \"The reviewed imagefilter minimum width must be zero or a positive number of pixels.\" };\n if (imagefilter.minheight !== undefined && (typeof imagefilter.minheight !== \"number\" || !Number.isFinite(imagefilter.minheight) || imagefilter.minheight < 0)) return { allowed: false, reason: \"The reviewed imagefilter minimum height must be zero or a positive number of pixels.\" };\n if (imagefilter.formats !== undefined && (!Array.isArray(imagefilter.formats) || imagefilter.formats.length === 0 || !imagefilter.formats.every(item => isnonempty(item)))) return { allowed: false, reason: \"The reviewed imagefilter format list must be a non-empty list of mime or extension patterns when present.\" };\n if (options.naming !== undefined) {\n const namingcheck = validatecapturenaming(options.naming);\n if (!namingcheck.allowed) return namingcheck;\n }\n }\n if (kind === \"shotcanvas\") {\n const capturecheck = validatecaptureoptions(options.capture);\n if (!capturecheck.allowed) return capturecheck;\n }\n if (kind === \"probestream\" && options.selector !== undefined && !isnonempty(options.selector)) return { allowed: false, reason: \"The reviewed stream probe scope selector must be a non-empty string.\" };\n if (kind === \"timelapse\") {\n const lapse = options.lapse;\n if (!lapse || typeof lapse !== \"object\" || Array.isArray(lapse)) return { allowed: false, reason: \"A reviewed lapse plan with interval, duration and format is required in options.\" };\n const plan = lapse as Record<string, unknown>;\n if (typeof plan.interval !== \"number\" || !Number.isFinite(plan.interval) || plan.interval <= 0) return { allowed: false, reason: \"The reviewed lapse interval must be a positive number of milliseconds with no code ceiling.\" };\n if (typeof plan.duration !== \"number\" || !Number.isFinite(plan.duration) || plan.duration <= 0) return { allowed: false, reason: \"The reviewed lapse duration must be a positive number of milliseconds with no code ceiling.\" };\n if (plan.format !== undefined && plan.format !== \"png\" && plan.format !== \"jpeg\" && plan.format !== \"webp\") return { allowed: false, reason: \"The reviewed lapse format must be png, jpeg or webp.\" };\n const budget = lapsebudgetallowed(plan.interval as number, plan.duration as number, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budget.allowed) return budget;\n const capturecheck = validatecaptureoptions(options.capture);\n if (!capturecheck.allowed) return capturecheck;\n }\n if (kind === \"convertimage\" || kind === \"makethumbs\") {\n const single = options.capture;\n const list = options.captures;\n const hasone = isnonempty(single);\n const haslist = Array.isArray(list) && list.length > 0 && list.every(item => isnonempty(item));\n if (!hasone && !haslist) return { allowed: false, reason: \"A reviewed capture id or a reviewed non-empty capture id list is required in options.\" };\n if (hasone && haslist) return { allowed: false, reason: \"The reviewed step needs one capture id or a capture id list, not both.\" };\n }\n if (kind === \"convertimage\") {\n const convert = options.convert;\n if (!convert || typeof convert !== \"object\" || Array.isArray(convert)) return { allowed: false, reason: \"A reviewed convert directive with a target format is required in options.\" };\n const directive = convert as Record<string, unknown>;\n if (directive.target !== \"png\" && directive.target !== \"jpeg\" && directive.target !== \"webp\") return { allowed: false, reason: \"The reviewed conversion target must be png, jpeg or webp.\" };\n if (directive.source !== undefined && directive.source !== \"png\" && directive.source !== \"jpeg\" && directive.source !== \"webp\") return { allowed: false, reason: \"The reviewed conversion source must be png, jpeg or webp.\" };\n if (directive.quality !== undefined && (typeof directive.quality !== \"number\" || !Number.isFinite(directive.quality) || directive.quality < 0 || directive.quality > 100)) return { allowed: false, reason: \"The reviewed conversion quality must stay between zero and one hundred with no code cap inside that range.\" };\n }\n if (kind === \"makethumbs\") {\n const thumb = options.thumb;\n if (!thumb || typeof thumb !== \"object\" || Array.isArray(thumb)) return { allowed: false, reason: \"A reviewed thumb directive with size, fit and suffix is required in options.\" };\n const directive = thumb as Record<string, unknown>;\n if (typeof directive.size !== \"number\" || !Number.isFinite(directive.size) || directive.size <= 0) return { allowed: false, reason: \"The reviewed thumbnail size must be a positive number of pixels with no fixed set.\" };\n if (directive.fit !== \"cover\" && directive.fit !== \"contain\") return { allowed: false, reason: \"The reviewed thumbnail fit must be cover or contain.\" };\n if (!isnonempty(directive.suffix)) return { allowed: false, reason: \"The reviewed thumbnail naming suffix must be a non-empty string.\" };\n }\n return { allowed: true };\n}\n\n/** Validates a single proposal against the active tab origin and local policy. */\nexport function validatestep(step: toolstep, origin: string): policyevaluation {\n if (!allowedactions.has(step.kind)) return { allowed: false, reason: \"Unsupported action kind.\" };\n if (!step.summary.trim()) return { allowed: false, reason: \"A human-readable action summary is required.\" };\n let options: Record<string, unknown>;\n try { options = parseoptions(step); } catch { return { allowed: false, reason: \"Step options must be a JSON object.\" }; }\n const hastargetref = options.targetref !== undefined;\n if (targetactions.has(step.kind) && !step.target?.trim() && !hastargetref) return { allowed: false, reason: \"A page target is required.\" };\n if (valueactions.has(step.kind) && !step.value?.trim()) return { allowed: false, reason: \"A reviewed value is required.\" };\n if (step.kind === \"select\" && !step.value?.trim()) return { allowed: false, reason: \"A reviewed option value is required.\" };\n if (step.kind === \"navigate\" && !step.value) return { allowed: false, reason: \"A navigation URL is required.\" };\n if (hastargetref) {\n const reference = validatetargetref(options.targetref);\n if (!reference.allowed) return reference;\n }\n if (step.kind === \"wait\") {\n try { waitduration(step); } catch { return { allowed: false, reason: \"Wait duration must be zero or a positive number of milliseconds.\" }; }\n }\n if (step.kind === \"navigate\") {\n try {\n if (new URL(step.value ?? \"\").origin !== origin) return { allowed: false, reason: \"Navigation must remain within the approved origin.\" };\n } catch {\n return { allowed: false, reason: \"Navigation URL is invalid.\" };\n }\n }\n if (step.kind === \"tabcreate\" || step.kind === \"windowcreate\" || step.kind === \"downloadfile\") {\n try {\n const url = new URL(step.value ?? \"\");\n if (url.protocol !== \"https:\") return { allowed: false, reason: \"The reviewed URL must use HTTPS.\" };\n } catch {\n return { allowed: false, reason: \"The reviewed URL is invalid.\" };\n }\n }\n if (step.kind === \"tabactivate\" || step.kind === \"tabclose\" || step.kind === \"tabreload\" || step.kind === \"windowclose\" || step.kind === \"windowresize\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser id is required.\" };\n }\n if (step.kind === \"zoomset\") {\n const zoom = Number(step.value);\n if (!Number.isFinite(zoom) || zoom <= 0) return { allowed: false, reason: \"The reviewed zoom must be a positive number.\" };\n }\n if (step.kind === \"setattribute\" || step.kind === \"writestorage\") {\n const keyname = step.kind === \"setattribute\" ? \"name\" : \"key\";\n if (typeof options[keyname] !== \"string\" || !(options[keyname] as string).trim()) return { allowed: false, reason: `A reviewed ${keyname} is required in options.` };\n if (typeof options.value !== \"string\") return { allowed: false, reason: \"A reviewed value is required in options.\" };\n }\n if (step.kind === \"windowresize\") {\n if (typeof options.width !== \"number\" || typeof options.height !== \"number\" || !Number.isFinite(options.width) || !Number.isFinite(options.height)) return { allowed: false, reason: \"Reviewed width and height numbers are required in options.\" };\n }\n if ((step.kind === \"scrollpage\" || step.kind === \"scrollby\") && (!numericoption(options, \"x\") || !numericoption(options, \"y\"))) return { allowed: false, reason: \"Scroll amounts must be numbers in options.\" };\n if (step.kind === \"waitfor\" && options.timeout !== undefined && (typeof options.timeout !== \"number\" || options.timeout < 0)) return { allowed: false, reason: \"The waitfor timeout must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"movepointer\") {\n const path = options.pointpath;\n if (!path || typeof path !== \"object\" || Array.isArray(path)) return { allowed: false, reason: \"A reviewed pointpath with start and end points is required in options.\" };\n const points = path as Record<string, unknown>;\n if (!ispoint(points.start) || !ispoint(points.end)) return { allowed: false, reason: \"The reviewed pointpath needs numeric start and end points.\" };\n if (points.waypoints !== undefined && (!Array.isArray(points.waypoints) || !points.waypoints.every(waypoint => ispoint(waypoint)))) return { allowed: false, reason: \"The reviewed pointpath waypoints must be numeric points.\" };\n if (!nonnegativeoption(points, \"duration\")) return { allowed: false, reason: \"The reviewed pointpath duration must be zero or a positive number of milliseconds.\" };\n const speed = options.speedprofile;\n if (speed !== undefined) {\n if (!speed || typeof speed !== \"object\" || Array.isArray(speed)) return { allowed: false, reason: \"The reviewed speed profile must be an object.\" };\n const profile = speed as Record<string, unknown>;\n if (profile.easing !== undefined && profile.easing !== \"linear\" && profile.easing !== \"easeinout\") return { allowed: false, reason: \"The reviewed easing must be linear or easeinout.\" };\n if (!nonnegativeoption(profile, \"peak\")) return { allowed: false, reason: \"The reviewed peak velocity must be zero or a positive number.\" };\n if (!nonnegativeoption(profile, \"jitter\")) return { allowed: false, reason: \"The reviewed jitter window must be zero or a positive number of milliseconds.\" };\n }\n }\n if (step.kind === \"clickpoint\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"point\")) return { allowed: false, reason: \"A reviewed point target reference is required in options.\" };\n if (step.kind === \"clicktext\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"text\")) return { allowed: false, reason: \"A reviewed text target reference is required in options.\" };\n if (step.kind === \"clickaria\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"aria\")) return { allowed: false, reason: \"A reviewed aria target reference is required in options.\" };\n if (step.kind === \"clickname\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"name\")) return { allowed: false, reason: \"A reviewed name target reference is required in options.\" };\n if (step.kind === \"resolvexpath\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"xpath\")) return { allowed: false, reason: \"A reviewed xpath target reference is required in options.\" };\n if (step.kind === \"typetime\" && options.delay !== undefined && (typeof options.delay !== \"number\" || !Number.isFinite(options.delay) || options.delay < 0)) return { allowed: false, reason: \"The reviewed per keystroke delay must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"submitsearch\") {\n if (!isnonempty(options.results)) return { allowed: false, reason: \"A reviewed results region selector is required in options.\" };\n if (options.timeout !== undefined && (typeof options.timeout !== \"number\" || !Number.isFinite(options.timeout) || options.timeout < 0)) return { allowed: false, reason: \"The submitsearch timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"selectmulti\") {\n const values = options.values;\n if (!Array.isArray(values) || values.length === 0 || !values.every(value => isnonempty(value))) return { allowed: false, reason: \"A reviewed list of option values is required in options.\" };\n }\n if (step.kind === \"setslider\") {\n const slider = Number(step.value);\n if (!Number.isFinite(slider)) return { allowed: false, reason: \"The reviewed slider value must be a number.\" };\n }\n if (step.kind === \"setdate\" && !/^\\d{4}-\\d{2}-\\d{2}$/.test(step.value ?? \"\")) return { allowed: false, reason: \"The reviewed date must use the yyyy-mm-dd form.\" };\n if (step.kind === \"setcolor\" && !/^#[0-9a-fA-F]{6}$/.test(step.value ?? \"\")) return { allowed: false, reason: \"The reviewed color must use the #rrggbb form.\" };\n if (step.kind === \"keyhold\" && options.holdid !== undefined && !isnonempty(options.holdid)) return { allowed: false, reason: \"The reviewed hold id must be a non-empty string.\" };\n if (step.kind === \"dismissdialog\") {\n const accept = options.accept;\n const answer = options.answer;\n if (accept === undefined && !isnonempty(answer)) return { allowed: false, reason: \"A reviewed accept flag or prompt answer is required in options.\" };\n if (accept !== undefined && typeof accept !== \"boolean\") return { allowed: false, reason: \"The reviewed dialog accept flag must be a boolean.\" };\n if (answer !== undefined && !isnonempty(answer)) return { allowed: false, reason: \"The reviewed prompt answer must be a non-empty string.\" };\n }\n if (step.kind === \"pierceshadow\" && options.shadow !== undefined) {\n if (!Array.isArray(options.shadow) || !options.shadow.every(item => isnonempty(item))) return { allowed: false, reason: \"The reviewed shadow path must be a list of non-empty selectors.\" };\n }\n if (step.kind === \"enterframe\") {\n const path = options.framepath;\n if (!Array.isArray(path) || path.length === 0 || !path.every(item => typeof item === \"number\" && Number.isInteger(item) && item >= 0)) return { allowed: false, reason: \"A reviewed frame path of frame indexes is required in options.\" };\n return validateinnerstep(options, origin);\n }\n if (step.kind === \"retryaction\") {\n const inner = validateinnerstep(options, origin);\n if (!inner.allowed) return inner;\n const rule = options.retryrule;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule)) return { allowed: false, reason: \"A reviewed retry rule with attempts is required in options.\" };\n const retry = rule as Record<string, unknown>;\n if (typeof retry.attempts !== \"number\" || !Number.isInteger(retry.attempts) || retry.attempts < 1) return { allowed: false, reason: \"The reviewed retry attempts must be a positive integer with no code ceiling.\" };\n if (!nonnegativeoption(retry, \"settle\")) return { allowed: false, reason: \"The reviewed retry settle window must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(retry, \"tolerance\")) return { allowed: false, reason: \"The reviewed retry movement tolerance must be zero or a positive number of pixels.\" };\n }\n if (watchactions.has(step.kind)) {\n if (typeof options.lifetime !== \"number\" || !Number.isFinite(options.lifetime) || options.lifetime <= 0) return { allowed: false, reason: \"A reviewed watch lifetime window in milliseconds is required in options.\" };\n if (options.scopes !== undefined && (!Array.isArray(options.scopes) || !options.scopes.every(scope => isnonempty(scope)))) return { allowed: false, reason: \"The reviewed watch scopes must be a list of non-empty selectors.\" };\n if (options.events !== undefined && (!Array.isArray(options.events) || !options.events.every(event => isnonempty(event)))) return { allowed: false, reason: \"The reviewed watch event kinds must be a list of non-empty strings.\" };\n if (!nonnegativeoption(options, \"poll\")) return { allowed: false, reason: \"The reviewed watch poll interval must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"waitquiet\") {\n const rule = options.quietrule;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule)) return { allowed: false, reason: \"A reviewed quietrule with an idle threshold is required in options.\" };\n const quiet = rule as Record<string, unknown>;\n if (typeof quiet.idle !== \"number\" || !Number.isFinite(quiet.idle) || quiet.idle <= 0) return { allowed: false, reason: \"The reviewed quiet idle threshold must be a positive number of milliseconds with no code ceiling.\" };\n if (!nonnegativeoption(quiet, \"poll\")) return { allowed: false, reason: \"The reviewed quiet poll interval must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(quiet, \"timeout\")) return { allowed: false, reason: \"The reviewed quiet timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"diffsnapshots\") {\n const versions = options.versions;\n if (!Array.isArray(versions) || versions.length !== 2 || !versions.every(version => typeof version === \"number\" && Number.isInteger(version) && version >= 1)) return { allowed: false, reason: \"Two reviewed observation version numbers are required in options.\" };\n }\n if (step.kind === \"openlink\" || step.kind === \"openprivate\" || step.kind === \"deeplink\") {\n const targetcheck = validatenavtarget(options.navtarget, step.kind);\n if (!targetcheck.allowed) return targetcheck;\n if (step.kind === \"deeplink\") {\n const app = options.app;\n if (!isnonempty(app)) return { allowed: false, reason: \"A reviewed deep link app pattern is required in options.\" };\n const params = options.params;\n if (params !== undefined && (!params || typeof params !== \"object\" || Array.isArray(params) || !Object.values(params).every(item => typeof item === \"string\"))) return { allowed: false, reason: \"The reviewed deep link params must be an object of string values.\" };\n }\n }\n if (step.kind === \"waitload\" && !nonnegativeoption(options, \"timeout\")) return { allowed: false, reason: \"The waitload timeout must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"waiturl\" || step.kind === \"spawait\") {\n if (step.kind === \"waiturl\") {\n const patterncheck = validateurlpattern(options.urlpattern);\n if (!patterncheck.allowed) return patterncheck;\n }\n if (!nonnegativeoption(options, \"timeout\")) return { allowed: false, reason: \"The wait timeout must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(options, \"poll\")) return { allowed: false, reason: \"The wait poll interval must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"followlink\") {\n if (options.fragment !== undefined && typeof options.fragment !== \"boolean\") return { allowed: false, reason: \"The reviewed followlink fragment flag must be a boolean.\" };\n }\n if (step.kind === \"spanav\") {\n if (options.routepattern !== undefined) {\n const routecheck = validateurlpattern(options.routepattern);\n if (!routecheck.allowed) return routecheck;\n }\n if (!nonnegativeoption(options, \"timeout\")) return { allowed: false, reason: \"The spanav route timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"rewritequery\") {\n const set = options.set;\n const remove = options.remove;\n if (set === undefined && remove === undefined) return { allowed: false, reason: \"Reviewed query parameters to set or remove are required in options.\" };\n if (set !== undefined && (!set || typeof set !== \"object\" || Array.isArray(set) || !Object.values(set).every(item => typeof item === \"string\"))) return { allowed: false, reason: \"The reviewed query parameters to set must be an object of string values.\" };\n if (remove !== undefined && (!Array.isArray(remove) || !remove.every(item => isnonempty(item)))) return { allowed: false, reason: \"The reviewed query parameters to remove must be a list of non-empty names.\" };\n }\n if (step.kind === \"navlist\") {\n const listcheck = validateurllist(options, \"urls\");\n if (!listcheck.allowed) return listcheck;\n }\n if (step.kind === \"navprofile\") {\n const profilecheck = validatewaitprofile(options.waitprofile);\n if (!profilecheck.allowed) return profilecheck;\n }\n if (step.kind === \"handleauth\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"A reviewed HTTPS origin or url is required as the auth target.\" };\n if (step.kind === \"printpdf\" && options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed artifact name must be a non-empty string.\" };\n if (step.kind === \"prefetch\") {\n const listcheck = validateurllist(options, \"urls\");\n if (!listcheck.allowed) return listcheck;\n }\n if (step.kind === \"preconnect\") {\n const origins = options.origins;\n if (!Array.isArray(origins) || origins.length === 0 || !origins.every(originurl => ishttpsurl(originurl))) return { allowed: false, reason: \"A reviewed non-empty list of HTTPS origins is required in options.\" };\n }\n if (step.kind === \"reopentab\" && step.value !== undefined && !ishttpsurl(step.value)) return { allowed: false, reason: \"The reviewed reopen url must use HTTPS.\" };\n if (step.kind === \"navrate\") {\n const limitcheck = validateratelimit(options.ratelimit);\n if (!limitcheck.allowed) return limitcheck;\n }\n if (step.kind === \"checksafe\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"A reviewed HTTPS url is required for the safety check.\" };\n if (step.kind === \"batchopen\") {\n const listcheck = validateurllist(options, \"urls\");\n if (!listcheck.allowed) return listcheck;\n }\n if (istabscommandkind(step.kind)) {\n const tabscheck = validatetabsgrammar(step, options);\n if (!tabscheck.allowed) return tabscheck;\n }\n if (isformkind(step.kind)) {\n const formcheck = validateformgrammar(step, options);\n if (!formcheck.allowed) return formcheck;\n }\n if (isdatasetkind(step.kind)) {\n const datacheck = validatedatagrammar(step, options, origin);\n if (!datacheck.allowed) return datacheck;\n }\n if (isfileskind(step.kind)) {\n const filescheck = validatefilesgrammar(step, options);\n if (!filescheck.allowed) return filescheck;\n }\n if (iscapturekind(step.kind)) {\n const capturecheck = validatecapturegrammar(step, options);\n if (!capturecheck.allowed) return capturecheck;\n }\n if (ismediakind(step.kind)) {\n const mediacheck = validatemediagrammar(step, options);\n if (!mediacheck.allowed) return mediacheck;\n }\n if (ishttpkind(step.kind)) {\n const httpcheck = validatehttpgrammar(step, options);\n if (!httpcheck.allowed) return httpcheck;\n }\n if (issocketkind(step.kind)) {\n const socketcheck = validatesocketgrammar(step, options);\n if (!socketcheck.allowed) return socketcheck;\n }\n if (isnetwatchkind(step.kind)) {\n const netwatchcheck = validatenetwatchgrammar(step, options);\n if (!netwatchcheck.allowed) return netwatchcheck;\n }\n if (iscontrolkind(step.kind)) {\n const controlcheck = validatecontrolgrammar(step, options);\n if (!controlcheck.allowed) return controlcheck;\n }\n if (isdebugkind(step.kind)) {\n const timelinecheck = validatetimelinegrammar(step, options);\n if (!timelinecheck.allowed) return timelinecheck;\n }\n if (iscdpkind(step.kind)) {\n const cdpcheck = validatecdpgrammar(step, options);\n if (!cdpcheck.allowed) return cdpcheck;\n }\n if (isprofilekind(step.kind)) {\n const profilecheck = validateprofilegrammar(step, options);\n if (!profilecheck.allowed) return profilecheck;\n }\n if (isemulationkind(step.kind)) {\n const emulationcheck = validateemulationgrammar(step, options);\n if (!emulationcheck.allowed) return emulationcheck;\n }\n if (issessionkind(step.kind)) {\n const sessioncheck = validatesessiongrammar(step, options);\n if (!sessioncheck.allowed) return sessioncheck;\n }\n if (isworkflowkind(step.kind)) {\n const workflowcheck = validateworkflowgrammar(step, options);\n if (!workflowcheck.allowed) return workflowcheck;\n }\n if (istriggeraction(step.kind)) {\n const triggercheck = validatetriggergrammar(step, options);\n if (!triggercheck.allowed) return triggercheck;\n }\n if (step.kind === \"tabcreate\") {\n if (options.background !== undefined && typeof options.background !== \"boolean\") return { allowed: false, reason: \"The reviewed background flag must be a boolean.\" };\n if (options.window !== undefined && (typeof options.window !== \"number\" || !Number.isInteger(options.window) || options.window < 0)) return { allowed: false, reason: \"The reviewed target window id must be a non-negative integer.\" };\n }\n if (step.kind === \"windowcreate\") {\n for (const field of [\"left\", \"top\", \"width\", \"height\"]) {\n if (options[field] !== undefined && (typeof options[field] !== \"number\" || !Number.isFinite(options[field]))) return { allowed: false, reason: `The reviewed window ${field} must be a number.` };\n }\n if (options.state !== undefined && ![\"normal\", \"maximized\", \"minimized\", \"fullscreen\"].includes(options.state as string)) return { allowed: false, reason: \"The reviewed window state must be normal, maximized, minimized or fullscreen.\" };\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; verdicts?: safetyverdict[]; settings?: runsettings }): policyevaluation {\n const now = input.now ?? Date.now();\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"execute an action\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"The plan has not received explicit approval.\" };\n if (input.plan.expiresat <= now) return { allowed: false, reason: \"The approved plan has expired.\" };\n if ((input.step.kind === \"pierceshadow\" || input.step.kind === \"enterframe\") && !origingranted(input.session, input.origin)) return { allowed: false, reason: \"The shadow or frame step is outside the session origin grants.\" };\n if (input.step.kind === \"readjson\" && !origingranted(input.session, input.origin)) return { allowed: false, reason: \"The json state read is outside the session origin grants.\" };\n if (isexportkind(input.step.kind)) {\n const exportgate = exportgranted(input.session, input.origin);\n if (!exportgate.allowed) return exportgate;\n }\n if (input.step.kind === \"navlist\") {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(input.step); } catch { options = {}; }\n for (const url of Array.isArray(options.urls) ? options.urls : []) {\n if (typeof url !== \"string\") continue;\n const navigation = navigationgranted(input.session, url);\n if (!navigation.allowed) return navigation;\n }\n }\n if (islayoutkind(input.step.kind) && !layoutmutationgranted(input.session, now).allowed) return { allowed: false, reason: \"Group and layout mutations stay inside the active session.\" };\n if (input.step.kind === \"submitform\" || input.step.kind === \"retryform\") {\n if (!input.plan) return { allowed: false, reason: \"Form submission requires an asksubmit review step before it.\" };\n const reviewgate = submitreviewgranted(input.plan.steps, input.step.id);\n if (!reviewgate.allowed) return reviewgate;\n }\n if (input.step.kind === \"consentpassword\") {\n const consentgate = passwordconsentgranted(input.step);\n if (!consentgate.allowed) return consentgate;\n }\n if (input.step.kind === \"readclipboard\") {\n const clipgate = clipboardconsentgranted(input.step);\n if (!clipgate.allowed) return clipgate;\n }\n if (input.step.kind === \"interceptmime\" && !origingranted(input.session, input.origin)) return { allowed: false, reason: \"The download interception is outside the session origin grants.\" };\n if (iscapturekind(input.step.kind)) {\n const capturegatecheck = capturegate(input.session, input.tabid, input.origin, now);\n if (!capturegatecheck.allowed) return capturegatecheck;\n let captureoptions: Record<string, unknown> = {};\n try { captureoptions = parseoptions(input.step); } catch { captureoptions = {}; }\n const target = (captureoptions.capture as Record<string, unknown> | undefined)?.exporttarget;\n if (target !== undefined && target !== \"memory\" && target !== \"download\" && target !== \"clipboard\") return { allowed: false, reason: \"The capture export target must be memory, download or clipboard.\" };\n }\n if (ismediakind(input.step.kind)) {\n const mediagatecheck = mediagate(input.session, input.tabid, input.origin, now);\n if (!mediagatecheck.allowed) return mediagatecheck;\n }\n if (isrecordingkind(input.step.kind)) {\n const recordinggate = recordingconsentgranted(input.step);\n if (!recordinggate.allowed) return recordinggate;\n }\n if (ishttpkind(input.step.kind)) {\n const target = outboundtarget(input.step);\n if (target !== undefined) {\n const outboundgate = origincheck(input.session, target);\n if (!outboundgate.allowed) return outboundgate;\n }\n if (input.step.kind === \"fetchurl\" || input.step.kind === \"callrest\" || input.step.kind === \"callgraphql\") {\n const consentgate = fetchconsentrefgranted(input.step);\n if (!consentgate.allowed) return consentgate;\n }\n }\n if (issocketkind(input.step.kind)) {\n const channelurl = sockettarget(input.step);\n if (channelurl !== undefined) {\n const channelgate = socketgate(input.session, channelurl);\n if (!channelgate.allowed) return channelgate;\n }\n }\n if (input.step.kind === \"watchrequests\") {\n const watchgatecheck = watchgate(input.session, input.settings, now);\n if (!watchgatecheck.allowed) return watchgatecheck;\n }\n if (isdebugkind(input.step.kind)) {\n const timelinegatecheck = timelinegate(input.session, input.tabid, input.origin, now);\n if (!timelinegatecheck.allowed) return timelinegatecheck;\n }\n if (iscdpkind(input.step.kind)) {\n const debuggatecheck = debuggate(input.session, input.tabid, input.origin, now);\n if (!debuggatecheck.allowed) return debuggatecheck;\n if (!input.plan) return { allowed: false, reason: \"The devtools protocol steps need an approved plan.\" };\n const allowlist = planallowlist(input.plan.steps);\n if (input.step.kind !== \"attachcdp\") {\n if (allowlist === undefined) return { allowed: false, reason: \"The devtools protocol step needs the attachcdp step of the same plan with its enabled domains first.\" };\n if (input.step.kind === \"cdpcmd\") {\n let cdpoptions: Record<string, unknown> = {};\n try { cdpoptions = parseoptions(input.step); } catch { cdpoptions = {}; }\n const command = cdpoptions.command && typeof cdpoptions.command === \"object\" && !Array.isArray(cdpoptions.command) ? cdpoptions.command as Record<string, unknown> : undefined;\n const method = typeof command?.method === \"string\" ? command.method : \"\";\n if (methoddomain(method) === undefined || !allowlistcovers(allowlist, method)) return { allowed: false, reason: `The raw command ${method || \"\"} stays outside the enabled domain allowlist of the plan attach; review the attach domains or the method gates.` };\n }\n }\n let cdpoptions: Record<string, unknown> = {};\n try { cdpoptions = parseoptions(input.step); } catch { cdpoptions = {}; }\n if (input.step.kind === \"setbreakpoint\") {\n const breakpoint = breakpointinputof(cdpoptions.breakpoint);\n if (breakpoint) {\n const targetgate = origincheck(input.session, breakpoint.url);\n if (!targetgate.allowed) return targetgate;\n }\n }\n if (input.step.kind === \"overridescript\") {\n const override = overrideinputof(cdpoptions.override);\n if (override) {\n const targetgate = origincheck(input.session, override.urlpattern);\n if (!targetgate.allowed) return targetgate;\n }\n }\n }\n if (isprofilekind(input.step.kind)) {\n let profileoptions: Record<string, unknown> = {};\n try { profileoptions = parseoptions(input.step); } catch { profileoptions = {}; }\n const targets = [\n ...(attachtargetof(profileoptions.target) !== undefined ? [attachtargetof(profileoptions.target) as attachtarget] : []),\n ...(Array.isArray(profileoptions.attachtargets) ? profileoptions.attachtargets.flatMap(target => { const parsed = attachtargetof(target); return parsed !== undefined ? [parsed] : []; }) : []),\n ];\n const targetgatecheck = targetgate({ session: input.session, tabid: input.tabid, origin: input.origin, targets, grants: undefined, now });\n if (!targetgatecheck.allowed) return targetgatecheck;\n if (input.step.kind === \"capturesourcemaps\") {\n for (const url of Array.isArray(profileoptions.scripts) ? profileoptions.scripts : []) {\n if (typeof url !== \"string\") continue;\n const scriptgate = origincheck(input.session, url);\n if (!scriptgate.allowed) return scriptgate;\n }\n }\n }\n if (isemulationkind(input.step.kind)) {\n const emugatecheck = emugate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });\n if (!emugatecheck.allowed) return emugatecheck;\n }\n if (issessionkind(input.step.kind)) {\n const sessiongatecheck = sessionrestoregate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });\n if (!sessiongatecheck.allowed) return sessiongatecheck;\n if (input.step.kind === \"restoresession\") {\n let restoreoptions: Record<string, unknown> = {};\n try { restoreoptions = parseoptions(input.step); } catch { restoreoptions = {}; }\n for (const url of Array.isArray(restoreoptions.origins) ? restoreoptions.origins : []) {\n if (typeof url !== \"string\" || !url) continue;\n const origingate = origincheck(input.session, url);\n if (!origingate.allowed) return { allowed: false, reason: `The session restore reopens ${url} outside the session origin grants; review the restore record or grant the origin.` };\n }\n }\n }\n if (isworkflowkind(input.step.kind)) {\n const workflowgatecheck = workflowgate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });\n if (!workflowgatecheck.allowed) return workflowgatecheck;\n }\n if (istriggeraction(input.step.kind)) {\n const triggergatecheck = triggergate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });\n if (!triggergatecheck.allowed) return triggergatecheck;\n }\n if (iscontrolkind(input.step.kind)) {\n const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"control the network\" });\n if (!controlgate.allowed) return controlgate;\n let controloptions: Record<string, unknown> = {};\n try { controloptions = parseoptions(input.step); } catch { controloptions = {}; }\n if (input.step.kind === \"blockrequest\") {\n const blockgatecheck = blockgate(input.session, input.step, now);\n if (!blockgatecheck.allowed) return blockgatecheck;\n const rule = blockruleof(controloptions.block);\n if (rule) {\n const blockorigin = origincheck(input.session, rule.urlpattern);\n if (!blockorigin.allowed) return blockorigin;\n }\n }\n if (input.step.kind === \"mockresponse\" || input.step.kind === \"rewriteheaders\") {\n const patterns = input.step.kind === \"mockresponse\" ? [mockspecof(controloptions.mock)?.urlpattern ?? \"\"] : (Array.isArray(controloptions.rules) ? controloptions.rules.map(item => item && typeof item === \"object\" && !Array.isArray(item) ? String((item as Record<string, unknown>).urlpattern ?? \"\") : \"\") : []);\n for (const pattern of patterns) {\n const patterngate = origincheck(input.session, pattern);\n if (!patterngate.allowed) return patterngate;\n }\n }\n if (input.step.kind === \"setcookies\" || input.step.kind === \"readcookies\" || input.step.kind === \"clearcookies\") {\n const domain = typeof controloptions.domain === \"string\" && controloptions.domain.trim() ? controloptions.domain : Array.isArray(controloptions.cookies) ? String((controloptions.cookies[0] as Record<string, unknown> | undefined)?.domain ?? \"\") : \"\";\n if (!domain) return { allowed: false, reason: \"A reviewed cookie domain is required before cookie control runs.\" };\n const cookiegatecheck = cookiegate(input.session, domain, now);\n if (!cookiegatecheck.allowed) return cookiegatecheck;\n }\n if (input.step.kind === \"authflow\") {\n const authconsent = authconsentgranted(input.step);\n if (!authconsent.allowed) return authconsent;\n }\n if (input.step.kind === \"saveapikey\") {\n const keyconsent = apikeyconsentgranted(input.step);\n if (!keyconsent.allowed) return keyconsent;\n }\n if (input.step.kind === \"routeproxy\") {\n const proxygatecheck = proxygate(input.session, input.step, now);\n if (!proxygatecheck.allowed) return proxygatecheck;\n }\n const target = controltarget(input.step);\n if (target !== undefined) {\n const targetgate = origincheck(input.session, target);\n if (!targetgate.allowed) return targetgate;\n }\n }\n if (input.step.kind === \"extractapi\") {\n let replayoptions: Record<string, unknown> = {};\n try { replayoptions = parseoptions(input.step); } catch { replayoptions = {}; }\n const replay = apireplayspecof(replayoptions.replay);\n if (replay !== undefined) {\n const replaygate = origincheck(input.session, replay.endpoint);\n if (!replaygate.allowed) return replaygate;\n }\n }\n if (input.step.kind === \"openlink\" || input.step.kind === \"openprivate\" || input.step.kind === \"batchopen\" || input.step.kind === \"prefetch\" || input.step.kind === \"deeplink\" || input.step.kind === \"reopentab\") {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(input.step); } catch { options = {}; }\n const grammar = validatestep(input.step, input.origin);\n if (!grammar.allowed) return grammar;\n const grants = input.session?.grants ?? [input.session?.origin ?? input.origin];\n const targets: unknown[] = input.step.kind === \"batchopen\" || input.step.kind === \"prefetch\" ? (Array.isArray(options.urls) ? options.urls : []) : input.step.kind === \"reopentab\" ? [input.step.value] : [(options.navtarget as Record<string, unknown> | undefined)?.url];\n for (const target of targets) {\n if (typeof target !== \"string\" || !target) continue;\n const verified = originverified(target, grants, input.verdicts ?? []);\n if (!verified.allowed) return verified;\n }\n }\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\n/** Lists every reviewed action kind of the policy table so the step library of the editor browses the whole vocabulary. */\nexport function reviewedkinds(): string[] {\n return [...allowedactions].sort();\n}\n\n/** The editor save gate: the canvas model of a save needs a live session and an approved plan like every other reviewed artifact, its nodes must be steps or block invocations with unique ids, its edges must reference existing steps and run forward only so no cycle forms, and the composed record still passes the full workflow grammar through the composition the save triggers. */\nexport function editorsavegate(input: { session: agentsession | undefined; plan: agentplan | undefined; model: editormodel; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.session?.tabid ?? 0, origin: input.session?.origin ?? \"https://example.com\", now: input.now, action: \"save the workflow editor canvas\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Editor saves need the approved plan review before a new workflow version composes.\" };\n const model = input.model;\n if (typeof model.name !== \"string\" || !model.name.trim()) return { allowed: false, reason: \"The workflow name of the canvas must be a non-empty string.\" };\n if (typeof model.version !== \"number\" || !Number.isInteger(model.version) || model.version < 1) return { allowed: false, reason: \"The workflow version of the canvas must be a positive integer.\" };\n if (!Array.isArray(model.origins) || model.origins.length === 0) return { allowed: false, reason: \"The canvas needs at least one granted HTTPS origin.\" };\n const ids = new Set<string>();\n for (const node of model.nodes) {\n if ((node.step === undefined) === (node.invocation === undefined)) return { allowed: false, reason: \"Every canvas node must be exactly one workflow step or one block invocation.\" };\n const id = node.id ?? (node.step !== undefined ? node.step.id : (node.invocation as { block: string }).block);\n if (!id || ids.has(id)) return { allowed: false, reason: `The canvas node id ${id || \"(empty)\"} must be unique.` };\n ids.add(id);\n }\n const reachable = new Set<string>();\n for (const node of model.nodes) {\n if (node.step !== undefined) { reachable.add(node.step.id); continue; }\n const walk = (entries: Array<{ id?: string; kind?: string; label?: string; block?: string }>): void => {\n for (const entry of entries) {\n if (typeof entry.id === \"string\" && typeof entry.kind === \"string\") { reachable.add(entry.id); continue; }\n if (typeof entry.block === \"string\") {\n const nested = model.blocks.find(candidate => candidate.name === entry.block);\n if (nested) walk(nested.steps as Array<{ id?: string; kind?: string; label?: string; block?: string }>);\n }\n }\n };\n const block = model.blocks.find(candidate => candidate.name === (node.invocation as { block: string }).block);\n if (!block) return { allowed: false, reason: `The block ${(node.invocation as { block: string }).block} of the canvas has no definition.` };\n walk(block.steps as Array<{ id?: string; kind?: string; label?: string; block?: string }>);\n }\n let order = 0;\n const positionof = new Map<string, number>();\n for (const node of model.nodes) {\n if (node.step !== undefined) { positionof.set(node.step.id, order); order += 1; continue; }\n const walk = (entries: Array<{ id?: string; kind?: string; label?: string; block?: string }>): void => {\n for (const entry of entries) {\n if (typeof entry.id === \"string\" && typeof entry.kind === \"string\") { positionof.set(entry.id, order); order += 1; continue; }\n if (typeof entry.block === \"string\") {\n const nested = model.blocks.find(candidate => candidate.name === entry.block);\n if (nested) walk(nested.steps as Array<{ id?: string; kind?: string; label?: string; block?: string }>);\n }\n }\n };\n walk((model.blocks.find(candidate => candidate.name === (node.invocation as { block: string }).block) as { steps: Array<{ id?: string; kind?: string; label?: string; block?: string }> }).steps);\n }\n for (const edge of model.edges) {\n if (!reachable.has(edge.from)) return { allowed: false, reason: `The canvas edge of ${edge.variable} references the unknown source step ${edge.from}.` };\n if (!reachable.has(edge.to)) return { allowed: false, reason: `The canvas edge of ${edge.variable} references the unknown target step ${edge.to}.` };\n if ((positionof.get(edge.from) ?? -1) >= (positionof.get(edge.to) ?? -1)) return { allowed: false, reason: `The canvas edge of ${edge.variable} runs backwards and would form a cycle.` };\n }\n return { allowed: true };\n}\n\n/** Refuses to run a workflow whose review state stays pending: an imported workflow or a version rollback stays unreviewed until the user approves its expanded step list through the import or rollback review. */\nexport function runreviewgranted(record: workflowrecord): policyevaluation {\n if (record.reviewstate === \"pending\") return { allowed: false, reason: \"The workflow stays unreviewed: the import or rollback review must approve its expanded step list before any run.\" };\n return { allowed: true };\n}\n\n/** The reviewed policy knobs a per site override may adjust: loop safety bounds, per step and per run timeout budgets, element wait timeouts and delay bases. */\nconst overrideknobs = [\"loopbound\", \"stepms\", \"runms\", \"waitms\", \"delaybase\"];\n\n/** Validates one per site policy override so overrides only adjust reviewed knobs: the pattern must be an https origin or a `*` subdomain glob of one and every delta must name a reviewed knob with a positive user value and no code ceiling. */\nexport function validatesiteoverride(override: { pattern: string; deltas: Record<string, number> }): policyevaluation {\n if (typeof override.pattern !== \"string\" || !override.pattern.startsWith(\"https://\") || !/[a-z0-9.-]+/i.test(override.pattern.slice(8))) return { allowed: false, reason: \"The override pattern must be an https origin or a `*` subdomain glob of one.\" };\n if (!override.pattern.includes(\"*\")) {\n try {\n if (new URL(override.pattern).origin !== override.pattern) return { allowed: false, reason: \"The override pattern must be a bare https origin or a `*` subdomain glob, never a path.\" };\n } catch {\n return { allowed: false, reason: \"The override pattern must parse as an https origin or a `*` subdomain glob of one.\" };\n }\n }\n for (const [knob, delta] of Object.entries(override.deltas)) {\n if (!overrideknobs.includes(knob)) return { allowed: false, reason: `The override knob ${knob} is not one of the reviewed knobs: ${overrideknobs.join(\", \")}.` };\n if (typeof delta !== \"number\" || !Number.isFinite(delta) || delta <= 0) return { allowed: false, reason: `The override delta of ${knob} must be a positive user value with no code ceiling.` };\n }\n return { allowed: true };\n}\n\n/** Validates the export contents of a workflow file so secrets never leave the browser: every step options object of the workflow and of every packed template is parsed and any field that names a secret, token, api key, password or authorization header refuses the export. */\nexport function exportcontentreview(file: { workflow: workflowrecord; templates: steptemplate[] }): policyevaluation {\n const secretkeys = /(secret|token|apikey|api_key|password|authorization|credential)/i;\n const scan = (label: string, options: string | undefined): policyevaluation | undefined => {\n if (options === undefined) return undefined;\n let payload: unknown;\n try { payload = JSON.parse(options); } catch { return undefined; }\n const walk = (value: unknown, path: string): policyevaluation | undefined => {\n if (!value || typeof value !== \"object\") return undefined;\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n if (secretkeys.test(key)) return { allowed: false, reason: `The export of ${label} carries the secret field ${path}${key} and secrets never leave the browser.` };\n const nested = walk(entry, `${path}${key}.`);\n if (nested !== undefined) return nested;\n }\n return undefined;\n };\n return walk(payload, \"\");\n };\n for (const step of file.workflow.steps) {\n const refusal = scan(`the step ${step.id}`, step.options);\n if (refusal !== undefined) return refusal;\n }\n for (const template of file.templates) {\n const refusal = scan(`the template ${template.name}`, template.step.options);\n if (refusal !== undefined) return refusal;\n }\n return { allowed: true };\n}\n\n/** Validates one watchdog configuration: the stall threshold stays a positive user value with no code ceiling, the recovery action is one of retry, pause or cancel and the zombie window, when configured, stays positive with no ceiling. */\nexport function watchdogconfigvalid(config: watchdogconfig): policyevaluation {\n if (typeof config.enabled !== \"boolean\") return { allowed: false, reason: \"The watchdog enabled flag must be a boolean.\" };\n if (typeof config.stallthreshold !== \"number\" || !Number.isFinite(config.stallthreshold) || config.stallthreshold <= 0) return { allowed: false, reason: \"The watchdog stall threshold must be a positive number of milliseconds with no code ceiling.\" };\n if (![\"retry\", \"pause\", \"cancel\"].includes(config.action)) return { allowed: false, reason: \"The watchdog recovery action must be retry, pause or cancel.\" };\n if (config.zombiewindow !== undefined && (typeof config.zombiewindow !== \"number\" || !Number.isFinite(config.zombiewindow) || config.zombiewindow <= 0)) return { allowed: false, reason: \"The watchdog zombie window, when configured, must be a positive number of milliseconds with no code ceiling.\" };\n return { allowed: true };\n}\n\n/** Validates one mcp tool catalog against the action kind grammar: every tool name stays namespaced and unique, every wrapped kind belongs to the reviewed vocabulary, every namespace keeps its tools inside its domain kinds and every input schema carries typed properties with its required list. */\nexport function validatetoolcatalog(catalog: toolcatalog): policyevaluation {\n if (!Array.isArray(catalog.domains) || catalog.domains.length === 0) return { allowed: false, reason: \"The tool catalog needs its tool domains.\" };\n const seen = new Set<string>();\n for (const domain of catalog.domains) {\n if (!toolnamespaces.includes(domain.namespace)) return { allowed: false, reason: `The tool domain ${String(domain.namespace)} is not a reviewed namespace.` };\n if (!Array.isArray(domain.tools) || domain.tools.length === 0) return { allowed: false, reason: `The ${domain.namespace} domain exposes no tools.` };\n for (const tool of domain.tools) {\n if (typeof tool.name !== \"string\" || !tool.name.startsWith(`${domain.namespace}.`)) return { allowed: false, reason: `The tool ${String(tool.name)} does not carry its ${domain.namespace} namespace prefix.` };\n if (seen.has(tool.name)) return { allowed: false, reason: `The tool name ${tool.name} is not unique across the catalog.` };\n seen.add(tool.name);\n if (!allowedactions.has(tool.kind)) return { allowed: false, reason: `The tool ${tool.name} wraps ${String(tool.kind)} which is outside the reviewed action kind grammar.` };\n if (!domainkinds[domain.namespace].includes(tool.kind)) return { allowed: false, reason: `The tool ${tool.name} wraps ${String(tool.kind)} which does not belong to the ${domain.namespace} domain.` };\n if (typeof tool.description !== \"string\" || tool.description.trim() === \"\") return { allowed: false, reason: `The tool ${tool.name} needs its plain language description.` };\n const schema = tool.inputschema;\n if (!schema || schema.type !== \"object\" || schema.properties === undefined || schema.properties === null || typeof schema.properties !== \"object\" || Array.isArray(schema.properties) || Object.keys(schema.properties).length === 0) return { allowed: false, reason: `The tool ${tool.name} needs its json schema inputs of at least one typed property.` };\n for (const [name, property] of Object.entries(schema.properties)) {\n if (![\"string\", \"number\", \"boolean\", \"object\", \"array\"].includes(property.type)) return { allowed: false, reason: `The ${tool.name} input ${name} carries an untyped property.` };\n if (typeof property.description !== \"string\" || property.description.trim() === \"\") return { allowed: false, reason: `The ${tool.name} input ${name} needs its plain language description.` };\n }\n for (const name of schema.required) {\n if (!(name in schema.properties)) return { allowed: false, reason: `The tool ${tool.name} marks ${name} required outside its properties.` };\n }\n }\n }\n return { allowed: true };\n}\n\n/** Grades one tooldef with the risk class of its action kind and refuses a tool whose declared grade disagrees with the grammar. */\nexport function toolriskgrade(tool: tooldef): policyevaluation {\n const grade = actionrisk(tool.kind);\n if (grade !== tool.risk) return { allowed: false, reason: `The tool ${tool.name} declares the ${tool.risk} grade while its kind ${String(tool.kind)} grades ${grade}.` };\n return { allowed: true };\n}\n\n/** Requires consent metadata on every tool with side effects: read only tools stay free of the extra review while interaction and sensitive tools must declare their review requirement. */\nexport function toolconsentrequired(tool: tooldef): policyevaluation {\n if (tool.risk === \"read\") return { allowed: true };\n if (tool.consentmeta === undefined || typeof tool.consentmeta.review !== \"string\" || tool.consentmeta.review.trim() === \"\") return { allowed: false, reason: `The tool ${tool.name} has side effects and needs its consent metadata with the review requirement.` };\n return { allowed: true };\n}\n\n/** Grades one server bind configuration: the localhost bind stays the reviewed default while a bind outside localhost grades sensitive and needs the explicit remote review flag. */\nexport function serverbindgate(config: mcpserverconfig): policyevaluation {\n const bind = config.bind !== undefined && config.bind.trim() !== \"\" ? config.bind.trim() : \"127.0.0.1\";\n const local = bind === \"127.0.0.1\" || bind === \"localhost\" || bind === \"::1\";\n if (!local && config.remote !== true) return { allowed: false, reason: `The bind ${bind} leaves localhost and grades sensitive: the explicit remote review must approve it first.` };\n return { allowed: true };\n}\n\n/** Refuses one tool whose version stays below the negotiated compatibility floor so a client never receives a tool older than it can parse. */\nexport function toolversionfloor(tool: tooldef, floor: number): policyevaluation {\n if (typeof floor === \"number\" && Number.isFinite(floor) && tool.version < floor) return { allowed: false, reason: `The tool ${tool.name} of version ${tool.version} stays below the negotiated compatibility floor of ${floor}.` };\n return { allowed: true };\n}\n\n/** Requires the explicit user enablement before the mcp server ever starts; a disabled or unreviewed config never listens. */\nexport function serverenablementgate(config: mcpserverconfig): policyevaluation {\n if (config.enabled !== true) return { allowed: false, reason: \"The mcp server starts only after the user enables it; the protocol surface stays closed by default.\" };\n const bind = serverbindgate(config);\n if (!bind.allowed) return bind;\n if (!Array.isArray(config.transports) || config.transports.length === 0) return { allowed: false, reason: \"The mcp server needs at least one allowed transport of stdio or http.\" };\n if (!config.transports.every(transport => transport === \"stdio\" || transport === \"http\")) return { allowed: false, reason: \"The allowed transports of the mcp server are stdio and http.\" };\n if (typeof config.port !== \"number\" || !Number.isFinite(config.port) || config.port <= 0 || config.port > 65535) return { allowed: false, reason: \"The http listener port must be a valid port number.\" };\n if (config.framesize !== undefined && (typeof config.framesize !== \"number\" || !Number.isFinite(config.framesize) || config.framesize <= 0)) return { allowed: false, reason: \"The user configured frame size must stay a positive number with no code ceiling.\" };\n if (config.queuedepth !== undefined && (typeof config.queuedepth !== \"number\" || !Number.isFinite(config.queuedepth) || config.queuedepth <= 0)) return { allowed: false, reason: \"The user configured queue depth must stay a positive number with no code ceiling.\" };\n const remote = remoteenablementgate(config);\n if (!remote.allowed) return remote;\n return { allowed: true };\n}\n\n/** Validates the namespace membership of one tool: the name prefix must name the domain the tool lives in and the wrapped kind must belong to that domain so no tool drifts out of its namespace. */\nexport function toolnamespacegate(tool: tooldef): policyevaluation {\n const namespace = tool.name.split(\".\")[0];\n if (!toolnamespaces.includes(namespace as never)) return { allowed: false, reason: `The tool ${tool.name} carries no reviewed namespace prefix.` };\n if (!domainkinds[namespace as keyof typeof domainkinds].includes(tool.kind)) return { allowed: false, reason: `The tool ${tool.name} wraps ${String(tool.kind)} which does not belong to the ${namespace} domain.` };\n return { allowed: true };\n}\n\n/** The mcp tool dispatch gate: the client must be paired, the session live, the plan approved and the origin inside the session grants; read only tools pass under the dryrun risk class without extra approval while every tool with side effects must name the approved plan step of its own kind it executes. The full canexecute gates re-run at execution time. */\nexport function tooldispatchgate(input: { client: clientrecord; tool: tooldef; session: agentsession | undefined; plan: agentplan | undefined; origin: string; stepid?: string; now: number }): policyevaluation {\n if (input.client.disconnectedat !== undefined) return { allowed: false, reason: \"The mcp client is disconnected and its tool calls are refused.\" };\n if (!input.client.paired) return { allowed: false, reason: \"The mcp client waits for the user pairing approval; unpaired clients never dispatch tools.\" };\n if (!input.session || input.session.stoppedat || input.session.pausedat) return { allowed: false, reason: \"Tool dispatch needs the live browser session behind the consent gates.\" };\n if (input.session.expiresat <= input.now) return { allowed: false, reason: \"The browser session has expired and tool dispatch is refused.\" };\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Tool dispatch needs the approved plan review before any tool runs.\" };\n if (!origingranted(input.session, input.origin)) return { allowed: false, reason: `The tool call origin ${input.origin} stays outside the session grants and is refused.` };\n if (input.tool.risk === \"read\") return { allowed: true };\n if (input.stepid === undefined || input.stepid.trim() === \"\") return { allowed: false, reason: `The ${input.tool.name} tool has side effects and needs the id of the approved plan step it executes.` };\n const step = input.plan.steps.find(candidate => candidate.id === input.stepid);\n if (step === undefined) return { allowed: false, reason: `The tool call names the step ${input.stepid} which the approved plan does not carry.` };\n if (step.kind !== input.tool.kind) return { allowed: false, reason: `The tool call names the step ${input.stepid} whose kind ${String(step.kind)} does not match the ${input.tool.name} tool.` };\n return { allowed: true };\n}\n\n/** Grades the consent metadata of every sensitive tool: the risk class must match the policy grading of the wrapped kind, the approval gate requirement must be explicit and the origin scope must stay the session grants. */\nexport function consentmetagrade(tool: tooldef): policyevaluation {\n if (tool.risk === \"read\") return { allowed: true };\n if (tool.consentmeta === undefined) return { allowed: false, reason: `The tool ${tool.name} has side effects and needs its consent metadata.` };\n if (tool.consentmeta.riskclass !== actionrisk(tool.kind)) return { allowed: false, reason: `The consent metadata of ${tool.name} declares the ${String(tool.consentmeta.riskclass)} risk class while policy grades its kind ${String(tool.kind)} as ${actionrisk(tool.kind)}.` };\n if (tool.consentmeta.approvalrequired !== true) return { allowed: false, reason: `The tool ${tool.name} has side effects and its consent metadata must require the explicit approval gate.` };\n if (tool.consentmeta.originscope !== \"session\") return { allowed: false, reason: `The tool ${tool.name} must scope its calls to the session grants.` };\n return { allowed: true };\n}\n\n/** Validates one allowlist entry against the known client identities: the fingerprint must belong to a stored identity, the display name must be non empty and every granted namespace must be a reviewed namespace. */\nexport function allowlistentryvalid(entry: allowlistentry, identities: clientidentity[]): policyevaluation {\n if (typeof entry.fingerprint !== \"string\" || entry.fingerprint.trim() === \"\") return { allowed: false, reason: \"The allowlist entry needs the client fingerprint it grants.\" };\n if (!identities.some(identity => identity.fingerprint === entry.fingerprint)) return { allowed: false, reason: `The allowlist entry ${entry.fingerprint} matches no known client identity.` };\n if (typeof entry.displayname !== \"string\" || entry.displayname.trim() === \"\") return { allowed: false, reason: `The allowlist entry ${entry.fingerprint} needs its display name.` };\n if (!Array.isArray(entry.namespaces) || entry.namespaces.length === 0) return { allowed: false, reason: `The allowlist entry ${entry.displayname} grants no tool namespace.` };\n if (!entry.namespaces.every(namespace => toolnamespaces.includes(namespace))) return { allowed: false, reason: `The allowlist entry ${entry.displayname} grants an unreviewed namespace.` };\n return { allowed: true };\n}\n\n/** Validates one session token lifetime as a user configured value: an absent lifetime keeps the documented default while a configured window must stay positive with no code ceiling. */\nexport function tokenlifetimevalid(lifetime: number | undefined): policyevaluation {\n if (lifetime === undefined) return { allowed: true };\n if (typeof lifetime !== \"number\" || !Number.isFinite(lifetime) || lifetime <= 0) return { allowed: false, reason: \"The token lifetime must stay a positive user value with no code ceiling.\" };\n return { allowed: true };\n}\n\n/** Requires tls for any non localhost transport: a configured remote access policy or a bind outside localhost must carry the on or required tls mode before any remote traffic passes. */\nexport function remotetransporttls(config: mcpserverconfig): policyevaluation {\n const bind = config.bind !== undefined && config.bind.trim() !== \"\" ? config.bind.trim() : \"127.0.0.1\";\n const local = bind === \"127.0.0.1\" || bind === \"localhost\" || bind === \"::1\";\n const tls = config.remoteaccess?.tls ?? config.httpstream?.tls;\n if ((config.remoteaccess !== undefined || !local) && (tls === undefined || tls.mode === \"off\")) return { allowed: false, reason: `The ${config.remoteaccess !== undefined ? \"remote transport\" : `bind ${bind}`} leaves localhost and every non localhost transport requires tls before any remote traffic.` };\n return { allowed: true };\n}\n\n/** Refuses the pairing flow when no session is active: pairing codes issue only while the live browser session exists, so no remote client pairs against a closed surface. */\nexport function pairingreadinessgate(session: agentsession | undefined, now: number): policyevaluation {\n if (!session || session.stoppedat || session.pausedat) return { allowed: false, reason: \"The pairing flow needs the live browser session before any code issues.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and the pairing flow is refused.\" };\n return { allowed: true };\n}\n\n/** Grades the remote transport enablement as a sensitive user choice: a configured remote access policy requires the explicit remote review and tls before the remote surface opens. */\nexport function remoteenablementgate(config: mcpserverconfig): policyevaluation {\n if (config.remoteaccess === undefined) return { allowed: true };\n if (config.remote !== true) return { allowed: false, reason: \"The remote transport enablement is a sensitive user choice and needs the explicit remote review.\" };\n const tls = remotetransporttls(config);\n if (!tls.allowed) return tls;\n if (typeof config.remoteaccess.endpoint !== \"string\" || config.remoteaccess.endpoint.trim() === \"\") return { allowed: false, reason: \"The remote access policy needs its user configured endpoint.\" };\n if (config.remoteaccess.maxclients !== undefined && (typeof config.remoteaccess.maxclients !== \"number\" || !Number.isFinite(config.remoteaccess.maxclients) || config.remoteaccess.maxclients <= 0)) return { allowed: false, reason: \"The user configured client ceiling must stay a positive value with no code ceiling.\" };\n const lifetime = tokenlifetimevalid(config.remoteaccess.tokenlifetimems);\n if (!lifetime.allowed) return lifetime;\n const timeout = approvaltimeoutvalid(config.remoteaccess.approvaltimeout);\n if (!timeout.allowed) return timeout;\n return { allowed: true };\n}\n\n/** Limits the token scopes to the namespaces the user granted: every scope must be a reviewed namespace the grant list carries, so a token never widens beyond the allowlist. */\nexport function tokenscopevalid(scopes: toolnamespace[], granted: toolnamespace[]): policyevaluation {\n if (!Array.isArray(scopes) || scopes.length === 0) return { allowed: false, reason: \"A session token needs at least one granted tool namespace.\" };\n for (const scope of scopes) {\n if (!toolnamespaces.includes(scope)) return { allowed: false, reason: `The scope ${String(scope)} is not a reviewed tool namespace.` };\n if (!granted.includes(scope)) return { allowed: false, reason: `The scope ${scope} stays outside the namespaces the user granted.` };\n }\n return { allowed: true };\n}\n\n/** Validates one approval timeout as a user configured positive window with the documented refusal default; an absent timeout keeps the documented default. */\nexport function approvaltimeoutvalid(timeout: approvaltimeout | undefined): policyevaluation {\n if (timeout === undefined) return { allowed: true };\n if (typeof timeout.windowms !== \"number\" || !Number.isFinite(timeout.windowms) || timeout.windowms <= 0) return { allowed: false, reason: \"The approval timeout must stay a positive user window with no code ceiling.\" };\n if (timeout.ontimeout !== \"refuse\") return { allowed: false, reason: \"The documented disposition of an unanswered approval gate is refusal.\" };\n return { allowed: true };\n}\n\n/** Grades the token revocation as an always available user action: no gate, review or state ever blocks the user from revoking a paired client. */\nexport function revocationgate(): policyevaluation {\n return { allowed: true };\n}\n\n/** Grades one client event subscription as read only when its filters exclude the mutation mirror: a subscription that listens to the callstarted kind must narrow itself with an origin or tool filter so it never streams the side effect calls of unreviewed origins. */\nexport function subscriptiongrade(subscription: protocoleventsubscription): policyevaluation {\n if (!Array.isArray(subscription.kinds) || subscription.kinds.length === 0) return { allowed: false, reason: \"An event subscription needs at least one protocol event kind.\" };\n if (subscription.kinds.includes(\"callstarted\") && subscription.origin === undefined && subscription.tool === undefined) return { allowed: false, reason: \"An event subscription that mirrors the callstarted events of tools with side effects needs its origin or tool filter so it never widens what the session grants.\" };\n return { allowed: true };\n}\n\n/** Grades sampling callbacks as sensitive: the prompt leaves the browser, so page content rides a callback only behind the explicit user grant and the granted maximum tokens stay a positive user value. */\nexport function samplinggrade(input: { request: samplingrequest; pagegrant: boolean }): policyevaluation {\n if (!input.pagegrant && input.request.pagecontent !== undefined) return { allowed: false, reason: \"The sampling callback carries page content the user never granted and is refused.\" };\n if (input.request.maxtokens !== undefined && (!Number.isFinite(input.request.maxtokens) || input.request.maxtokens <= 0)) return { allowed: false, reason: \"The granted maximum tokens of a sampling callback must stay a positive user value with no code ceiling.\" };\n if (input.request.prompt.trim() === \"\") return { allowed: false, reason: \"A sampling callback needs its prompt.\" };\n return { allowed: true };\n}\n\n/** Validates one per client rate limit as a user configured value: the window and budget stay positive when set while an absent limit or budget documents the unbounded choice instead of a silent default. */\nexport function callratelimitvalid(limit: callratelimit | undefined): policyevaluation {\n if (limit === undefined) return { allowed: true };\n if (typeof limit.windowms !== \"number\" || !Number.isFinite(limit.windowms) || limit.windowms <= 0) return { allowed: false, reason: \"The rate limit window must stay a positive user value with no code ceiling.\" };\n if (limit.budget !== undefined && (typeof limit.budget !== \"number\" || !Number.isFinite(limit.budget) || limit.budget <= 0)) return { allowed: false, reason: \"The rate limit budget must stay a positive user value with no code ceiling.\" };\n if (limit.clientid.trim() === \"\") return { allowed: false, reason: \"A per client rate limit needs the client it counts.\" };\n return { allowed: true };\n}\n\n/** Requires one audit entry for every tool call without exception: every call record of the runtime must appear in the audit set so no call ever leaves the trail. */\nexport function callauditcomplete(input: { calls: toolcallrecord[]; audit: toolcallrecord[] }): policyevaluation {\n const audited = new Set(input.audit.map(record => record.id));\n const missing = input.calls.filter(record => !audited.has(record.id));\n if (missing.length > 0) return { allowed: false, reason: `${missing.length} tool call${missing.length === 1 ? \"\" : \"s\"} carry no audit entry and the audit trail must name every call without exception.` };\n return { allowed: true };\n}\n\n/** Grades one batch call by its most sensitive member: a batch that carries a sensitive member takes the sensitive grade and runs only behind the approval gates while a read only batch stays read. */\nexport function batchgrade(input: { calls: Array<{ risk: tooldef[\"risk\"] }>; approved: boolean }): policyevaluation {\n if (input.calls.length === 0) return { allowed: false, reason: \"A batch call needs at least one ordered tool call.\" };\n const sensitive = input.calls.some(call => call.risk === \"sensitive\");\n if (sensitive && !input.approved) return { allowed: false, reason: \"The batch grades sensitive through its most sensitive member and runs only behind the approval gates.\" };\n return { allowed: true };\n}\n\n/** Keeps one tool dry run free of page mutations: a dry run record that claims execution or lists page mutations is refused because a dry run evaluates arguments and consent and never executes anything. */\nexport function dryrunpurity(dryrun: tooldryrun): policyevaluation {\n if (dryrun.executed) return { allowed: false, reason: `The ${dryrun.tool} dry run claims execution and a dry run never executes anything.` };\n if (dryrun.mutations.length > 0) return { allowed: false, reason: `The ${dryrun.tool} dry run lists ${dryrun.mutations.length} page mutation${dryrun.mutations.length === 1 ? \"\" : \"s\"} and a dry run leaves the page untouched.` };\n return { allowed: true };\n}\n\n/** Validates tool mock usage to test contexts only: a mock outside a test context is refused while a test context mock must name a tool and carry a canned result. */\nexport function mockusagevalid(mock: toolmock): policyevaluation {\n if (mock.testcontext !== true) return { allowed: false, reason: `The ${mock.tool} mock stays outside a test context and is refused; tool mocks never answer real calls.` };\n if (mock.tool.trim() === \"\") return { allowed: false, reason: \"A tool mock needs the namespaced tool it stands in for.\" };\n if (typeof mock.result.content !== \"string\") return { allowed: false, reason: \"A tool mock needs its canned result content.\" };\n return { allowed: true };\n}\n\n/**\n * Llm integration gates of the 1.1.57 family.\n * Every model side gate lives here: the provider validation that keeps every endpoint, model and protocol shape a user configured value, the data egress grading of provider calls, the explicit consent requirement before page content leaves the browser, the local endpoint preference for sensitive extractions, the review requirement of model drafted plans, the fresh review requirement of replanned steps, the cost budget validation, the guard verdict gate that refuses invalid model output and the plan lint that checks model drafts against the action grammar before review.\n * No provider, endpoint, model, key or ceiling is ever hardcoded: the gates validate user choices and refuse everything else.\n */\n\n/** Validates one provider config: the endpoint stays a user configured http or https url, the model list stays non-empty free text, the protocol shape stays one of the four wire shapes and the auth reference stays a storage id reference that never carries key material. */\nexport function providervalid(config: providerconfig): policyevaluation {\n if (config.name.trim() === \"\") return { allowed: false, reason: \"The provider config needs its name.\" };\n if (config.endpoint.trim() === \"\") return { allowed: false, reason: \"The provider config needs the user configured endpoint url; no default endpoint ever applies.\" };\n let parsed: URL;\n try { parsed = new URL(config.endpoint); } catch { return { allowed: false, reason: \"The provider endpoint must be a well-formed url.\" }; }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return { allowed: false, reason: \"The provider endpoint must speak http or https.\" };\n if (config.models.length === 0) return { allowed: false, reason: \"The provider config needs at least one user configured model name.\" };\n if (config.models.some(model => model.trim() === \"\")) return { allowed: false, reason: \"Every provider model name must stay non-empty free text.\" };\n if (config.style !== \"chatcompletions\" && config.style !== \"responses\" && config.style !== \"messages\" && config.style !== \"gemini\") return { allowed: false, reason: \"The provider protocol shape must be one of the four wire shapes the user picks.\" };\n if (config.authref !== undefined && config.authref.storageid.trim() === \"\") return { allowed: false, reason: \"The provider auth reference needs the storage id of the stored key; the key material never enters the config.\" };\n return { allowed: true };\n}\n\n/** Grades one provider call as a data egress event for the audit trail: every remote completion leaves the browser with its prompt text, so the audit names the endpoint, the model and the token counts; a local endpoint grades as the local preference. */\nexport function provideregressgrade(input: { provider: providerconfig; local: boolean }): policyevaluation {\n const valid = providervalid(input.provider);\n if (!valid.allowed) return valid;\n return { allowed: true, reason: input.local ? \"The model call stays on the local machine endpoint and grades as the local data egress preference.\" : \"The model call leaves the browser for the user configured endpoint and grades as a data egress event with its endpoint, model and token counts in the audit trail.\" };\n}\n\n/** Requires explicit consent before any page content leaves the browser: page content inside a call the user has not granted refuses the call, while calls without page content pass untouched. */\nexport function egressconsentgate(input: { pagecontent?: string; granted: boolean }): policyevaluation {\n if (input.pagecontent !== undefined && input.pagecontent.trim() !== \"\" && input.granted !== true) return { allowed: false, reason: \"The model call carries page content the user has not granted, so the content stays in the browser and the call refuses.\" };\n return { allowed: true };\n}\n\n/** Grades the local endpoint preference for sensitive extractions: a sensitive extraction prefers the local model endpoint, and the grade names the preference while a local endpoint satisfies it. */\nexport function localsensitivegrade(input: { sensitive: boolean; local: boolean }): policyevaluation {\n if (input.sensitive && !input.local) return { allowed: true, reason: \"The sensitive extraction prefers the local model endpoint; the user keeps the choice of the remote provider.\" };\n return { allowed: true, reason: input.local ? \"The local model endpoint satisfies the sensitive extraction preference.\" : \"The extraction stays non-sensitive and every configured endpoint serves it.\" };\n}\n\n/** Requires the plan review before any model drafted plan executes: only an approved draft may turn into a plan, and the plan itself still passes the same human plan review every local plan passes. */\nexport function plandraftreviewgate(draft: plandraft): policyevaluation {\n if (draft.state !== \"approved\") return { allowed: false, reason: \"The model drafted plan stays unreviewed; the human review approves the draft before any step executes.\" };\n if (draft.steps.length === 0) return { allowed: false, reason: \"The model drafted plan carries no step, so nothing executes.\" };\n return { allowed: true };\n}\n\n/** Requires fresh review for replanned steps: a pending replan never executes and every revised tail step carries the fresh review marker, so the human review sees the changed tail before it runs. */\nexport function replanreviewgate(replan: replanrecord): policyevaluation {\n if (replan.state !== \"approved\") return { allowed: false, reason: \"The replanned tail stays unreviewed; the fresh review approves the changed steps before any of them executes.\" };\n if (replan.tail.some(step => step.freshreview !== true)) return { allowed: false, reason: \"Every revised step of a replan must carry the fresh review marker.\" };\n return { allowed: true };\n}\n\n/** Validates one cost budget: the token and currency ceilings stay positive user values with no code ceiling, the currency names the unit of the cost ceiling and a budget without any ceiling documents the unbounded choice instead of inventing one. */\nexport function costbudgetvalid(budget: costbudget): policyevaluation {\n if (budget.maxtokens !== undefined && (!Number.isFinite(budget.maxtokens) || budget.maxtokens <= 0)) return { allowed: false, reason: \"The token ceiling of a cost budget must stay a positive user value.\" };\n if (budget.maxcost !== undefined && (!Number.isFinite(budget.maxcost) || budget.maxcost <= 0)) return { allowed: false, reason: \"The cost ceiling of a cost budget must stay a positive user value.\" };\n if (budget.maxcost !== undefined && (budget.currency === undefined || budget.currency.trim() === \"\")) return { allowed: false, reason: \"The cost ceiling of a cost budget needs its currency unit.\" };\n if (budget.maxtokens === undefined && budget.maxcost === undefined) return { allowed: false, reason: \"The cost budget needs at least one ceiling the user configured; an absent budget stays the documented unbounded choice.\" };\n return { allowed: true };\n}\n\n/** Refuses tool calls the guardrails marked invalid: only a valid guard verdict passes, an invalid or refused model output never executes. */\nexport function guardverdictgate(output: modeloutput): policyevaluation {\n if (output.verdict === \"invalid\") return { allowed: false, reason: output.reason ?? \"The guardrails marked the model output invalid.\" };\n if (output.verdict === \"refused\") return { allowed: false, reason: output.reason ?? \"The model refused the request, so nothing executes.\" };\n return { allowed: true };\n}\n\n/** Lints one model drafted plan against the action grammar before review: every drafted step maps onto the tool step grammar with its derived risk, and every violation lands in the findings the review sees; an empty origin skips the origin bound checks the way the review pipeline runs them later. */\n/** Derives the risk of one drafted step for the grammar check; an unknown kind grades sensitive so the grammar check names the violation instead of crashing. */\nfunction draftriskof(step: draftstep): \"read\" | \"interaction\" | \"sensitive\" {\n try { return resolvedrisk({ id: step.id, kind: step.kind as actionkind, ...(step.target !== undefined ? { target: step.target } : {}), ...(step.value !== undefined ? { value: step.value } : {}), summary: step.summary, risk: \"sensitive\" }); } catch { return \"sensitive\"; }\n}\n\nexport function planlint(draft: plandraft, origin: string): string[] {\n const findings: string[] = [];\n if (draft.goal.trim() === \"\") findings.push(\"The drafted plan carries no goal.\");\n if (draft.steps.length === 0) findings.push(\"The drafted plan carries no step.\");\n for (const step of draft.steps) {\n const mapped: toolstep = { id: step.id, kind: step.kind as actionkind, ...(step.target !== undefined ? { target: step.target } : {}), ...(step.value !== undefined ? { value: step.value } : {}), summary: step.summary, risk: draftriskof(step) } as toolstep;\n const verdict = validatestep(mapped, origin);\n if (!verdict.allowed) findings.push(`The drafted step ${step.id || \"without id\"} of kind ${step.kind || \"unknown\"} violates the action grammar: ${verdict.reason ?? \"the step failed its grammar check.\"}`);\n }\n return findings;\n}\n\n/**\n * Multi agent part one gates of the 1.1.58 family.\n * Every swarm side gate lives here: the task queue validation of the user configured lanes, priorities and completion policy, the work stealing grade that permits stealing only inside one user approved swarm, the per agent budget validation of positive user ceilings, the per agent scope validation against the session grant list, the spawn grading with the risk class of the requested role, the killswitch gate that stays available with no configuration barrier, the egress grading of cross agent messages that carry page content and the blackboard consent grade that inherits the class of the source extraction.\n * No agent count, lane name, priority scale, depth ceiling or freshness window is ever hardcoded: the gates validate user choices and refuse everything else, and no swarm coordination ever bypasses the human review.\n */\n\n/** Validates one task queue: the lanes stay non-empty unique user names, the priorities stay finite user values, the completion policy stays all or any, and every item waits in a configured lane under a configured priority when the user configured the scales. */\nexport function queuelanesvalid(queue: taskqueue): policyevaluation {\n if (queue.lanes.some(lane => lane.trim() === \"\")) return { allowed: false, reason: \"Every queue lane needs its user configured name.\" };\n if (new Set(queue.lanes).size !== queue.lanes.length) return { allowed: false, reason: \"The queue lane names must stay unique.\" };\n if (queue.priorities.some(priority => !Number.isFinite(priority))) return { allowed: false, reason: \"Every queue priority must stay a finite user value.\" };\n if (queue.completionpolicy !== \"all\" && queue.completionpolicy !== \"any\") return { allowed: false, reason: \"The queue completion policy must stay all or any.\" };\n for (const item of queue.items) {\n if (item.payload.trim() === \"\") return { allowed: false, reason: `The task ${item.id} carries no payload.` };\n if (queue.lanes.length > 0 && !queue.lanes.includes(item.lane)) return { allowed: false, reason: `The task ${item.id} waits in the lane ${item.lane} which the user did not configure.` };\n if (queue.priorities.length > 0 && !queue.priorities.includes(item.priority)) return { allowed: false, reason: `The task ${item.id} carries the priority ${item.priority} which the user did not configure.` };\n }\n return { allowed: true };\n}\n\n/** Grades one work stealing attempt: stealing is permitted only inside one user approved swarm, and a steal outside an approved swarm refuses because lane ownership only exists inside the reviewed swarm. */\nexport function workstealgrade(input: { swarmapproved: boolean; agentrole: string; lane: string; ownership?: Array<{ lane: string; roles: string[] }> }): policyevaluation {\n if (!input.swarmapproved) return { allowed: false, reason: \"Work stealing runs only inside one user approved swarm; an unapproved swarm keeps every lane closed.\" };\n const rule = (input.ownership ?? []).find(entry => entry.lane === input.lane);\n if (rule && !rule.roles.includes(input.agentrole)) return { allowed: false, reason: `The lane ${input.lane} only opens its tasks to the roles ${rule.roles.join(\", \")} the user configured; the ${input.agentrole} agent may not steal.` };\n return { allowed: true, reason: rule === undefined ? `The lane ${input.lane} carries no ownership rule, so every agent of the approved swarm may steal its tasks.` : `The lane ${input.lane} opens its tasks to the ${input.agentrole} role the user configured.` };\n}\n\n/** Validates one per agent budget: the token, cost and step ceilings stay positive user values with no code ceiling, the cost ceiling names its currency and a budget without any ceiling documents the unbounded choice instead of inventing one. */\nexport function agentbudgetvalid(budget: agentbudget): policyevaluation {\n if (budget.agentid.trim() === \"\") return { allowed: false, reason: \"The agent budget needs its agent id.\" };\n if (budget.maxtokens !== undefined && (!Number.isFinite(budget.maxtokens) || budget.maxtokens <= 0)) return { allowed: false, reason: \"The token ceiling of an agent budget must stay a positive user value.\" };\n if (budget.maxcost !== undefined && (!Number.isFinite(budget.maxcost) || budget.maxcost <= 0)) return { allowed: false, reason: \"The cost ceiling of an agent budget must stay a positive user value.\" };\n if (budget.maxsteps !== undefined && (!Number.isFinite(budget.maxsteps) || budget.maxsteps <= 0)) return { allowed: false, reason: \"The step ceiling of an agent budget must stay a positive user value.\" };\n if (budget.maxcost !== undefined && (budget.currency === undefined || budget.currency.trim() === \"\")) return { allowed: false, reason: \"The cost ceiling of an agent budget needs its currency unit.\" };\n if (budget.maxtokens === undefined && budget.maxcost === undefined && budget.maxsteps === undefined) return { allowed: false, reason: \"The agent budget needs at least one ceiling the user configured; an absent budget stays the documented unbounded choice.\" };\n return { allowed: true };\n}\n\n/** Validates one per agent scope against the session grant list: every granted origin must sit inside the session grants and every granted tool namespace must be one of the catalog namespaces, so no agent scope ever widens the session. */\nexport function agentscopevalid(input: { scope: agentscope; grants: string[] }): policyevaluation {\n if (input.scope.agentid.trim() === \"\") return { allowed: false, reason: \"The agent scope needs its agent id.\" };\n if (input.scope.origins.some(origin => origin.trim() === \"\")) return { allowed: false, reason: \"Every origin of an agent scope needs its user configured name.\" };\n if (input.scope.toolnamespaces.some(namespace => !toolnamespaces.includes(namespace))) return { allowed: false, reason: \"Every tool namespace of an agent scope must be one of the catalog namespaces.\" };\n const outside = input.scope.origins.filter(origin => input.grants.length > 0 && !input.grants.includes(origin));\n if (outside.length > 0) return { allowed: false, reason: `The agent scope grants the origins ${outside.join(\", \")} which the session grant list does not carry; no agent scope widens the session.` };\n return { allowed: true };\n}\n\n/** Grades one spawn request with the risk class of the requested role: a planner or observer spawn grades read side while a worker or custom role spawn grades sensitive because it may execute reviewed steps, and the grade names the class the review sees. */\nexport function spawngrade(request: spawnrequest): policyevaluation {\n if (request.parentid.trim() === \"\") return { allowed: false, reason: \"The spawn request needs its parent agent id.\" };\n if (request.task.trim() === \"\") return { allowed: false, reason: \"The spawn request needs its task in plain language.\" };\n if (request.depth < 1) return { allowed: false, reason: \"The spawn request depth must sit at one or deeper because every sub agent lives under a parent.\" };\n const risk = request.role === \"planner\" || request.role === \"observer\" ? \"read\" : \"sensitive\";\n return { allowed: true, reason: risk === \"read\" ? `The spawn of the ${request.role} sub agent grades read side: the role composes or observes and never executes page actions.` : `The spawn of the ${request.role} sub agent grades sensitive: the role may execute reviewed steps, so every proposal it drafts still passes the same human review.` };\n}\n\n/** Keeps the killswitch available with no configuration barrier: the switch never needs a setting, an approval or a state to fire, so the user halts every agent at once at any time. */\nexport function killswitchgate(): policyevaluation {\n return { allowed: true, reason: \"The killswitch stays available with no configuration barrier: the user halts every agent of the swarm at once at any time.\" };\n}\n\n/** Grades one cross agent message that carries page content as a data egress event: the delivery stays inside the swarm while the copied page content lands its egress class in the audit trail so the reviewer reads what moved between agents. */\nexport function messageegressgrade(input: { message: agentmessage; carriespagecontent: boolean }): policyevaluation {\n if (input.message.payload.trim() === \"\") return { allowed: false, reason: \"The agent message needs its payload.\" };\n if (input.carriespagecontent) return { allowed: true, reason: `The message ${input.message.id} from ${input.message.senderid} to ${input.message.recipient} carries page content and grades as a data egress event with its sender, recipient and routing in the audit trail.` };\n return { allowed: true, reason: `The message ${input.message.id} from ${input.message.senderid} to ${input.message.recipient} carries no page content and stays a plain swarm delivery.` };\n}\n\n/** Grades one blackboard entry by the consent class of its source extraction: the entry inherits the class exactly, a sensitive extraction stays sensitive on the board and every reader sees the class beside the value. */\nexport function blackboardconsentgrade(entry: blackboardentry): policyevaluation {\n if (entry.key.trim() === \"\") return { allowed: false, reason: \"The blackboard entry needs its key.\" };\n if (entry.consentclass !== \"read\" && entry.consentclass !== \"interaction\" && entry.consentclass !== \"sensitive\") return { allowed: false, reason: \"The blackboard entry inherits one of the three consent classes of its source extraction.\" };\n if (entry.consentclass === \"sensitive\") return { allowed: true, reason: `The blackboard entry ${entry.key} inherits the sensitive class of its source extraction; every agent reads the class beside the value.` };\n return { allowed: true, reason: `The blackboard entry ${entry.key} inherits the ${entry.consentclass} class of its source extraction; every agent reads the class beside the value.` };\n}\n\n/**\n * Multi agent part two gates of the 1.1.59 family.\n * Every orchestration side gate lives here: the leader election validation of the user configured rule, the critic review grade that stays read only over agent outputs, the verifier method grade against the methods the user allows, the handoff grant gate that preserves the original session grants, the lock scope validation that keeps one lock inside one origin, the conflict resolution grade that marks overwriting rules sensitive, the escalation gate that keeps every lifted decision human, the consensus quorum validation of the user configured value, the worker scale bound validation with no engine cap and the merge egress grade of exported reports that include page content.\n * No quorum, worker bound, election rule or merge rule is ever hardcoded: the gates validate user choices and refuse everything else, and no coordination path bypasses the human review.\n */\n\n/** Validates one leader election rule as the user configured it: first takes the first registration while named takes one agent id the user typed, and a named rule without its agent or an unknown rule kind refuses. */\nexport function leaderelectionvalid(input: { rule: { kind: string; agentid?: string }; agents: agentidentity[] }): policyevaluation {\n if (input.rule.kind !== \"first\" && input.rule.kind !== \"named\") return { allowed: false, reason: \"The leader election rule stays first or named as the user configured it.\" };\n if (input.rule.kind === \"named\") {\n if (input.rule.agentid === undefined || input.rule.agentid.trim() === \"\") return { allowed: false, reason: \"The named leader election rule needs the agent id the user named.\" };\n if (!input.agents.some(agent => agent.id === input.rule.agentid && agent.state !== \"stopped\")) return { allowed: false, reason: `The named leader election rule names the agent ${input.rule.agentid} which is not a live agent of the swarm.` };\n }\n return { allowed: true, reason: input.rule.kind === \"first\" ? \"The first registration rule elects the leader exactly as the user configured.\" : `The named rule elects the agent ${input.rule.agentid} exactly as the user configured.` };\n}\n\n/** Grades one critic review as read only over agent outputs: the critic reads the output of the subject agent and returns its verdict, its issues and its required changes without ever acting on the page, and a review without its reviewer, subject or verdict refuses. */\nexport function criticreviewgrade(review: criticreview): policyevaluation {\n if (review.reviewerid.trim() === \"\") return { allowed: false, reason: \"The critic review needs its reviewing agent.\" };\n if (review.subjectagentid.trim() === \"\") return { allowed: false, reason: \"The critic review needs the subject agent whose output it reviews.\" };\n if (review.verdict !== \"approve\" && review.verdict !== \"changes\" && review.verdict !== \"reject\") return { allowed: false, reason: \"The critic review carries one of the three verdicts approve, changes or reject.\" };\n if (review.verdict === \"changes\" && review.requiredchanges.length === 0) return { allowed: false, reason: \"A changes verdict needs its required changes in plain language.\" };\n if (review.verdict === \"reject\" && review.issues.length === 0) return { allowed: false, reason: \"A reject verdict needs the issues the critic found.\" };\n return { allowed: true, reason: `The critic review of the output of ${review.subjectagentid} stays read only: the critic ${review.reviewerid} returns its ${review.verdict} verdict and never acts on the page; the rework still passes the same human review.` };\n}\n\n/** Grades one verifier check by the methods the user allows: an empty allowed list keeps every method open as the documented user choice while a configured list restricts the verifier to the methods it names. */\nexport function verifiermethodgrade(input: { method: string; allowed: string[] }): policyevaluation {\n if (input.method.trim() === \"\") return { allowed: false, reason: \"The verifier check needs the method it used.\" };\n if (input.allowed.length > 0 && !input.allowed.includes(input.method)) return { allowed: false, reason: `The verifier method ${input.method} is not one of the methods the user allowed: ${input.allowed.join(\", \")}.` };\n return { allowed: true, reason: input.allowed.length === 0 ? `The verifier method ${input.method} runs under the documented open method list the user chose not to narrow.` : `The verifier method ${input.method} sits inside the methods the user allowed.` };\n}\n\n/** Requires one handoff to preserve the original session grants: the receiving agent scope stays inside the session grant list exactly like every agent scope, so a tab transfer never widens what the session granted. */\nexport function handoffgrantgate(input: { record: handoffrecord; toscope: agentscope | undefined; sessiongrants: string[] }): policyevaluation {\n if (input.record.toagentid.trim() === \"\" || input.record.fromagentid.trim() === \"\") return { allowed: false, reason: \"The handoff names its transferring and receiving agents.\" };\n if (input.toscope === undefined) return { allowed: true, reason: `The receiving agent ${input.record.toagentid} carries no narrowed scope, so the handoff stays unbounded inside the original session grants.` };\n const outside = input.toscope.origins.filter(origin => input.sessiongrants.length > 0 && !input.sessiongrants.includes(origin));\n if (outside.length > 0) return { allowed: false, reason: `The handoff to ${input.record.toagentid} would need the origins ${outside.join(\", \")} which the session grant list does not carry; a tab transfer never widens the session grants.` };\n return { allowed: true, reason: `The handoff from ${input.record.fromagentid} to ${input.record.toagentid} preserves the original session grants; the receiving scope stays inside them.` };\n}\n\n/** Validates one lock scope so a lock never spans unrelated origins: the key composes of exactly one origin and one selector, both non-empty, and the kind stays exclusive or shared. */\nexport function lockscopevalid(lock: resourcelock): policyevaluation {\n if (lock.key.trim() === \"\") return { allowed: false, reason: \"The resource lock needs its key.\" };\n if (lock.origin.trim() === \"\" || lock.selector.trim() === \"\") return { allowed: false, reason: \"The resource lock names exactly one origin and one selector; a lock never spans unrelated origins.\" };\n if (lock.key !== `${lock.origin}|${lock.selector}`) return { allowed: false, reason: \"The lock key must compose of its one origin and its one selector so the scope never spans unrelated origins.\" };\n if (lock.kind !== \"exclusive\" && lock.kind !== \"shared\") return { allowed: false, reason: \"The lock kind stays exclusive or shared.\" };\n return { allowed: true, reason: `The lock ${lock.key} spans exactly one target of one origin for the holder ${lock.holder}.` };\n}\n\n/** Grades one conflict resolution rule: the overwriting rules last and preferagent grade sensitive because one parallel value overwrites another inside the report, while first and fail grade read side. */\nexport function conflictresolutiongrade(rule: mergerule): policyevaluation {\n if (rule !== \"first\" && rule !== \"last\" && rule !== \"preferagent\" && rule !== \"fail\") return { allowed: false, reason: \"The conflict resolution rule stays first, last, preferagent or fail as the user configured it.\" };\n if (rule === \"last\" || rule === \"preferagent\") return { allowed: true, reason: `The ${rule} conflict resolution rule overwrites one parallel value with another, so it grades sensitive and the merged report still passes the human review.` };\n return { allowed: true, reason: `The ${rule} conflict resolution rule keeps or refuses the parallel values without overwriting, so it grades read side.` };\n}\n\n/** Grades one escalation as always human decided: the open escalation waits for the user and only the user writes the decision; an escalation without its subject or full context refuses because the user decides on what the agent saw. */\nexport function escalationgate(escalation: escalationrecord): policyevaluation {\n if (escalation.agentid.trim() === \"\") return { allowed: false, reason: \"The escalation names the agent whose decision it lifts.\" };\n if (escalation.subject.trim() === \"\") return { allowed: false, reason: \"The escalation needs its subject.\" };\n if (escalation.context.trim() === \"\") return { allowed: false, reason: \"The escalation needs its full context in plain language; the user decides on what the agent saw.\" };\n if (escalation.state === \"decided\" && (escalation.decision === undefined || escalation.decision.trim() === \"\")) return { allowed: false, reason: \"A decided escalation carries the decision the user wrote.\" };\n return { allowed: true, reason: `The escalation of ${escalation.agentid} stays human decided: the agent lifts the stalled decision with its full context and the user alone writes the outcome.` };\n}\n\n/** Validates one consensus quorum as a user configured value: the quorum stays a positive whole number and never exceeds the live voters the user counted, so no round carries an unreachable quorum. */\nexport function consensusquorumvalid(input: { quorum: number; voters: number }): policyevaluation {\n if (!Number.isInteger(input.quorum) || input.quorum < 1) return { allowed: false, reason: \"The consensus quorum stays a positive whole number the user configured.\" };\n if (input.quorum > input.voters) return { allowed: false, reason: `The consensus quorum ${input.quorum} exceeds the ${input.voters} voting agents the user counted; an unreachable quorum never carries.` };\n return { allowed: true, reason: `The consensus quorum ${input.quorum} of ${input.voters} voting agents stays the user configured value with no engine default.` };\n}\n\n/** Validates the worker scale bound as a user choice with no engine cap: an absent bound stays the documented unbounded choice while a configured bound stays a positive user value the scaling respects. */\nexport function workerscalevalid(bound: number | undefined): policyevaluation {\n if (bound === undefined) return { allowed: true, reason: \"No worker bound is configured, so the worker scale stays the user choice alone with no engine cap.\" };\n if (!Number.isFinite(bound) || bound < 1) return { allowed: false, reason: \"The worker scale bound stays a positive user value; no engine cap exists.\" };\n return { allowed: true, reason: `The worker scale bound ${bound} stays the user configured value; the scaling never passes it and no engine cap exists.` };\n}\n\n/** Grades one exported merged report that includes page content as a data egress event: the export carries the report with its sources into the audit trail so the reviewer reads what left the browser. */\nexport function mergeegressgrade(input: { report: resultreport; carriespagecontent: boolean }): policyevaluation {\n if (input.report.title.trim() === \"\") return { allowed: false, reason: \"The merged report needs its title before any export.\" };\n if (input.carriespagecontent) return { allowed: true, reason: `The export of the report ${input.report.title} carries page content from the sources ${input.report.sources.join(\", \")} and grades as a data egress event in the audit trail.` };\n return { allowed: true, reason: `The export of the report ${input.report.title} carries no page content and stays a plain report export.` };\n}\n\n/**\n * Execution environment gates of the 1.1.60 family.\n * Every environment side gate lives here: the environment field validation of every step kind (the evaluate kind runs inside the isolated world only while untrusted markup renders inside the sandboxframe only), the session environment grant gate that refuses a step whose environment sits outside the granted list, the offscreen capability gate that keeps the worker pool behind the optional offscreen grant, the keepalive gate that limits the keepalive port to sessions with an active reviewed plan, the worker pool size validation with no engine cap and the sandbox render origin gate that carries the user per origin toggle.\n * No environment posture, pool size, heartbeat interval or origin list is ever hardcoded: the gates validate user choices and refuse everything else, and no environment ever bypasses the human review.\n */\n\n/** Validates the environment field of every step kind: the field stays one of the four environments, the evaluate kind names the isolated world only, a step carrying untrusted markup names the sandboxframe only, the parse heavy kinds choose between the live page and the offscreen worker pool and every other kind keeps the pagecontext of the page bridge. */\nexport function stepenvironmentvalid(step: toolstep): policyevaluation {\n if (step.environment !== undefined && step.environment !== \"pagecontext\" && step.environment !== \"isolatedworld\" && step.environment !== \"offscreenworker\" && step.environment !== \"sandboxframe\") return { allowed: false, reason: \"The step environment stays one of pagecontext, isolatedworld, offscreenworker and sandboxframe.\" };\n const allowed = environmentsof(step);\n if (step.environment !== undefined && !allowed.includes(step.environment)) return { allowed: false, reason: `The ${step.environment} environment sits outside the ${allowed.join(\", \")} the ${step.kind} kind permits; the review sees the environment of every step.` };\n return { allowed: true, reason: step.environment === undefined ? `The ${step.kind} step carries no environment field and routes to its ${defaultenvironment(step)} default.` : `The ${step.environment} environment of the ${step.kind} step sits inside the ${allowed.join(\", \")} the kind permits.` };\n}\n\n/** Requires the step environment to sit inside the session environment grant list: a configured list narrows every step of the session to its entries while an absent list keeps the documented default posture where the pagecontext and the isolated world stay open behind the same review. */\nexport function environmentgrantgate(step: toolstep, grants: environmentkind[] | undefined): policyevaluation {\n if (grants === undefined || grants.length === 0) return { allowed: true, reason: `The session carries no environment grant list, so the ${defaultenvironment(step)} default of the ${step.kind} step stays the documented posture behind the same review.` };\n const environment = step.environment ?? defaultenvironment(step);\n if (!grants.includes(environment)) return { allowed: false, reason: `The ${environment} environment sits outside the ${grants.join(\", \")} the session granted; no step ever widens the environment grants.` };\n return { allowed: true, reason: `The ${environment} environment of the ${step.kind} step sits inside the ${grants.join(\", \")} the session granted.` };\n}\n\n/** Refuses offscreenworker steps when the optional offscreen capability grant stays absent: the worker pool only runs under the user granted offscreen permission, and a step without the grant falls back to inline parsing instead of refusing the reviewed work. */\nexport function offscreencapabilitygate(input: { environment: environmentkind; granted: boolean }): policyevaluation {\n if (input.environment !== \"offscreenworker\") return { allowed: true, reason: `The ${input.environment} environment needs no offscreen capability grant.` };\n if (!input.granted) return { allowed: false, reason: \"The offscreen worker pool runs only under the user granted offscreen capability; the step falls back to inline parsing inside the page.\" };\n return { allowed: true, reason: \"The offscreen worker pool runs under the user granted offscreen capability.\" };\n}\n\n/** Limits the keepalive port to sessions with an active reviewed plan: a stopped or paused session holds no port, an expired session holds none, and a plan outside the approved state holds none because the keepalive signal rides a run the user already reviewed. */\nexport function keepalivegate(input: { session: agentsession | undefined; plan: agentplan | undefined; now: number }): policyevaluation {\n if (!input.session) return { allowed: false, reason: \"The keepalive port opens only inside an active session.\" };\n if (input.session.stoppedat !== undefined) return { allowed: false, reason: \"The keepalive port stays closed for a stopped session.\" };\n if (input.session.pausedat !== undefined) return { allowed: false, reason: \"The keepalive port stays closed while the session pauses; a resumed run reattaches it.\" };\n if (input.now > input.session.expiresat) return { allowed: false, reason: \"The keepalive port stays closed for an expired session.\" };\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"The keepalive port opens only behind an active reviewed plan; unreviewed work never holds the service worker alive.\" };\n return { allowed: true, reason: `The approved plan ${input.plan.id} of the active session holds the keepalive port open for its whole run.` };\n}\n\n/** Validates the keepalive heartbeat interval as a positive user value in milliseconds: the roadmap documents thirty seconds while the interval stays the user's choice with no engine default. */\nexport function keepaliveintervalvalid(interval: number): policyevaluation {\n if (!Number.isFinite(interval) || interval <= 0) return { allowed: false, reason: \"The keepalive heartbeat interval stays a positive user value in milliseconds.\" };\n return { allowed: true, reason: `The keepalive heartbeat interval ${interval} milliseconds stays the user configured value; the roadmap documents thirty seconds while the choice stays the user's.` };\n}\n\n/** Validates the worker pool size as a user choice with no engine cap: an absent size lets the pool follow the pending parse queue alone while a configured size stays a positive whole number. */\nexport function workerpoolsizevalid(size: number | undefined): policyevaluation {\n if (size === undefined) return { allowed: true, reason: \"No worker pool size is configured, so the pool follows the pending parse queue alone with no engine cap.\" };\n if (!Number.isInteger(size) || size < 1) return { allowed: false, reason: \"The worker pool size stays a positive whole number the user configured; no engine cap exists.\" };\n return { allowed: true, reason: `The worker pool size ${size} stays the user configured value; no engine cap exists.` };\n}\n\n/** Grades one sandbox render by the origins the user allows: an absent list keeps every origin open as the documented user choice while a configured list restricts the untrusted markup renders to the origins it names. */\nexport function sandboxorigingate(input: { origin: string; allowed: string[] }): policyevaluation {\n if (input.origin.trim() === \"\") return { allowed: false, reason: \"The sandbox render needs the source origin of its untrusted markup.\" };\n if (input.allowed.length > 0 && !input.allowed.includes(input.origin)) return { allowed: false, reason: `The origin ${input.origin} sits outside the origins the user allows to render untrusted markup: ${input.allowed.join(\", \")}.` };\n return { allowed: true, reason: input.allowed.length === 0 ? `The origin ${input.origin} renders untrusted markup under the documented open origin list the user chose not to narrow.` : `The origin ${input.origin} sits inside the origins the user allows to render untrusted markup.` };\n}\n\n/** Exposes the environment requirements of every reviewed action kind: one profile per kind with its allowed environments and its default, so the panel and the executor read the same table the policy validates against. */\nexport function environmentrequirements(): environmentrequirement[] {\n return environmentrequirementsof([...allowedactions]);\n}\n\n/**\n * Security part one gates of the 1.1.61 family.\n * Every trust boundary gate lives here: the automation allowlist gate that flips the posture to denydefault for every ungranted origin and binds each grant to one exact origin with no wildcard expansion while the active tab grant counts as exactly one explicit single origin grant, the origin profile gate that consults the per site profile before every sensitive kind and refuses the kinds its denials name, the consent window gate that scopes every window to one session and one origin, expires it at its duration boundary and suspends the run when the window expires mid step, the revocation gate that accepts mid run revocation as a terminal session event halting the pending step and every queued step, the sensitive class gate that routes payment, credential, delete and publish steps through one fresh consent prompt per class per origin, the consent duration validation that keeps every boundary a positive user value that never defaults to unlimited, and the log read gate that refuses reads of a hash chain with a broken link.\n * No duration, boundary, shape list or kind list is ever hardcoded: every window, profile, consent and mask shape stays the user's choice, and no security operation ever bypasses the human review.\n */\n\n/** Checks one origin against the per origin automation allowlist under the denydefault posture: every ungranted origin stays refused before any step dispatches, the active tab origin counts as exactly one explicit single origin grant, and a wildcard entry refuses the whole check because no wildcard expansion exists. */\nexport function automationallowlistgate(input: { origin: string; allowlist: automationallowlistentry[]; session: agentsession | undefined }): policyevaluation {\n const verdict = allowlistcheck({ origin: input.origin, allowlist: input.allowlist, ...(input.session !== undefined ? { sessionorigin: input.session.origin } : {}) });\n if (!verdict.allowed) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Consults the per site origin profile before one sensitive kind: an explicit denial refuses the kind on that origin, an explicit grant allows it while the fresh class consent gate still routes the sensitive classes through their prompt, and a non-sensitive kind needs no profile consult. */\nexport function originprofilegate(input: { profile: originprofile | undefined; kind: actionkind; sensitive: boolean }): policyevaluation {\n const verdict = profilegrade(input);\n if (!verdict.allowed) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Checks the consent window of one step before dispatch: the window scopes to exactly one session and one origin, a window past its duration boundary suspends the run mid step, and a sensitive step without an active window waits for its consent prompt. */\nexport function consentwindowgate(input: { window: consentwindow | undefined; sessionid: string; origin: string; sensitive: boolean; now: number }): policyevaluation {\n if (!input.sensitive) return { allowed: true, reason: \"The non-sensitive step rides the session origin grants and needs no consent window of its own.\" };\n const verdict = windowgatesstep({ window: input.window, sessionid: input.sessionid, origin: input.origin, now: input.now });\n if (!verdict.allowed) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Accepts the mid run revocation as a terminal session event: the pending step and every queued step halt without executing, the run aborts its plan, and no later step of the revoked run dispatches. */\nexport function revokerungate(input: { revocation: revokerunevent | undefined; sessionid: string; runid: string }): policyevaluation {\n if (input.revocation === undefined) return { allowed: true, reason: \"No revocation halted the run; the steps keep their reviewed order.\" };\n if (input.revocation.sessionid !== input.sessionid) return { allowed: true, reason: \"The revocation belongs to another session and halts nothing here.\" };\n if (input.revocation.runid !== input.runid) return { allowed: true, reason: \"The revocation belongs to another run and halts nothing here.\" };\n return { allowed: false, reason: `The revocation of ${input.revocation.actor} halted the pending step and ${Math.max(0, input.revocation.haltedstepids.length - 1)} queued step${Math.max(0, input.revocation.haltedstepids.length - 1) === 1 ? \"\" : \"s\"} without executing them: ${input.revocation.haltedstepids.join(\", \")}.` };\n}\n\n/** Routes one sensitive step through its fresh consent prompt: every sensitive class the classification names needs its own fresh consent per origin, an upload, download or evaluate grade sensitive by default rides its consent window prompt, and a non-sensitive step needs no fresh consent. */\nexport function sensitiveclassgate(input: { origin: string; classes: sensitiveclass[]; bydefault: boolean; sensitive: boolean; consents: classconsent[]; now: number }): policyevaluation {\n if (!input.sensitive) return { allowed: true, reason: \"The step carries no sensitive class and needs no fresh consent prompt.\" };\n const verdict = missingclassconsents({ origin: input.origin, classes: input.classes, bydefault: input.bydefault, consents: input.consents, now: input.now });\n if (verdict.needed) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Validates the consent window duration as a positive user value in milliseconds: every consent window names its boundary and no window ever defaults to unlimited. */\nexport function consentdurationvalid(duration: number): policyevaluation {\n if (!Number.isFinite(duration) || duration <= 0) return { allowed: false, reason: \"The consent window duration stays a positive user value in milliseconds; no grant ever defaults to unlimited.\" };\n return { allowed: true, reason: `The consent window duration ${duration} milliseconds stays the user configured boundary the prompt names.` };\n}\n\n/** Refuses log reads of a chain with a broken link: the audit accessor verifies the whole hash chain before returning a single entry, and tamper evidence refuses the read instead of serving a forged record. */\nexport function logreadgate(input: { valid: boolean; brokenat?: number }): policyevaluation {\n if (!input.valid) return { allowed: false, reason: input.brokenat !== undefined ? `The log chain breaks at entry ${input.brokenat}; the audit accessor refuses the read of a forged record.` : \"The log chain fails its verification; the audit accessor refuses the read of a forged record.\" };\n return { allowed: true, reason: \"The log chain verifies from the genesis hash to the last entry; the audit accessor serves the entries.\" };\n}\n\n/** Grades one step through the whole sensitive pipeline: the classification names the classes and the default grade, the origin profile consults its per kind decisions, and the fresh class consent gate routes every named class through its own prompt per origin. */\nexport function sensitivepipelingate(input: { step: Pick<toolstep, \"kind\" | \"value\" | \"options\">; profile: originprofile | undefined; consents: classconsent[]; origin: string; now: number }): policyevaluation {\n const classification = sensitiveclassesof(input.step);\n const profileverdict = originprofilegate({ profile: input.profile, kind: input.step.kind, sensitive: classification.sensitive });\n if (!profileverdict.allowed) return { allowed: false, reason: profileverdict.reason ?? \"\" };\n const consentverdict = sensitiveclassgate({ origin: input.origin, classes: classification.classes, bydefault: classification.bydefault, sensitive: classification.sensitive, consents: input.consents, now: input.now });\n if (!consentverdict.allowed) return { allowed: false, reason: `${classification.reason} ${consentverdict.reason}` };\n return { allowed: true, reason: `${classification.reason} ${consentverdict.reason}` };\n}\n\n/**\n * Security part two gates of the 1.1.62 family.\n * Every inbound and dispatch guard lives here: the schemastrict gate that validates every inbound command before dispatch and rejects unknown fields with the path and the expected shape, the origincheck and connectallow gates that guard every runtime message and port connection while dropping senders absent from the user managed list, the ratelimit gate that bounds automation commands per origin and per session by deferring past the bucket until the window resets, the confirmpay, confirmdelete and confirmcreds gates that route payment, destructive delete and credential use steps through one distinct human action each with no batch approval and no timeout, the phishguard gate that blocks login origins crossing the user lookalike threshold while naming the matched known origin, the safedefaults gate that profiles unknown origins as reads only, the vault secret gate that refuses secrets in step options, variables and plan texts, and the untrusted render gate that marks extracted markup untrusted and routes it through the sandboxframe.\n * No bound, window, threshold or sender list is ever hardcoded: every ratelimit bound, lookalike threshold and connectallow entry stays the user's choice, and no security operation ever bypasses the human review.\n */\n\n/** Requires schemastrict validation of every inbound command before dispatch: every schema error names its path and its expected shape, and the refusal echoes no payload. */\nexport function schemaguardgate(input: { errors: schemaerror[] }): policyevaluation {\n if (input.errors.length === 0) return { allowed: true, reason: \"The inbound command matches its declared schemastrict grammar field by field.\" };\n const first = input.errors[0];\n return { allowed: false, reason: `${input.errors.length} schema error${input.errors.length === 1 ? \"\" : \"s\"} refuse the command before dispatch: ${input.errors.map(error => error.reason).join(\" \")}${first !== undefined ? ` The first error sits at ${first.path} expecting ${first.expected}.` : \"\"}` };\n}\n\n/** Requires the origincheck verdict on every runtime message before handling: a sender the verdict drops never reaches a handler. */\nexport function origincheckgate(input: { verdict: origincheckverdict }): policyevaluation {\n if (!input.verdict.accepted) return { allowed: false, reason: input.verdict.reason };\n return { allowed: true, reason: input.verdict.reason };\n}\n\n/** Drops messages and port connections from senders absent from the connectallow list: the list ships empty by default and holds user managed entries only. */\nexport function connectallowgate(input: { senderid?: string; senderorigin?: string; extensionid: string; connectallow: connectallowentry[] }): policyevaluation {\n const verdict = origincheckof(input);\n if (!verdict.accepted) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Validates the ratelimit bucket bounds as user choices: the limit and the window stay positive user values because no hidden ceiling exists. */\nexport function ratelimitboundsvalid(limit: number, window: number): policyevaluation {\n const bounds = bucketboundsvalid(limit, window);\n if (!bounds.valid) return { allowed: false, reason: bounds.reason };\n return { allowed: true, reason: bounds.reason };\n}\n\n/** Applies one ratelimit bucket to an automation command per origin and per session: a command inside its bound consumes a slot while a command past its bound defers until the window resets; an origin without a bucket keeps every command because the bounds stay user choices only. */\nexport function ratelimitgate(input: { bucket: ratelimitbucket | undefined; now: number }): policyevaluation {\n if (input.bucket === undefined) return { allowed: true, reason: \"No ratelimit bucket covers the origin of the command; the bounds stay user configured choices only.\" };\n if (input.now >= input.bucket.resetsat) return { allowed: true, reason: `The bucket window of ${input.bucket.origin} reset at ${input.bucket.resetsat}; the command consumes the first slot of its fresh window.` };\n if (input.bucket.used < input.bucket.limit) return { allowed: true, reason: `The command consumes slot ${input.bucket.used + 1} of ${input.bucket.limit} in the bucket of ${input.bucket.origin}.` };\n return { allowed: false, reason: `The bucket of ${input.bucket.origin} holds its ${input.bucket.limit} command bound; the command defers until the window resets at ${input.bucket.resetsat}.` };\n}\n\n/** Routes payment class steps through the confirmpay gate: a payment step without a gate opens one and pauses, an open gate keeps the pause, a resolved gate lets the step dispatch and a refused gate never dispatches it. */\nexport function confirmpaygate(input: { classes: sensitiveclass[]; state: \"none\" | \"open\" | \"resolved\" | \"refused\" }): policyevaluation {\n const kind = gatekindfor(input.classes);\n if (kind !== \"confirmpay\") return { allowed: true, reason: \"The step carries no payment class and needs no confirmpay gate.\" };\n if (input.state === \"resolved\") return { allowed: true, reason: \"The human resolved the confirmpay gate of the payment step; the step dispatches with its reviewed amount, payee origin and target.\" };\n if (input.state === \"refused\") return { allowed: false, reason: \"The human refused the confirmpay gate of the payment step; the payment never dispatches.\" };\n if (input.state === \"open\") return { allowed: false, reason: \"The confirmpay gate of the payment step stays open with the amount, the payee origin and the target element; the executor pauses until the human resolves it and no timeout ever resolves a gate.\" };\n return { allowed: false, reason: \"The payment step opens its confirmpay gate with the amount, the payee origin and the target element; the executor pauses until one distinct human action resolves it.\" };\n}\n\n/** Routes destructive delete steps through the confirmdelete gate: a delete step without a gate opens one and pauses, an open gate keeps the pause, a resolved gate lets the step dispatch and a refused gate never dispatches it. */\nexport function confirmdeletegate(input: { classes: sensitiveclass[]; state: \"none\" | \"open\" | \"resolved\" | \"refused\" }): policyevaluation {\n const kind = gatekindfor(input.classes);\n if (kind !== \"confirmdelete\") return { allowed: true, reason: \"The step carries no delete class and needs no confirmdelete gate.\" };\n if (input.state === \"resolved\") return { allowed: true, reason: \"The human resolved the confirmdelete gate of the destructive step; the step dispatches with its reviewed target, scope and irreversibility.\" };\n if (input.state === \"refused\") return { allowed: false, reason: \"The human refused the confirmdelete gate of the destructive step; the deletion never dispatches.\" };\n if (input.state === \"open\") return { allowed: false, reason: \"The confirmdelete gate of the destructive step stays open with the target, the scope and the irreversibility; the executor pauses until the human resolves it and no timeout ever resolves a gate.\" };\n return { allowed: false, reason: \"The destructive step opens its confirmdelete gate with the target, the scope and the irreversibility; the executor pauses until one distinct human action resolves it.\" };\n}\n\n/** Routes credential use steps through the confirmcreds gate: a credential step without a gate opens one and pauses with the credential label only, an open gate keeps the pause, a resolved gate lets the step read the vault at the last possible moment and a refused gate never dispatches it. */\nexport function confirmcredsgate(input: { classes: sensitiveclass[]; state: \"none\" | \"open\" | \"resolved\" | \"refused\" }): policyevaluation {\n const kind = gatekindfor(input.classes);\n if (kind !== \"confirmcreds\") return { allowed: true, reason: \"The step carries no credential class and needs no confirmcreds gate.\" };\n if (input.state === \"resolved\") return { allowed: true, reason: \"The human resolved the confirmcreds gate of the credential step; the step reads its value from the vault at the last possible moment and no log records it.\" };\n if (input.state === \"refused\") return { allowed: false, reason: \"The human refused the confirmcreds gate of the credential step; the credential never dispatches.\" };\n if (input.state === \"open\") return { allowed: false, reason: \"The confirmcreds gate of the credential step stays open with its credential label only; the executor pauses until the human resolves it and no timeout ever resolves a gate.\" };\n return { allowed: false, reason: \"The credential step opens its confirmcreds gate with its credential label only; the executor pauses until one distinct human action resolves it.\" };\n}\n\n/** Requires a distinct human action for each gated step: one action resolves exactly one gate, so a batch resolution refuses in full. */\nexport function gatebatchgate(input: { gateids: string[] }): policyevaluation {\n if (input.gateids.length === 0) return { allowed: false, reason: \"A gate resolution names its single gate.\" };\n if (input.gateids.length > 1) return { allowed: false, reason: `One human action resolves exactly one gate; the batch of ${input.gateids.length} gates refuses in full because no batch approval exists.` };\n return { allowed: true, reason: \"The resolution names exactly one gate; the distinct human action resolves it alone.\" };\n}\n\n/** Validates the phishguard lookalike threshold as a user choice between zero and one: the block line never defaults to an engine value. */\nexport function phishthresholdgate(threshold: number): policyevaluation {\n const verdict = phishthresholdvalid(threshold);\n if (!verdict.valid) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Blocks the credential step when the phishguard verdict crosses the user threshold: the deny event names the matched known origin the login target resembles. */\nexport function phishguardgate(input: { verdict: phishverdict }): policyevaluation {\n if (input.verdict.blocked) return { allowed: false, reason: input.verdict.reason };\n return { allowed: true, reason: input.verdict.reason };\n}\n\n/** Applies safedefaults to an origin without an originprofile: the documented read kinds pass while every sensitive class denies until the user widens the profile in the originprofile editor. */\nexport function safedefaultsgate(input: { profile: originprofile | undefined; classes: sensitiveclass[]; sensitive: boolean }): policyevaluation {\n if (input.profile !== undefined) return { allowed: true, reason: `The origin profile of ${input.profile.origin} exists; the safedefaults posture stays out of the decision.` };\n if (!input.sensitive) return { allowed: true, reason: \"The non-sensitive step rides the reads only baseline of the safedefaults posture; the first visit grants reads alone.\" };\n return { allowed: false, reason: `No origin profile exists and the safedefaults posture denies the sensitive ${input.classes.length > 0 ? input.classes.join(\" and \") : \"by default\"} step; open the originprofile editor to widen the profile the user controls.` };\n}\n\n/** Refuses secrets in step options, variables and plan text: a plaintext value that digests to a vault record or a raw value behind a masked field shape never dispatches. */\nexport function vaultsecretgate(input: { leaks: string[]; carries: boolean }): policyevaluation {\n if (input.leaks.length > 0) return { allowed: false, reason: `The plan carries ${input.leaks.length} plaintext secret value${input.leaks.length === 1 ? \"\" : \"s\"} that digest to vault records; secrets never ride step options, variables or plan texts, only the vault holds them.` };\n if (input.carries) return { allowed: false, reason: \"The step types a raw value into a masked field shape; credential steps read their value from the vault at the last possible moment and never carry it in the options.\" };\n return { allowed: true, reason: \"The step and the plan carry no secret outside the vault; the values stay behind the seam.\" };\n}\n\n/** Marks extracted markup untrusted before any render: the render routes through the sandboxframe of the 1.1.60 family while an injection into the page context refuses. */\nexport function untrustedrendergate(input: { environment: \"pagecontext\" | \"isolatedworld\" | \"offscreenworker\" | \"sandboxframe\" }): policyevaluation {\n if (input.environment === \"sandboxframe\") return { allowed: true, reason: \"The extracted markup renders inside the sandboxframe under its nonce with scripts and handlers stripped; the untrusted content never reenters the page context.\" };\n return { allowed: false, reason: `The extracted markup grades untrusted and refuses to render inside the ${input.environment}; every untrusted render routes through the sandboxframe.` };\n}\n", "import { hostpattern, normalizeendpoint } from \"../policy.js\";\nimport type { capabilityreport, captchahandoff, clipboardconsentrecord, consoleconsentrecord, dataset, dialogdecision, downloadrecord, keyholdstate, navqueues, navstate, quarantineentry, streamstate, tabbadge, waitprofilerecord } from \"../types.js\";\nimport type { windowshape } from \"./tabscommand.js\";\n\ntype pagesignallike = { language?: string; template?: string; scrolllocked?: boolean; banner?: string };\n\nconst endpointinput = document.querySelector<HTMLInputElement>(\"#endpoint\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst capabilitiesnode = document.querySelector<HTMLElement>(\"#capabilities\");\nconst livenode = document.querySelector<HTMLElement>(\"#livelogic\");\nconst navstatenode = document.querySelector<HTMLElement>(\"#navstate\");\nconst windowlayoutnode = document.querySelector<HTMLElement>(\"#windowlayout\");\nconst captchastatenode = document.querySelector<HTMLElement>(\"#captchastate\");\nconst datasetstatenode = document.querySelector<HTMLElement>(\"#datasetstate\");\nconst downloadstatenode = document.querySelector<HTMLElement>(\"#downloadstate\");\nconst clipboardstatenode = document.querySelector<HTMLElement>(\"#clipboardstate\");\nconst capturestatenode = document.querySelector<HTMLElement>(\"#capturestate\");\nconst mediastatenode = document.querySelector<HTMLElement>(\"#mediastate\");\nconst callstatenode = document.querySelector<HTMLElement>(\"#callstate\");\nconst rulestatenode = document.querySelector<HTMLElement>(\"#rulestate\");\nconst netstatenode = document.querySelector<HTMLElement>(\"#netstate\");\nconst debugstatenode = document.querySelector<HTMLElement>(\"#debugstate\");\nconst emulationstatenode = document.querySelector<HTMLElement>(\"#emulationstate\");\nconst sessionstatenode = document.querySelector<HTMLElement>(\"#sessionstate\");\nconst workflowstatenode = document.querySelector<HTMLElement>(\"#workflowstate\");\nconst triggerstatenode = document.querySelector<HTMLElement>(\"#triggerstate\");\nconst mcpstatenode = document.querySelector<HTMLElement>(\"#mcpstate\");\nconst modelstatenode = document.querySelector<HTMLElement>(\"#modelstate\");\nconst swarmstatenode = document.querySelector<HTMLElement>(\"#swarmstate\");\nconst environmentstatenode = document.querySelector<HTMLElement>(\"#environmentstate\");\nconst securitystatenode = document.querySelector<HTMLElement>(\"#securitystate\");\nconst securitycontrols = document.querySelector<HTMLElement>(\"#securitycontrols\");\nconst environmentcontrols = document.querySelector<HTMLElement>(\"#environmentcontrols\");\nconst workflowcontrols = document.querySelector<HTMLElement>(\"#workflowcontrols\");\nconst windowcontrols = document.querySelector<HTMLElement>(\"#windowcontrols\");\nconst connectbutton = document.querySelector<HTMLButtonElement>(\"#connect\");\nconst sessionbutton = document.querySelector<HTMLButtonElement>(\"#session\");\nconst pausebutton = document.querySelector<HTMLButtonElement>(\"#pause\");\nconst stopbutton = document.querySelector<HTMLButtonElement>(\"#stop\");\nconst openbutton = document.querySelector<HTMLButtonElement>(\"#openpanel\");\n\nfunction status(message: string, error = false): void { if (statusnode) { statusnode.textContent = message; statusnode.dataset.state = error ? \"error\" : \"ready\"; } }\nasync function request(message: unknown): Promise<unknown> { const response = await chrome.runtime.sendMessage(message) as { ok: boolean; value?: unknown; error?: string }; if (!response.ok) throw new Error(response.error); return response.value; }\n\nfunction pauselabel(paused: boolean): string { return paused ? \"Resume session\" : \"Pause session\"; }\n\n/** Renders the live optional capability set the user has granted. */\nfunction rendercapabilities(report?: capabilityreport): void {\n if (!capabilitiesnode) return;\n if (!report) { capabilitiesnode.textContent = \"Capabilities unknown.\"; return; }\n const granted = [`tabs ${report.tabs ? \"granted\" : \"absent\"}`, `downloads ${report.downloads ? \"granted\" : \"absent\"}`, `clipboard read ${report.clipboardread ? \"granted\" : \"absent\"}`, `clipboard write ${report.clipboardwrite ? \"granted\" : \"absent\"}`];\n capabilitiesnode.textContent = `Optional capabilities: ${granted.join(\" \u00B7 \")}.`;\n}\n\n/** Renders the currently held keys and the dialogs answered by the reviewed policy. */\nfunction renderlivestate(holds?: keyholdstate[], dialogs?: dialogdecision[], signals?: pagesignallike): void {\n if (!livenode) return;\n const held = (holds ?? []).filter(hold => hold.releasedat === undefined);\n const heldsummary = held.length > 0 ? held.map(hold => `${hold.key}${hold.holdid ? ` (${hold.holdid})` : \"\"}`).join(\", \") : \"none\";\n const dialogsummary = (dialogs ?? []).length > 0 ? `${dialogs?.length} answered` : \"none\";\n const pages = signals && (signals.language !== undefined || signals.template !== undefined || signals.scrolllocked !== undefined)\n ? [signals.language !== undefined ? `language ${signals.language || \"unknown\"}` : \"\", signals.template !== undefined ? `template ${signals.template}` : \"\", signals.scrolllocked !== undefined ? `scroll lock ${signals.scrolllocked ? \"on\" : \"off\"}` : \"\"].filter(Boolean).join(\" \u00B7 \")\n : \"unknown\";\n livenode.textContent = `Held keys: ${heldsummary} \u00B7 Open dialogs answered: ${dialogsummary} \u00B7 Page signals: ${pages}.`;\n}\n\n/** Renders the load phase, offline state, active wait profile and queued navigation targets of the session tab. */\nfunction rendernavstate(state?: navstate, offline?: boolean, profile?: waitprofilerecord, queues?: navqueues): void {\n if (!navstatenode) return;\n const phase = state?.phase ? `load phase ${state.phase}` : \"load phase unknown\";\n const offlinestate = offline === undefined ? \"connectivity unknown\" : offline ? \"offline\" : \"online\";\n const wait = profile ? `wait profile ${profile.profile.signals.join(\"+\")}` : \"no wait profile\";\n const queued = queues ? `queued targets ${queues.prefetch + queues.batchopen} (${queues.prefetch} prefetch \u00B7 ${queues.batchopen} batch open)` : \"no queued targets\";\n navstatenode.textContent = `Navigation: ${phase} \u00B7 ${offlinestate} \u00B7 ${wait} \u00B7 ${queued}.`;\n}\n\n/** Renders the current window layout and the quick window state buttons. */\nfunction renderwindowlayout(windows?: windowshape[], badges?: tabbadge[]): void {\n if (!windowlayoutnode || !windowcontrols) return;\n windowlayoutnode.textContent = windows === undefined\n ? \"Window layout: unknown.\"\n : `Window layout: ${windows.length} window${windows.length === 1 ? \"\" : \"s\"}${windows.length > 0 ? ` (${windows.map(item => `${item.windowid}: ${item.state}${item.incognito ? \" incognito\" : \"\"}`).join(\" \u00B7 \")})` : \"\"} \u00B7 ${new Set((badges ?? []).map(badge => badge.tabid)).size} tab${new Set((badges ?? []).map(badge => badge.tabid)).size === 1 ? \"\" : \"s\"} with active tasks.`;\n windowcontrols.replaceChildren();\n for (const item of (windows ?? []).slice(0, 4)) {\n for (const state of [\"maximized\", \"minimized\", \"normal\"] as const) {\n const quick = document.createElement(\"button\");\n quick.type = \"button\";\n quick.className = \"secondary\";\n quick.textContent = `${state === \"normal\" ? \"restore\" : state} ${item.windowid}`;\n quick.addEventListener(\"click\", () => request({ kind: \"windowstate\", windowid: item.windowid, state }).then(() => { status(`Window ${item.windowid} set to ${state}.`); return restore(); }).catch(error => status(error instanceof Error ? error.message : String(error), true)));\n windowcontrols.append(quick);\n }\n }\n}\n\n/** Renders the captcha handoff state while a plan waits for the user. */\nfunction rendercaptchastate(captchas?: captchahandoff[]): void {\n if (!captchastatenode) return;\n const open = (captchas ?? []).find(handoff => !handoff.resolved);\n if (open) { captchastatenode.dataset.state = \"error\"; captchastatenode.textContent = `Captcha handoff open on ${open.origin}; the plan waits until you resolve it in the review panel.`; return; }\n delete captchastatenode.dataset.state;\n captchastatenode.textContent = \"Captcha handoff: none.\";\n}\n\n/** Renders the active dataset row count and the stream state of the session. */\nfunction renderdatasetstate(datasets?: dataset[], streams?: streamstate[]): void {\n if (!datasetstatenode) return;\n const rows = (datasets ?? []).reduce((total, datasetvalue) => total + datasetvalue.rows.length, 0);\n const count = datasets?.length ?? 0;\n const stream = (streams ?? []).find(item => item.done !== true) ?? (streams ?? [])[0];\n const streamsummary = stream ? ` \u00B7 stream ${stream.name} chunk ${stream.chunk} of ${stream.chunks}${stream.done ? \" complete\" : \"\"}` : \"\";\n datasetstatenode.textContent = count === 0 ? \"Datasets: none collected yet.\" : `Datasets: ${count} collected (${rows} row${rows === 1 ? \"\" : \"s\"})${streamsummary}.`;\n}\n\n/** Renders the live batch download states and the quarantined files awaiting scan verdicts. */\nfunction renderdownloadstate(downloads?: downloadrecord[], quarantines?: quarantineentry[]): void {\n if (!downloadstatenode) return;\n const states = (downloads ?? []).filter(record => record.state === \"running\" || record.state === \"queued\" || record.state === \"paused\");\n const completed = (downloads ?? []).filter(record => record.state === \"complete\").length;\n const failed = (downloads ?? []).filter(record => record.state === \"failed\").length;\n const pending = (quarantines ?? []).filter(entry => entry.scan === \"pending\").length;\n const parts = [\n states.length > 0 ? `${states.length} in flight (${states.map(record => record.state).join(\" \u00B7 \")})` : \"\",\n completed > 0 ? `${completed} completed` : \"\",\n failed > 0 ? `${failed} failed` : \"\",\n pending > 0 ? `${pending} quarantined awaiting scan` : \"\",\n ].filter(Boolean);\n downloadstatenode.textContent = (downloads ?? []).length === 0 && pending === 0 ? \"Downloads: none.\" : `Downloads: ${parts.join(\" \u00B7 \") || \"none active\"}.`;\n}\n\n/** Renders the clipboard consent state while a read waits for the user approval. */\nfunction renderclipboardstate(consents?: clipboardconsentrecord[]): void {\n if (!clipboardstatenode) return;\n const waiting = (consents ?? []).filter(record => record.approved === undefined);\n if (waiting.length > 0) {\n clipboardstatenode.dataset.state = \"error\";\n clipboardstatenode.textContent = `Clipboard consent: ${waiting.length} read prompt${waiting.length === 1 ? \"\" : \"s\"} waiting for your approval in the review panel.`;\n return;\n }\n delete clipboardstatenode.dataset.state;\n clipboardstatenode.textContent = \"Clipboard consent: none waiting.\";\n}\n\n/** Renders the capture count of the active run beside the capture policy. */\nfunction rendercapturestate(captures?: Array<{ id: string }>, policy?: string): void {\n if (!capturestatenode) return;\n const count = captures?.length ?? 0;\n const mode = policy ?? \"manual\";\n capturestatenode.textContent = count === 0 ? `Captures: none (policy ${mode}).` : `Captures: ${count} of the run (policy ${mode}).`;\n}\n\n/** Renders the observed request count of the active run beside the live channel and stream state. */\nfunction rendernetstate(exchanges?: Array<{ id: string }>, channels?: Array<{ id: string; state: string }>): void {\n if (!netstatenode) return;\n const count = exchanges?.length ?? 0;\n const live = (channels ?? []).filter(channel => channel.state === \"open\" || channel.state === \"connecting\").length;\n netstatenode.textContent = `Observed requests: ${count} of the run${live > 0 ? ` \u00B7 ${live} live channel${live === 1 ? \"\" : \"s\"}` : \"\"}.`;\n}\n\n/** Renders the error count captured in the active run beside pending console capture consent prompts, the debugger attached badge of devtools sessions and the profiling badge of active profile or trace instruments. */\nfunction renderdebugstate(timeline?: { errors?: Array<{ id: string }>; rejections?: Array<{ id: string }> }, consoleconsents?: consoleconsentrecord[], debuggergrants?: Array<{ approved?: boolean; revokedat?: number }>, cdpattached?: number, profileactive?: number, sourcemapconsents?: Array<{ approved?: boolean; revokedat?: number }>): void {\n if (!debugstatenode) return;\n const errors = timeline?.errors?.length ?? 0;\n const rejections = timeline?.rejections?.length ?? 0;\n const waiting = (consoleconsents ?? []).filter(consent => consent.approved === undefined).length;\n const debuggerwaiting = (debuggergrants ?? []).filter(grant => grant.approved === undefined && grant.revokedat === undefined).length;\n const attached = cdpattached ?? 0;\n const profiling = profileactive ?? 0;\n const sourcemapwaiting = (sourcemapconsents ?? []).filter(consent => consent.approved === undefined && consent.revokedat === undefined).length;\n debugstatenode.textContent = errors === 0 && rejections === 0 && waiting === 0 && debuggerwaiting === 0 && attached === 0 && profiling === 0 && sourcemapwaiting === 0\n ? \"Timeline: no errors captured.\"\n : `Timeline: ${errors} error${errors === 1 ? \"\" : \"s\"}${rejections > 0 ? ` \u00B7 ${rejections} rejection${rejections === 1 ? \"\" : \"s\"}` : \"\"} captured in the active run${waiting > 0 ? ` \u00B7 ${waiting} console consent prompt${waiting === 1 ? \"\" : \"s\"} waiting` : \"\"}${debuggerwaiting > 0 ? ` \u00B7 ${debuggerwaiting} debugger consent prompt${debuggerwaiting === 1 ? \"\" : \"s\"} waiting` : \"\"}${attached > 0 ? ` \u00B7 debugger attached on ${attached} session${attached === 1 ? \"\" : \"s\"}` : \"\"}${profiling > 0 ? ` \u00B7 profiling active on ${profiling} instrument${profiling === 1 ? \"\" : \"s\"}` : \"\"}${sourcemapwaiting > 0 ? ` \u00B7 ${sourcemapwaiting} source map prompt${sourcemapwaiting === 1 ? \"\" : \"s\"} waiting` : \"\"}.`;\n if (errors > 0 || waiting > 0 || debuggerwaiting > 0 || sourcemapwaiting > 0) debugstatenode.dataset.state = \"error\";\n else delete debugstatenode.dataset.state;\n if (attached > 0) debugstatenode.dataset.debugger = \"attached\";\n else delete debugstatenode.dataset.debugger;\n if (profiling > 0) debugstatenode.dataset.profile = \"active\";\n else delete debugstatenode.dataset.profile;\n}\n\n/** Renders the active traffic rule count badge of controlled runs. */\nfunction renderrulestate(activerules?: number, blocked?: number, mocked?: number): void {\n if (!rulestatenode) return;\n const rules = activerules ?? 0;\n rulestatenode.textContent = rules === 0 ? \"Traffic rules: none active.\" : `Traffic rules: ${rules} active of the run${blocked !== undefined && blocked > 0 ? ` \u00B7 ${blocked} blocked request${blocked === 1 ? \"\" : \"s\"}` : \"\"}${mocked !== undefined && mocked > 0 ? ` \u00B7 ${mocked} mocked response${mocked === 1 ? \"\" : \"s\"}` : \"\"}.`;\n if (rules > 0) rulestatenode.dataset.state = \"controlled\";\n else delete rulestatenode.dataset.state;\n}\n\n/** Renders the media state badge: the recording indicator of active recordings beside the stored media record count. */\nfunction rendercallstate(calls?: Array<{ id: string }>, fetchconsents?: Array<{ approved?: boolean }>): void {\n if (!callstatenode) return;\n const count = calls?.length ?? 0;\n const waiting = (fetchconsents ?? []).filter(consent => consent.approved === undefined).length;\n callstatenode.textContent = `Outbound calls: ${count} of the run${waiting > 0 ? ` \u00B7 ${waiting} header consent prompt${waiting === 1 ? \"\" : \"s\"} waiting` : \"\"}.`;\n}\n\nfunction rendermediastate(media?: Array<{ id: string }>, recordingactive?: Array<{ id: string; kind: string }>, recordingconsents?: Array<{ approved?: boolean }>): void {\n if (!mediastatenode) return;\n const active = (recordingactive ?? []).length;\n const waiting = (recordingconsents ?? []).filter(record => record.approved === undefined).length;\n const count = media?.length ?? 0;\n const recording = active > 0 ? `\u25CF recording ${active} active capture${active === 1 ? \"\" : \"s\"}` : waiting > 0 ? `recording consent: ${waiting} prompt${waiting === 1 ? \"\" : \"s\"} waiting` : \"\";\n mediastatenode.textContent = `Media: ${count} record${count === 1 ? \"\" : \"s\"}${recording ? ` \u00B7 ${recording}` : \"\"}.`;\n if (active > 0) mediastatenode.dataset.state = \"recording\";\n else delete mediastatenode.dataset.state;\n}\n\n/** Renders the active emulation badge of the run: the layer names and the pending location consent prompts. */\nfunction renderemulationstate(layers?: string[], locationconsents?: Array<{ approved?: boolean }>): void {\n if (!emulationstatenode) return;\n const active = layers ?? [];\n const waiting = (locationconsents ?? []).filter(consent => consent.approved === undefined).length;\n emulationstatenode.textContent = `Emulation: ${active.length > 0 ? `${active.length} active layer${active.length === 1 ? \"\" : \"s\"} (${active.join(\", \")})` : \"no active layer\"}${waiting > 0 ? ` \u00B7 ${waiting} location consent prompt${waiting === 1 ? \"\" : \"s\"} waiting` : \"\"}.`;\n if (active.length > 0) emulationstatenode.dataset.emulated = \"active\";\n else delete emulationstatenode.dataset.emulated;\n if (waiting > 0) emulationstatenode.dataset.state = \"error\";\n else delete emulationstatenode.dataset.state;\n}\n\n/** Renders the saved session count and the auto snapshot state with its interval and count. */\nfunction rendersessionstate(memory?: { records: Array<{ id: string; name: string }>; auto?: { period: number; maxsnapshots: number; expiry: number } }, auto?: { interval: { period: number; maxsnapshots: number; expiry: number }; count: number }): void {\n if (!sessionstatenode) return;\n const saved = memory?.records.length ?? 0;\n const interval = auto?.interval ?? memory?.auto;\n sessionstatenode.textContent = `Sessions: ${saved} saved${interval !== undefined ? ` \u00B7 auto snapshot every ${interval.period} ms (${auto?.count ?? 0} of ${interval.maxsnapshots})` : \" \u00B7 no auto interval\"}.`;\n if (interval !== undefined) sessionstatenode.dataset.auto = \"active\";\n else delete sessionstatenode.dataset.auto;\n}\n\n/** Renders the active workflow state with pause and cancel buttons, the count of workflows running in the background and the active loop iteration count of the running runs. */\nfunction renderworkflowstate(runs?: Array<{ id: string; state: string; cursor: number; workflowid: string; dryrun?: boolean }>, control?: Array<{ runid: string; kind: string; loops?: Array<{ path: string }> }>): void {\n if (!workflowstatenode) return;\n const all = runs ?? [];\n const running = all.filter(run => run.state === \"running\");\n const paused = all.filter(run => run.state === \"paused\");\n const iterations = (control ?? []).filter(decision => decision.kind === \"loop\" && running.some(run => run.id === decision.runid)).reduce((total, decision) => total + (decision.loops?.length ?? 0), 0);\n workflowstatenode.textContent = `Workflows: ${running.length} running in the background${paused.length > 0 ? ` \u00B7 ${paused.length} paused` : \"\"}${running.length > 0 && iterations > 0 ? ` \u00B7 loop iteration ${iterations}` : \"\"}${all.length > 0 ? ` \u00B7 latest ${all[0]?.state ?? \"\"}` : \" \u00B7 none composed yet\"}.`;\n if (running.length > 0) workflowstatenode.dataset.state = \"active\";\n else delete workflowstatenode.dataset.state;\n if (!workflowcontrols) return;\n workflowcontrols.replaceChildren();\n for (const run of [...running, ...paused]) {\n const controls = document.createElement(\"span\");\n const pause = document.createElement(\"button\");\n pause.className = \"secondary\";\n pause.textContent = run.state === \"running\" ? `Pause ${run.id.slice(0, 6)}` : `Resume ${run.id.slice(0, 6)}`;\n pause.disabled = false;\n pause.addEventListener(\"click\", async () => {\n try {\n await request({ kind: run.state === \"running\" ? \"pauseworkflowrun\" : \"resumeworkflowrun\", runid: run.id });\n status(run.state === \"running\" ? \"Workflow run paused at its checkpoint.\" : \"Workflow run resumed from its checkpoint.\");\n await restore();\n } catch (error) { status(error instanceof Error ? error.message : String(error), true); }\n });\n const cancel = document.createElement(\"button\");\n cancel.className = \"danger\";\n cancel.textContent = `Cancel ${run.id.slice(0, 6)}`;\n cancel.addEventListener(\"click\", async () => {\n try {\n await request({ kind: \"cancelworkflowrun\", runid: run.id, reason: \"popup cancel\" });\n status(\"Workflow run cancelled with its reason recorded.\");\n await restore();\n } catch (error) { status(error instanceof Error ? error.message : String(error), true); }\n });\n controls.append(pause, \" \", cancel);\n workflowcontrols.append(controls);\n }\n}\n\n/** Renders the trigger layer: the armed rule count, the queued fires held while the session is paused and the button rule status of the toolbar click. */\n/** Renders the mcp server running indicator of the agent protocol: the running state, the localhost bind, the connected clients, the paired count, the pending approval gates as notifications and the long tool progress of the in flight calls that announces itself while the panel stays hidden. */\n/** Renders the active model per running task kind: every routed task kind shows its provider and model so the user reads which model drives which task. */\nfunction rendermodelstate(state?: { providers: Array<{ id: string; name: string; status: string }>; tasks: Array<{ kind: string; provider?: string; model?: string }>; usage: { totaltokens: number; cost: number; calls: number } }): void {\n if (!modelstatenode) return;\n if (!state) { modelstatenode.textContent = \"Models: none connected.\"; return; }\n const routed = state.tasks.filter(task => task.provider !== undefined);\n modelstatenode.textContent = `Models: ${routed.length > 0 ? routed.slice(0, 3).map(task => `${task.kind} ${task.model}`).join(\" \u00B7 \") : \"no route yet\"} \u00B7 ${state.usage.calls} call${state.usage.calls === 1 ? \"\" : \"s\"} \u00B7 ${state.usage.totaltokens} tokens.`;\n}\n\n/** Renders the swarm summary: the popup reads the agent count with the active, paused and stopped split, the claimed tasks of the shared queue, the elected leader and the held lock count, so the user reads the swarm at a glance. */\n/** Renders the execution environment status of the running session: the environments of the open run, the keepalive heartbeat indicator, the offscreen capability state and the zombie warning with its reap action. */\nfunction renderenvironmentstate(view?: { report: { environments: Array<{ stepid: string; environment: string }>; workers: number; keepalive?: { runid: string; state: string; beats: number; lastbeatat: number; portopen: boolean } }; offscreengranted: boolean; parseoffload: boolean; zombies: string[]; grants?: string[] }): void {\n if (!environmentstatenode) return;\n if (!view) { environmentstatenode.textContent = \"Environments: no run state.\"; return; }\n const counted = new Map<string, number>();\n for (const entry of view.report.environments) counted.set(entry.environment, (counted.get(entry.environment) ?? 0) + 1);\n const breakdown = [...counted.entries()].map(([environment, count]) => `${environment} ${count}`).join(\" \u00B7 \");\n const keepalive = view.report.keepalive;\n environmentstatenode.textContent = `Environments: ${breakdown.length > 0 ? breakdown : \"no step executed\"} \u00B7 offscreen ${view.offscreengranted ? \"granted\" : \"absent\"} \u00B7 offload ${view.parseoffload ? \"on\" : \"off\"}${view.report.workers > 0 ? ` \u00B7 workers ${view.report.workers}` : \"\"} \u00B7 keepalive ${keepalive ? `${keepalive.state === \"active\" && keepalive.portopen ? \"\u2665\" : \"closed\"} ${keepalive.beats} beat${keepalive.beats === 1 ? \"\" : \"s\"}` : \"none\"}${view.zombies.length > 0 ? ` \u00B7 zombie runs ${view.zombies.join(\", \")}` : \"\"}${view.grants !== undefined ? ` \u00B7 grants ${view.grants.length > 0 ? view.grants.join(\", \") : \"default\"}` : \"\"}.`;\n if (keepalive?.state === \"active\" && keepalive.portopen) environmentstatenode.dataset.state = \"active\";\n else delete environmentstatenode.dataset.state;\n if (environmentcontrols) {\n environmentcontrols.replaceChildren();\n if (view.zombies.length > 0) {\n const reap = document.createElement(\"button\");\n reap.className = \"secondary\";\n reap.textContent = \"Reap zombie runs\";\n reap.addEventListener(\"click\", () => { void request({ kind: \"runstate\", reap: true }).then(() => restore()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });\n environmentcontrols.append(reap);\n }\n if (!view.offscreengranted) {\n const grant = document.createElement(\"button\");\n grant.className = \"secondary\";\n grant.textContent = \"Request offscreen grant\";\n grant.addEventListener(\"click\", () => { void request({ kind: \"environments\", requestcapability: true }).then(() => restore()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });\n environmentcontrols.append(grant);\n }\n }\n}\n\n/** Renders the security posture of the active tab: the origin allowlist state, the originprofile summary, the consent window state, the denydefault notice and the safedefaults notice of an unprofiled origin that links to the transparencypage originprofile editor, with the allowlist grant request for a new origin. */\nfunction rendersecuritystate(view?: { posture: string; allowlist: Array<{ origin: string; profileid: string }>; profiles: Array<{ origin: string; grants: string[]; denials: string[] }>; windows: Array<{ origin: string; state: string; expiresat: number }>; sessionorigin?: string; chain: Array<{ runid: string; valid: boolean; entries: number; sealhash?: string }> }, activeorigin?: string): void {\n if (!securitystatenode) return;\n if (!view) { securitystatenode.textContent = \"Security: state unknown.\"; return; }\n const origin = activeorigin ?? view.sessionorigin ?? \"the active tab\";\n const granted = view.allowlist.some(entry => entry.origin === origin);\n const profile = view.profiles.find(candidate => candidate.origin === origin);\n const windowstate = view.windows.find(candidate => candidate.origin === origin && candidate.state === \"active\");\n const verified = view.chain.filter(chain => chain.valid).length;\n securitystatenode.textContent = `Security: ${origin} ${granted ? \"allowlisted\" : \"ungranted (denydefault)\"}${profile !== undefined ? ` \u00B7 profile ${profile.grants.length} granted / ${profile.denials.length} denied` : \" \u00B7 no origin profile\"}${windowstate !== undefined ? ` \u00B7 consent window until ${new Date(windowstate.expiresat).toLocaleTimeString()}` : \" \u00B7 no active consent window\"} \u00B7 ${verified}/${view.chain.length} log chain${view.chain.length === 1 ? \"\" : \"s\"} verified.`;\n if (granted) delete securitystatenode.dataset.state;\n else securitystatenode.dataset.state = \"error\";\n if (securitycontrols) {\n securitycontrols.replaceChildren();\n if (!granted) {\n const grant = document.createElement(\"button\");\n grant.className = \"secondary\";\n grant.textContent = `Grant ${origin} allowlist entry`;\n grant.addEventListener(\"click\", () => { void request({ kind: \"security\", allowlist: { add: { origin } } }).then(() => restore()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });\n securitycontrols.append(grant);\n }\n if (profile === undefined && origin !== \"the active tab\") {\n const safedefaults = document.createElement(\"button\");\n safedefaults.className = \"secondary\";\n safedefaults.textContent = `Safedefaults on ${origin}: reads only \u2014 open the originprofile editor`;\n safedefaults.addEventListener(\"click\", () => { try { chrome.runtime.openOptionsPage(() => { void chrome.runtime.lastError; }); } catch { status(\"The transparencypage did not open; open it from the extensions page.\", true); } });\n securitycontrols.append(safedefaults);\n }\n if (view.chain.length > 0) {\n const seal = view.chain.find(chain => chain.sealhash !== undefined);\n if (seal?.sealhash !== undefined) {\n const sealinfo = document.createElement(\"span\");\n sealinfo.className = \"muted\";\n sealinfo.textContent = `Seal ${seal.sealhash.slice(0, 10)}\u2026`;\n securitycontrols.append(sealinfo);\n }\n }\n }\n}\n\nfunction renderswarmstate(state?: { overview: { agents: number; active: number; paused: number; stopped: number; claimed: number; queued: number }; killswitch: { engaged: boolean }; topology?: { leaderid: string }; locks?: Array<{ key: string }> }): void {\n if (!swarmstatenode) return;\n if (!state) { swarmstatenode.textContent = \"Swarm: no agents.\"; return; }\n swarmstatenode.textContent = `Swarm: ${state.overview.agents} agent${state.overview.agents === 1 ? \"\" : \"s\"}${state.overview.agents > 0 ? ` (${state.overview.active} active${state.overview.paused > 0 ? `, ${state.overview.paused} paused` : \"\"}${state.overview.stopped > 0 ? `, ${state.overview.stopped} stopped` : \"\"})` : \"\"} \u00B7 ${state.overview.claimed} active task${state.overview.claimed === 1 ? \"\" : \"s\"}${state.overview.queued > 0 ? ` \u00B7 ${state.overview.queued} queued` : \"\"}${state.topology !== undefined ? ` \u00B7 leader ${state.topology.leaderid}` : \"\"}${state.locks !== undefined && state.locks.length > 0 ? ` \u00B7 ${state.locks.length} lock${state.locks.length === 1 ? \"\" : \"s\"}` : \"\"}${state.killswitch.engaged ? \" \u00B7 killswitch engaged\" : \"\"}.`;\n if (state.overview.active > 0 || state.killswitch.engaged) swarmstatenode.dataset.state = \"active\";\n else delete swarmstatenode.dataset.state;\n}\n\nfunction rendermcpstate(state?: { state: string; bind?: string; port: number; localhost: boolean; clients?: Array<{ id: string; paired: boolean }>; approvals?: Array<{ id: string; tool: string; state: string }>; inflight?: Array<{ callid: string; tool: string; chunks: number }>; progressnotices?: Array<{ callid: string; percent?: number; message: string; cancellable?: boolean; at: number }> }): void {\n if (!mcpstatenode) return;\n const running = state?.state === \"running\";\n const clients = state?.clients ?? [];\n const paired = clients.filter(client => client.paired).length;\n const pending = (state?.approvals ?? []).filter(approval => approval.state === \"pending\");\n const inflight = state?.inflight ?? [];\n const notice = (state?.progressnotices ?? [])[0];\n mcpstatenode.textContent = `Mcp server: ${running ? `running on ${state?.bind ?? \"127.0.0.1\"}:${state?.port ?? 7436}${state?.localhost === false ? \" (remote bind)\" : \"\"} \u00B7 ${clients.length} client${clients.length === 1 ? \"\" : \"s\"}${paired !== clients.length ? ` \u00B7 ${paired} paired` : \"\"}${pending.length > 0 ? ` \u00B7 ${pending.length} approval gate${pending.length === 1 ? \"\" : \"s\"} waiting (${[...new Set(pending.map(approval => approval.tool))].slice(0, 3).join(\", \")})` : \"\"}${inflight.length > 0 ? ` \u00B7 ${inflight.length} call${inflight.length === 1 ? \"\" : \"s\"} in flight${notice !== undefined ? ` (${notice.percent !== undefined ? `${notice.percent}%` : notice.message}${notice.cancellable === false ? \"\" : \", cancellable in the panel\"})` : \"\"}` : \"\"}` : state?.state ?? \"stopped\"}.`;\n if (running) mcpstatenode.dataset.state = \"active\";\n else delete mcpstatenode.dataset.state;\n}\n\nfunction rendertriggerstate(rules?: Array<{ id: string; kind: string; enabled: boolean; paused?: boolean; nextfireat?: number }>, queued?: number, paused?: boolean, buttonfired?: boolean): void {\n if (!triggerstatenode) return;\n const armed = rules ?? [];\n const enabled = armed.filter(rule => rule.enabled && rule.paused !== true);\n const scheduled = enabled.filter(rule => rule.nextfireat !== undefined);\n const buttons = enabled.filter(rule => rule.kind === \"button\");\n triggerstatenode.textContent = `Triggers: ${armed.length} armed rule${armed.length === 1 ? \"\" : \"s\"}${enabled.length !== armed.length ? ` \u00B7 ${enabled.length} enabled` : \"\"}${scheduled.length > 0 ? ` \u00B7 ${scheduled.length} scheduled` : \"\"}${(queued ?? 0) > 0 ? ` \u00B7 ${queued} fire${queued === 1 ? \"\" : \"s\"} ${paused ? \"held while the session is paused\" : \"queued\"}` : \"\"}${buttons.length > 0 ? ` \u00B7 toolbar button ${buttonfired ? \"fired\" : \"ready\"}` : \"\"}.`;\n if (enabled.length > 0) triggerstatenode.dataset.state = \"active\";\n else delete triggerstatenode.dataset.state;\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; origin?: string }; capabilities?: capabilityreport; holds?: { heldkeys: keyholdstate[] }; dialogs?: dialogdecision[]; signals?: pagesignallike; navstate?: navstate; offline?: boolean; waitprofile?: waitprofilerecord; navqueues?: navqueues; windows?: windowshape[]; badges?: tabbadge[]; captchas?: captchahandoff[]; datasets?: dataset[]; streams?: streamstate[]; downloads?: downloadrecord[]; clipconsents?: clipboardconsentrecord[]; quarantines?: quarantineentry[]; captures?: Array<{ id: string }>; capturepolicy?: string; media?: Array<{ id: string }>; recordingactive?: Array<{ id: string; kind: string }>; recordingconsents?: Array<{ approved?: boolean }>; calls?: Array<{ id: string }>; fetchconsents?: Array<{ approved?: boolean }>; exchanges?: Array<{ id: string }>; channels?: Array<{ id: string; state: string }>; activerules?: number; timeline?: { errors?: Array<{ id: string }>; rejections?: Array<{ id: string }> }; consoleconsents?: consoleconsentrecord[]; debuggergrants?: Array<{ approved?: boolean; revokedat?: number }>; cdpattached?: number; profileactive?: number; profile?: { consents?: Array<{ approved?: boolean; revokedat?: number }> }; emulatedlayers?: string[]; emulation?: { consents: Array<{ approved?: boolean }> }; traffic?: { blocks: Array<{ hits: number; revertedat?: number }>; mocks: Array<{ hits: number; revertedat?: number }> }; sessionmemory?: { records: Array<{ id: string; name: string }>; auto?: { period: number; maxsnapshots: number; expiry: number } }; autosnapshotstate?: { interval: { period: number; maxsnapshots: number; expiry: number }; count: number }; workflow?: { runs: Array<{ id: string; state: string; cursor: number; workflowid: string; dryrun?: boolean }>; control?: Array<{ runid: string; kind: string; loops?: Array<{ path: string }> }> }; trigger?: { rules: Array<{ id: string; kind: string; enabled: boolean; paused?: boolean; nextfireat?: number }>; queued: number }; mcp?: { state: string; bind?: string; port: number; localhost: boolean; clients: Array<{ id: string; paired: boolean }>; approvals: Array<{ id: string; tool: string; state: string }>; inflight?: Array<{ callid: string; tool: string; chunks: number }>; progressnotices?: Array<{ callid: string; percent?: number; message: string; cancellable?: boolean; at: number }> }; llm?: { providers: Array<{ id: string; name: string; status: string }>; tasks: Array<{ kind: string; provider?: string; model?: string }>; usage: { totaltokens: number; cost: number; calls: number } }; swarm?: { overview: { agents: number; active: number; paused: number; stopped: number; claimed: number; queued: number }; killswitch: { engaged: boolean }; topology?: { leaderid: string }; locks?: Array<{ key: string }> }; environments?: { report: { environments: Array<{ stepid: string; environment: string }>; workers: number; keepalive?: { runid: string; state: string; beats: number; lastbeatat: number; portopen: boolean } }; offscreengranted: boolean; parseoffload: boolean; zombies: string[]; grants?: string[] }; security?: { posture: string; allowlist: Array<{ origin: string; profileid: string }>; profiles: Array<{ origin: string; grants: string[]; denials: string[] }>; windows: Array<{ origin: string; state: string; expiresat: number }>; sessionorigin?: string; chain: Array<{ runid: string; valid: boolean; entries: number; sealhash?: string }> }; };\n if (endpointinput && context.config) endpointinput.value = context.config.endpoint;\n rendercapabilities(context.capabilities);\n renderlivestate(context.holds?.heldkeys, context.dialogs, context.signals);\n rendernavstate(context.navstate, context.offline, context.waitprofile, context.navqueues);\n renderwindowlayout(context.windows, context.badges);\n rendercaptchastate(context.captchas);\n renderdatasetstate(context.datasets, context.streams);\n renderdownloadstate(context.downloads, context.quarantines);\n renderclipboardstate(context.clipconsents);\n rendercapturestate(context.captures, context.capturepolicy);\n rendermediastate(context.media, context.recordingactive, context.recordingconsents);\n rendercallstate(context.calls, context.fetchconsents);\n rendernetstate(context.exchanges, context.channels);\n renderdebugstate(context.timeline, context.consoleconsents, context.debuggergrants, context.cdpattached, context.profileactive, context.profile?.consents);\n renderemulationstate(context.emulatedlayers, context.emulation?.consents);\n rendersessionstate(context.sessionmemory, context.autosnapshotstate);\n renderworkflowstate(context.workflow?.runs, context.workflow?.control);\n rendertriggerstate(context.trigger?.rules, context.trigger?.queued, context.session?.pausedat !== undefined, triggerbuttonfired);\n rendermcpstate(context.mcp);\n rendermodelstate(context.llm);\n renderswarmstate(context.swarm);\n renderenvironmentstate(context.environments);\n rendersecuritystate(context.security, context.session?.origin);\n renderrulestate(context.activerules, (context.traffic?.blocks ?? []).filter(rule => rule.revertedat === undefined).reduce((total, rule) => total + rule.hits, 0), (context.traffic?.mocks ?? []).filter(spec => spec.revertedat === undefined).reduce((total, spec) => total + spec.hits, 0));\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\n/** The popup open is the toolbar button click of a default popup: an armed button rule evaluates behind the gates once per popup open. */\nlet triggerbuttonfired = false;\nvoid (async () => {\n try {\n const result = await request({ kind: \"buttontrigger\" }) as { fired: boolean; reason?: string };\n if (result.fired) { triggerbuttonfired = true; status(\"The toolbar button rule fired behind the consent gates.\"); }\n } catch { /* no armed button rule or the gates refused the fire; the panel shows the reason */ }\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
|
-
"names": []
|
|
4
|
+
"sourcesContent": ["import type { actionkind, agentbudget, agentidentity, agentmessage, agentplan, agentscope, agentsession, agentusage, allowlistentry, approvaltimeout, arbitrationrule, attachtarget, blackboardentry, callratelimit, captureexport, automationallowlistentry, classconsent, conflictwriter, connectallowentry, consentwindow, costbudget, criticreview, draftstep, environmentkind, environmentrequirement, escalationrecord, handoffrecord, lockkind, mergerule, modeloutput, origincheckverdict, originprofile, phishverdict, plandraft, providerconfig, replanrecord, resourcelock, resultreport, reviewrequest, revokerunevent, ratelimitbucket, sensitiveclass, schemaerror, spawnrequest, swarmaction, taskqueue, captureformat, capturenaming, captureoptions, cdpallowlist, cleanuprule, clientidentity, clientrecord, consoleconsentrecord, debuggergrant, delaystep, downloadspec, editormodel, endpointconfig, fieldkind, formprofile, locationconsent, loglevel, loglevelset, mimefilter, mcpserverconfig, observationmode, permissionstate, policyevaluation, protocoleventsubscription, quarantineentry, regionrect, rotationrule, runsettings, safetyverdict, samplingrequest, siteoverride, sourcemapconsent, spamrule, steptemplate, toolcallrecord, toolcatalog, tooldef, toolmock, toolnamespace, toolstep, tooldryrun, transformrule, verifiercheck, waitstep, watchdogconfig, workerassignment, workflowrecord, workflowstep } from \"./types.js\";\nimport { allowlistcheck, missingclassconsents, profilegrade, sensitiveclassesof, windowgatesstep } from \"./originpolicy.js\";\nimport { defaultenvironment, environmentrequirementsof, environmentsof } from \"./environments.js\";\nimport { domainkinds, toolnamespaces } from \"./toolcatalog.js\";\nimport { channeloptionsof, channelorigin, pollcursorof, subscriptionoptionsof } from \"./socketbus.js\";\nimport { apireplayspecof, privatemime } from \"./netwatch.js\";\nimport { blockruleof, cookiedomaingranted, cookierecordof, mockspecof, patternorigin, proxyrouteof, headeruleof } from \"./netcontrol.js\";\nimport { allowlistcovers, breakpointinputof, cdpallowlistof, cdpdomains, cdpeventruleof, methoddomain, overrideinputof, stepmodeof, teardownplanof, watchexpressionof } from \"./cdpbus.js\";\nimport { annotationof, attachtargetof, flowspecof, tracecategories } from \"./profilers.js\";\nimport { agentgrammarvalid, agentpresetof, blackboxruleof, browserpermissions, devicepresetof, familyofkind, locationconsentcovers, locationpresetof, locationrangevalid, networkpresetof, permissiongrantof, permissiongrade, permissionstates, revertplanof } from \"./emulation.js\";\nimport { autointervalof, importsessionfile, restoreplanof, searchqueryof, sessionkinds, snapshotplanof } from \"./sessions.js\";\nimport { composeworkflow, expressionof, expressionoperators, regexruleof, steptemplateof, validateworkflow, workflowblockof, workflowstepof } from \"./workflow.js\";\nimport { branchof, conditionof, controlsteps, foreachof, iscontrolflowkind, loopof, parallelof, repeatuntilof, tryof, whileof } from \"./controlflow.js\";\nimport { armrule, cronparse, triggerfamilyof, triggerpayloadof, triggereventcatalog, webhooksecretok } from \"./trigger.js\";\nimport { formpayloadof, multipartpayloadof, oauthflowof } from \"./netauth.js\";\nimport { loglevels, timelinesources } from \"./runtimeline.js\";\nimport { bucketboundsvalid, origincheckof } from \"./inboundguard.js\";\nimport { gatekindfor } from \"./confirmgates.js\";\nimport { phishthresholdvalid } from \"./phishguard.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\", \"openlink\", \"openprivate\", \"reloadcache\", \"stopnav\", \"followlink\", \"spanav\", \"rewritequery\", \"setfragment\", \"navlist\", \"navprofile\", \"handleauth\", \"printpdf\", \"prefetch\", \"preconnect\", \"deeplink\", \"reopentab\", \"pausenav\", \"navrate\", \"openclipboard\", \"batchopen\", \"duplicatetab\", \"closepattern\", \"pintab\", \"mutetab\", \"movetab\", \"movetabwindow\", \"grouptabs\", \"colorgroup\", \"collapsegroup\", \"discardtab\", \"reloadtabs\", \"zoomin\", \"zoomout\", \"switchtab\", \"maximizewindow\", \"minimizewindow\", \"restorewindow\", \"focuswindow\", \"scratchwindow\", \"incognitowindow\", \"restoretab\", \"restorelayout\", \"reopenrun\", \"badgetab\", \"fillform\", \"filllabel\", \"fillplaceholder\", \"submitform\", \"retryform\", \"runwizard\", \"selectchain\", \"picktypeahead\", \"pickdate\", \"attachfile\", \"fillcard\", \"fillcode\", \"consentpassword\", \"exportcsv\", \"exportjson\", \"exportexcel\", \"copytable\", \"pushsheets\", \"streamdisk\", \"paginateextract\", \"resumeextract\", \"batchdownload\", \"pausedownload\", \"resumedownload\", \"interceptmime\", \"readclipboard\", \"writeclipboard\", \"copyscreen\", \"quarantinedownload\", \"scanvirus\", \"cleanupartifacts\", \"recordscreen\", \"captureaudio\", \"downloadimages\", \"callrest\", \"callgraphql\", \"sendmessage\", \"blockrequest\", \"mockresponse\", \"rewriteheaders\", \"setcookies\", \"clearcookies\", \"authflow\", \"saveapikey\", \"routeproxy\", \"postform\", \"postfiles\", \"attachcdp\", \"detachcdp\", \"cdpcmd\", \"overridescript\", \"heapshot\", \"profilecpu\", \"capturesourcemaps\", \"emulatedevice\", \"emulatenetwork\", \"emulatelocate\", \"setuseragent\", \"overridepermission\", \"restoresession\", \"exportsessions\", \"importsessions\", \"runworkflow\", \"visitrule\", \"urlrule\", \"menurule\", \"keyrule\", \"buttonrule\", \"cronrule\", \"intervalrule\", \"urllistrule\", \"webhookrule\", \"eventrule\"]);\nconst interactionactions = new Set<actionkind>([\"focus\", \"scroll\", \"hover\", \"clickdeep\", \"rightclick\", \"doubleclick\", \"scrollpage\", \"scrollby\", \"scrollend\", \"scrolltop\", \"fullscreen\", \"zoomset\", \"movepointer\", \"clicktext\", \"clickaria\", \"clickname\", \"expanddetails\", \"pierceshadow\", \"retryaction\", \"capturebodies\", \"setbreakpoint\", \"stepcode\", \"watchexpr\", \"loop\", \"repeatuntil\", \"whileloop\", \"foreach\", \"parallel\", \"trycatch\"]);\nconst readactions = new Set<actionkind>([\"observe\", \"inspect\", \"extract\", \"wait\", \"waitfor\", \"waittext\", \"readattribute\", \"readstyle\", \"readgeometry\", \"readvalue\", \"readtext\", \"readhtml\", \"countelements\", \"readtable\", \"readlinks\", \"readimages\", \"readmeta\", \"readforms\", \"readstorage\", \"highlight\", \"tablist\", \"windowlist\", \"tabsnapshot\", \"mapclicks\", \"verifyvisible\", \"verifyenabled\", \"resolvexpath\", \"a11ytree\", \"readvisible\", \"readertree\", \"detectlists\", \"detecttables\", \"readjson\", \"watchmutate\", \"waitquiet\", \"watchbanner\", \"detectinfinitescroll\", \"detectvirtual\", \"detectlazy\", \"readscrollpos\", \"readlang\", \"readoutline\", \"countpages\", \"listshadow\", \"listframes\", \"classifypage\", \"fingerprintsection\", \"diffsnapshots\", \"readselection\", \"watchfocus\", \"detectsticky\", \"detectscrolllock\", \"readopengraph\", \"detectlanguage\", \"deriveselector\", \"waitload\", \"waiturl\", \"spawait\", \"detecthttp\", \"readredirects\", \"readfinalurl\", \"trailaudit\", \"navintent\", \"checksafe\", \"querytabs\", \"watchtab\", \"findclones\", \"searchtabs\", \"listaudio\", \"snapshotsession\", \"savelayout\", \"attachmeta\", \"detectfields\", \"generatevalues\", \"saveprofiles\", \"asksubmit\", \"readerrors\", \"skiphoneypot\", \"detectlogin\", \"detecttemplate\", \"handoffcaptcha\", \"scrapetable\", \"importcsv\", \"looprows\", \"transformvalues\", \"deduperows\", \"mergepages\", \"stamplerows\", \"previewgrid\", \"logprovenance\", \"verifydownload\", \"exportnetlog\", \"namecaptures\", \"shotview\", \"shotfullpage\", \"shotelement\", \"shotregion\", \"contactsheet\", \"capturepdf\", \"captureframe\", \"readmedia\", \"readassets\", \"probestream\", \"timelapse\", \"shotcanvas\", \"convertimage\", \"makethumbs\", \"fetchurl\", \"parsejson\", \"parsehtml\", \"opensocket\", \"waitmessage\", \"watchrequests\", \"readheaders\", \"mapapi\", \"subscribesse\", \"longpoll\", \"extractapi\", \"readcookies\", \"watchconsole\", \"watcherrors\", \"watchtasks\", \"watchcdp\", \"measureflow\", \"trackmemory\", \"watchshifts\", \"traceload\", \"annotatetrace\", \"replaytrace\", \"blackboxscripts\", \"persiststate\", \"capturesession\", \"namedsessions\", \"diffsessions\", \"searchsessions\", \"composeworkflow\", \"savetemplate\", \"dryrun\", \"delay\", \"waitelement\", \"compute\", \"extractvars\", \"listruns\", \"condition\", \"branch\"]);\nconst allowedactions = new Set<actionkind>([...sensitiveactions, ...interactionactions, ...readactions]);\nconst watchactions = new Set<actionkind>([\"watchmutate\", \"watchbanner\", \"watchfocus\", \"watchtab\"]);\nconst targetactions = new Set<actionkind>([\"inspect\", \"focus\", \"click\", \"type\", \"scroll\", \"select\", \"hover\", \"clickdeep\", \"rightclick\", \"doubleclick\", \"drag\", \"drop\", \"upload\", \"clear\", \"check\", \"uncheck\", \"toggle\", \"submit\", \"readattribute\", \"readstyle\", \"readgeometry\", \"readvalue\", \"readtext\", \"readhtml\", \"countelements\", \"readtable\", \"highlight\", \"setattribute\", \"removeattribute\", \"waitfor\", \"shiftclick\", \"typetime\", \"appendtext\", \"setvalue\", \"typeedit\", \"submitsearch\", \"selectmulti\", \"chooseradio\", \"setslider\", \"setdate\", \"setcolor\", \"expanddetails\", \"verifyvisible\", \"verifyenabled\", \"pierceshadow\", \"deriveselector\", \"fingerprintsection\", \"submitform\", \"retryform\", \"selectchain\", \"picktypeahead\", \"pickdate\", \"attachfile\", \"fillcode\", \"consentpassword\", \"scrapetable\", \"paginateextract\", \"shotelement\", \"captureframe\", \"shotcanvas\"]);\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\", \"followlink\", \"setfragment\", \"handleauth\", \"navintent\", \"openclipboard\", \"checksafe\", \"reopentab\", \"spanav\", \"duplicatetab\", \"pintab\", \"mutetab\", \"movetab\", \"movetabwindow\", \"searchtabs\", \"badgetab\", \"attachmeta\", \"focuswindow\", \"maximizewindow\", \"minimizewindow\", \"restorewindow\", \"incognitowindow\", \"asksubmit\", \"selectchain\", \"picktypeahead\", \"pickdate\", \"attachfile\", \"fillcode\", \"consentpassword\", \"pausedownload\", \"resumedownload\", \"verifydownload\", \"writeclipboard\", \"quarantinedownload\", \"scanvirus\"]);\nconst tabscommandactions = new Set<actionkind>([\"querytabs\", \"duplicatetab\", \"closepattern\", \"pintab\", \"mutetab\", \"movetab\", \"movetabwindow\", \"grouptabs\", \"colorgroup\", \"collapsegroup\", \"discardtab\", \"reloadtabs\", \"zoomin\", \"zoomout\", \"watchtab\", \"switchtab\", \"maximizewindow\", \"minimizewindow\", \"restorewindow\", \"focuswindow\", \"scratchwindow\", \"incognitowindow\", \"restoretab\", \"savelayout\", \"restorelayout\", \"findclones\", \"searchtabs\", \"badgetab\", \"attachmeta\", \"listaudio\", \"reopenrun\", \"snapshotsession\"]);\nconst formactions = new Set<actionkind>([\"fillform\", \"filllabel\", \"fillplaceholder\", \"detectfields\", \"generatevalues\", \"saveprofiles\", \"asksubmit\", \"submitform\", \"readerrors\", \"retryform\", \"runwizard\", \"selectchain\", \"picktypeahead\", \"pickdate\", \"attachfile\", \"handoffcaptcha\", \"fillcard\", \"fillcode\", \"consentpassword\", \"skiphoneypot\", \"detectlogin\", \"detecttemplate\"]);\n/** Extraction, transform, export and provenance kinds of the forms and data part two family. */\nconst datasetactions = new Set<actionkind>([\"scrapetable\", \"exportcsv\", \"exportjson\", \"exportexcel\", \"copytable\", \"pushsheets\", \"importcsv\", \"looprows\", \"transformvalues\", \"deduperows\", \"paginateextract\", \"mergepages\", \"stamplerows\", \"previewgrid\", \"streamdisk\", \"resumeextract\", \"logprovenance\"]);\n/** Export kinds that move extracted data out of local memory to disk, the clipboard or a reviewed sheet endpoint. */\nconst exportactions = new Set<actionkind>([\"exportcsv\", \"exportjson\", \"exportexcel\", \"copytable\", \"pushsheets\", \"streamdisk\"]);\n/** Files, clipboard and downloads kinds of the batch queue, interception, clipboard, quarantine, naming and cleanup family. */\nconst filesactions = new Set<actionkind>([\"batchdownload\", \"pausedownload\", \"resumedownload\", \"verifydownload\", \"interceptmime\", \"exportnetlog\", \"readclipboard\", \"writeclipboard\", \"copyscreen\", \"quarantinedownload\", \"scanvirus\", \"namecaptures\", \"cleanupartifacts\"]);\nconst captureactions = new Set<actionkind>([\"shotview\", \"shotfullpage\", \"shotelement\", \"shotregion\", \"contactsheet\"]);\n/** Media capture part two kinds of the pdf, recording, image, canvas, stream, asset, lapse, conversion and thumbnail family. */\nconst mediaactions = new Set<actionkind>([\"capturepdf\", \"recordscreen\", \"captureaudio\", \"captureframe\", \"downloadimages\", \"shotcanvas\", \"probestream\", \"readmedia\", \"readassets\", \"timelapse\", \"convertimage\", \"makethumbs\"]);\n\nconst httpactions = new Set<actionkind>([\"fetchurl\", \"parsejson\", \"parsehtml\", \"callrest\", \"callgraphql\"]);\n\nconst socketactions = new Set<actionkind>([\"opensocket\", \"sendmessage\", \"waitmessage\", \"subscribesse\", \"longpoll\"]);\n\nconst netwatchactions = new Set<actionkind>([\"watchrequests\", \"readheaders\", \"capturebodies\", \"mapapi\", \"extractapi\"]);\n\n/** Network control kinds of the 1.1.44 family: blocking, mocking, header rewriting, cookies, auth, api keys, proxy routing and uploads. */\nconst controlactions = new Set<actionkind>([\"blockrequest\", \"mockresponse\", \"rewriteheaders\", \"setcookies\", \"readcookies\", \"clearcookies\", \"authflow\", \"saveapikey\", \"routeproxy\", \"postform\", \"postfiles\"]);\n\n/** Debugging kinds of the 1.1.45 family: console, error and task watching stays read only timeline capture. */\nconst debugactions = new Set<actionkind>([\"watchconsole\", \"watcherrors\", \"watchtasks\"]);\n\n/** The devtools protocol kinds of the 1.1.46 debugging family: attach, detach, raw commands, event watches, breakpoints, stepping, watch expressions and script overrides. */\nconst cdpactions = new Set<actionkind>([\"attachcdp\", \"detachcdp\", \"cdpcmd\", \"watchcdp\", \"setbreakpoint\", \"stepcode\", \"watchexpr\", \"overridescript\"]);\n\n/** The profiling kinds of the 1.1.47 debugging part three family: flow measurement, heap snapshots, memory growth tracking, cpu profiles, layout shift watches, trace records, trace annotation, offline trace replay and source map capture. */\nconst profileractions = new Set<actionkind>([\"measureflow\", \"heapshot\", \"trackmemory\", \"profilecpu\", \"watchshifts\", \"traceload\", \"annotatetrace\", \"replaytrace\", \"capturesourcemaps\"]);\n\nconst emulationactions = new Set<actionkind>([\"emulatedevice\", \"emulatenetwork\", \"emulatelocate\", \"setuseragent\", \"overridepermission\", \"blackboxscripts\"]);\n\n/** The session memory kinds of the 1.1.49 family: task state persistence, session capture, restore, naming, diffing, search, export and import. */\nconst sessionactions = new Set<actionkind>([\"persiststate\", \"capturesession\", \"restoresession\", \"namedsessions\", \"diffsessions\", \"searchsessions\", \"exportsessions\", \"importsessions\"]);\n\n/** The workflow kinds of the 1.1.50 and 1.1.51 families: composition, templates, runs, dry runs, jittered delays, element waits, expressions, variable extraction, and the control flow family of conditionals, branching, loops, parallel branches with joins and try catch with retries and timeouts. */\nconst workflowactions = new Set<actionkind>([\"composeworkflow\", \"savetemplate\", \"runworkflow\", \"dryrun\", \"delay\", \"waitelement\", \"compute\", \"extractvars\", \"condition\", \"branch\", \"loop\", \"repeatuntil\", \"whileloop\", \"foreach\", \"parallel\", \"trycatch\"]);\n\n/** The trigger kinds of the 1.1.52 family: page visit, url pattern, context menu, keyboard shortcut, toolbar button, cron, interval, url list, webhook and page event rules that launch reviewed workflows; every rule arms behind the explicit arm review and grades sensitive because it launches runs automatically. */\nconst triggeractions = new Set<actionkind>([\"visitrule\", \"urlrule\", \"menurule\", \"keyrule\", \"buttonrule\", \"cronrule\", \"intervalrule\", \"urllistrule\", \"webhookrule\", \"eventrule\"]);\n\n/** Header names that carry credentials; sending any of them needs the explicit consent that names the header. */\nconst credentialheaders = new Set([\"authorization\", \"proxy-authorization\", \"cookie\", \"cookie2\", \"set-cookie\", \"api-key\", \"x-api-key\", \"x-auth-token\", \"x-session-token\", \"proxy-authorization\"]);\n/** Field kinds the form grammar accepts inside records, profiles and value rules. */\nconst fieldkinds: fieldkind[] = [\"text\", \"email\", \"phone\", \"date\", \"number\", \"select\", \"check\", \"radio\", \"file\", \"password\", \"card\", \"code\"];\nconst layoutmutationactions = new Set<actionkind>([\"grouptabs\", \"colorgroup\", \"collapsegroup\", \"savelayout\", \"restorelayout\"]);\n/** Chromium tab group colors accepted as reviewed group color choices. */\nconst groupcolors = [\"grey\", \"blue\", \"red\", \"yellow\", \"green\", \"pink\", \"purple\", \"cyan\", \"orange\"];\n\n/** Normalizes a user supplied HTTPS endpoint without preserving a provider lock-in. */\nexport function normalizeendpoint(value: string): endpointconfig {\n const endpoint = new URL(value.trim());\n if (endpoint.protocol !== \"https:\") throw new Error(\"Devthink accepts HTTPS endpoints only.\");\n if (endpoint.username || endpoint.password) throw new Error(\"Endpoint credentials are not allowed in the URL.\");\n return { endpoint: endpoint.toString(), origin: endpoint.origin, configuredat: Date.now() };\n}\n\n/** Creates the exact optional host pattern requested from Chromium. */\nexport function hostpattern(origin: string): string {\n const parsed = new URL(origin);\n if (parsed.protocol !== \"https:\") throw new Error(\"Only HTTPS origins can be granted.\");\n return `${parsed.origin}/*`;\n}\n\n/** True when the kind belongs to the session memory family of the 1.1.49 release. */\nexport function issessionkind(kind: actionkind): boolean {\n return sessionactions.has(kind);\n}\n\n/** True when the kind belongs to the workflow family of the 1.1.50 release. */\nexport function isworkflowkind(kind: actionkind): boolean {\n return workflowactions.has(kind);\n}\n\n/** True when the kind belongs to the trigger family of the 1.1.52 release: every trigger kind arms an automatic launcher and needs the explicit arm review. */\nexport function istriggeraction(kind: actionkind): boolean {\n return triggeractions.has(kind);\n}\n\n/** True when the action kind observes the page over a reviewed lifetime window. */\nexport function iswatchkind(kind: actionkind): boolean {\n return watchactions.has(kind);\n}\n\n/** True when the kind belongs to the debugging family of console, error and task watching. */\nexport function isdebugkind(kind: actionkind): boolean {\n return debugactions.has(kind);\n}\n\n/** True when the kind belongs to the devtools protocol family of attaches, raw commands, event watches, breakpoints, stepping, watch expressions and script overrides. */\nexport function iscdpkind(kind: actionkind): boolean {\n return cdpactions.has(kind);\n}\n\n/** True when the kind belongs to the profiling family of flow, heap, cpu, shift, trace and source map instruments. */\nexport function isprofilekind(kind: actionkind): boolean {\n return profileractions.has(kind);\n}\n\n/** True when the kind belongs to the emulation family of device, network, location, agent and permission layers plus blackbox trace shaping. */\nexport function isemulationkind(kind: actionkind): boolean {\n return emulationactions.has(kind);\n}\n\n/** Grades the observation mode of a kind: passive capture, watched lifetimes or diffing passes. */\nexport function observationmodeof(kind: actionkind): observationmode {\n if (watchactions.has(kind) || debugactions.has(kind) || profileractions.has(kind) && kind !== \"heapshot\" && kind !== \"replaytrace\" && kind !== \"annotatetrace\" && kind !== \"capturesourcemaps\" && kind !== \"profilecpu\" || cdpactions.has(kind) && kind === \"watchcdp\" || kind === \"waitquiet\") return \"watching\";\n if (kind === \"diffsnapshots\") return \"diffing\";\n return \"passive\";\n}\n\n/** Defines action risk from the fixed local allowlist. */\nexport function actionrisk(kind: actionkind): \"read\" | \"interaction\" | \"sensitive\" {\n if (!allowedactions.has(kind)) throw new Error(\"Unsupported browser action.\");\n if (sensitiveactions.has(kind)) return \"sensitive\";\n return interactionactions.has(kind) ? \"interaction\" : \"read\";\n}\n\n/** True when the action kind accepts a css selector target or a reviewed targetref. */\nexport function needstarget(kind: actionkind): boolean {\n return targetactions.has(kind);\n}\n\n/** Parses the reviewed JSON options of a step; malformed payloads are rejected early. */\nexport function parseoptions(step: toolstep): Record<string, unknown> {\n if (step.options === undefined) return {};\n let parsed: unknown;\n try { parsed = JSON.parse(step.options); } catch { throw new Error(\"Step options must be a JSON object.\"); }\n if (!parsed || typeof parsed !== \"object\" || Array.isArray(parsed)) throw new Error(\"Step options must be a JSON object.\");\n return parsed as Record<string, unknown>;\n}\n\n/** Maps an action kind to the optional browser permission it requires, if any. */\nexport function requiredcapability(kind: actionkind): string | undefined {\n if (kind === \"tablist\") return \"tabs\";\n if (kind === \"downloadfile\") return \"downloads\";\n if (kind === \"openclipboard\") return \"clipboardRead\";\n if (kind === \"copytable\") return \"clipboardWrite\";\n if (kind === \"batchdownload\" || kind === \"pausedownload\" || kind === \"resumedownload\" || kind === \"verifydownload\" || kind === \"interceptmime\" || kind === \"quarantinedownload\" || kind === \"scanvirus\") return \"downloads\";\n if (kind === \"readclipboard\") return \"clipboardRead\";\n if (kind === \"writeclipboard\" || kind === \"copyscreen\") return \"clipboardWrite\";\n if (kind === \"downloadimages\") return \"downloads\";\n if (kind === \"authflow\") return \"tabs\";\n if (kind === \"capturesession\" || kind === \"restoresession\") return \"tabs\";\n if (kind === \"exportsessions\") return \"downloads\";\n if (kind === \"openlink\" || kind === \"openprivate\" || kind === \"navlist\" || kind === \"batchopen\" || kind === \"reopentab\" || kind === \"deeplink\") return \"tabs\";\n if (tabscommandactions.has(kind)) return \"tabs\";\n return undefined;\n}\n\n/** True when the kind commands tabs or windows beyond the active tab and needs the optional tabs capability. */\nexport function istabscommandkind(kind: actionkind): boolean {\n return tabscommandactions.has(kind);\n}\n\n/** True when the kind mutates tab groups or layouts and therefore stays inside the active session. */\nexport function islayoutkind(kind: actionkind): boolean {\n return layoutmutationactions.has(kind);\n}\n\n/** True when the kind belongs to the forms and data family. */\nexport function isformkind(kind: actionkind): boolean {\n return formactions.has(kind);\n}\n\n/** True when the kind belongs to the extraction, transform, export and provenance family. */\nexport function isdatasetkind(kind: actionkind): boolean {\n return datasetactions.has(kind);\n}\n\n/** True when the kind exports extracted data out of local memory to disk, the clipboard or a reviewed sheet endpoint. */\nexport function isexportkind(kind: actionkind): boolean {\n return exportactions.has(kind);\n}\n\n/** True when the kind belongs to the files, clipboard and downloads family. */\nexport function isfileskind(kind: actionkind): boolean {\n return filesactions.has(kind);\n}\n\n/** True when the kind belongs to the media capture family of viewport, full page, element, region and contact sheet shots. */\nexport function iscapturekind(kind: actionkind): boolean {\n return captureactions.has(kind);\n}\n\n/** Requires the active tab grant of the live session before any capture kind runs: the session tab and origin must match and the origin grant must cover the active origin. */\nexport function capturegate(session: agentsession | undefined, tabid: number, origin: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the capture.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot capture.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot capture.\" };\n if (session.tabid !== tabid) return { allowed: false, reason: `The capture needs the active tab grant of session tab ${session.tabid} and refuses tab ${tabid}.` };\n if (!origingranted(session, origin)) return { allowed: false, reason: `The capture of ${origin} needs the session origin grants first.` };\n return { allowed: true };\n}\n\n/** Validates one reviewed capture options payload: format inside the png, jpeg and webp set, quality bounded only by the format range, pixel ratio from one up with no code ceiling, and a known export target. */\nexport function validatecaptureoptions(value: unknown): policyevaluation {\n if (value === undefined) return { allowed: true };\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"The reviewed capture options must be an object in options.capture.\" };\n const options = value as Record<string, unknown>;\n if (options.format !== undefined && options.format !== \"png\" && options.format !== \"jpeg\" && options.format !== \"webp\") return { allowed: false, reason: \"The reviewed capture format must be png, jpeg or webp.\" };\n if (options.quality !== undefined && (typeof options.quality !== \"number\" || !Number.isFinite(options.quality) || options.quality < 0 || options.quality > 100)) return { allowed: false, reason: \"The reviewed capture quality must stay between zero and one hundred; any value in that range is the user choice with no code cap.\" };\n if (options.pixelratio !== undefined && (typeof options.pixelratio !== \"number\" || !Number.isFinite(options.pixelratio) || options.pixelratio < 1)) return { allowed: false, reason: \"The reviewed pixel ratio starts at one and climbs to any user configured ceiling with no code ceiling.\" };\n if (options.annotate !== undefined && typeof options.annotate !== \"boolean\") return { allowed: false, reason: \"The reviewed capture annotation flag must be a boolean.\" };\n if (options.exporttarget !== undefined && options.exporttarget !== \"memory\" && options.exporttarget !== \"download\" && options.exporttarget !== \"clipboard\") return { allowed: false, reason: \"The reviewed capture export target must be memory, download or clipboard.\" };\n return { allowed: true };\n}\n\n/** Validates one reviewed region rectangle in css pixels; negative coordinates and non positive sizes are refused. */\nexport function validateregionrect(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed regionrect with x, y, width and height in css pixels is required in options.\" };\n const rect = value as Record<string, unknown>;\n for (const field of [\"x\", \"y\", \"width\", \"height\"]) {\n if (typeof rect[field] !== \"number\" || !Number.isFinite(rect[field] as number)) return { allowed: false, reason: `The reviewed regionrect needs a numeric ${field} in css pixels.` };\n }\n if ((rect.x as number) < 0 || (rect.y as number) < 0) return { allowed: false, reason: \"The reviewed regionrect refuses negative coordinates.\" };\n if ((rect.width as number) <= 0 || (rect.height as number) <= 0) return { allowed: false, reason: \"The reviewed regionrect needs positive width and height values.\" };\n return { allowed: true };\n}\n\n/** Validates one reviewed capture naming rule against the allowed segment set: run, step, sequence and kind flags only. */\nexport function validatecapturenaming(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed capturenaming rule with run, step, sequence and kind flags is required.\" };\n const rule = value as Record<string, unknown>;\n const segments = [\"run\", \"step\", \"sequence\", \"kind\"];\n for (const key of Object.keys(rule)) {\n if (!segments.includes(key)) return { allowed: false, reason: `The reviewed capturenaming rule refuses the unknown ${key} segment; only run, step, sequence and kind participate.` };\n }\n for (const segment of segments) {\n if (rule[segment] !== undefined && typeof rule[segment] !== \"boolean\") return { allowed: false, reason: `The reviewed capturenaming ${segment} flag must be a boolean.` };\n }\n if (!segments.some(segment => rule[segment] === true)) return { allowed: false, reason: \"The reviewed capturenaming rule needs at least one enabled segment of run, step, sequence and kind.\" };\n return { allowed: true };\n}\n\n/** Routes the capture export target: memory stays local, clipboard needs the clipboardwrite grant and disk writes only run through the reviewed download flow. */\nexport function captureexportgranted(target: captureexport | undefined): policyevaluation {\n if (target === undefined || target === \"memory\") return { allowed: true };\n if (target === \"clipboard\") return { allowed: true, reason: \"The clipboard capture export runs behind the optional clipboardwrite capability, negotiated through the permissions api before the copy.\" };\n if (target === \"download\") return { allowed: true, reason: \"The download capture export runs only through the reviewed download flow behind the optional downloads capability.\" };\n return { allowed: false, reason: \"The capture export target must be memory, download or clipboard; no other disk route exists.\" };\n}\n\n/** Keeps the stitching scroll budget inside the reviewed wait window: the settle windows of every tile must fit the reviewed wait window with no code ceiling on either side. */\nexport function stitchbudgetallowed(tiles: number, settle: number, wait: number): policyevaluation {\n if (tiles <= 0) return { allowed: false, reason: \"The stitch budget needs at least one tile.\" };\n if (settle < 0 || wait < 0) return { allowed: false, reason: \"The reviewed settle and wait windows must be zero or positive milliseconds.\" };\n if (tiles * settle > wait) return { allowed: false, reason: `The stitching scroll budget of ${tiles} tiles at ${settle} milliseconds exceeds the reviewed wait window of ${wait} milliseconds; review a wider window or a smaller settle.` };\n return { allowed: true };\n}\n\n/** Allows beforeafter state capture to wrap any existing action kind except the capture kinds themselves; pixel evidence around sensitive actions grades as reviewable evidence. */\nexport function beforeafterwrapallowed(kind: actionkind): boolean {\n return allowedactions.has(kind) && !captureactions.has(kind);\n}\n\n/** Exposes the capture retention window as a user configured choice; an absent value keeps every capture byte forever with no code ceiling. */\nexport function captureretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.captureretention;\n}\n\n/** Validates the reviewed media capture parameter grammar; pixel ratios, quality values, cell counts and retention windows stay user choices with no code ceilings. */\nfunction validatecapturegrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n const optioncheck = validatecaptureoptions(options.capture);\n if (!optioncheck.allowed) return optioncheck;\n if (options.settle !== undefined && (typeof options.settle !== \"number\" || !Number.isFinite(options.settle) || options.settle < 0)) return { allowed: false, reason: \"The reviewed capture settle window must be zero or a positive number of milliseconds.\" };\n if (options.overlap !== undefined && (typeof options.overlap !== \"number\" || !Number.isInteger(options.overlap) || options.overlap < 0)) return { allowed: false, reason: \"The reviewed stitch overlap must be zero or a positive number of rows.\" };\n if (options.wait !== undefined && (typeof options.wait !== \"number\" || !Number.isFinite(options.wait) || options.wait < 0)) return { allowed: false, reason: \"The reviewed capture wait window must be zero or a positive number of milliseconds.\" };\n if (options.naming !== undefined) {\n const namingcheck = validatecapturenaming(options.naming);\n if (!namingcheck.allowed) return namingcheck;\n }\n if (kind === \"shotregion\") {\n const rectcheck = validateregionrect(options.regionrect);\n if (!rectcheck.allowed) return rectcheck;\n if (options.reviewed !== true) return { allowed: false, reason: \"Every reviewed regionrect needs the explicit reviewed flag before shotregion runs.\" };\n if (options.container !== undefined && !isnonempty(options.container)) return { allowed: false, reason: \"The reviewed scrollable container selector must be a non-empty string.\" };\n if (options.steps !== undefined && (typeof options.steps !== \"number\" || !Number.isInteger(options.steps) || options.steps < 1)) return { allowed: false, reason: \"The reviewed container scroll steps must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"contactsheet\") {\n const elements = options.elements;\n if (!Array.isArray(elements) || elements.length === 0 || !elements.every(item => isnonempty(item))) return { allowed: false, reason: \"A reviewed non-empty list of element selectors is required in options for the contact sheet; the cell count stays the user choice.\" };\n const layout = options.sheet;\n if (layout !== undefined) {\n if (!layout || typeof layout !== \"object\" || Array.isArray(layout)) return { allowed: false, reason: \"The reviewed sheetlayout must be an object with cellsize, columns and label.\" };\n const sheet = layout as Record<string, unknown>;\n if (typeof sheet.cellsize !== \"number\" || !Number.isFinite(sheet.cellsize) || sheet.cellsize <= 0) return { allowed: false, reason: \"The reviewed contact sheet cell size must be a positive number of pixels.\" };\n if (typeof sheet.columns !== \"number\" || !Number.isInteger(sheet.columns) || sheet.columns < 1) return { allowed: false, reason: \"The reviewed contact sheet column count must be a positive integer with no code ceiling.\" };\n if (sheet.label !== undefined && sheet.label !== \"none\" && sheet.label !== \"index\" && sheet.label !== \"selector\" && sheet.label !== \"both\") return { allowed: false, reason: \"The reviewed contact sheet label style must be none, index, selector or both.\" };\n }\n }\n return { allowed: true };\n}\n\n/** Refuses any export that would leave local memory while the session origin grants do not cover the active origin. */\nexport function exportgranted(session: agentsession | undefined, origin: string): policyevaluation {\n if (!origingranted(session, origin)) return { allowed: false, reason: `The export of extracted data from ${origin} needs the session origin grants before it leaves local memory.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed fieldmatch grammar of one form field entry. */\nexport function validatefieldmatch(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed field match is required in options.\" };\n const match = value as Record<string, unknown>;\n if (match.mode !== \"label\" && match.mode !== \"placeholder\" && match.mode !== \"arialabel\" && match.mode !== \"name\") return { allowed: false, reason: \"The reviewed field match mode must be label, placeholder, arialabel or name.\" };\n const key = match.mode === \"label\" ? \"label\" : match.mode === \"placeholder\" ? \"placeholder\" : match.mode === \"arialabel\" ? \"arialabel\" : \"name\";\n if (!isnonempty(match[key])) return { allowed: false, reason: `The reviewed ${match.mode} field match needs a non-empty ${key}.` };\n return { allowed: true };\n}\n\n/** Validates a reviewed structured form record; password entries are refused because passwords need the explicit consentpassword consent. */\nexport function validateformrecord(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed form record with entries is required in options.\" };\n const record = value as Record<string, unknown>;\n if (record.form !== undefined && !isnonempty(record.form)) return { allowed: false, reason: \"The reviewed form record form selector must be a non-empty string.\" };\n if (!Array.isArray(record.entries) || record.entries.length === 0) return { allowed: false, reason: \"The reviewed form record needs a non-empty list of entries.\" };\n for (const item of record.entries) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every reviewed form record entry must be an object.\" };\n const entry = item as Record<string, unknown>;\n const matchcheck = validatefieldmatch(entry.match);\n if (!matchcheck.allowed) return matchcheck;\n if (typeof entry.kind !== \"string\" || !fieldkinds.includes(entry.kind as fieldkind)) return { allowed: false, reason: \"Every reviewed form record entry needs a known field kind.\" };\n if (typeof entry.value !== \"string\") return { allowed: false, reason: \"Every reviewed form record entry needs a string value.\" };\n if (entry.kind === \"password\") return { allowed: false, reason: \"Password entries are refused inside form records; use consentpassword with a reviewed consent ref.\" };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed valuegen grammar of a generatevalues step. */\nexport function validatevaluegen(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed valuegen rule with a field kind is required in options.\" };\n const rule = value as Record<string, unknown>;\n if (typeof rule.kind !== \"string\" || !fieldkinds.includes(rule.kind as fieldkind)) return { allowed: false, reason: \"The reviewed valuegen kind must be a known field kind.\" };\n if (rule.locale !== undefined && !isnonempty(rule.locale)) return { allowed: false, reason: \"The reviewed valuegen locale must be a non-empty string.\" };\n if (rule.seed !== undefined && (typeof rule.seed !== \"number\" || !Number.isFinite(rule.seed))) return { allowed: false, reason: \"The reviewed valuegen seed must be a finite number.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed list of label or placeholder value pairs for filllabel and fillplaceholder steps. */\nfunction validatefieldpairs(options: Record<string, unknown>, mode: \"label\" | \"placeholder\"): policyevaluation {\n const pairs = options.fields;\n if (!Array.isArray(pairs) || pairs.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of field pairs is required in options.\" };\n for (const item of pairs) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every reviewed field pair must be an object.\" };\n const pair = item as Record<string, unknown>;\n if (!isnonempty(pair[mode])) return { allowed: false, reason: `Every reviewed field pair needs a non-empty ${mode}.` };\n if (typeof pair.value !== \"string\" || !pair.value.trim()) return { allowed: false, reason: \"Every reviewed field pair needs a non-empty value.\" };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed card segment grammar of a fillcard step. */\nfunction validatecardsegments(value: unknown): policyevaluation {\n if (!Array.isArray(value) || value.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of card segments is required in options.\" };\n for (const item of value) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every reviewed card segment must be an object.\" };\n const segment = item as Record<string, unknown>;\n const matchcheck = validatefieldmatch(segment.match);\n if (!matchcheck.allowed) return matchcheck;\n if (typeof segment.value !== \"string\" || !segment.value.trim()) return { allowed: false, reason: \"Every reviewed card segment needs a non-empty value.\" };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed forms and data parameter grammar of the form family. */\nfunction validateformgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"fillform\" || (kind === \"saveprofiles\" && options.formrecord !== undefined)) {\n const recordcheck = validateformrecord(options.formrecord);\n if (!recordcheck.allowed) return recordcheck;\n }\n if (kind === \"filllabel\" || kind === \"fillplaceholder\") {\n const paircheck = validatefieldpairs(options, kind === \"filllabel\" ? \"label\" : \"placeholder\");\n if (!paircheck.allowed) return paircheck;\n }\n if (kind === \"generatevalues\" && options.valuegen !== undefined) {\n const rulecheck = validatevaluegen(options.valuegen);\n if (!rulecheck.allowed) return rulecheck;\n }\n if (kind === \"saveprofiles\" && !isnonempty(options.name)) return { allowed: false, reason: \"A reviewed profile name is required in options.\" };\n if (kind === \"submitform\" && !isnonempty(options.consentref)) return { allowed: false, reason: \"A reviewed consent ref of an approved asksubmit ticket is required in options.\" };\n if (kind === \"retryform\") {\n const backoff = options.backoff;\n if (!backoff || typeof backoff !== \"object\" || Array.isArray(backoff)) return { allowed: false, reason: \"A reviewed backoff rule with wait and factor is required in options.\" };\n const rule = backoff as Record<string, unknown>;\n if (typeof rule.wait !== \"number\" || !Number.isFinite(rule.wait) || rule.wait <= 0) return { allowed: false, reason: \"The reviewed retry backoff wait must be a positive number of milliseconds with no code ceiling.\" };\n if (typeof rule.factor !== \"number\" || !Number.isFinite(rule.factor) || rule.factor < 1) return { allowed: false, reason: \"The reviewed retry backoff factor must be one or greater with no code ceiling.\" };\n if (options.attempts !== undefined && (typeof options.attempts !== \"number\" || !Number.isInteger(options.attempts) || options.attempts < 1)) return { allowed: false, reason: \"The reviewed retry attempts must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"runwizard\" && options.steps !== undefined && (typeof options.steps !== \"number\" || !Number.isInteger(options.steps) || options.steps < 1)) return { allowed: false, reason: \"The reviewed wizard step count must be a positive integer with no code ceiling.\" };\n if (kind === \"selectchain\") {\n if (!isnonempty(options.child)) return { allowed: false, reason: \"A reviewed child selector of the dependent control is required in options.\" };\n if (!nonnegativeoption(options, \"wait\")) return { allowed: false, reason: \"The reviewed dependent wait must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"picktypeahead\") {\n if (!isnonempty(options.pick)) return { allowed: false, reason: \"A reviewed suggestion entry to pick is required in options.\" };\n if (!nonnegativeoption(options, \"timeout\")) return { allowed: false, reason: \"The reviewed typeahead timeout must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"pickdate\" && !/^\\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 (kind === \"fillcard\") {\n const segmentcheck = validatecardsegments(options.segments);\n if (!segmentcheck.allowed) return segmentcheck;\n if (!nonnegativeoption(options, \"pause\")) return { allowed: false, reason: \"The reviewed card typing pause must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"fillcode\" && !isnonempty(options.source)) return { allowed: false, reason: \"A reviewed one time code source is required in options.\" };\n if (kind === \"consentpassword\" && !isnonempty(options.consentref)) return { allowed: false, reason: \"A reviewed consent ref is required in options before any password is filled.\" };\n return { allowed: true };\n}\n\n/** Validates one reviewed transform rule: a supported expression, source columns and a target column. */\nexport function validatetransformrule(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed transform rule with an expression, sources and a target is required in options.\" };\n const rule = value as Record<string, unknown>;\n const expression = rule.expression;\n if (typeof expression !== \"string\" || !/^(trim|upper|lower|number|prefix|suffix|replace)(?::.+)?$/.test(expression)) return { allowed: false, reason: \"The reviewed transform expression must be trim, upper, lower, number, prefix, suffix or replace with an optional argument.\" };\n if (expression.startsWith(\"replace\") && !expression.slice(\"replace\".length).includes(\"=>\")) return { allowed: false, reason: \"The reviewed replace expression needs the from=>to separator.\" };\n if (expression.startsWith(\"replace\") && expression.slice(\"replace:\".length).split(\"=>\")[0] === \"\") return { allowed: false, reason: \"The reviewed replace expression needs a non-empty from part.\" };\n if (!Array.isArray(rule.sources) || rule.sources.length === 0 || !rule.sources.every(source => isnonempty(source))) return { allowed: false, reason: \"Every reviewed transform rule needs a non-empty list of source columns.\" };\n if (!isnonempty(rule.target)) return { allowed: false, reason: \"Every reviewed transform rule needs a non-empty target column.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed dataset id list in options. */\nfunction validatedatasetids(options: Record<string, unknown>, key: string): policyevaluation {\n const ids = options[key];\n if (!Array.isArray(ids) || ids.length === 0 || !ids.every(id => isnonempty(id))) return { allowed: false, reason: `A reviewed non-empty list of dataset ids is required in options as ${key}.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed extraction, transform, export and provenance parameter grammar of the data family. */\nfunction validatedatagrammar(step: toolstep, options: Record<string, unknown>, origin: string): policyevaluation {\n const kind = step.kind;\n if (kind === \"scrapetable\") {\n if (options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed dataset name must be a non-empty string.\" };\n if (options.rowlimit !== undefined && (typeof options.rowlimit !== \"number\" || !Number.isInteger(options.rowlimit) || options.rowlimit < 1)) return { allowed: false, reason: \"The reviewed row limit must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"paginateextract\") {\n if (!isnonempty(options.next)) return { allowed: false, reason: \"A reviewed next control selector is required in options.\" };\n if (options.pages !== undefined && (typeof options.pages !== \"number\" || !Number.isInteger(options.pages) || options.pages < 1)) return { allowed: false, reason: \"The reviewed page count must be a positive integer with no code ceiling.\" };\n if (!nonnegativeoption(options, \"wait\")) return { allowed: false, reason: \"The reviewed row freshness wait must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"exportcsv\" || kind === \"exportjson\" || kind === \"exportexcel\" || kind === \"copytable\" || kind === \"streamdisk\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed artifact name must be a non-empty string.\" };\n }\n if (kind === \"exportcsv\" && options.delimiter !== undefined && (typeof options.delimiter !== \"string\" || options.delimiter.length !== 1)) return { allowed: false, reason: \"The reviewed csv delimiter must be a single character.\" };\n if (kind === \"streamdisk\" && (typeof options.chunk !== \"number\" || !Number.isInteger(options.chunk) || options.chunk < 1)) return { allowed: false, reason: \"The reviewed streaming chunk size must be a positive integer with no code ceiling.\" };\n if (kind === \"pushsheets\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (!isnonempty(options.sheet)) return { allowed: false, reason: \"A reviewed sheet endpoint url is required in options.\" };\n if (!ishttpsurl(options.sheet)) return { allowed: false, reason: \"The reviewed sheet endpoint url must use HTTPS.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"The sheet push needs the explicit reviewed flag before any data leaves local memory.\" };\n }\n if (kind === \"importcsv\") {\n if (typeof options.csv !== \"string\" || !options.csv.trim()) return { allowed: false, reason: \"Reviewed csv content is required in options.\" };\n if (options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed dataset name must be a non-empty string.\" };\n if (options.mapping !== undefined) {\n const mapping = options.mapping;\n if (!mapping || typeof mapping !== \"object\" || Array.isArray(mapping) || !Object.values(mapping).every(item => typeof item === \"string\")) return { allowed: false, reason: \"The reviewed csv column mapping must be an object of string values.\" };\n }\n }\n if (kind === \"looprows\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (options.variable !== undefined && !isnonempty(options.variable)) return { allowed: false, reason: \"The reviewed row variable name must be a non-empty string.\" };\n const inner = validateinnerstep(options, origin);\n if (!inner.allowed) return inner;\n }\n if (kind === \"transformvalues\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n const rules = options.rules;\n if (!Array.isArray(rules) || rules.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of transform rules is required in options.\" };\n for (const item of rules) {\n const rulecheck = validatetransformrule(item);\n if (!rulecheck.allowed) return rulecheck;\n }\n }\n if (kind === \"deduperows\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n const keys = options.keys;\n if (!Array.isArray(keys) || keys.length === 0 || !keys.every(key => isnonempty(key))) return { allowed: false, reason: \"A reviewed non-empty list of dedupe column keys is required in options.\" };\n }\n if (kind === \"mergepages\") {\n const listcheck = validatedatasetids(options, \"datasets\");\n if (!listcheck.allowed) return listcheck;\n }\n if (kind === \"stamplerows\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (options.url !== undefined && !ishttpsurl(options.url)) return { allowed: false, reason: \"The reviewed source url must use HTTPS.\" };\n }\n if (kind === \"previewgrid\") {\n if (!isnonempty(options.dataset)) return { allowed: false, reason: \"A reviewed dataset id is required in options.\" };\n if (options.sample !== undefined && (typeof options.sample !== \"number\" || !Number.isInteger(options.sample) || options.sample < 1)) return { allowed: false, reason: \"The reviewed sample row count must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"resumeextract\" && !isnonempty(options.session)) return { allowed: false, reason: \"A reviewed extract session id is required in options.\" };\n if (kind === \"logprovenance\" && !isnonempty(options.artifact)) return { allowed: false, reason: \"A reviewed artifact id or name is required in options.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed batch download specification: a non-empty HTTPS url list, an optional filename rule and an optional completion criterion. */\nexport function validatedownloadspec(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed downloadspec with a url list is required in options.\" };\n const spec = value as Record<string, unknown>;\n if (!Array.isArray(spec.urls) || spec.urls.length === 0 || !spec.urls.every(url => ishttpsurl(url))) return { allowed: false, reason: \"The reviewed downloadspec needs a non-empty list of HTTPS urls.\" };\n if (spec.filename !== undefined && !isnonempty(spec.filename)) return { allowed: false, reason: \"The reviewed downloadspec filename rule must be a non-empty string.\" };\n if (spec.complete !== undefined && spec.complete !== \"size\" && spec.complete !== \"checksum\") return { allowed: false, reason: \"The reviewed downloadspec completion criterion must be size or checksum.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed mime interception filter: include and exclude patterns plus the deny default for unlisted mime types. */\nexport function validatemimefilter(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed mimefilter with include and exclude patterns is required in options.\" };\n const filter = value as Record<string, unknown>;\n if (!Array.isArray(filter.include) || filter.include.length === 0 || !filter.include.every(pattern => isnonempty(pattern))) return { allowed: false, reason: \"The reviewed mimefilter needs a non-empty list of include patterns.\" };\n if (filter.exclude !== undefined && (!Array.isArray(filter.exclude) || !filter.exclude.every(pattern => isnonempty(pattern)))) return { allowed: false, reason: \"The reviewed mimefilter exclude patterns must be a list of non-empty strings.\" };\n if (filter.default !== \"deny\" && filter.default !== \"allow\") return { allowed: false, reason: \"The reviewed mimefilter needs the deny or allow default for unlisted mime types.\" };\n return { allowed: true };\n}\n\n/** Validates one reviewed cleanup rule: a positive age window with no code ceiling, an artifact kind and a keep policy. */\nexport function validatecleanuprule(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed cleanuprule with an age, a kind and a keep policy is required.\" };\n const rule = value as Record<string, unknown>;\n if (typeof rule.age !== \"number\" || !Number.isFinite(rule.age) || rule.age <= 0) return { allowed: false, reason: \"The reviewed cleanup age window must be a positive number of milliseconds with no code ceiling.\" };\n if (!isnonempty(rule.kind)) return { allowed: false, reason: \"The reviewed cleanup rule needs a non-empty artifact kind, or any to match every kind.\" };\n if (rule.keep !== \"none\" && rule.keep !== \"latest\" && rule.keep !== \"all\") return { allowed: false, reason: \"The reviewed cleanup keep policy must be none, latest or all.\" };\n return { allowed: true };\n}\n\n/** Validates the reviewed files, clipboard and downloads parameter grammar; batch sizes, concurrent windows and cleanup ages stay user configured with no code ceilings. */\nfunction validatefilesgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"batchdownload\") {\n const speccheck = validatedownloadspec(options.downloadspec);\n if (!speccheck.allowed) return speccheck;\n if (options.concurrent !== undefined && (typeof options.concurrent !== \"number\" || !Number.isInteger(options.concurrent) || options.concurrent < 1)) return { allowed: false, reason: \"The reviewed concurrent download window must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"pausedownload\" || kind === \"resumedownload\" || kind === \"verifydownload\" || kind === \"quarantinedownload\" || kind === \"scanvirus\") {\n if (!isnonempty(step.value)) return { allowed: false, reason: \"A reviewed download or quarantine reference is required.\" };\n if (kind === \"verifydownload\") {\n if (options.checksum !== undefined && !isnonempty(options.checksum)) return { allowed: false, reason: \"The reviewed expected checksum must be a non-empty string.\" };\n if (options.bytes !== undefined && (typeof options.bytes !== \"number\" || !Number.isFinite(options.bytes) || options.bytes < 0)) return { allowed: false, reason: \"The reviewed expected size must be zero or a positive number of bytes.\" };\n }\n if (kind === \"scanvirus\" && options.scanner !== undefined && !isnonempty(options.scanner)) return { allowed: false, reason: \"The reviewed scanner name must be a non-empty string.\" };\n if (kind === \"quarantinedownload\" && options.reason !== undefined && !isnonempty(options.reason)) return { allowed: false, reason: \"The reviewed quarantine reason must be a non-empty string.\" };\n }\n if (kind === \"interceptmime\") {\n const filtercheck = validatemimefilter(options.mimefilter);\n if (!filtercheck.allowed) return filtercheck;\n }\n if (kind === \"readclipboard\") {\n if (!isnonempty(options.consentref)) return { allowed: false, reason: \"A clipboard read requires a reviewed consent ref of an approved consent prompt in options.\" };\n if (options.prompt !== undefined && !isnonempty(options.prompt)) return { allowed: false, reason: \"The reviewed clipboard consent prompt must be a non-empty string.\" };\n }\n if (kind === \"exportnetlog\" && options.stepid !== undefined && !isnonempty(options.stepid)) return { allowed: false, reason: \"The reviewed netlog step filter must be a non-empty step id.\" };\n if (kind === \"namecaptures\") {\n if (!isnonempty(options.task)) return { allowed: false, reason: \"A reviewed task id is required in options for capture naming.\" };\n if (options.steps !== undefined && (!Array.isArray(options.steps) || options.steps.length === 0 || !options.steps.every(item => isnonempty(item)))) return { allowed: false, reason: \"The reviewed capture steps must be a non-empty list of step ids when present.\" };\n if (options.extension !== undefined && !isnonempty(options.extension)) return { allowed: false, reason: \"The reviewed capture extension must be a non-empty string.\" };\n }\n if (kind === \"cleanupartifacts\" && options.rules !== undefined) {\n const rules = options.rules;\n if (!Array.isArray(rules) || rules.length === 0) return { allowed: false, reason: \"The reviewed cleanup rules must be a non-empty list when present.\" };\n for (const item of rules) {\n const rulecheck = validatecleanuprule(item);\n if (!rulecheck.allowed) return rulecheck;\n }\n }\n return { allowed: true };\n}\n\n/** Requires an approved consent prompt before any clipboard read; every read consumes its own prompt. */\nexport function clipboardconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"A clipboard read requires a reviewed consent ref in options.\" };\n return { allowed: true };\n}\n\n/** Refuses to release any quarantined file before a clean scan verdict exists. */\nexport function quarantinereleasegranted(entry: quarantineentry): policyevaluation {\n if (entry.scan !== \"clean\") return { allowed: false, reason: `The quarantined file ${entry.path} cannot leave quarantine with the ${entry.scan} scan verdict; only a clean verdict releases it.` };\n return { allowed: true };\n}\n\n/** Refuses downloads and download interception that fall outside the session origin grants. */\nexport function downloadgranted(session: agentsession | undefined, url: string): policyevaluation {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { return { allowed: false, reason: \"The reviewed download URL is invalid.\" }; }\n if (!origingranted(session, origin)) return { allowed: false, reason: `The download from ${origin} leaves the session origin grants and needs a session grant first.` };\n return { allowed: true };\n}\n\n/** Masks a clipboard payload for every log line; the full text never persists anywhere. */\nexport function maskclipboard(payload: string): string {\n return `[clipboard payload of ${payload.length} character${payload.length === 1 ? \"\" : \"s\"}]`;\n}\n\n/** Requires an asksubmit review step before every form submission step. */\nexport function submitreviewgranted(steps: toolstep[], submitid: string): policyevaluation {\n const position = steps.findIndex(candidate => candidate.id === submitid);\n const asked = steps.some((candidate, index) => candidate.kind === \"asksubmit\" && (position === -1 || index < position));\n return asked ? { allowed: true } : { allowed: false, reason: \"Form submission requires an asksubmit review step before it.\" };\n}\n\n/** Requires a reviewed consent ref before any password field is filled. */\nexport function passwordconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"A password fill requires a reviewed consent ref in options.\" };\n return { allowed: true };\n}\n\n/** True when a numeric value passes the Luhn checksum used by card networks. */\nfunction luhnvalid(digits: string): boolean {\n let sum = 0;\n let double = false;\n for (let index = digits.length - 1; index >= 0; index -= 1) {\n let value = Number.parseInt(digits[index] ?? \"\", 10);\n if (!Number.isFinite(value)) return false;\n if (double) { value *= 2; if (value > 9) value -= 9; }\n sum += value;\n double = !double;\n }\n return sum % 10 === 0;\n}\n\n/** Refuses generated values that look like real card numbers or personal identifiers; test prefixed card values stay allowed. */\nexport function generatedvalueallowed(value: string): policyevaluation {\n const compact = value.replace(/[\\s-]/g, \"\");\n if (/^\\d{13,19}$/.test(compact) && luhnvalid(compact) && !compact.startsWith(\"4111\")) return { allowed: false, reason: \"The generated value looks like a real card number and is refused; generated card values use the 4111 test prefix.\" };\n if (/^\\d{3}-\\d{2}-\\d{4}$/.test(value.trim())) return { allowed: false, reason: \"The generated value looks like a personal identifier and is refused.\" };\n return { allowed: true };\n}\n\n/** Requires the origin grants of a saved profile to cover the origin before its values fill a page. */\nexport function profilegrantgranted(profile: formprofile, origin: string): policyevaluation {\n if (!profile.grants.includes(origin)) return { allowed: false, reason: `The saved profile ${profile.name} is not granted to ${origin}; add the origin to the profile grants first.` };\n return { allowed: true };\n}\n\n/** Restricts group and layout mutations to the active session: they refuse without a live session. */\nexport function layoutmutationgranted(session: agentsession | undefined, now: number): policyevaluation {\n if (!session || session.stoppedat || session.expiresat <= now) return { allowed: false, reason: \"Group and layout mutations stay inside the active session.\" };\n return { allowed: true };\n}\n\n/** Requires explicit review before closing a window that holds more than one task tab. */\nexport function windowclosegate(tasktabcount: number, reviewed: boolean): policyevaluation {\n if (tasktabcount > 1 && !reviewed) return { allowed: false, reason: `The window holds ${tasktabcount} task tabs and needs explicit review before it closes.` };\n return { allowed: true };\n}\n\n/** Reads the user configured concurrent task tab ceiling; an absent value never refuses a tab. */\nexport function tasktabceiling(settings: runsettings | undefined): number | undefined {\n const ceiling = settings?.tasktabceiling;\n return typeof ceiling === \"number\" && Number.isFinite(ceiling) && ceiling >= 0 ? ceiling : 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/** Decides whether an unreviewed origin may open: the session grants cover it or a safe checksafe verdict vouches for it. */\nexport function originverified(url: string, grants: string[], verdicts: safetyverdict[]): policyevaluation {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { return { allowed: false, reason: \"The reviewed navigation URL is invalid.\" }; }\n if (grants.includes(origin)) return { allowed: true };\n const covered = verdicts.find(verdict => verdict.safe && (verdict.url === url || (safeorigin(verdict.url) === origin)));\n if (covered) return { allowed: true };\n return { allowed: false, reason: `The origin ${origin} is outside the session grants and has no safe checksafe verdict; run checksafe and review it first.` };\n}\n\nfunction safeorigin(url: string): string {\n try { return new URL(url).origin; } catch { return \"\"; }\n}\n\n/** Refuses navigation that would move a granted task tab outside the session origin grants until the user consents. */\nexport function navigationgranted(session: agentsession | undefined, url: string): policyevaluation {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { return { allowed: false, reason: \"The reviewed navigation URL is invalid.\" }; }\n if (origingranted(session, origin)) return { allowed: true };\n return { allowed: false, reason: `Navigation to ${origin} leaves the task tab origins and needs the user consent of a session grant first.` };\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\" || kind === \"looprows\") 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/** True when a reviewed https url parses. */\nfunction ishttpsurl(value: unknown): value is string {\n if (typeof value !== \"string\" || !value.trim()) return false;\n try { return new URL(value).protocol === \"https:\"; } catch { return false; }\n}\n\n/** Validates the reviewed navtarget grammar of a navigation step. */\nfunction validatenavtarget(value: unknown, kind: string): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed navtarget with a url is required in options.\" };\n const target = value as Record<string, unknown>;\n if (!ishttpsurl(target.url)) return { allowed: false, reason: \"The reviewed navtarget url must use HTTPS.\" };\n const container = target.container ?? \"tab\";\n if (container !== \"current\" && container !== \"tab\" && container !== \"window\" && container !== \"private\") return { allowed: false, reason: \"The reviewed navtarget container must be current, tab, window or private.\" };\n if (target.position !== undefined && target.position !== \"adjacent\" && target.position !== \"end\") return { allowed: false, reason: \"The reviewed navtarget position must be adjacent or end.\" };\n if (kind === \"openprivate\" && container !== \"private\") return { allowed: false, reason: \"The openprivate step requires the private container.\" };\n if (kind === \"openlink\" && container === \"private\") return { allowed: false, reason: \"The openlink step cannot open the private container; use openprivate.\" };\n return { allowed: true };\n}\n\n/** Validates the reviewed waitprofile grammar with its load signals, thresholds and per origin overrides. */\nfunction validatewaitprofile(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed waitprofile with load signals is required in options.\" };\n const profile = value as Record<string, unknown>;\n if (!Array.isArray(profile.signals) || profile.signals.length === 0 || !profile.signals.every(signal => isnonempty(signal))) return { allowed: false, reason: \"The reviewed waitprofile needs a non-empty list of load signals.\" };\n if (!nonnegativeoption(profile, \"idle\")) return { allowed: false, reason: \"The reviewed waitprofile idle threshold must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(profile, \"timeout\")) return { allowed: false, reason: \"The reviewed waitprofile timeout must be zero or a positive number of milliseconds.\" };\n if (profile.overrides !== undefined) {\n if (!Array.isArray(profile.overrides) || profile.overrides.length === 0) return { allowed: false, reason: \"The reviewed waitprofile overrides must be a non-empty list when present.\" };\n for (const entry of profile.overrides) {\n if (!entry || typeof entry !== \"object\" || Array.isArray(entry)) return { allowed: false, reason: \"Every reviewed waitprofile override must be an object with an origin.\" };\n const override = entry as Record<string, unknown>;\n if (!ishttpsurl(override.origin)) return { allowed: false, reason: \"Every reviewed waitprofile override origin must use HTTPS.\" };\n if (override.signals !== undefined && (!Array.isArray(override.signals) || !override.signals.every(signal => isnonempty(signal)))) return { allowed: false, reason: \"The reviewed waitprofile override signals must be a list of non-empty strings.\" };\n if (!nonnegativeoption(override, \"idle\") || !nonnegativeoption(override, \"timeout\")) return { allowed: false, reason: \"The reviewed waitprofile override thresholds must be zero or positive numbers.\" };\n }\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed urlpattern grammar with its match mode plus query and fragment parts. */\nexport function validateurlpattern(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed urlpattern is required in options.\" };\n const pattern = value as Record<string, unknown>;\n if (pattern.mode !== \"exact\" && pattern.mode !== \"prefix\" && pattern.mode !== \"host\" && pattern.mode !== \"pattern\") return { allowed: false, reason: \"The reviewed urlpattern mode must be exact, prefix, host or pattern.\" };\n if (!ishttpsurl(pattern.url)) return { allowed: false, reason: \"The reviewed urlpattern url must use HTTPS.\" };\n if (pattern.query !== undefined) {\n if (!pattern.query || typeof pattern.query !== \"object\" || Array.isArray(pattern.query)) return { allowed: false, reason: \"The reviewed urlpattern query part must be an object of parameter names and values.\" };\n for (const item of Object.values(pattern.query)) if (typeof item !== \"string\") return { allowed: false, reason: \"The reviewed urlpattern query values must be strings.\" };\n }\n if (pattern.fragment !== undefined && !isnonempty(pattern.fragment)) return { allowed: false, reason: \"The reviewed urlpattern fragment must be a non-empty string.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed non-empty list of HTTPS urls in options. */\nfunction validateurllist(options: Record<string, unknown>, key: string): policyevaluation {\n const urls = options[key];\n if (!Array.isArray(urls) || urls.length === 0 || !urls.every(url => ishttpsurl(url))) return { allowed: false, reason: `A reviewed non-empty list of HTTPS urls is required in options as ${key}.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed ratelimit grammar of a navrate step; the window and ceiling stay user configured with no hardcoded cap. */\nfunction validateratelimit(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed ratelimit with a window and a ceiling is required in options.\" };\n const limit = value as Record<string, unknown>;\n if (limit.domain !== undefined && !isnonempty(limit.domain)) return { allowed: false, reason: \"The reviewed ratelimit domain must be a non-empty string.\" };\n if (typeof limit.window !== \"number\" || !Number.isFinite(limit.window) || limit.window <= 0) return { allowed: false, reason: \"The reviewed ratelimit window must be a positive number of milliseconds with no code ceiling.\" };\n if (typeof limit.ceiling !== \"number\" || !Number.isInteger(limit.ceiling) || limit.ceiling < 1) return { allowed: false, reason: \"The reviewed ratelimit ceiling must be a positive integer with no code ceiling.\" };\n return { allowed: true };\n}\n\n/** Validates the reviewed tabquery grammar with url, title, id and pattern matchers; at least one matcher is required. */\nexport function validatetabquery(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed tabquery with at least one matcher is required in options.\" };\n const query = value as Record<string, unknown>;\n const hasmatcher = query.url !== undefined || query.title !== undefined || query.id !== undefined || query.pattern !== undefined;\n if (!hasmatcher) return { allowed: false, reason: \"The reviewed tabquery needs a url, title, id or pattern matcher.\" };\n if (query.url !== undefined && !isnonempty(query.url)) return { allowed: false, reason: \"The reviewed tabquery url matcher must be a non-empty string.\" };\n if (query.title !== undefined && !isnonempty(query.title)) return { allowed: false, reason: \"The reviewed tabquery title matcher must be a non-empty string.\" };\n if (query.pattern !== undefined && !isnonempty(query.pattern)) return { allowed: false, reason: \"The reviewed tabquery pattern matcher must be a non-empty string.\" };\n if (query.id !== undefined && (typeof query.id !== \"number\" || !Number.isInteger(query.id) || query.id < 0)) return { allowed: false, reason: \"The reviewed tabquery id matcher must be a non-negative integer tab id.\" };\n return { allowed: true };\n}\n\n/** Validates a reviewed group color choice against the Chromium tab group palette. */\nfunction validategroupcolor(value: unknown): boolean {\n return typeof value === \"string\" && (groupcolors as string[]).includes(value);\n}\n\n/** Validates a reviewed list of numeric browser ids in options. */\nfunction validateidlist(options: Record<string, unknown>, key: string): boolean {\n const ids = options[key];\n return Array.isArray(ids) && ids.length > 0 && ids.every(id => typeof id === \"number\" && Number.isInteger(id) && id >= 0);\n}\n\n/** Validates the reviewed tab and window parameter grammar of the tabs and windows command family. */\nfunction validatetabsgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"querytabs\" || kind === \"closepattern\") {\n const querycheck = validatetabquery(options.tabquery);\n if (!querycheck.allowed) return querycheck;\n if (kind === \"closepattern\" && options.reviewed !== true) return { allowed: false, reason: \"The close pattern needs the explicit reviewed flag before any tab closes.\" };\n }\n if (kind === \"duplicatetab\" || kind === \"pintab\" || kind === \"mutetab\" || kind === \"movetab\" || kind === \"movetabwindow\" || kind === \"badgetab\" || kind === \"attachmeta\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser tab id is required.\" };\n }\n if (kind === \"focuswindow\" || kind === \"maximizewindow\" || kind === \"minimizewindow\" || kind === \"restorewindow\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser window id is required.\" };\n }\n if (kind === \"pintab\" && typeof options.pinned !== \"boolean\") return { allowed: false, reason: \"A reviewed pinned flag is required in options.\" };\n if (kind === \"mutetab\" && typeof options.muted !== \"boolean\") return { allowed: false, reason: \"A reviewed muted flag is required in options.\" };\n if (kind === \"movetab\") {\n if (typeof options.index !== \"number\" || !Number.isInteger(options.index) || options.index < 0) return { allowed: false, reason: \"A reviewed non-negative target index is required in options.\" };\n }\n if (kind === \"movetabwindow\") {\n if (typeof options.windowid !== \"number\" || !Number.isInteger(options.windowid) || options.windowid < 0) return { allowed: false, reason: \"A reviewed target window id is required in options.\" };\n }\n if (kind === \"grouptabs\") {\n const group = options.group;\n if (!group || typeof group !== \"object\" || Array.isArray(group)) return { allowed: false, reason: \"A reviewed group with a name is required in options.\" };\n const spec = group as Record<string, unknown>;\n if (!isnonempty(spec.name)) return { allowed: false, reason: \"The reviewed group needs a non-empty name.\" };\n if (!validategroupcolor(spec.color)) return { allowed: false, reason: \"The reviewed group color must be a Chromium tab group color.\" };\n if (!validateidlist(spec, \"tabids\")) return { allowed: false, reason: \"The reviewed group needs a non-empty list of member tab ids.\" };\n }\n if (kind === \"colorgroup\") {\n if (!isnonempty(options.name)) return { allowed: false, reason: \"A reviewed group name is required in options.\" };\n if (!validategroupcolor(options.color)) return { allowed: false, reason: \"The reviewed group color must be a Chromium tab group color.\" };\n }\n if (kind === \"collapsegroup\") {\n if (!isnonempty(options.name)) return { allowed: false, reason: \"A reviewed group name is required in options.\" };\n if (typeof options.collapsed !== \"boolean\") return { allowed: false, reason: \"A reviewed collapsed flag is required in options.\" };\n }\n if (kind === \"discardtab\" || kind === \"reloadtabs\") {\n if (!isnumericid(step.value) && !validateidlist(options, \"tabs\")) return { allowed: false, reason: \"A numeric tab id or a reviewed list of tab ids is required.\" };\n }\n if (kind === \"zoomin\" || kind === \"zoomout\") {\n if (options.step !== undefined && (typeof options.step !== \"number\" || !Number.isFinite(options.step) || options.step <= 0)) return { allowed: false, reason: \"The reviewed zoom step must be a positive number with no code ceiling.\" };\n if (step.value !== undefined && step.value !== \"\" && !isnumericid(step.value)) return { allowed: false, reason: \"The reviewed zoom target must be a numeric tab id.\" };\n }\n if (kind === \"switchtab\") {\n if (options.direction !== \"next\" && options.direction !== \"previous\") return { allowed: false, reason: \"A reviewed switch direction of next or previous is required in options.\" };\n }\n if (kind === \"restorewindow\") {\n const bounds = options.bounds;\n if (bounds !== undefined) {\n if (!bounds || typeof bounds !== \"object\" || Array.isArray(bounds)) return { allowed: false, reason: \"The reviewed window bounds must be an object.\" };\n const shape = bounds as Record<string, unknown>;\n for (const field of [\"left\", \"top\", \"width\", \"height\"]) {\n if (typeof shape[field] !== \"number\" || !Number.isFinite(shape[field])) return { allowed: false, reason: \"The reviewed window bounds need numeric left, top, width and height.\" };\n }\n }\n }\n if (kind === \"scratchwindow\") {\n if (step.value !== undefined && step.value !== \"\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"The reviewed scratch window url must use HTTPS.\" };\n }\n if (kind === \"incognitowindow\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"A reviewed HTTPS url is required to open an incognito window.\" };\n if (kind === \"restoretab\" && step.value !== undefined && step.value !== \"\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"The reviewed restore url must use HTTPS.\" };\n if (kind === \"savelayout\" || kind === \"restorelayout\") {\n if (!isnonempty(options.name)) return { allowed: false, reason: \"A reviewed layout name is required in options.\" };\n }\n if (kind === \"badgetab\") {\n if (!isnonempty(options.label)) return { allowed: false, reason: \"A reviewed badge label is required in options.\" };\n if (options.taskid !== undefined && !isnonempty(options.taskid)) return { allowed: false, reason: \"The reviewed badge task id must be a non-empty string.\" };\n }\n if (kind === \"attachmeta\") {\n const labels = options.labels;\n const taskrefs = options.taskrefs;\n const haslabels = Array.isArray(labels) && labels.length > 0 && labels.every(label => isnonempty(label));\n const hastaskrefs = Array.isArray(taskrefs) && taskrefs.length > 0 && taskrefs.every(ref => isnonempty(ref));\n if (!haslabels && !hastaskrefs) return { allowed: false, reason: \"Reviewed labels or task refs are required in options to attach metadata.\" };\n if (options.provenance !== undefined && !isnonempty(options.provenance)) return { allowed: false, reason: \"The reviewed provenance must be a non-empty string.\" };\n }\n if (kind === \"reopenrun\" && !isnonempty(options.run)) return { allowed: false, reason: \"A reviewed run id is required in options to reopen its tabs.\" };\n return { allowed: true };\n}\n\n/** True when the kind belongs to the media capture family of pdf documents, recordings, images, canvases, streams, assets, lapses, conversions and thumbnails. */\nexport function ismediakind(kind: actionkind): boolean {\n return mediaactions.has(kind);\n}\n\n/** True when the kind belongs to the network observation family of fetching, parsing and typed calls. */\nexport function ishttpkind(kind: actionkind): boolean {\n return httpactions.has(kind);\n}\n\n/** True when the kind belongs to the socket and stream family of channels, messages, subscriptions and poll loops. */\nexport function issocketkind(kind: actionkind): boolean {\n return socketactions.has(kind);\n}\n\n/** True when the kind belongs to the request observation family of watches, headers, bodies and page api discovery. */\nexport function isnetwatchkind(kind: actionkind): boolean {\n return netwatchactions.has(kind);\n}\n\n/** True when the kind belongs to the network control family of blocking, mocking, header rewriting, cookies, auth, api keys, proxy routing and uploads. */\nexport function iscontrolkind(kind: actionkind): boolean {\n return controlactions.has(kind);\n}\n\n/** Resolves the reviewed risk of one step: capturebodies grades sensitive when the reviewed mime list carries private payload types and extractapi grades sensitive when the replay verb mutates, while every other kind keeps its risk table grade. */\nexport function resolvedrisk(step: toolstep): \"read\" | \"interaction\" | \"sensitive\" {\n if (step.kind === \"capturebodies\") {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const body = options.body;\n const mimes = body && typeof body === \"object\" && !Array.isArray(body) ? (body as Record<string, unknown>).mimes : undefined;\n if (Array.isArray(mimes) && mimes.some(mime => typeof mime === \"string\" && privatemime(mime))) return \"sensitive\";\n return \"interaction\";\n }\n if (step.kind === \"extractapi\") {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const replay = options.replay;\n const verb = replay && typeof replay === \"object\" && !Array.isArray(replay) ? (replay as Record<string, unknown>).verb : undefined;\n if (typeof verb === \"string\" && ![\"GET\", \"HEAD\", \"OPTIONS\"].includes(verb.trim().toUpperCase())) return \"sensitive\";\n return \"read\";\n }\n return actionrisk(step.kind);\n}\n\n/** Restricts every outbound channel to a granted origin: wss websocket and https event stream urls map onto their https origin, carry no embedded credentials and stay inside the session origin grants. */\nexport function socketgate(session: agentsession | undefined, url: string): policyevaluation {\n let parsed: URL;\n try { parsed = new URL(url); } catch { return { allowed: false, reason: \"The channel needs a valid url before it can be reviewed.\" }; }\n if (parsed.protocol !== \"wss:\" && parsed.protocol !== \"https:\") return { allowed: false, reason: \"Channels use wss websocket urls or https event stream urls only.\" };\n if (parsed.username || parsed.password) return { allowed: false, reason: \"Channel credentials are not allowed in the url.\" };\n const origin = channelorigin(url);\n if (!origingranted(session, origin)) return { allowed: false, reason: `The channel to ${origin} stays outside the session origin grants.` };\n return { allowed: true };\n}\n\n/** Requires the user granted request watching before any watchrequests step runs; the observation derives from the page timing buffers and the grant adds no manifest permission. */\nexport function watchgate(session: agentsession | undefined, settings: runsettings | undefined, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the request watch.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot watch requests.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot watch requests.\" };\n if (settings?.webrequestgrant !== true) return { allowed: false, reason: \"Request watching needs the webrequest grant in the review panel first; the observation derives from the page timing buffers and adds no manifest permission.\" };\n return { allowed: true };\n}\n\n/** Requires the host grant for every observed origin before header reads, body captures and endpoint replays touch an exchange. */\nexport function observedorigingranted(session: agentsession | undefined, url: string): policyevaluation {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { return { allowed: false, reason: \"The observed exchange url does not parse for an origin check.\" }; }\n if (!origingranted(session, origin)) return { allowed: false, reason: `The observed origin ${origin} stays outside the session origin grants; grant it before reading headers, bodies or replays.` };\n return { allowed: true };\n}\n\n/** Restricts every outbound request to a granted origin: the url must be a reviewed HTTPS url inside the session origin grants. */\nexport function origincheck(session: agentsession | undefined, url: string): policyevaluation {\n let parsed: URL;\n try { parsed = new URL(url); } catch { return { allowed: false, reason: \"The outbound request needs a valid url before it can be reviewed.\" }; }\n if (parsed.protocol !== \"https:\") return { allowed: false, reason: \"Outbound requests use HTTPS urls only.\" };\n if (parsed.username || parsed.password) return { allowed: false, reason: \"Endpoint credentials are not allowed in the url.\" };\n if (!origingranted(session, parsed.origin)) return { allowed: false, reason: `The outbound request to ${parsed.origin} stays outside the session origin grants.` };\n return { allowed: true };\n}\n\n/** True when a header name carries credentials and therefore needs the explicit consent that names it. */\nexport function credentialheadername(name: string): boolean {\n return credentialheaders.has(name.trim().toLowerCase());\n}\n\n/** Requires a reviewed consent ref before any custom header leaves the extension; requests without custom headers need no prompt. */\nexport function fetchconsentrefgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const request = options.fetch;\n const headers = request && typeof request === \"object\" && !Array.isArray(request) ? (request as Record<string, unknown>).headers : undefined;\n const names = headers && typeof headers === \"object\" && !Array.isArray(headers) ? Object.keys(headers as Record<string, unknown>) : [];\n if (names.length === 0) return { allowed: true };\n const empty = names.some(name => !name.trim());\n if (empty) return { allowed: false, reason: \"Header allowlists with empty names are refused.\" };\n const credential = names.find(name => credentialheadername(name));\n if (credential !== undefined && !isnonempty(options.consentref)) return { allowed: false, reason: `The credential bearing header ${credential} needs the explicit reviewed consent that names it before it is sent.` };\n if (!isnonempty(options.consentref)) return { allowed: false, reason: `The ${names.length} reviewed custom header${names.length === 1 ? \"\" : \"s\"} need a reviewed consent ref in options before any send.` };\n return { allowed: true };\n}\n\n/** True when one stored fetch consent still covers the origin and every header name inside its expiry window. */\nexport function fetchconsentcovers(consent: { origin: string; headers: Array<{ name: string }>; approved?: boolean; expiresat: number }, origin: string, headernames: string[], now: number): boolean {\n if (consent.approved !== true) return false;\n if (consent.expiresat <= now) return false;\n if (consent.origin !== origin) return false;\n const covered = new Set(consent.headers.map(header => header.name.trim().toLowerCase()));\n return headernames.every(name => covered.has(name.trim().toLowerCase()));\n}\n\n/** True when the reviewed call mutates: rest verbs beyond get, head and options or a graphql mutation; mutating calls grade sensitive. */\nexport function mutationcallof(step: toolstep): boolean {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n if (step.kind === \"callgraphql\") {\n const request = options.graphql;\n return Boolean(request && typeof request === \"object\" && !Array.isArray(request) && (request as Record<string, unknown>).operationkind === \"mutation\");\n }\n if (step.kind === \"callrest\") {\n const method = typeof options.method === \"string\" ? options.method.trim().toUpperCase() : undefined;\n if (method !== undefined) return ![\"GET\", \"HEAD\", \"OPTIONS\"].includes(method);\n }\n return false;\n}\n\n/** Keeps the reviewed fetch waits inside the reviewed wait budget: the worst case of every timeout plus every backoff wait must fit; every bound itself stays a user choice with no code ceiling. */\nexport function fetchbudgetallowed(timeout: number | undefined, retries: number | undefined, backoff: number | undefined, wait: number | undefined): policyevaluation {\n for (const [label, value] of [[\"timeout\", timeout], [\"retries\", retries], [\"backoff\", backoff]] as Array<[string, number | undefined]>) {\n if (value !== undefined && (typeof value !== \"number\" || !Number.isFinite(value) || value < 0)) return { allowed: false, reason: `The reviewed fetch ${label} must be zero or a positive number with no code ceiling.` };\n }\n if (wait !== undefined && (typeof wait !== \"number\" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: \"The reviewed fetch wait budget must be zero or a positive number of milliseconds.\" };\n if (wait === undefined || timeout === undefined) return { allowed: true };\n const attempts = Math.max(1, Math.floor((retries ?? 0)) + 1);\n const waits = (backoff ?? 0) * (attempts * (attempts - 1)) / 2;\n const worstcase = timeout * attempts + waits;\n if (worstcase > wait) return { allowed: false, reason: `The fetch worst case of ${worstcase} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or fewer retries.` };\n return { allowed: true };\n}\n\n/** Resolves the outbound url of an http step at review time: the fetch request url of a fetchurl step and nothing for typed calls whose endpoints resolve at execution. */\nexport function outboundtarget(step: toolstep): string | undefined {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const request = options.fetch;\n if (request && typeof request === \"object\" && !Array.isArray(request)) {\n const url = (request as Record<string, unknown>).url;\n if (typeof url === \"string\" && url.trim()) return url.trim();\n }\n return undefined;\n}\n\n/** Validates one reviewed typed endpoint definition: name, method, HTTPS url template with variables, header allowlist with non-empty names and a payload schema with kinds, required flags and defaults. */\nexport function validateendpointrecord(value: unknown): policyevaluation {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"A reviewed endpoint record is required.\" };\n const record = value as Record<string, unknown>;\n if (!isnonempty(record.name)) return { allowed: false, reason: \"The endpoint record needs a reviewed non-empty name.\" };\n if (!isnonempty(record.method)) return { allowed: false, reason: \"The endpoint record needs a reviewed method.\" };\n if (!ishttpsurl(record.url)) return { allowed: false, reason: \"The endpoint record url template must be an HTTPS url.\" };\n if (record.headers !== undefined) {\n if (!record.headers || typeof record.headers !== \"object\" || Array.isArray(record.headers)) return { allowed: false, reason: \"The endpoint header allowlist must be an object of reviewed headers.\" };\n for (const name of Object.keys(record.headers as Record<string, unknown>)) {\n if (!name.trim()) return { allowed: false, reason: \"Endpoint header allowlists with empty names are refused.\" };\n const headervalue = (record.headers as Record<string, unknown>)[name];\n if (typeof headervalue !== \"string\") return { allowed: false, reason: `The endpoint header ${name} needs a reviewed string value.` };\n }\n }\n const schema = record.schema;\n if (!schema || typeof schema !== \"object\" || Array.isArray(schema)) return { allowed: false, reason: \"Every typed endpoint call needs a reviewed payload schema; endpoint records without schemas are refused.\" };\n const fields = (schema as Record<string, unknown>).fields;\n if (!Array.isArray(fields) || fields.length === 0) return { allowed: false, reason: \"The endpoint payload schema needs a non-empty field list.\" };\n for (const item of fields) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every payload schema field must be an object.\" };\n const field = item as Record<string, unknown>;\n if (!isnonempty(field.name)) return { allowed: false, reason: \"Every payload schema field needs a non-empty name.\" };\n if (field.kind !== \"string\" && field.kind !== \"number\" && field.kind !== \"boolean\") return { allowed: false, reason: `The payload schema field ${field.name} must be a string, number or boolean kind.` };\n if (field.required !== undefined && typeof field.required !== \"boolean\") return { allowed: false, reason: `The payload schema field ${field.name} required flag must be a boolean.` };\n if (field.default !== undefined && typeof field.default !== \"string\" && typeof field.default !== \"number\" && typeof field.default !== \"boolean\") return { allowed: false, reason: `The payload schema field ${field.name} default must match its kind.` };\n }\n return { allowed: true };\n}\n\n/** Validates one dotted json path against the path grammar: non-empty segments of names, digits, underscores or hyphens. */\nfunction validpath(path: string): boolean {\n return path.split(\".\").every(segment => /^[A-Za-z0-9_-]+$/.test(segment));\n}\n\n/** Validates the reviewed network observation parameter grammar of the 1.1.42 family: fetch requests with header allowlists, fetch policies with timeout, retries, backoff and follow limit, stream budgets, dotted json paths, html queries, graphql operations and typed endpoint references; every bound stays a user choice with no code ceiling. */\nfunction validatehttpgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"fetchurl\") {\n const request = options.fetch;\n if (!request || typeof request !== \"object\" || Array.isArray(request)) return { allowed: false, reason: \"A reviewed fetch request with a url is required in options.fetch.\" };\n const fetchrequest = request as Record<string, unknown>;\n if (typeof fetchrequest.url !== \"string\" || !fetchrequest.url.trim()) return { allowed: false, reason: \"The reviewed fetch request needs a non-empty url.\" };\n if (fetchrequest.method !== undefined && (typeof fetchrequest.method !== \"string\" || ![\"GET\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"].includes(fetchrequest.method.trim().toUpperCase()))) return { allowed: false, reason: \"The reviewed fetch method must be a known HTTP verb.\" };\n if (fetchrequest.headers !== undefined) {\n if (!fetchrequest.headers || typeof fetchrequest.headers !== \"object\" || Array.isArray(fetchrequest.headers)) return { allowed: false, reason: \"The reviewed header allowlist must be an object of custom headers.\" };\n for (const name of Object.keys(fetchrequest.headers as Record<string, unknown>)) {\n if (!name.trim()) return { allowed: false, reason: \"Header allowlists with empty names are refused.\" };\n if (typeof (fetchrequest.headers as Record<string, unknown>)[name] !== \"string\") return { allowed: false, reason: `The reviewed header ${name} needs a string value.` };\n }\n }\n if (fetchrequest.body !== undefined && typeof fetchrequest.body !== \"string\") return { allowed: false, reason: \"The reviewed fetch body must be a string.\" };\n if (fetchrequest.mode !== undefined && fetchrequest.mode !== \"cors\" && fetchrequest.mode !== \"no-cors\" && fetchrequest.mode !== \"same-origin\") return { allowed: false, reason: \"The reviewed fetch mode must be cors, no-cors or same-origin.\" };\n const consentgate = fetchconsentrefgranted(step);\n if (!consentgate.allowed) return consentgate;\n const policycheck = validatefetchoptions(options.fetchoptions);\n if (!policycheck.allowed) return policycheck;\n const fetchpolicy = fetchoptionsvalues(options.fetchoptions);\n const budget = fetchbudgetallowed(fetchpolicy.timeout, fetchpolicy.retries, fetchpolicy.backoff, fetchnumeric(options, \"wait\"));\n if (!budget.allowed) return budget;\n if (options.stream !== undefined) {\n if (!options.stream || typeof options.stream !== \"object\" || Array.isArray(options.stream)) return { allowed: false, reason: \"The reviewed stream window must be an object with an optional byte budget.\" };\n const streambudget = (options.stream as Record<string, unknown>).budget;\n if (streambudget !== undefined && (typeof streambudget !== \"number\" || !Number.isFinite(streambudget) || streambudget < 0)) return { allowed: false, reason: \"The reviewed stream byte budget must be zero or a positive number of bytes with no code ceiling.\" };\n }\n }\n if (kind === \"parsejson\") {\n if (!isnonempty(options.call)) return { allowed: false, reason: \"A reviewed stored call id is required in options.call before the body parses.\" };\n const fields = options.fields;\n if (!Array.isArray(fields) || fields.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of json path rules is required in options.fields.\" };\n for (const item of fields) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every json path rule must be an object.\" };\n const rule = item as Record<string, unknown>;\n if (!isnonempty(rule.name)) return { allowed: false, reason: \"Every json path rule needs a non-empty field name.\" };\n if (typeof rule.path !== \"string\" || !rule.path.trim() || !validpath(rule.path.trim())) return { allowed: false, reason: `The json path of ${rule.name} must be a dotted path of non-empty segments.` };\n if (rule.kind !== undefined && rule.kind !== \"text\" && rule.kind !== \"number\" && rule.kind !== \"boolean\" && rule.kind !== \"json\") return { allowed: false, reason: `The json path kind of ${rule.name} must be text, number, boolean or json.` };\n }\n }\n if (kind === \"parsehtml\") {\n if (!isnonempty(options.call)) return { allowed: false, reason: \"A reviewed stored call id is required in options.call before the markup parses.\" };\n const queries = options.queries;\n if (!Array.isArray(queries) || queries.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of html queries is required in options.queries.\" };\n for (const item of queries) {\n if (!item || typeof item !== \"object\" || Array.isArray(item)) return { allowed: false, reason: \"Every html query must be an object.\" };\n const query = item as Record<string, unknown>;\n if (!isnonempty(query.selector)) return { allowed: false, reason: \"Every html query needs a selector from the reviewed selector grammar.\" };\n if (query.attribute !== undefined && !isnonempty(query.attribute)) return { allowed: false, reason: \"The reviewed html query attribute must be a non-empty attribute name.\" };\n if (query.multi !== undefined && typeof query.multi !== \"boolean\") return { allowed: false, reason: \"The reviewed html query multi flag must be a boolean.\" };\n }\n }\n if (kind === \"callrest\" || kind === \"callgraphql\") {\n if (!isnonempty(options.endpoint)) return { allowed: false, reason: \"A reviewed typed endpoint name is required in options.endpoint.\" };\n if (kind === \"callrest\") {\n if (options.payload !== undefined && (!options.payload || typeof options.payload !== \"object\" || Array.isArray(options.payload))) return { allowed: false, reason: \"The reviewed rest payload must be an object of reviewed values.\" };\n if (options.method !== undefined && (typeof options.method !== \"string\" || ![\"GET\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"].includes(options.method.trim().toUpperCase()))) return { allowed: false, reason: \"The reviewed endpoint method override must be a known HTTP verb.\" };\n if (options.success !== undefined && (!Array.isArray(options.success) || !options.success.every(code => typeof code === \"number\" && Number.isInteger(code)))) return { allowed: false, reason: \"The reviewed success status list must be a list of integer status codes.\" };\n }\n if (kind === \"callgraphql\") {\n const request = options.graphql;\n if (!request || typeof request !== \"object\" || Array.isArray(request)) return { allowed: false, reason: \"A reviewed graphql request with an operation is required in options.graphql.\" };\n const graphql = request as Record<string, unknown>;\n if (typeof graphql.query !== \"string\" || !graphql.query.trim()) return { allowed: false, reason: \"The reviewed graphql operation text must be a non-empty string.\" };\n if (graphql.operationkind !== \"query\" && graphql.operationkind !== \"mutation\") return { allowed: false, reason: \"The reviewed graphql operation kind must be query or mutation; unknown operation kinds are refused.\" };\n if (graphql.variables !== undefined && (!graphql.variables || typeof graphql.variables !== \"object\" || Array.isArray(graphql.variables))) return { allowed: false, reason: \"The reviewed graphql variables must be an object of reviewed values.\" };\n if (graphql.operationname !== undefined && !isnonempty(graphql.operationname)) return { allowed: false, reason: \"The reviewed graphql operation name must be a non-empty string.\" };\n }\n if (options.apikeys !== undefined && (!Array.isArray(options.apikeys) || !options.apikeys.every(name => isnonempty(name)))) return { allowed: false, reason: \"The reviewed api key reference list must be a list of non-empty stored names.\" };\n const policycheck = validatefetchoptions(options.fetchoptions);\n if (!policycheck.allowed) return policycheck;\n const fetchpolicy = fetchoptionsvalues(options.fetchoptions);\n const budget = fetchbudgetallowed(fetchpolicy.timeout, fetchpolicy.retries, fetchpolicy.backoff, fetchnumeric(options, \"wait\"));\n if (!budget.allowed) return budget;\n }\n return { allowed: true };\n}\n\n/** Validates one reviewed fetch policy object: timeout, retries, backoff base and redirect follow limit stay user choices with no code ceiling. */\nfunction validatefetchoptions(value: unknown): policyevaluation {\n if (value === undefined) return { allowed: true };\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return { allowed: false, reason: \"The reviewed fetch options must be an object with timeout, retries, backoff and follow.\" };\n const options = value as Record<string, unknown>;\n for (const key of [\"timeout\", \"backoff\"]) {\n if (options[key] !== undefined && (typeof options[key] !== \"number\" || !Number.isFinite(options[key]) || options[key] < 0)) return { allowed: false, reason: `The reviewed fetch ${key} must be zero or a positive number with no code ceiling.` };\n }\n for (const key of [\"retries\", \"follow\"]) {\n if (options[key] !== undefined && (typeof options[key] !== \"number\" || !Number.isInteger(options[key]) || options[key] < 0)) return { allowed: false, reason: `The reviewed fetch ${key} must be zero or a positive integer with no code ceiling.` };\n }\n return { allowed: true };\n}\n\n/** Reads the numeric fetch policy fields of one reviewed fetch options object. */\nfunction fetchoptionsvalues(value: unknown): { timeout?: number | undefined; retries?: number | undefined; backoff?: number | undefined } {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return {};\n const options = value as Record<string, unknown>;\n return { timeout: fetchnumeric(options, \"timeout\"), retries: fetchnumeric(options, \"retries\"), backoff: fetchnumeric(options, \"backoff\") };\n}\n\n/** Reads one numeric fetch policy field from the step options. */\nfunction fetchnumeric(options: Record<string, unknown>, key: string): number | undefined {\n const value = options[key];\n return typeof value === \"number\" && Number.isFinite(value) ? value : undefined;\n}\n\n/** True when the kind records user activity and needs the reviewed recording consent before it starts. */\nexport function isrecordingkind(kind: actionkind): boolean {\n return kind === \"recordscreen\" || kind === \"captureaudio\";\n}\n\n/** Validates the reviewed socket and stream parameter grammar of the 1.1.43 family: channel urls with protocols, reconnect budgets and backoff ceilings, multiplexed message payloads, message filters with dotted paths and match limits, event subscriptions with cancellation paths and long poll cursors with intervals kept inside the reviewed wait budget; every bound stays a user choice with no code ceiling. */\nfunction validatesocketgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"opensocket\") {\n const channel = channeloptionsof(options.socket);\n if (!channel) return { allowed: false, reason: \"A reviewed socket with a url is required in options.socket.\" };\n if (channel.options.reconnect !== undefined && !Number.isInteger(channel.options.reconnect)) return { allowed: false, reason: \"The reviewed socket reconnect budget must be an integer attempt count with no code ceiling.\" };\n for (const label of [\"backoff\", \"backoffceiling\"] as const) {\n const value = channel.options[label];\n if (value !== undefined && (typeof value !== \"number\" || !Number.isFinite(value) || value < 0)) return { allowed: false, reason: `The reviewed socket ${label} must be zero or a positive number of milliseconds with no code ceiling.` };\n }\n if (channel.options.lifetime !== undefined && (typeof channel.options.lifetime !== \"number\" || !Number.isFinite(channel.options.lifetime) || channel.options.lifetime <= 0)) return { allowed: false, reason: \"The reviewed socket lifetime window must be a positive number of milliseconds.\" };\n }\n if (kind === \"sendmessage\") {\n const message = options.message;\n if (!message || typeof message !== \"object\" || Array.isArray(message)) return { allowed: false, reason: \"A reviewed message with a channel, stream and payload is required in options.message.\" };\n const envelope = message as Record<string, unknown>;\n if (!isnonempty(envelope.channel)) return { allowed: false, reason: \"The reviewed message needs the open channel id in options.message.channel.\" };\n if (envelope.stream !== undefined && !isnonempty(envelope.stream)) return { allowed: false, reason: \"The reviewed message stream name must be a non-empty string.\" };\n if (typeof envelope.payload !== \"string\") return { allowed: false, reason: \"The reviewed message payload must be a string.\" };\n }\n if (kind === \"waitmessage\") {\n if (options.filter !== undefined) {\n const filter = options.filter;\n if (!filter || typeof filter !== \"object\" || Array.isArray(filter)) return { allowed: false, reason: \"The reviewed message filter must be an object of stream, path and limit.\" };\n const reviewed = filter as Record<string, unknown>;\n if (reviewed.stream !== undefined && !isnonempty(reviewed.stream)) return { allowed: false, reason: \"The reviewed message filter stream name must be a non-empty string.\" };\n if (reviewed.path !== undefined && (typeof reviewed.path !== \"string\" || !validpath(reviewed.path.trim()))) return { allowed: false, reason: \"The reviewed message filter path must be a dotted path of non-empty segments.\" };\n if (reviewed.limit !== undefined && (typeof reviewed.limit !== \"number\" || !Number.isInteger(reviewed.limit) || reviewed.limit < 1)) return { allowed: false, reason: \"The reviewed message match limit must be a positive integer with no code ceiling.\" };\n }\n if (options.wait !== undefined && (typeof options.wait !== \"number\" || !Number.isFinite(options.wait) || options.wait < 0)) return { allowed: false, reason: \"The reviewed message wait budget must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"subscribesse\") {\n const subscription = subscriptionoptionsof(options.subscription);\n if (!subscription) return { allowed: false, reason: \"A reviewed subscription with an event stream url and a cancellation path is required in options.subscription.\" };\n const rawlifetime = options.subscription && typeof options.subscription === \"object\" && !Array.isArray(options.subscription) ? (options.subscription as Record<string, unknown>).lifetime : undefined;\n if (rawlifetime !== undefined && (typeof rawlifetime !== \"number\" || !Number.isFinite(rawlifetime) || rawlifetime <= 0)) return { allowed: false, reason: \"The reviewed subscription lifetime window must be a positive number of milliseconds.\" };\n }\n if (kind === \"longpoll\") {\n const cursor = pollcursorof(options.poll);\n if (!cursor) return { allowed: false, reason: \"A reviewed poll cursor with a url, cursor field, interval and stop condition is required in options.poll.\" };\n const wait = options.wait;\n if (wait !== undefined && (typeof wait !== \"number\" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: \"The reviewed long poll wait budget must be zero or a positive number of milliseconds.\" };\n if (wait !== undefined && cursor.interval > wait) return { allowed: false, reason: `The long poll interval of ${cursor.interval} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or a shorter interval.` };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed request observation parameter grammar of the 1.1.43 family: watch windows with user configured match limits, header filters whose redaction list is required before any header value is stored, body filters with url patterns, mime lists and byte ceilings and api replay specs with known verbs and dotted extraction paths. */\nfunction validatenetwatchgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"watchrequests\") {\n if (options.watch !== undefined) {\n const watch = options.watch;\n if (!watch || typeof watch !== \"object\" || Array.isArray(watch)) return { allowed: false, reason: \"The reviewed watch window must be an object.\" };\n const reviewed = watch as Record<string, unknown>;\n if (reviewed.window !== undefined && (typeof reviewed.window !== \"number\" || !Number.isFinite(reviewed.window) || reviewed.window < 0)) return { allowed: false, reason: \"The reviewed watch window must be zero or a positive number of milliseconds.\" };\n }\n if (options.limit !== undefined && (typeof options.limit !== \"number\" || !Number.isInteger(options.limit) || options.limit < 1)) return { allowed: false, reason: \"The reviewed watch match limit must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"readheaders\") {\n const headers = options.headers;\n if (!headers || typeof headers !== \"object\" || Array.isArray(headers)) return { allowed: false, reason: \"A reviewed header filter with a name allowlist and a redaction list is required in options.headers.\" };\n const reviewed = headers as Record<string, unknown>;\n if (!Array.isArray(reviewed.allow) || reviewed.allow.length === 0 || !reviewed.allow.every((name): name is string => isnonempty(name))) return { allowed: false, reason: \"The reviewed header allowlist must be a non-empty list of header names.\" };\n if (!Array.isArray(reviewed.redact) || reviewed.redact.length === 0 || !reviewed.redact.every((name): name is string => isnonempty(name))) return { allowed: false, reason: \"Header capture requires a reviewed redaction list before any header value is stored.\" };\n }\n if (kind === \"capturebodies\") {\n const body = options.body;\n if (!body || typeof body !== \"object\" || Array.isArray(body)) return { allowed: false, reason: \"A reviewed body filter with a url pattern, mime list and byte ceiling is required in options.body.\" };\n const reviewed = body as Record<string, unknown>;\n if (reviewed.urlpattern !== undefined && !isnonempty(reviewed.urlpattern)) return { allowed: false, reason: \"The reviewed body url pattern must be a non-empty string.\" };\n if (reviewed.mimes !== undefined && (!Array.isArray(reviewed.mimes) || reviewed.mimes.length === 0 || !reviewed.mimes.every((mime): mime is string => isnonempty(mime)))) return { allowed: false, reason: \"The reviewed body mime list must be a non-empty list of mime types.\" };\n if (reviewed.ceiling !== undefined && (typeof reviewed.ceiling !== \"number\" || !Number.isFinite(reviewed.ceiling) || reviewed.ceiling < 0)) return { allowed: false, reason: \"The reviewed body byte ceiling must be zero or a positive number of bytes with no code ceiling.\" };\n }\n if (kind === \"mapapi\") {\n if (options.limit !== undefined && (typeof options.limit !== \"number\" || !Number.isInteger(options.limit) || options.limit < 1)) return { allowed: false, reason: \"The reviewed mapapi match limit must be a positive integer with no code ceiling.\" };\n }\n if (kind === \"extractapi\") {\n const replay = apireplayspecof(options.replay);\n if (!replay) return { allowed: false, reason: \"A reviewed replay spec with an endpoint is required in options.replay.\" };\n if (!ishttpsurl(replay.endpoint)) return { allowed: false, reason: \"The reviewed replay endpoint must be an HTTPS url.\" };\n if (replay.verb !== undefined && ![\"GET\", \"HEAD\", \"POST\", \"PUT\", \"PATCH\", \"DELETE\", \"OPTIONS\"].includes(replay.verb)) return { allowed: false, reason: \"The reviewed replay verb must be a known HTTP verb.\" };\n for (const path of replay.paths ?? []) {\n if (!validpath(path.trim())) return { allowed: false, reason: `The reviewed replay extraction path ${path} must be a dotted path of non-empty segments.` };\n }\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed network control parameter grammar of the 1.1.44 family: block rules with url patterns that name their origin, mock fixtures reviewed with their full body, header rewrite rules with named origin patterns and set, append and remove operations, cookie records scoped to granted domains, oauth flows with provider consent refs, api key entries behind explicit consent, proxy routes with required bypass lists, urlencoded form payloads and multipart uploads whose every file carries the explicit reviewed flag; every bound stays a user choice with no code ceiling. */\nfunction validatecontrolgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"blockrequest\") {\n const rule = blockruleof(options.block);\n if (!rule) return { allowed: false, reason: \"A reviewed block rule with a url pattern is required in options.block.\" };\n if (patternorigin(rule.urlpattern) === undefined) return { allowed: false, reason: \"Block rules need an https origin pattern; patterns without a named origin are refused.\" };\n if ((options.block as Record<string, unknown>).reviewed !== true) return { allowed: false, reason: \"The block rule carries the explicit reviewed flag before any request is blocked.\" };\n }\n if (kind === \"mockresponse\") {\n const spec = mockspecof(options.mock);\n if (!spec) return { allowed: false, reason: \"A reviewed mock fixture with a url pattern, status and its reviewed body or a captured body ref is required in options.mock.\" };\n if (patternorigin(spec.urlpattern) === undefined) return { allowed: false, reason: \"Mock fixtures need an https origin pattern; patterns without a named origin are refused.\" };\n if (spec.reviewed !== true) return { allowed: false, reason: \"Every mock fixture is reviewed with its full body or the referenced captured body through the explicit reviewed flag before it serves.\" };\n }\n if (kind === \"rewriteheaders\") {\n const rules = options.rules;\n if (!Array.isArray(rules) || rules.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of header rewrite rules is required in options.rules.\" };\n for (const item of rules) {\n const rule = headeruleof(item);\n if (!rule) return { allowed: false, reason: \"Every header rewrite rule needs a url pattern, header name, a set, append or remove operation and its value.\" };\n if (patternorigin(rule.urlpattern) === undefined) return { allowed: false, reason: \"Header rewrite rules must name their origin pattern explicitly; patterns without a named origin are refused.\" };\n }\n }\n if (kind === \"setcookies\") {\n const cookies = options.cookies;\n if (!Array.isArray(cookies) || cookies.length === 0) return { allowed: false, reason: \"A reviewed non-empty list of cookie records is required in options.cookies.\" };\n for (const item of cookies) {\n if (!cookierecordof(item)) return { allowed: false, reason: \"Every cookie record needs a name, domain, path and reviewed string value with an optional expiry.\" };\n }\n }\n if (kind === \"readcookies\" && options.domain !== undefined && !isnonempty(options.domain)) return { allowed: false, reason: \"The reviewed cookie read domain must be a non-empty host.\" };\n if (kind === \"clearcookies\") {\n if (!isnonempty(options.domain)) return { allowed: false, reason: \"A reviewed cookie domain is required before cookies are cleared.\" };\n if (options.names !== undefined && (!Array.isArray(options.names) || options.names.length === 0 || !options.names.every((name): name is string => isnonempty(name)))) return { allowed: false, reason: \"The reviewed cookie clear list must be a non-empty list of cookie names when present.\" };\n }\n if (kind === \"authflow\") {\n const flow = oauthflowof(options.oauth);\n if (!flow) return { allowed: false, reason: \"A reviewed oauth flow with provider, authorize url, token url, scopes and redirect origin is required in options.oauth.\" };\n if (!ishttpsurl(flow.authorizeurl) || !ishttpsurl(flow.tokenurl)) return { allowed: false, reason: \"The oauth authorize and token urls must use HTTPS.\" };\n if (!ishttpsurl(flow.redirectorigin) && !/^https:\\/\\/[^/]+\\/?$/.test(flow.redirectorigin)) return { allowed: false, reason: \"The oauth redirect origin must be an HTTPS origin inside the grants.\" };\n const consent = authconsentgranted(step);\n if (!consent.allowed) return consent;\n }\n if (kind === \"saveapikey\") {\n const key = options.key;\n if (!key || typeof key !== \"object\" || Array.isArray(key)) return { allowed: false, reason: \"A reviewed api key entry with name, origin scopes and header is required in options.key.\" };\n const entry = key as Record<string, unknown>;\n if (!isnonempty(entry.name)) return { allowed: false, reason: \"The api key entry needs a reviewed non-empty name.\" };\n if (!Array.isArray(entry.origins) || entry.origins.length === 0 || !entry.origins.every((item): item is string => ishttpsurl(item))) return { allowed: false, reason: \"The api key needs a reviewed non-empty list of HTTPS origin scopes.\" };\n if (!isnonempty(entry.header)) return { allowed: false, reason: \"The api key entry needs a reviewed non-empty header name.\" };\n if (typeof entry.value !== \"string\" || !entry.value) return { allowed: false, reason: \"The api key needs its secret value in the reviewed options; it never enters the audit trail.\" };\n const consent = apikeyconsentgranted(step);\n if (!consent.allowed) return consent;\n }\n if (kind === \"routeproxy\") {\n if (!proxyrouteof(options.proxy)) return { allowed: false, reason: \"A reviewed proxy route with scheme, host, port and a non-empty bypass list is required in options.proxy.\" };\n if (!isnonempty(options.consentref)) return { allowed: false, reason: \"Proxy routing needs the explicit reviewed consent ref before any route applies.\" };\n }\n if (kind === \"postform\") {\n const form = formpayloadof(options.form);\n if (!form) return { allowed: false, reason: \"A reviewed form payload with a url and a non-empty field list is required in options.form.\" };\n if (!ishttpsurl(form.url)) return { allowed: false, reason: \"The form submission target must use HTTPS.\" };\n if (options.wait !== undefined && (typeof options.wait !== \"number\" || !Number.isFinite(options.wait) || options.wait < 0)) return { allowed: false, reason: \"The reviewed rate limit wait budget must be zero or a positive number of milliseconds.\" };\n }\n if (kind === \"postfiles\") {\n const upload = multipartpayloadof(options.upload);\n if (!upload) return { allowed: false, reason: \"A reviewed multipart upload with a url and reviewed files is required in options.upload; every file carries the explicit reviewed flag.\" };\n if (!ishttpsurl(upload.url)) return { allowed: false, reason: \"The multipart upload target must use HTTPS.\" };\n if (options.wait !== undefined && (typeof options.wait !== \"number\" || !Number.isFinite(options.wait) || options.wait < 0)) return { allowed: false, reason: \"The reviewed rate limit wait budget must be zero or a positive number of milliseconds.\" };\n }\n return { allowed: true };\n}\n\n/** Requires the reviewed block rule of a live session before any blockrequest runs: the rule must carry the explicit reviewed flag and the session must stay active, unpaused and unexpired; every rule applies for the run only and reverts at run end. */\nexport function blockgate(session: agentsession | undefined, step: toolstep, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the request block.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot block requests.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot block requests.\" };\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const rule = options.block;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule) || (rule as Record<string, unknown>).reviewed !== true) return { allowed: false, reason: \"Request blocking needs its reviewed block rule with the explicit reviewed flag before any rule applies.\" };\n if (!blockruleof(rule)) return { allowed: false, reason: \"The block rule needs a url pattern and an optional resource type list.\" };\n return { allowed: true };\n}\n\n/** Scopes every cookie kind to a granted domain of a live session: the domain must equal a granted origin host or sit beneath it, and every other domain is refused. */\nexport function cookiegate(session: agentsession | undefined, domain: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the cookie operation.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot touch cookies.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot touch cookies.\" };\n const grants = session.grants ?? [session.origin];\n if (!cookiedomaingranted(domain, grants)) return { allowed: false, reason: `The cookie domain ${domain} stays outside the session origin grants; cookie control refuses domains beyond the grants.` };\n return { allowed: true };\n}\n\n/** Requires the explicit reviewed consent before routeproxy changes routing: a live session, a reviewed consent ref and a valid route with its bypass list; the route applies for the run only and restores the previous state at run end. */\nexport function proxygate(session: agentsession | undefined, step: toolstep, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the proxy route.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot change routing.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot change routing.\" };\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n if (!isnonempty(options.consentref)) return { allowed: false, reason: \"Proxy routing needs the explicit reviewed consent ref before any route applies.\" };\n if (!proxyrouteof(options.proxy)) return { allowed: false, reason: \"The proxy route needs a scheme, host, port and a non-empty bypass list of origins that stay direct.\" };\n return { allowed: true };\n}\n\n/** Requires the reviewed provider consent prompt ref before any authflow runs. */\nexport function authconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"An oauth flow requires the reviewed provider consent prompt ref in options before it starts.\" };\n return { allowed: true };\n}\n\n/** Requires the explicit consent prompt ref before saveapikey stores a key. */\nexport function apikeyconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"Storing an api key requires the explicit reviewed consent prompt ref in options before anything is stored.\" };\n return { allowed: true };\n}\n\n/** Keeps the rate limit wait inside the reviewed wait budget as user configured behavior: the wait until the reset window passes must fit when a budget was reviewed; both bounds stay user choices with no code ceiling. */\nexport function ratelimitbudgetallowed(wait: number | undefined, budget: number | undefined): policyevaluation {\n if (wait !== undefined && (typeof wait !== \"number\" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: \"The rate limit wait must be zero or a positive number of milliseconds.\" };\n if (budget !== undefined && (typeof budget !== \"number\" || !Number.isFinite(budget) || budget < 0)) return { allowed: false, reason: \"The reviewed rate limit budget must be zero or a positive number of milliseconds.\" };\n if (wait !== undefined && budget !== undefined && wait > budget) return { allowed: false, reason: `The rate limit wait of ${wait} milliseconds exceeds the reviewed budget of ${budget} milliseconds; review a wider budget or submit later.` };\n return { allowed: true };\n}\n\n/** Requires the active tab grant of the live session for every debugging kind: the timeline gate scopes console, error and task capture to the run tab only and refuses every other tab. */\nexport function timelinegate(session: agentsession | undefined, tabid: number, origin: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the timeline capture.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot capture the timeline.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot capture the timeline.\" };\n if (session.tabid !== tabid) return { allowed: false, reason: `The timeline capture needs the run tab ${session.tabid} and refuses tab ${tabid}.` };\n if (!origingranted(session, origin)) return { allowed: false, reason: `The timeline capture of ${origin} needs the session origin grants first.` };\n return { allowed: true };\n}\n\n/** True when one approved console capture consent of that origin exists; console capture on a new origin prompts once and the approved decision persists. */\nexport function consoleconsentcovers(origin: string, consents: consoleconsentrecord[]): policyevaluation {\n if (consents.some(consent => consent.origin === origin && consent.approved === true)) return { allowed: true };\n return { allowed: false, reason: `Console capture on ${origin} needs the reviewed console consent first; approve the prompt in the review panel.` };\n}\n\n/** Requires the granted origin before stack frames are captured; stack capture outside the granted origin is refused. */\nexport function stackgate(session: agentsession | undefined, origin: string): policyevaluation {\n if (!origingranted(session, origin)) return { allowed: false, reason: `Stack capture of ${origin} stays outside the session origin grants.` };\n return { allowed: true };\n}\n\n/** Keeps the debug watch window inside the reviewed wait budget: the watch wait must fit the reviewed budget when one was reviewed; both bounds stay user choices with no code ceiling. */\nexport function debugwaitbudgetallowed(watchwindow: number | undefined, wait: number | undefined): policyevaluation {\n if (watchwindow !== undefined && (typeof watchwindow !== \"number\" || !Number.isFinite(watchwindow) || watchwindow < 0)) return { allowed: false, reason: \"The debug watch window must be zero or a positive number of milliseconds.\" };\n if (wait !== undefined && (typeof wait !== \"number\" || !Number.isFinite(wait) || wait < 0)) return { allowed: false, reason: \"The reviewed debug wait budget must be zero or a positive number of milliseconds.\" };\n if (watchwindow !== undefined && wait !== undefined && watchwindow > wait) return { allowed: false, reason: `The debug watch window of ${watchwindow} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or a shorter window.` };\n return { allowed: true };\n}\n\n/** Exposes the timeline retention window as a user configured choice; an absent value keeps every timeline entry forever while the level count summaries always survive. */\nexport function timelineretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.timelineretention;\n}\n\n/** Grades console diffing as read only comparison evidence: the diff compares two stored console outputs and touches no page or browser state. */\nexport function diffreviewgrade(): { risk: \"read\"; mode: \"diffing\"; evidence: \"comparison\" } {\n return { risk: \"read\", mode: \"diffing\", evidence: \"comparison\" };\n}\n\n/** Validates the reviewed debugging parameter grammar of the 1.1.45 family: a watch window inside the reviewed wait budget, level floors from the reviewed level set, source filters from the reviewed source grammar, spam rules with user configured thresholds, serialization depth bounds, rotation rules with no hardcoded entry ceiling and the required redaction pattern list before any console text is captured. */\nfunction validatetimelinegrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n let watchwindow: number | undefined;\n if (options.watch !== undefined) {\n const watch = options.watch;\n if (!watch || typeof watch !== \"object\" || Array.isArray(watch)) return { allowed: false, reason: \"The reviewed debug watch window must be an object.\" };\n const reviewed = watch as Record<string, unknown>;\n if (reviewed.window !== undefined) {\n if (typeof reviewed.window !== \"number\" || !Number.isFinite(reviewed.window) || reviewed.window < 0) return { allowed: false, reason: \"The reviewed debug watch window must be zero or a positive number of milliseconds.\" };\n watchwindow = reviewed.window;\n }\n }\n const budgetcheck = debugwaitbudgetallowed(watchwindow, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n if (options.level !== undefined && !loglevels.includes(options.level as loglevel)) return { allowed: false, reason: `The reviewed level floor must be one of ${loglevels.join(\", \")}.` };\n if (options.sources !== undefined) {\n if (!Array.isArray(options.sources) || options.sources.length === 0 || !options.sources.every(source => timelinesources.includes(source as never))) return { allowed: false, reason: `The reviewed source filters must be a non-empty list of the reviewed timeline sources: ${timelinesources.join(\", \")}.` };\n }\n if (kind === \"watchconsole\") {\n if (options.redact === undefined || !Array.isArray(options.redact) || options.redact.length === 0 || !options.redact.every(pattern => isnonempty(pattern))) return { allowed: false, reason: \"Console capture requires a reviewed non-empty redaction pattern list before any console text is captured.\" };\n if (options.depth !== undefined && (typeof options.depth !== \"number\" || !Number.isInteger(options.depth) || options.depth < 1)) return { allowed: false, reason: \"The reviewed serialization depth bound must be a positive integer with no code ceiling.\" };\n if (options.spam !== undefined) {\n const rule = spamruleof(options.spam);\n if (!rule) return { allowed: false, reason: \"The reviewed spam rule needs a pattern, a window size and a collapse threshold.\" };\n if (rule.collapse < 1) return { allowed: false, reason: \"The reviewed spam collapse threshold must be a positive integer of user configured value with no code ceiling.\" };\n }\n if (options.rotation !== undefined) {\n const rule = rotationruleof(options.rotation);\n if (!rule) return { allowed: false, reason: \"The reviewed rotation rule needs a max entry count and an overflow target.\" };\n }\n }\n if (kind === \"watchtasks\") {\n if (options.threshold !== undefined && (typeof options.threshold !== \"number\" || !Number.isFinite(options.threshold) || options.threshold < 0)) return { allowed: false, reason: \"The reviewed long task threshold must be zero or a positive number of milliseconds with no code ceiling.\" };\n }\n return { allowed: true };\n}\n\n/** Normalizes a reviewed spam rule: the pattern, the window size and the collapse threshold as user configured values. */\nexport function spamruleof(value: unknown): spamrule | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const entry = value as Record<string, unknown>;\n const pattern = typeof entry.pattern === \"string\" ? entry.pattern : \"\";\n const windowsize = typeof entry.windowsize === \"number\" && Number.isFinite(entry.windowsize) && entry.windowsize >= 0 ? entry.windowsize : undefined;\n const collapse = typeof entry.collapse === \"number\" && Number.isInteger(entry.collapse) ? entry.collapse : undefined;\n if (windowsize === undefined || collapse === undefined) return undefined;\n return { pattern, windowsize, collapse };\n}\n\n/** Normalizes a reviewed log rotation rule: the max entries per run and the overflow target store with no hardcoded entry ceiling. */\nexport function rotationruleof(value: unknown): rotationrule | undefined {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return undefined;\n const entry = value as Record<string, unknown>;\n const maxentries = typeof entry.maxentries === \"number\" && Number.isInteger(entry.maxentries) && entry.maxentries >= 1 ? entry.maxentries : undefined;\n const overflowtarget = typeof entry.overflowtarget === \"string\" && entry.overflowtarget.trim() ? entry.overflowtarget.trim() : undefined;\n if (maxentries === undefined || overflowtarget === undefined) return undefined;\n return { maxentries, overflowtarget };\n}\n\n/** Requires the active run tab grant of the live session for every devtools protocol kind: the debug gate scopes attaches, commands, watches, breakpoints, steps and overrides to the run tab only and refuses every other tab. */\nexport function debuggate(session: agentsession | undefined, tabid: number, origin: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the devtools protocol step.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot run a devtools protocol step.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot run a devtools protocol step.\" };\n if (session.tabid !== tabid) return { allowed: false, reason: `The devtools protocol step needs the run tab ${session.tabid} and refuses tab ${tabid}.` };\n if (!origingranted(session, origin)) return { allowed: false, reason: `The devtools protocol step on ${origin} needs the session origin grants first.` };\n return { allowed: true };\n}\n\n/** True when one approved debugger consent of that origin covers every requested domain; the first attachcdp of a run needs the approved record and revocation removes the coverage. */\nexport function debuggerconsentcovers(origin: string, domains: string[], grants: debuggergrant[]): policyevaluation {\n const needed = [...new Set(domains)];\n const covering = grants.find(grant => grant.origin === origin && grant.approved === true && grant.revokedat === undefined && needed.every(domain => grant.domains.includes(domain)));\n if (covering) return { allowed: true };\n if (grants.some(grant => grant.origin === origin && grant.revokedat !== undefined)) return { allowed: false, reason: `The debugger consent on ${origin} was revoked; approve a new prompt before the devtools protocol runs again.` };\n return { allowed: false, reason: `The devtools protocol on ${origin} needs the reviewed debugger consent for ${needed.join(\", \")} first; approve the prompt with the domain allowlist shown in the review panel.` };\n}\n\n/** The profiling target gate of every 1.1.47 kind: the live session run tab and origin grants come first, every iframe, worker and service worker target stays inside the granted origins, and the reviewed debugger grant of the origin covers every profiling instrument because profiling is debugger grade instrumentation. */\nexport function targetgate(input: { session: agentsession | undefined; tabid: number; origin: string; targets: attachtarget[]; grants: debuggergrant[] | undefined; now: number }): policyevaluation {\n const base = debuggate(input.session, input.tabid, input.origin, input.now);\n if (!base.allowed) return base;\n for (const target of input.targets) {\n if (target.kind === \"page\") continue;\n const origincheckresult = origincheck(input.session, target.url);\n if (!origincheckresult.allowed) return { allowed: false, reason: `The ${target.kind} target ${target.url} stays outside the granted origins; profiling refuses to attach.` };\n }\n if (input.grants === undefined) return { allowed: true };\n const consent = debuggerconsentcovers(input.origin, [], input.grants);\n if (!consent.allowed) return { allowed: false, reason: `The profiling step on ${input.origin} needs the reviewed debugger grant of the origin first; approve the prompt with the profiling derivation shown in the review panel.` };\n return { allowed: true };\n}\n\n/** True when one approved source map capture consent of that origin covers the capture; revocation removes the coverage and the next capture needs a new reviewed prompt. */\nexport function sourcemapconsentcovers(origin: string, consents: sourcemapconsent[]): policyevaluation {\n const covering = consents.find(consent => consent.origin === origin && consent.approved === true && consent.revokedat === undefined);\n if (covering) return { allowed: true };\n if (consents.some(consent => consent.origin === origin && consent.revokedat !== undefined)) return { allowed: false, reason: `The source map capture consent on ${origin} was revoked; approve a new prompt before another map file is fetched.` };\n return { allowed: false, reason: `The source map capture on ${origin} needs the reviewed per origin consent first; approve the prompt shown in the review panel.` };\n}\n\n/** Exposes the user configured retention window for the heavy profile bytes; an absent window keeps every snapshot, sample and trace file. */\nexport function profileretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.profileretention;\n}\n\n/** Exposes the user configured trace byte ceiling; an absent value never refuses a trace export because the cap stays a user choice only. */\nexport function traceceilingof(settings: runsettings | undefined): number | undefined {\n return settings?.traceceiling;\n}\n\n/** Validates one breakpoint condition against the reviewed expression grammar: member chains, literals of number, string, boolean and null, comparison and logic operators, negation and parentheses; assignments, calls and statements are refused. */\nexport function validatebreakpointcondition(condition: string): policyevaluation {\n const expression = condition.trim();\n if (expression.length === 0) return { allowed: false, reason: \"The breakpoint condition must not be empty.\" };\n if (/(?<![=!<>])=(?!=)/.test(expression)) return { allowed: false, reason: \"Breakpoint conditions refuse assignment because the reviewed grammar is comparison only.\" };\n if (/[A-Za-z_$][\\w$]*\\s*\\(/.test(expression)) return { allowed: false, reason: \"Breakpoint conditions refuse calls because the reviewed grammar is comparison only.\" };\n const literal = /^(?:-?\\d+(?:\\.\\d+)?|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|true|false|null)$/;\n const tokens = expression.match(/(?:[A-Za-z_$][\\w$]*|-?\\d+(?:\\.\\d+)?|\"(?:[^\"\\\\]|\\\\.)*\"|'(?:[^'\\\\]|\\\\.)*'|===|!==|==|!=|>=|<=|&&|\\|\\||[!.<>()+\\-*\\/%])/g);\n if (tokens === null || tokens.join(\"\") !== expression.replace(/\\s+/g, \"\")) return { allowed: false, reason: \"The breakpoint condition must use the reviewed expression grammar of member chains, literals, comparisons, logic operators, negation and parentheses.\" };\n const identifierlike = /^(?:true|false|null)$/;\n for (const token of tokens) {\n if (literal.test(token) || identifierlike.test(token)) continue;\n if ([\"===\", \"!==\", \"==\", \"!=\", \">=\", \"<=\", \"&&\", \"||\", \"!\", \".\", \"(\", \")\", \"<\", \">\", \"+\", \"-\", \"*\", \"/\", \"%\"].includes(token)) continue;\n if (/^[A-Za-z_$][\\w$]*$/.test(token)) continue;\n return { allowed: false, reason: `The token ${token} of the breakpoint condition stays outside the reviewed expression grammar.` };\n }\n return { allowed: true };\n}\n\n/** Keeps the breakpoint count of one run inside the user configured ceiling: an absent ceiling never refuses a breakpoint because the cap stays a user choice only. */\nexport function breakpointbudgetallowed(active: number, ceiling: number | undefined): policyevaluation {\n if (ceiling === undefined) return { allowed: true };\n if (typeof ceiling !== \"number\" || !Number.isInteger(ceiling) || ceiling < 0) return { allowed: false, reason: \"The reviewed breakpoint ceiling must be zero or a positive integer of user configured value with no code ceiling.\" };\n if (active >= ceiling) return { allowed: false, reason: `The run already holds ${active} active breakpoint${active === 1 ? \"\" : \"s\"} and the reviewed breakpoint ceiling is ${ceiling}; revert one or review a wider ceiling.` };\n return { allowed: true };\n}\n\n/** Exposes the pause capture retention window as a user configured choice; an absent value keeps every pause capture with its call frames. */\nexport function pauseretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.pauseretention;\n}\n\n/** Exposes the user configured breakpoint ceiling; an absent value never refuses a breakpoint because the cap stays a user choice only. */\nexport function breakpointceilingof(settings: runsettings | undefined): number | undefined {\n return settings?.breakpointceiling;\n}\n\n/** Requires the review of every emulation layer before it applies: a live session on the run tab, an approved plan, the explicit reviewed flag on the layer options and the reviewed revert plan beside it. */\nexport function emugate(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: \"emulate the run tab\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Emulation layers need an approved plan before they apply.\" };\n let options: Record<string, unknown> = {};\n try { options = parseoptions(input.step); } catch { options = {}; }\n if (options.reviewed !== true) return { allowed: false, reason: `The ${input.step.kind} layer needs the explicit reviewed flag before any mask applies.` };\n if (revertplanof(options.revertplan) === undefined) return { allowed: false, reason: `Every ${input.step.kind} layer needs a reviewed revert plan beside it before any mask applies.` };\n return { allowed: true };\n}\n\n/** Allows layer stacking only when the reviewed plan lists the steps: a second layer of one family needs at least two reviewed steps of that family in the same plan because the last applied layer wins conflicts. */\nexport function emulationstackallowed(plan: agentplan | undefined, kind: actionkind, active: number): policyevaluation {\n if (!plan) return { allowed: false, reason: \"Layer stacking needs the reviewed plan first.\" };\n const listed = plan.steps.filter(step => step.kind === kind).length;\n if (active >= listed) return { allowed: false, reason: `The plan lists ${listed} reviewed ${kind} step${listed === 1 ? \"\" : \"s\"} and ${active} layer${active === 1 ? \"\" : \"s\"} of that family are already active; stacking beyond the reviewed plan is refused.` };\n return { allowed: true };\n}\n\n/** True when one approved location consent of that origin covers the reviewed coordinates; the prompt shows the exact latitude and longitude before emulatelocate applies. */\nexport function locationconsentgate(origin: string, latitude: number, longitude: number, consents: locationconsent[]): policyevaluation {\n if (consents.some(consent => consent.origin === origin && consent.revokedat !== undefined)) return { allowed: false, reason: `The location consent on ${origin} was revoked; approve a new prompt before the location override runs again.` };\n if (locationconsentcovers(origin, latitude, longitude, consents)) return { allowed: true };\n return { allowed: false, reason: `The location override of ${latitude}, ${longitude} on ${origin} needs the reviewed location consent first; approve the prompt with the coordinates shown in the review panel.` };\n}\n\n/** Exposes the user configured retention window for reverted emulation layer states; an absent window keeps every prior state while the layer history always survives. */\nexport function emulationretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.emulationretention;\n}\n\n/** Validates the reviewed emulation parameter grammar of the 1.1.48 family: device presets with width, height, pixel ratio and the mobile flag plus the reviewed reload flag, network presets with latency, download and upload bounds and the offline window, location presets inside the latitude and longitude ranges behind the location consent, agent presets of the reviewed user agent grammar with platform and brand list, permission overrides of the reviewed browser permission set graded by name, blackbox rules of explicit origin patterns with their trace scope, and the reviewed revert plan beside every layer. */\nfunction validateemulationgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (revertplanof(options.revertplan) === undefined) return { allowed: false, reason: `Every ${kind} layer needs a reviewed revert plan before any mask applies.` };\n if (kind === \"emulatedevice\") {\n const preset = devicepresetof(options.device);\n if (!preset) return { allowed: false, reason: \"The device layer needs a reviewed preset with a name, positive integer width and height and a positive pixel ratio.\" };\n if (options.reload !== undefined && typeof options.reload !== \"boolean\") return { allowed: false, reason: \"The reviewed reload flag must be a boolean; the page reloads only when the reviewed plan asks.\" };\n return { allowed: true };\n }\n if (kind === \"emulatenetwork\") {\n const preset = networkpresetof(options.network);\n if (!preset) return { allowed: false, reason: \"The network layer needs a reviewed preset with a name and zero or positive latency, download and upload bounds.\" };\n if (options.window !== undefined && (typeof options.window !== \"number\" || !Number.isFinite(options.window) || options.window < 0)) return { allowed: false, reason: \"The reviewed offline window must be zero or a positive number of milliseconds with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"emulatelocate\") {\n const preset = locationpresetof(options.location);\n if (!preset) return { allowed: false, reason: \"The location layer needs a reviewed preset with a name, a latitude inside -90 and 90, a longitude inside -180 and 180 and a zero or positive accuracy radius.\" };\n if (!locationrangevalid(preset.latitude, preset.longitude)) return { allowed: false, reason: \"The reviewed latitude must stay inside -90 and 90 degrees and the longitude inside -180 and 180 degrees.\" };\n return { allowed: true };\n }\n if (kind === \"setuseragent\") {\n const preset = agentpresetof(options.agent);\n if (!preset) return { allowed: false, reason: \"The agent layer needs a reviewed preset with a user agent string of the reviewed grammar, a platform and a non-empty brand list.\" };\n if (!agentgrammarvalid(preset.useragent)) return { allowed: false, reason: \"The reviewed user agent string must use the reviewed grammar of tokens, separators and version marks without line breaks.\" };\n return { allowed: true };\n }\n if (kind === \"overridepermission\") {\n const grant = permissiongrantof(options.permission);\n if (!grant) return { allowed: false, reason: `The permission override needs a reviewed name of the browser permission set (${browserpermissions.join(\", \")}) and a state of ${permissionstates.join(\", \")}.` };\n void permissiongrade(grant.name);\n return { allowed: true };\n }\n if (kind === \"blackboxscripts\") {\n const rules = Array.isArray(options.rules) ? options.rules.flatMap(rule => { const parsed = blackboxruleof(rule); return parsed !== undefined ? [parsed] : []; }) : [];\n if (rules.length === 0) return { allowed: false, reason: \"The blackbox layer needs a reviewed non-empty rule list where every pattern names its origin explicitly and carries a trace scope.\" };\n return { allowed: true };\n }\n return { allowed: true };\n}\n\n/** Validates one permission override name against the reviewed browser permission set. */\nexport function permissionnamevalid(name: string): policyevaluation {\n if (!browserpermissions.includes(name)) return { allowed: false, reason: `The permission ${name} stays outside the reviewed browser permission set: ${browserpermissions.join(\", \")}.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed session parameter grammar of the 1.1.49 memory family: snapshot plans with the scope, the section toggles of the reviewed grammar and the optional auto interval whose period, maximum snapshot count and expiry stay user choices with no code ceiling, restore plans with their tab, form and capture policies behind the explicit restore review, session filings with unique reviewed names and folders, diffs of two saved records, searches with the term grammar and the field set, exports behind the explicit export review and imports of the known file format behind the full record review. */\nfunction validatesessiongrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"persiststate\") {\n if (options.resume !== undefined && typeof options.resume !== \"boolean\") return { allowed: false, reason: \"The reviewed resume flag must be a boolean.\" };\n return { allowed: true };\n }\n if (kind === \"capturesession\") {\n const plan = snapshotplanof(options.snapshot);\n if (!plan) return { allowed: false, reason: \"The session capture needs a reviewed snapshot plan with its scope, a non-empty section list of the reviewed grammar (tabs, scroll, forms, storage, cookies) and the capture link flag.\" };\n if (plan.auto !== undefined) {\n const interval = autointervalof((options.snapshot as Record<string, unknown>).auto);\n if (interval === undefined) return { allowed: false, reason: \"The reviewed auto snapshot interval needs a positive period, a positive maximum snapshot count and a zero or positive expiry window with no code ceiling.\" };\n }\n return { allowed: true };\n }\n if (kind === \"restoresession\") {\n if (typeof options.sessionid !== \"string\" || !options.sessionid.trim()) return { allowed: false, reason: \"The session restore needs the reviewed session id of the saved record.\" };\n if (restoreplanof(options.restore) === undefined) return { allowed: false, reason: \"The session restore needs a reviewed restore plan with its tab, form and capture policies.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"Every session restore needs the explicit restore review with its tabs, form state and captures listed before it reopens anything.\" };\n return { allowed: true };\n }\n if (kind === \"namedsessions\") {\n if (typeof options.sessionid !== \"string\" || !options.sessionid.trim()) return { allowed: false, reason: \"The session filing needs the reviewed session id of the saved record.\" };\n if (typeof options.name !== \"string\" || !options.name.trim()) return { allowed: false, reason: \"The session filing needs a reviewed non-empty session name.\" };\n if (options.folder !== undefined && (typeof options.folder !== \"string\" || !options.folder.trim())) return { allowed: false, reason: \"The reviewed folder name must be a non-empty string.\" };\n if (options.tags !== undefined && (!Array.isArray(options.tags) || !options.tags.every(tag => typeof tag === \"string\" && tag.trim()))) return { allowed: false, reason: \"The reviewed tag list must be a list of non-empty strings.\" };\n return { allowed: true };\n }\n if (kind === \"diffsessions\") {\n if (typeof options.left !== \"string\" || !options.left.trim() || typeof options.right !== \"string\" || !options.right.trim()) return { allowed: false, reason: \"The session diff needs the reviewed ids of both saved sessions.\" };\n return { allowed: true };\n }\n if (kind === \"searchsessions\") {\n if (searchqueryof(options.query) === undefined) return { allowed: false, reason: \"The session search needs a reviewed query with a non-empty term list, fields of the reviewed grammar (urls, titles, names, text) and an optional time window.\" };\n return { allowed: true };\n }\n if (kind === \"exportsessions\") {\n if (options.reviewed !== true) return { allowed: false, reason: \"Session exports need the explicit export review before any session file leaves the device.\" };\n if (options.ids !== undefined && (!Array.isArray(options.ids) || options.ids.length === 0 || !options.ids.every(id => typeof id === \"string\" && id.trim()))) return { allowed: false, reason: \"The reviewed export id list must be a non-empty list of saved session ids.\" };\n return { allowed: true };\n }\n if (kind === \"importsessions\") {\n if (options.reviewed !== true) return { allowed: false, reason: \"Session imports need the explicit full record review before any record joins the library.\" };\n if (importsessionfile(options.file) === undefined) return { allowed: false, reason: \"The session import needs a reviewed file of the known format version with an intact checksum.\" };\n return { allowed: true };\n }\n return { allowed: true };\n}\n\n/** Requires the explicit restore review flag and the reviewed restore plan before any session restore reopens a tab; the review lists every tab, form state and capture first. */\nexport function restorereviewgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n if (restoreplanof(options.restore) === undefined) return { allowed: false, reason: \"Every session restore needs a reviewed restore plan with its tab, form and capture policies.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"The session restore needs the explicit restore review of its tabs, form state and captures before it reopens anything.\" };\n return { allowed: true };\n}\n\n/** The session consent gate of every session memory step: a live session, an approved plan and the restore review of every restore; crash restore prompts stay inside the same consent model. */\nexport function sessionrestoregate(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: \"run the session memory step\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Session memory steps need an approved plan before they run.\" };\n if (input.step.kind === \"restoresession\") return restorereviewgranted(input.step);\n return { allowed: true };\n}\n\n/** Returns the origins a restore reopens outside the grants so the restore skips and reports them; captures and cookies restore only with their origin grants. */\nexport function restoreoriginsgranted(urls: string[], grants: string[]): { allowed: boolean; skippedorigins: string[] } {\n const covered = new Set(grants);\n const skippedorigins: string[] = [];\n for (const url of urls) {\n let origin = \"\";\n try { origin = new URL(url).origin; } catch { origin = \"\"; }\n if (!origin || !covered.has(origin)) skippedorigins.push(origin || url);\n }\n return { allowed: skippedorigins.length === 0, skippedorigins: [...new Set(skippedorigins)] };\n}\n\n/** Requires session names to stay unique inside the library so a filing never shadows another saved session. */\nexport function sessionnameunique(name: string, records: Array<{ id: string; name: string }>, recordid?: string): policyevaluation {\n if (records.some(record => record.name === name && record.id !== recordid)) return { allowed: false, reason: `The session name ${name} already exists in the library; review a unique name.` };\n return { allowed: true };\n}\n\n/** Requires folder names to stay unique inside the folder tree so one folder never shadows another. */\nexport function sessionfolderunique(name: string, folders: Array<{ name: string }>): policyevaluation {\n if (folders.some(folder => folder.name === name)) return { allowed: false, reason: `The folder name ${name} already exists in the library; review a unique folder name.` };\n return { allowed: true };\n}\n\n/** Exposes the user configured retention window for saved session sections; an absent window keeps every section and no code ceiling applies. */\nexport function snapshotretentionwindow(settings: runsettings | undefined): number | undefined {\n return settings?.sessionretention;\n}\n\n/** Validates the reviewed workflow parameter grammar of the 1.1.50 and 1.1.51 families: composition with the expanded block list so no step stays hidden, shareable step templates, workflow runs behind the explicit run review, dry runs of the known workflow, jittered delays and element waits of user configured bounds with no code ceiling, expressions whose operators match the operand kinds and result kinds, regex rules of bounded backtracking shapes applied to reviewed text, and the control flow payloads of conditionals, branching, loops with user configured safety bounds, foreach selectors, parallel branches with join policies and try catch with retry and timeout policies whose child kinds all stay inside the reviewed vocabulary. */\nfunction validateworkflowgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"composeworkflow\") {\n const payload = options.workflow;\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) return { allowed: false, reason: \"The workflow composition needs the reviewed workflow payload with its name, version, origins, steps and blocks.\" };\n const candidate = payload as Record<string, unknown>;\n if (typeof candidate.name !== \"string\" || !candidate.name.trim()) return { allowed: false, reason: \"The workflow composition needs a reviewed non-empty name.\" };\n if (typeof candidate.version !== \"number\" || !Number.isInteger(candidate.version) || candidate.version < 1) return { allowed: false, reason: \"The workflow version must be a positive integer.\" };\n if (!Array.isArray(candidate.origins) || candidate.origins.length === 0 || !candidate.origins.every(origin => typeof origin === \"string\" && origin.startsWith(\"https://\"))) return { allowed: false, reason: \"The workflow needs at least one granted HTTPS origin so every step stays inside the grants.\" };\n if (!Array.isArray(candidate.steps) || candidate.steps.length === 0 || !candidate.steps.every(entry => workflowstepof(entry) !== undefined || (entry && typeof entry === \"object\" && typeof (entry as Record<string, unknown>).block === \"string\"))) return { allowed: false, reason: \"The workflow needs a non-empty reviewed step list of the workflow step grammar or block invocations.\" };\n const blocks = Array.isArray(candidate.blocks) ? candidate.blocks.flatMap(block => { const parsed = workflowblockof(block); return parsed !== undefined ? [parsed] : []; }) : [];\n if (Array.isArray(candidate.blocks) && blocks.length !== (candidate.blocks as unknown[]).length) return { allowed: false, reason: \"The reviewed block list must carry unique lowercase names, labels and valid child steps.\" };\n try {\n const record = composeworkflow({ name: candidate.name, version: candidate.version, origins: candidate.origins as string[], steps: (candidate.steps as Array<Record<string, unknown>>).map(entry => \"block\" in entry ? { block: entry.block as string, label: typeof entry.label === \"string\" ? entry.label : entry.block as string } : workflowstepof(entry) as workflowstep), blocks, now: 0, kindallowed: candidatekind => { try { actionrisk(candidatekind as actionkind); return true; } catch { return false; } }, riskof: candidatekind => actionrisk(candidatekind as actionkind) });\n const inputs = Array.isArray(candidate.inputs) ? candidate.inputs.flatMap(name => typeof name === \"string\" ? [name] : []) : undefined;\n const checked = validateworkflow(record, { kindallowed: workflowkind => { try { actionrisk(workflowkind as actionkind); return true; } catch { return false; } }, ...(inputs !== undefined ? { inputs } : {}) });\n if (!checked.allowed) return checked;\n } catch (error) {\n return { allowed: false, reason: error instanceof Error ? error.message : \"The workflow payload failed its composition validation.\" };\n }\n return { allowed: true };\n }\n if (kind === \"savetemplate\") {\n const payload = options.template && typeof options.template === \"object\" && !Array.isArray(options.template) ? options.template as Record<string, unknown> : {};\n const template = steptemplateof({ id: \"templatereview\", origin: \"https://example.com\", sharedat: 0, ...payload });\n if (!template) return { allowed: false, reason: \"The step template needs a reviewed name and a valid workflow step it shares across workflows.\" };\n return { allowed: true };\n }\n if (kind === \"runworkflow\") {\n if (typeof options.workflowid !== \"string\" || !options.workflowid.trim()) return { allowed: false, reason: \"The workflow run needs the reviewed id of the composed workflow.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"Every real workflow run needs the explicit run review with its expanded step list shown before the first step executes.\" };\n if (options.variables !== undefined && (!options.variables || typeof options.variables !== \"object\" || Array.isArray(options.variables) || !Object.values(options.variables).every(value => typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\"))) return { allowed: false, reason: \"The reviewed run variables must be an object of string, number or boolean values.\" };\n return { allowed: true };\n }\n if (kind === \"dryrun\") {\n if (typeof options.workflowid !== \"string\" || !options.workflowid.trim()) return { allowed: false, reason: \"The dry run needs the reviewed id of the composed workflow.\" };\n return { allowed: true };\n }\n if (kind === \"delay\") {\n const delay = options.delay;\n if (!delay || typeof delay !== \"object\" || Array.isArray(delay)) return { allowed: false, reason: \"The delay needs a reviewed base and jitter window in options.\" };\n const reviewed = delay as Record<string, unknown>;\n if (typeof reviewed.base !== \"number\" || !Number.isFinite(reviewed.base) || reviewed.base < 0) return { allowed: false, reason: \"The reviewed delay base must be zero or a positive number of milliseconds.\" };\n if (typeof reviewed.jitter !== \"number\" || !Number.isFinite(reviewed.jitter) || reviewed.jitter < 0) return { allowed: false, reason: \"The reviewed delay jitter window must be zero or a positive number of milliseconds with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"waitelement\") {\n const wait = options.wait;\n if (!wait || typeof wait !== \"object\" || Array.isArray(wait)) return { allowed: false, reason: \"The element wait needs a reviewed selector, timeout and poll interval in options.\" };\n const reviewed = wait as Record<string, unknown>;\n if (typeof reviewed.selector !== \"string\" || !reviewed.selector.trim()) return { allowed: false, reason: \"The element wait needs a reviewed non-empty selector.\" };\n if (typeof reviewed.timeout !== \"number\" || !Number.isFinite(reviewed.timeout) || reviewed.timeout < 0) return { allowed: false, reason: \"The reviewed element wait timeout must be zero or a positive number of milliseconds with no code ceiling.\" };\n if (typeof reviewed.poll !== \"number\" || !Number.isFinite(reviewed.poll) || reviewed.poll < 0) return { allowed: false, reason: \"The reviewed element wait poll interval must be zero or a positive number of milliseconds with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"compute\") {\n const expression = expressionof(options.expression);\n if (!expression) return { allowed: false, reason: `The expression step needs a reviewed expression with operands, an operator of the reviewed set (${expressionoperators.join(\", \")}) and a result variable of a reviewed kind.` };\n const operatorcheck = validatexpressionoperators(expression);\n if (!operatorcheck.allowed) return operatorcheck;\n return { allowed: true };\n }\n if (kind === \"extractvars\") {\n const rule = regexruleof(options.rule);\n if (!rule) return { allowed: false, reason: \"The variable extraction needs a reviewed regex rule with its pattern, flags and named capture groups.\" };\n const shapecheck = validateregexrule(rule.pattern);\n if (!shapecheck.allowed) return shapecheck;\n if (typeof options.text !== \"string\") return { allowed: false, reason: \"The variable extraction needs the reviewed text the regex rule applies to.\" };\n return { allowed: true };\n }\n if (kind === \"condition\") {\n const condition = conditionof(options.condition);\n if (!condition) return { allowed: false, reason: \"The condition step needs a reviewed boolean expression in its options.\" };\n const operatorcheck = validatexpressionoperators(condition.expression);\n if (!operatorcheck.allowed) return operatorcheck;\n return { allowed: true };\n }\n if (kind === \"branch\") {\n const branch = branchof(options.branch);\n if (!branch) return { allowed: false, reason: \"The branch step needs reviewed unique paths with boolean match expressions and an else path in its options so every branch terminates.\" };\n for (const path of [...branch.paths, branch.else]) {\n if (path.when === undefined) continue;\n const operatorcheck = validatexpressionoperators(path.when);\n if (!operatorcheck.allowed) return operatorcheck;\n }\n return controlchildkinds(step);\n }\n if (kind === \"loop\") {\n const loop = loopof(options.loop);\n if (!loop) return { allowed: false, reason: \"The loop step needs a reviewed list variable, distinct item and index variables, an optional positive safety bound and a non-empty body in its options; an absent bound keeps the documented default.\" };\n return controlchildkinds(step);\n }\n if (kind === \"repeatuntil\") {\n const repeat = repeatuntilof(options.repeatuntil);\n if (!repeat) return { allowed: false, reason: \"The repeat until step needs a reviewed convergence expression, an optional positive safety bound and a non-empty body in its options.\" };\n const operatorcheck = validatexpressionoperators(repeat.until);\n if (!operatorcheck.allowed) return operatorcheck;\n return controlchildkinds(step);\n }\n if (kind === \"whileloop\") {\n const condition = whileof(options.while);\n if (!condition) return { allowed: false, reason: \"The while step needs a reviewed condition, a mandatory positive safety bound and a non-empty body in its options; a while loop without a safety bound is refused.\" };\n const operatorcheck = validatexpressionoperators(condition.while);\n if (!operatorcheck.allowed) return operatorcheck;\n return controlchildkinds(step);\n }\n if (kind === \"foreach\") {\n const foreach = foreachof(options.foreach);\n if (!foreach) return { allowed: false, reason: \"The foreach step needs a reviewed non-empty selector, distinct item and index variables and a non-empty body in its options.\" };\n return controlchildkinds(step);\n }\n if (kind === \"parallel\") {\n const parallel = parallelof(options.parallel);\n if (!parallel) return { allowed: false, reason: \"The parallel step needs uniquely identified branches with bodies and a join policy of the first, last or fail strategy with cancel or continue on branch failure in its options.\" };\n return controlchildkinds(step);\n }\n if (kind === \"trycatch\") {\n const fragile = tryof(options.try);\n if (!fragile) return { allowed: false, reason: \"The try step needs a fragile body, a catch handler and optional retry and timeout policies in its options: attempts stay user configured with no code ceiling, backoff is fixed or exponential and budgets are positive.\" };\n return controlchildkinds(step);\n }\n return { allowed: true };\n}\n\n/** Checks every child step of a control payload against the reviewed action vocabulary so no control construct hides an unreviewed kind behind its body. */\nfunction controlchildkinds(step: toolstep): policyevaluation {\n const children = controlsteps({ id: step.id, kind: step.kind, label: step.summary, ...(step.options !== undefined ? { options: step.options } : {}) });\n for (const child of children) {\n try { actionrisk(child.kind); } catch { return { allowed: false, reason: `The ${child.kind} step inside the control payload of the ${step.kind} step is not a reviewed action kind.` }; }\n }\n return { allowed: true };\n}\n\n/** Rejects unbounded backtracking shapes of reviewed regex patterns: a quantified group whose body itself ends with an unbounded quantifier can explode on adversarial text, so the shape is refused while bounded repetitions stay user choices. */\nexport function validateregexrule(pattern: string): policyevaluation {\n try { new RegExp(pattern); } catch { return { allowed: false, reason: \"The reviewed regex pattern does not compile.\" }; }\n const nestedquantifier = /\\((?:[^()\\\\]|\\\\.)*[+*}]\\)[+*{]/.test(pattern) || /\\(\\)[+*{]/.test(pattern);\n if (nestedquantifier) return { allowed: false, reason: \"The reviewed regex pattern nests an unbounded quantifier inside a quantified group and is refused because adversarial text could explode the backtracking.\" };\n const unboundedrepeat = /\\{\\d+,\\}/.test(pattern);\n if (unboundedrepeat && /\\([^)]*\\{\\d+,\\}[^)]*\\)[+*{]/.test(pattern)) return { allowed: false, reason: \"The reviewed regex pattern repeats an unbounded group and is refused because adversarial text could explode the backtracking.\" };\n return { allowed: true };\n}\n\n/** Validates the reviewed expression operators against the operand kinds and the result kind: arithmetic needs numbers and returns numbers, logic needs booleans and returns booleans, comparison needs numbers and returns booleans, text operators return strings or booleans and length returns a number. */\nfunction validatexpressionoperators(expression: import(\"./types.js\").expressiontype): policyevaluation {\n const numeric = new Set([\"add\", \"subtract\", \"multiply\", \"divide\", \"modulo\"]);\n const logic = new Set([\"and\", \"or\", \"not\"]);\n const comparison = new Set([\"less\", \"greater\", \"lessequal\", \"greaterequal\"]);\n const text = new Set([\"concat\", \"contains\"]);\n const operator = expression.operator;\n if (numeric.has(operator)) {\n for (const operand of [expression.left, expression.right]) {\n if (operand === undefined) continue;\n if (operand.literal !== undefined && typeof operand.literal === \"boolean\") return { allowed: false, reason: `The ${operator} operator needs numeric operands; boolean literals are refused.` };\n }\n if (expression.resultkind !== \"number\" && expression.resultkind !== \"string\") return { allowed: false, reason: `The ${operator} operator needs a number result kind.` };\n }\n if (logic.has(operator)) {\n for (const operand of [expression.left, expression.right]) {\n if (operand === undefined) continue;\n if (operand.literal !== undefined && typeof operand.literal !== \"boolean\") return { allowed: false, reason: `The ${operator} operator needs boolean operands; non boolean literals are refused.` };\n }\n if (expression.resultkind !== \"boolean\") return { allowed: false, reason: `The ${operator} operator needs a boolean result kind.` };\n if (operator === \"not\" && expression.right !== undefined) return { allowed: false, reason: \"The not operator takes one operand only.\" };\n }\n if (comparison.has(operator) && expression.resultkind !== \"boolean\") return { allowed: false, reason: `The ${operator} operator needs a boolean result kind.` };\n if (text.has(operator) && expression.resultkind !== \"boolean\" && expression.resultkind !== \"string\") return { allowed: false, reason: `The ${operator} operator needs a string or boolean result kind.` };\n if (operator === \"contains\" && expression.resultkind !== \"boolean\") return { allowed: false, reason: \"The contains operator needs a boolean result kind.\" };\n if (operator === \"length\") {\n if (expression.right !== undefined) return { allowed: false, reason: \"The length operator takes one operand only.\" };\n if (expression.resultkind !== \"number\") return { allowed: false, reason: \"The length operator needs a number result kind.\" };\n }\n if ((operator === \"equal\" || operator === \"notequal\") && !new Set([\"boolean\", \"string\", \"number\"]).has(expression.resultkind)) return { allowed: false, reason: \"The equality operator needs a primitive result kind.\" };\n return { allowed: true };\n}\n\n/** The workflow consent gate: a live session, an approved plan and the explicit run review of every real run; dry runs stay read only inside the same session and plan gates. */\nexport function workflowgate(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: \"run the workflow step\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Workflow steps need the approved plan review before they run.\" };\n if (input.step.kind === \"runworkflow\") {\n let runoptions: Record<string, unknown> = {};\n try { runoptions = parseoptions(input.step); } catch { runoptions = {}; }\n if (runoptions.reviewed !== true) return { allowed: false, reason: \"Every real workflow run needs the explicit run review with its expanded step list shown before the first step executes.\" };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed trigger parameter grammar of the 1.1.52 family: every kind arms exactly one rule behind the explicit arm review, the workflow reference must name a composed workflow, the match payloads follow their family grammar \u2014 visit origins and url list entries must be HTTPS urls, url patterns must parse as HTTPS globs, cron expressions must parse as five field schedules with named weekdays and months and a resolvable timezone, interval periods stay positive with zero or positive jitter, webhook secrets must clear the documented entropy floor with a non-empty payload schema, event names must come from the observed event catalog and context menu titles stay non-empty \u2014 while cooldown windows stay user configured positive values with the documented default of the webhook and event families winning only when the review configures none. */\nfunction validatetriggergrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const family = triggerfamilyof(step.kind);\n if (family === undefined) return { allowed: false, reason: \"The trigger step is not a reviewed trigger kind.\" };\n if (typeof options.workflowid !== \"string\" || !options.workflowid.trim()) return { allowed: false, reason: \"Every trigger rule needs the reviewed id of the composed workflow it launches.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"Every trigger rule needs the explicit arm review with its match fields and bound workflow shown before it arms.\" };\n if (options.label !== undefined && (typeof options.label !== \"string\" || !options.label.trim())) return { allowed: false, reason: \"The reviewed trigger label must be a non-empty string.\" };\n if (options.cooldown !== undefined && (typeof options.cooldown !== \"number\" || !Number.isFinite(options.cooldown) || options.cooldown <= 0)) return { allowed: false, reason: \"The reviewed cooldown window must be a positive number of milliseconds with no code ceiling; the webhook and event families keep the documented default when the review configures none.\" };\n const payload = options.rule;\n if (!payload || typeof payload !== \"object\" || Array.isArray(payload)) return { allowed: false, reason: `The ${step.kind} step needs its reviewed rule payload in options.` };\n if (triggerpayloadof(family, payload) === undefined) {\n if (family === \"visit\") return { allowed: false, reason: \"The visit rule needs a non-empty reviewed list of HTTPS origins it fires on.\" };\n if (family === \"url\") return { allowed: false, reason: \"The url rule needs a reviewed HTTPS glob url pattern; `*` spans one path segment and `**` spans across segments.\" };\n if (family === \"menu\") return { allowed: false, reason: \"The menu rule needs a reviewed non-empty context menu entry title.\" };\n if (family === \"key\") return { allowed: false, reason: \"The keyboard shortcut rule needs a reviewed lowercase command name and an optional suggested key binding.\" };\n if (family === \"cron\") return { allowed: false, reason: \"The cron rule needs a reviewed five field cron expression of minutes, hours, days, months and weekdays with named weekdays and months and an optional resolvable timezone; unparseable schedules are refused.\" };\n if (family === \"interval\") return { allowed: false, reason: \"The interval rule needs a reviewed positive period in milliseconds with an optional zero or positive jitter window.\" };\n if (family === \"urllist\") return { allowed: false, reason: \"The url list rule needs a reviewed non-empty list of HTTPS urls its workflow runs across.\" };\n if (family === \"webhook\") return { allowed: false, reason: `The webhook rule needs a reviewed shared secret of at least twenty four characters mixing letters and digits and a non-empty payload schema of named string, number or boolean fields.` };\n if (family === \"event\") return { allowed: false, reason: `The page event rule needs a reviewed non-empty list of event names of the observed event catalog: ${triggereventcatalog.join(\", \")}.` };\n return { allowed: false, reason: \"The trigger rule payload does not follow its family grammar.\" };\n }\n if (family === \"cron\") {\n const candidate = payload as Record<string, unknown>;\n if (typeof candidate.cron === \"string\" && cronparse(candidate.cron) === undefined) return { allowed: false, reason: \"The cron expression does not parse as a five field schedule and is refused.\" };\n }\n if (family === \"webhook\") {\n const candidate = payload as Record<string, unknown>;\n if (typeof candidate.secret === \"string\" && !webhooksecretok(candidate.secret)) return { allowed: false, reason: \"The webhook shared secret must hold at least twenty four characters mixing letters and digits; the entropy floor is a floor, never a cap.\" };\n }\n const armed = armrule({ family, workflowid: options.workflowid, ...(typeof options.label === \"string\" && options.label.trim() ? { label: options.label } : {}), payload, ...(typeof options.cooldown === \"number\" ? { cooldown: options.cooldown } : {}), now: 0 });\n if (armed === undefined) return { allowed: false, reason: \"The trigger rule payload does not arm as a reviewed rule.\" };\n return { allowed: true };\n}\n\n/** The trigger consent gate: a live session, an approved plan and the explicit arm review of every rule; automatic launchers never arm outside the consent gates. */\nexport function triggergate(input: { session: agentsession | undefined; plan: agentplan | undefined; step: toolstep; tabid: number; origin: string; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now: input.now, action: \"arm the trigger rule\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Trigger rules need the approved plan review before they arm.\" };\n let triggeroptions: Record<string, unknown> = {};\n try { triggeroptions = parseoptions(input.step); } catch { triggeroptions = {}; }\n if (triggeroptions.reviewed !== true) return { allowed: false, reason: \"Every trigger rule needs the explicit arm review with its match fields and bound workflow shown before it arms.\" };\n return { allowed: true };\n}\n\n/** Returns the match origins of one reviewed trigger rule so callers can keep every rule inside the workflow grant list; triggers on origins outside the grants are refused. */\nexport function triggerorigins(step: toolstep): string[] {\n let triggeroptions: Record<string, unknown> = {};\n try { triggeroptions = parseoptions(step); } catch { return []; }\n const family = triggerfamilyof(step.kind);\n if (family === undefined) return [];\n const armed = armrule({ family, workflowid: typeof triggeroptions.workflowid === \"string\" ? triggeroptions.workflowid : \"\", payload: triggeroptions.rule, ...(typeof triggeroptions.cooldown === \"number\" ? { cooldown: triggeroptions.cooldown } : {}), now: 0 });\n if (armed === undefined) return [];\n const origins: string[] = [];\n for (const origin of armed.origins ?? []) origins.push(origin);\n if (armed.pattern !== undefined) { try { origins.push(new URL(armed.pattern).origin); } catch { /* the pattern grammar already refused unparseable patterns */ } }\n for (const url of armed.urls ?? []) { try { origins.push(new URL(url).origin); } catch { /* the url list grammar already refused unparseable urls */ } }\n return [...new Set(origins)];\n}\n\n/** Returns the read only projection of one workflow step for dry runs: read class steps report their would be outcome while interaction and mutation steps carry no projection and the dry run refuses them; a control step projects only when every child step of its payload grades read. */\nexport function dryrunprojection(step: workflowstep): string | undefined {\n if (iscontrolflowkind(step.kind)) {\n for (const child of controlsteps(step)) {\n const childrisk = resolvedrisk({ id: child.id, kind: child.kind, summary: child.label, risk: \"read\", ...(child.target !== undefined ? { target: child.target } : {}), ...(child.value !== undefined ? { value: child.value } : {}), ...(child.options !== undefined ? { options: child.options } : {}) });\n if (childrisk !== \"read\") return undefined;\n }\n if (step.kind === \"condition\") return \"The condition step would evaluate its reviewed expression over the extracted values with no page side effect.\";\n if (step.kind === \"branch\") return \"The branch step would choose one reviewed path by page state and only the chosen path would run.\";\n if (step.kind === \"loop\") return \"The loop step would iterate its reviewed list binding the item and index variables per iteration inside the safety bound.\";\n if (step.kind === \"repeatuntil\") return \"The repeat until step would rerun its body until the convergence expression holds inside the safety bound.\";\n if (step.kind === \"whileloop\") return \"The while step would loop while its condition holds inside the reviewed safety bound.\";\n if (step.kind === \"foreach\") return \"The foreach step would iterate the elements of its reviewed selector binding the item and index variables per iteration.\";\n if (step.kind === \"parallel\") return \"The parallel step would run its branches concurrently and join their outcomes under the reviewed strategy.\";\n return \"The try step would run its fragile body and only the catch handler on failure.\";\n }\n const risk = resolvedrisk({ id: step.id, kind: step.kind, summary: step.label, risk: \"read\", ...(step.target !== undefined ? { target: step.target } : {}), ...(step.value !== undefined ? { value: step.value } : {}), ...(step.options !== undefined ? { options: step.options } : {}) });\n if (risk !== \"read\") return undefined;\n if (step.kind === \"delay\") return `The delay step would sleep its reviewed base inside the jitter window.`;\n if (step.kind === \"waitelement\") return `The element wait step would poll ${step.target ?? \"the reviewed selector\"} until appearance or the reviewed timeout.`;\n if (step.kind === \"compute\") return `The compute step would evaluate its reviewed expression into the result variable.`;\n if (step.kind === \"extractvars\") return `The variable extraction step would apply its reviewed regex rule and store the named captures.`;\n return `The ${step.kind} step would run read only and mutate nothing.`;\n}\n\n/** Validates one reviewed permission state of an override. */\nexport function permissionstatevalid(state: string): policyevaluation {\n if (!permissionstates.includes(state as permissionstate)) return { allowed: false, reason: `The reviewed permission state must be one of ${permissionstates.join(\", \")}.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed devtools parameter grammar of the 1.1.46 family: enabled domains bounded by the reviewed domain grammar, the required teardown plan of every attach, raw commands of the Domain.method form, domain event rules with match filters inside the reviewed watch window, breakpoints with conditions of the reviewed expression grammar, step modes, reviewed watch expressions, and script overrides with the explicit reviewed flag and a url pattern that names its origin. */\nfunction validatecdpgrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"attachcdp\") {\n if (!Array.isArray(options.domains) || options.domains.length === 0 || !options.domains.every((domain): domain is string => typeof domain === \"string\" && cdpdomains.includes(domain))) return { allowed: false, reason: `The attach needs a non-empty enabled domain list of the reviewed domain grammar: ${cdpdomains.join(\", \")}.` };\n if (teardownplanof(options.teardown) === undefined) return { allowed: false, reason: \"Every attach needs a reviewed teardown plan with its revert steps and resume policy before approval.\" };\n if (options.allowlist !== undefined) {\n const allowlist = cdpallowlistof(options.allowlist);\n if (!allowlist || !allowlist.domains.every(domain => (options.domains as string[]).includes(domain))) return { allowed: false, reason: \"The reviewed method allowlist must stay inside the enabled domains of the attach.\" };\n }\n const budgetcheck = debugwaitbudgetallowed(typeof options.wait === \"number\" ? options.wait : undefined, undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"detachcdp\") return { allowed: true };\n if (kind === \"cdpcmd\") {\n const command = options.command && typeof options.command === \"object\" && !Array.isArray(options.command) ? options.command as Record<string, unknown> : undefined;\n if (!command || typeof command.method !== \"string\" || methoddomain(command.method) === undefined) return { allowed: false, reason: \"The raw command needs a reviewed method of the Domain.method form.\" };\n if (command.params !== undefined && (typeof command.params !== \"object\" || Array.isArray(command.params))) return { allowed: false, reason: \"The raw command params must be a JSON object.\" };\n if (command.resultpath !== undefined && typeof command.resultpath !== \"string\") return { allowed: false, reason: \"The reviewed result path must be a dotted path string.\" };\n return { allowed: true };\n }\n if (kind === \"watchcdp\") {\n if (!Array.isArray(options.events) || options.events.length === 0 || !options.events.every(rule => cdpeventruleof(rule) !== undefined)) return { allowed: false, reason: \"The event watch needs a non-empty reviewed list of domain event rules of the reviewed domain grammar.\" };\n let watchwindow: number | undefined;\n if (options.watch !== undefined) {\n const watch = options.watch;\n if (!watch || typeof watch !== \"object\" || Array.isArray(watch)) return { allowed: false, reason: \"The reviewed event watch window must be an object.\" };\n const reviewed = watch as Record<string, unknown>;\n if (reviewed.window !== undefined) {\n if (typeof reviewed.window !== \"number\" || !Number.isFinite(reviewed.window) || reviewed.window < 0) return { allowed: false, reason: \"The reviewed event watch window must be zero or a positive number of milliseconds.\" };\n watchwindow = reviewed.window;\n }\n }\n if (watchwindow === undefined) return { allowed: false, reason: \"The event watch needs a reviewed lifetime window before any domain event is observed.\" };\n const budgetcheck = debugwaitbudgetallowed(watchwindow, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"setbreakpoint\") {\n const breakpoint = breakpointinputof(options.breakpoint);\n if (!breakpoint) return { allowed: false, reason: \"The breakpoint needs a reviewed script url and a zero based line.\" };\n if (!ishttpsurl(breakpoint.url)) return { allowed: false, reason: \"The breakpoint script url must be a reviewed HTTPS url.\" };\n if (breakpoint.condition !== undefined) {\n const conditioncheck = validatebreakpointcondition(breakpoint.condition);\n if (!conditioncheck.allowed) return conditioncheck;\n }\n return { allowed: true };\n }\n if (kind === \"stepcode\") {\n if (stepmodeof(options.mode) === undefined) return { allowed: false, reason: \"The step code mode must be one of stepover, stepinto, stepout or resume.\" };\n return { allowed: true };\n }\n if (kind === \"watchexpr\") {\n if (watchexpressionof(options.expression) === undefined) return { allowed: false, reason: \"The watch expression needs the reviewed expression text.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"Watch expressions must be reviewed before evaluation; set the explicit reviewed flag on the step.\" };\n return { allowed: true };\n }\n if (kind === \"overridescript\") {\n const override = overrideinputof(options.override);\n if (!override) return { allowed: false, reason: \"The script override needs a reviewed url pattern and its full fixture source.\" };\n if (patternorigin(override.urlpattern) === undefined) return { allowed: false, reason: \"Script overrides without a named https origin pattern are refused.\" };\n if (options.reviewed !== true) return { allowed: false, reason: \"The full fixture source must be reviewed before the script override runs; set the explicit reviewed flag on the step.\" };\n return { allowed: true };\n }\n return { allowed: true };\n}\n\n/** Validates the reviewed profiling parameter grammar of the 1.1.47 family: flow specs of the reviewed metric set inside a reviewed watch window, heap snapshots with the user chosen interval only, growth tracking with the reviewed slope, cpu profiles bounded by the reviewed wait budget, layout shift watches with the user chosen window only, trace records bounded by the reviewed category list and byte ceiling, trace annotations that carry step ids, offline replays of stored traces and source map capture scripts of explicit https urls. */\nfunction validateprofilegrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"measureflow\") {\n if (flowspecof(options.flow) === undefined) return { allowed: false, reason: `The flow measurement needs a reviewed flow spec with its mark prefix, step window and metric list of the reviewed metric set: navigation, paint, lcp, fid, interaction, blocking.` };\n const watch = options.watch && typeof options.watch === \"object\" && !Array.isArray(options.watch) ? options.watch as Record<string, unknown> : {};\n if (typeof watch.window !== \"number\" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: \"The flow measurement needs a reviewed watch window of zero or more milliseconds.\" };\n const budgetcheck = debugwaitbudgetallowed(watch.window, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"heapshot\") {\n const heap = options.heap && typeof options.heap === \"object\" && !Array.isArray(options.heap) ? options.heap as Record<string, unknown> : {};\n if (heap.interval !== undefined && (typeof heap.interval !== \"number\" || !Number.isFinite(heap.interval) || heap.interval < 0)) return { allowed: false, reason: \"The reviewed heap snapshot interval must be zero or a positive number of milliseconds and stays a user choice with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"trackmemory\") {\n const growth = options.growth && typeof options.growth === \"object\" && !Array.isArray(options.growth) ? options.growth as Record<string, unknown> : undefined;\n if (!growth || typeof growth.slope !== \"number\" || !Number.isFinite(growth.slope) || growth.slope < 0) return { allowed: false, reason: \"Memory growth tracking needs the reviewed slope in bytes per millisecond before any sample is flagged.\" };\n if (growth.interval !== undefined && (typeof growth.interval !== \"number\" || !Number.isFinite(growth.interval) || growth.interval < 0)) return { allowed: false, reason: \"The reviewed sampling interval must be zero or a positive number of milliseconds and stays a user choice with no code ceiling.\" };\n return { allowed: true };\n }\n if (kind === \"profilecpu\") {\n const profile = options.profile && typeof options.profile === \"object\" && !Array.isArray(options.profile) ? options.profile as Record<string, unknown> : undefined;\n if (!profile || typeof profile.duration !== \"number\" || !Number.isFinite(profile.duration) || profile.duration < 0) return { allowed: false, reason: \"The cpu profile needs a reviewed duration of zero or more milliseconds.\" };\n const budgetcheck = debugwaitbudgetallowed(profile.duration, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"watchshifts\") {\n const watch = options.watch && typeof options.watch === \"object\" && !Array.isArray(options.watch) ? options.watch as Record<string, unknown> : {};\n if (typeof watch.window !== \"number\" || !Number.isFinite(watch.window) || watch.window < 0) return { allowed: false, reason: \"The layout shift watch needs a reviewed observation window of zero or more milliseconds; the window stays a user choice with no code ceiling.\" };\n if (options.threshold !== undefined && (typeof options.threshold !== \"number\" || !Number.isFinite(options.threshold) || options.threshold < 0)) return { allowed: false, reason: \"The reviewed shift score threshold must be zero or a positive number.\" };\n const budgetcheck = debugwaitbudgetallowed(watch.window, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"traceload\") {\n const trace = options.trace && typeof options.trace === \"object\" && !Array.isArray(options.trace) ? options.trace as Record<string, unknown> : undefined;\n if (!trace || !Array.isArray(trace.categories) || trace.categories.length === 0 || !trace.categories.every((category): category is string => typeof category === \"string\" && tracecategories.includes(category))) return { allowed: false, reason: `The trace record needs a non-empty reviewed category list of the reviewed category grammar: ${tracecategories.join(\", \")}.` };\n if (typeof trace.window !== \"number\" || !Number.isFinite(trace.window) || trace.window < 0) return { allowed: false, reason: \"The trace record needs a reviewed window of zero or more milliseconds and stops at the reviewed window end.\" };\n if (trace.exporttarget !== undefined && trace.exporttarget !== \"memory\" && trace.exporttarget !== \"download\") return { allowed: false, reason: \"The trace export target must be memory or download.\" };\n const budgetcheck = debugwaitbudgetallowed(trace.window, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budgetcheck.allowed) return budgetcheck;\n return { allowed: true };\n }\n if (kind === \"annotatetrace\" || kind === \"replaytrace\") {\n const trace = options.trace && typeof options.trace === \"object\" && !Array.isArray(options.trace) ? options.trace as Record<string, unknown> : undefined;\n if (!trace || typeof trace.traceid !== \"string\" || !trace.traceid.trim()) return { allowed: false, reason: `The ${kind === \"annotatetrace\" ? \"trace annotation\" : \"trace replay\"} needs the stored trace id of a recorded trace.` };\n if (kind === \"replaytrace\") return { allowed: true };\n if (!Array.isArray(options.annotations) || options.annotations.length === 0 || !options.annotations.every(annotation => annotationof(annotation) !== undefined)) return { allowed: false, reason: \"Exported traces carry their step annotations: every annotation needs a step id, a label and an optional offset from the trace start.\" };\n return { allowed: true };\n }\n if (kind === \"capturesourcemaps\") {\n if (options.scripts !== undefined) {\n if (!Array.isArray(options.scripts) || options.scripts.length === 0 || !options.scripts.every((url): url is string => typeof url === \"string\" && ishttpsurl(url))) return { allowed: false, reason: \"The source map capture scripts must be a non-empty list of reviewed HTTPS urls.\" };\n }\n return { allowed: true };\n }\n return { allowed: true };\n}\n\n/** Resolves the reviewed cdp allowlist of one plan: the enabled domains and method gates of its attachcdp step, the reviewable contract every later cdp kind of the plan must stay inside. */\nexport function planallowlist(steps: toolstep[]): cdpallowlist | undefined {\n const attach = steps.find(step => step.kind === \"attachcdp\");\n if (!attach) return undefined;\n let options: Record<string, unknown> = {};\n try { options = parseoptions(attach); } catch { options = {}; }\n const domains = Array.isArray(options.domains) ? options.domains.filter((domain): domain is string => typeof domain === \"string\" && cdpdomains.includes(domain)) : [];\n if (domains.length === 0) return undefined;\n const gated = cdpallowlistof(options.allowlist);\n return { domains, ...(gated?.methods !== undefined ? { methods: gated.methods } : {}) };\n}\n\n/** Resolves the reviewed outbound url of a network control step at review time: the form url of postform, the upload url of postfiles and the token url of authflow. */\nexport function controltarget(step: toolstep): string | undefined {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n for (const key of [\"form\", \"upload\"] as const) {\n const value = options[key];\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const url = (value as Record<string, unknown>).url;\n if (typeof url === \"string\" && url.trim()) return url.trim();\n }\n }\n if (step.kind === \"authflow\") {\n const flow = oauthflowof(options.oauth);\n if (flow) return flow.tokenurl;\n }\n return undefined;\n}\n\n/** Resolves the reviewed channel url of a socket step at review time: the socket url of opensocket, the event stream url of subscribesse and the poll url of longpoll. */\nexport function sockettarget(step: toolstep): string | undefined {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n for (const key of [\"socket\", \"subscription\", \"poll\"] as const) {\n const value = options[key];\n if (value && typeof value === \"object\" && !Array.isArray(value)) {\n const url = (value as Record<string, unknown>).url;\n if (typeof url === \"string\" && url.trim()) return url.trim();\n }\n }\n return undefined;\n}\n\n/** Requires the active tab grant of the live session for every media kind: the session tab and origin must match and the origin grant must cover the active origin. */\nexport function mediagate(session: agentsession | undefined, tabid: number, origin: string, now: number): policyevaluation {\n if (!session || session.stoppedat) return { allowed: false, reason: \"No active browser session exists for the media capture.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and cannot capture media.\" };\n if (session.pausedat) return { allowed: false, reason: \"The browser session is paused and cannot capture media.\" };\n if (session.tabid !== tabid) return { allowed: false, reason: `The media capture needs the active tab grant of session tab ${session.tabid} and refuses tab ${tabid}.` };\n if (!origingranted(session, origin)) return { allowed: false, reason: `The media capture of ${origin} needs the session origin grants first.` };\n return { allowed: true };\n}\n\n/** Requires an approved recording consent prompt before any recording of user activity starts; every start consumes its own prompt. */\nexport function recordingconsentgranted(step: toolstep): policyevaluation {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(step); } catch { options = {}; }\n const consentref = options.consentref;\n if (typeof consentref !== \"string\" || !consentref.trim()) return { allowed: false, reason: \"A recording of user activity requires a reviewed consent ref in options before it starts.\" };\n return { allowed: true };\n}\n\n/** Exposes the recording duration window as a user configured choice in milliseconds; an absent window leaves the duration to the reviewed step options with no code ceiling. */\nexport function recordingwindow(settings: runsettings | undefined): number | undefined {\n const window = settings?.recordingwindow;\n return typeof window === \"number\" && Number.isFinite(window) && window > 0 ? window : undefined;\n}\n\n/** Keeps the reviewed lapse plan inside the reviewed wait budget: the whole lapse duration must fit the wait window with no code ceiling on either side. */\nexport function lapsebudgetallowed(interval: number, duration: number, wait: number | undefined): policyevaluation {\n if (!(interval > 0)) return { allowed: false, reason: \"The reviewed lapse interval must be a positive number of milliseconds.\" };\n if (!(duration > 0)) return { allowed: false, reason: \"The reviewed lapse duration must be a positive number of milliseconds.\" };\n if (wait !== undefined && !(wait >= 0)) return { allowed: false, reason: \"The reviewed wait budget must be zero or a positive number of milliseconds.\" };\n if (wait !== undefined && duration > wait) return { allowed: false, reason: `The lapse duration of ${duration} milliseconds exceeds the reviewed wait budget of ${wait} milliseconds; review a wider budget or a shorter duration.` };\n return { allowed: true };\n}\n\n/** Validates the reviewed media capture parameter grammar of the 1.1.41 family: pdf paper sizes, recording scopes and windows, image filters, lapse plans, conversion targets and thumbnail directives stay user choices with no code ceilings. */\nfunction validatemediagrammar(step: toolstep, options: Record<string, unknown>): policyevaluation {\n const kind = step.kind;\n if (kind === \"capturepdf\") {\n const pdf = options.pdf;\n if (pdf !== undefined) {\n if (!pdf || typeof pdf !== \"object\" || Array.isArray(pdf)) return { allowed: false, reason: \"The reviewed pdf options must be an object in options.pdf.\" };\n const pdfoptions = pdf as Record<string, unknown>;\n if (pdfoptions.paperwidth !== undefined && (typeof pdfoptions.paperwidth !== \"number\" || !Number.isFinite(pdfoptions.paperwidth) || pdfoptions.paperwidth <= 0)) return { allowed: false, reason: \"The reviewed pdf paper width must be a positive number of inches with no code cap.\" };\n if (pdfoptions.paperheight !== undefined && (typeof pdfoptions.paperheight !== \"number\" || !Number.isFinite(pdfoptions.paperheight) || pdfoptions.paperheight <= 0)) return { allowed: false, reason: \"The reviewed pdf paper height must be a positive number of inches with no code cap.\" };\n if (pdfoptions.margins !== undefined) {\n const margins = pdfoptions.margins;\n if (!margins || typeof margins !== \"object\" || Array.isArray(margins)) return { allowed: false, reason: \"The reviewed pdf margins must be an object with top, right, bottom and left inches.\" };\n for (const side of [\"top\", \"right\", \"bottom\", \"left\"]) {\n const value = (margins as Record<string, unknown>)[side];\n if (value === undefined) continue;\n if (typeof value !== \"number\" || !Number.isFinite(value) || value < 0) return { allowed: false, reason: `The reviewed pdf ${side} margin must be zero or a positive number of inches; negative margins are refused.` };\n }\n }\n if (pdfoptions.scale !== undefined && (typeof pdfoptions.scale !== \"number\" || !Number.isFinite(pdfoptions.scale) || pdfoptions.scale <= 0)) return { allowed: false, reason: \"The reviewed pdf scale must be a positive number with no code cap.\" };\n if (pdfoptions.landscape !== undefined && typeof pdfoptions.landscape !== \"boolean\") return { allowed: false, reason: \"The reviewed pdf landscape flag must be a boolean.\" };\n if (pdfoptions.paginate !== undefined && typeof pdfoptions.paginate !== \"boolean\") return { allowed: false, reason: \"The reviewed pdf paginate flag must be a boolean.\" };\n }\n if (options.breakpoints !== undefined && (!Array.isArray(options.breakpoints) || options.breakpoints.length === 0 || !options.breakpoints.every(item => isnonempty(item)))) return { allowed: false, reason: \"The reviewed pdf break points must be a non-empty list of selectors when present.\" };\n if (options.exporttarget !== undefined && options.exporttarget !== \"memory\" && options.exporttarget !== \"download\") return { allowed: false, reason: \"The reviewed pdf export target must be memory or download; pdf documents do not route to the clipboard.\" };\n if (options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed pdf artifact name must be a non-empty string.\" };\n }\n if (kind === \"recordscreen\" || kind === \"captureaudio\") {\n const recording = options.recording;\n if (recording !== undefined) {\n if (!recording || typeof recording !== \"object\" || Array.isArray(recording)) return { allowed: false, reason: \"The reviewed recording options must be an object in options.recording.\" };\n const recordoptions = recording as Record<string, unknown>;\n if (recordoptions.scope !== undefined && recordoptions.scope !== \"tab\" && recordoptions.scope !== \"run\") return { allowed: false, reason: \"The reviewed recording scope must be tab or run.\" };\n if (recordoptions.fps !== undefined && (typeof recordoptions.fps !== \"number\" || !Number.isFinite(recordoptions.fps) || recordoptions.fps <= 0)) return { allowed: false, reason: \"The reviewed recording fps must be a positive number with no code ceiling.\" };\n if (recordoptions.bitrate !== undefined && (typeof recordoptions.bitrate !== \"number\" || !Number.isFinite(recordoptions.bitrate) || recordoptions.bitrate <= 0)) return { allowed: false, reason: \"The reviewed recording bitrate must be a positive number with no code ceiling.\" };\n if (recordoptions.audio !== undefined && typeof recordoptions.audio !== \"boolean\") return { allowed: false, reason: \"The reviewed recording audio flag must be a boolean.\" };\n }\n if (options.duration !== undefined && (typeof options.duration !== \"number\" || !Number.isFinite(options.duration) || options.duration <= 0)) return { allowed: false, reason: \"The reviewed recording duration must be a positive number of milliseconds with no code ceiling.\" };\n const consent = recordingconsentgranted(step);\n if (!consent.allowed) return consent;\n }\n if (kind === \"captureframe\") {\n if (options.timestamp !== undefined && (typeof options.timestamp !== \"number\" || !Number.isFinite(options.timestamp) || options.timestamp < 0)) return { allowed: false, reason: \"The reviewed frame timestamp must be zero or a positive number of seconds.\" };\n if (options.poster !== undefined && typeof options.poster !== \"boolean\") return { allowed: false, reason: \"The reviewed poster flag must be a boolean.\" };\n const capturecheck = validatecaptureoptions(options.capture);\n if (!capturecheck.allowed) return capturecheck;\n }\n if (kind === \"downloadimages\") {\n const filter = options.imagefilter;\n if (!filter || typeof filter !== \"object\" || Array.isArray(filter)) return { allowed: false, reason: \"A reviewed imagefilter is required in options before any image downloads.\" };\n const imagefilter = filter as Record<string, unknown>;\n if (imagefilter.selector !== undefined && !isnonempty(imagefilter.selector)) return { allowed: false, reason: \"The reviewed imagefilter selector must be a non-empty selector from the reviewed selector grammar.\" };\n if (imagefilter.minwidth !== undefined && (typeof imagefilter.minwidth !== \"number\" || !Number.isFinite(imagefilter.minwidth) || imagefilter.minwidth < 0)) return { allowed: false, reason: \"The reviewed imagefilter minimum width must be zero or a positive number of pixels.\" };\n if (imagefilter.minheight !== undefined && (typeof imagefilter.minheight !== \"number\" || !Number.isFinite(imagefilter.minheight) || imagefilter.minheight < 0)) return { allowed: false, reason: \"The reviewed imagefilter minimum height must be zero or a positive number of pixels.\" };\n if (imagefilter.formats !== undefined && (!Array.isArray(imagefilter.formats) || imagefilter.formats.length === 0 || !imagefilter.formats.every(item => isnonempty(item)))) return { allowed: false, reason: \"The reviewed imagefilter format list must be a non-empty list of mime or extension patterns when present.\" };\n if (options.naming !== undefined) {\n const namingcheck = validatecapturenaming(options.naming);\n if (!namingcheck.allowed) return namingcheck;\n }\n }\n if (kind === \"shotcanvas\") {\n const capturecheck = validatecaptureoptions(options.capture);\n if (!capturecheck.allowed) return capturecheck;\n }\n if (kind === \"probestream\" && options.selector !== undefined && !isnonempty(options.selector)) return { allowed: false, reason: \"The reviewed stream probe scope selector must be a non-empty string.\" };\n if (kind === \"timelapse\") {\n const lapse = options.lapse;\n if (!lapse || typeof lapse !== \"object\" || Array.isArray(lapse)) return { allowed: false, reason: \"A reviewed lapse plan with interval, duration and format is required in options.\" };\n const plan = lapse as Record<string, unknown>;\n if (typeof plan.interval !== \"number\" || !Number.isFinite(plan.interval) || plan.interval <= 0) return { allowed: false, reason: \"The reviewed lapse interval must be a positive number of milliseconds with no code ceiling.\" };\n if (typeof plan.duration !== \"number\" || !Number.isFinite(plan.duration) || plan.duration <= 0) return { allowed: false, reason: \"The reviewed lapse duration must be a positive number of milliseconds with no code ceiling.\" };\n if (plan.format !== undefined && plan.format !== \"png\" && plan.format !== \"jpeg\" && plan.format !== \"webp\") return { allowed: false, reason: \"The reviewed lapse format must be png, jpeg or webp.\" };\n const budget = lapsebudgetallowed(plan.interval as number, plan.duration as number, typeof options.wait === \"number\" ? options.wait : undefined);\n if (!budget.allowed) return budget;\n const capturecheck = validatecaptureoptions(options.capture);\n if (!capturecheck.allowed) return capturecheck;\n }\n if (kind === \"convertimage\" || kind === \"makethumbs\") {\n const single = options.capture;\n const list = options.captures;\n const hasone = isnonempty(single);\n const haslist = Array.isArray(list) && list.length > 0 && list.every(item => isnonempty(item));\n if (!hasone && !haslist) return { allowed: false, reason: \"A reviewed capture id or a reviewed non-empty capture id list is required in options.\" };\n if (hasone && haslist) return { allowed: false, reason: \"The reviewed step needs one capture id or a capture id list, not both.\" };\n }\n if (kind === \"convertimage\") {\n const convert = options.convert;\n if (!convert || typeof convert !== \"object\" || Array.isArray(convert)) return { allowed: false, reason: \"A reviewed convert directive with a target format is required in options.\" };\n const directive = convert as Record<string, unknown>;\n if (directive.target !== \"png\" && directive.target !== \"jpeg\" && directive.target !== \"webp\") return { allowed: false, reason: \"The reviewed conversion target must be png, jpeg or webp.\" };\n if (directive.source !== undefined && directive.source !== \"png\" && directive.source !== \"jpeg\" && directive.source !== \"webp\") return { allowed: false, reason: \"The reviewed conversion source must be png, jpeg or webp.\" };\n if (directive.quality !== undefined && (typeof directive.quality !== \"number\" || !Number.isFinite(directive.quality) || directive.quality < 0 || directive.quality > 100)) return { allowed: false, reason: \"The reviewed conversion quality must stay between zero and one hundred with no code cap inside that range.\" };\n }\n if (kind === \"makethumbs\") {\n const thumb = options.thumb;\n if (!thumb || typeof thumb !== \"object\" || Array.isArray(thumb)) return { allowed: false, reason: \"A reviewed thumb directive with size, fit and suffix is required in options.\" };\n const directive = thumb as Record<string, unknown>;\n if (typeof directive.size !== \"number\" || !Number.isFinite(directive.size) || directive.size <= 0) return { allowed: false, reason: \"The reviewed thumbnail size must be a positive number of pixels with no fixed set.\" };\n if (directive.fit !== \"cover\" && directive.fit !== \"contain\") return { allowed: false, reason: \"The reviewed thumbnail fit must be cover or contain.\" };\n if (!isnonempty(directive.suffix)) return { allowed: false, reason: \"The reviewed thumbnail naming suffix must be a non-empty string.\" };\n }\n return { allowed: true };\n}\n\n/** Validates a single proposal against the active tab origin and local policy. */\nexport function validatestep(step: toolstep, origin: string): policyevaluation {\n if (!allowedactions.has(step.kind)) return { allowed: false, reason: \"Unsupported action kind.\" };\n if (!step.summary.trim()) return { allowed: false, reason: \"A human-readable action summary is required.\" };\n let options: Record<string, unknown>;\n try { options = parseoptions(step); } catch { return { allowed: false, reason: \"Step options must be a JSON object.\" }; }\n const hastargetref = options.targetref !== undefined;\n if (targetactions.has(step.kind) && !step.target?.trim() && !hastargetref) return { allowed: false, reason: \"A page target is required.\" };\n if (valueactions.has(step.kind) && !step.value?.trim()) return { allowed: false, reason: \"A reviewed value is required.\" };\n if (step.kind === \"select\" && !step.value?.trim()) return { allowed: false, reason: \"A reviewed option value is required.\" };\n if (step.kind === \"navigate\" && !step.value) return { allowed: false, reason: \"A navigation URL is required.\" };\n if (hastargetref) {\n const reference = validatetargetref(options.targetref);\n if (!reference.allowed) return reference;\n }\n if (step.kind === \"wait\") {\n try { waitduration(step); } catch { return { allowed: false, reason: \"Wait duration must be zero or a positive number of milliseconds.\" }; }\n }\n if (step.kind === \"navigate\") {\n try {\n if (new URL(step.value ?? \"\").origin !== origin) return { allowed: false, reason: \"Navigation must remain within the approved origin.\" };\n } catch {\n return { allowed: false, reason: \"Navigation URL is invalid.\" };\n }\n }\n if (step.kind === \"tabcreate\" || step.kind === \"windowcreate\" || step.kind === \"downloadfile\") {\n try {\n const url = new URL(step.value ?? \"\");\n if (url.protocol !== \"https:\") return { allowed: false, reason: \"The reviewed URL must use HTTPS.\" };\n } catch {\n return { allowed: false, reason: \"The reviewed URL is invalid.\" };\n }\n }\n if (step.kind === \"tabactivate\" || step.kind === \"tabclose\" || step.kind === \"tabreload\" || step.kind === \"windowclose\" || step.kind === \"windowresize\") {\n if (!isnumericid(step.value)) return { allowed: false, reason: \"A numeric browser id is required.\" };\n }\n if (step.kind === \"zoomset\") {\n const zoom = Number(step.value);\n if (!Number.isFinite(zoom) || zoom <= 0) return { allowed: false, reason: \"The reviewed zoom must be a positive number.\" };\n }\n if (step.kind === \"setattribute\" || step.kind === \"writestorage\") {\n const keyname = step.kind === \"setattribute\" ? \"name\" : \"key\";\n if (typeof options[keyname] !== \"string\" || !(options[keyname] as string).trim()) return { allowed: false, reason: `A reviewed ${keyname} is required in options.` };\n if (typeof options.value !== \"string\") return { allowed: false, reason: \"A reviewed value is required in options.\" };\n }\n if (step.kind === \"windowresize\") {\n if (typeof options.width !== \"number\" || typeof options.height !== \"number\" || !Number.isFinite(options.width) || !Number.isFinite(options.height)) return { allowed: false, reason: \"Reviewed width and height numbers are required in options.\" };\n }\n if ((step.kind === \"scrollpage\" || step.kind === \"scrollby\") && (!numericoption(options, \"x\") || !numericoption(options, \"y\"))) return { allowed: false, reason: \"Scroll amounts must be numbers in options.\" };\n if (step.kind === \"waitfor\" && options.timeout !== undefined && (typeof options.timeout !== \"number\" || options.timeout < 0)) return { allowed: false, reason: \"The waitfor timeout must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"movepointer\") {\n const path = options.pointpath;\n if (!path || typeof path !== \"object\" || Array.isArray(path)) return { allowed: false, reason: \"A reviewed pointpath with start and end points is required in options.\" };\n const points = path as Record<string, unknown>;\n if (!ispoint(points.start) || !ispoint(points.end)) return { allowed: false, reason: \"The reviewed pointpath needs numeric start and end points.\" };\n if (points.waypoints !== undefined && (!Array.isArray(points.waypoints) || !points.waypoints.every(waypoint => ispoint(waypoint)))) return { allowed: false, reason: \"The reviewed pointpath waypoints must be numeric points.\" };\n if (!nonnegativeoption(points, \"duration\")) return { allowed: false, reason: \"The reviewed pointpath duration must be zero or a positive number of milliseconds.\" };\n const speed = options.speedprofile;\n if (speed !== undefined) {\n if (!speed || typeof speed !== \"object\" || Array.isArray(speed)) return { allowed: false, reason: \"The reviewed speed profile must be an object.\" };\n const profile = speed as Record<string, unknown>;\n if (profile.easing !== undefined && profile.easing !== \"linear\" && profile.easing !== \"easeinout\") return { allowed: false, reason: \"The reviewed easing must be linear or easeinout.\" };\n if (!nonnegativeoption(profile, \"peak\")) return { allowed: false, reason: \"The reviewed peak velocity must be zero or a positive number.\" };\n if (!nonnegativeoption(profile, \"jitter\")) return { allowed: false, reason: \"The reviewed jitter window must be zero or a positive number of milliseconds.\" };\n }\n }\n if (step.kind === \"clickpoint\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"point\")) return { allowed: false, reason: \"A reviewed point target reference is required in options.\" };\n if (step.kind === \"clicktext\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"text\")) return { allowed: false, reason: \"A reviewed text target reference is required in options.\" };\n if (step.kind === \"clickaria\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"aria\")) return { allowed: false, reason: \"A reviewed aria target reference is required in options.\" };\n if (step.kind === \"clickname\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"name\")) return { allowed: false, reason: \"A reviewed name target reference is required in options.\" };\n if (step.kind === \"resolvexpath\" && (!hastargetref || (options.targetref as Record<string, unknown>).mode !== \"xpath\")) return { allowed: false, reason: \"A reviewed xpath target reference is required in options.\" };\n if (step.kind === \"typetime\" && options.delay !== undefined && (typeof options.delay !== \"number\" || !Number.isFinite(options.delay) || options.delay < 0)) return { allowed: false, reason: \"The reviewed per keystroke delay must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"submitsearch\") {\n if (!isnonempty(options.results)) return { allowed: false, reason: \"A reviewed results region selector is required in options.\" };\n if (options.timeout !== undefined && (typeof options.timeout !== \"number\" || !Number.isFinite(options.timeout) || options.timeout < 0)) return { allowed: false, reason: \"The submitsearch timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"selectmulti\") {\n const values = options.values;\n if (!Array.isArray(values) || values.length === 0 || !values.every(value => isnonempty(value))) return { allowed: false, reason: \"A reviewed list of option values is required in options.\" };\n }\n if (step.kind === \"setslider\") {\n const slider = Number(step.value);\n if (!Number.isFinite(slider)) return { allowed: false, reason: \"The reviewed slider value must be a number.\" };\n }\n if (step.kind === \"setdate\" && !/^\\d{4}-\\d{2}-\\d{2}$/.test(step.value ?? \"\")) return { allowed: false, reason: \"The reviewed date must use the yyyy-mm-dd form.\" };\n if (step.kind === \"setcolor\" && !/^#[0-9a-fA-F]{6}$/.test(step.value ?? \"\")) return { allowed: false, reason: \"The reviewed color must use the #rrggbb form.\" };\n if (step.kind === \"keyhold\" && options.holdid !== undefined && !isnonempty(options.holdid)) return { allowed: false, reason: \"The reviewed hold id must be a non-empty string.\" };\n if (step.kind === \"dismissdialog\") {\n const accept = options.accept;\n const answer = options.answer;\n if (accept === undefined && !isnonempty(answer)) return { allowed: false, reason: \"A reviewed accept flag or prompt answer is required in options.\" };\n if (accept !== undefined && typeof accept !== \"boolean\") return { allowed: false, reason: \"The reviewed dialog accept flag must be a boolean.\" };\n if (answer !== undefined && !isnonempty(answer)) return { allowed: false, reason: \"The reviewed prompt answer must be a non-empty string.\" };\n }\n if (step.kind === \"pierceshadow\" && options.shadow !== undefined) {\n if (!Array.isArray(options.shadow) || !options.shadow.every(item => isnonempty(item))) return { allowed: false, reason: \"The reviewed shadow path must be a list of non-empty selectors.\" };\n }\n if (step.kind === \"enterframe\") {\n const path = options.framepath;\n if (!Array.isArray(path) || path.length === 0 || !path.every(item => typeof item === \"number\" && Number.isInteger(item) && item >= 0)) return { allowed: false, reason: \"A reviewed frame path of frame indexes is required in options.\" };\n return validateinnerstep(options, origin);\n }\n if (step.kind === \"retryaction\") {\n const inner = validateinnerstep(options, origin);\n if (!inner.allowed) return inner;\n const rule = options.retryrule;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule)) return { allowed: false, reason: \"A reviewed retry rule with attempts is required in options.\" };\n const retry = rule as Record<string, unknown>;\n if (typeof retry.attempts !== \"number\" || !Number.isInteger(retry.attempts) || retry.attempts < 1) return { allowed: false, reason: \"The reviewed retry attempts must be a positive integer with no code ceiling.\" };\n if (!nonnegativeoption(retry, \"settle\")) return { allowed: false, reason: \"The reviewed retry settle window must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(retry, \"tolerance\")) return { allowed: false, reason: \"The reviewed retry movement tolerance must be zero or a positive number of pixels.\" };\n }\n if (watchactions.has(step.kind)) {\n if (typeof options.lifetime !== \"number\" || !Number.isFinite(options.lifetime) || options.lifetime <= 0) return { allowed: false, reason: \"A reviewed watch lifetime window in milliseconds is required in options.\" };\n if (options.scopes !== undefined && (!Array.isArray(options.scopes) || !options.scopes.every(scope => isnonempty(scope)))) return { allowed: false, reason: \"The reviewed watch scopes must be a list of non-empty selectors.\" };\n if (options.events !== undefined && (!Array.isArray(options.events) || !options.events.every(event => isnonempty(event)))) return { allowed: false, reason: \"The reviewed watch event kinds must be a list of non-empty strings.\" };\n if (!nonnegativeoption(options, \"poll\")) return { allowed: false, reason: \"The reviewed watch poll interval must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"waitquiet\") {\n const rule = options.quietrule;\n if (!rule || typeof rule !== \"object\" || Array.isArray(rule)) return { allowed: false, reason: \"A reviewed quietrule with an idle threshold is required in options.\" };\n const quiet = rule as Record<string, unknown>;\n if (typeof quiet.idle !== \"number\" || !Number.isFinite(quiet.idle) || quiet.idle <= 0) return { allowed: false, reason: \"The reviewed quiet idle threshold must be a positive number of milliseconds with no code ceiling.\" };\n if (!nonnegativeoption(quiet, \"poll\")) return { allowed: false, reason: \"The reviewed quiet poll interval must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(quiet, \"timeout\")) return { allowed: false, reason: \"The reviewed quiet timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"diffsnapshots\") {\n const versions = options.versions;\n if (!Array.isArray(versions) || versions.length !== 2 || !versions.every(version => typeof version === \"number\" && Number.isInteger(version) && version >= 1)) return { allowed: false, reason: \"Two reviewed observation version numbers are required in options.\" };\n }\n if (step.kind === \"openlink\" || step.kind === \"openprivate\" || step.kind === \"deeplink\") {\n const targetcheck = validatenavtarget(options.navtarget, step.kind);\n if (!targetcheck.allowed) return targetcheck;\n if (step.kind === \"deeplink\") {\n const app = options.app;\n if (!isnonempty(app)) return { allowed: false, reason: \"A reviewed deep link app pattern is required in options.\" };\n const params = options.params;\n if (params !== undefined && (!params || typeof params !== \"object\" || Array.isArray(params) || !Object.values(params).every(item => typeof item === \"string\"))) return { allowed: false, reason: \"The reviewed deep link params must be an object of string values.\" };\n }\n }\n if (step.kind === \"waitload\" && !nonnegativeoption(options, \"timeout\")) return { allowed: false, reason: \"The waitload timeout must be zero or a positive number of milliseconds.\" };\n if (step.kind === \"waiturl\" || step.kind === \"spawait\") {\n if (step.kind === \"waiturl\") {\n const patterncheck = validateurlpattern(options.urlpattern);\n if (!patterncheck.allowed) return patterncheck;\n }\n if (!nonnegativeoption(options, \"timeout\")) return { allowed: false, reason: \"The wait timeout must be zero or a positive number of milliseconds.\" };\n if (!nonnegativeoption(options, \"poll\")) return { allowed: false, reason: \"The wait poll interval must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"followlink\") {\n if (options.fragment !== undefined && typeof options.fragment !== \"boolean\") return { allowed: false, reason: \"The reviewed followlink fragment flag must be a boolean.\" };\n }\n if (step.kind === \"spanav\") {\n if (options.routepattern !== undefined) {\n const routecheck = validateurlpattern(options.routepattern);\n if (!routecheck.allowed) return routecheck;\n }\n if (!nonnegativeoption(options, \"timeout\")) return { allowed: false, reason: \"The spanav route timeout must be zero or a positive number of milliseconds.\" };\n }\n if (step.kind === \"rewritequery\") {\n const set = options.set;\n const remove = options.remove;\n if (set === undefined && remove === undefined) return { allowed: false, reason: \"Reviewed query parameters to set or remove are required in options.\" };\n if (set !== undefined && (!set || typeof set !== \"object\" || Array.isArray(set) || !Object.values(set).every(item => typeof item === \"string\"))) return { allowed: false, reason: \"The reviewed query parameters to set must be an object of string values.\" };\n if (remove !== undefined && (!Array.isArray(remove) || !remove.every(item => isnonempty(item)))) return { allowed: false, reason: \"The reviewed query parameters to remove must be a list of non-empty names.\" };\n }\n if (step.kind === \"navlist\") {\n const listcheck = validateurllist(options, \"urls\");\n if (!listcheck.allowed) return listcheck;\n }\n if (step.kind === \"navprofile\") {\n const profilecheck = validatewaitprofile(options.waitprofile);\n if (!profilecheck.allowed) return profilecheck;\n }\n if (step.kind === \"handleauth\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"A reviewed HTTPS origin or url is required as the auth target.\" };\n if (step.kind === \"printpdf\" && options.name !== undefined && !isnonempty(options.name)) return { allowed: false, reason: \"The reviewed artifact name must be a non-empty string.\" };\n if (step.kind === \"prefetch\") {\n const listcheck = validateurllist(options, \"urls\");\n if (!listcheck.allowed) return listcheck;\n }\n if (step.kind === \"preconnect\") {\n const origins = options.origins;\n if (!Array.isArray(origins) || origins.length === 0 || !origins.every(originurl => ishttpsurl(originurl))) return { allowed: false, reason: \"A reviewed non-empty list of HTTPS origins is required in options.\" };\n }\n if (step.kind === \"reopentab\" && step.value !== undefined && !ishttpsurl(step.value)) return { allowed: false, reason: \"The reviewed reopen url must use HTTPS.\" };\n if (step.kind === \"navrate\") {\n const limitcheck = validateratelimit(options.ratelimit);\n if (!limitcheck.allowed) return limitcheck;\n }\n if (step.kind === \"checksafe\" && !ishttpsurl(step.value)) return { allowed: false, reason: \"A reviewed HTTPS url is required for the safety check.\" };\n if (step.kind === \"batchopen\") {\n const listcheck = validateurllist(options, \"urls\");\n if (!listcheck.allowed) return listcheck;\n }\n if (istabscommandkind(step.kind)) {\n const tabscheck = validatetabsgrammar(step, options);\n if (!tabscheck.allowed) return tabscheck;\n }\n if (isformkind(step.kind)) {\n const formcheck = validateformgrammar(step, options);\n if (!formcheck.allowed) return formcheck;\n }\n if (isdatasetkind(step.kind)) {\n const datacheck = validatedatagrammar(step, options, origin);\n if (!datacheck.allowed) return datacheck;\n }\n if (isfileskind(step.kind)) {\n const filescheck = validatefilesgrammar(step, options);\n if (!filescheck.allowed) return filescheck;\n }\n if (iscapturekind(step.kind)) {\n const capturecheck = validatecapturegrammar(step, options);\n if (!capturecheck.allowed) return capturecheck;\n }\n if (ismediakind(step.kind)) {\n const mediacheck = validatemediagrammar(step, options);\n if (!mediacheck.allowed) return mediacheck;\n }\n if (ishttpkind(step.kind)) {\n const httpcheck = validatehttpgrammar(step, options);\n if (!httpcheck.allowed) return httpcheck;\n }\n if (issocketkind(step.kind)) {\n const socketcheck = validatesocketgrammar(step, options);\n if (!socketcheck.allowed) return socketcheck;\n }\n if (isnetwatchkind(step.kind)) {\n const netwatchcheck = validatenetwatchgrammar(step, options);\n if (!netwatchcheck.allowed) return netwatchcheck;\n }\n if (iscontrolkind(step.kind)) {\n const controlcheck = validatecontrolgrammar(step, options);\n if (!controlcheck.allowed) return controlcheck;\n }\n if (isdebugkind(step.kind)) {\n const timelinecheck = validatetimelinegrammar(step, options);\n if (!timelinecheck.allowed) return timelinecheck;\n }\n if (iscdpkind(step.kind)) {\n const cdpcheck = validatecdpgrammar(step, options);\n if (!cdpcheck.allowed) return cdpcheck;\n }\n if (isprofilekind(step.kind)) {\n const profilecheck = validateprofilegrammar(step, options);\n if (!profilecheck.allowed) return profilecheck;\n }\n if (isemulationkind(step.kind)) {\n const emulationcheck = validateemulationgrammar(step, options);\n if (!emulationcheck.allowed) return emulationcheck;\n }\n if (issessionkind(step.kind)) {\n const sessioncheck = validatesessiongrammar(step, options);\n if (!sessioncheck.allowed) return sessioncheck;\n }\n if (isworkflowkind(step.kind)) {\n const workflowcheck = validateworkflowgrammar(step, options);\n if (!workflowcheck.allowed) return workflowcheck;\n }\n if (istriggeraction(step.kind)) {\n const triggercheck = validatetriggergrammar(step, options);\n if (!triggercheck.allowed) return triggercheck;\n }\n if (step.kind === \"tabcreate\") {\n if (options.background !== undefined && typeof options.background !== \"boolean\") return { allowed: false, reason: \"The reviewed background flag must be a boolean.\" };\n if (options.window !== undefined && (typeof options.window !== \"number\" || !Number.isInteger(options.window) || options.window < 0)) return { allowed: false, reason: \"The reviewed target window id must be a non-negative integer.\" };\n }\n if (step.kind === \"windowcreate\") {\n for (const field of [\"left\", \"top\", \"width\", \"height\"]) {\n if (options[field] !== undefined && (typeof options[field] !== \"number\" || !Number.isFinite(options[field]))) return { allowed: false, reason: `The reviewed window ${field} must be a number.` };\n }\n if (options.state !== undefined && ![\"normal\", \"maximized\", \"minimized\", \"fullscreen\"].includes(options.state as string)) return { allowed: false, reason: \"The reviewed window state must be normal, maximized, minimized or fullscreen.\" };\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; verdicts?: safetyverdict[]; settings?: runsettings }): policyevaluation {\n const now = input.now ?? Date.now();\n const gate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"execute an action\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"The plan has not received explicit approval.\" };\n if (input.plan.expiresat <= now) return { allowed: false, reason: \"The approved plan has expired.\" };\n if ((input.step.kind === \"pierceshadow\" || input.step.kind === \"enterframe\") && !origingranted(input.session, input.origin)) return { allowed: false, reason: \"The shadow or frame step is outside the session origin grants.\" };\n if (input.step.kind === \"readjson\" && !origingranted(input.session, input.origin)) return { allowed: false, reason: \"The json state read is outside the session origin grants.\" };\n if (isexportkind(input.step.kind)) {\n const exportgate = exportgranted(input.session, input.origin);\n if (!exportgate.allowed) return exportgate;\n }\n if (input.step.kind === \"navlist\") {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(input.step); } catch { options = {}; }\n for (const url of Array.isArray(options.urls) ? options.urls : []) {\n if (typeof url !== \"string\") continue;\n const navigation = navigationgranted(input.session, url);\n if (!navigation.allowed) return navigation;\n }\n }\n if (islayoutkind(input.step.kind) && !layoutmutationgranted(input.session, now).allowed) return { allowed: false, reason: \"Group and layout mutations stay inside the active session.\" };\n if (input.step.kind === \"submitform\" || input.step.kind === \"retryform\") {\n if (!input.plan) return { allowed: false, reason: \"Form submission requires an asksubmit review step before it.\" };\n const reviewgate = submitreviewgranted(input.plan.steps, input.step.id);\n if (!reviewgate.allowed) return reviewgate;\n }\n if (input.step.kind === \"consentpassword\") {\n const consentgate = passwordconsentgranted(input.step);\n if (!consentgate.allowed) return consentgate;\n }\n if (input.step.kind === \"readclipboard\") {\n const clipgate = clipboardconsentgranted(input.step);\n if (!clipgate.allowed) return clipgate;\n }\n if (input.step.kind === \"interceptmime\" && !origingranted(input.session, input.origin)) return { allowed: false, reason: \"The download interception is outside the session origin grants.\" };\n if (iscapturekind(input.step.kind)) {\n const capturegatecheck = capturegate(input.session, input.tabid, input.origin, now);\n if (!capturegatecheck.allowed) return capturegatecheck;\n let captureoptions: Record<string, unknown> = {};\n try { captureoptions = parseoptions(input.step); } catch { captureoptions = {}; }\n const target = (captureoptions.capture as Record<string, unknown> | undefined)?.exporttarget;\n if (target !== undefined && target !== \"memory\" && target !== \"download\" && target !== \"clipboard\") return { allowed: false, reason: \"The capture export target must be memory, download or clipboard.\" };\n }\n if (ismediakind(input.step.kind)) {\n const mediagatecheck = mediagate(input.session, input.tabid, input.origin, now);\n if (!mediagatecheck.allowed) return mediagatecheck;\n }\n if (isrecordingkind(input.step.kind)) {\n const recordinggate = recordingconsentgranted(input.step);\n if (!recordinggate.allowed) return recordinggate;\n }\n if (ishttpkind(input.step.kind)) {\n const target = outboundtarget(input.step);\n if (target !== undefined) {\n const outboundgate = origincheck(input.session, target);\n if (!outboundgate.allowed) return outboundgate;\n }\n if (input.step.kind === \"fetchurl\" || input.step.kind === \"callrest\" || input.step.kind === \"callgraphql\") {\n const consentgate = fetchconsentrefgranted(input.step);\n if (!consentgate.allowed) return consentgate;\n }\n }\n if (issocketkind(input.step.kind)) {\n const channelurl = sockettarget(input.step);\n if (channelurl !== undefined) {\n const channelgate = socketgate(input.session, channelurl);\n if (!channelgate.allowed) return channelgate;\n }\n }\n if (input.step.kind === \"watchrequests\") {\n const watchgatecheck = watchgate(input.session, input.settings, now);\n if (!watchgatecheck.allowed) return watchgatecheck;\n }\n if (isdebugkind(input.step.kind)) {\n const timelinegatecheck = timelinegate(input.session, input.tabid, input.origin, now);\n if (!timelinegatecheck.allowed) return timelinegatecheck;\n }\n if (iscdpkind(input.step.kind)) {\n const debuggatecheck = debuggate(input.session, input.tabid, input.origin, now);\n if (!debuggatecheck.allowed) return debuggatecheck;\n if (!input.plan) return { allowed: false, reason: \"The devtools protocol steps need an approved plan.\" };\n const allowlist = planallowlist(input.plan.steps);\n if (input.step.kind !== \"attachcdp\") {\n if (allowlist === undefined) return { allowed: false, reason: \"The devtools protocol step needs the attachcdp step of the same plan with its enabled domains first.\" };\n if (input.step.kind === \"cdpcmd\") {\n let cdpoptions: Record<string, unknown> = {};\n try { cdpoptions = parseoptions(input.step); } catch { cdpoptions = {}; }\n const command = cdpoptions.command && typeof cdpoptions.command === \"object\" && !Array.isArray(cdpoptions.command) ? cdpoptions.command as Record<string, unknown> : undefined;\n const method = typeof command?.method === \"string\" ? command.method : \"\";\n if (methoddomain(method) === undefined || !allowlistcovers(allowlist, method)) return { allowed: false, reason: `The raw command ${method || \"\"} stays outside the enabled domain allowlist of the plan attach; review the attach domains or the method gates.` };\n }\n }\n let cdpoptions: Record<string, unknown> = {};\n try { cdpoptions = parseoptions(input.step); } catch { cdpoptions = {}; }\n if (input.step.kind === \"setbreakpoint\") {\n const breakpoint = breakpointinputof(cdpoptions.breakpoint);\n if (breakpoint) {\n const targetgate = origincheck(input.session, breakpoint.url);\n if (!targetgate.allowed) return targetgate;\n }\n }\n if (input.step.kind === \"overridescript\") {\n const override = overrideinputof(cdpoptions.override);\n if (override) {\n const targetgate = origincheck(input.session, override.urlpattern);\n if (!targetgate.allowed) return targetgate;\n }\n }\n }\n if (isprofilekind(input.step.kind)) {\n let profileoptions: Record<string, unknown> = {};\n try { profileoptions = parseoptions(input.step); } catch { profileoptions = {}; }\n const targets = [\n ...(attachtargetof(profileoptions.target) !== undefined ? [attachtargetof(profileoptions.target) as attachtarget] : []),\n ...(Array.isArray(profileoptions.attachtargets) ? profileoptions.attachtargets.flatMap(target => { const parsed = attachtargetof(target); return parsed !== undefined ? [parsed] : []; }) : []),\n ];\n const targetgatecheck = targetgate({ session: input.session, tabid: input.tabid, origin: input.origin, targets, grants: undefined, now });\n if (!targetgatecheck.allowed) return targetgatecheck;\n if (input.step.kind === \"capturesourcemaps\") {\n for (const url of Array.isArray(profileoptions.scripts) ? profileoptions.scripts : []) {\n if (typeof url !== \"string\") continue;\n const scriptgate = origincheck(input.session, url);\n if (!scriptgate.allowed) return scriptgate;\n }\n }\n }\n if (isemulationkind(input.step.kind)) {\n const emugatecheck = emugate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });\n if (!emugatecheck.allowed) return emugatecheck;\n }\n if (issessionkind(input.step.kind)) {\n const sessiongatecheck = sessionrestoregate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });\n if (!sessiongatecheck.allowed) return sessiongatecheck;\n if (input.step.kind === \"restoresession\") {\n let restoreoptions: Record<string, unknown> = {};\n try { restoreoptions = parseoptions(input.step); } catch { restoreoptions = {}; }\n for (const url of Array.isArray(restoreoptions.origins) ? restoreoptions.origins : []) {\n if (typeof url !== \"string\" || !url) continue;\n const origingate = origincheck(input.session, url);\n if (!origingate.allowed) return { allowed: false, reason: `The session restore reopens ${url} outside the session origin grants; review the restore record or grant the origin.` };\n }\n }\n }\n if (isworkflowkind(input.step.kind)) {\n const workflowgatecheck = workflowgate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });\n if (!workflowgatecheck.allowed) return workflowgatecheck;\n }\n if (istriggeraction(input.step.kind)) {\n const triggergatecheck = triggergate({ session: input.session, plan: input.plan, step: input.step, tabid: input.tabid, origin: input.origin, now });\n if (!triggergatecheck.allowed) return triggergatecheck;\n }\n if (iscontrolkind(input.step.kind)) {\n const controlgate = sessiongate({ session: input.session, tabid: input.tabid, origin: input.origin, now, action: \"control the network\" });\n if (!controlgate.allowed) return controlgate;\n let controloptions: Record<string, unknown> = {};\n try { controloptions = parseoptions(input.step); } catch { controloptions = {}; }\n if (input.step.kind === \"blockrequest\") {\n const blockgatecheck = blockgate(input.session, input.step, now);\n if (!blockgatecheck.allowed) return blockgatecheck;\n const rule = blockruleof(controloptions.block);\n if (rule) {\n const blockorigin = origincheck(input.session, rule.urlpattern);\n if (!blockorigin.allowed) return blockorigin;\n }\n }\n if (input.step.kind === \"mockresponse\" || input.step.kind === \"rewriteheaders\") {\n const patterns = input.step.kind === \"mockresponse\" ? [mockspecof(controloptions.mock)?.urlpattern ?? \"\"] : (Array.isArray(controloptions.rules) ? controloptions.rules.map(item => item && typeof item === \"object\" && !Array.isArray(item) ? String((item as Record<string, unknown>).urlpattern ?? \"\") : \"\") : []);\n for (const pattern of patterns) {\n const patterngate = origincheck(input.session, pattern);\n if (!patterngate.allowed) return patterngate;\n }\n }\n if (input.step.kind === \"setcookies\" || input.step.kind === \"readcookies\" || input.step.kind === \"clearcookies\") {\n const domain = typeof controloptions.domain === \"string\" && controloptions.domain.trim() ? controloptions.domain : Array.isArray(controloptions.cookies) ? String((controloptions.cookies[0] as Record<string, unknown> | undefined)?.domain ?? \"\") : \"\";\n if (!domain) return { allowed: false, reason: \"A reviewed cookie domain is required before cookie control runs.\" };\n const cookiegatecheck = cookiegate(input.session, domain, now);\n if (!cookiegatecheck.allowed) return cookiegatecheck;\n }\n if (input.step.kind === \"authflow\") {\n const authconsent = authconsentgranted(input.step);\n if (!authconsent.allowed) return authconsent;\n }\n if (input.step.kind === \"saveapikey\") {\n const keyconsent = apikeyconsentgranted(input.step);\n if (!keyconsent.allowed) return keyconsent;\n }\n if (input.step.kind === \"routeproxy\") {\n const proxygatecheck = proxygate(input.session, input.step, now);\n if (!proxygatecheck.allowed) return proxygatecheck;\n }\n const target = controltarget(input.step);\n if (target !== undefined) {\n const targetgate = origincheck(input.session, target);\n if (!targetgate.allowed) return targetgate;\n }\n }\n if (input.step.kind === \"extractapi\") {\n let replayoptions: Record<string, unknown> = {};\n try { replayoptions = parseoptions(input.step); } catch { replayoptions = {}; }\n const replay = apireplayspecof(replayoptions.replay);\n if (replay !== undefined) {\n const replaygate = origincheck(input.session, replay.endpoint);\n if (!replaygate.allowed) return replaygate;\n }\n }\n if (input.step.kind === \"openlink\" || input.step.kind === \"openprivate\" || input.step.kind === \"batchopen\" || input.step.kind === \"prefetch\" || input.step.kind === \"deeplink\" || input.step.kind === \"reopentab\") {\n let options: Record<string, unknown> = {};\n try { options = parseoptions(input.step); } catch { options = {}; }\n const grammar = validatestep(input.step, input.origin);\n if (!grammar.allowed) return grammar;\n const grants = input.session?.grants ?? [input.session?.origin ?? input.origin];\n const targets: unknown[] = input.step.kind === \"batchopen\" || input.step.kind === \"prefetch\" ? (Array.isArray(options.urls) ? options.urls : []) : input.step.kind === \"reopentab\" ? [input.step.value] : [(options.navtarget as Record<string, unknown> | undefined)?.url];\n for (const target of targets) {\n if (typeof target !== \"string\" || !target) continue;\n const verified = originverified(target, grants, input.verdicts ?? []);\n if (!verified.allowed) return verified;\n }\n }\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\n/** Lists every reviewed action kind of the policy table so the step library of the editor browses the whole vocabulary. */\nexport function reviewedkinds(): string[] {\n return [...allowedactions].sort();\n}\n\n/** The editor save gate: the canvas model of a save needs a live session and an approved plan like every other reviewed artifact, its nodes must be steps or block invocations with unique ids, its edges must reference existing steps and run forward only so no cycle forms, and the composed record still passes the full workflow grammar through the composition the save triggers. */\nexport function editorsavegate(input: { session: agentsession | undefined; plan: agentplan | undefined; model: editormodel; now: number }): policyevaluation {\n const gate = sessiongate({ session: input.session, tabid: input.session?.tabid ?? 0, origin: input.session?.origin ?? \"https://example.com\", now: input.now, action: \"save the workflow editor canvas\" });\n if (!gate.allowed) return gate;\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Editor saves need the approved plan review before a new workflow version composes.\" };\n const model = input.model;\n if (typeof model.name !== \"string\" || !model.name.trim()) return { allowed: false, reason: \"The workflow name of the canvas must be a non-empty string.\" };\n if (typeof model.version !== \"number\" || !Number.isInteger(model.version) || model.version < 1) return { allowed: false, reason: \"The workflow version of the canvas must be a positive integer.\" };\n if (!Array.isArray(model.origins) || model.origins.length === 0) return { allowed: false, reason: \"The canvas needs at least one granted HTTPS origin.\" };\n const ids = new Set<string>();\n for (const node of model.nodes) {\n if ((node.step === undefined) === (node.invocation === undefined)) return { allowed: false, reason: \"Every canvas node must be exactly one workflow step or one block invocation.\" };\n const id = node.id ?? (node.step !== undefined ? node.step.id : (node.invocation as { block: string }).block);\n if (!id || ids.has(id)) return { allowed: false, reason: `The canvas node id ${id || \"(empty)\"} must be unique.` };\n ids.add(id);\n }\n const reachable = new Set<string>();\n for (const node of model.nodes) {\n if (node.step !== undefined) { reachable.add(node.step.id); continue; }\n const walk = (entries: Array<{ id?: string; kind?: string; label?: string; block?: string }>): void => {\n for (const entry of entries) {\n if (typeof entry.id === \"string\" && typeof entry.kind === \"string\") { reachable.add(entry.id); continue; }\n if (typeof entry.block === \"string\") {\n const nested = model.blocks.find(candidate => candidate.name === entry.block);\n if (nested) walk(nested.steps as Array<{ id?: string; kind?: string; label?: string; block?: string }>);\n }\n }\n };\n const block = model.blocks.find(candidate => candidate.name === (node.invocation as { block: string }).block);\n if (!block) return { allowed: false, reason: `The block ${(node.invocation as { block: string }).block} of the canvas has no definition.` };\n walk(block.steps as Array<{ id?: string; kind?: string; label?: string; block?: string }>);\n }\n let order = 0;\n const positionof = new Map<string, number>();\n for (const node of model.nodes) {\n if (node.step !== undefined) { positionof.set(node.step.id, order); order += 1; continue; }\n const walk = (entries: Array<{ id?: string; kind?: string; label?: string; block?: string }>): void => {\n for (const entry of entries) {\n if (typeof entry.id === \"string\" && typeof entry.kind === \"string\") { positionof.set(entry.id, order); order += 1; continue; }\n if (typeof entry.block === \"string\") {\n const nested = model.blocks.find(candidate => candidate.name === entry.block);\n if (nested) walk(nested.steps as Array<{ id?: string; kind?: string; label?: string; block?: string }>);\n }\n }\n };\n walk((model.blocks.find(candidate => candidate.name === (node.invocation as { block: string }).block) as { steps: Array<{ id?: string; kind?: string; label?: string; block?: string }> }).steps);\n }\n for (const edge of model.edges) {\n if (!reachable.has(edge.from)) return { allowed: false, reason: `The canvas edge of ${edge.variable} references the unknown source step ${edge.from}.` };\n if (!reachable.has(edge.to)) return { allowed: false, reason: `The canvas edge of ${edge.variable} references the unknown target step ${edge.to}.` };\n if ((positionof.get(edge.from) ?? -1) >= (positionof.get(edge.to) ?? -1)) return { allowed: false, reason: `The canvas edge of ${edge.variable} runs backwards and would form a cycle.` };\n }\n return { allowed: true };\n}\n\n/** Refuses to run a workflow whose review state stays pending: an imported workflow or a version rollback stays unreviewed until the user approves its expanded step list through the import or rollback review. */\nexport function runreviewgranted(record: workflowrecord): policyevaluation {\n if (record.reviewstate === \"pending\") return { allowed: false, reason: \"The workflow stays unreviewed: the import or rollback review must approve its expanded step list before any run.\" };\n return { allowed: true };\n}\n\n/** The reviewed policy knobs a per site override may adjust: loop safety bounds, per step and per run timeout budgets, element wait timeouts and delay bases. */\nconst overrideknobs = [\"loopbound\", \"stepms\", \"runms\", \"waitms\", \"delaybase\"];\n\n/** Validates one per site policy override so overrides only adjust reviewed knobs: the pattern must be an https origin or a `*` subdomain glob of one and every delta must name a reviewed knob with a positive user value and no code ceiling. */\nexport function validatesiteoverride(override: { pattern: string; deltas: Record<string, number> }): policyevaluation {\n if (typeof override.pattern !== \"string\" || !override.pattern.startsWith(\"https://\") || !/[a-z0-9.-]+/i.test(override.pattern.slice(8))) return { allowed: false, reason: \"The override pattern must be an https origin or a `*` subdomain glob of one.\" };\n if (!override.pattern.includes(\"*\")) {\n try {\n if (new URL(override.pattern).origin !== override.pattern) return { allowed: false, reason: \"The override pattern must be a bare https origin or a `*` subdomain glob, never a path.\" };\n } catch {\n return { allowed: false, reason: \"The override pattern must parse as an https origin or a `*` subdomain glob of one.\" };\n }\n }\n for (const [knob, delta] of Object.entries(override.deltas)) {\n if (!overrideknobs.includes(knob)) return { allowed: false, reason: `The override knob ${knob} is not one of the reviewed knobs: ${overrideknobs.join(\", \")}.` };\n if (typeof delta !== \"number\" || !Number.isFinite(delta) || delta <= 0) return { allowed: false, reason: `The override delta of ${knob} must be a positive user value with no code ceiling.` };\n }\n return { allowed: true };\n}\n\n/** Validates the export contents of a workflow file so secrets never leave the browser: every step options object of the workflow and of every packed template is parsed and any field that names a secret, token, api key, password or authorization header refuses the export. */\nexport function exportcontentreview(file: { workflow: workflowrecord; templates: steptemplate[] }): policyevaluation {\n const secretkeys = /(secret|token|apikey|api_key|password|authorization|credential)/i;\n const scan = (label: string, options: string | undefined): policyevaluation | undefined => {\n if (options === undefined) return undefined;\n let payload: unknown;\n try { payload = JSON.parse(options); } catch { return undefined; }\n const walk = (value: unknown, path: string): policyevaluation | undefined => {\n if (!value || typeof value !== \"object\") return undefined;\n for (const [key, entry] of Object.entries(value as Record<string, unknown>)) {\n if (secretkeys.test(key)) return { allowed: false, reason: `The export of ${label} carries the secret field ${path}${key} and secrets never leave the browser.` };\n const nested = walk(entry, `${path}${key}.`);\n if (nested !== undefined) return nested;\n }\n return undefined;\n };\n return walk(payload, \"\");\n };\n for (const step of file.workflow.steps) {\n const refusal = scan(`the step ${step.id}`, step.options);\n if (refusal !== undefined) return refusal;\n }\n for (const template of file.templates) {\n const refusal = scan(`the template ${template.name}`, template.step.options);\n if (refusal !== undefined) return refusal;\n }\n return { allowed: true };\n}\n\n/** Validates one watchdog configuration: the stall threshold stays a positive user value with no code ceiling, the recovery action is one of retry, pause or cancel and the zombie window, when configured, stays positive with no ceiling. */\nexport function watchdogconfigvalid(config: watchdogconfig): policyevaluation {\n if (typeof config.enabled !== \"boolean\") return { allowed: false, reason: \"The watchdog enabled flag must be a boolean.\" };\n if (typeof config.stallthreshold !== \"number\" || !Number.isFinite(config.stallthreshold) || config.stallthreshold <= 0) return { allowed: false, reason: \"The watchdog stall threshold must be a positive number of milliseconds with no code ceiling.\" };\n if (![\"retry\", \"pause\", \"cancel\"].includes(config.action)) return { allowed: false, reason: \"The watchdog recovery action must be retry, pause or cancel.\" };\n if (config.zombiewindow !== undefined && (typeof config.zombiewindow !== \"number\" || !Number.isFinite(config.zombiewindow) || config.zombiewindow <= 0)) return { allowed: false, reason: \"The watchdog zombie window, when configured, must be a positive number of milliseconds with no code ceiling.\" };\n return { allowed: true };\n}\n\n/** Validates one mcp tool catalog against the action kind grammar: every tool name stays namespaced and unique, every wrapped kind belongs to the reviewed vocabulary, every namespace keeps its tools inside its domain kinds and every input schema carries typed properties with its required list. */\nexport function validatetoolcatalog(catalog: toolcatalog): policyevaluation {\n if (!Array.isArray(catalog.domains) || catalog.domains.length === 0) return { allowed: false, reason: \"The tool catalog needs its tool domains.\" };\n const seen = new Set<string>();\n for (const domain of catalog.domains) {\n if (!toolnamespaces.includes(domain.namespace)) return { allowed: false, reason: `The tool domain ${String(domain.namespace)} is not a reviewed namespace.` };\n if (!Array.isArray(domain.tools) || domain.tools.length === 0) return { allowed: false, reason: `The ${domain.namespace} domain exposes no tools.` };\n for (const tool of domain.tools) {\n if (typeof tool.name !== \"string\" || !tool.name.startsWith(`${domain.namespace}.`)) return { allowed: false, reason: `The tool ${String(tool.name)} does not carry its ${domain.namespace} namespace prefix.` };\n if (seen.has(tool.name)) return { allowed: false, reason: `The tool name ${tool.name} is not unique across the catalog.` };\n seen.add(tool.name);\n if (!allowedactions.has(tool.kind)) return { allowed: false, reason: `The tool ${tool.name} wraps ${String(tool.kind)} which is outside the reviewed action kind grammar.` };\n if (!domainkinds[domain.namespace].includes(tool.kind)) return { allowed: false, reason: `The tool ${tool.name} wraps ${String(tool.kind)} which does not belong to the ${domain.namespace} domain.` };\n if (typeof tool.description !== \"string\" || tool.description.trim() === \"\") return { allowed: false, reason: `The tool ${tool.name} needs its plain language description.` };\n const schema = tool.inputschema;\n if (!schema || schema.type !== \"object\" || schema.properties === undefined || schema.properties === null || typeof schema.properties !== \"object\" || Array.isArray(schema.properties) || Object.keys(schema.properties).length === 0) return { allowed: false, reason: `The tool ${tool.name} needs its json schema inputs of at least one typed property.` };\n for (const [name, property] of Object.entries(schema.properties)) {\n if (![\"string\", \"number\", \"boolean\", \"object\", \"array\"].includes(property.type)) return { allowed: false, reason: `The ${tool.name} input ${name} carries an untyped property.` };\n if (typeof property.description !== \"string\" || property.description.trim() === \"\") return { allowed: false, reason: `The ${tool.name} input ${name} needs its plain language description.` };\n }\n for (const name of schema.required) {\n if (!(name in schema.properties)) return { allowed: false, reason: `The tool ${tool.name} marks ${name} required outside its properties.` };\n }\n }\n }\n return { allowed: true };\n}\n\n/** Grades one tooldef with the risk class of its action kind and refuses a tool whose declared grade disagrees with the grammar. */\nexport function toolriskgrade(tool: tooldef): policyevaluation {\n const grade = actionrisk(tool.kind);\n if (grade !== tool.risk) return { allowed: false, reason: `The tool ${tool.name} declares the ${tool.risk} grade while its kind ${String(tool.kind)} grades ${grade}.` };\n return { allowed: true };\n}\n\n/** Requires consent metadata on every tool with side effects: read only tools stay free of the extra review while interaction and sensitive tools must declare their review requirement. */\nexport function toolconsentrequired(tool: tooldef): policyevaluation {\n if (tool.risk === \"read\") return { allowed: true };\n if (tool.consentmeta === undefined || typeof tool.consentmeta.review !== \"string\" || tool.consentmeta.review.trim() === \"\") return { allowed: false, reason: `The tool ${tool.name} has side effects and needs its consent metadata with the review requirement.` };\n return { allowed: true };\n}\n\n/** Grades one server bind configuration: the localhost bind stays the reviewed default while a bind outside localhost grades sensitive and needs the explicit remote review flag. */\nexport function serverbindgate(config: mcpserverconfig): policyevaluation {\n const bind = config.bind !== undefined && config.bind.trim() !== \"\" ? config.bind.trim() : \"127.0.0.1\";\n const local = bind === \"127.0.0.1\" || bind === \"localhost\" || bind === \"::1\";\n if (!local && config.remote !== true) return { allowed: false, reason: `The bind ${bind} leaves localhost and grades sensitive: the explicit remote review must approve it first.` };\n return { allowed: true };\n}\n\n/** Refuses one tool whose version stays below the negotiated compatibility floor so a client never receives a tool older than it can parse. */\nexport function toolversionfloor(tool: tooldef, floor: number): policyevaluation {\n if (typeof floor === \"number\" && Number.isFinite(floor) && tool.version < floor) return { allowed: false, reason: `The tool ${tool.name} of version ${tool.version} stays below the negotiated compatibility floor of ${floor}.` };\n return { allowed: true };\n}\n\n/** Requires the explicit user enablement before the mcp server ever starts; a disabled or unreviewed config never listens. */\nexport function serverenablementgate(config: mcpserverconfig): policyevaluation {\n if (config.enabled !== true) return { allowed: false, reason: \"The mcp server starts only after the user enables it; the protocol surface stays closed by default.\" };\n const bind = serverbindgate(config);\n if (!bind.allowed) return bind;\n if (!Array.isArray(config.transports) || config.transports.length === 0) return { allowed: false, reason: \"The mcp server needs at least one allowed transport of stdio or http.\" };\n if (!config.transports.every(transport => transport === \"stdio\" || transport === \"http\")) return { allowed: false, reason: \"The allowed transports of the mcp server are stdio and http.\" };\n if (typeof config.port !== \"number\" || !Number.isFinite(config.port) || config.port <= 0 || config.port > 65535) return { allowed: false, reason: \"The http listener port must be a valid port number.\" };\n if (config.framesize !== undefined && (typeof config.framesize !== \"number\" || !Number.isFinite(config.framesize) || config.framesize <= 0)) return { allowed: false, reason: \"The user configured frame size must stay a positive number with no code ceiling.\" };\n if (config.queuedepth !== undefined && (typeof config.queuedepth !== \"number\" || !Number.isFinite(config.queuedepth) || config.queuedepth <= 0)) return { allowed: false, reason: \"The user configured queue depth must stay a positive number with no code ceiling.\" };\n const remote = remoteenablementgate(config);\n if (!remote.allowed) return remote;\n return { allowed: true };\n}\n\n/** Validates the namespace membership of one tool: the name prefix must name the domain the tool lives in and the wrapped kind must belong to that domain so no tool drifts out of its namespace. */\nexport function toolnamespacegate(tool: tooldef): policyevaluation {\n const namespace = tool.name.split(\".\")[0];\n if (!toolnamespaces.includes(namespace as never)) return { allowed: false, reason: `The tool ${tool.name} carries no reviewed namespace prefix.` };\n if (!domainkinds[namespace as keyof typeof domainkinds].includes(tool.kind)) return { allowed: false, reason: `The tool ${tool.name} wraps ${String(tool.kind)} which does not belong to the ${namespace} domain.` };\n return { allowed: true };\n}\n\n/** The mcp tool dispatch gate: the client must be paired, the session live, the plan approved and the origin inside the session grants; read only tools pass under the dryrun risk class without extra approval while every tool with side effects must name the approved plan step of its own kind it executes. The full canexecute gates re-run at execution time. */\nexport function tooldispatchgate(input: { client: clientrecord; tool: tooldef; session: agentsession | undefined; plan: agentplan | undefined; origin: string; stepid?: string; now: number }): policyevaluation {\n if (input.client.disconnectedat !== undefined) return { allowed: false, reason: \"The mcp client is disconnected and its tool calls are refused.\" };\n if (!input.client.paired) return { allowed: false, reason: \"The mcp client waits for the user pairing approval; unpaired clients never dispatch tools.\" };\n if (!input.session || input.session.stoppedat || input.session.pausedat) return { allowed: false, reason: \"Tool dispatch needs the live browser session behind the consent gates.\" };\n if (input.session.expiresat <= input.now) return { allowed: false, reason: \"The browser session has expired and tool dispatch is refused.\" };\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"Tool dispatch needs the approved plan review before any tool runs.\" };\n if (!origingranted(input.session, input.origin)) return { allowed: false, reason: `The tool call origin ${input.origin} stays outside the session grants and is refused.` };\n if (input.tool.risk === \"read\") return { allowed: true };\n if (input.stepid === undefined || input.stepid.trim() === \"\") return { allowed: false, reason: `The ${input.tool.name} tool has side effects and needs the id of the approved plan step it executes.` };\n const step = input.plan.steps.find(candidate => candidate.id === input.stepid);\n if (step === undefined) return { allowed: false, reason: `The tool call names the step ${input.stepid} which the approved plan does not carry.` };\n if (step.kind !== input.tool.kind) return { allowed: false, reason: `The tool call names the step ${input.stepid} whose kind ${String(step.kind)} does not match the ${input.tool.name} tool.` };\n return { allowed: true };\n}\n\n/** Grades the consent metadata of every sensitive tool: the risk class must match the policy grading of the wrapped kind, the approval gate requirement must be explicit and the origin scope must stay the session grants. */\nexport function consentmetagrade(tool: tooldef): policyevaluation {\n if (tool.risk === \"read\") return { allowed: true };\n if (tool.consentmeta === undefined) return { allowed: false, reason: `The tool ${tool.name} has side effects and needs its consent metadata.` };\n if (tool.consentmeta.riskclass !== actionrisk(tool.kind)) return { allowed: false, reason: `The consent metadata of ${tool.name} declares the ${String(tool.consentmeta.riskclass)} risk class while policy grades its kind ${String(tool.kind)} as ${actionrisk(tool.kind)}.` };\n if (tool.consentmeta.approvalrequired !== true) return { allowed: false, reason: `The tool ${tool.name} has side effects and its consent metadata must require the explicit approval gate.` };\n if (tool.consentmeta.originscope !== \"session\") return { allowed: false, reason: `The tool ${tool.name} must scope its calls to the session grants.` };\n return { allowed: true };\n}\n\n/** Validates one allowlist entry against the known client identities: the fingerprint must belong to a stored identity, the display name must be non empty and every granted namespace must be a reviewed namespace. */\nexport function allowlistentryvalid(entry: allowlistentry, identities: clientidentity[]): policyevaluation {\n if (typeof entry.fingerprint !== \"string\" || entry.fingerprint.trim() === \"\") return { allowed: false, reason: \"The allowlist entry needs the client fingerprint it grants.\" };\n if (!identities.some(identity => identity.fingerprint === entry.fingerprint)) return { allowed: false, reason: `The allowlist entry ${entry.fingerprint} matches no known client identity.` };\n if (typeof entry.displayname !== \"string\" || entry.displayname.trim() === \"\") return { allowed: false, reason: `The allowlist entry ${entry.fingerprint} needs its display name.` };\n if (!Array.isArray(entry.namespaces) || entry.namespaces.length === 0) return { allowed: false, reason: `The allowlist entry ${entry.displayname} grants no tool namespace.` };\n if (!entry.namespaces.every(namespace => toolnamespaces.includes(namespace))) return { allowed: false, reason: `The allowlist entry ${entry.displayname} grants an unreviewed namespace.` };\n return { allowed: true };\n}\n\n/** Validates one session token lifetime as a user configured value: an absent lifetime keeps the documented default while a configured window must stay positive with no code ceiling. */\nexport function tokenlifetimevalid(lifetime: number | undefined): policyevaluation {\n if (lifetime === undefined) return { allowed: true };\n if (typeof lifetime !== \"number\" || !Number.isFinite(lifetime) || lifetime <= 0) return { allowed: false, reason: \"The token lifetime must stay a positive user value with no code ceiling.\" };\n return { allowed: true };\n}\n\n/** Requires tls for any non localhost transport: a configured remote access policy or a bind outside localhost must carry the on or required tls mode before any remote traffic passes. */\nexport function remotetransporttls(config: mcpserverconfig): policyevaluation {\n const bind = config.bind !== undefined && config.bind.trim() !== \"\" ? config.bind.trim() : \"127.0.0.1\";\n const local = bind === \"127.0.0.1\" || bind === \"localhost\" || bind === \"::1\";\n const tls = config.remoteaccess?.tls ?? config.httpstream?.tls;\n if ((config.remoteaccess !== undefined || !local) && (tls === undefined || tls.mode === \"off\")) return { allowed: false, reason: `The ${config.remoteaccess !== undefined ? \"remote transport\" : `bind ${bind}`} leaves localhost and every non localhost transport requires tls before any remote traffic.` };\n return { allowed: true };\n}\n\n/** Refuses the pairing flow when no session is active: pairing codes issue only while the live browser session exists, so no remote client pairs against a closed surface. */\nexport function pairingreadinessgate(session: agentsession | undefined, now: number): policyevaluation {\n if (!session || session.stoppedat || session.pausedat) return { allowed: false, reason: \"The pairing flow needs the live browser session before any code issues.\" };\n if (session.expiresat <= now) return { allowed: false, reason: \"The browser session has expired and the pairing flow is refused.\" };\n return { allowed: true };\n}\n\n/** Grades the remote transport enablement as a sensitive user choice: a configured remote access policy requires the explicit remote review and tls before the remote surface opens. */\nexport function remoteenablementgate(config: mcpserverconfig): policyevaluation {\n if (config.remoteaccess === undefined) return { allowed: true };\n if (config.remote !== true) return { allowed: false, reason: \"The remote transport enablement is a sensitive user choice and needs the explicit remote review.\" };\n const tls = remotetransporttls(config);\n if (!tls.allowed) return tls;\n if (typeof config.remoteaccess.endpoint !== \"string\" || config.remoteaccess.endpoint.trim() === \"\") return { allowed: false, reason: \"The remote access policy needs its user configured endpoint.\" };\n if (config.remoteaccess.maxclients !== undefined && (typeof config.remoteaccess.maxclients !== \"number\" || !Number.isFinite(config.remoteaccess.maxclients) || config.remoteaccess.maxclients <= 0)) return { allowed: false, reason: \"The user configured client ceiling must stay a positive value with no code ceiling.\" };\n const lifetime = tokenlifetimevalid(config.remoteaccess.tokenlifetimems);\n if (!lifetime.allowed) return lifetime;\n const timeout = approvaltimeoutvalid(config.remoteaccess.approvaltimeout);\n if (!timeout.allowed) return timeout;\n return { allowed: true };\n}\n\n/** Limits the token scopes to the namespaces the user granted: every scope must be a reviewed namespace the grant list carries, so a token never widens beyond the allowlist. */\nexport function tokenscopevalid(scopes: toolnamespace[], granted: toolnamespace[]): policyevaluation {\n if (!Array.isArray(scopes) || scopes.length === 0) return { allowed: false, reason: \"A session token needs at least one granted tool namespace.\" };\n for (const scope of scopes) {\n if (!toolnamespaces.includes(scope)) return { allowed: false, reason: `The scope ${String(scope)} is not a reviewed tool namespace.` };\n if (!granted.includes(scope)) return { allowed: false, reason: `The scope ${scope} stays outside the namespaces the user granted.` };\n }\n return { allowed: true };\n}\n\n/** Validates one approval timeout as a user configured positive window with the documented refusal default; an absent timeout keeps the documented default. */\nexport function approvaltimeoutvalid(timeout: approvaltimeout | undefined): policyevaluation {\n if (timeout === undefined) return { allowed: true };\n if (typeof timeout.windowms !== \"number\" || !Number.isFinite(timeout.windowms) || timeout.windowms <= 0) return { allowed: false, reason: \"The approval timeout must stay a positive user window with no code ceiling.\" };\n if (timeout.ontimeout !== \"refuse\") return { allowed: false, reason: \"The documented disposition of an unanswered approval gate is refusal.\" };\n return { allowed: true };\n}\n\n/** Grades the token revocation as an always available user action: no gate, review or state ever blocks the user from revoking a paired client. */\nexport function revocationgate(): policyevaluation {\n return { allowed: true };\n}\n\n/** Grades one client event subscription as read only when its filters exclude the mutation mirror: a subscription that listens to the callstarted kind must narrow itself with an origin or tool filter so it never streams the side effect calls of unreviewed origins. */\nexport function subscriptiongrade(subscription: protocoleventsubscription): policyevaluation {\n if (!Array.isArray(subscription.kinds) || subscription.kinds.length === 0) return { allowed: false, reason: \"An event subscription needs at least one protocol event kind.\" };\n if (subscription.kinds.includes(\"callstarted\") && subscription.origin === undefined && subscription.tool === undefined) return { allowed: false, reason: \"An event subscription that mirrors the callstarted events of tools with side effects needs its origin or tool filter so it never widens what the session grants.\" };\n return { allowed: true };\n}\n\n/** Grades sampling callbacks as sensitive: the prompt leaves the browser, so page content rides a callback only behind the explicit user grant and the granted maximum tokens stay a positive user value. */\nexport function samplinggrade(input: { request: samplingrequest; pagegrant: boolean }): policyevaluation {\n if (!input.pagegrant && input.request.pagecontent !== undefined) return { allowed: false, reason: \"The sampling callback carries page content the user never granted and is refused.\" };\n if (input.request.maxtokens !== undefined && (!Number.isFinite(input.request.maxtokens) || input.request.maxtokens <= 0)) return { allowed: false, reason: \"The granted maximum tokens of a sampling callback must stay a positive user value with no code ceiling.\" };\n if (input.request.prompt.trim() === \"\") return { allowed: false, reason: \"A sampling callback needs its prompt.\" };\n return { allowed: true };\n}\n\n/** Validates one per client rate limit as a user configured value: the window and budget stay positive when set while an absent limit or budget documents the unbounded choice instead of a silent default. */\nexport function callratelimitvalid(limit: callratelimit | undefined): policyevaluation {\n if (limit === undefined) return { allowed: true };\n if (typeof limit.windowms !== \"number\" || !Number.isFinite(limit.windowms) || limit.windowms <= 0) return { allowed: false, reason: \"The rate limit window must stay a positive user value with no code ceiling.\" };\n if (limit.budget !== undefined && (typeof limit.budget !== \"number\" || !Number.isFinite(limit.budget) || limit.budget <= 0)) return { allowed: false, reason: \"The rate limit budget must stay a positive user value with no code ceiling.\" };\n if (limit.clientid.trim() === \"\") return { allowed: false, reason: \"A per client rate limit needs the client it counts.\" };\n return { allowed: true };\n}\n\n/** Requires one audit entry for every tool call without exception: every call record of the runtime must appear in the audit set so no call ever leaves the trail. */\nexport function callauditcomplete(input: { calls: toolcallrecord[]; audit: toolcallrecord[] }): policyevaluation {\n const audited = new Set(input.audit.map(record => record.id));\n const missing = input.calls.filter(record => !audited.has(record.id));\n if (missing.length > 0) return { allowed: false, reason: `${missing.length} tool call${missing.length === 1 ? \"\" : \"s\"} carry no audit entry and the audit trail must name every call without exception.` };\n return { allowed: true };\n}\n\n/** Grades one batch call by its most sensitive member: a batch that carries a sensitive member takes the sensitive grade and runs only behind the approval gates while a read only batch stays read. */\nexport function batchgrade(input: { calls: Array<{ risk: tooldef[\"risk\"] }>; approved: boolean }): policyevaluation {\n if (input.calls.length === 0) return { allowed: false, reason: \"A batch call needs at least one ordered tool call.\" };\n const sensitive = input.calls.some(call => call.risk === \"sensitive\");\n if (sensitive && !input.approved) return { allowed: false, reason: \"The batch grades sensitive through its most sensitive member and runs only behind the approval gates.\" };\n return { allowed: true };\n}\n\n/** Keeps one tool dry run free of page mutations: a dry run record that claims execution or lists page mutations is refused because a dry run evaluates arguments and consent and never executes anything. */\nexport function dryrunpurity(dryrun: tooldryrun): policyevaluation {\n if (dryrun.executed) return { allowed: false, reason: `The ${dryrun.tool} dry run claims execution and a dry run never executes anything.` };\n if (dryrun.mutations.length > 0) return { allowed: false, reason: `The ${dryrun.tool} dry run lists ${dryrun.mutations.length} page mutation${dryrun.mutations.length === 1 ? \"\" : \"s\"} and a dry run leaves the page untouched.` };\n return { allowed: true };\n}\n\n/** Validates tool mock usage to test contexts only: a mock outside a test context is refused while a test context mock must name a tool and carry a canned result. */\nexport function mockusagevalid(mock: toolmock): policyevaluation {\n if (mock.testcontext !== true) return { allowed: false, reason: `The ${mock.tool} mock stays outside a test context and is refused; tool mocks never answer real calls.` };\n if (mock.tool.trim() === \"\") return { allowed: false, reason: \"A tool mock needs the namespaced tool it stands in for.\" };\n if (typeof mock.result.content !== \"string\") return { allowed: false, reason: \"A tool mock needs its canned result content.\" };\n return { allowed: true };\n}\n\n/**\n * Llm integration gates of the 1.1.57 family.\n * Every model side gate lives here: the provider validation that keeps every endpoint, model and protocol shape a user configured value, the data egress grading of provider calls, the explicit consent requirement before page content leaves the browser, the local endpoint preference for sensitive extractions, the review requirement of model drafted plans, the fresh review requirement of replanned steps, the cost budget validation, the guard verdict gate that refuses invalid model output and the plan lint that checks model drafts against the action grammar before review.\n * No provider, endpoint, model, key or ceiling is ever hardcoded: the gates validate user choices and refuse everything else.\n */\n\n/** Validates one provider config: the endpoint stays a user configured http or https url, the model list stays non-empty free text, the protocol shape stays one of the four wire shapes and the auth reference stays a storage id reference that never carries key material. */\nexport function providervalid(config: providerconfig): policyevaluation {\n if (config.name.trim() === \"\") return { allowed: false, reason: \"The provider config needs its name.\" };\n if (config.endpoint.trim() === \"\") return { allowed: false, reason: \"The provider config needs the user configured endpoint url; no default endpoint ever applies.\" };\n let parsed: URL;\n try { parsed = new URL(config.endpoint); } catch { return { allowed: false, reason: \"The provider endpoint must be a well-formed url.\" }; }\n if (parsed.protocol !== \"https:\" && parsed.protocol !== \"http:\") return { allowed: false, reason: \"The provider endpoint must speak http or https.\" };\n if (config.models.length === 0) return { allowed: false, reason: \"The provider config needs at least one user configured model name.\" };\n if (config.models.some(model => model.trim() === \"\")) return { allowed: false, reason: \"Every provider model name must stay non-empty free text.\" };\n if (config.style !== \"chatcompletions\" && config.style !== \"responses\" && config.style !== \"messages\" && config.style !== \"gemini\") return { allowed: false, reason: \"The provider protocol shape must be one of the four wire shapes the user picks.\" };\n if (config.authref !== undefined && config.authref.storageid.trim() === \"\") return { allowed: false, reason: \"The provider auth reference needs the storage id of the stored key; the key material never enters the config.\" };\n return { allowed: true };\n}\n\n/** Grades one provider call as a data egress event for the audit trail: every remote completion leaves the browser with its prompt text, so the audit names the endpoint, the model and the token counts; a local endpoint grades as the local preference. */\nexport function provideregressgrade(input: { provider: providerconfig; local: boolean }): policyevaluation {\n const valid = providervalid(input.provider);\n if (!valid.allowed) return valid;\n return { allowed: true, reason: input.local ? \"The model call stays on the local machine endpoint and grades as the local data egress preference.\" : \"The model call leaves the browser for the user configured endpoint and grades as a data egress event with its endpoint, model and token counts in the audit trail.\" };\n}\n\n/** Requires explicit consent before any page content leaves the browser: page content inside a call the user has not granted refuses the call, while calls without page content pass untouched. */\nexport function egressconsentgate(input: { pagecontent?: string; granted: boolean }): policyevaluation {\n if (input.pagecontent !== undefined && input.pagecontent.trim() !== \"\" && input.granted !== true) return { allowed: false, reason: \"The model call carries page content the user has not granted, so the content stays in the browser and the call refuses.\" };\n return { allowed: true };\n}\n\n/** Grades the local endpoint preference for sensitive extractions: a sensitive extraction prefers the local model endpoint, and the grade names the preference while a local endpoint satisfies it. */\nexport function localsensitivegrade(input: { sensitive: boolean; local: boolean }): policyevaluation {\n if (input.sensitive && !input.local) return { allowed: true, reason: \"The sensitive extraction prefers the local model endpoint; the user keeps the choice of the remote provider.\" };\n return { allowed: true, reason: input.local ? \"The local model endpoint satisfies the sensitive extraction preference.\" : \"The extraction stays non-sensitive and every configured endpoint serves it.\" };\n}\n\n/** Requires the plan review before any model drafted plan executes: only an approved draft may turn into a plan, and the plan itself still passes the same human plan review every local plan passes. */\nexport function plandraftreviewgate(draft: plandraft): policyevaluation {\n if (draft.state !== \"approved\") return { allowed: false, reason: \"The model drafted plan stays unreviewed; the human review approves the draft before any step executes.\" };\n if (draft.steps.length === 0) return { allowed: false, reason: \"The model drafted plan carries no step, so nothing executes.\" };\n return { allowed: true };\n}\n\n/** Requires fresh review for replanned steps: a pending replan never executes and every revised tail step carries the fresh review marker, so the human review sees the changed tail before it runs. */\nexport function replanreviewgate(replan: replanrecord): policyevaluation {\n if (replan.state !== \"approved\") return { allowed: false, reason: \"The replanned tail stays unreviewed; the fresh review approves the changed steps before any of them executes.\" };\n if (replan.tail.some(step => step.freshreview !== true)) return { allowed: false, reason: \"Every revised step of a replan must carry the fresh review marker.\" };\n return { allowed: true };\n}\n\n/** Validates one cost budget: the token and currency ceilings stay positive user values with no code ceiling, the currency names the unit of the cost ceiling and a budget without any ceiling documents the unbounded choice instead of inventing one. */\nexport function costbudgetvalid(budget: costbudget): policyevaluation {\n if (budget.maxtokens !== undefined && (!Number.isFinite(budget.maxtokens) || budget.maxtokens <= 0)) return { allowed: false, reason: \"The token ceiling of a cost budget must stay a positive user value.\" };\n if (budget.maxcost !== undefined && (!Number.isFinite(budget.maxcost) || budget.maxcost <= 0)) return { allowed: false, reason: \"The cost ceiling of a cost budget must stay a positive user value.\" };\n if (budget.maxcost !== undefined && (budget.currency === undefined || budget.currency.trim() === \"\")) return { allowed: false, reason: \"The cost ceiling of a cost budget needs its currency unit.\" };\n if (budget.maxtokens === undefined && budget.maxcost === undefined) return { allowed: false, reason: \"The cost budget needs at least one ceiling the user configured; an absent budget stays the documented unbounded choice.\" };\n return { allowed: true };\n}\n\n/** Refuses tool calls the guardrails marked invalid: only a valid guard verdict passes, an invalid or refused model output never executes. */\nexport function guardverdictgate(output: modeloutput): policyevaluation {\n if (output.verdict === \"invalid\") return { allowed: false, reason: output.reason ?? \"The guardrails marked the model output invalid.\" };\n if (output.verdict === \"refused\") return { allowed: false, reason: output.reason ?? \"The model refused the request, so nothing executes.\" };\n return { allowed: true };\n}\n\n/** Lints one model drafted plan against the action grammar before review: every drafted step maps onto the tool step grammar with its derived risk, and every violation lands in the findings the review sees; an empty origin skips the origin bound checks the way the review pipeline runs them later. */\n/** Derives the risk of one drafted step for the grammar check; an unknown kind grades sensitive so the grammar check names the violation instead of crashing. */\nfunction draftriskof(step: draftstep): \"read\" | \"interaction\" | \"sensitive\" {\n try { return resolvedrisk({ id: step.id, kind: step.kind as actionkind, ...(step.target !== undefined ? { target: step.target } : {}), ...(step.value !== undefined ? { value: step.value } : {}), summary: step.summary, risk: \"sensitive\" }); } catch { return \"sensitive\"; }\n}\n\nexport function planlint(draft: plandraft, origin: string): string[] {\n const findings: string[] = [];\n if (draft.goal.trim() === \"\") findings.push(\"The drafted plan carries no goal.\");\n if (draft.steps.length === 0) findings.push(\"The drafted plan carries no step.\");\n for (const step of draft.steps) {\n const mapped: toolstep = { id: step.id, kind: step.kind as actionkind, ...(step.target !== undefined ? { target: step.target } : {}), ...(step.value !== undefined ? { value: step.value } : {}), summary: step.summary, risk: draftriskof(step) } as toolstep;\n const verdict = validatestep(mapped, origin);\n if (!verdict.allowed) findings.push(`The drafted step ${step.id || \"without id\"} of kind ${step.kind || \"unknown\"} violates the action grammar: ${verdict.reason ?? \"the step failed its grammar check.\"}`);\n }\n return findings;\n}\n\n/**\n * Multi agent part one gates of the 1.1.58 family.\n * Every swarm side gate lives here: the task queue validation of the user configured lanes, priorities and completion policy, the work stealing grade that permits stealing only inside one user approved swarm, the per agent budget validation of positive user ceilings, the per agent scope validation against the session grant list, the spawn grading with the risk class of the requested role, the killswitch gate that stays available with no configuration barrier, the egress grading of cross agent messages that carry page content and the blackboard consent grade that inherits the class of the source extraction.\n * No agent count, lane name, priority scale, depth ceiling or freshness window is ever hardcoded: the gates validate user choices and refuse everything else, and no swarm coordination ever bypasses the human review.\n */\n\n/** Validates one task queue: the lanes stay non-empty unique user names, the priorities stay finite user values, the completion policy stays all or any, and every item waits in a configured lane under a configured priority when the user configured the scales. */\nexport function queuelanesvalid(queue: taskqueue): policyevaluation {\n if (queue.lanes.some(lane => lane.trim() === \"\")) return { allowed: false, reason: \"Every queue lane needs its user configured name.\" };\n if (new Set(queue.lanes).size !== queue.lanes.length) return { allowed: false, reason: \"The queue lane names must stay unique.\" };\n if (queue.priorities.some(priority => !Number.isFinite(priority))) return { allowed: false, reason: \"Every queue priority must stay a finite user value.\" };\n if (queue.completionpolicy !== \"all\" && queue.completionpolicy !== \"any\") return { allowed: false, reason: \"The queue completion policy must stay all or any.\" };\n for (const item of queue.items) {\n if (item.payload.trim() === \"\") return { allowed: false, reason: `The task ${item.id} carries no payload.` };\n if (queue.lanes.length > 0 && !queue.lanes.includes(item.lane)) return { allowed: false, reason: `The task ${item.id} waits in the lane ${item.lane} which the user did not configure.` };\n if (queue.priorities.length > 0 && !queue.priorities.includes(item.priority)) return { allowed: false, reason: `The task ${item.id} carries the priority ${item.priority} which the user did not configure.` };\n }\n return { allowed: true };\n}\n\n/** Grades one work stealing attempt: stealing is permitted only inside one user approved swarm, and a steal outside an approved swarm refuses because lane ownership only exists inside the reviewed swarm. */\nexport function workstealgrade(input: { swarmapproved: boolean; agentrole: string; lane: string; ownership?: Array<{ lane: string; roles: string[] }> }): policyevaluation {\n if (!input.swarmapproved) return { allowed: false, reason: \"Work stealing runs only inside one user approved swarm; an unapproved swarm keeps every lane closed.\" };\n const rule = (input.ownership ?? []).find(entry => entry.lane === input.lane);\n if (rule && !rule.roles.includes(input.agentrole)) return { allowed: false, reason: `The lane ${input.lane} only opens its tasks to the roles ${rule.roles.join(\", \")} the user configured; the ${input.agentrole} agent may not steal.` };\n return { allowed: true, reason: rule === undefined ? `The lane ${input.lane} carries no ownership rule, so every agent of the approved swarm may steal its tasks.` : `The lane ${input.lane} opens its tasks to the ${input.agentrole} role the user configured.` };\n}\n\n/** Validates one per agent budget: the token, cost and step ceilings stay positive user values with no code ceiling, the cost ceiling names its currency and a budget without any ceiling documents the unbounded choice instead of inventing one. */\nexport function agentbudgetvalid(budget: agentbudget): policyevaluation {\n if (budget.agentid.trim() === \"\") return { allowed: false, reason: \"The agent budget needs its agent id.\" };\n if (budget.maxtokens !== undefined && (!Number.isFinite(budget.maxtokens) || budget.maxtokens <= 0)) return { allowed: false, reason: \"The token ceiling of an agent budget must stay a positive user value.\" };\n if (budget.maxcost !== undefined && (!Number.isFinite(budget.maxcost) || budget.maxcost <= 0)) return { allowed: false, reason: \"The cost ceiling of an agent budget must stay a positive user value.\" };\n if (budget.maxsteps !== undefined && (!Number.isFinite(budget.maxsteps) || budget.maxsteps <= 0)) return { allowed: false, reason: \"The step ceiling of an agent budget must stay a positive user value.\" };\n if (budget.maxcost !== undefined && (budget.currency === undefined || budget.currency.trim() === \"\")) return { allowed: false, reason: \"The cost ceiling of an agent budget needs its currency unit.\" };\n if (budget.maxtokens === undefined && budget.maxcost === undefined && budget.maxsteps === undefined) return { allowed: false, reason: \"The agent budget needs at least one ceiling the user configured; an absent budget stays the documented unbounded choice.\" };\n return { allowed: true };\n}\n\n/** Validates one per agent scope against the session grant list: every granted origin must sit inside the session grants and every granted tool namespace must be one of the catalog namespaces, so no agent scope ever widens the session. */\nexport function agentscopevalid(input: { scope: agentscope; grants: string[] }): policyevaluation {\n if (input.scope.agentid.trim() === \"\") return { allowed: false, reason: \"The agent scope needs its agent id.\" };\n if (input.scope.origins.some(origin => origin.trim() === \"\")) return { allowed: false, reason: \"Every origin of an agent scope needs its user configured name.\" };\n if (input.scope.toolnamespaces.some(namespace => !toolnamespaces.includes(namespace))) return { allowed: false, reason: \"Every tool namespace of an agent scope must be one of the catalog namespaces.\" };\n const outside = input.scope.origins.filter(origin => input.grants.length > 0 && !input.grants.includes(origin));\n if (outside.length > 0) return { allowed: false, reason: `The agent scope grants the origins ${outside.join(\", \")} which the session grant list does not carry; no agent scope widens the session.` };\n return { allowed: true };\n}\n\n/** Grades one spawn request with the risk class of the requested role: a planner or observer spawn grades read side while a worker or custom role spawn grades sensitive because it may execute reviewed steps, and the grade names the class the review sees. */\nexport function spawngrade(request: spawnrequest): policyevaluation {\n if (request.parentid.trim() === \"\") return { allowed: false, reason: \"The spawn request needs its parent agent id.\" };\n if (request.task.trim() === \"\") return { allowed: false, reason: \"The spawn request needs its task in plain language.\" };\n if (request.depth < 1) return { allowed: false, reason: \"The spawn request depth must sit at one or deeper because every sub agent lives under a parent.\" };\n const risk = request.role === \"planner\" || request.role === \"observer\" ? \"read\" : \"sensitive\";\n return { allowed: true, reason: risk === \"read\" ? `The spawn of the ${request.role} sub agent grades read side: the role composes or observes and never executes page actions.` : `The spawn of the ${request.role} sub agent grades sensitive: the role may execute reviewed steps, so every proposal it drafts still passes the same human review.` };\n}\n\n/** Keeps the killswitch available with no configuration barrier: the switch never needs a setting, an approval or a state to fire, so the user halts every agent at once at any time. */\nexport function killswitchgate(): policyevaluation {\n return { allowed: true, reason: \"The killswitch stays available with no configuration barrier: the user halts every agent of the swarm at once at any time.\" };\n}\n\n/** Grades one cross agent message that carries page content as a data egress event: the delivery stays inside the swarm while the copied page content lands its egress class in the audit trail so the reviewer reads what moved between agents. */\nexport function messageegressgrade(input: { message: agentmessage; carriespagecontent: boolean }): policyevaluation {\n if (input.message.payload.trim() === \"\") return { allowed: false, reason: \"The agent message needs its payload.\" };\n if (input.carriespagecontent) return { allowed: true, reason: `The message ${input.message.id} from ${input.message.senderid} to ${input.message.recipient} carries page content and grades as a data egress event with its sender, recipient and routing in the audit trail.` };\n return { allowed: true, reason: `The message ${input.message.id} from ${input.message.senderid} to ${input.message.recipient} carries no page content and stays a plain swarm delivery.` };\n}\n\n/** Grades one blackboard entry by the consent class of its source extraction: the entry inherits the class exactly, a sensitive extraction stays sensitive on the board and every reader sees the class beside the value. */\nexport function blackboardconsentgrade(entry: blackboardentry): policyevaluation {\n if (entry.key.trim() === \"\") return { allowed: false, reason: \"The blackboard entry needs its key.\" };\n if (entry.consentclass !== \"read\" && entry.consentclass !== \"interaction\" && entry.consentclass !== \"sensitive\") return { allowed: false, reason: \"The blackboard entry inherits one of the three consent classes of its source extraction.\" };\n if (entry.consentclass === \"sensitive\") return { allowed: true, reason: `The blackboard entry ${entry.key} inherits the sensitive class of its source extraction; every agent reads the class beside the value.` };\n return { allowed: true, reason: `The blackboard entry ${entry.key} inherits the ${entry.consentclass} class of its source extraction; every agent reads the class beside the value.` };\n}\n\n/**\n * Multi agent part two gates of the 1.1.59 family.\n * Every orchestration side gate lives here: the leader election validation of the user configured rule, the critic review grade that stays read only over agent outputs, the verifier method grade against the methods the user allows, the handoff grant gate that preserves the original session grants, the lock scope validation that keeps one lock inside one origin, the conflict resolution grade that marks overwriting rules sensitive, the escalation gate that keeps every lifted decision human, the consensus quorum validation of the user configured value, the worker scale bound validation with no engine cap and the merge egress grade of exported reports that include page content.\n * No quorum, worker bound, election rule or merge rule is ever hardcoded: the gates validate user choices and refuse everything else, and no coordination path bypasses the human review.\n */\n\n/** Validates one leader election rule as the user configured it: first takes the first registration while named takes one agent id the user typed, and a named rule without its agent or an unknown rule kind refuses. */\nexport function leaderelectionvalid(input: { rule: { kind: string; agentid?: string }; agents: agentidentity[] }): policyevaluation {\n if (input.rule.kind !== \"first\" && input.rule.kind !== \"named\") return { allowed: false, reason: \"The leader election rule stays first or named as the user configured it.\" };\n if (input.rule.kind === \"named\") {\n if (input.rule.agentid === undefined || input.rule.agentid.trim() === \"\") return { allowed: false, reason: \"The named leader election rule needs the agent id the user named.\" };\n if (!input.agents.some(agent => agent.id === input.rule.agentid && agent.state !== \"stopped\")) return { allowed: false, reason: `The named leader election rule names the agent ${input.rule.agentid} which is not a live agent of the swarm.` };\n }\n return { allowed: true, reason: input.rule.kind === \"first\" ? \"The first registration rule elects the leader exactly as the user configured.\" : `The named rule elects the agent ${input.rule.agentid} exactly as the user configured.` };\n}\n\n/** Grades one critic review as read only over agent outputs: the critic reads the output of the subject agent and returns its verdict, its issues and its required changes without ever acting on the page, and a review without its reviewer, subject or verdict refuses. */\nexport function criticreviewgrade(review: criticreview): policyevaluation {\n if (review.reviewerid.trim() === \"\") return { allowed: false, reason: \"The critic review needs its reviewing agent.\" };\n if (review.subjectagentid.trim() === \"\") return { allowed: false, reason: \"The critic review needs the subject agent whose output it reviews.\" };\n if (review.verdict !== \"approve\" && review.verdict !== \"changes\" && review.verdict !== \"reject\") return { allowed: false, reason: \"The critic review carries one of the three verdicts approve, changes or reject.\" };\n if (review.verdict === \"changes\" && review.requiredchanges.length === 0) return { allowed: false, reason: \"A changes verdict needs its required changes in plain language.\" };\n if (review.verdict === \"reject\" && review.issues.length === 0) return { allowed: false, reason: \"A reject verdict needs the issues the critic found.\" };\n return { allowed: true, reason: `The critic review of the output of ${review.subjectagentid} stays read only: the critic ${review.reviewerid} returns its ${review.verdict} verdict and never acts on the page; the rework still passes the same human review.` };\n}\n\n/** Grades one verifier check by the methods the user allows: an empty allowed list keeps every method open as the documented user choice while a configured list restricts the verifier to the methods it names. */\nexport function verifiermethodgrade(input: { method: string; allowed: string[] }): policyevaluation {\n if (input.method.trim() === \"\") return { allowed: false, reason: \"The verifier check needs the method it used.\" };\n if (input.allowed.length > 0 && !input.allowed.includes(input.method)) return { allowed: false, reason: `The verifier method ${input.method} is not one of the methods the user allowed: ${input.allowed.join(\", \")}.` };\n return { allowed: true, reason: input.allowed.length === 0 ? `The verifier method ${input.method} runs under the documented open method list the user chose not to narrow.` : `The verifier method ${input.method} sits inside the methods the user allowed.` };\n}\n\n/** Requires one handoff to preserve the original session grants: the receiving agent scope stays inside the session grant list exactly like every agent scope, so a tab transfer never widens what the session granted. */\nexport function handoffgrantgate(input: { record: handoffrecord; toscope: agentscope | undefined; sessiongrants: string[] }): policyevaluation {\n if (input.record.toagentid.trim() === \"\" || input.record.fromagentid.trim() === \"\") return { allowed: false, reason: \"The handoff names its transferring and receiving agents.\" };\n if (input.toscope === undefined) return { allowed: true, reason: `The receiving agent ${input.record.toagentid} carries no narrowed scope, so the handoff stays unbounded inside the original session grants.` };\n const outside = input.toscope.origins.filter(origin => input.sessiongrants.length > 0 && !input.sessiongrants.includes(origin));\n if (outside.length > 0) return { allowed: false, reason: `The handoff to ${input.record.toagentid} would need the origins ${outside.join(\", \")} which the session grant list does not carry; a tab transfer never widens the session grants.` };\n return { allowed: true, reason: `The handoff from ${input.record.fromagentid} to ${input.record.toagentid} preserves the original session grants; the receiving scope stays inside them.` };\n}\n\n/** Validates one lock scope so a lock never spans unrelated origins: the key composes of exactly one origin and one selector, both non-empty, and the kind stays exclusive or shared. */\nexport function lockscopevalid(lock: resourcelock): policyevaluation {\n if (lock.key.trim() === \"\") return { allowed: false, reason: \"The resource lock needs its key.\" };\n if (lock.origin.trim() === \"\" || lock.selector.trim() === \"\") return { allowed: false, reason: \"The resource lock names exactly one origin and one selector; a lock never spans unrelated origins.\" };\n if (lock.key !== `${lock.origin}|${lock.selector}`) return { allowed: false, reason: \"The lock key must compose of its one origin and its one selector so the scope never spans unrelated origins.\" };\n if (lock.kind !== \"exclusive\" && lock.kind !== \"shared\") return { allowed: false, reason: \"The lock kind stays exclusive or shared.\" };\n return { allowed: true, reason: `The lock ${lock.key} spans exactly one target of one origin for the holder ${lock.holder}.` };\n}\n\n/** Grades one conflict resolution rule: the overwriting rules last and preferagent grade sensitive because one parallel value overwrites another inside the report, while first and fail grade read side. */\nexport function conflictresolutiongrade(rule: mergerule): policyevaluation {\n if (rule !== \"first\" && rule !== \"last\" && rule !== \"preferagent\" && rule !== \"fail\") return { allowed: false, reason: \"The conflict resolution rule stays first, last, preferagent or fail as the user configured it.\" };\n if (rule === \"last\" || rule === \"preferagent\") return { allowed: true, reason: `The ${rule} conflict resolution rule overwrites one parallel value with another, so it grades sensitive and the merged report still passes the human review.` };\n return { allowed: true, reason: `The ${rule} conflict resolution rule keeps or refuses the parallel values without overwriting, so it grades read side.` };\n}\n\n/** Grades one escalation as always human decided: the open escalation waits for the user and only the user writes the decision; an escalation without its subject or full context refuses because the user decides on what the agent saw. */\nexport function escalationgate(escalation: escalationrecord): policyevaluation {\n if (escalation.agentid.trim() === \"\") return { allowed: false, reason: \"The escalation names the agent whose decision it lifts.\" };\n if (escalation.subject.trim() === \"\") return { allowed: false, reason: \"The escalation needs its subject.\" };\n if (escalation.context.trim() === \"\") return { allowed: false, reason: \"The escalation needs its full context in plain language; the user decides on what the agent saw.\" };\n if (escalation.state === \"decided\" && (escalation.decision === undefined || escalation.decision.trim() === \"\")) return { allowed: false, reason: \"A decided escalation carries the decision the user wrote.\" };\n return { allowed: true, reason: `The escalation of ${escalation.agentid} stays human decided: the agent lifts the stalled decision with its full context and the user alone writes the outcome.` };\n}\n\n/** Validates one consensus quorum as a user configured value: the quorum stays a positive whole number and never exceeds the live voters the user counted, so no round carries an unreachable quorum. */\nexport function consensusquorumvalid(input: { quorum: number; voters: number }): policyevaluation {\n if (!Number.isInteger(input.quorum) || input.quorum < 1) return { allowed: false, reason: \"The consensus quorum stays a positive whole number the user configured.\" };\n if (input.quorum > input.voters) return { allowed: false, reason: `The consensus quorum ${input.quorum} exceeds the ${input.voters} voting agents the user counted; an unreachable quorum never carries.` };\n return { allowed: true, reason: `The consensus quorum ${input.quorum} of ${input.voters} voting agents stays the user configured value with no engine default.` };\n}\n\n/** Validates the worker scale bound as a user choice with no engine cap: an absent bound stays the documented unbounded choice while a configured bound stays a positive user value the scaling respects. */\nexport function workerscalevalid(bound: number | undefined): policyevaluation {\n if (bound === undefined) return { allowed: true, reason: \"No worker bound is configured, so the worker scale stays the user choice alone with no engine cap.\" };\n if (!Number.isFinite(bound) || bound < 1) return { allowed: false, reason: \"The worker scale bound stays a positive user value; no engine cap exists.\" };\n return { allowed: true, reason: `The worker scale bound ${bound} stays the user configured value; the scaling never passes it and no engine cap exists.` };\n}\n\n/** Grades one exported merged report that includes page content as a data egress event: the export carries the report with its sources into the audit trail so the reviewer reads what left the browser. */\nexport function mergeegressgrade(input: { report: resultreport; carriespagecontent: boolean }): policyevaluation {\n if (input.report.title.trim() === \"\") return { allowed: false, reason: \"The merged report needs its title before any export.\" };\n if (input.carriespagecontent) return { allowed: true, reason: `The export of the report ${input.report.title} carries page content from the sources ${input.report.sources.join(\", \")} and grades as a data egress event in the audit trail.` };\n return { allowed: true, reason: `The export of the report ${input.report.title} carries no page content and stays a plain report export.` };\n}\n\n/**\n * Execution environment gates of the 1.1.60 family.\n * Every environment side gate lives here: the environment field validation of every step kind (the evaluate kind runs inside the isolated world only while untrusted markup renders inside the sandboxframe only), the session environment grant gate that refuses a step whose environment sits outside the granted list, the offscreen capability gate that keeps the worker pool behind the optional offscreen grant, the keepalive gate that limits the keepalive port to sessions with an active reviewed plan, the worker pool size validation with no engine cap and the sandbox render origin gate that carries the user per origin toggle.\n * No environment posture, pool size, heartbeat interval or origin list is ever hardcoded: the gates validate user choices and refuse everything else, and no environment ever bypasses the human review.\n */\n\n/** Validates the environment field of every step kind: the field stays one of the four environments, the evaluate kind names the isolated world only, a step carrying untrusted markup names the sandboxframe only, the parse heavy kinds choose between the live page and the offscreen worker pool and every other kind keeps the pagecontext of the page bridge. */\nexport function stepenvironmentvalid(step: toolstep): policyevaluation {\n if (step.environment !== undefined && step.environment !== \"pagecontext\" && step.environment !== \"isolatedworld\" && step.environment !== \"offscreenworker\" && step.environment !== \"sandboxframe\") return { allowed: false, reason: \"The step environment stays one of pagecontext, isolatedworld, offscreenworker and sandboxframe.\" };\n const allowed = environmentsof(step);\n if (step.environment !== undefined && !allowed.includes(step.environment)) return { allowed: false, reason: `The ${step.environment} environment sits outside the ${allowed.join(\", \")} the ${step.kind} kind permits; the review sees the environment of every step.` };\n return { allowed: true, reason: step.environment === undefined ? `The ${step.kind} step carries no environment field and routes to its ${defaultenvironment(step)} default.` : `The ${step.environment} environment of the ${step.kind} step sits inside the ${allowed.join(\", \")} the kind permits.` };\n}\n\n/** Requires the step environment to sit inside the session environment grant list: a configured list narrows every step of the session to its entries while an absent list keeps the documented default posture where the pagecontext and the isolated world stay open behind the same review. */\nexport function environmentgrantgate(step: toolstep, grants: environmentkind[] | undefined): policyevaluation {\n if (grants === undefined || grants.length === 0) return { allowed: true, reason: `The session carries no environment grant list, so the ${defaultenvironment(step)} default of the ${step.kind} step stays the documented posture behind the same review.` };\n const environment = step.environment ?? defaultenvironment(step);\n if (!grants.includes(environment)) return { allowed: false, reason: `The ${environment} environment sits outside the ${grants.join(\", \")} the session granted; no step ever widens the environment grants.` };\n return { allowed: true, reason: `The ${environment} environment of the ${step.kind} step sits inside the ${grants.join(\", \")} the session granted.` };\n}\n\n/** Refuses offscreenworker steps when the optional offscreen capability grant stays absent: the worker pool only runs under the user granted offscreen permission, and a step without the grant falls back to inline parsing instead of refusing the reviewed work. */\nexport function offscreencapabilitygate(input: { environment: environmentkind; granted: boolean }): policyevaluation {\n if (input.environment !== \"offscreenworker\") return { allowed: true, reason: `The ${input.environment} environment needs no offscreen capability grant.` };\n if (!input.granted) return { allowed: false, reason: \"The offscreen worker pool runs only under the user granted offscreen capability; the step falls back to inline parsing inside the page.\" };\n return { allowed: true, reason: \"The offscreen worker pool runs under the user granted offscreen capability.\" };\n}\n\n/** Limits the keepalive port to sessions with an active reviewed plan: a stopped or paused session holds no port, an expired session holds none, and a plan outside the approved state holds none because the keepalive signal rides a run the user already reviewed. */\nexport function keepalivegate(input: { session: agentsession | undefined; plan: agentplan | undefined; now: number }): policyevaluation {\n if (!input.session) return { allowed: false, reason: \"The keepalive port opens only inside an active session.\" };\n if (input.session.stoppedat !== undefined) return { allowed: false, reason: \"The keepalive port stays closed for a stopped session.\" };\n if (input.session.pausedat !== undefined) return { allowed: false, reason: \"The keepalive port stays closed while the session pauses; a resumed run reattaches it.\" };\n if (input.now > input.session.expiresat) return { allowed: false, reason: \"The keepalive port stays closed for an expired session.\" };\n if (!input.plan || input.plan.state !== \"approved\") return { allowed: false, reason: \"The keepalive port opens only behind an active reviewed plan; unreviewed work never holds the service worker alive.\" };\n return { allowed: true, reason: `The approved plan ${input.plan.id} of the active session holds the keepalive port open for its whole run.` };\n}\n\n/** Validates the keepalive heartbeat interval as a positive user value in milliseconds: the roadmap documents thirty seconds while the interval stays the user's choice with no engine default. */\nexport function keepaliveintervalvalid(interval: number): policyevaluation {\n if (!Number.isFinite(interval) || interval <= 0) return { allowed: false, reason: \"The keepalive heartbeat interval stays a positive user value in milliseconds.\" };\n return { allowed: true, reason: `The keepalive heartbeat interval ${interval} milliseconds stays the user configured value; the roadmap documents thirty seconds while the choice stays the user's.` };\n}\n\n/** Validates the worker pool size as a user choice with no engine cap: an absent size lets the pool follow the pending parse queue alone while a configured size stays a positive whole number. */\nexport function workerpoolsizevalid(size: number | undefined): policyevaluation {\n if (size === undefined) return { allowed: true, reason: \"No worker pool size is configured, so the pool follows the pending parse queue alone with no engine cap.\" };\n if (!Number.isInteger(size) || size < 1) return { allowed: false, reason: \"The worker pool size stays a positive whole number the user configured; no engine cap exists.\" };\n return { allowed: true, reason: `The worker pool size ${size} stays the user configured value; no engine cap exists.` };\n}\n\n/** Grades one sandbox render by the origins the user allows: an absent list keeps every origin open as the documented user choice while a configured list restricts the untrusted markup renders to the origins it names. */\nexport function sandboxorigingate(input: { origin: string; allowed: string[] }): policyevaluation {\n if (input.origin.trim() === \"\") return { allowed: false, reason: \"The sandbox render needs the source origin of its untrusted markup.\" };\n if (input.allowed.length > 0 && !input.allowed.includes(input.origin)) return { allowed: false, reason: `The origin ${input.origin} sits outside the origins the user allows to render untrusted markup: ${input.allowed.join(\", \")}.` };\n return { allowed: true, reason: input.allowed.length === 0 ? `The origin ${input.origin} renders untrusted markup under the documented open origin list the user chose not to narrow.` : `The origin ${input.origin} sits inside the origins the user allows to render untrusted markup.` };\n}\n\n/** Exposes the environment requirements of every reviewed action kind: one profile per kind with its allowed environments and its default, so the panel and the executor read the same table the policy validates against. */\nexport function environmentrequirements(): environmentrequirement[] {\n return environmentrequirementsof([...allowedactions]);\n}\n\n/**\n * Security part one gates of the 1.1.61 family.\n * Every trust boundary gate lives here: the automation allowlist gate that flips the posture to denydefault for every ungranted origin and binds each grant to one exact origin with no wildcard expansion while the active tab grant counts as exactly one explicit single origin grant, the origin profile gate that consults the per site profile before every sensitive kind and refuses the kinds its denials name, the consent window gate that scopes every window to one session and one origin, expires it at its duration boundary and suspends the run when the window expires mid step, the revocation gate that accepts mid run revocation as a terminal session event halting the pending step and every queued step, the sensitive class gate that routes payment, credential, delete and publish steps through one fresh consent prompt per class per origin, the consent duration validation that keeps every boundary a positive user value that never defaults to unlimited, and the log read gate that refuses reads of a hash chain with a broken link.\n * No duration, boundary, shape list or kind list is ever hardcoded: every window, profile, consent and mask shape stays the user's choice, and no security operation ever bypasses the human review.\n */\n\n/** Checks one origin against the per origin automation allowlist under the denydefault posture: every ungranted origin stays refused before any step dispatches, the active tab origin counts as exactly one explicit single origin grant, and a wildcard entry refuses the whole check because no wildcard expansion exists. */\nexport function automationallowlistgate(input: { origin: string; allowlist: automationallowlistentry[]; session: agentsession | undefined }): policyevaluation {\n const verdict = allowlistcheck({ origin: input.origin, allowlist: input.allowlist, ...(input.session !== undefined ? { sessionorigin: input.session.origin } : {}) });\n if (!verdict.allowed) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Consults the per site origin profile before one sensitive kind: an explicit denial refuses the kind on that origin, an explicit grant allows it while the fresh class consent gate still routes the sensitive classes through their prompt, and a non-sensitive kind needs no profile consult. */\nexport function originprofilegate(input: { profile: originprofile | undefined; kind: actionkind; sensitive: boolean }): policyevaluation {\n const verdict = profilegrade(input);\n if (!verdict.allowed) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Checks the consent window of one step before dispatch: the window scopes to exactly one session and one origin, a window past its duration boundary suspends the run mid step, and a sensitive step without an active window waits for its consent prompt. */\nexport function consentwindowgate(input: { window: consentwindow | undefined; sessionid: string; origin: string; sensitive: boolean; now: number }): policyevaluation {\n if (!input.sensitive) return { allowed: true, reason: \"The non-sensitive step rides the session origin grants and needs no consent window of its own.\" };\n const verdict = windowgatesstep({ window: input.window, sessionid: input.sessionid, origin: input.origin, now: input.now });\n if (!verdict.allowed) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Accepts the mid run revocation as a terminal session event: the pending step and every queued step halt without executing, the run aborts its plan, and no later step of the revoked run dispatches. */\nexport function revokerungate(input: { revocation: revokerunevent | undefined; sessionid: string; runid: string }): policyevaluation {\n if (input.revocation === undefined) return { allowed: true, reason: \"No revocation halted the run; the steps keep their reviewed order.\" };\n if (input.revocation.sessionid !== input.sessionid) return { allowed: true, reason: \"The revocation belongs to another session and halts nothing here.\" };\n if (input.revocation.runid !== input.runid) return { allowed: true, reason: \"The revocation belongs to another run and halts nothing here.\" };\n return { allowed: false, reason: `The revocation of ${input.revocation.actor} halted the pending step and ${Math.max(0, input.revocation.haltedstepids.length - 1)} queued step${Math.max(0, input.revocation.haltedstepids.length - 1) === 1 ? \"\" : \"s\"} without executing them: ${input.revocation.haltedstepids.join(\", \")}.` };\n}\n\n/** Routes one sensitive step through its fresh consent prompt: every sensitive class the classification names needs its own fresh consent per origin, an upload, download or evaluate grade sensitive by default rides its consent window prompt, and a non-sensitive step needs no fresh consent. */\nexport function sensitiveclassgate(input: { origin: string; classes: sensitiveclass[]; bydefault: boolean; sensitive: boolean; consents: classconsent[]; now: number }): policyevaluation {\n if (!input.sensitive) return { allowed: true, reason: \"The step carries no sensitive class and needs no fresh consent prompt.\" };\n const verdict = missingclassconsents({ origin: input.origin, classes: input.classes, bydefault: input.bydefault, consents: input.consents, now: input.now });\n if (verdict.needed) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Validates the consent window duration as a positive user value in milliseconds: every consent window names its boundary and no window ever defaults to unlimited. */\nexport function consentdurationvalid(duration: number): policyevaluation {\n if (!Number.isFinite(duration) || duration <= 0) return { allowed: false, reason: \"The consent window duration stays a positive user value in milliseconds; no grant ever defaults to unlimited.\" };\n return { allowed: true, reason: `The consent window duration ${duration} milliseconds stays the user configured boundary the prompt names.` };\n}\n\n/** Refuses log reads of a chain with a broken link: the audit accessor verifies the whole hash chain before returning a single entry, and tamper evidence refuses the read instead of serving a forged record. */\nexport function logreadgate(input: { valid: boolean; brokenat?: number }): policyevaluation {\n if (!input.valid) return { allowed: false, reason: input.brokenat !== undefined ? `The log chain breaks at entry ${input.brokenat}; the audit accessor refuses the read of a forged record.` : \"The log chain fails its verification; the audit accessor refuses the read of a forged record.\" };\n return { allowed: true, reason: \"The log chain verifies from the genesis hash to the last entry; the audit accessor serves the entries.\" };\n}\n\n/** Grades one step through the whole sensitive pipeline: the classification names the classes and the default grade, the origin profile consults its per kind decisions, and the fresh class consent gate routes every named class through its own prompt per origin. */\nexport function sensitivepipelingate(input: { step: Pick<toolstep, \"kind\" | \"value\" | \"options\">; profile: originprofile | undefined; consents: classconsent[]; origin: string; now: number }): policyevaluation {\n const classification = sensitiveclassesof(input.step);\n const profileverdict = originprofilegate({ profile: input.profile, kind: input.step.kind, sensitive: classification.sensitive });\n if (!profileverdict.allowed) return { allowed: false, reason: profileverdict.reason ?? \"\" };\n const consentverdict = sensitiveclassgate({ origin: input.origin, classes: classification.classes, bydefault: classification.bydefault, sensitive: classification.sensitive, consents: input.consents, now: input.now });\n if (!consentverdict.allowed) return { allowed: false, reason: `${classification.reason} ${consentverdict.reason}` };\n return { allowed: true, reason: `${classification.reason} ${consentverdict.reason}` };\n}\n\n/**\n * Security part two gates of the 1.1.62 family.\n * Every inbound and dispatch guard lives here: the schemastrict gate that validates every inbound command before dispatch and rejects unknown fields with the path and the expected shape, the origincheck and connectallow gates that guard every runtime message and port connection while dropping senders absent from the user managed list, the ratelimit gate that bounds automation commands per origin and per session by deferring past the bucket until the window resets, the confirmpay, confirmdelete and confirmcreds gates that route payment, destructive delete and credential use steps through one distinct human action each with no batch approval and no timeout, the phishguard gate that blocks login origins crossing the user lookalike threshold while naming the matched known origin, the safedefaults gate that profiles unknown origins as reads only, the vault secret gate that refuses secrets in step options, variables and plan texts, and the untrusted render gate that marks extracted markup untrusted and routes it through the sandboxframe.\n * No bound, window, threshold or sender list is ever hardcoded: every ratelimit bound, lookalike threshold and connectallow entry stays the user's choice, and no security operation ever bypasses the human review.\n */\n\n/** Requires schemastrict validation of every inbound command before dispatch: every schema error names its path and its expected shape, and the refusal echoes no payload. */\nexport function schemaguardgate(input: { errors: schemaerror[] }): policyevaluation {\n if (input.errors.length === 0) return { allowed: true, reason: \"The inbound command matches its declared schemastrict grammar field by field.\" };\n const first = input.errors[0];\n return { allowed: false, reason: `${input.errors.length} schema error${input.errors.length === 1 ? \"\" : \"s\"} refuse the command before dispatch: ${input.errors.map(error => error.reason).join(\" \")}${first !== undefined ? ` The first error sits at ${first.path} expecting ${first.expected}.` : \"\"}` };\n}\n\n/** Requires the origincheck verdict on every runtime message before handling: a sender the verdict drops never reaches a handler. */\nexport function origincheckgate(input: { verdict: origincheckverdict }): policyevaluation {\n if (!input.verdict.accepted) return { allowed: false, reason: input.verdict.reason };\n return { allowed: true, reason: input.verdict.reason };\n}\n\n/** Drops messages and port connections from senders absent from the connectallow list: the list ships empty by default and holds user managed entries only. */\nexport function connectallowgate(input: { senderid?: string; senderorigin?: string; extensionid: string; connectallow: connectallowentry[] }): policyevaluation {\n const verdict = origincheckof(input);\n if (!verdict.accepted) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Validates the ratelimit bucket bounds as user choices: the limit and the window stay positive user values because no hidden ceiling exists. */\nexport function ratelimitboundsvalid(limit: number, window: number): policyevaluation {\n const bounds = bucketboundsvalid(limit, window);\n if (!bounds.valid) return { allowed: false, reason: bounds.reason };\n return { allowed: true, reason: bounds.reason };\n}\n\n/** Applies one ratelimit bucket to an automation command per origin and per session: a command inside its bound consumes a slot while a command past its bound defers until the window resets; an origin without a bucket keeps every command because the bounds stay user choices only. */\nexport function ratelimitgate(input: { bucket: ratelimitbucket | undefined; now: number }): policyevaluation {\n if (input.bucket === undefined) return { allowed: true, reason: \"No ratelimit bucket covers the origin of the command; the bounds stay user configured choices only.\" };\n if (input.now >= input.bucket.resetsat) return { allowed: true, reason: `The bucket window of ${input.bucket.origin} reset at ${input.bucket.resetsat}; the command consumes the first slot of its fresh window.` };\n if (input.bucket.used < input.bucket.limit) return { allowed: true, reason: `The command consumes slot ${input.bucket.used + 1} of ${input.bucket.limit} in the bucket of ${input.bucket.origin}.` };\n return { allowed: false, reason: `The bucket of ${input.bucket.origin} holds its ${input.bucket.limit} command bound; the command defers until the window resets at ${input.bucket.resetsat}.` };\n}\n\n/** Routes payment class steps through the confirmpay gate: a payment step without a gate opens one and pauses, an open gate keeps the pause, a resolved gate lets the step dispatch and a refused gate never dispatches it. */\nexport function confirmpaygate(input: { classes: sensitiveclass[]; state: \"none\" | \"open\" | \"resolved\" | \"refused\" }): policyevaluation {\n const kind = gatekindfor(input.classes);\n if (kind !== \"confirmpay\") return { allowed: true, reason: \"The step carries no payment class and needs no confirmpay gate.\" };\n if (input.state === \"resolved\") return { allowed: true, reason: \"The human resolved the confirmpay gate of the payment step; the step dispatches with its reviewed amount, payee origin and target.\" };\n if (input.state === \"refused\") return { allowed: false, reason: \"The human refused the confirmpay gate of the payment step; the payment never dispatches.\" };\n if (input.state === \"open\") return { allowed: false, reason: \"The confirmpay gate of the payment step stays open with the amount, the payee origin and the target element; the executor pauses until the human resolves it and no timeout ever resolves a gate.\" };\n return { allowed: false, reason: \"The payment step opens its confirmpay gate with the amount, the payee origin and the target element; the executor pauses until one distinct human action resolves it.\" };\n}\n\n/** Routes destructive delete steps through the confirmdelete gate: a delete step without a gate opens one and pauses, an open gate keeps the pause, a resolved gate lets the step dispatch and a refused gate never dispatches it. */\nexport function confirmdeletegate(input: { classes: sensitiveclass[]; state: \"none\" | \"open\" | \"resolved\" | \"refused\" }): policyevaluation {\n const kind = gatekindfor(input.classes);\n if (kind !== \"confirmdelete\") return { allowed: true, reason: \"The step carries no delete class and needs no confirmdelete gate.\" };\n if (input.state === \"resolved\") return { allowed: true, reason: \"The human resolved the confirmdelete gate of the destructive step; the step dispatches with its reviewed target, scope and irreversibility.\" };\n if (input.state === \"refused\") return { allowed: false, reason: \"The human refused the confirmdelete gate of the destructive step; the deletion never dispatches.\" };\n if (input.state === \"open\") return { allowed: false, reason: \"The confirmdelete gate of the destructive step stays open with the target, the scope and the irreversibility; the executor pauses until the human resolves it and no timeout ever resolves a gate.\" };\n return { allowed: false, reason: \"The destructive step opens its confirmdelete gate with the target, the scope and the irreversibility; the executor pauses until one distinct human action resolves it.\" };\n}\n\n/** Routes credential use steps through the confirmcreds gate: a credential step without a gate opens one and pauses with the credential label only, an open gate keeps the pause, a resolved gate lets the step read the vault at the last possible moment and a refused gate never dispatches it. */\nexport function confirmcredsgate(input: { classes: sensitiveclass[]; state: \"none\" | \"open\" | \"resolved\" | \"refused\" }): policyevaluation {\n const kind = gatekindfor(input.classes);\n if (kind !== \"confirmcreds\") return { allowed: true, reason: \"The step carries no credential class and needs no confirmcreds gate.\" };\n if (input.state === \"resolved\") return { allowed: true, reason: \"The human resolved the confirmcreds gate of the credential step; the step reads its value from the vault at the last possible moment and no log records it.\" };\n if (input.state === \"refused\") return { allowed: false, reason: \"The human refused the confirmcreds gate of the credential step; the credential never dispatches.\" };\n if (input.state === \"open\") return { allowed: false, reason: \"The confirmcreds gate of the credential step stays open with its credential label only; the executor pauses until the human resolves it and no timeout ever resolves a gate.\" };\n return { allowed: false, reason: \"The credential step opens its confirmcreds gate with its credential label only; the executor pauses until one distinct human action resolves it.\" };\n}\n\n/** Requires a distinct human action for each gated step: one action resolves exactly one gate, so a batch resolution refuses in full. */\nexport function gatebatchgate(input: { gateids: string[] }): policyevaluation {\n if (input.gateids.length === 0) return { allowed: false, reason: \"A gate resolution names its single gate.\" };\n if (input.gateids.length > 1) return { allowed: false, reason: `One human action resolves exactly one gate; the batch of ${input.gateids.length} gates refuses in full because no batch approval exists.` };\n return { allowed: true, reason: \"The resolution names exactly one gate; the distinct human action resolves it alone.\" };\n}\n\n/** Validates the phishguard lookalike threshold as a user choice between zero and one: the block line never defaults to an engine value. */\nexport function phishthresholdgate(threshold: number): policyevaluation {\n const verdict = phishthresholdvalid(threshold);\n if (!verdict.valid) return { allowed: false, reason: verdict.reason };\n return { allowed: true, reason: verdict.reason };\n}\n\n/** Blocks the credential step when the phishguard verdict crosses the user threshold: the deny event names the matched known origin the login target resembles. */\nexport function phishguardgate(input: { verdict: phishverdict }): policyevaluation {\n if (input.verdict.blocked) return { allowed: false, reason: input.verdict.reason };\n return { allowed: true, reason: input.verdict.reason };\n}\n\n/** Applies safedefaults to an origin without an originprofile: the documented read kinds pass while every sensitive class denies until the user widens the profile in the originprofile editor. */\nexport function safedefaultsgate(input: { profile: originprofile | undefined; classes: sensitiveclass[]; sensitive: boolean }): policyevaluation {\n if (input.profile !== undefined) return { allowed: true, reason: `The origin profile of ${input.profile.origin} exists; the safedefaults posture stays out of the decision.` };\n if (!input.sensitive) return { allowed: true, reason: \"The non-sensitive step rides the reads only baseline of the safedefaults posture; the first visit grants reads alone.\" };\n return { allowed: false, reason: `No origin profile exists and the safedefaults posture denies the sensitive ${input.classes.length > 0 ? input.classes.join(\" and \") : \"by default\"} step; open the originprofile editor to widen the profile the user controls.` };\n}\n\n/** Refuses secrets in step options, variables and plan text: a plaintext value that digests to a vault record or a raw value behind a masked field shape never dispatches. */\nexport function vaultsecretgate(input: { leaks: string[]; carries: boolean }): policyevaluation {\n if (input.leaks.length > 0) return { allowed: false, reason: `The plan carries ${input.leaks.length} plaintext secret value${input.leaks.length === 1 ? \"\" : \"s\"} that digest to vault records; secrets never ride step options, variables or plan texts, only the vault holds them.` };\n if (input.carries) return { allowed: false, reason: \"The step types a raw value into a masked field shape; credential steps read their value from the vault at the last possible moment and never carry it in the options.\" };\n return { allowed: true, reason: \"The step and the plan carry no secret outside the vault; the values stay behind the seam.\" };\n}\n\n/** Marks extracted markup untrusted before any render: the render routes through the sandboxframe of the 1.1.60 family while an injection into the page context refuses. */\nexport function untrustedrendergate(input: { environment: \"pagecontext\" | \"isolatedworld\" | \"offscreenworker\" | \"sandboxframe\" }): policyevaluation {\n if (input.environment === \"sandboxframe\") return { allowed: true, reason: \"The extracted markup renders inside the sandboxframe under its nonce with scripts and handlers stripped; the untrusted content never reenters the page context.\" };\n return { allowed: false, reason: `The extracted markup grades untrusted and refuses to render inside the ${input.environment}; every untrusted render routes through the sandboxframe.` };\n}\n\n/**\n * Session interface gates of the 1.1.63 family.\n * Every interface gate lives here: the sitenotes read gate that limits note reads to the origins the session granted, the sitenotes write gate that keeps note writes behind one explicit consent, the scratchpad scope gate that binds every entry to its owning task session, the memory read scope gate that opens correctionmemory and consentmemory reads during planning and prompting alone, the semanticrecall scope gate that refuses recall queries across origins outside the run scope, the runsummary window validation that keeps the distillation length a user value with no fixed cap, the retention validations that keep every note, scratchpad, summary and correction window a positive user choice, the consentmemory advisory gate that keeps every record advisory with denials carrying the same weight as grants, the cancelrun gate that rolls the queued steps back only while the executed steps stay untouched in the sealed log, and the retry dispatch gate that lets a retry pass only through a new reviewed dispatch.\n * No window, retention, limit or rollback scope is ever hardcoded: every length and expiry stays the user's choice, and no interface surface ever bypasses the human review.\n */\n\n/** Gates one sitenotes read against the granted origins: a note of an origin the session never granted refuses the read. */\nexport function sitenotesreadgate(input: { origin: string; grants: string[] }): policyevaluation {\n if (input.grants.includes(input.origin)) return { allowed: true, reason: `The session granted ${input.origin}, so the site notes of the origin read.` };\n return { allowed: false, reason: `The session never granted ${input.origin}; the site notes of the origin refuse the read.` };\n}\n\n/** Gates one sitenotes write behind explicit consent: a note write without the reviewed consent of the user refuses. */\nexport function sitenoteswritegate(input: { consent: boolean; origin: string }): policyevaluation {\n if (!input.consent) return { allowed: false, reason: `The site note write for ${input.origin} needs the explicit consent of the user; no note lands without a reviewed write.` };\n return { allowed: true, reason: `The user consented to the site note write for ${input.origin}; the note keeps its author provenance and its timestamps.` };\n}\n\n/** Gates one scratchpad access to its owning task session: an entry of another task or another session never reads or writes. */\nexport function scratchpadscopegate(input: { taskid: string; sessionid: string; entrytaskid: string; entrysessionid: string }): policyevaluation {\n if (input.taskid !== input.entrytaskid || input.sessionid !== input.entrysessionid) return { allowed: false, reason: `The scratchpad entry belongs to the task ${input.entrytaskid} of the session ${input.entrysessionid}; the task ${input.taskid} of the session ${input.sessionid} never crosses that boundary.` };\n return { allowed: true, reason: `The scratchpad entry belongs to the task ${input.taskid} of the session ${input.sessionid} that asks for it.` };\n}\n\n/** Gates correctionmemory and consentmemory reads to the planning and prompting phases: a read outside those phases refuses because the history serves the review alone. */\nexport function memoryreadscopegate(input: { phase: \"planning\" | \"prompting\" | \"execution\" | \"idle\" }): policyevaluation {\n if (input.phase === \"planning\" || input.phase === \"prompting\") return { allowed: true, reason: `The ${input.phase} phase reads the correction and consent memory so the proposal and the prompt carry the prior decisions.` };\n return { allowed: false, reason: `The ${input.phase} phase reads no correction or consent memory; the history serves the planning and the prompting alone.` };\n}\n\n/** Refuses semanticrecall queries across origins outside the run scope: the recall ranks only the origins the run scope names. */\nexport function semanticrecallscopegate(input: { origin: string | undefined; scope: string[] }): policyevaluation {\n if (input.origin === undefined) return { allowed: true, reason: `The recall query names no origin, so it ranks the ${input.scope.length} origin${input.scope.length === 1 ? \"\" : \"s\"} of the run scope only.` };\n if (!input.scope.includes(input.origin)) return { allowed: false, reason: `The recall query asks for ${input.origin} while the run scope holds ${input.scope.length > 0 ? input.scope.join(\", \") : \"no origin\"}; a recall across origins outside the run scope refuses.` };\n return { allowed: true, reason: `The recall query asks for ${input.origin} inside the run scope; the ranking stays scoped.` };\n}\n\n/** Validates the runsummary window as a user value: the distillation length follows the user configured step window with no fixed cap while an absent window keeps every step. */\nexport function summarywindowvalid(window: number | undefined): policyevaluation {\n if (window === undefined) return { allowed: true, reason: \"No runsummary window is configured, so the distillation keeps every step with no fixed cap.\" };\n if (!Number.isInteger(window) || window < 0) return { allowed: false, reason: \"The runsummary window stays a whole number of steps the user chose; no engine cap exists.\" };\n return { allowed: true, reason: `The runsummary window of ${window} step${window === 1 ? \"\" : \"s\"} stays the user configured choice; no engine cap exists.` };\n}\n\n/** Validates one retention window of the five session stores as a positive user value: no note, scratchpad, summary or correction ever expires at an engine boundary. */\nexport function sessionretentionvalid(window: number | undefined): policyevaluation {\n if (window === undefined) return { allowed: true, reason: \"No retention window is configured, so the session store keeps every record forever.\" };\n if (!Number.isFinite(window) || window <= 0) return { allowed: false, reason: \"The retention window stays a positive user value in milliseconds; no engine boundary expires a record.\" };\n return { allowed: true, reason: `The retention window of ${window} milliseconds stays the user configured choice.` };\n}\n\n/** Keeps the consentmemory advisory: no record auto grants, a denial carries the same weight as a grant, and every decision still needs its own prompt. */\nexport function consentmemoryadvisorygate(input: { auto: boolean; latest?: { decision: string; origin: string } }): policyevaluation {\n if (input.auto) return { allowed: false, reason: `The consent memory never auto grants: the prior decisions of ${input.latest?.origin ?? \"the origin\"} stay advisory and every grant needs its own prompt.` };\n if (input.latest !== undefined && input.latest.decision === \"deny\") return { allowed: true, reason: `The consent memory holds a prior denial for ${input.latest.origin} with the same weight as a grant; the prompt shows the refusal and the user decides again.` };\n return { allowed: true, reason: \"The consent memory stays advisory; the prompt opens with the prior decisions and the user decides.\" };\n}\n\n/** Gates one cancelrun action: the rollback touches the queued steps only while the executed steps stay untouched in the sealed immutable log. */\nexport function cancelrungate(input: { queuedstepids: string[]; executedstepids: string[]; rollbackscope: \"queued\" | \"none\" }): policyevaluation {\n if (input.rollbackscope === \"none\") return { allowed: true, reason: `The cancelrun stops the run with no rollback; the ${input.queuedstepids.length} queued step${input.queuedstepids.length === 1 ? \"\" : \"s\"} stay as the run left them and the ${input.executedstepids.length} executed step${input.executedstepids.length === 1 ? \"\" : \"s\"} stay in the sealed log.` };\n return { allowed: true, reason: `The cancelrun rolls the ${input.queuedstepids.length} queued step${input.queuedstepids.length === 1 ? \"\" : \"s\"} back${input.queuedstepids.length > 0 ? ` (${input.queuedstepids.join(\", \")})` : \"\"} while the ${input.executedstepids.length} executed step${input.executedstepids.length === 1 ? \"\" : \"s\"} stay untouched in the sealed log.` };\n}\n\n/** Gates one step retry through a new reviewed dispatch: an automatic retry without the review refuses while a reviewed retry rides the full consent gate chain. */\nexport function retrydispatchgate(input: { reviewed: boolean; stepid: string }): policyevaluation {\n if (!input.reviewed) return { allowed: false, reason: `The retry of the step ${input.stepid} passes only through a new reviewed dispatch; an automatic retry never bypasses the review.` };\n return { allowed: true, reason: `The retry of the step ${input.stepid} dispatches again through the full consent gate chain: the session, the plan and the origin gates all recheck the step.` };\n}\n\n/**\n * Interface surface gates of the 1.1.64 family.\n * Every surface gate lives here: the palette action gate that keeps each commandpalette action behind its existing permission gate, the taskinput proposal gate that routes every submission through the same proposal flow as the api, the plan review gate that requires the plancard review before any execution, the stepapprove gate that binds each resolution to one step with one distinct human action and no batch approval, the diffpreview gate that limits generation to write class steps, the onboarding consent gate that lets a full completion write exactly one consent scoped event, the logstream buffer bound validation that keeps the live window a user value with no engine cap, and the logstream egress gate that lets an audit excerpt copy only a verified range.\n * No window, limit or batch scope is ever hardcoded: every bound stays the user's choice, and no interface surface ever bypasses the human review.\n */\n\n/** Gates one commandpalette action behind its existing permission gate: a command that names an ungranted capability never lists, and a command that needs a session never lists without one. */\nexport function paletteactiongate(input: { action: { command: string; permission?: string; session?: boolean }; granted: string[]; sessionactive: boolean }): policyevaluation {\n if (input.action.permission !== undefined && !input.granted.includes(input.action.permission)) return { allowed: false, reason: `The ${input.action.command} command needs the ${input.action.permission} capability granted before the palette lists it; the palette never offers an action the current capability set refuses.` };\n if (input.action.session === true && !input.sessionactive) return { allowed: false, reason: `The ${input.action.command} command needs an active browser session before the palette lists it; the palette never offers a run action without its session.` };\n return { allowed: true, reason: `The ${input.action.command} command rides its granted permissions and lists in the palette.` };\n}\n\n/** Routes one taskinput submission through the same proposal flow as the api: an empty goal or origin refuses, and a direct execution bypass refuses because every goal becomes a reviewed plan first. */\nexport function taskinputproposalgate(input: { text: string; origin: string; direct: boolean }): policyevaluation {\n if (input.direct) return { allowed: false, reason: \"The taskinput never executes a goal directly; every natural language goal routes through the same proposal flow as the api and becomes a reviewed plan first.\" };\n if (input.text.trim() === \"\") return { allowed: false, reason: \"The taskinput submission needs its natural language goal; an empty goal never reaches the proposal flow.\" };\n if (input.origin.trim() === \"\") return { allowed: false, reason: \"The taskinput submission needs its active origin scope; a goal without an origin never reaches the proposal flow.\" };\n return { allowed: true, reason: `The taskinput goal for ${input.origin} rides the same proposal flow as the api: the observation, the capabilities and the plan review all recheck it.` };\n}\n\n/** Requires the plan review through plancards before any execution: an approved plan already passed the review while a pending plan without its plancard review refuses every execution. */\nexport function planreviewgate(input: { reviewed: boolean; state: \"pending\" | \"approved\" }): policyevaluation {\n if (input.state === \"approved\") return { allowed: true, reason: \"The plan already passed its review: the approval is the review of record and the execution proceeds.\" };\n if (!input.reviewed) return { allowed: false, reason: \"The pending plan has no plancard review yet; every step renders its card with the risk class, the environment and the options before any execution.\" };\n return { allowed: true, reason: \"The plancard review of the pending plan is open; the resolution of each step stays a distinct human action.\" };\n}\n\n/** Gates one stepapprove resolution: each action resolves exactly one step with one distinct human provenance and a batch resolution refuses in full. */\nexport function stepapprovegate(input: { stepids: string[]; resolution: \"approve\" | \"reject\" | \"edit\"; surface: \"popup\" | \"sidepanel\" | \"dashboardpage\" | \"optionspage\" | \"onboarding\" | \"background\" }): policyevaluation {\n if (input.stepids.length === 0) return { allowed: false, reason: \"A stepapprove resolution names its single step.\" };\n if (input.stepids.length > 1) return { allowed: false, reason: `One human action resolves exactly one step; the batch of ${input.stepids.length} steps refuses in full because no batch approval exists.` };\n if (input.surface === \"background\") return { allowed: false, reason: `The ${input.resolution} resolution of the step ${input.stepids[0]} needs its distinct human action from a surface; the background never resolves a review on its own.` };\n if (input.resolution === \"edit\" ) return { allowed: true, reason: `The user edits the step ${input.stepids[0]} from the ${input.surface} before approving; the corrected shape rides the plan and the resolution keeps its human provenance.` };\n return { allowed: true, reason: `The user ${input.resolution === \"approve\" ? \"approved\" : \"rejected\"} the step ${input.stepids[0]} from the ${input.surface}; one distinct human action resolved the step alone.` };\n}\n\n/** Limits diffpreview generation to write class steps: a read or interaction step changes no page state, so no before and after pair exists to compare. */\nexport function diffpreviewgate(input: { risk: \"read\" | \"interaction\" | \"sensitive\" }): policyevaluation {\n if (input.risk !== \"sensitive\") return { allowed: false, reason: `The ${input.risk} step changes no page or browser state; the diffpreview compares the observed before state with the predicted after state of write class steps only.` };\n return { allowed: true, reason: \"The write class step changes page or browser state, so the diffpreview compares its observed before state with its predicted after state.\" };\n}\n\n/** Gates the onboarding completion event: a full completion writes exactly one consent scoped event and a second consent event refuses. */\nexport function onboardingconsentgate(input: { consentevents: string[] }): policyevaluation {\n if (input.consentevents.length === 0) return { allowed: true, reason: \"The onboarding completion writes its single consent scoped event; no consent event exists yet.\" };\n if (input.consentevents.length === 1) return { allowed: false, reason: `The onboarding already wrote its single consent scoped event ${input.consentevents[0]}; a walkthrough never writes a second one.` };\n return { allowed: false, reason: `The onboarding found ${input.consentevents.length} consent scoped events; a walkthrough writes exactly one and the extra events refuse.` };\n}\n\n/** Validates the logstream live buffer bound as a user value: the live window follows the user configured bound with no engine cap while an absent bound keeps every event live. */\nexport function logbufferboundvalid(bound: number | undefined): policyevaluation {\n if (bound === undefined) return { allowed: true, reason: \"No logstream buffer bound is configured, so the live window keeps every event while the full history stays in memory.\" };\n if (!Number.isInteger(bound) || bound <= 0) return { allowed: false, reason: \"The logstream buffer bound stays a positive whole number of events the user chose; no engine cap exists.\" };\n return { allowed: true, reason: `The logstream buffer bound of ${bound} event${bound === 1 ? \"\" : \"s\"} stays the user configured choice; the full history stays in memory.` };\n}\n\n/** Gates the logstream audit excerpt copy: only a verified range copies while an unverified chain refuses the egress in full. */\nexport function logstreamegressgate(input: { verified: boolean; entries: number }): policyevaluation {\n if (input.entries === 0) return { allowed: false, reason: \"The audit excerpt names no event of the logstream; an empty range never copies.\" };\n if (!input.verified) return { allowed: false, reason: \"The logstream chain failed its live verification; the audit excerpt refuses the copy because only a verified range leaves the stream.\" };\n return { allowed: true, reason: `The logstream chain verifies across the ${input.entries} event${input.entries === 1 ? \"\" : \"s\"} of the range; the audit excerpt copies as one verified record.` };\n}\n", "import { hostpattern, normalizeendpoint } from \"../policy.js\";\nimport type { capabilityreport, captchahandoff, clipboardconsentrecord, consoleconsentrecord, dataset, dialogdecision, downloadrecord, keyholdstate, navqueues, navstate, quarantineentry, streamstate, tabbadge, waitprofilerecord } from \"../types.js\";\nimport type { windowshape } from \"./tabscommand.js\";\n\ntype pagesignallike = { language?: string; template?: string; scrolllocked?: boolean; banner?: string };\n\nconst endpointinput = document.querySelector<HTMLInputElement>(\"#endpoint\");\nconst statusnode = document.querySelector<HTMLElement>(\"#status\");\nconst capabilitiesnode = document.querySelector<HTMLElement>(\"#capabilities\");\nconst livenode = document.querySelector<HTMLElement>(\"#livelogic\");\nconst navstatenode = document.querySelector<HTMLElement>(\"#navstate\");\nconst windowlayoutnode = document.querySelector<HTMLElement>(\"#windowlayout\");\nconst captchastatenode = document.querySelector<HTMLElement>(\"#captchastate\");\nconst datasetstatenode = document.querySelector<HTMLElement>(\"#datasetstate\");\nconst downloadstatenode = document.querySelector<HTMLElement>(\"#downloadstate\");\nconst clipboardstatenode = document.querySelector<HTMLElement>(\"#clipboardstate\");\nconst capturestatenode = document.querySelector<HTMLElement>(\"#capturestate\");\nconst mediastatenode = document.querySelector<HTMLElement>(\"#mediastate\");\nconst callstatenode = document.querySelector<HTMLElement>(\"#callstate\");\nconst rulestatenode = document.querySelector<HTMLElement>(\"#rulestate\");\nconst netstatenode = document.querySelector<HTMLElement>(\"#netstate\");\nconst debugstatenode = document.querySelector<HTMLElement>(\"#debugstate\");\nconst emulationstatenode = document.querySelector<HTMLElement>(\"#emulationstate\");\nconst sessionstatenode = document.querySelector<HTMLElement>(\"#sessionstate\");\nconst workflowstatenode = document.querySelector<HTMLElement>(\"#workflowstate\");\nconst triggerstatenode = document.querySelector<HTMLElement>(\"#triggerstate\");\nconst mcpstatenode = document.querySelector<HTMLElement>(\"#mcpstate\");\nconst modelstatenode = document.querySelector<HTMLElement>(\"#modelstate\");\nconst swarmstatenode = document.querySelector<HTMLElement>(\"#swarmstate\");\nconst environmentstatenode = document.querySelector<HTMLElement>(\"#environmentstate\");\nconst securitystatenode = document.querySelector<HTMLElement>(\"#securitystate\");\nconst securitycontrols = document.querySelector<HTMLElement>(\"#securitycontrols\");\nconst sessiongridstatenode = document.querySelector<HTMLElement>(\"#sessiongridstate\");\nconst sessiongridcontrols = document.querySelector<HTMLElement>(\"#sessiongridcontrols\");\nconst environmentcontrols = document.querySelector<HTMLElement>(\"#environmentcontrols\");\nconst workflowcontrols = document.querySelector<HTMLElement>(\"#workflowcontrols\");\nconst windowcontrols = document.querySelector<HTMLElement>(\"#windowcontrols\");\nconst connectbutton = document.querySelector<HTMLButtonElement>(\"#connect\");\nconst sessionbutton = document.querySelector<HTMLButtonElement>(\"#session\");\nconst pausebutton = document.querySelector<HTMLButtonElement>(\"#pause\");\nconst stopbutton = document.querySelector<HTMLButtonElement>(\"#stop\");\nconst openbutton = document.querySelector<HTMLButtonElement>(\"#openpanel\");\n\nfunction status(message: string, error = false): void { if (statusnode) { statusnode.textContent = message; statusnode.dataset.state = error ? \"error\" : \"ready\"; } }\nasync function request(message: unknown): Promise<unknown> { const response = await chrome.runtime.sendMessage(message) as { ok: boolean; value?: unknown; error?: string }; if (!response.ok) throw new Error(response.error); return response.value; }\n\nfunction pauselabel(paused: boolean): string { return paused ? \"Resume session\" : \"Pause session\"; }\n\n/** Renders the live optional capability set the user has granted. */\nfunction rendercapabilities(report?: capabilityreport): void {\n if (!capabilitiesnode) return;\n if (!report) { capabilitiesnode.textContent = \"Capabilities unknown.\"; return; }\n const granted = [`tabs ${report.tabs ? \"granted\" : \"absent\"}`, `downloads ${report.downloads ? \"granted\" : \"absent\"}`, `clipboard read ${report.clipboardread ? \"granted\" : \"absent\"}`, `clipboard write ${report.clipboardwrite ? \"granted\" : \"absent\"}`];\n capabilitiesnode.textContent = `Optional capabilities: ${granted.join(\" \u00B7 \")}.`;\n}\n\n/** Renders the currently held keys and the dialogs answered by the reviewed policy. */\nfunction renderlivestate(holds?: keyholdstate[], dialogs?: dialogdecision[], signals?: pagesignallike): void {\n if (!livenode) return;\n const held = (holds ?? []).filter(hold => hold.releasedat === undefined);\n const heldsummary = held.length > 0 ? held.map(hold => `${hold.key}${hold.holdid ? ` (${hold.holdid})` : \"\"}`).join(\", \") : \"none\";\n const dialogsummary = (dialogs ?? []).length > 0 ? `${dialogs?.length} answered` : \"none\";\n const pages = signals && (signals.language !== undefined || signals.template !== undefined || signals.scrolllocked !== undefined)\n ? [signals.language !== undefined ? `language ${signals.language || \"unknown\"}` : \"\", signals.template !== undefined ? `template ${signals.template}` : \"\", signals.scrolllocked !== undefined ? `scroll lock ${signals.scrolllocked ? \"on\" : \"off\"}` : \"\"].filter(Boolean).join(\" \u00B7 \")\n : \"unknown\";\n livenode.textContent = `Held keys: ${heldsummary} \u00B7 Open dialogs answered: ${dialogsummary} \u00B7 Page signals: ${pages}.`;\n}\n\n/** Renders the load phase, offline state, active wait profile and queued navigation targets of the session tab. */\nfunction rendernavstate(state?: navstate, offline?: boolean, profile?: waitprofilerecord, queues?: navqueues): void {\n if (!navstatenode) return;\n const phase = state?.phase ? `load phase ${state.phase}` : \"load phase unknown\";\n const offlinestate = offline === undefined ? \"connectivity unknown\" : offline ? \"offline\" : \"online\";\n const wait = profile ? `wait profile ${profile.profile.signals.join(\"+\")}` : \"no wait profile\";\n const queued = queues ? `queued targets ${queues.prefetch + queues.batchopen} (${queues.prefetch} prefetch \u00B7 ${queues.batchopen} batch open)` : \"no queued targets\";\n navstatenode.textContent = `Navigation: ${phase} \u00B7 ${offlinestate} \u00B7 ${wait} \u00B7 ${queued}.`;\n}\n\n/** Renders the current window layout and the quick window state buttons. */\nfunction renderwindowlayout(windows?: windowshape[], badges?: tabbadge[]): void {\n if (!windowlayoutnode || !windowcontrols) return;\n windowlayoutnode.textContent = windows === undefined\n ? \"Window layout: unknown.\"\n : `Window layout: ${windows.length} window${windows.length === 1 ? \"\" : \"s\"}${windows.length > 0 ? ` (${windows.map(item => `${item.windowid}: ${item.state}${item.incognito ? \" incognito\" : \"\"}`).join(\" \u00B7 \")})` : \"\"} \u00B7 ${new Set((badges ?? []).map(badge => badge.tabid)).size} tab${new Set((badges ?? []).map(badge => badge.tabid)).size === 1 ? \"\" : \"s\"} with active tasks.`;\n windowcontrols.replaceChildren();\n for (const item of (windows ?? []).slice(0, 4)) {\n for (const state of [\"maximized\", \"minimized\", \"normal\"] as const) {\n const quick = document.createElement(\"button\");\n quick.type = \"button\";\n quick.className = \"secondary\";\n quick.textContent = `${state === \"normal\" ? \"restore\" : state} ${item.windowid}`;\n quick.addEventListener(\"click\", () => request({ kind: \"windowstate\", windowid: item.windowid, state }).then(() => { status(`Window ${item.windowid} set to ${state}.`); return restore(); }).catch(error => status(error instanceof Error ? error.message : String(error), true)));\n windowcontrols.append(quick);\n }\n }\n}\n\n/** Renders the captcha handoff state while a plan waits for the user. */\nfunction rendercaptchastate(captchas?: captchahandoff[]): void {\n if (!captchastatenode) return;\n const open = (captchas ?? []).find(handoff => !handoff.resolved);\n if (open) { captchastatenode.dataset.state = \"error\"; captchastatenode.textContent = `Captcha handoff open on ${open.origin}; the plan waits until you resolve it in the review panel.`; return; }\n delete captchastatenode.dataset.state;\n captchastatenode.textContent = \"Captcha handoff: none.\";\n}\n\n/** Renders the active dataset row count and the stream state of the session. */\nfunction renderdatasetstate(datasets?: dataset[], streams?: streamstate[]): void {\n if (!datasetstatenode) return;\n const rows = (datasets ?? []).reduce((total, datasetvalue) => total + datasetvalue.rows.length, 0);\n const count = datasets?.length ?? 0;\n const stream = (streams ?? []).find(item => item.done !== true) ?? (streams ?? [])[0];\n const streamsummary = stream ? ` \u00B7 stream ${stream.name} chunk ${stream.chunk} of ${stream.chunks}${stream.done ? \" complete\" : \"\"}` : \"\";\n datasetstatenode.textContent = count === 0 ? \"Datasets: none collected yet.\" : `Datasets: ${count} collected (${rows} row${rows === 1 ? \"\" : \"s\"})${streamsummary}.`;\n}\n\n/** Renders the live batch download states and the quarantined files awaiting scan verdicts. */\nfunction renderdownloadstate(downloads?: downloadrecord[], quarantines?: quarantineentry[]): void {\n if (!downloadstatenode) return;\n const states = (downloads ?? []).filter(record => record.state === \"running\" || record.state === \"queued\" || record.state === \"paused\");\n const completed = (downloads ?? []).filter(record => record.state === \"complete\").length;\n const failed = (downloads ?? []).filter(record => record.state === \"failed\").length;\n const pending = (quarantines ?? []).filter(entry => entry.scan === \"pending\").length;\n const parts = [\n states.length > 0 ? `${states.length} in flight (${states.map(record => record.state).join(\" \u00B7 \")})` : \"\",\n completed > 0 ? `${completed} completed` : \"\",\n failed > 0 ? `${failed} failed` : \"\",\n pending > 0 ? `${pending} quarantined awaiting scan` : \"\",\n ].filter(Boolean);\n downloadstatenode.textContent = (downloads ?? []).length === 0 && pending === 0 ? \"Downloads: none.\" : `Downloads: ${parts.join(\" \u00B7 \") || \"none active\"}.`;\n}\n\n/** Renders the clipboard consent state while a read waits for the user approval. */\nfunction renderclipboardstate(consents?: clipboardconsentrecord[]): void {\n if (!clipboardstatenode) return;\n const waiting = (consents ?? []).filter(record => record.approved === undefined);\n if (waiting.length > 0) {\n clipboardstatenode.dataset.state = \"error\";\n clipboardstatenode.textContent = `Clipboard consent: ${waiting.length} read prompt${waiting.length === 1 ? \"\" : \"s\"} waiting for your approval in the review panel.`;\n return;\n }\n delete clipboardstatenode.dataset.state;\n clipboardstatenode.textContent = \"Clipboard consent: none waiting.\";\n}\n\n/** Renders the capture count of the active run beside the capture policy. */\nfunction rendercapturestate(captures?: Array<{ id: string }>, policy?: string): void {\n if (!capturestatenode) return;\n const count = captures?.length ?? 0;\n const mode = policy ?? \"manual\";\n capturestatenode.textContent = count === 0 ? `Captures: none (policy ${mode}).` : `Captures: ${count} of the run (policy ${mode}).`;\n}\n\n/** Renders the observed request count of the active run beside the live channel and stream state. */\nfunction rendernetstate(exchanges?: Array<{ id: string }>, channels?: Array<{ id: string; state: string }>): void {\n if (!netstatenode) return;\n const count = exchanges?.length ?? 0;\n const live = (channels ?? []).filter(channel => channel.state === \"open\" || channel.state === \"connecting\").length;\n netstatenode.textContent = `Observed requests: ${count} of the run${live > 0 ? ` \u00B7 ${live} live channel${live === 1 ? \"\" : \"s\"}` : \"\"}.`;\n}\n\n/** Renders the error count captured in the active run beside pending console capture consent prompts, the debugger attached badge of devtools sessions and the profiling badge of active profile or trace instruments. */\nfunction renderdebugstate(timeline?: { errors?: Array<{ id: string }>; rejections?: Array<{ id: string }> }, consoleconsents?: consoleconsentrecord[], debuggergrants?: Array<{ approved?: boolean; revokedat?: number }>, cdpattached?: number, profileactive?: number, sourcemapconsents?: Array<{ approved?: boolean; revokedat?: number }>): void {\n if (!debugstatenode) return;\n const errors = timeline?.errors?.length ?? 0;\n const rejections = timeline?.rejections?.length ?? 0;\n const waiting = (consoleconsents ?? []).filter(consent => consent.approved === undefined).length;\n const debuggerwaiting = (debuggergrants ?? []).filter(grant => grant.approved === undefined && grant.revokedat === undefined).length;\n const attached = cdpattached ?? 0;\n const profiling = profileactive ?? 0;\n const sourcemapwaiting = (sourcemapconsents ?? []).filter(consent => consent.approved === undefined && consent.revokedat === undefined).length;\n debugstatenode.textContent = errors === 0 && rejections === 0 && waiting === 0 && debuggerwaiting === 0 && attached === 0 && profiling === 0 && sourcemapwaiting === 0\n ? \"Timeline: no errors captured.\"\n : `Timeline: ${errors} error${errors === 1 ? \"\" : \"s\"}${rejections > 0 ? ` \u00B7 ${rejections} rejection${rejections === 1 ? \"\" : \"s\"}` : \"\"} captured in the active run${waiting > 0 ? ` \u00B7 ${waiting} console consent prompt${waiting === 1 ? \"\" : \"s\"} waiting` : \"\"}${debuggerwaiting > 0 ? ` \u00B7 ${debuggerwaiting} debugger consent prompt${debuggerwaiting === 1 ? \"\" : \"s\"} waiting` : \"\"}${attached > 0 ? ` \u00B7 debugger attached on ${attached} session${attached === 1 ? \"\" : \"s\"}` : \"\"}${profiling > 0 ? ` \u00B7 profiling active on ${profiling} instrument${profiling === 1 ? \"\" : \"s\"}` : \"\"}${sourcemapwaiting > 0 ? ` \u00B7 ${sourcemapwaiting} source map prompt${sourcemapwaiting === 1 ? \"\" : \"s\"} waiting` : \"\"}.`;\n if (errors > 0 || waiting > 0 || debuggerwaiting > 0 || sourcemapwaiting > 0) debugstatenode.dataset.state = \"error\";\n else delete debugstatenode.dataset.state;\n if (attached > 0) debugstatenode.dataset.debugger = \"attached\";\n else delete debugstatenode.dataset.debugger;\n if (profiling > 0) debugstatenode.dataset.profile = \"active\";\n else delete debugstatenode.dataset.profile;\n}\n\n/** Renders the active traffic rule count badge of controlled runs. */\nfunction renderrulestate(activerules?: number, blocked?: number, mocked?: number): void {\n if (!rulestatenode) return;\n const rules = activerules ?? 0;\n rulestatenode.textContent = rules === 0 ? \"Traffic rules: none active.\" : `Traffic rules: ${rules} active of the run${blocked !== undefined && blocked > 0 ? ` \u00B7 ${blocked} blocked request${blocked === 1 ? \"\" : \"s\"}` : \"\"}${mocked !== undefined && mocked > 0 ? ` \u00B7 ${mocked} mocked response${mocked === 1 ? \"\" : \"s\"}` : \"\"}.`;\n if (rules > 0) rulestatenode.dataset.state = \"controlled\";\n else delete rulestatenode.dataset.state;\n}\n\n/** Renders the media state badge: the recording indicator of active recordings beside the stored media record count. */\nfunction rendercallstate(calls?: Array<{ id: string }>, fetchconsents?: Array<{ approved?: boolean }>): void {\n if (!callstatenode) return;\n const count = calls?.length ?? 0;\n const waiting = (fetchconsents ?? []).filter(consent => consent.approved === undefined).length;\n callstatenode.textContent = `Outbound calls: ${count} of the run${waiting > 0 ? ` \u00B7 ${waiting} header consent prompt${waiting === 1 ? \"\" : \"s\"} waiting` : \"\"}.`;\n}\n\nfunction rendermediastate(media?: Array<{ id: string }>, recordingactive?: Array<{ id: string; kind: string }>, recordingconsents?: Array<{ approved?: boolean }>): void {\n if (!mediastatenode) return;\n const active = (recordingactive ?? []).length;\n const waiting = (recordingconsents ?? []).filter(record => record.approved === undefined).length;\n const count = media?.length ?? 0;\n const recording = active > 0 ? `\u25CF recording ${active} active capture${active === 1 ? \"\" : \"s\"}` : waiting > 0 ? `recording consent: ${waiting} prompt${waiting === 1 ? \"\" : \"s\"} waiting` : \"\";\n mediastatenode.textContent = `Media: ${count} record${count === 1 ? \"\" : \"s\"}${recording ? ` \u00B7 ${recording}` : \"\"}.`;\n if (active > 0) mediastatenode.dataset.state = \"recording\";\n else delete mediastatenode.dataset.state;\n}\n\n/** Renders the active emulation badge of the run: the layer names and the pending location consent prompts. */\nfunction renderemulationstate(layers?: string[], locationconsents?: Array<{ approved?: boolean }>): void {\n if (!emulationstatenode) return;\n const active = layers ?? [];\n const waiting = (locationconsents ?? []).filter(consent => consent.approved === undefined).length;\n emulationstatenode.textContent = `Emulation: ${active.length > 0 ? `${active.length} active layer${active.length === 1 ? \"\" : \"s\"} (${active.join(\", \")})` : \"no active layer\"}${waiting > 0 ? ` \u00B7 ${waiting} location consent prompt${waiting === 1 ? \"\" : \"s\"} waiting` : \"\"}.`;\n if (active.length > 0) emulationstatenode.dataset.emulated = \"active\";\n else delete emulationstatenode.dataset.emulated;\n if (waiting > 0) emulationstatenode.dataset.state = \"error\";\n else delete emulationstatenode.dataset.state;\n}\n\n/** Renders the saved session count and the auto snapshot state with its interval and count. */\nfunction rendersessionstate(memory?: { records: Array<{ id: string; name: string }>; auto?: { period: number; maxsnapshots: number; expiry: number } }, auto?: { interval: { period: number; maxsnapshots: number; expiry: number }; count: number }): void {\n if (!sessionstatenode) return;\n const saved = memory?.records.length ?? 0;\n const interval = auto?.interval ?? memory?.auto;\n sessionstatenode.textContent = `Sessions: ${saved} saved${interval !== undefined ? ` \u00B7 auto snapshot every ${interval.period} ms (${auto?.count ?? 0} of ${interval.maxsnapshots})` : \" \u00B7 no auto interval\"}.`;\n if (interval !== undefined) sessionstatenode.dataset.auto = \"active\";\n else delete sessionstatenode.dataset.auto;\n}\n\n/** Renders the active workflow state with pause and cancel buttons, the count of workflows running in the background and the active loop iteration count of the running runs. */\nfunction renderworkflowstate(runs?: Array<{ id: string; state: string; cursor: number; workflowid: string; dryrun?: boolean }>, control?: Array<{ runid: string; kind: string; loops?: Array<{ path: string }> }>): void {\n if (!workflowstatenode) return;\n const all = runs ?? [];\n const running = all.filter(run => run.state === \"running\");\n const paused = all.filter(run => run.state === \"paused\");\n const iterations = (control ?? []).filter(decision => decision.kind === \"loop\" && running.some(run => run.id === decision.runid)).reduce((total, decision) => total + (decision.loops?.length ?? 0), 0);\n workflowstatenode.textContent = `Workflows: ${running.length} running in the background${paused.length > 0 ? ` \u00B7 ${paused.length} paused` : \"\"}${running.length > 0 && iterations > 0 ? ` \u00B7 loop iteration ${iterations}` : \"\"}${all.length > 0 ? ` \u00B7 latest ${all[0]?.state ?? \"\"}` : \" \u00B7 none composed yet\"}.`;\n if (running.length > 0) workflowstatenode.dataset.state = \"active\";\n else delete workflowstatenode.dataset.state;\n if (!workflowcontrols) return;\n workflowcontrols.replaceChildren();\n for (const run of [...running, ...paused]) {\n const controls = document.createElement(\"span\");\n const pause = document.createElement(\"button\");\n pause.className = \"secondary\";\n pause.textContent = run.state === \"running\" ? `Pause ${run.id.slice(0, 6)}` : `Resume ${run.id.slice(0, 6)}`;\n pause.disabled = false;\n pause.addEventListener(\"click\", async () => {\n try {\n await request({ kind: run.state === \"running\" ? \"pauseworkflowrun\" : \"resumeworkflowrun\", runid: run.id });\n status(run.state === \"running\" ? \"Workflow run paused at its checkpoint.\" : \"Workflow run resumed from its checkpoint.\");\n await restore();\n } catch (error) { status(error instanceof Error ? error.message : String(error), true); }\n });\n const cancel = document.createElement(\"button\");\n cancel.className = \"danger\";\n cancel.textContent = `Cancel ${run.id.slice(0, 6)}`;\n cancel.addEventListener(\"click\", async () => {\n try {\n await request({ kind: \"cancelworkflowrun\", runid: run.id, reason: \"popup cancel\" });\n status(\"Workflow run cancelled with its reason recorded.\");\n await restore();\n } catch (error) { status(error instanceof Error ? error.message : String(error), true); }\n });\n controls.append(pause, \" \", cancel);\n workflowcontrols.append(controls);\n }\n}\n\n/** Renders the trigger layer: the armed rule count, the queued fires held while the session is paused and the button rule status of the toolbar click. */\n/** Renders the mcp server running indicator of the agent protocol: the running state, the localhost bind, the connected clients, the paired count, the pending approval gates as notifications and the long tool progress of the in flight calls that announces itself while the panel stays hidden. */\n/** Renders the active model per running task kind: every routed task kind shows its provider and model so the user reads which model drives which task. */\nfunction rendermodelstate(state?: { providers: Array<{ id: string; name: string; status: string }>; tasks: Array<{ kind: string; provider?: string; model?: string }>; usage: { totaltokens: number; cost: number; calls: number } }): void {\n if (!modelstatenode) return;\n if (!state) { modelstatenode.textContent = \"Models: none connected.\"; return; }\n const routed = state.tasks.filter(task => task.provider !== undefined);\n modelstatenode.textContent = `Models: ${routed.length > 0 ? routed.slice(0, 3).map(task => `${task.kind} ${task.model}`).join(\" \u00B7 \") : \"no route yet\"} \u00B7 ${state.usage.calls} call${state.usage.calls === 1 ? \"\" : \"s\"} \u00B7 ${state.usage.totaltokens} tokens.`;\n}\n\n/** Renders the swarm summary: the popup reads the agent count with the active, paused and stopped split, the claimed tasks of the shared queue, the elected leader and the held lock count, so the user reads the swarm at a glance. */\n/** Renders the execution environment status of the running session: the environments of the open run, the keepalive heartbeat indicator, the offscreen capability state and the zombie warning with its reap action. */\nfunction renderenvironmentstate(view?: { report: { environments: Array<{ stepid: string; environment: string }>; workers: number; keepalive?: { runid: string; state: string; beats: number; lastbeatat: number; portopen: boolean } }; offscreengranted: boolean; parseoffload: boolean; zombies: string[]; grants?: string[] }): void {\n if (!environmentstatenode) return;\n if (!view) { environmentstatenode.textContent = \"Environments: no run state.\"; return; }\n const counted = new Map<string, number>();\n for (const entry of view.report.environments) counted.set(entry.environment, (counted.get(entry.environment) ?? 0) + 1);\n const breakdown = [...counted.entries()].map(([environment, count]) => `${environment} ${count}`).join(\" \u00B7 \");\n const keepalive = view.report.keepalive;\n environmentstatenode.textContent = `Environments: ${breakdown.length > 0 ? breakdown : \"no step executed\"} \u00B7 offscreen ${view.offscreengranted ? \"granted\" : \"absent\"} \u00B7 offload ${view.parseoffload ? \"on\" : \"off\"}${view.report.workers > 0 ? ` \u00B7 workers ${view.report.workers}` : \"\"} \u00B7 keepalive ${keepalive ? `${keepalive.state === \"active\" && keepalive.portopen ? \"\u2665\" : \"closed\"} ${keepalive.beats} beat${keepalive.beats === 1 ? \"\" : \"s\"}` : \"none\"}${view.zombies.length > 0 ? ` \u00B7 zombie runs ${view.zombies.join(\", \")}` : \"\"}${view.grants !== undefined ? ` \u00B7 grants ${view.grants.length > 0 ? view.grants.join(\", \") : \"default\"}` : \"\"}.`;\n if (keepalive?.state === \"active\" && keepalive.portopen) environmentstatenode.dataset.state = \"active\";\n else delete environmentstatenode.dataset.state;\n if (environmentcontrols) {\n environmentcontrols.replaceChildren();\n if (view.zombies.length > 0) {\n const reap = document.createElement(\"button\");\n reap.className = \"secondary\";\n reap.textContent = \"Reap zombie runs\";\n reap.addEventListener(\"click\", () => { void request({ kind: \"runstate\", reap: true }).then(() => restore()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });\n environmentcontrols.append(reap);\n }\n if (!view.offscreengranted) {\n const grant = document.createElement(\"button\");\n grant.className = \"secondary\";\n grant.textContent = \"Request offscreen grant\";\n grant.addEventListener(\"click\", () => { void request({ kind: \"environments\", requestcapability: true }).then(() => restore()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });\n environmentcontrols.append(grant);\n }\n }\n}\n\n/** Renders the security posture of the active tab: the origin allowlist state, the originprofile summary, the consent window state, the denydefault notice and the safedefaults notice of an unprofiled origin that links to the transparencypage originprofile editor, with the allowlist grant request for a new origin. */\nfunction rendersecuritystate(view?: { posture: string; allowlist: Array<{ origin: string; profileid: string }>; profiles: Array<{ origin: string; grants: string[]; denials: string[] }>; windows: Array<{ origin: string; state: string; expiresat: number }>; sessionorigin?: string; chain: Array<{ runid: string; valid: boolean; entries: number; sealhash?: string }> }, activeorigin?: string): void {\n if (!securitystatenode) return;\n if (!view) { securitystatenode.textContent = \"Security: state unknown.\"; return; }\n const origin = activeorigin ?? view.sessionorigin ?? \"the active tab\";\n const granted = view.allowlist.some(entry => entry.origin === origin);\n const profile = view.profiles.find(candidate => candidate.origin === origin);\n const windowstate = view.windows.find(candidate => candidate.origin === origin && candidate.state === \"active\");\n const verified = view.chain.filter(chain => chain.valid).length;\n securitystatenode.textContent = `Security: ${origin} ${granted ? \"allowlisted\" : \"ungranted (denydefault)\"}${profile !== undefined ? ` \u00B7 profile ${profile.grants.length} granted / ${profile.denials.length} denied` : \" \u00B7 no origin profile\"}${windowstate !== undefined ? ` \u00B7 consent window until ${new Date(windowstate.expiresat).toLocaleTimeString()}` : \" \u00B7 no active consent window\"} \u00B7 ${verified}/${view.chain.length} log chain${view.chain.length === 1 ? \"\" : \"s\"} verified.`;\n if (granted) delete securitystatenode.dataset.state;\n else securitystatenode.dataset.state = \"error\";\n if (securitycontrols) {\n securitycontrols.replaceChildren();\n if (!granted) {\n const grant = document.createElement(\"button\");\n grant.className = \"secondary\";\n grant.textContent = `Grant ${origin} allowlist entry`;\n grant.addEventListener(\"click\", () => { void request({ kind: \"security\", allowlist: { add: { origin } } }).then(() => restore()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });\n securitycontrols.append(grant);\n }\n if (profile === undefined && origin !== \"the active tab\") {\n const safedefaults = document.createElement(\"button\");\n safedefaults.className = \"secondary\";\n safedefaults.textContent = `Safedefaults on ${origin}: reads only \u2014 open the originprofile editor`;\n safedefaults.addEventListener(\"click\", () => { try { chrome.runtime.openOptionsPage(() => { void chrome.runtime.lastError; }); } catch { status(\"The transparencypage did not open; open it from the extensions page.\", true); } });\n securitycontrols.append(safedefaults);\n }\n if (view.chain.length > 0) {\n const seal = view.chain.find(chain => chain.sealhash !== undefined);\n if (seal?.sealhash !== undefined) {\n const sealinfo = document.createElement(\"span\");\n sealinfo.className = \"muted\";\n sealinfo.textContent = `Seal ${seal.sealhash.slice(0, 10)}\u2026`;\n securitycontrols.append(sealinfo);\n }\n }\n }\n}\n\nfunction renderswarmstate(state?: { overview: { agents: number; active: number; paused: number; stopped: number; claimed: number; queued: number }; killswitch: { engaged: boolean }; topology?: { leaderid: string }; locks?: Array<{ key: string }> }): void {\n if (!swarmstatenode) return;\n if (!state) { swarmstatenode.textContent = \"Swarm: no agents.\"; return; }\n swarmstatenode.textContent = `Swarm: ${state.overview.agents} agent${state.overview.agents === 1 ? \"\" : \"s\"}${state.overview.agents > 0 ? ` (${state.overview.active} active${state.overview.paused > 0 ? `, ${state.overview.paused} paused` : \"\"}${state.overview.stopped > 0 ? `, ${state.overview.stopped} stopped` : \"\"})` : \"\"} \u00B7 ${state.overview.claimed} active task${state.overview.claimed === 1 ? \"\" : \"s\"}${state.overview.queued > 0 ? ` \u00B7 ${state.overview.queued} queued` : \"\"}${state.topology !== undefined ? ` \u00B7 leader ${state.topology.leaderid}` : \"\"}${state.locks !== undefined && state.locks.length > 0 ? ` \u00B7 ${state.locks.length} lock${state.locks.length === 1 ? \"\" : \"s\"}` : \"\"}${state.killswitch.engaged ? \" \u00B7 killswitch engaged\" : \"\"}.`;\n if (state.overview.active > 0 || state.killswitch.engaged) swarmstatenode.dataset.state = \"active\";\n else delete swarmstatenode.dataset.state;\n}\n\nfunction rendermcpstate(state?: { state: string; bind?: string; port: number; localhost: boolean; clients?: Array<{ id: string; paired: boolean }>; approvals?: Array<{ id: string; tool: string; state: string }>; inflight?: Array<{ callid: string; tool: string; chunks: number }>; progressnotices?: Array<{ callid: string; percent?: number; message: string; cancellable?: boolean; at: number }> }): void {\n if (!mcpstatenode) return;\n const running = state?.state === \"running\";\n const clients = state?.clients ?? [];\n const paired = clients.filter(client => client.paired).length;\n const pending = (state?.approvals ?? []).filter(approval => approval.state === \"pending\");\n const inflight = state?.inflight ?? [];\n const notice = (state?.progressnotices ?? [])[0];\n mcpstatenode.textContent = `Mcp server: ${running ? `running on ${state?.bind ?? \"127.0.0.1\"}:${state?.port ?? 7436}${state?.localhost === false ? \" (remote bind)\" : \"\"} \u00B7 ${clients.length} client${clients.length === 1 ? \"\" : \"s\"}${paired !== clients.length ? ` \u00B7 ${paired} paired` : \"\"}${pending.length > 0 ? ` \u00B7 ${pending.length} approval gate${pending.length === 1 ? \"\" : \"s\"} waiting (${[...new Set(pending.map(approval => approval.tool))].slice(0, 3).join(\", \")})` : \"\"}${inflight.length > 0 ? ` \u00B7 ${inflight.length} call${inflight.length === 1 ? \"\" : \"s\"} in flight${notice !== undefined ? ` (${notice.percent !== undefined ? `${notice.percent}%` : notice.message}${notice.cancellable === false ? \"\" : \", cancellable in the panel\"})` : \"\"}` : \"\"}` : state?.state ?? \"stopped\"}.`;\n if (running) mcpstatenode.dataset.state = \"active\";\n else delete mcpstatenode.dataset.state;\n}\n\nfunction rendertriggerstate(rules?: Array<{ id: string; kind: string; enabled: boolean; paused?: boolean; nextfireat?: number }>, queued?: number, paused?: boolean, buttonfired?: boolean): void {\n if (!triggerstatenode) return;\n const armed = rules ?? [];\n const enabled = armed.filter(rule => rule.enabled && rule.paused !== true);\n const scheduled = enabled.filter(rule => rule.nextfireat !== undefined);\n const buttons = enabled.filter(rule => rule.kind === \"button\");\n triggerstatenode.textContent = `Triggers: ${armed.length} armed rule${armed.length === 1 ? \"\" : \"s\"}${enabled.length !== armed.length ? ` \u00B7 ${enabled.length} enabled` : \"\"}${scheduled.length > 0 ? ` \u00B7 ${scheduled.length} scheduled` : \"\"}${(queued ?? 0) > 0 ? ` \u00B7 ${queued} fire${queued === 1 ? \"\" : \"s\"} ${paused ? \"held while the session is paused\" : \"queued\"}` : \"\"}${buttons.length > 0 ? ` \u00B7 toolbar button ${buttonfired ? \"fired\" : \"ready\"}` : \"\"}.`;\n if (enabled.length > 0) triggerstatenode.dataset.state = \"active\";\n else delete triggerstatenode.dataset.state;\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; origin?: string }; capabilities?: capabilityreport; holds?: { heldkeys: keyholdstate[] }; dialogs?: dialogdecision[]; signals?: pagesignallike; navstate?: navstate; offline?: boolean; waitprofile?: waitprofilerecord; navqueues?: navqueues; windows?: windowshape[]; badges?: tabbadge[]; captchas?: captchahandoff[]; datasets?: dataset[]; streams?: streamstate[]; downloads?: downloadrecord[]; clipconsents?: clipboardconsentrecord[]; quarantines?: quarantineentry[]; captures?: Array<{ id: string }>; capturepolicy?: string; media?: Array<{ id: string }>; recordingactive?: Array<{ id: string; kind: string }>; recordingconsents?: Array<{ approved?: boolean }>; calls?: Array<{ id: string }>; fetchconsents?: Array<{ approved?: boolean }>; exchanges?: Array<{ id: string }>; channels?: Array<{ id: string; state: string }>; activerules?: number; timeline?: { errors?: Array<{ id: string }>; rejections?: Array<{ id: string }> }; consoleconsents?: consoleconsentrecord[]; debuggergrants?: Array<{ approved?: boolean; revokedat?: number }>; cdpattached?: number; profileactive?: number; profile?: { consents?: Array<{ approved?: boolean; revokedat?: number }> }; emulatedlayers?: string[]; emulation?: { consents: Array<{ approved?: boolean }> }; traffic?: { blocks: Array<{ hits: number; revertedat?: number }>; mocks: Array<{ hits: number; revertedat?: number }> }; sessionmemory?: { records: Array<{ id: string; name: string }>; auto?: { period: number; maxsnapshots: number; expiry: number } }; autosnapshotstate?: { interval: { period: number; maxsnapshots: number; expiry: number }; count: number }; workflow?: { runs: Array<{ id: string; state: string; cursor: number; workflowid: string; dryrun?: boolean }>; control?: Array<{ runid: string; kind: string; loops?: Array<{ path: string }> }> }; trigger?: { rules: Array<{ id: string; kind: string; enabled: boolean; paused?: boolean; nextfireat?: number }>; queued: number }; mcp?: { state: string; bind?: string; port: number; localhost: boolean; clients: Array<{ id: string; paired: boolean }>; approvals: Array<{ id: string; tool: string; state: string }>; inflight?: Array<{ callid: string; tool: string; chunks: number }>; progressnotices?: Array<{ callid: string; percent?: number; message: string; cancellable?: boolean; at: number }> }; llm?: { providers: Array<{ id: string; name: string; status: string }>; tasks: Array<{ kind: string; provider?: string; model?: string }>; usage: { totaltokens: number; cost: number; calls: number } }; swarm?: { overview: { agents: number; active: number; paused: number; stopped: number; claimed: number; queued: number }; killswitch: { engaged: boolean }; topology?: { leaderid: string }; locks?: Array<{ key: string }> }; environments?: { report: { environments: Array<{ stepid: string; environment: string }>; workers: number; keepalive?: { runid: string; state: string; beats: number; lastbeatat: number; portopen: boolean } }; offscreengranted: boolean; parseoffload: boolean; zombies: string[]; grants?: string[] }; security?: { posture: string; allowlist: Array<{ origin: string; profileid: string }>; profiles: Array<{ origin: string; grants: string[]; denials: string[] }>; windows: Array<{ origin: string; state: string; expiresat: number }>; sessionorigin?: string; chain: Array<{ runid: string; valid: boolean; entries: number; sealhash?: string }> }; sessionview?: { grid: Array<{ sessionid: string; runid: string; origins: string[]; state: string; outcome: string; steps: number; completed: number; lock: string; tabid?: number; sealhash?: string; updatedat: number; actions: string[] }>; }; };\n if (endpointinput && context.config) endpointinput.value = context.config.endpoint;\n rendercapabilities(context.capabilities);\n renderlivestate(context.holds?.heldkeys, context.dialogs, context.signals);\n rendernavstate(context.navstate, context.offline, context.waitprofile, context.navqueues);\n renderwindowlayout(context.windows, context.badges);\n rendercaptchastate(context.captchas);\n renderdatasetstate(context.datasets, context.streams);\n renderdownloadstate(context.downloads, context.quarantines);\n renderclipboardstate(context.clipconsents);\n rendercapturestate(context.captures, context.capturepolicy);\n rendermediastate(context.media, context.recordingactive, context.recordingconsents);\n rendercallstate(context.calls, context.fetchconsents);\n rendernetstate(context.exchanges, context.channels);\n renderdebugstate(context.timeline, context.consoleconsents, context.debuggergrants, context.cdpattached, context.profileactive, context.profile?.consents);\n renderemulationstate(context.emulatedlayers, context.emulation?.consents);\n rendersessionstate(context.sessionmemory, context.autosnapshotstate);\n renderworkflowstate(context.workflow?.runs, context.workflow?.control);\n rendertriggerstate(context.trigger?.rules, context.trigger?.queued, context.session?.pausedat !== undefined, triggerbuttonfired);\n rendermcpstate(context.mcp);\n rendermodelstate(context.llm);\n renderswarmstate(context.swarm);\n renderenvironmentstate(context.environments);\n rendersecuritystate(context.security, context.session?.origin);\n rendersessiongridstate(context.sessionview?.grid);\n renderrulestate(context.activerules, (context.traffic?.blocks ?? []).filter(rule => rule.revertedat === undefined).reduce((total, rule) => total + rule.hits, 0), (context.traffic?.mocks ?? []).filter(spec => spec.revertedat === undefined).reduce((total, spec) => total + spec.hits, 0));\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\n/** The popup open is the toolbar button click of a default popup: an armed button rule evaluates behind the gates once per popup open. */\nlet triggerbuttonfired = false;\nvoid (async () => {\n try {\n const result = await request({ kind: \"buttontrigger\" }) as { fired: boolean; reason?: string };\n if (result.fired) { triggerbuttonfired = true; status(\"The toolbar button rule fired behind the consent gates.\"); }\n } catch { /* no armed button rule or the gates refused the fire; the panel shows the reason */ }\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\n/** Renders the compact sessiongrid of recent sessions: every live and saved run in one row with its state, outcome and lock, and a deep link that opens the sidepanel run view. */\nfunction rendersessiongridstate(grid?: Array<{ sessionid: string; runid: string; origins: string[]; state: string; outcome: string; steps: number; completed: number; lock: string; tabid?: number; sealhash?: string; updatedat: number; actions: string[] }>): void {\n if (!sessiongridstatenode) return;\n if (!grid || grid.length === 0) { sessiongridstatenode.textContent = \"Session grid: no run yet.\"; if (sessiongridcontrols) sessiongridcontrols.replaceChildren(); return; }\n const recent = grid.slice(0, 3);\n sessiongridstatenode.textContent = `Session grid: ${grid.length} run${grid.length === 1 ? \"\" : \"s\"} \u2014 ${recent.map(row => `${row.state} ${row.runid.slice(0, 8)} ${row.completed}/${row.steps} ${row.lock}`).join(\" \u00B7 \")}.`;\n if (!sessiongridcontrols) return;\n sessiongridcontrols.replaceChildren();\n for (const row of recent) {\n const deep = document.createElement(\"button\");\n deep.type = \"button\";\n deep.className = \"secondary\";\n deep.textContent = `Open ${row.state} run ${row.runid.slice(0, 8)}`;\n deep.addEventListener(\"click\", () => request({ kind: \"sessions\", grid: { open: { runid: row.runid } } }).then(() => { status(`The session grid row of the run ${row.runid} opened in the sidepanel run view.`); return restore(); }).catch(error => status(error instanceof Error ? error.message : String(error), true)));\n sessiongridcontrols.append(deep);\n }\n}\n\n/** Command surface of the 1.1.64 family: the popup carries the task input, the quick run actions, the command palette with its keyboard shortcut, the onboarding walkthrough and the live broadcast refresh. */\nconst taskinputnode = document.querySelector<HTMLTextAreaElement>(\"#taskinput\");\nconst tasksubmitbutton = document.querySelector<HTMLButtonElement>(\"#tasksubmit\");\nconst proposalstatusnode = document.querySelector<HTMLElement>(\"#proposalstatus\");\nconst runquickbutton = document.querySelector<HTMLButtonElement>(\"#runquick\");\nconst cancelquickbutton = document.querySelector<HTMLButtonElement>(\"#cancelquick\");\nconst opendashboardbutton = document.querySelector<HTMLButtonElement>(\"#opendashboard\");\nconst openoptionsbutton = document.querySelector<HTMLButtonElement>(\"#openoptions\");\nconst paletteinput = document.querySelector<HTMLInputElement>(\"#paletteinput\");\nconst paletteopenbutton = document.querySelector<HTMLButtonElement>(\"#paletteopen\");\nconst palettematchesnode = document.querySelector<HTMLElement>(\"#palettematches\");\nconst onboardingcardnode = document.querySelector<HTMLElement>(\"#onboardingcard\");\n\n/** The single broadcast channel every surface subscribes to: the popup refreshes its live status from each frame. */\nconst surfacechannel: BroadcastChannel | undefined = typeof BroadcastChannel === \"function\" ? new BroadcastChannel(\"devthinksurfaces\") : undefined;\nsurfacechannel?.addEventListener(\"message\", () => { void restore().catch(() => { /* a failing refresh keeps the last rendered state */ }); void renderonboarding().catch(() => { /* a failing walkthrough render keeps the card as it was */ }); });\n\n/** Shows the proposal status of the taskinput while the plan generates. */\nfunction proposalstatus(status: \"idle\" | \"generating\" | \"ready\" | \"failed\", detail = \"\"): void {\n if (!proposalstatusnode) return;\n proposalstatusnode.textContent = detail === \"\" ? status : `${status} \u2014 ${detail}`;\n proposalstatusnode.dataset.state = status;\n}\n\n/** Submits the taskinput goal through the same proposal flow as the api with the active origin and page outline attached. */\ntasksubmitbutton?.addEventListener(\"click\", async () => {\n const text = taskinputnode?.value ?? \"\";\n if (text.trim() === \"\") { status(\"Describe the goal first; the proposal flow needs its natural language task.\", true); return; }\n proposalstatus(\"generating\");\n try {\n const result = await request({ kind: \"surface\", task: { submit: { text, surface: \"popup\" } } }) as { plan?: { id: string; state: string }; status?: string };\n proposalstatus(\"ready\", `plan ${result.plan?.id ?? \"\"} awaits its plancard review`);\n status(`The taskinput goal became the plan ${result.plan?.id ?? \"\"}; review its plancards in the side panel.`);\n await restore();\n } catch (error) {\n proposalstatus(\"failed\", error instanceof Error ? error.message : String(error));\n status(error instanceof Error ? error.message : String(error), true);\n }\n});\n\n/** Runs the quick actions of the command surface through the command bus. */\nasync function busaction(command: string, payload = \"\", stepid?: string): Promise<void> {\n const action = { surface: \"popup\", command, ...(payload !== \"\" ? { payload } : {}), ...(stepid !== undefined ? { stepid } : {}) };\n const route = await request({ kind: \"surface\", bus: { action } }) as { route: { dispatched: boolean; reason: string } };\n status(route.route.reason);\n await restore();\n}\nrunquickbutton?.addEventListener(\"click\", () => { void busaction(\"starttask\", taskinputnode?.value ?? \"\").catch(error => status(error instanceof Error ? error.message : String(error), true)); });\ncancelquickbutton?.addEventListener(\"click\", () => { void busaction(\"cancelrun\").catch(error => status(error instanceof Error ? error.message : String(error), true)); });\nopendashboardbutton?.addEventListener(\"click\", () => { void chrome.tabs.create({ url: chrome.runtime.getURL(\"dashboardpage.html\") }); });\nopenoptionsbutton?.addEventListener(\"click\", () => { void chrome.tabs.create({ url: chrome.runtime.getURL(\"optionspage.html\") }); });\n\n/** Renders the fuzzy palette matches with the recent commands ranked first. */\nasync function renderpalette(): Promise<void> {\n if (!palettematchesnode) return;\n const text = paletteinput?.value ?? \"\";\n try {\n const result = await request({ kind: \"surface\", palette: { query: { text } } }) as { matches: Array<{ entry: { id: string; label: string; action: { command: string; surface: string } }; reason: string }> };\n palettematchesnode.replaceChildren();\n for (const match of result.matches.slice(0, 6)) {\n const item = document.createElement(\"button\");\n item.type = \"button\";\n item.className = \"secondary\";\n item.textContent = `${match.entry.label} (${match.entry.action.surface})`;\n item.title = match.reason;\n item.addEventListener(\"click\", () => { void runpalettecommand(match.entry.action.command).catch(error => status(error instanceof Error ? error.message : String(error), true)); });\n palettematchesnode.append(item, \" \");\n }\n if (result.matches.length === 0) palettematchesnode.textContent = \"No command matches; the palette lists only the actions the current capability set allows.\";\n } catch (error) { palettematchesnode.textContent = error instanceof Error ? error.message : String(error); }\n}\n\n/** Runs one commandpalette command: the navigation commands open their surface while every other command rides the command bus and its use records for the ranking. */\nasync function runpalettecommand(command: string): Promise<void> {\n await request({ kind: \"surface\", palette: { used: { command } } });\n if (command === \"opendashboardpage\") { await chrome.tabs.create({ url: chrome.runtime.getURL(\"dashboardpage.html\") }); status(\"The dashboardpage opened in a new tab.\"); return; }\n if (command === \"openoptionspage\") { await chrome.tabs.create({ url: chrome.runtime.getURL(\"optionspage.html\") }); status(\"The optionspage opened in a new tab.\"); return; }\n if (command === \"opentransparencypage\") { await chrome.tabs.create({ url: chrome.runtime.getURL(\"transparencypage.html\") }); status(\"The transparencypage opened in a new tab.\"); return; }\n if (command === \"replayonboarding\") { await request({ kind: \"surface\", onboarding: { replay: true } }); await renderonboarding(); status(\"The onboarding walkthrough replays.\"); return; }\n if (command === \"historysearch\") { await chrome.tabs.create({ url: chrome.runtime.getURL(\"dashboardpage.html\") }); status(\"The dashboardpage opened with the historysearch view.\"); return; }\n if (command === \"copyauditexcerpt\") { const result = await request({ kind: \"surface\", logstream: { excerpt: {} } }) as { excerpt: string }; await navigator.clipboard?.writeText(result.excerpt).catch(() => { /* the clipboard write needs its reviewed clipboard consent */ }); status(\"The verified range copied as an audit excerpt.\"); return; }\n await busaction(command);\n await renderpalette();\n}\npaletteinput?.addEventListener(\"input\", () => { void renderpalette(); });\npaletteopenbutton?.addEventListener(\"click\", () => { paletteinput?.focus(); void renderpalette(); });\ndocument.addEventListener(\"keydown\", event => {\n const shortcut = event.ctrlKey && event.key === \".\";\n if (!shortcut) return;\n event.preventDefault();\n paletteinput?.focus();\n void renderpalette();\n});\nvoid renderpalette();\n\n/** Renders the onboarding walkthrough card while the walkthrough runs: the current step with its body and its completion button. */\nasync function renderonboarding(): Promise<void> {\n if (!onboardingcardnode) return;\n try {\n const result = await request({ kind: \"surface\", onboarding: {} }) as { steps?: Array<{ id: string; surface: string; title: string; body: string; completion: string }>; state?: { stepscompleted: string[]; done: boolean } };\n const steps = result.steps ?? [];\n const state = result.state;\n if (!state || state.done || steps.length === 0) { onboardingcardnode.hidden = true; onboardingcardnode.replaceChildren(); return; }\n const current = steps.find(step => !state.stepscompleted.includes(step.id)) ?? steps[0];\n if (current === undefined) { onboardingcardnode.hidden = true; return; }\n onboardingcardnode.hidden = false;\n onboardingcardnode.replaceChildren();\n const title = document.createElement(\"strong\");\n title.textContent = `Onboarding \u2014 ${current.title} (${state.stepscompleted.length}/${steps.length})`;\n const body = document.createElement(\"p\");\n body.textContent = current.body;\n const done = document.createElement(\"button\");\n done.type = \"button\";\n done.textContent = `Mark ${current.title} done`;\n done.addEventListener(\"click\", () => { void request({ kind: \"surface\", onboarding: { complete: { stepid: current.id } } }).then(() => renderonboarding()).then(() => restore()).catch(error => status(error instanceof Error ? error.message : String(error), true)); });\n onboardingcardnode.append(title, body, done);\n } catch { onboardingcardnode.hidden = true; }\n}\nvoid renderonboarding();\n"],
|
|
5
|
+
"mappings": "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",
|
|
6
|
+
"names": ["status"]
|
|
7
7
|
}
|