@@ -5958,6 +5958,7 @@ describe("CodexAdapterV2 post-settle continuation", () => {
59585958 nativeThreadId,
59595959 nativeTurnId : "goal-turn-1" ,
59605960 prompt : objective ,
5961+ sentPrompt : `Start working toward this goal: ${ objective } ` ,
59615962 } ) . slice ( 5 ) ,
59625963 5 ,
59635964 ) ,
@@ -6022,6 +6023,8 @@ describe("CodexAdapterV2 post-settle continuation", () => {
60226023 [ providerTurnId ( "goal-turn-2" ) , RunAttemptId . make ( "goal-attempt" ) , 2 ] ,
60236024 ] ,
60246025 ) ;
6026+ // The goal is set paused until its first turn starts; only the result shows.
6027+ assert . notInclude ( providerGoals ( harness . events ) , "paused" ) ;
60256028 assert . equal ( providerGoals ( harness . events ) . at ( - 1 ) , "complete" ) ;
60266029 } ) . pipe (
60276030 Effect . scoped ,
@@ -6204,6 +6207,233 @@ describe("CodexAdapterV2 post-settle continuation", () => {
62046207 ) ,
62056208 ) ;
62066209
6210+ it . effect ( "pauses a goal Codex does not continue before its run settles" , ( ) =>
6211+ Effect . gen ( function * ( ) {
6212+ const prompt = "Keep going" ;
6213+ const transcript = makeCodexReplayTranscript ( {
6214+ scenario : "goal-continuation-expired" ,
6215+ entries : [
6216+ ...codexReplayPreamble ( { nativeThreadId, nativeTurnId : "goal-turn" , prompt } ) ,
6217+ notification ( "goal active" , "thread/goal/updated" , {
6218+ threadId : nativeThreadId ,
6219+ turnId : "goal-turn" ,
6220+ goal : codexGoal ( "active" , 10 ) ,
6221+ } ) ,
6222+ notification ( "turn done" , "turn/completed" , {
6223+ threadId : nativeThreadId ,
6224+ turn : makeCodexReplayTurn ( { id : "goal-turn" , status : "completed" } ) ,
6225+ } ) ,
6226+ notification ( "goal accounted" , "thread/goal/updated" , {
6227+ threadId : nativeThreadId ,
6228+ turnId : null ,
6229+ goal : codexGoal ( "active" , 11 ) ,
6230+ } ) ,
6231+ ...request (
6232+ 4 ,
6233+ "thread/goal/set" ,
6234+ { threadId : nativeThreadId , status : "paused" } ,
6235+ { goal : codexGoal ( "paused" , 11 ) } ,
6236+ ) ,
6237+ ] ,
6238+ } ) ;
6239+ const harness = yield * makeCodexReplayHarness ( transcript ) ;
6240+ yield * harness . runtime . startTurn (
6241+ makeCodexTestTurnInput ( {
6242+ threadId : harness . threadId ,
6243+ providerThread : harness . providerThread ,
6244+ now : yield * DateTime . now ,
6245+ attemptId : RunAttemptId . make ( "goal-expired-attempt" ) ,
6246+ text : prompt ,
6247+ } ) ,
6248+ ) ;
6249+ yield * harness . until ( ( ) =>
6250+ harness . events . some (
6251+ ( event ) =>
6252+ event . type === "provider_thread.updated" &&
6253+ event . providerThread . goal ?. tokensUsed === 11 ,
6254+ ) ,
6255+ ) ;
6256+ assert . lengthOf ( harness . terminalEvents ( ) , 0 ) ;
6257+ yield * TestClock . adjust ( "5 seconds" ) ;
6258+ yield * harness . firstTerminal ;
6259+
6260+ assert . deepEqual ( providerGoals ( harness . events ) , [ "active" , "active" , "paused" ] ) ;
6261+ assert . deepEqual (
6262+ harness . terminalEvents ( ) . map ( ( event ) => event . status ) ,
6263+ [ "completed" ] ,
6264+ ) ;
6265+ } ) . pipe (
6266+ Effect . scoped ,
6267+ Effect . provide (
6268+ Layer . mergeAll ( IdAllocator . layer , McpProviderSessions . layer , NodeServices . layer ) ,
6269+ ) ,
6270+ ) ,
6271+ ) ;
6272+
6273+ it . effect ( "stops a turn Codex starts while the expiry pause is in flight" , ( ) =>
6274+ Effect . gen ( function * ( ) {
6275+ const prompt = "Keep going" ;
6276+ const transcript = makeCodexReplayTranscript ( {
6277+ scenario : "goal-continuation-during-expiry" ,
6278+ entries : [
6279+ ...codexReplayPreamble ( { nativeThreadId, nativeTurnId : "goal-turn" , prompt } ) ,
6280+ notification ( "goal active" , "thread/goal/updated" , {
6281+ threadId : nativeThreadId ,
6282+ turnId : "goal-turn" ,
6283+ goal : codexGoal ( "active" , 10 ) ,
6284+ } ) ,
6285+ notification ( "turn done" , "turn/completed" , {
6286+ threadId : nativeThreadId ,
6287+ turn : makeCodexReplayTurn ( { id : "goal-turn" , status : "completed" } ) ,
6288+ } ) ,
6289+ notification ( "goal accounted" , "thread/goal/updated" , {
6290+ threadId : nativeThreadId ,
6291+ turnId : null ,
6292+ goal : codexGoal ( "active" , 11 ) ,
6293+ } ) ,
6294+ {
6295+ type : "expect_outbound" ,
6296+ label : "pause" ,
6297+ frame : {
6298+ id : 4 ,
6299+ method : "thread/goal/set" ,
6300+ params : { threadId : nativeThreadId , status : "paused" } ,
6301+ } ,
6302+ } ,
6303+ notification ( "goal paused" , "thread/goal/updated" , {
6304+ threadId : nativeThreadId ,
6305+ turnId : null ,
6306+ goal : codexGoal ( "paused" , 11 ) ,
6307+ } ) ,
6308+ notification ( "late continuation" , "turn/started" , {
6309+ threadId : nativeThreadId ,
6310+ turn : makeCodexReplayTurn ( { id : "goal-turn-2" , status : "inProgress" } ) ,
6311+ } ) ,
6312+ ...request (
6313+ 5 ,
6314+ "thread/goal/set" ,
6315+ { threadId : nativeThreadId , status : "paused" } ,
6316+ { goal : codexGoal ( "paused" , 11 ) } ,
6317+ ) ,
6318+ ...request (
6319+ 6 ,
6320+ "turn/interrupt" ,
6321+ { threadId : nativeThreadId , turnId : "goal-turn-2" } ,
6322+ { } ,
6323+ ) ,
6324+ {
6325+ type : "emit_inbound" ,
6326+ label : "paused" ,
6327+ frame : { id : 4 , result : { goal : codexGoal ( "paused" , 11 ) } } ,
6328+ } ,
6329+ ] ,
6330+ } ) ;
6331+ const harness = yield * makeCodexReplayHarness ( transcript ) ;
6332+ yield * harness . runtime . startTurn (
6333+ makeCodexTestTurnInput ( {
6334+ threadId : harness . threadId ,
6335+ providerThread : harness . providerThread ,
6336+ now : yield * DateTime . now ,
6337+ attemptId : RunAttemptId . make ( "goal-race-attempt" ) ,
6338+ text : prompt ,
6339+ } ) ,
6340+ ) ;
6341+ yield * harness . until ( ( ) =>
6342+ harness . events . some (
6343+ ( event ) =>
6344+ event . type === "provider_thread.updated" &&
6345+ event . providerThread . goal ?. tokensUsed === 11 ,
6346+ ) ,
6347+ ) ;
6348+ yield * TestClock . adjust ( "5 seconds" ) ;
6349+ yield * harness . firstTerminal ;
6350+
6351+ assert . deepEqual (
6352+ harness . terminalEvents ( ) . map ( ( event ) => [ event . providerTurnId , event . status ] ) ,
6353+ [
6354+ [
6355+ ( yield * IdAllocator . IdAllocatorV2 ) . derive . providerTurn ( {
6356+ driver : CodexAdapterV2 . CODEX_DRIVER_KIND ,
6357+ nativeTurnId : "goal-turn" ,
6358+ } ) ,
6359+ "completed" ,
6360+ ] ,
6361+ ] ,
6362+ ) ;
6363+ assert . equal ( providerGoals ( harness . events ) . at ( - 1 ) , "paused" ) ;
6364+ } ) . pipe (
6365+ Effect . scoped ,
6366+ Effect . provide (
6367+ Layer . mergeAll ( IdAllocator . layer , McpProviderSessions . layer , NodeServices . layer ) ,
6368+ ) ,
6369+ ) ,
6370+ ) ;
6371+
6372+ it . effect ( "settles the run when Codex never answers the expiry pause" , ( ) =>
6373+ Effect . gen ( function * ( ) {
6374+ const prompt = "Keep going" ;
6375+ const transcript = makeCodexReplayTranscript ( {
6376+ scenario : "goal-expiry-pause-unanswered" ,
6377+ entries : [
6378+ ...codexReplayPreamble ( { nativeThreadId, nativeTurnId : "goal-turn" , prompt } ) ,
6379+ notification ( "goal active" , "thread/goal/updated" , {
6380+ threadId : nativeThreadId ,
6381+ turnId : "goal-turn" ,
6382+ goal : codexGoal ( "active" , 10 ) ,
6383+ } ) ,
6384+ notification ( "turn done" , "turn/completed" , {
6385+ threadId : nativeThreadId ,
6386+ turn : makeCodexReplayTurn ( { id : "goal-turn" , status : "completed" } ) ,
6387+ } ) ,
6388+ notification ( "goal accounted" , "thread/goal/updated" , {
6389+ threadId : nativeThreadId ,
6390+ turnId : null ,
6391+ goal : codexGoal ( "active" , 11 ) ,
6392+ } ) ,
6393+ {
6394+ type : "expect_outbound" ,
6395+ label : "pause" ,
6396+ frame : {
6397+ id : 4 ,
6398+ method : "thread/goal/set" ,
6399+ params : { threadId : nativeThreadId , status : "paused" } ,
6400+ } ,
6401+ } ,
6402+ ] ,
6403+ } ) ;
6404+ const harness = yield * makeCodexReplayHarness ( transcript ) ;
6405+ yield * harness . runtime . startTurn (
6406+ makeCodexTestTurnInput ( {
6407+ threadId : harness . threadId ,
6408+ providerThread : harness . providerThread ,
6409+ now : yield * DateTime . now ,
6410+ attemptId : RunAttemptId . make ( "goal-unanswered-attempt" ) ,
6411+ text : prompt ,
6412+ } ) ,
6413+ ) ;
6414+ yield * harness . until ( ( ) =>
6415+ harness . events . some (
6416+ ( event ) =>
6417+ event . type === "provider_thread.updated" &&
6418+ event . providerThread . goal ?. tokensUsed === 11 ,
6419+ ) ,
6420+ ) ;
6421+ yield * TestClock . adjust ( "5 seconds" ) ;
6422+ yield * TestClock . adjust ( "10 seconds" ) ;
6423+ yield * harness . firstTerminal ;
6424+
6425+ assert . deepEqual (
6426+ harness . terminalEvents ( ) . map ( ( event ) => event . status ) ,
6427+ [ "completed" ] ,
6428+ ) ;
6429+ } ) . pipe (
6430+ Effect . scoped ,
6431+ Effect . provide (
6432+ Layer . mergeAll ( IdAllocator . layer , McpProviderSessions . layer , NodeServices . layer ) ,
6433+ ) ,
6434+ ) ,
6435+ ) ;
6436+
62076437 it . effect ( "pauses an active goal before Stop interrupts its turn" , ( ) =>
62086438 Effect . gen ( function * ( ) {
62096439 const prompt = "Keep going" ;
0 commit comments