@@ -33,6 +33,7 @@ const RESPONSE_FILE = "response.jsonl";
3333const MANIFEST_FILE = "manifest.jsonl" ;
3434const METADATA_FILE = "metadata.json" ;
3535const TOOL_OUTPUT_DIR = "tool-output" ;
36+ const EVIDENCE_ARCHIVE_DIR = "evidence-archive" ;
3637
3738const log = getLogger ( [ LOG_NAMESPACE_ROOT , "session" , "context-store" ] ) ;
3839
@@ -439,6 +440,33 @@ export async function createSessionStores(
439440 const pendingSegmentPaths = new Set < string > ( ) ;
440441 const writeTurnsSegmented = createSegmentedJSONLWriter ( dir , TURNS_FILE ) ;
441442 const writePromptSegmented = createSegmentedJSONLWriter ( dir , PROMPT_FILE ) ;
443+ let liveTurnRefs : readonly ConversationTurn [ ] | null = null ;
444+ let unpublishedRewrite : ConversationTurn [ ] | null = null ;
445+
446+ function refPrefixLength (
447+ prev : readonly ConversationTurn [ ] ,
448+ next : readonly ConversationTurn [ ] ,
449+ ) : number {
450+ const max = Math . min ( prev . length , next . length ) ;
451+ let prefix = 0 ;
452+ while ( prefix < max && prev [ prefix ] === next [ prefix ] ) prefix ++ ;
453+ return prefix ;
454+ }
455+
456+ function contentPrefixLength (
457+ prev : readonly ConversationTurn [ ] ,
458+ next : readonly ConversationTurn [ ] ,
459+ ) : number {
460+ const max = Math . min ( prev . length , next . length ) ;
461+ let prefix = 0 ;
462+ while (
463+ prefix < max &&
464+ JSON . stringify ( prev [ prefix ] ) === JSON . stringify ( next [ prefix ] )
465+ ) {
466+ prefix ++ ;
467+ }
468+ return prefix ;
469+ }
442470
443471 async function writeSegmented (
444472 writer : ReturnType < typeof createSegmentedJSONLWriter > ,
@@ -448,6 +476,39 @@ export async function createSessionStores(
448476 for ( const filepath of modifiedPaths ) pendingSegmentPaths . add ( filepath ) ;
449477 }
450478
479+ async function writeTurnsLiveOrStage (
480+ turns : readonly ConversationTurn [ ] ,
481+ ) : Promise < void > {
482+ if ( unpublishedRewrite !== null ) {
483+ unpublishedRewrite = [ ...turns ] ;
484+ return ;
485+ }
486+ if ( liveTurnRefs !== null ) {
487+ if ( refPrefixLength ( liveTurnRefs , turns ) < liveTurnRefs . length ) {
488+ unpublishedRewrite = [ ...turns ] ;
489+ return ;
490+ }
491+ await writeSegmented ( writeTurnsSegmented , turns ) ;
492+ liveTurnRefs = [ ...turns ] ;
493+ return ;
494+ }
495+ const extraTexts = await readExtraSegmentTexts ( dir , TURNS_FILE ) ;
496+ const baseResult = await base . load ( ) ;
497+ const live =
498+ extraTexts . length === 0
499+ ? baseResult . turns
500+ : await loadTurnsWithoutMalformedToolSequence (
501+ baseResult . turns ,
502+ extraTexts ,
503+ ) ;
504+ if ( live . length > 0 && contentPrefixLength ( live , turns ) < live . length ) {
505+ unpublishedRewrite = [ ...turns ] ;
506+ return ;
507+ }
508+ await writeSegmented ( writeTurnsSegmented , turns ) ;
509+ liveTurnRefs = [ ...turns ] ;
510+ }
511+
451512 // Prefer the longest prefix of base + extras whose tool sequence the reactor
452513 // will accept. Orphan tails left by a fresh-writer compaction rewrite are
453514 // dropped and unlinked so the next load does not re-poison the session.
@@ -569,7 +630,7 @@ export async function createSessionStores(
569630 writePrompt : ( turns ) => writeSegmented ( writePromptSegmented , turns ) ,
570631 writeResponse : ( turn , signal ) => base . writeResponse ( turn , signal ) ,
571632 writeManifest : ( records , signal ) => base . writeManifest ( records , signal ) ,
572- writeTurns : ( turns ) => writeSegmented ( writeTurnsSegmented , turns ) ,
633+ writeTurns : ( turns ) => writeTurnsLiveOrStage ( turns ) ,
573634 writeMetadata : ( metadata , signal ) => base . writeMetadata ( metadata , signal ) ,
574635 readManifestHistory : ( limit , signal ) =>
575636 base . readManifestHistory ( limit , signal ) ,
@@ -580,6 +641,11 @@ export async function createSessionStores(
580641 } ,
581642 async commit ( options , signal ) {
582643 return withResolvedDirLock ( dir , async ( ) => {
644+ if ( unpublishedRewrite !== null ) {
645+ await writeSegmented ( writeTurnsSegmented , unpublishedRewrite ) ;
646+ liveTurnRefs = unpublishedRewrite ;
647+ unpublishedRewrite = null ;
648+ }
583649 const toAdd : string [ ] = [ ] ;
584650 const toRemove : string [ ] = [ ] ;
585651
@@ -596,6 +662,10 @@ export async function createSessionStores(
596662 await reconcileSegmentStaging ( dir , TURNS_FILE , toAdd , toRemove ) ;
597663 await reconcileSegmentStaging ( dir , PROMPT_FILE , toAdd , toRemove ) ;
598664
665+ if ( await pathExists ( path . join ( dir , EVIDENCE_ARCHIVE_DIR ) ) ) {
666+ toAdd . push ( EVIDENCE_ARCHIVE_DIR ) ;
667+ }
668+
599669 const add = extraCommitPaths ( [ ...new Set ( toAdd ) ] ) ;
600670 const remove = extraCommitPaths ( [ ...new Set ( toRemove ) ] ) . filter (
601671 ( p ) => ! add . includes ( p ) ,
0 commit comments