2026-10-13 10:33:31 +02:00
'use strict' ;
function domAction ( mode , selector , text , expectedUrl ) {
if ( location . href !== expectedUrl ) throw Error ( 'Document changed before action' );
const visible = e => {
if ( ! e || e . closest ( '[hidden],[aria-hidden="true"],script,style,template,noscript' )) return false ;
2026-10-13 16:58:15 +02:00
for ( let node = e ; node ; node = node . parentElement ) { const style = getComputedStyle ( node ); if ( style . display === 'none' || style . visibility === 'hidden' || style . opacity === '0' ) return false ; }
return e . isConnected !== false && e . getClientRects (). length > 0 ;
2026-10-13 10:33:31 +02:00
};
const sensitive = e => {
const forbidden = /password|passwd|secret|token|credential|api.?key|auth|one.?time|otp|credit.?card|cc-number|cc-csc/i ;
2026-10-13 16:58:15 +02:00
for ( let node = e ; node ; node = node . parentElement ) {
if ( node . getAttribute ( 'data-sensitive' ) !== null && node . getAttribute ( 'data-sensitive' ) !== undefined ) return true ;
2026-10-13 10:33:31 +02:00
if ([ 'name' , 'id' , 'autocomplete' , 'aria-label' , 'data-sensitive' , 'action' ]. some ( a => forbidden . test ( node . getAttribute ( a ) || '' ))) return true ;
}
return /^(password|file|hidden)$/i . test ( e . type || '' );
};
if ( mode === 'read' ) {
const walker = document . createTreeWalker ( document . body , NodeFilter . SHOW_TEXT );
const parts = []; let length = 0 , visited = 0 , node ;
while (( node = walker . nextNode ()) && visited ++ < 20000 && length < 10000 ) {
const element = node . parentElement ;
if ( ! element || element . closest ( 'input,textarea,select,option,[contenteditable],script,style,template,noscript' ) || ! visible ( element ) || sensitive ( element )) continue ;
const value = ( node . textContent || '' ). replace ( /\s+/g , ' ' ). trim ();
if ( value ) { const piece = value . slice ( 0 , 10000 - length ); parts . push ( piece ); length += piece . length + 1 ; }
}
return { text : parts . join ( '\n' ). slice ( 0 , 10000 ) };
}
2026-10-13 16:58:15 +02:00
if ([ 'select' , 'check' ]. includes ( mode ) && document . querySelectorAll ( selector ). length !== 1 ) throw Error ( 'Target is ambiguous' );
2026-10-13 10:33:31 +02:00
const e = document . querySelector ( selector );
2026-10-13 16:58:15 +02:00
if ( ! visible ( e ) || sensitive ( e ) || e . disabled || e . closest ( '[disabled],[aria-disabled="true"],[inert]' ) || e . getAttribute ( 'aria-disabled' ) === 'true' ) throw Error ( 'Target is unavailable or sensitive' );
if ( mode === 'select' ) {
if ( e . tagName !== 'SELECT' || e . multiple ) throw Error ( 'Target cannot be selected' );
const optionUnavailable = option => {
if ( option . disabled || option . parentElement ? . disabled || sensitive ( option )) return true ;
for ( let node = option ; node && node !== e ; node = node . parentElement ) {
if ( node . hidden || node . getAttribute ( 'aria-hidden' ) === 'true' || node . getAttribute ( 'aria-disabled' ) === 'true' ) return true ;
const style = getComputedStyle ( node ); if ( style . display === 'none' || style . visibility === 'hidden' || style . opacity === '0' ) return true ;
}
return false ;
};
const matches = Array . from ( e . options ). filter ( option => option . value === text );
if ( matches . length !== 1 || optionUnavailable ( matches [ 0 ])) throw Error ( 'Option is unavailable or ambiguous' );
Object . getOwnPropertyDescriptor ( HTMLSelectElement . prototype , 'value' ). set . call ( e , text );
e . dispatchEvent ( new Event ( 'input' ,{ bubbles : true })); e . dispatchEvent ( new Event ( 'change' ,{ bubbles : true }));
if ( location . href !== expectedUrl || e . value !== text || ! visible ( e ) || sensitive ( e ) || e . disabled || e . closest ( '[disabled],[aria-disabled="true"],[inert]' ) || optionUnavailable ( matches [ 0 ]) || ! Array . from ( e . options ). includes ( matches [ 0 ]) || Array . from ( e . options ). filter ( option => option . value === text ). length !== 1 ) throw Error ( 'Target changed during action' );
return { completed : true };
}
if ( mode === 'check' ) {
if ( e . tagName !== 'INPUT' || e . type !== 'checkbox' || typeof text !== 'boolean' ) throw Error ( 'Target cannot be checked' );
if ( e . checked !== text ) e . click ();
if ( location . href !== expectedUrl || e . checked !== text || ! visible ( e ) || sensitive ( e ) || e . disabled || e . closest ( '[disabled],[aria-disabled="true"],[inert]' )) throw Error ( 'Target changed during action' );
return { completed : true };
}
2026-10-13 10:33:31 +02:00
if ( mode === 'click' ) {
if ( ! /^(A|BUTTON|INPUT)$/ . test ( e . tagName ) || ( e . tagName === 'INPUT' && ! /^(button|submit|checkbox|radio)$/i . test ( e . type ))) throw Error ( 'Target cannot be clicked' );
2026-10-13 18:04:02 +02:00
const rect = e . getBoundingClientRect (), target = { tag : e . tagName . toLowerCase ()}, role = e . getAttribute ( 'role' );
if ([ 'button' , 'link' , 'textbox' , 'checkbox' , 'radio' , 'combobox' , 'menuitem' , 'option' ]. includes ( role )) target . role = role ;
const x = rect . left + rect . width / 2 , y = rect . top + rect . height / 2 ; if ( Number . isFinite ( x ) && Number . isFinite ( y ) && x >= 0 && y >= 0 && x <= innerWidth && y <= innerHeight ){ target . x = Math . round ( x ); target . y = Math . round ( y );}
e . click (); return { completed : true , target };
2026-10-13 10:33:31 +02:00
}
if ( mode === 'type' ) {
if ( e . readOnly || ! ( e . tagName === 'TEXTAREA' || ( e . tagName === 'INPUT' && /^(text|search|email|url|tel)$/i . test ( e . type )))) throw Error ( 'Target cannot receive text' );
const prototype = e . tagName === 'TEXTAREA' ? HTMLTextAreaElement . prototype : HTMLInputElement . prototype ;
Object . getOwnPropertyDescriptor ( prototype , 'value' ). set . call ( e , text );
e . dispatchEvent ( new Event ( 'input' ,{ bubbles : true })); e . dispatchEvent ( new Event ( 'change' ,{ bubbles : true }));
return { completed : true };
}
throw Error ( 'Unsupported DOM action' );
}
const secret = text => /----- BEGIN ( ?: [ A - Z ] + ) ? PRIVATE KEY -----| \ b ( ?: AKIA | ASIA )[ A - Z0 - 9 ]{ 16 } \ b | \ b ( ?: sk - [ A - Za - z0 - 9_ - ]{ 16 ,} | gh [ pousr ] _ [ A - Za - z0 - 9 ]{ 20 ,}) \ b | \ b ( ?: password | passwd | api [ _ - ] ? key | access [ _ - ] ? token | client [ _ - ] ? secret ) \ s * [ := ] \ s * [ "']?[^\s" ']{4,}/i.test(text);
2026-10-13 16:58:15 +02:00
// Fixed inspection code: no caller-supplied JavaScript, credential fields or hidden DOM.
function inspectControls(expectedUrl) {
if(location.href!==expectedUrl)throw Error(' Document changed before action ');
const controls=[];
for(const e of document.querySelectorAll(' a , button , input , textarea , select ')){
if(controls.length>=100)break;
const forbidden=/password|passwd|secret|token|credential|api.?key|auth|one.?time|otp|credit.?card|cc-number|cc-csc/i;
let sensitive=false;
for(let node=e;node;node=node.parentElement)if((node.getAttribute(' data - sensitive ')!==null&&node.getAttribute(' data - sensitive ')!==undefined)||[' name ',' id ',' autocomplete ',' aria - label ',' data - sensitive ',' action '].some(a=>forbidden.test(node.getAttribute(a)||'')))sensitive=true;
const style=getComputedStyle(e);
if(sensitive||/^(password|file|hidden)$/i.test(e.type||'')||e.closest(' [ hidden ],[ aria - hidden = "true" ] ')||!e.getClientRects().length||style.display===' none '||style.visibility===' hidden '||style.opacity===' 0 ')continue;
let selector='';
if(/^[a-zA-Z_][a-zA-Z0-9_-]{0,100}$/.test(e.id||'')&&document.querySelectorAll(' # '+e.id).length===1)selector=' # '+e.id;
else if(/^[a-zA-Z0-9_-]{1,100}$/.test(e.getAttribute(' name ')||'')){const candidate=e.tagName.toLowerCase()+' [ name = "'+e.getAttribute('name')+'" ] ';if(document.querySelectorAll(candidate).length===1)selector=candidate;}
const row={selector,role:e.tagName.toLowerCase(),label:(e.getAttribute(' aria - label ')||'').slice(0,160),text:(e.innerText||'').slice(0,300),value:typeof e.value===' string '?e.value.slice(0,300):''};
if(e.tagName===' INPUT '&&e.type===' checkbox ')row.checked=e.checked===true;
if(e.tagName===' SELECT ') {
row.options=[];
for(const option of Array.from(e.options)) {
if(row.options.length>=20)break;
let excluded=false;
for(let node=option;node;node=node.parentElement) {
if(node.hidden||node.getAttribute(' aria - hidden ')===' true '||(node.getAttribute(' data - sensitive ')!==null&&node.getAttribute(' data - sensitive ')!==undefined)||[' name ',' id ',' autocomplete ',' aria - label ',' action '].some(a=>forbidden.test(node.getAttribute(a)||'')))excluded=true;
const style=getComputedStyle(node);if(style.display===' none '||style.visibility===' hidden '||style.opacity===' 0 ')excluded=true;
}
if(!excluded&&String(option.value).length<=128)row.options.push({value:String(option.value),label:String(option.label||option.textContent||'').slice(0,160),disabled:option.disabled===true||option.parentElement?.disabled===true});
}
}
controls.push(row);
}
return {text:"",controls};
}
function scrollPage(expectedUrl,x,y){if(location.href!==expectedUrl)throw Error(' Document changed before action ');window.scrollTo({left:x,top:y,behavior:' instant '});return {completed:true};}
const script = (mode,args,url) => `(${domAction.toString()})(${JSON.stringify(mode)},${JSON.stringify(args.selector || '')},${JSON.stringify(mode===' check '?args.checked:mode===' select '?args.value:args.text || '')},${JSON.stringify(url)})`;
2026-10-13 10:33:31 +02:00
const hostMatches = (hostname, pattern) => {
const name=hostname.toLowerCase().replace(/\.$/,''),p=String(pattern).toLowerCase();
return p.startsWith(' * . ') ? name.endsWith(p.slice(1)) && name!==p.slice(2) : name===p;
};
2026-10-13 18:04:02 +02:00
function createAgentAdapters({getContext,getSessions,contextCurrent,navigate,history,ai,inspect,audit,onInteraction,appControls={}} = {}) {
2026-10-13 10:33:31 +02:00
if ([getContext,getSessions,contextCurrent,navigate,ai,inspect,audit].some(fn=>typeof fn!==' function ')) throw TypeError(' All adapter callbacks are required ');
const snapshot = (args,signal,action) => {
const c = getContext(args.sessionId);
if (signal?.aborted || !c?.ready || !c.policy?.agents?.enabled || !c.webContents || c.webContents.isDestroyed?.() || (c.policy.expiresAt && Date.parse(c.policy.expiresAt)<=Date.now())) throw Error(' Managed session unavailable ');
if (!c.sessionId || !c.token || (args.sessionId !== undefined && args.sessionId !== c.sessionId)) throw Error(' Unknown managed session ');
2026-10-13 16:58:15 +02:00
if ([' click ',' type ',' scroll ',' select ',' check ',' back ',' forward ',' reload '].includes(action) && (c.foreground !== true || c.interactive===false)) throw Error(' Managed session unavailable ');
2026-10-13 10:33:31 +02:00
const url = c.webContents.getURL();
2026-10-13 16:58:15 +02:00
const changesDestination=[' navigate ',' back ',' forward '].includes(action);
if (typeof c.destinationAllowed!==' function ' || (!changesDestination && (!/^https?:\/\//.test(url) || c.destinationAllowed(url,c.policy)!==true))) throw Error(' Session destination blocked ');
2026-10-13 10:33:31 +02:00
const agents=c.policy.agents;
if (!Array.isArray(agents.scopes) || !agents.scopes.includes(action)) throw Error(' Agent scope denied ');
2026-10-13 16:58:15 +02:00
if (!Array.isArray(agents.allowedHosts) || (!changesDestination && agents.allowedHosts.length && !agents.allowedHosts.some(p=>hostMatches(new URL(url).hostname,p)))) throw Error(' Agent destination denied ');
2026-10-13 10:33:31 +02:00
return {...c,url,action,version:c.policyVersion ?? c.policy.version};
};
const check = (initial,args,signal,expectedUrl=initial.url) => {
const current = snapshot({...args,sessionId:initial.sessionId},signal,initial.action);
if (!contextCurrent(initial.token) || current.version !== initial.version || current.webContents !== initial.webContents || current.url !== expectedUrl) throw Error(' Managed session changed during action ');
return current;
};
const classify = async (text,initial,args,signal,allowSensitive=false) => {
if (typeof text!==' string ' || text.length>10000 || secret(text)) throw Error(' Sensitive content cannot be returned ');
const verdict = await inspect(text,initial);check(initial,args,signal);
if (!verdict || ![' allow ',' block '].includes(verdict.decision)) throw Error(' Inspection unavailable ');
const reserved=/^(?:private-key|aws-access-key|api-secret|uninspectable|size-limit|inspection-incomplete|yara-.*)$/;
const knownCorporateRule=verdict.ruleId===' content - blocklist ' || initial.policy.dlp?.rules?.some(rule=>rule.id===verdict.ruleId && [' literal ',' sha256 ',' fragment - sha256 '].includes(rule.kind));
const override=allowSensitive && knownCorporateRule && !reserved.test(verdict.ruleId || '') && !verdict.malware && verdict.engine!==' yara ' && verdict.coverage?.complete!==false;
if (verdict.decision===' block ' && !override) { await audit({action:' agent . read ',outcome:' blocked '});throw Error(' Company policy blocks content access '); }
return text;
};
const dom = mode => async (args,{signal}={}) => {
const initial = snapshot(args,signal,mode);
if (mode!==' read ' && (typeof args.selector!==' string ' || args.selector.length>512 || !/^[a-zA-Z0-9_#.*\[\]="' >+~- ] + $ / . test ( args . selector ))) throw Error ( 'Invalid selector' );
if ( mode === 'type' && ( typeof args . text !== 'string' || args . text . length > 16384 )) throw Error ( 'Invalid text' );
2026-10-13 16:58:15 +02:00
if ( mode === 'select' && ( typeof args . value !== 'string' || args . value . length > 16384 )) throw Error ( 'Invalid value' );
if ( mode === 'check' && typeof args . checked !== 'boolean' ) throw Error ( 'Invalid checked state' );
2026-10-13 10:33:31 +02:00
const result = await initial . webContents . executeJavaScript ( script ( mode , args , initial . url ), false );
check ( initial , args , signal );
if ( mode === 'read' ) return { text : await classify ( result ? . text , initial , args , signal , initial . policy . agents . allowSensitiveRead === true )};
2026-10-13 18:04:02 +02:00
if ( mode === 'click' && result ? . completed === true && onInteraction ){
const source = result . target , target = source && [ 'a' , 'button' , 'input' ]. includes ( source . tag ) ? { tag : source . tag } : undefined ;
if ( target && [ 'button' , 'link' , 'textbox' , 'checkbox' , 'radio' , 'combobox' , 'menuitem' , 'option' ]. includes ( source . role )) target . role = source . role ;
if ( target && [ 'x' , 'y' ]. every ( k => Number . isSafeInteger ( source [ k ]) && source [ k ] >= 0 && source [ k ] <= 100000 )){ target . x = source . x ; target . y = source . y ;}
await onInteraction ({ action : 'click' , sessionId : initial . sessionId , pageUrl : initial . url , outcome : 'completed' , sensitiveField : false ,...( target ? { target } : {})}); check ( initial , args , signal );}
2026-10-13 10:33:31 +02:00
return { completed : result ? . completed === true };
};
2026-10-13 16:58:15 +02:00
const appActions = [ 'application:state' , 'settings:get' , 'settings:update' , 'bookmarks:list' , 'bookmarks:mutate' , 'tabs:create' , 'tabs:activate' , 'tabs:close' , 'tabs:group' , 'library:organize-plan' , 'library:organize-apply' ];
// Native history owns target resolution and pre-release destination checks. A
// refreshed token permits intentional navigation but never a different tab.
const historyAdapters = Object . fromEntries ([ 'back' , 'forward' , 'reload' ]. map ( action =>[ action , async ( args = {}, { signal } = {})=>{
const initial = snapshot ( args , signal , action );
if ( typeof history !== 'function' ) throw Error ( 'Managed session unavailable' );
const result = await history ( action , initial ,{ signal }), refreshed = result ? . context ;
if ( result ? . completed !== true || ! refreshed || refreshed . sessionId !== initial . sessionId || refreshed . webContents !== initial . webContents || ( refreshed . policyVersion ?? refreshed . policy ? . version ) !== initial . version ) throw Error ( 'Managed session changed during action' );
const url = refreshed . webContents . getURL ();
let parsed ; try { parsed = new URL ( url );} catch { throw Error ( 'Destination blocked' );}
if ( ! [ 'http:' , 'https:' ]. includes ( parsed . protocol ) || parsed . username || parsed . password || refreshed . destinationAllowed ? .( url , refreshed . policy ) !== true || ( refreshed . policy ? . agents ? . allowedHosts ? . length &&! refreshed . policy . agents . allowedHosts . some ( p => hostMatches ( parsed . hostname , p )))) throw Error ( 'Destination blocked' );
check ({... refreshed , action , version : initial . version , url }, args , signal , url );
return { completed : true };
}]));
2026-10-13 10:33:31 +02:00
const appSnapshot = action =>{ const c = getContext (); if ( ! c ? . ready ||! c . policy ? . agents ? . enabled ||! c . policy . agents . scopes ? . includes ( action ) || ( c . policy . expiresAt && Date . parse ( c . policy . expiresAt ) <= Date . now ())) throw Error ( 'Agent scope denied' ); return c ;};
const appAdapters = Object . fromEntries ( appActions . map ( action =>[ action , async ( args = {}, { signal } = {})=>{
const initial = appSnapshot ( action ), version = initial . policyVersion ?? initial . policy . version ;
const current = ()=>{ const c = appSnapshot ( action ); if ( signal ? . aborted || c . policy !== initial . policy || ( c . policyVersion ?? c . policy . version ) !== version ) throw Error ( 'Managed session changed during action' );};
if ( signal ? . aborted || typeof appControls [ action ] !== 'function' ) throw Error ( 'Managed session unavailable' );
if ( action === 'tabs:create' && args . url ){ const url = new URL ( args . url ); if ( ! [ 'http:' , 'https:' ]. includes ( url . protocol ) || url . username || url . password || initial . destinationAllowed ( url . href , initial . policy ) !== true || ( initial . policy . agents . allowedHosts . length &&! initial . policy . agents . allowedHosts . some ( p => hostMatches ( url . hostname , p )))) throw Error ( 'Agent destination denied' );}
current (); const result = await appControls [ action ]( args ,{ signal , policyVersion : version }); current ();
2026-10-13 16:58:15 +02:00
if ( action === 'library:organize-plan' ){
const labels = [...( result ? . plan ? . bookmarks || []). map ( row => row . folder ),...( result ? . plan ? . tabs || []). map ( row => row . group )]. join ( '\n' );
if ( labels . length > 16000 || secret ( labels )) throw Error ( 'Sensitive content cannot be returned' ); const verdict = await inspect ( labels ,{... initial , action }); current (); if ( verdict ? . decision !== 'allow' ) throw Error ( 'Company policy blocks content access' );
}
2026-10-13 10:33:31 +02:00
if ( action === 'bookmarks:list' ){
const entries = [...( result ? . managed || []),...( result ? . personal ? . items || [])];
for ( const item of entries ){ const text = [ item . title , item . url , item . folder ]. filter ( v => typeof v === 'string' ). join ( '\n' ); if ( text . length > 10000 || secret ( text )) throw Error ( 'Sensitive content cannot be returned' ); const verdict = await inspect ( text ,{... initial , action }); current (); if ( verdict ? . decision !== 'allow' ) throw Error ( 'Company policy blocks content access' );}
}
return result ;
}]));
return {
... appAdapters ,
2026-10-13 16:58:15 +02:00
... historyAdapters ,
2026-10-13 10:33:31 +02:00
sessions : async ( args = {}, { signal } = {})=>{
const c = getContext ();
if ( signal ? . aborted || ! c ? . ready || ! c . policy ? . agents ? . enabled || ! c . policy . agents . scopes ? . includes ( 'sessions' ) || ( c . policy . expiresAt && Date . parse ( c . policy . expiresAt ) <= Date . now ())) throw Error ( 'Managed session unavailable' );
2026-10-13 18:04:02 +02:00
const version = c . policyVersion ?? c . policy . version ;
const inventory = ()=>{ const rows = getSessions (); if ( ! Array . isArray ( rows ) || rows . length > 1000 ) throw Error ( 'Invalid managed inventory' ); return rows . map ( item =>{ if ( typeof item . title !== 'string' || item . title . length > 200 ) throw Error ( 'Invalid managed title' ); return { id : item . id , title : item . title || 'Tab' , foreground : item . foreground === true };});};
const rows = inventory (), binding = JSON . stringify ( rows );
const current = ()=>{ const latest = getContext (); if ( signal ? . aborted ||! latest ? . ready ||! latest . policy ? . agents ? . enabled ||! latest . policy . agents . scopes ? . includes ( 'sessions' ) || latest . policy !== c . policy || ( latest . policyVersion ?? latest . policy . version ) !== version || ( latest . policy . expiresAt && Date . parse ( latest . policy . expiresAt ) <= Date . now ()) || ( c . token &&! contextCurrent ( c . token )) || JSON . stringify ( inventory ()) !== binding ) throw Error ( 'Managed session changed during action' );};
// One bounded classification avoids reloading local OCR/semantic models
// per tab. A blocked aggregate masks every title; opaque IDs remain usable.
const candidates = rows . filter ( row => ! secret ( row . title ));
let allow = false ;
if ( candidates . length ){
const verdict = await inspect ( candidates . map ( row => row . title ). join ( '\n' ),{... c , action : 'sessions' }); current ();
if ( ! verdict ||! [ 'allow' , 'block' ]. includes ( verdict . decision ) || verdict . coverage ? . complete === false || /uninspectable|size-limit|inspection-incomplete|unavailable|failure|failed|timeout|unsupported|encrypted|parser/i . test ( verdict . ruleId || '' )) throw Error ( 'Inspection unavailable' );
allow = verdict . decision === 'allow' ;
}
current (); return rows . map ( row =>({... row , title : allow &&! secret ( row . title ) ? row . title . slice ( 0 , 160 ) : 'Protected page' }));
2026-10-13 10:33:31 +02:00
},
navigate : async ( args ,{ signal } = {})=>{ const initial = snapshot ( args , signal , 'navigate' ); const url = new URL ( args . url ); if ( ! [ 'http:' , 'https:' ]. includes ( url . protocol ) || url . username || url . password || initial . destinationAllowed ( url . href , initial . policy ) !== true || ( initial . policy . agents . allowedHosts . length && ! initial . policy . agents . allowedHosts . some ( p => hostMatches ( url . hostname , p )))) throw Error ( 'Destination blocked' ); const refreshed = await navigate ( url . href , initial ,{ signal }); if ( ! refreshed || refreshed . sessionId !== initial . sessionId || refreshed . webContents !== initial . webContents || ( refreshed . policyVersion ?? refreshed . policy ? . version ) !== initial . version ) throw Error ( 'Managed session changed during action' ); check ({... refreshed , action : initial . action , version : initial . version , url : url . href }, args , signal , url . href ); return { completed : true };},
2026-10-13 16:58:15 +02:00
read : dom ( 'read' ), click : dom ( 'click' ), type : dom ( 'type' ), select : dom ( 'select' ), check : dom ( 'check' ),
inspect : async ( args ,{ signal } = {})=>{ const initial = snapshot ( args , signal , 'inspect' ); const result = await initial . webContents . executeJavaScript ( `( ${ inspectControls . toString () } )( ${ JSON . stringify ( initial . url ) } )` , false ); check ( initial , args , signal ); if ( ! Array . isArray ( result ? . controls ) || result . controls . length > 100 ) throw Error ( 'Invalid text response' ); const text = JSON . stringify ( result ); if ( text . length > 10000 ) throw Error ( 'Sensitive content cannot be returned' ); await classify ( text , initial , args , signal , initial . policy . agents . allowSensitiveRead === true ); return result ;},
scroll : async ( args ,{ signal } = {})=>{ const initial = snapshot ( args , signal , 'scroll' ); if ( ! [ args . x , args . y ]. every ( n => Number . isSafeInteger ( n ) && n >= 0 && n <= 10000000 )) throw Error ( 'Invalid scroll position' ); const result = await initial . webContents . executeJavaScript ( `( ${ scrollPage . toString () } )( ${ JSON . stringify ( initial . url ) } , ${ args . x } , ${ args . y } )` , false ); check ( initial , args , signal ); return { completed : result ? . completed === true };},
2026-10-13 10:33:31 +02:00
ai : async ( args ,{ signal } = {})=>{ const initial = snapshot ( args , signal , 'ai' ); if ( typeof args . prompt !== 'string' || args . prompt . length > 16384 ) throw Error ( 'Invalid prompt' ); const result = await ai ( args . prompt ,{ signal }); check ( initial , args , signal ); const text = typeof result === 'string' ? result : result ? . text ?? result ? . answer ; return { text : await classify ( text , initial , args , signal , false )};}
};
}
module . exports = { createAgentAdapters };