char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
6183bc3
main
1/** A self-contained program sent to the target, owning Firefox and its native WebSocket. */ 2export async function run () :Promise < never > { 3// owns firefox and its profile, so both go once its stdin closes: when berry closes the session, or 4// however berry exits. it ignores the terminal's signals and leaves shutting down to berry 5const WATCHDOG = ` 6profile=$(mktemp -d "\${TMPDIR:-/tmp}/berry-agent-browser-XXXXXXXX") || exit 1 7exec 3<&0 8firefox --profile "$profile" "$@" 3<&- </dev/null & 9browser=$! 10trap '' HUP INT TERM 11{ read -r _ <&3; kill $browser; sleep 2; kill -KILL $browser; rm -rf "$profile"; } 2>/dev/null & 12watcher=$! 13wait $browser 14kill -KILL $watcher 2>/dev/null 15rm -rf "$profile" 16` ; 17const child = new Deno . Command ( "sh" , { 18args :[ 19"-c" , 20WATCHDOG , 21"berry-agent-browser" , 22"--headless" , 23"--no-remote" , 24"--remote-debugging-port" , 25"0" , 26"about:blank" , 27], 28stdin :"piped" , 29stdout :"null" , 30stderr :"piped" , 31}). spawn (); 32const output = Deno . stdout . writable . getWriter (); 33const encoder = new TextEncoder (); 34const stopped = Promise . withResolvers < void >(); 35let ended = false ; 36let failure :unknown ; 37let socket :WebSocket | undefined ; 38const stop = ( error ?:unknown ) => { 39if ( ended ) return ; 40ended = true ; 41failure = error ; 42stopped . resolve (); 43}; 44const send = ( text :string ) => output . write ( encoder . encode ( text + "\n" )); 45void ( async () => { 46let buffered = "" ; 47for await ( const chunk of Deno . stdin . readable . pipeThrough ( new TextDecoderStream ())) { 48buffered += chunk ; 49let newline :number ; 50while (( newline = buffered . indexOf ( "\n" )) >= 0 ) { 51if ( socket ?. readyState !== WebSocket . OPEN ) { 52throw new Error ( "Firefox connection is not open." ); 53} 54socket . send ( buffered . slice ( 0 , newline )); 55buffered = buffered . slice ( newline + 1 ); 56} 57} 58stop (); 59})(). catch ( stop ); 60try { 61await Promise . race ([ 62( async () => { 63const endpoint = await new Promise < string >(( resolve , reject ) => { 64const timeout = setTimeout ( 65() => reject ( new Error ( "Firefox did not start within 10 seconds." )), 6610_000 , 67); 68let seen = "" ; 69void ( async () => { 70try { 71// drain even after finding the endpoint, or firefox can block on stderr 72for await ( const chunk of child . stderr . pipeThrough ( new TextDecoderStream ())) { 73seen = ( seen + chunk ). slice ( - 16_384 ); 74const match = seen . match ( / WebDriver BiDi listening on (ws:\/\/[^\r\n]+)\r?\n / ); 75if ( match ) { 76clearTimeout ( timeout ); 77resolve ( ` ${ match [ 1 ]} /session` ); 78} 79} 80reject ( new Error ( seen . trim () || "Firefox exited before starting." )); 81} catch ( error ) { 82reject ( error ); 83} finally { 84clearTimeout ( timeout ); 85} 86})(); 87}); 88if ( ended ) return ; 89const ws = socket = new WebSocket ( endpoint ); 90ws . addEventListener ( "message" , ( event ) => { 91try { 92void send ( JSON . stringify ( JSON . parse ( String ( event . data )))). catch ( stop ); 93} catch ( error ) { 94stop ( error ); 95} 96}); 97ws . addEventListener ( "close" , () => stop ( new Error ( "Firefox connection closed." ))); 98await new Promise < void >(( resolve , reject ) => { 99const timeout = setTimeout ( 100() => reject ( new Error ( "Firefox connection timed out." )), 10110_000 , 102); 103ws . addEventListener ( "open" , () => { 104clearTimeout ( timeout ); 105resolve (); 106}, { once :true }); 107ws . addEventListener ( "error" , () => { 108clearTimeout ( timeout ); 109reject ( new Error ( "Could not connect to Firefox." )); 110}, { once :true }); 111ws . addEventListener ( "close" , () => { 112clearTimeout ( timeout ); 113reject ( new Error ( "Firefox connection closed." )); 114}, { once :true }); 115}); 116await send ( JSON . stringify ({ type :"ready" })); 117})(), 118stopped . promise , 119]); 120await stopped . promise ; 121} catch ( error ) { 122stop ( error ); 123} finally { 124if ( failure ) { 125await send ( JSON . stringify ({ type :"error" , message :String ( failure ) })). catch (() => {}); 126} 127socket ?. close (); 128await child . stdin . close (). catch (() => {}); 129await child . status ; 130await output . close (). catch (() => {}); 131} 132Deno . exit ( failure ?1 :0 ); 133}