@@ -649,6 +649,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
649649
650650 const sessionState : SessionState = createTestSessionState ( ) ;
651651 vi . spyOn ( codexAcpAgent , "getSessionState" ) . mockReturnValue ( sessionState ) ;
652+ // @ts -expect-error seeding private session store for slash-command test
653+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
652654
653655 await codexAcpAgent . prompt ( { sessionId : "session-id" , prompt : [ { type : "text" , text : "/status" } ] } ) ;
654656 await expect ( mockFixture . getAcpConnectionDump ( [ ] ) ) . toMatchFileSnapshot ( "data/command-status.json" ) ;
@@ -661,6 +663,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
661663 const sessionState : SessionState = createTestSessionState ( ) ;
662664
663665 const logoutSpy = vi . spyOn ( mockFixture . getCodexAcpClient ( ) , "logout" ) . mockResolvedValue ( { } ) ;
666+ // @ts -expect-error seeding private session store for slash-command test
667+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
664668
665669 // @ts -expect-error - exercising private helper
666670 const handled = await codexAcpAgent . availableCommands . handleCommand ( { name : "logout" , input : null } , sessionState ) ;
@@ -687,6 +691,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
687691 } ]
688692 } ;
689693 const skillsSpy = vi . spyOn ( mockFixture . getCodexAcpClient ( ) , "listSkills" ) . mockResolvedValue ( skillsResponse ) ;
694+ // @ts -expect-error seeding private session store for slash-command test
695+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
690696
691697 // @ts -expect-error - exercising private helper
692698 const handled = await codexAcpAgent . availableCommands . handleCommand ( { name : "skills" , input : null } , sessionState ) ;
@@ -722,6 +728,8 @@ describe('ACP server test', { timeout: 40_000 }, () => {
722728 nextCursor : null
723729 } ;
724730 const mcpSpy = vi . spyOn ( mockFixture . getCodexAcpClient ( ) , "listMcpServers" ) . mockResolvedValue ( mcpResponse ) ;
731+ // @ts -expect-error seeding private session store for slash-command test
732+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
725733
726734 // @ts -expect-error - exercising private helper
727735 const handled = await codexAcpAgent . availableCommands . handleCommand ( { name : "mcp" , input : null } , sessionState ) ;
@@ -811,7 +819,10 @@ describe('ACP server test', { timeout: 40_000 }, () => {
811819 durationMs : null ,
812820 }
813821 } ) ;
814- vi . spyOn ( mockFixture . getCodexAcpAgent ( ) , "getSessionState" ) . mockReturnValue ( sessionState ) ;
822+ const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
823+ vi . spyOn ( codexAcpAgent , "getSessionState" ) . mockReturnValue ( sessionState ) ;
824+ // @ts -expect-error seeding private session store for prompt test
825+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
815826 return { mockFixture, sessionState, turnStartSpy } ;
816827 }
817828
@@ -1055,6 +1066,24 @@ describe('ACP server test', { timeout: 40_000 }, () => {
10551066 expect ( codexAppServerClient . approvalHandlers . has ( "session-close" ) ) . toBe ( true ) ;
10561067 // @ts -expect-error verifying test-only access to private maps
10571068 expect ( codexAppServerClient . elicitationHandlers . has ( "session-close" ) ) . toBe ( true ) ;
1069+ // @ts -expect-error verifying test-only access to private maps
1070+ codexAppServerClient . turnCompletedResolvers . set ( "session-close" , [ {
1071+ turnId : "other-turn" ,
1072+ resolve : vi . fn ( ) ,
1073+ } ] ) ;
1074+ // @ts -expect-error verifying test-only access to private maps
1075+ codexAppServerClient . lastTurnCompletedByThread . set ( "session-close" , {
1076+ threadId : "session-close" ,
1077+ turn : {
1078+ id : "completed-turn" ,
1079+ items : [ ] ,
1080+ status : "completed" ,
1081+ error : null ,
1082+ startedAt : null ,
1083+ completedAt : null ,
1084+ durationMs : null ,
1085+ } ,
1086+ } ) ;
10581087
10591088 await codexAcpClient . closeSession ( "session-close" , "turn-close" ) ;
10601089
@@ -1079,6 +1108,10 @@ describe('ACP server test', { timeout: 40_000 }, () => {
10791108 expect ( codexAppServerClient . approvalHandlers . has ( "session-close" ) ) . toBe ( false ) ;
10801109 // @ts -expect-error verifying test-only access to private maps
10811110 expect ( codexAppServerClient . elicitationHandlers . has ( "session-close" ) ) . toBe ( false ) ;
1111+ // @ts -expect-error verifying test-only access to private maps
1112+ expect ( codexAppServerClient . turnCompletedResolvers . has ( "session-close" ) ) . toBe ( false ) ;
1113+ // @ts -expect-error verifying test-only access to private maps
1114+ expect ( codexAppServerClient . lastTurnCompletedByThread . has ( "session-close" ) ) . toBe ( false ) ;
10821115 } ) ;
10831116
10841117 it ( 'waits for the matching turn completion before resolving a prompt' , async ( ) => {
@@ -1212,6 +1245,45 @@ describe('ACP server test', { timeout: 40_000 }, () => {
12121245 expect ( closeSpy ) . toHaveBeenCalledWith ( sessionState . sessionId , "turn-pending" ) ;
12131246 } ) ;
12141247
1248+ it ( 'rejects a prompt if the session closes before turn start begins' , async ( ) => {
1249+ const mockFixture = createCodexMockTestFixture ( ) ;
1250+ const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
1251+ const codexAcpClient = mockFixture . getCodexAcpClient ( ) ;
1252+ const sessionState = createTestSessionState ( {
1253+ sessionId : "session-close" ,
1254+ } ) ;
1255+
1256+ let resolveTryHandle ! : ( handled : boolean ) => void ;
1257+ const tryHandlePromise = new Promise < boolean > ( ( resolve ) => {
1258+ resolveTryHandle = resolve ;
1259+ } ) ;
1260+
1261+ // @ts -expect-error seeding private session store for focused close-session test
1262+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
1263+ // @ts -expect-error exercising private helper through the availableCommands collaborator
1264+ const tryHandleSpy = vi . spyOn ( codexAcpAgent . availableCommands , "tryHandle" ) . mockReturnValue ( tryHandlePromise ) ;
1265+ const startPromptSpy = vi . spyOn ( codexAcpClient , "startPrompt" ) ;
1266+ vi . spyOn ( codexAcpClient , "closeSession" ) . mockResolvedValue ( ) ;
1267+
1268+ const promptPromise = codexAcpAgent . prompt ( {
1269+ sessionId : sessionState . sessionId ,
1270+ prompt : [ { type : "text" , text : "normal prompt" } ] ,
1271+ } ) ;
1272+
1273+ await vi . waitFor ( ( ) => {
1274+ expect ( tryHandleSpy ) . toHaveBeenCalled ( ) ;
1275+ } ) ;
1276+
1277+ await expect (
1278+ codexAcpAgent . unstable_closeSession ( { sessionId : sessionState . sessionId } )
1279+ ) . resolves . toEqual ( { } ) ;
1280+
1281+ resolveTryHandle ( false ) ;
1282+
1283+ await expect ( promptPromise ) . rejects . toThrow ( "Invalid request" ) ;
1284+ expect ( startPromptSpy ) . not . toHaveBeenCalled ( ) ;
1285+ } ) ;
1286+
12151287 it ( 'skips late MCP startup updates after a session is closed' , async ( ) => {
12161288 const mockFixture = createCodexMockTestFixture ( ) ;
12171289 const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
@@ -1224,7 +1296,7 @@ describe('ACP server test', { timeout: 40_000 }, () => {
12241296 const startupPromise = new Promise ( ( resolve ) => {
12251297 resolveStartup = resolve ;
12261298 } ) ;
1227- vi . spyOn ( codexAcpClient , "awaitMcpStartupResult " ) . mockReturnValue ( startupPromise as Promise < any > ) ;
1299+ vi . spyOn ( codexAcpClient , "awaitMcpServerStartup " ) . mockReturnValue ( startupPromise as Promise < any > ) ;
12281300
12291301 // @ts -expect-error seeding private session store for focused close-session test
12301302 codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
@@ -1288,4 +1360,45 @@ describe('ACP server test', { timeout: 40_000 }, () => {
12881360
12891361 expect ( mockFixture . getAcpConnectionEvents ( [ ] ) ) . toEqual ( [ ] ) ;
12901362 } ) ;
1363+
1364+ it ( 'skips late slash command updates after a session is closed' , async ( ) => {
1365+ const mockFixture = createCodexMockTestFixture ( ) ;
1366+ const codexAcpAgent = mockFixture . getCodexAcpAgent ( ) ;
1367+ const codexAcpClient = mockFixture . getCodexAcpClient ( ) ;
1368+ const sessionState = createTestSessionState ( {
1369+ sessionId : "session-close" ,
1370+ } ) ;
1371+
1372+ let resolveSkills ! : ( response : SkillsListResponse ) => void ;
1373+ const skillsPromise = new Promise < SkillsListResponse > ( ( resolve ) => {
1374+ resolveSkills = resolve ;
1375+ } ) ;
1376+ vi . spyOn ( codexAcpClient , "listSkills" ) . mockReturnValue ( skillsPromise ) ;
1377+
1378+ // @ts -expect-error seeding private session store for focused close-session test
1379+ codexAcpAgent . sessions . set ( sessionState . sessionId , sessionState ) ;
1380+
1381+ // @ts -expect-error exercising private helper through the availableCommands collaborator
1382+ const commandPromise = codexAcpAgent . availableCommands . handleCommand ( { name : "skills" , input : null } , sessionState ) ;
1383+ // @ts -expect-error exercising private helper to simulate close-session cleanup
1384+ codexAcpAgent . forgetSession ( sessionState . sessionId ) ;
1385+
1386+ resolveSkills ( {
1387+ data : [ {
1388+ cwd : "/workspace" ,
1389+ skills : [ {
1390+ name : "build" ,
1391+ description : "Build the project" ,
1392+ shortDescription : "Build" ,
1393+ path : "/workspace" ,
1394+ scope : "user" ,
1395+ enabled : true ,
1396+ } ] ,
1397+ errors : [ ] ,
1398+ } ] ,
1399+ } ) ;
1400+
1401+ await expect ( commandPromise ) . resolves . toBe ( true ) ;
1402+ expect ( mockFixture . getAcpConnectionEvents ( [ ] ) ) . toEqual ( [ ] ) ;
1403+ } ) ;
12911404} ) ;
0 commit comments