@@ -1466,3 +1466,82 @@ it.effect("stops the running turn when a goal clear ends the goal driving it", (
14661466 } ) ,
14671467 ) ,
14681468) ;
1469+
1470+ it . effect ( "a side chat that opens with /goal gets its context on the next question" , ( ) =>
1471+ Effect . scoped (
1472+ Effect . gen ( function * ( ) {
1473+ const { events, layer, started, startFirstTurn } = yield * goalHarness ( "side-chat-goal" ) ;
1474+ yield * Effect . gen ( function * ( ) {
1475+ const orchestrator = yield * Orchestrator . OrchestratorV2 ;
1476+ const worker = yield * EffectWorker . OrchestrationEffectWorkerV2 ;
1477+ const parentId = yield * startFirstTurn ;
1478+ const sideChatId = ThreadId . make ( "thread:side-chat-goal-child" ) ;
1479+ yield * orchestrator . dispatch ( {
1480+ type : "thread.fork" ,
1481+ commandId : CommandId . make ( "side-chat" ) ,
1482+ sourceThreadId : parentId ,
1483+ targetThreadId : sideChatId ,
1484+ sourcePoint : { type : "latest_stable" } ,
1485+ sideChat : true ,
1486+ createdBy : "user" ,
1487+ creationSource : "web" ,
1488+ } ) ;
1489+ const ask = ( id : string , text : string ) =>
1490+ orchestrator . dispatch ( {
1491+ type : "message.dispatch" ,
1492+ commandId : CommandId . make ( id ) ,
1493+ threadId : sideChatId ,
1494+ messageId : MessageId . make ( `message:${ id } ` ) ,
1495+ text,
1496+ attachments : [ ] ,
1497+ modelSelection : runSelection ,
1498+ dispatchMode : { type : "start_immediately" } ,
1499+ createdBy : "user" ,
1500+ creationSource : "web" ,
1501+ } ) ;
1502+ const sideChatEvent = ( matches : ( event : OrchestrationV2DomainEvent ) => boolean ) =>
1503+ orchestrator . streamDomainEvents . pipe (
1504+ Stream . filter ( ( event ) => event . threadId === sideChatId && matches ( event ) ) ,
1505+ Stream . take ( 1 ) ,
1506+ Stream . runDrain ,
1507+ Effect . forkScoped ( { startImmediately : true } ) ,
1508+ ) ;
1509+ const running = yield * sideChatEvent (
1510+ ( event ) => event . type === "provider-turn.updated" && event . payload . status === "running" ,
1511+ ) ;
1512+ const settled = yield * sideChatEvent (
1513+ ( event ) => event . type === "run.updated" && event . payload . status === "waiting" ,
1514+ ) ;
1515+ yield * ask ( "goal" , "/goal clear" ) ;
1516+ yield * worker . drain ( ) ;
1517+ yield * Fiber . join ( running ) ;
1518+ const turn = ( yield * orchestrator . getThreadProjection ( sideChatId ) ) . providerTurns [ 0 ] ! ;
1519+ yield * Queue . offer ( events , {
1520+ type : "provider_turn.updated" ,
1521+ driver,
1522+ providerTurn : { ...turn , status : "completed" , completedAt : yield * DateTime . now } ,
1523+ } ) ;
1524+ yield * Queue . offer ( events , {
1525+ type : "turn.terminal" ,
1526+ driver,
1527+ providerThreadId : turn . providerThreadId ,
1528+ providerTurnId : turn . id ,
1529+ runOrdinal : started [ 1 ] ! . runOrdinal ,
1530+ status : "completed" ,
1531+ failure : null ,
1532+ threadDisposition : "reusable" ,
1533+ } ) ;
1534+ yield * Fiber . join ( settled ) ;
1535+ yield * worker . drain ( ) ;
1536+ yield * ask ( "question" , "What is the main thread doing?" ) ;
1537+ yield * worker . drain ( ) ;
1538+
1539+ const [ first , goal , question ] = started . map ( ( turn ) => turn . message . text ) ;
1540+ assert . deepEqual ( [ first , goal ] , [ "first" , "/goal clear" ] ) ;
1541+ assert . include ( question , "You are in a side chat beside the main thread." ) ;
1542+ assert . include ( question , "- User: first" ) ;
1543+ assert . isTrue ( question ?. endsWith ( "What is the main thread doing?" ) ) ;
1544+ } ) . pipe ( Effect . provide ( layer ) ) ;
1545+ } ) ,
1546+ ) ,
1547+ ) ;
0 commit comments