44 *--------------------------------------------------------------------------------------------*/
55
66import { Codicon } from '../../../../base/common/codicons.js' ;
7+ import { isEqualOrParent } from '../../../../base/common/extpath.js' ;
78import { Disposable } from '../../../../base/common/lifecycle.js' ;
89import { autorun } from '../../../../base/common/observable.js' ;
910import { URI } from '../../../../base/common/uri.js' ;
@@ -14,7 +15,7 @@ import { ILogService } from '../../../../platform/log/common/log.js';
1415import { IWorkbenchContribution , getWorkbenchContribution , registerWorkbenchContribution2 , WorkbenchPhase } from '../../../../workbench/common/contributions.js' ;
1516import { IAgentSessionsService } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessionsService.js' ;
1617import { AgentSessionProviders } from '../../../../workbench/contrib/chat/browser/agentSessions/agentSessions.js' ;
17- import { ITerminalService } from '../../../../workbench/contrib/terminal/browser/terminal.js' ;
18+ import { ITerminalInstance , ITerminalService } from '../../../../workbench/contrib/terminal/browser/terminal.js' ;
1819import { IPathService } from '../../../../workbench/services/path/common/pathService.js' ;
1920import { Menus } from '../../../browser/menus.js' ;
2021import { IActiveSessionItem , ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js' ;
@@ -83,16 +84,17 @@ export class SessionsTerminalContribution extends Disposable implements IWorkben
8384 }
8485 } ) ) ;
8586
86- // When terminals are restored on startup, ensure visibility matches active session
87+ // When terminals are created externally, try to relate them to the active session
8788 this . _register ( this . _terminalService . onDidCreateInstance ( instance => {
8889 if ( this . _isCreatingTerminal || this . _activeKey === undefined ) {
8990 return ;
9091 }
91- // If this instance is not tracked by us, hide it
92+ // If this instance is already tracked by us, nothing to do
9293 const activeIds = this . _pathToInstanceIds . get ( this . _activeKey ) ;
93- if ( ! activeIds ?. has ( instance . instanceId ) ) {
94- this . _terminalService . moveToBackground ( instance ) ;
94+ if ( activeIds ?. has ( instance . instanceId ) ) {
95+ return ;
9596 }
97+ this . _tryAdoptTerminal ( instance ) ;
9698 } ) ) ;
9799 }
98100
@@ -160,6 +162,58 @@ export class SessionsTerminalContribution extends Disposable implements IWorkben
160162 ids . add ( instanceId ) ;
161163 }
162164
165+ /**
166+ * Attempts to associate an externally-created terminal with the active
167+ * session by checking whether its initial cwd falls within the active
168+ * session's worktree or repository. Hides the terminal if it cannot be
169+ * related.
170+ */
171+ private async _tryAdoptTerminal ( instance : ITerminalInstance ) : Promise < void > {
172+ let cwd : string | undefined ;
173+ try {
174+ cwd = await instance . getInitialCwd ( ) ;
175+ } catch {
176+ return ;
177+ }
178+
179+ if ( instance . isDisposed ) {
180+ return ;
181+ }
182+
183+ const activeKey = this . _activeKey ;
184+ if ( ! activeKey ) {
185+ return ;
186+ }
187+
188+ // Re-check tracking — the terminal may have been adopted while awaiting
189+ const activeIds = this . _pathToInstanceIds . get ( activeKey ) ;
190+ if ( activeIds ?. has ( instance . instanceId ) ) {
191+ return ;
192+ }
193+
194+ const session = this . _sessionsManagementService . activeSession . get ( ) ;
195+ if ( cwd && this . _isRelatedToSession ( cwd , session , activeKey ) ) {
196+ this . _addInstanceToPath ( activeKey , instance . instanceId ) ;
197+ this . _logService . trace ( `[SessionsTerminal] Adopted terminal ${ instance . instanceId } with cwd ${ cwd } ` ) ;
198+ } else {
199+ this . _terminalService . moveToBackground ( instance ) ;
200+ }
201+ }
202+
203+ /**
204+ * Returns whether the given cwd falls within the active session's
205+ * worktree, repository, or the current active key (home dir fallback).
206+ */
207+ private _isRelatedToSession ( cwd : string , session : IActiveSessionItem | undefined , activeKey : string ) : boolean {
208+ if ( isEqualOrParent ( cwd , activeKey , true ) ) {
209+ return true ;
210+ }
211+ if ( session ?. providerType === AgentSessionProviders . Background && session . repository ) {
212+ return isEqualOrParent ( cwd , session . repository . fsPath , true ) ;
213+ }
214+ return false ;
215+ }
216+
163217 /**
164218 * Hides all foreground terminals that do not belong to the given active key
165219 * and shows all background terminals that do belong to it.
@@ -199,6 +253,32 @@ export class SessionsTerminalContribution extends Disposable implements IWorkben
199253 this . _pathToInstanceIds . delete ( key ) ;
200254 }
201255 }
256+
257+ async dumpTracking ( ) : Promise < void > {
258+ const trackedInstanceIds = new Set < number > ( ) ;
259+
260+ console . log ( '[SessionsTerminal] === Tracked Terminals ===' ) ;
261+ for ( const [ key , ids ] of this . _pathToInstanceIds ) {
262+ for ( const instanceId of ids ) {
263+ trackedInstanceIds . add ( instanceId ) ;
264+ const instance = this . _terminalService . getInstanceFromId ( instanceId ) ;
265+ let cwd = '<unknown>' ;
266+ if ( instance ) {
267+ try { cwd = await instance . getInitialCwd ( ) ; } catch { /* ignored */ }
268+ }
269+ console . log ( ` ${ instanceId } - ${ cwd } - ${ key } ` ) ;
270+ }
271+ }
272+
273+ console . log ( '[SessionsTerminal] === Untracked Terminals ===' ) ;
274+ for ( const instance of this . _terminalService . instances ) {
275+ if ( ! trackedInstanceIds . has ( instance . instanceId ) ) {
276+ let cwd = '<unknown>' ;
277+ try { cwd = await instance . getInitialCwd ( ) ; } catch { /* ignored */ }
278+ console . log ( ` ${ instance . instanceId } - ${ cwd } ` ) ;
279+ }
280+ }
281+ }
202282}
203283
204284registerWorkbenchContribution2 ( SessionsTerminalContribution . ID , SessionsTerminalContribution , WorkbenchPhase . AfterRestored ) ;
@@ -231,3 +311,21 @@ class OpenSessionInTerminalAction extends Action2 {
231311}
232312
233313registerAction2 ( OpenSessionInTerminalAction ) ;
314+
315+ class DumpTerminalTrackingAction extends Action2 {
316+
317+ constructor ( ) {
318+ super ( {
319+ id : 'agentSession.dumpTerminalTracking' ,
320+ title : localize2 ( 'dumpTerminalTracking' , "Dump Terminal Tracking" ) ,
321+ f1 : true ,
322+ } ) ;
323+ }
324+
325+ override async run ( ) : Promise < void > {
326+ const contribution = getWorkbenchContribution < SessionsTerminalContribution > ( SessionsTerminalContribution . ID ) ;
327+ await contribution . dumpTracking ( ) ;
328+ }
329+ }
330+
331+ registerAction2 ( DumpTerminalTrackingAction ) ;
0 commit comments