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