@@ -915,4 +915,117 @@ describe('ACP server test', { timeout: 40_000 }, () => {
915915 }
916916 } ) ;
917917 } ) ;
918+
919+ it ( 'closes an active session by interrupting the turn, unsubscribing the thread, and removing listeners' , async ( ) => {
920+ const mockFixture = createCodexMockTestFixture ( ) ;
921+ const codexAcpClient = mockFixture . getCodexAcpClient ( ) ;
922+ const codexAppServerClient = mockFixture . getCodexAppServerClient ( ) ;
923+
924+ await codexAcpClient . subscribeToSessionEvents (
925+ "session-close" ,
926+ vi . fn ( ) ,
927+ {
928+ handleCommandExecution : vi . fn ( ) ,
929+ handleFileChange : vi . fn ( ) ,
930+ } ,
931+ {
932+ handleElicitation : vi . fn ( ) ,
933+ }
934+ ) ;
935+
936+ // @ts -expect-error verifying test-only access to private maps
937+ expect ( codexAppServerClient . notificationHandlers . has ( "session-close" ) ) . toBe ( true ) ;
938+ // @ts -expect-error verifying test-only access to private maps
939+ expect ( codexAppServerClient . approvalHandlers . has ( "session-close" ) ) . toBe ( true ) ;
940+ // @ts -expect-error verifying test-only access to private maps
941+ expect ( codexAppServerClient . elicitationHandlers . has ( "session-close" ) ) . toBe ( true ) ;
942+
943+ await codexAcpClient . closeSession ( "session-close" , "turn-close" ) ;
944+
945+ const closeRequests = mockFixture . getCodexConnectionEvents ( [ ] )
946+ . filter ( ( event ) => event . eventType === "request" ) ;
947+ expect ( closeRequests ) . toEqual ( [
948+ {
949+ eventType : "request" ,
950+ method : "turn/interrupt" ,
951+ params : { threadId : "session-close" , turnId : "turn-close" } ,
952+ } ,
953+ {
954+ eventType : "request" ,
955+ method : "thread/unsubscribe" ,
956+ params : { threadId : "session-close" } ,
957+ } ,
958+ ] ) ;
959+
960+ // @ts -expect-error verifying test-only access to private maps
961+ expect ( codexAppServerClient . notificationHandlers . has ( "session-close" ) ) . toBe ( false ) ;
962+ // @ts -expect-error verifying test-only access to private maps
963+ expect ( codexAppServerClient . approvalHandlers . has ( "session-close" ) ) . toBe ( false ) ;
964+ // @ts -expect-error verifying test-only access to private maps
965+ expect ( codexAppServerClient . elicitationHandlers . has ( "session-close" ) ) . toBe ( false ) ;
966+ } ) ;
967+
968+ it ( 'removes session bookkeeping after unstable_closeSession' , async ( ) => {
969+ const mockFixture = createCodexMockTestFixture ( ) ;
970+ const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
971+ const codexAcpClient = mockFixture . getCodexAcpClient ( ) ;
972+ const sessionState = createTestSessionState ( {
973+ sessionId : "session-close" ,
974+ currentTurnId : "turn-close" ,
975+ } ) ;
976+ const closeSpy = vi . spyOn ( codexAcpClient , "closeSession" ) . mockResolvedValue ( ) ;
977+
978+ // @ts -expect-error seeding private session store for focused close-session test
979+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
980+ // @ts -expect-error seeding private session store for focused close-session test
981+ codexAcpAgent . pendingMcpStartupSessions . set ( sessionState . sessionId , {
982+ requestedServers : new Set ( [ "alpha" ] ) ,
983+ } ) ;
984+
985+ await expect (
986+ codexAcpAgent . unstable_closeSession ( { sessionId : sessionState . sessionId } )
987+ ) . resolves . toEqual ( { } ) ;
988+ expect ( closeSpy ) . toHaveBeenCalledWith ( sessionState . sessionId , sessionState . currentTurnId ) ;
989+ expect ( ( ) => codexAcpAgent . getSessionState ( sessionState . sessionId ) ) . toThrow (
990+ `Session ${ sessionState . sessionId } not found`
991+ ) ;
992+ // @ts -expect-error verifying test-only access to private map
993+ expect ( codexAcpAgent . pendingMcpStartupSessions . has ( sessionState . sessionId ) ) . toBe ( false ) ;
994+ } ) ;
995+
996+ it ( 'skips late MCP startup updates after a session is closed' , async ( ) => {
997+ const mockFixture = createCodexMockTestFixture ( ) ;
998+ const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
999+ const codexAcpClient = mockFixture . getCodexAcpClient ( ) ;
1000+ const sessionState = createTestSessionState ( {
1001+ sessionId : "session-close" ,
1002+ } ) ;
1003+
1004+ let resolveStartup ! : ( event : any ) => void ;
1005+ const startupPromise = new Promise ( ( resolve ) => {
1006+ resolveStartup = resolve ;
1007+ } ) ;
1008+ vi . spyOn ( codexAcpClient , "awaitMcpStartupResult" ) . mockReturnValue ( startupPromise as Promise < any > ) ;
1009+
1010+ // @ts -expect-error seeding private session store for focused close-session test
1011+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
1012+ // @ts -expect-error seeding private session store for focused close-session test
1013+ codexAcpAgent . pendingMcpStartupSessions . set ( sessionState . sessionId , {
1014+ requestedServers : new Set ( [ "alpha" ] ) ,
1015+ } ) ;
1016+
1017+ // @ts -expect-error exercising private helper to verify close-session race handling
1018+ const publishPromise = codexAcpAgent . doPublishMcpStartupStatus ( sessionState . sessionId , 1 ) ;
1019+ // @ts -expect-error exercising private helper to simulate close-session cleanup
1020+ codexAcpAgent . forgetSession ( sessionState . sessionId ) ;
1021+
1022+ resolveStartup ( {
1023+ ready : [ "alpha" ] ,
1024+ failed : [ ] ,
1025+ cancelled : [ ] ,
1026+ } ) ;
1027+ await publishPromise ;
1028+
1029+ expect ( mockFixture . getAcpConnectionEvents ( [ ] ) ) . toEqual ( [ ] ) ;
1030+ } ) ;
9181031} ) ;
0 commit comments