@@ -658,6 +658,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
658658
659659 const sessionState : SessionState = createTestSessionState ( ) ;
660660 vi . spyOn ( codexAcpAgent , "getSessionState" ) . mockReturnValue ( sessionState ) ;
661+ // @ts -expect-error seeding private session store for slash-command test
662+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
661663
662664 await codexAcpAgent . prompt ( { sessionId : "session-id" , prompt : [ { type : "text" , text : "/status" } ] } ) ;
663665 await expect ( mockFixture . getAcpConnectionDump ( [ ] ) ) . toMatchFileSnapshot ( "data/command-status.json" ) ;
@@ -670,6 +672,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
670672 const sessionState : SessionState = createTestSessionState ( ) ;
671673
672674 const logoutSpy = vi . spyOn ( mockFixture . getCodexAcpClient ( ) , "logout" ) . mockResolvedValue ( { } ) ;
675+ // @ts -expect-error seeding private session store for slash-command test
676+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
673677
674678 // @ts -expect-error - exercising private helper
675679 const handled = await codexAcpAgent . availableCommands . handleCommand ( { name : "logout" , input : null } , sessionState ) ;
@@ -696,6 +700,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
696700 } ]
697701 } ;
698702 const skillsSpy = vi . spyOn ( mockFixture . getCodexAcpClient ( ) , "listSkills" ) . mockResolvedValue ( skillsResponse ) ;
703+ // @ts -expect-error seeding private session store for slash-command test
704+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
699705
700706 // @ts -expect-error - exercising private helper
701707 const handled = await codexAcpAgent . availableCommands . handleCommand ( { name : "skills" , input : null } , sessionState ) ;
@@ -731,6 +737,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
731737 nextCursor : null
732738 } ;
733739 const mcpSpy = vi . spyOn ( mockFixture . getCodexAcpClient ( ) , "listMcpServers" ) . mockResolvedValue ( mcpResponse ) ;
740+ // @ts -expect-error seeding private session store for slash-command test
741+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
734742
735743 // @ts -expect-error - exercising private helper
736744 const handled = await codexAcpAgent . availableCommands . handleCommand ( { name : "mcp" , input : null } , sessionState ) ;
@@ -820,7 +828,10 @@ describe('ACP server test', { timeout: 40_000 }, () => {
820828 durationMs : null ,
821829 }
822830 } ) ;
823- vi . spyOn ( mockFixture . getCodexAcpAgent ( ) , "getSessionState" ) . mockReturnValue ( sessionState ) ;
831+ const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
832+ vi . spyOn ( codexAcpAgent , "getSessionState" ) . mockReturnValue ( sessionState ) ;
833+ // @ts -expect-error seeding private session store for prompt test
834+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
824835 return { mockFixture, sessionState, turnStartSpy } ;
825836 }
826837
@@ -1064,6 +1075,24 @@ describe('ACP server test', { timeout: 40_000 }, () => {
10641075 expect ( codexAppServerClient . approvalHandlers . has ( "session-close" ) ) . toBe ( true ) ;
10651076 // @ts -expect-error verifying test-only access to private maps
10661077 expect ( codexAppServerClient . elicitationHandlers . has ( "session-close" ) ) . toBe ( true ) ;
1078+ // @ts -expect-error verifying test-only access to private maps
1079+ codexAppServerClient . turnCompletedResolvers . set ( "session-close" , [ {
1080+ turnId : "other-turn" ,
1081+ resolve : vi . fn ( ) ,
1082+ } ] ) ;
1083+ // @ts -expect-error verifying test-only access to private maps
1084+ codexAppServerClient . lastTurnCompletedByThread . set ( "session-close" , {
1085+ threadId : "session-close" ,
1086+ turn : {
1087+ id : "completed-turn" ,
1088+ items : [ ] ,
1089+ status : "completed" ,
1090+ error : null ,
1091+ startedAt : null ,
1092+ completedAt : null ,
1093+ durationMs : null ,
1094+ } ,
1095+ } ) ;
10671096
10681097 await codexAcpClient . closeSession ( "session-close" , "turn-close" ) ;
10691098
@@ -1088,6 +1117,10 @@ describe('ACP server test', { timeout: 40_000 }, () => {
10881117 expect ( codexAppServerClient . approvalHandlers . has ( "session-close" ) ) . toBe ( false ) ;
10891118 // @ts -expect-error verifying test-only access to private maps
10901119 expect ( codexAppServerClient . elicitationHandlers . has ( "session-close" ) ) . toBe ( false ) ;
1120+ // @ts -expect-error verifying test-only access to private maps
1121+ expect ( codexAppServerClient . turnCompletedResolvers . has ( "session-close" ) ) . toBe ( false ) ;
1122+ // @ts -expect-error verifying test-only access to private maps
1123+ expect ( codexAppServerClient . lastTurnCompletedByThread . has ( "session-close" ) ) . toBe ( false ) ;
10911124 } ) ;
10921125
10931126 it ( 'waits for the matching turn completion before resolving a prompt' , async ( ) => {
@@ -1221,6 +1254,45 @@ describe('ACP server test', { timeout: 40_000 }, () => {
12211254 expect ( closeSpy ) . toHaveBeenCalledWith ( sessionState . sessionId , "turn-pending" ) ;
12221255 } ) ;
12231256
1257+ it ( 'rejects a prompt if the session closes before turn start begins' , async ( ) => {
1258+ const mockFixture = createCodexMockTestFixture ( ) ;
1259+ const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
1260+ const codexAcpClient = mockFixture . getCodexAcpClient ( ) ;
1261+ const sessionState = createTestSessionState ( {
1262+ sessionId : "session-close" ,
1263+ } ) ;
1264+
1265+ let resolveTryHandle ! : ( handled : boolean ) => void ;
1266+ const tryHandlePromise = new Promise < boolean > ( ( resolve ) => {
1267+ resolveTryHandle = resolve ;
1268+ } ) ;
1269+
1270+ // @ts -expect-error seeding private session store for focused close-session test
1271+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
1272+ // @ts -expect-error exercising private helper through the availableCommands collaborator
1273+ const tryHandleSpy = vi . spyOn ( codexAcpAgent . availableCommands , "tryHandle" ) . mockReturnValue ( tryHandlePromise ) ;
1274+ const startPromptSpy = vi . spyOn ( codexAcpClient , "startPrompt" ) ;
1275+ vi . spyOn ( codexAcpClient , "closeSession" ) . mockResolvedValue ( ) ;
1276+
1277+ const promptPromise = codexAcpAgent . prompt ( {
1278+ sessionId : sessionState . sessionId ,
1279+ prompt : [ { type : "text" , text : "normal prompt" } ] ,
1280+ } ) ;
1281+
1282+ await vi . waitFor ( ( ) => {
1283+ expect ( tryHandleSpy ) . toHaveBeenCalled ( ) ;
1284+ } ) ;
1285+
1286+ await expect (
1287+ codexAcpAgent . unstable_closeSession ( { sessionId : sessionState . sessionId } )
1288+ ) . resolves . toEqual ( { } ) ;
1289+
1290+ resolveTryHandle ( false ) ;
1291+
1292+ await expect ( promptPromise ) . rejects . toThrow ( "Invalid request" ) ;
1293+ expect ( startPromptSpy ) . not . toHaveBeenCalled ( ) ;
1294+ } ) ;
1295+
12241296 it ( 'skips late MCP startup updates after a session is closed' , async ( ) => {
12251297 const mockFixture = createCodexMockTestFixture ( ) ;
12261298 const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
@@ -1233,7 +1305,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
12331305 const startupPromise = new Promise ( ( resolve ) => {
12341306 resolveStartup = resolve ;
12351307 } ) ;
1236- vi . spyOn ( codexAcpClient , "awaitMcpStartupResult " ) . mockReturnValue ( startupPromise as Promise < any > ) ;
1308+ vi . spyOn ( codexAcpClient , "awaitMcpServerStartup " ) . mockReturnValue ( startupPromise as Promise < any > ) ;
12371309
12381310 // @ts -expect-error seeding private session store for focused close-session test
12391311 codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
@@ -1297,4 +1369,45 @@ describe('ACP server test', { timeout: 40_000 }, () => {
12971369
12981370 expect ( mockFixture . getAcpConnectionEvents ( [ ] ) ) . toEqual ( [ ] ) ;
12991371 } ) ;
1372+
1373+ it ( 'skips late slash command updates after a session is closed' , async ( ) => {
1374+ const mockFixture = createCodexMockTestFixture ( ) ;
1375+ const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
1376+ const codexAcpClient = mockFixture . getCodexAcpClient ( ) ;
1377+ const sessionState = createTestSessionState ( {
1378+ sessionId : "session-close" ,
1379+ } ) ;
1380+
1381+ let resolveSkills ! : ( response : SkillsListResponse ) => void ;
1382+ const skillsPromise = new Promise < SkillsListResponse > ( ( resolve ) => {
1383+ resolveSkills = resolve ;
1384+ } ) ;
1385+ vi . spyOn ( codexAcpClient , "listSkills" ) . mockReturnValue ( skillsPromise ) ;
1386+
1387+ // @ts -expect-error seeding private session store for focused close-session test
1388+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
1389+
1390+ // @ts -expect-error exercising private helper through the availableCommands collaborator
1391+ const commandPromise = codexAcpAgent . availableCommands . handleCommand ( { name : "skills" , input : null } , sessionState ) ;
1392+ // @ts -expect-error exercising private helper to simulate close-session cleanup
1393+ codexAcpAgent . forgetSession ( sessionState . sessionId ) ;
1394+
1395+ resolveSkills ( {
1396+ data : [ {
1397+ cwd : "/workspace" ,
1398+ skills : [ {
1399+ name : "build" ,
1400+ description : "Build the project" ,
1401+ shortDescription : "Build" ,
1402+ path : "/workspace" ,
1403+ scope : "user" ,
1404+ enabled : true ,
1405+ } ] ,
1406+ errors : [ ] ,
1407+ } ] ,
1408+ } ) ;
1409+
1410+ await expect ( commandPromise ) . resolves . toBe ( true ) ;
1411+ expect ( mockFixture . getAcpConnectionEvents ( [ ] ) ) . toEqual ( [ ] ) ;
1412+ } ) ;
13001413} ) ;
0 commit comments