@@ -13,13 +13,15 @@ import { URI } from '../../../../base/common/uri.js';
1313import { generateUuid } from '../../../../base/common/uuid.js' ;
1414import { localize } from '../../../../nls.js' ;
1515import { IAgentHostConnectionsService } from '../../../../platform/agentHost/common/agentHostConnectionsService.js' ;
16+ import { resolveAgentHostSessionTrustFolders } from '../../../../platform/agentHost/common/agentHostWorkspaceTrust.js' ;
1617import { toRemoteSessionMessageMetadata } from '../../../../platform/agentHost/common/meta/agentRemoteSessionMeta.js' ;
1718import { buildOpenSessionLinkUri } from '../../../../platform/agentHost/common/openSessionLink.js' ;
1819import { RemoteAgentHostsEnabledSettingId } from '../../../../platform/agentHost/common/remoteAgentHostService.js' ;
1920import { ChatInteractivity as ProtocolChatInteractivity } from '../../../../platform/agentHost/common/state/protocol/state.js' ;
2021import { ActionType } from '../../../../platform/agentHost/common/state/sessionActions.js' ;
21- import { DEFAULT_CHAT_ID , effectiveChatInteractivity , getSessionChatResource , isSessionStatusArchived , MessageKind , parseChatUri , PendingMessageKind , StateComponents } from '../../../../platform/agentHost/common/state/sessionState.js' ;
22+ import { DEFAULT_CHAT_ID , effectiveChatInteractivity , getSessionChatResource , isSessionStatusArchived , MessageKind , parseChatUri , PendingMessageKind , readSessionWorkspaceless , SessionState , StateComponents } from '../../../../platform/agentHost/common/state/sessionState.js' ;
2223import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js' ;
24+ import { IWorkspaceTrustManagementService } from '../../../../platform/workspace/common/workspaceTrust.js' ;
2325import { isAgentHostProvider } from '../../../common/agentHostSessionsProvider.js' ;
2426import { ISessionsProvidersService } from '../../../services/sessions/browser/sessionsProvidersService.js' ;
2527import { ChatInteractivity } from '../../../services/sessions/common/session.js' ;
@@ -73,6 +75,7 @@ export class RemoteSessionMessageRouter {
7375 @IAgentHostConnectionsService private readonly connectionsService : IAgentHostConnectionsService ,
7476 @IConfigurationService private readonly configurationService : IConfigurationService ,
7577 @IRemoteSessionChatService private readonly backgroundChats : IRemoteSessionChatService ,
78+ @IWorkspaceTrustManagementService private readonly workspaceTrustService : IWorkspaceTrustManagementService ,
7679 ) { }
7780
7881 async prepareTarget ( source : URI , target : string , token : CancellationToken ) : Promise < IRemoteMessageTarget > {
@@ -170,45 +173,70 @@ export class RemoteSessionMessageRouter {
170173 } ;
171174 }
172175
176+ private async checkTargetTrust ( target : IResolvedRemoteChat , state : SessionState , token : CancellationToken ) : Promise < void > {
177+ if ( ! readSessionWorkspaceless ( state . _meta ) && state . workingDirectories === undefined ) {
178+ throw new Error ( localize ( 'remoteMessage.unknownWorkspace' , "The target session's working directories are unavailable. Open the session before sending a remote message." ) ) ;
179+ }
180+ const folders = await raceCancellationError ( resolveAgentHostSessionTrustFolders (
181+ state , this . workspaceTrustService , resource => target . host . connection . resourceUris . fromAgentHost ( resource ) ,
182+ ) , token ) ;
183+ if ( folders === undefined ) {
184+ return ;
185+ }
186+ const trusted = folders . length === 0
187+ ? this . workspaceTrustService . isWorkspaceTrusted ( )
188+ : ( await raceCancellationError ( Promise . all ( folders . map ( folder => this . workspaceTrustService . getUriTrustInfo ( folder ) ) ) , token ) ) . every ( info => info . trusted ) ;
189+ if ( ! trusted ) {
190+ throw new Error ( localize ( 'remoteMessage.untrustedWorkspace' , "The target session's workspace is not trusted. Trust it before sending a remote message." ) ) ;
191+ }
192+ }
193+
173194 private async doSend ( source : IResolvedRemoteChat , target : IResolvedRemoteChat , options : ISendRemoteMessageOptions , token : CancellationToken ) : Promise < ISendRemoteMessageResult > {
174195 const store = new DisposableStore ( ) ;
175196 let dispatched = false ;
176197 let rejected = false ;
177198 let confirmed = false ;
178199 let started = false ;
179- let connectionError : Error | undefined ;
200+ let operationError : Error | undefined ;
180201 let background : IRemoteSessionChatReference | undefined ;
181202 try {
182203 const cancellation = store . add ( new CancellationTokenSource ( token ) ) ;
183- store . add ( disposableTimeout ( ( ) => cancellation . cancel ( ) , 10_000 ) ) ;
204+ store . add ( disposableTimeout ( ( ) => {
205+ operationError = new Error ( dispatched
206+ ? localize ( 'remoteMessage.acknowledgementTimeout' , "Timed out waiting for remote message acknowledgement." )
207+ : localize ( 'remoteMessage.preparationTimeout' , "Timed out preparing the remote message." ) ) ;
208+ cancellation . cancel ( ) ;
209+ } , 10_000 ) ) ;
184210 const checkConnections = ( ) => {
185211 try {
186212 this . checkConnected ( source ) ;
187213 this . checkConnected ( target ) ;
188214 } catch ( error ) {
189- connectionError = error instanceof Error ? error : new Error ( toErrorMessage ( error ) ) ;
215+ operationError = error instanceof Error ? error : new Error ( toErrorMessage ( error ) ) ;
190216 cancellation . cancel ( ) ;
191217 }
192218 } ;
193219 store . add ( this . connectionsService . onDidChangeConnections ( checkConnections ) ) ;
194220 checkConnections ( ) ;
195221 const connection = target . host . connection ;
196222 const session = store . add ( connection . getSubscription ( StateComponents . Session , target . host . backendSession , 'RemoteSessionMessageRouter' ) ) ;
197- const sessionState = await readRemoteSessionState ( session . object , cancellation . token ) ;
223+ const sessionState = await readRemoteSessionState ( session . object , cancellation . token , true ) ;
198224 const chatResource = getSessionChatResource ( sessionState , target . chat . fragment || DEFAULT_CHAT_ID ) ;
199225 const identity = chatResource ? parseChatUri ( chatResource ) : undefined ;
200226 if ( ! chatResource || ! identity || ! isEqual ( URI . parse ( identity . session ) , target . host . backendSession ) ) {
201227 throw new Error ( localize ( 'remoteMessage.missingChat' , "The exact target chat no longer exists on its agent host." ) ) ;
202228 }
203229 const chat = store . add ( connection . getSubscription ( StateComponents . Chat , URI . parse ( chatResource ) , 'RemoteSessionMessageRouter' ) ) ;
204- const chatState = await readRemoteSessionState ( chat . object , cancellation . token ) ;
230+ const chatState = await readRemoteSessionState ( chat . object , cancellation . token , true ) ;
205231 if ( ! isEqual ( URI . parse ( chatState . resource ) , URI . parse ( chatResource ) ) ) {
206232 throw new Error ( localize ( 'remoteMessage.chatChanged' , "The target chat identity changed while preparing the message." ) ) ;
207233 }
208234 if ( effectiveChatInteractivity ( chatState . interactivity , isSessionStatusArchived ( sessionState . status ) ) !== ProtocolChatInteractivity . Full ) {
209235 throw new Error ( localize ( 'remoteMessage.readOnly' , "The target chat is archived or read-only." ) ) ;
210236 }
237+ await this . checkTargetTrust ( target , sessionState , cancellation . token ) ;
211238 background = await this . backgroundChats . acquire ( target . chat , cancellation . token , true ) ;
239+ await this . checkTargetTrust ( target , await readRemoteSessionState ( session . object , cancellation . token , true ) , cancellation . token ) ;
212240 this . checkEnabled ( ) ;
213241 this . checkConnected ( source ) ;
214242 this . checkConnected ( target ) ;
@@ -254,13 +282,13 @@ export class RemoteSessionMessageRouter {
254282 await raceCancellationError ( accepted , cancellation . token ) ;
255283 } catch ( error ) {
256284 if ( ! confirmed && dispatched && ! rejected ) {
257- throw new Error ( localize ( 'remoteMessage.unconfirmed' , "Remote message delivery was not confirmed. It may already be queued; do not retry automatically. {0}" , toErrorMessage ( connectionError ?? error ) ) ) ;
285+ throw new Error ( localize ( 'remoteMessage.unconfirmed' , "Remote message delivery was not confirmed. It may already be queued; do not retry automatically. {0}" , toErrorMessage ( operationError ?? error ) ) ) ;
258286 }
259287 if ( ! confirmed ) {
260- throw connectionError ?? error ;
288+ throw operationError ?? error ;
261289 }
262290 } finally {
263- if ( confirmed ) {
291+ if ( confirmed || ( dispatched && ! rejected ) ) {
264292 background ?. releaseWhenIdle ( ) ;
265293 } else {
266294 background ?. dispose ( ) ;
0 commit comments