33 * Licensed under the MIT License. See License.txt in the project root for license information.
44 *--------------------------------------------------------------------------------------------*/
55
6- import { Disposable , IDisposable , toDisposable } from '../../../../base/common/lifecycle.js' ;
6+ import { CancellationToken , CancellationTokenSource } from '../../../../base/common/cancellation.js' ;
7+ import { Disposable , DisposableResourceMap , IDisposable , toDisposable } from '../../../../base/common/lifecycle.js' ;
78import { autorun , IObservable , IReader , observableSignalFromEvent , observableValue } from '../../../../base/common/observable.js' ;
89import { isEqual } from '../../../../base/common/resources.js' ;
910import { ResourceMap } from '../../../../base/common/map.js' ;
@@ -12,6 +13,7 @@ import { IConfigurationService } from '../../../../platform/configuration/common
1213import { createDecorator } from '../../../../platform/instantiation/common/instantiation.js' ;
1314import { IActiveSession , ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js' ;
1415import { ChatInteractivity , IChat , ISession } from '../../../services/sessions/common/session.js' ;
16+ import { ISessionsPartService } from '../../../services/sessions/browser/sessionsPartService.js' ;
1517import { ISessionsService } from '../../../services/sessions/browser/sessionsService.js' ;
1618
1719export const AGENT_SESSIONS_TRANSIENT_SIDE_CHAT_SETTING = 'chat.agentSessions.transientSideChat' ;
@@ -34,11 +36,22 @@ export interface IResolvedTransientSideChatState extends ITransientSideChatState
3436
3537export const ITransientSideChatService = createDecorator < ITransientSideChatService > ( 'transientSideChatService' ) ;
3638
39+ export const enum TransientSideChatPresentationResult {
40+ Shown = 'shown' ,
41+ Unavailable = 'unavailable' ,
42+ Superseded = 'superseded' ,
43+ }
44+
45+ export interface ITransientSideChatPresentation extends IDisposable {
46+ readonly token : CancellationToken ;
47+ show ( session : ISession , sideChat : IChat , question : string ) : Promise < TransientSideChatPresentationResult > ;
48+ }
49+
3750export interface ITransientSideChatService {
3851 readonly _serviceBrand : undefined ;
3952 readonly states : IObservable < readonly ITransientSideChatState [ ] > ;
4053 registerHost ( sourceChat : URI ) : IDisposable ;
41- show ( session : ISession , sourceChat : IChat , sideChat : IChat , question : string ) : Promise < boolean > ;
54+ beginPresentation ( sourceChat : IChat ) : ITransientSideChatPresentation ;
4255 resolveState ( state : ITransientSideChatState , reader ?: IReader ) : IResolvedTransientSideChatState | undefined ;
4356 promote ( sourceChat : URI ) : Promise < boolean > ;
4457 dismiss ( sourceChat : URI ) : void ;
@@ -53,14 +66,14 @@ export class TransientSideChatService extends Disposable implements ITransientSi
5366 readonly states : IObservable < readonly ITransientSideChatState [ ] > = this . _states ;
5467
5568 private readonly _hosts = new ResourceMap < Set < object > > ( ) ;
56- private readonly _presentationIds = new ResourceMap < number > ( ) ;
69+ private readonly _presentations = this . _register ( new DisposableResourceMap < CancellationTokenSource > ( ) ) ;
5770 private readonly _catalogChanged : IObservable < void > ;
58- private _presentationIdPool = 0 ;
5971
6072 constructor (
6173 @ISessionsService private readonly sessionsService : ISessionsService ,
6274 @ISessionsManagementService private readonly sessionsManagementService : ISessionsManagementService ,
6375 @IConfigurationService private readonly configurationService : IConfigurationService ,
76+ @ISessionsPartService private readonly sessionsPartService : ISessionsPartService ,
6477 ) {
6578 super ( ) ;
6679 this . _catalogChanged = observableSignalFromEvent ( this , sessionsManagementService . onDidChangeSessions ) ;
@@ -86,7 +99,7 @@ export class TransientSideChatService extends Disposable implements ITransientSi
8699 } ) ) ;
87100 this . _register ( configurationService . onDidChangeConfiguration ( event => {
88101 if ( event . affectsConfiguration ( AGENT_SESSIONS_TRANSIENT_SIDE_CHAT_SETTING ) && ! this . _isEnabled ( ) ) {
89- this . _presentationIds . clear ( ) ;
102+ this . _cancelPresentations ( ) ;
90103 this . _states . set ( [ ] , undefined ) ;
91104 }
92105 } ) ) ;
@@ -117,27 +130,36 @@ export class TransientSideChatService extends Disposable implements ITransientSi
117130 } ) ;
118131 }
119132
120- async show ( session : ISession , sourceChat : IChat , sideChat : IChat , question : string ) : Promise < boolean > {
121- if ( ! this . _isEnabled ( ) ) {
122- return false ;
123- }
124- if ( ! this . _hasHost ( sourceChat . resource ) ) {
125- return false ;
126- }
127- if ( sourceChat . interactivity . get ( ) !== ChatInteractivity . Full ) {
128- return false ;
133+ beginPresentation ( sourceChat : IChat ) : ITransientSideChatPresentation {
134+ this . _presentations . get ( sourceChat . resource ) ?. cancel ( ) ;
135+ const source = new CancellationTokenSource ( ) ;
136+ this . _presentations . set ( sourceChat . resource , source ) ;
137+ const token = source . token ;
138+ return Object . assign ( toDisposable ( ( ) => {
139+ source . cancel ( ) ;
140+ if ( this . _presentations . get ( sourceChat . resource ) === source ) {
141+ this . _presentations . deleteAndDispose ( sourceChat . resource ) ;
142+ }
143+ } ) , {
144+ token,
145+ show : ( session : ISession , sideChat : IChat , question : string ) => this . _show ( session , sourceChat , sideChat , question , token ) ,
146+ } ) ;
147+ }
148+
149+ private async _show ( session : ISession , sourceChat : IChat , sideChat : IChat , question : string , token : CancellationToken ) : Promise < TransientSideChatPresentationResult > {
150+ if ( ! token . isCancellationRequested && ( ! this . _isEnabled ( )
151+ || ! this . _hasHost ( sourceChat . resource )
152+ || sourceChat . interactivity . get ( ) !== ChatInteractivity . Full ) ) {
153+ return TransientSideChatPresentationResult . Unavailable ;
129154 }
130155
131- const presentationId = ++ this . _presentationIdPool ;
132- this . _presentationIds . set ( sourceChat . resource , presentationId ) ;
156+ // Superseded creations remain recoverable without taking over visible navigation.
133157 await this . sessionsService . closeChat ( session , sideChat , { skipHistory : true } ) ;
134- if ( this . _presentationIds . get ( sourceChat . resource ) !== presentationId
135- || ! this . _isEnabled ( )
136- || ! this . _hasHost ( sourceChat . resource ) ) {
137- if ( this . _presentationIds . get ( sourceChat . resource ) === presentationId ) {
138- this . _presentationIds . delete ( sourceChat . resource ) ;
139- }
140- return false ;
158+ if ( token . isCancellationRequested ) {
159+ return TransientSideChatPresentationResult . Superseded ;
160+ }
161+ if ( ! this . _isEnabled ( ) || ! this . _hasHost ( sourceChat . resource ) ) {
162+ return TransientSideChatPresentationResult . Unavailable ;
141163 }
142164 const liveSession = this . sessionsManagementService . getSession ( session . resource ) ;
143165 const liveChats = liveSession ?. chats . get ( ) ;
@@ -147,8 +169,7 @@ export class TransientSideChatService extends Disposable implements ITransientSi
147169 || ! liveSourceChat
148170 || liveSourceChat . interactivity . get ( ) !== ChatInteractivity . Full
149171 || ! liveChats . some ( chat => isEqual ( chat . resource , sideChat . resource ) ) ) {
150- this . _presentationIds . delete ( sourceChat . resource ) ;
151- return false ;
172+ return TransientSideChatPresentationResult . Unavailable ;
152173 }
153174 this . _setState ( {
154175 sessionResource : liveSession . resource ,
@@ -159,7 +180,7 @@ export class TransientSideChatService extends Disposable implements ITransientSi
159180 failed : false ,
160181 replacedExisting : this . _getState ( sourceChat . resource ) !== undefined ,
161182 } ) ;
162- return true ;
183+ return TransientSideChatPresentationResult . Shown ;
163184 }
164185
165186 private _isEnabled ( ) : boolean {
@@ -193,27 +214,34 @@ export class TransientSideChatService extends Disposable implements ITransientSi
193214 throw new Error ( 'The transient side chat is no longer available' ) ;
194215 }
195216
196- this . _setState ( { ... state , promoting : true } ) ;
217+ const presentation = this . beginPresentation ( resolved . sourceChat ) ;
197218 try {
198- await this . sessionsService . openChat ( resolved . session , state . sideChatResource ) ;
219+ this . _setState ( { ...state , promoting : true } ) ;
220+ await this . sessionsService . openChat ( resolved . session , state . sideChatResource , { token : presentation . token } ) ;
221+ if ( presentation . token . isCancellationRequested ) {
222+ return false ;
223+ }
199224 if ( ! this . _isActiveChat ( resolved . session , state . sideChatResource ) ) {
200225 throw new Error ( 'The transient side chat did not open' ) ;
201226 }
227+ this . sessionsPartService . focusSession ( this . sessionsService . activeSession . get ( ) ) ;
202228 const current = this . _getState ( sourceChat ) ;
203229 if ( current && isEqual ( current . sideChatResource , state . sideChatResource ) && current . promoting ) {
204230 this . _remove ( sourceChat ) ;
205231 }
206232 return true ;
207- } catch ( error ) {
233+ } finally {
208234 const current = this . _getState ( sourceChat ) ;
209235 if ( current && isEqual ( current . sideChatResource , state . sideChatResource ) && current . promoting ) {
210236 this . _setState ( { ...current , promoting : false } ) ;
211237 }
212- throw error ;
238+ presentation . dispose ( ) ;
213239 }
214240 }
215241
216242 dismiss ( sourceChat : URI ) : void {
243+ this . _presentations . get ( sourceChat ) ?. cancel ( ) ;
244+ this . _presentations . deleteAndDispose ( sourceChat ) ;
217245 this . _remove ( sourceChat ) ;
218246 }
219247
@@ -259,9 +287,7 @@ export class TransientSideChatService extends Disposable implements ITransientSi
259287 const states = this . _states . get ( ) ;
260288 const next : ITransientSideChatState [ ] = [ ] ;
261289 for ( const state of states ) {
262- if ( predicate ( state ) ) {
263- this . _presentationIds . delete ( state . sourceChatResource ) ;
264- } else {
290+ if ( ! predicate ( state ) ) {
265291 next . push ( state ) ;
266292 }
267293 }
@@ -279,4 +305,16 @@ export class TransientSideChatService extends Disposable implements ITransientSi
279305 return activeSession ?. sessionId === session . sessionId && isEqual ( activeSession . activeChat . get ( ) . resource , chatResource ) ;
280306 }
281307
308+ private _cancelPresentations ( ) : void {
309+ for ( const source of this . _presentations . values ( ) ) {
310+ source . cancel ( ) ;
311+ }
312+ this . _presentations . clearAndDisposeAll ( ) ;
313+ }
314+
315+ override dispose ( ) : void {
316+ this . _cancelPresentations ( ) ;
317+ super . dispose ( ) ;
318+ }
319+
282320}
0 commit comments