@@ -11,7 +11,6 @@ import { ConfigurationTarget } from '../../../../../../platform/configuration/co
1111import { TestConfigurationService } from '../../../../../../platform/configuration/test/common/testConfigurationService.js' ;
1212import { workbenchInstantiationService } from '../../../../../test/browser/workbenchTestServices.js' ;
1313import { IToolInvocationPreparationContext , IPreparedToolInvocation , ILanguageModelToolsService } from '../../../../chat/common/languageModelToolsService.js' ;
14- import { CommandLineAutoApprover } from '../../browser/commandLineAutoApprover.js' ;
1514import { RunInTerminalTool , type IRunInTerminalInputParams } from '../../browser/runInTerminalTool.js' ;
1615import { TerminalChatAgentToolsSettingId } from '../../common/terminalChatAgentToolsConfiguration.js' ;
1716import { IWorkspaceContextService } from '../../../../../../platform/workspace/common/workspace.js' ;
@@ -20,11 +19,14 @@ import type { TestInstantiationService } from '../../../../../../platform/instan
2019import { ITerminalService , type ITerminalInstance } from '../../../../terminal/browser/terminal.js' ;
2120import { OperatingSystem } from '../../../../../../base/common/platform.js' ;
2221import { Emitter } from '../../../../../../base/common/event.js' ;
22+ import { IChatService } from '../../../../chat/common/chatService.js' ;
23+ import { ShellIntegrationQuality } from '../../browser/toolTerminalCreator.js' ;
2324
2425class TestRunInTerminalTool extends RunInTerminalTool {
2526 protected override _osBackend : Promise < OperatingSystem > = Promise . resolve ( OperatingSystem . Windows ) ;
2627
27- get commandLineAutoApprover ( ) : CommandLineAutoApprover { return this . _commandLineAutoApprover ; }
28+ get commandLineAutoApprover ( ) { return this . _commandLineAutoApprover ; }
29+ get sessionTerminalAssociations ( ) { return this . _sessionTerminalAssociations ; }
2830
2931 async rewriteCommandIfNeeded ( args : IRunInTerminalInputParams , instance : Pick < ITerminalInstance , 'getCwdResource' > | undefined , shell : string ) : Promise < string > {
3032 return this . _rewriteCommandIfNeeded ( args , instance , shell ) ;
@@ -41,11 +43,16 @@ suite('RunInTerminalTool', () => {
4143 let instantiationService : TestInstantiationService ;
4244 let configurationService : TestConfigurationService ;
4345 let workspaceService : TestContextService ;
46+ let terminalServiceDisposeEmitter : Emitter < ITerminalInstance > ;
47+ let chatServiceDisposeEmitter : Emitter < { sessionId : string ; reason : 'cleared' } > ;
4448
4549 let runInTerminalTool : TestRunInTerminalTool ;
4650
4751 setup ( ( ) => {
4852 configurationService = new TestConfigurationService ( ) ;
53+ terminalServiceDisposeEmitter = new Emitter < ITerminalInstance > ( ) ;
54+ chatServiceDisposeEmitter = new Emitter < { sessionId : string ; reason : 'cleared' } > ( ) ;
55+
4956 instantiationService = workbenchInstantiationService ( {
5057 configurationService : ( ) => configurationService ,
5158 } , store ) ;
@@ -55,7 +62,10 @@ suite('RunInTerminalTool', () => {
5562 } ,
5663 } ) ;
5764 instantiationService . stub ( ITerminalService , {
58- onDidDisposeInstance : new Emitter < ITerminalInstance > ( ) . event
65+ onDidDisposeInstance : terminalServiceDisposeEmitter . event
66+ } ) ;
67+ instantiationService . stub ( IChatService , {
68+ onDidDisposeSession : chatServiceDisposeEmitter . event
5969 } ) ;
6070 workspaceService = instantiationService . invokeFunction ( accessor => accessor . get ( IWorkspaceContextService ) ) as TestContextService ;
6171
@@ -611,4 +621,71 @@ suite('RunInTerminalTool', () => {
611621 } ) ;
612622 } ) ;
613623 } ) ;
624+
625+ suite ( 'chat session disposal cleanup' , ( ) => {
626+ test ( 'should dispose associated terminals when chat session is disposed' , ( ) => {
627+ const sessionId = 'test-session-123' ;
628+ const mockTerminal : ITerminalInstance = {
629+ dispose : ( ) => { /* Mock dispose */ } ,
630+ processId : 12345
631+ } as any ;
632+ let terminalDisposed = false ;
633+ mockTerminal . dispose = ( ) => { terminalDisposed = true ; } ;
634+
635+ runInTerminalTool . sessionTerminalAssociations . set ( sessionId , {
636+ instance : mockTerminal ,
637+ shellIntegrationQuality : ShellIntegrationQuality . None
638+ } ) ;
639+
640+ ok ( runInTerminalTool . sessionTerminalAssociations . has ( sessionId ) , 'Terminal association should exist before disposal' ) ;
641+
642+ chatServiceDisposeEmitter . fire ( { sessionId, reason : 'cleared' } ) ;
643+
644+ strictEqual ( terminalDisposed , true , 'Terminal should have been disposed' ) ;
645+ ok ( ! runInTerminalTool . sessionTerminalAssociations . has ( sessionId ) , 'Terminal association should be removed after disposal' ) ;
646+ } ) ;
647+
648+ test ( 'should not affect other sessions when one session is disposed' , ( ) => {
649+ const sessionId1 = 'test-session-1' ;
650+ const sessionId2 = 'test-session-2' ;
651+ const mockTerminal1 : ITerminalInstance = {
652+ dispose : ( ) => { /* Mock dispose */ } ,
653+ processId : 12345
654+ } as any ;
655+ const mockTerminal2 : ITerminalInstance = {
656+ dispose : ( ) => { /* Mock dispose */ } ,
657+ processId : 67890
658+ } as any ;
659+
660+ let terminal1Disposed = false ;
661+ let terminal2Disposed = false ;
662+ mockTerminal1 . dispose = ( ) => { terminal1Disposed = true ; } ;
663+ mockTerminal2 . dispose = ( ) => { terminal2Disposed = true ; } ;
664+
665+ runInTerminalTool . sessionTerminalAssociations . set ( sessionId1 , {
666+ instance : mockTerminal1 ,
667+ shellIntegrationQuality : ShellIntegrationQuality . None
668+ } ) ;
669+ runInTerminalTool . sessionTerminalAssociations . set ( sessionId2 , {
670+ instance : mockTerminal2 ,
671+ shellIntegrationQuality : ShellIntegrationQuality . None
672+ } ) ;
673+
674+ ok ( runInTerminalTool . sessionTerminalAssociations . has ( sessionId1 ) , 'Session 1 terminal association should exist' ) ;
675+ ok ( runInTerminalTool . sessionTerminalAssociations . has ( sessionId2 ) , 'Session 2 terminal association should exist' ) ;
676+
677+ chatServiceDisposeEmitter . fire ( { sessionId : sessionId1 , reason : 'cleared' } ) ;
678+
679+ strictEqual ( terminal1Disposed , true , 'Terminal 1 should have been disposed' ) ;
680+ strictEqual ( terminal2Disposed , false , 'Terminal 2 should NOT have been disposed' ) ;
681+ ok ( ! runInTerminalTool . sessionTerminalAssociations . has ( sessionId1 ) , 'Session 1 terminal association should be removed' ) ;
682+ ok ( runInTerminalTool . sessionTerminalAssociations . has ( sessionId2 ) , 'Session 2 terminal association should remain' ) ;
683+ } ) ;
684+
685+ test ( 'should handle disposal of non-existent session gracefully' , ( ) => {
686+ strictEqual ( runInTerminalTool . sessionTerminalAssociations . size , 0 , 'No associations should exist initially' ) ;
687+ chatServiceDisposeEmitter . fire ( { sessionId : 'non-existent-session' , reason : 'cleared' } ) ;
688+ strictEqual ( runInTerminalTool . sessionTerminalAssociations . size , 0 , 'No associations should exist after handling non-existent session' ) ;
689+ } ) ;
690+ } ) ;
614691} ) ;
0 commit comments