@@ -29,7 +29,11 @@ import { resolveAgentStack } from "../src/agent-stack.ts";
2929import { createFakeAcp , makeRegistry , makeStore , tripwireAcp } from "./support/fake-acp.ts" ;
3030import type { FakeAcp } from "./support/fake-acp.ts" ;
3131import { ADAPTERS , AGENT } from "./support/plan-harness.ts" ;
32- import { parseDurableEvent } from "@executablemd/durable-streams" ;
32+ import {
33+ MalformedDurableEventError ,
34+ parseDurableEvent ,
35+ serializeDurableEvent ,
36+ } from "@executablemd/durable-streams" ;
3337import type { DurableEvent } from "@executablemd/durable-streams" ;
3438import { z } from "zod" ;
3539import {
@@ -1703,9 +1707,50 @@ function holds(asked: readonly string[]): Holds {
17031707 } ;
17041708}
17051709
1710+ describe ( "Live journey journal observation" , ( ) => {
1711+ it ( "observes an appended prompt only after its terminating newline" , function * ( ) {
1712+ const hostRoot = yield * useTempDirectory ( "xmd-live-journal-" ) ;
1713+ const directory = join ( hostRoot , "xmd" , "repl" ) ;
1714+ yield * ensureDir ( directory ) ;
1715+ const path = join ( directory , "entry.jsonl" ) ;
1716+ const events : DurableEvent [ ] = [ 1 , 2 , 3 ] . map ( ( turn ) => ( {
1717+ type : "yield" ,
1718+ coroutineId : "root" ,
1719+ description : { type : "agent_prompt" , name : `turn:${ turn } ` } ,
1720+ result : { status : "ok" , value : null } ,
1721+ } ) ) ;
1722+ const lines = events . map ( serializeDurableEvent ) ;
1723+ yield * writeTextFile ( path , lines [ 0 ] ) ;
1724+ const next = lines [ 1 ] ;
1725+ const split = Math . floor ( next . length / 2 ) ;
1726+ yield * untilResolved ( appendFile ( path , next . slice ( 0 , split ) ) ) ;
1727+ expect ( yield * journal ( hostRoot ) ) . toEqual ( events . slice ( 0 , 1 ) ) ;
1728+ yield * untilResolved ( appendFile ( path , next . slice ( split , - 1 ) ) ) ;
1729+ expect ( yield * journal ( hostRoot ) ) . toEqual ( events . slice ( 0 , 1 ) ) ;
1730+ yield * untilResolved ( appendFile ( path , `\n${ lines [ 2 ] } ` ) ) ;
1731+ const retained = yield * journal ( hostRoot ) ;
1732+ expect ( retained ) . toEqual ( events ) ;
1733+ expect ( recorded ( retained ) . filter ( ( kind ) => kind === "agent_prompt" ) ) . toHaveLength ( 3 ) ;
1734+ } ) ;
1735+
1736+ it ( "rejects a malformed terminated record even with an incomplete tail" , function * ( ) {
1737+ const hostRoot = yield * useTempDirectory ( "xmd-live-journal-malformed-" ) ;
1738+ const directory = join ( hostRoot , "xmd" , "repl" ) ;
1739+ yield * ensureDir ( directory ) ;
1740+ yield * writeTextFile ( join ( directory , "entry.jsonl" ) , '{"type":\n{"type":' ) ;
1741+ expect (
1742+ yield * failing ( function * ( ) {
1743+ yield * journal ( hostRoot ) ;
1744+ } ) ,
1745+ ) . toBeInstanceOf ( MalformedDurableEventError ) ;
1746+ } ) ;
1747+ } ) ;
1748+
17061749function * journal ( hostRoot : string ) : Operation < DurableEvent [ ] > {
17071750 const text = yield * entryHistory ( hostRoot ) ;
1751+ // Live observations can race an append; its final newline commits the record.
17081752 return text
1753+ . slice ( 0 , text . lastIndexOf ( "\n" ) + 1 )
17091754 . split ( "\n" )
17101755 . filter ( ( line ) => line . length > 0 )
17111756 . map ( ( line ) => {
0 commit comments