@@ -29,6 +29,7 @@ import * as TestClock from "effect/testing/TestClock";
2929
3030import { AgentScope } from "@t3tools/shared/AgentScope" ;
3131
32+ import * as ServerEnvironment from "../environment/ServerEnvironment.ts" ;
3233import * as ServerSettings from "../serverSettings.ts" ;
3334import * as SqlitePersistence from "../persistence/Sqlite.ts" ;
3435import * as CommandReceiptStore from "./CommandReceiptStore.ts" ;
@@ -873,3 +874,76 @@ it.effect("Force stop refuses once a newer run has started, before killing anyth
873874 expect ( killed ) . toBe ( false ) ;
874875 } ) ,
875876) ;
877+
878+ it . effect (
879+ "admits two transcript exports at a time and frees a slot on failure or interruption" ,
880+ ( ) =>
881+ Effect . gen ( function * ( ) {
882+ const started = yield * Queue . unbounded < string > ( ) ;
883+ const gates = new Map (
884+ [ "a" , "b" , "c" , "d" , "e" , "f" ] . map ( ( id ) => [
885+ id ,
886+ Deferred . makeUnsafe < void , Orchestrator . OrchestratorV2Error > ( ) ,
887+ ] ) ,
888+ ) ;
889+ const layerTest = ThreadManagementService . layer . pipe (
890+ Layer . provide ( Layer . mock ( ProviderSessionManager . ProviderSessionManagerV2 ) ( { } ) ) ,
891+ Layer . provide (
892+ Layer . mock ( Orchestrator . OrchestratorV2 ) ( {
893+ getThreadSnapshot : ( threadId ) =>
894+ Queue . offer ( started , threadId ) . pipe (
895+ Effect . andThen ( Deferred . await ( gates . get ( threadId ) ! ) ) ,
896+ Effect . as ( {
897+ schemaVersion : 1 ,
898+ snapshotSequence : 1 ,
899+ projection : {
900+ thread : { id : threadId , title : threadId } ,
901+ updatedAt : DateTime . makeUnsafe ( 0 ) ,
902+ visibleTurnItems : [ ] ,
903+ } as unknown as OrchestrationV2ThreadProjection ,
904+ } ) ,
905+ ) ,
906+ } ) ,
907+ ) ,
908+ Layer . provide ( ServerSettings . layerTest ( ) ) ,
909+ Layer . provide ( layerReceiptsTest ) ,
910+ ) ;
911+
912+ yield * Effect . gen ( function * ( ) {
913+ const threads = yield * ThreadManagementService . ThreadManagementService ;
914+ const exportThread = ( id : string ) =>
915+ threads . getThreadTranscript ( ThreadId . make ( id ) ) . pipe (
916+ Effect . provideService ( ServerEnvironment . ServerEnvironmentIdentity , {
917+ getEnvironmentId : Effect . succeed ( EnvironmentId . make ( "environment:export" ) ) ,
918+ } ) ,
919+ Effect . forkChild ( { startImmediately : true } ) ,
920+ ) ;
921+ const a = yield * exportThread ( "a" ) ;
922+ const b = yield * exportThread ( "b" ) ;
923+ const c = yield * exportThread ( "c" ) ;
924+ const d = yield * exportThread ( "d" ) ;
925+ const e = yield * exportThread ( "e" ) ;
926+ const f = yield * exportThread ( "f" ) ;
927+ expect ( yield * Queue . takeAll ( started ) ) . toEqual ( [ "a" , "b" ] ) ;
928+
929+ yield * Fiber . interrupt ( c ) ;
930+ yield * Deferred . fail (
931+ gates . get ( "a" ) ! ,
932+ new Orchestrator . OrchestratorProjectionError ( { threadId : ThreadId . make ( "a" ) , cause : "" } ) ,
933+ ) ;
934+ expect ( yield * Queue . take ( started ) ) . toBe ( "d" ) ;
935+ yield * Fiber . interrupt ( b ) ;
936+ expect ( yield * Queue . take ( started ) ) . toBe ( "e" ) ;
937+ yield * Deferred . succeed ( gates . get ( "d" ) ! , undefined ) ;
938+ expect ( yield * Queue . take ( started ) ) . toBe ( "f" ) ;
939+ yield * Deferred . succeed ( gates . get ( "e" ) ! , undefined ) ;
940+ yield * Deferred . succeed ( gates . get ( "f" ) ! , undefined ) ;
941+
942+ expect ( ( yield * Fiber . await ( a ) ) . _tag ) . toBe ( "Failure" ) ;
943+ expect ( ( yield * Fiber . join ( d ) ) . threadId ) . toBe ( "d" ) ;
944+ expect ( ( yield * Fiber . join ( e ) ) . threadId ) . toBe ( "e" ) ;
945+ expect ( ( yield * Fiber . join ( f ) ) . threadId ) . toBe ( "f" ) ;
946+ expect ( yield * Queue . size ( started ) ) . toBe ( 0 ) ;
947+ } ) . pipe ( Effect . provide ( layerTest ) ) ;
948+ } ) ,
949+ ) ;
0 commit comments