char-slop/berry
multipurpose self-modifiable agentic harness
git clone https://git.t4t.associates/char-slop/berry
cb11725
main
1import { assertEquals } from "@std/assert" ; 2import type { ConversationId , ConversationView , EntryId } from "@earendil-works/pi-durable" ; 3import { withDom } from "@berry/util/dom.ts" ; 4import { readWhenVisible } from "./read.ts" ; 5import { Scope } from "./ui.ts" ; 6 7Deno . test ( "read receipts follow visible settled views without acknowledging unseen turns" , async ( t ) => { 8await withDom ( async ( window ) => { 9let visibility = "visible" ; 10Object . defineProperty ( document , "visibilityState" , { 11configurable :true , 12get :() => visibility , 13}); 14const scope = new Scope (); 15const sent :number [] = []; 16let acknowledge = ( _entryId :number ) => Promise . resolve (); 17const update = readWhenVisible ( scope , ( entryId ) => { 18sent . push ( entryId ); 19return acknowledge ( entryId ); 20}); 21const view = ( id :number , docs :ConversationView [ "docs" ] = {}) :ConversationView => ({ 22conversation :{ id :1 as ConversationId }, 23entries :[ 24{ id :( id - 1 ) as EntryId , conversationId :1 as ConversationId , kind :"pi.user" }, 25{ id :id as EntryId , conversationId :1 as ConversationId , kind :"pi.assistant" }, 26{ id :( id + 1 ) as EntryId , conversationId :1 as ConversationId , kind :"berry.event" }, 27], 28 docs, 29}); 30const settle = () => window . happyDOM . waitUntilComplete (); 31const visible = ( state :string ) => { 32visibility = state ; 33document . dispatchEvent ( new window . Event ( "visibilitychange" ) as unknown as Event ); 34}; 35try { 36await t . step ( "working views and queued follow-ups do not send read receipts" , async () => { 37update ( view ( 10 , { "pi.live" :{ run :{ taskId :1 , inputs :[] } } })); 38update ( view ( 10 , { "pi.inbox" :{ items :[{ id :1 , mode :"followUp" , content :"next" }] } })); 39await settle (); 40assertEquals ( sent , []); 41update ( view ( 10 )); 42await settle (); 43assertEquals ( sent , [ 10 ]); 44update ( view ( 10 )); 45await settle (); 46assertEquals ( sent , [ 10 ]); 47}); 48await t . step ( "hidden turns wait until the tab becomes visible" , async () => { 49visible ( "hidden" ); 50update ( view ( 20 )); 51await settle (); 52assertEquals ( sent , [ 10 ]); 53visible ( "visible" ); 54await settle (); 55assertEquals ( sent , [ 10 , 20 ]); 56}); 57await t . step ( 58"a late read receipt sends only its captured message, then the newer displayed one" , 59async () => { 60const pending = Promise . withResolvers < void >(); 61acknowledge = ( entryId ) => entryId === 30 ?pending . promise :Promise . resolve (); 62update ( view ( 30 )); 63update ( view ( 40 )); 64assertEquals ( sent , [ 10 , 20 , 30 ]); 65pending . resolve (); 66await settle (); 67assertEquals ( sent , [ 10 , 20 , 30 , 40 ]); 68}, 69); 70await t . step ( 71"a newer hidden turn is not acknowledged after the older receipt finishes" , 72async () => { 73const pending = Promise . withResolvers < void >(); 74acknowledge = () => pending . promise ; 75update ( view ( 50 )); 76visible ( "hidden" ); 77update ( view ( 60 )); 78pending . resolve (); 79await settle (); 80assertEquals ( sent , [ 10 , 20 , 30 , 40 , 50 ]); 81acknowledge = () => Promise . resolve (); 82visible ( "visible" ); 83await settle (); 84assertEquals ( sent , [ 10 , 20 , 30 , 40 , 50 , 60 ]); 85}, 86); 87await t . step ( 88"returning to an earlier branch cannot move the read watermark backwards" , 89async () => { 90update ( view ( 20 )); 91await settle (); 92assertEquals ( sent , [ 10 , 20 , 30 , 40 , 50 , 60 ]); 93}, 94); 95await t . step ( 96"leaving the conversation removes visibility acknowledgements and pending work" , 97async () => { 98const pending = Promise . withResolvers < void >(); 99acknowledge = () => pending . promise ; 100update ( view ( 70 )); 101visible ( "hidden" ); 102update ( view ( 80 )); 103scope . dispose (); 104visible ( "visible" ); 105pending . resolve (); 106await settle (); 107update ( view ( 90 )); 108assertEquals ( sent , [ 10 , 20 , 30 , 40 , 50 , 60 , 70 ]); 109}, 110); 111} finally { 112scope . dispose (); 113} 114}); 115});