@@ -2366,6 +2366,116 @@ describe("CodexAdapterV2 post-settle continuation", () => {
23662366 ) ,
23672367 ) ;
23682368
2369+ it . effect . each ( [
2370+ { hook : "stopped" , status : "interrupted" , expected : true } ,
2371+ { hook : "completed" , status : "interrupted" , expected : false } ,
2372+ { hook : "warning" , status : "interrupted" , expected : false } ,
2373+ { hook : "empty" , status : "interrupted" , expected : false } ,
2374+ { hook : "wrong-turn" , status : "interrupted" , expected : false } ,
2375+ { hook : "wrong-thread" , status : "interrupted" , expected : false } ,
2376+ { hook : "unscoped" , status : "interrupted" , expected : false } ,
2377+ { hook : "stopped" , status : "completed" , expected : false } ,
2378+ { hook : "stopped" , status : "failed" , expected : false } ,
2379+ ] as const ) (
2380+ "correlates $hook hook output with a $status turn only" ,
2381+ ( { hook, status, expected } ) =>
2382+ Effect . gen ( function * ( ) {
2383+ const nativeThreadId = "hook-thread" ;
2384+ const stopReason = "Save a current task checkpoint before compaction." ;
2385+ const hookCompleted = ( turnId : string | null ) => ( {
2386+ type : "emit_inbound" as const ,
2387+ label : "hook/completed" ,
2388+ frame : {
2389+ method : "hook/completed" ,
2390+ params : {
2391+ threadId : hook === "wrong-thread" ? "other-thread" : nativeThreadId ,
2392+ turnId : hook === "unscoped" ? null : hook === "wrong-turn" ? "other-turn" : turnId ,
2393+ run : {
2394+ id : "checkpoint-hook" ,
2395+ eventName : "preCompact" ,
2396+ source : "project" ,
2397+ sourcePath : "/workspace/.codex/hooks.json" ,
2398+ scope : "turn" ,
2399+ executionMode : "sync" ,
2400+ handlerType : "command" ,
2401+ displayOrder : 1 ,
2402+ startedAt : 1782622440 ,
2403+ status : hook === "completed" ? "completed" : "stopped" ,
2404+ entries : [
2405+ { kind : "warning" , text : "Unrelated hook diagnostic" } ,
2406+ {
2407+ kind : hook === "warning" ? "warning" : "stop" ,
2408+ text : hook === "empty" ? " " : stopReason ,
2409+ } ,
2410+ ] ,
2411+ } ,
2412+ } ,
2413+ } ,
2414+ } ) ;
2415+ const transcript = makeCodexReplayTranscript ( {
2416+ scenario : `hook-stop-${ hook } -${ status } ` ,
2417+ entries : [
2418+ ...codexReplayPreamble ( { nativeThreadId, nativeTurnId : "hook-turn" , prompt : "work" } ) ,
2419+ hookCompleted ( "hook-turn" ) ,
2420+ {
2421+ type : "emit_inbound" ,
2422+ label : "turn/completed" ,
2423+ frame : {
2424+ method : "turn/completed" ,
2425+ params : {
2426+ threadId : nativeThreadId ,
2427+ turn : makeCodexReplayTurn ( { id : "hook-turn" , status } ) ,
2428+ } ,
2429+ } ,
2430+ } ,
2431+ ...codexReplayPreamble ( { nativeThreadId, nativeTurnId : "next-turn" , prompt : "work" } )
2432+ . slice ( - 3 )
2433+ . map ( ( entry , index ) => ( index < 2 ? withReplayRequestId ( entry , 4 ) : entry ) ) ,
2434+ hookCompleted ( "hook-turn" ) ,
2435+ {
2436+ type : "emit_inbound" ,
2437+ label : "turn/completed/next" ,
2438+ frame : {
2439+ method : "turn/completed" ,
2440+ params : {
2441+ threadId : nativeThreadId ,
2442+ turn : makeCodexReplayTurn ( { id : "next-turn" , status : "interrupted" } ) ,
2443+ } ,
2444+ } ,
2445+ } ,
2446+ ] ,
2447+ } ) ;
2448+ const harness = yield * makeCodexReplayHarness ( transcript ) ;
2449+ for ( const turn of [ "hook-turn" , "next-turn" ] ) {
2450+ yield * harness . runtime . startTurn (
2451+ makeCodexTestTurnInput ( {
2452+ threadId : harness . threadId ,
2453+ providerThread : harness . providerThread ,
2454+ now : yield * DateTime . now ,
2455+ attemptId : RunAttemptId . make ( turn ) ,
2456+ text : "work" ,
2457+ } ) ,
2458+ ) ;
2459+ yield * harness . awaitTerminal ( turn === "hook-turn" ? 1 : 2 ) ;
2460+ }
2461+ assert . deepEqual (
2462+ harness . terminalEvents ( ) . map ( ( event ) => ( {
2463+ status : event . status ,
2464+ reason : event . status === "failed" ? undefined : event . reason ,
2465+ } ) ) ,
2466+ [
2467+ { status, reason : expected ? `preCompact hook (project): ${ stopReason } ` : undefined } ,
2468+ { status : "interrupted" , reason : undefined } ,
2469+ ] ,
2470+ ) ;
2471+ } ) . pipe (
2472+ Effect . scoped ,
2473+ Effect . provide (
2474+ Layer . mergeAll ( IdAllocator . layer , McpProviderSessions . layer , NodeServices . layer ) ,
2475+ ) ,
2476+ ) ,
2477+ ) ;
2478+
23692479 it . effect ( "resumes a provider thread without requesting or decoding its history" , ( ) =>
23702480 Effect . scoped (
23712481 Effect . gen ( function * ( ) {
0 commit comments