From 41837b42ae585827f6641717d4b0d80e50e4d23f Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 11 Apr 2026 17:25:43 -0700 Subject: [PATCH 1/2] Fix agent host session working directories (Written by Copilot) --- package-lock.json | 66 +++++++++---------- package.json | 4 +- remote/package-lock.json | 66 +++++++++---------- remote/package.json | 4 +- .../agentHost/node/copilot/copilotAgent.ts | 9 ++- .../browser/localAgentHost.contribution.ts | 8 +++ .../browser/localAgentHostSessionsProvider.ts | 19 ++++++ .../localAgentHostSessionsProvider.test.ts | 23 +++++++ .../browser/remoteAgentHost.contribution.ts | 20 +++--- .../remoteAgentHostSessionsProvider.ts | 19 ++++++ .../remoteAgentHostSessionsProvider.test.ts | 23 +++++++ .../agentHost/agentHostSessionHandler.ts | 8 ++- ...gentHostSessionWorkingDirectoryResolver.ts | 38 +++++++++++ .../agentHostChatContribution.test.ts | 31 ++++++++- 14 files changed, 254 insertions(+), 84 deletions(-) create mode 100644 src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionWorkingDirectoryResolver.ts diff --git a/package-lock.json b/package-lock.json index cefb186f872..75b987198bc 100644 --- a/package-lock.json +++ b/package-lock.json @@ -11,8 +11,8 @@ "license": "MIT", "dependencies": { "@anthropic-ai/sandbox-runtime": "0.0.42", - "@github/copilot": "^1.0.11", - "@github/copilot-sdk": "^0.2.0", + "@github/copilot": "^1.0.24", + "@github/copilot-sdk": "^0.2.2", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@microsoft/dev-tunnels-connections": "^1.3.41", @@ -1072,26 +1072,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.11.tgz", - "integrity": "sha512-cptVopko/tNKEXyBP174yBjHQBEwg6CqaKN2S0M3J+5LEB8u31bLL75ioOPd+5vubqBrA0liyTdcHeZ8UTRbmg==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.24.tgz", + "integrity": "sha512-/nZ2GwhaGq0HeI3W+6LE0JGw25/bipC6tYVa+oQ5tIvAafBazuNt10CXkeaor+u9oBWLZtPbdTyAzE2tjy9NpQ==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.11", - "@github/copilot-darwin-x64": "1.0.11", - "@github/copilot-linux-arm64": "1.0.11", - "@github/copilot-linux-x64": "1.0.11", - "@github/copilot-win32-arm64": "1.0.11", - "@github/copilot-win32-x64": "1.0.11" + "@github/copilot-darwin-arm64": "1.0.24", + "@github/copilot-darwin-x64": "1.0.24", + "@github/copilot-linux-arm64": "1.0.24", + "@github/copilot-linux-x64": "1.0.24", + "@github/copilot-win32-arm64": "1.0.24", + "@github/copilot-win32-x64": "1.0.24" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.11.tgz", - "integrity": "sha512-wdKimjtbsVeXqMqQSnGpGBPFEYHljxXNuWeH8EIJTNRgFpAsimcivsFgql3Twq4YOp0AxfsH36icG4IEen30mA==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.24.tgz", + "integrity": "sha512-lejn6KV+09rZEICX3nRx9a58DQFQ2kK3NJ3EICfVLngUCWIUmwn1BLezjeTQc9YNasHltA1hulvfsWqX+VjlMw==", "cpu": [ "arm64" ], @@ -1105,9 +1105,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.11.tgz", - "integrity": "sha512-VeuPv8rzBVGBB8uDwMEhcHBpldoKaq26yZ5YQm+G9Ka5QIF+1DMah8ZNRMVsTeNKkb1ji9G8vcuCsaPbnG3fKg==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.24.tgz", + "integrity": "sha512-r2F3keTvr4Bunz3V+waRAvsHgqsVQGyIZFBebsNPWxBX1eh3IXgtBqxCR7vXTFyZonQ8VaiJH3YYEfAhyKsk9g==", "cpu": [ "x64" ], @@ -1121,9 +1121,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.11.tgz", - "integrity": "sha512-/d8p6RlFYKj1Va2hekFIcYNMHWagcEkaxgcllUNXSyQLnmEtXUkaWtz62VKGWE+n/UMkEwCB6vI2xEwPTlUNBQ==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.24.tgz", + "integrity": "sha512-B3oANXKKKLhnKYozXa/W+DxfCQAHJCs0QKR5rBwNrwJbf656twNgALSxWTSJk+1rEP6MrHCswUAcwjwZL7Q+FQ==", "cpu": [ "arm64" ], @@ -1137,9 +1137,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.11.tgz", - "integrity": "sha512-UujTRO3xkPFC1CybchBbCnaTEAG6JrH0etIst07JvfekMWgvRxbiCHQPpDPSzBCPiBcGu0gba0/IT+vUCORuIw==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.24.tgz", + "integrity": "sha512-NGTldizY54B+4Sfhu/GWoEQNMwqqUNgMwbSgBshFv+Hqy1EwuvNWKVov1Y0Vzhp4qAHc6ZxBk/OPIW8Ato9FUg==", "cpu": [ "x64" ], @@ -1153,12 +1153,12 @@ } }, "node_modules/@github/copilot-sdk": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.2.0.tgz", - "integrity": "sha512-fCEpD9W9xqcaCAJmatyNQ1PkET9P9liK2P4Vk0raDFoMXcvpIdqewa5JQeKtWCBUsN/HCz7ExkkFP8peQuo+DA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.2.2.tgz", + "integrity": "sha512-VZCqS08YlUM90bUKJ7VLeIxgTTEHtfXBo84T1IUMNvXRREX2csjPH6Z+CPw3S2468RcCLvzBXcc9LtJJTLIWFw==", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.10", + "@github/copilot": "^1.0.21", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -1176,9 +1176,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.11.tgz", - "integrity": "sha512-EOW8HUM+EmnHEZEa+iUMl4pP1+2eZUk2XCbynYiMehwX9sidc4BxEHp2RuxADSzFPTieQEWzgjQmHWrtet8pQg==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.24.tgz", + "integrity": "sha512-/pd/kgef7/HIIg1SQq4q8fext39pDSC44jHB10KkhfgG1WaDFhQbc/aSSMQfxeldkRbQh6VANp8WtGQdwtMCBA==", "cpu": [ "arm64" ], @@ -1192,9 +1192,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.11.tgz", - "integrity": "sha512-fKGkSNamzs3h9AbmswNvPYJBORCb2Y8CbusijU3C7fT3ohvqnHJwKo5iHhJXLOKZNOpFZgq9YKha410u9sIs6Q==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.24.tgz", + "integrity": "sha512-RDvOiSvyEJwELqErwANJTrdRuMIHkwPE4QK7Le7WsmaSKxiuS4H1Pa8Yxnd2FWrMsCHEbase23GJlymbnGYLXQ==", "cpu": [ "x64" ], diff --git a/package.json b/package.json index 6dbf10c9dc8..20519e3a40f 100644 --- a/package.json +++ b/package.json @@ -88,8 +88,8 @@ }, "dependencies": { "@anthropic-ai/sandbox-runtime": "0.0.42", - "@github/copilot": "^1.0.11", - "@github/copilot-sdk": "^0.2.0", + "@github/copilot": "^1.0.24", + "@github/copilot-sdk": "^0.2.2", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@microsoft/dev-tunnels-connections": "^1.3.41", diff --git a/remote/package-lock.json b/remote/package-lock.json index 88679db0d64..d09143e7a6e 100644 --- a/remote/package-lock.json +++ b/remote/package-lock.json @@ -9,8 +9,8 @@ "version": "0.0.0", "dependencies": { "@anthropic-ai/sandbox-runtime": "0.0.42", - "@github/copilot": "^1.0.11", - "@github/copilot-sdk": "^0.2.0", + "@github/copilot": "^1.0.24", + "@github/copilot-sdk": "^0.2.2", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "^2.5.6", @@ -83,26 +83,26 @@ } }, "node_modules/@github/copilot": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.11.tgz", - "integrity": "sha512-cptVopko/tNKEXyBP174yBjHQBEwg6CqaKN2S0M3J+5LEB8u31bLL75ioOPd+5vubqBrA0liyTdcHeZ8UTRbmg==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot/-/copilot-1.0.24.tgz", + "integrity": "sha512-/nZ2GwhaGq0HeI3W+6LE0JGw25/bipC6tYVa+oQ5tIvAafBazuNt10CXkeaor+u9oBWLZtPbdTyAzE2tjy9NpQ==", "license": "SEE LICENSE IN LICENSE.md", "bin": { "copilot": "npm-loader.js" }, "optionalDependencies": { - "@github/copilot-darwin-arm64": "1.0.11", - "@github/copilot-darwin-x64": "1.0.11", - "@github/copilot-linux-arm64": "1.0.11", - "@github/copilot-linux-x64": "1.0.11", - "@github/copilot-win32-arm64": "1.0.11", - "@github/copilot-win32-x64": "1.0.11" + "@github/copilot-darwin-arm64": "1.0.24", + "@github/copilot-darwin-x64": "1.0.24", + "@github/copilot-linux-arm64": "1.0.24", + "@github/copilot-linux-x64": "1.0.24", + "@github/copilot-win32-arm64": "1.0.24", + "@github/copilot-win32-x64": "1.0.24" } }, "node_modules/@github/copilot-darwin-arm64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.11.tgz", - "integrity": "sha512-wdKimjtbsVeXqMqQSnGpGBPFEYHljxXNuWeH8EIJTNRgFpAsimcivsFgql3Twq4YOp0AxfsH36icG4IEen30mA==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-arm64/-/copilot-darwin-arm64-1.0.24.tgz", + "integrity": "sha512-lejn6KV+09rZEICX3nRx9a58DQFQ2kK3NJ3EICfVLngUCWIUmwn1BLezjeTQc9YNasHltA1hulvfsWqX+VjlMw==", "cpu": [ "arm64" ], @@ -116,9 +116,9 @@ } }, "node_modules/@github/copilot-darwin-x64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.11.tgz", - "integrity": "sha512-VeuPv8rzBVGBB8uDwMEhcHBpldoKaq26yZ5YQm+G9Ka5QIF+1DMah8ZNRMVsTeNKkb1ji9G8vcuCsaPbnG3fKg==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-darwin-x64/-/copilot-darwin-x64-1.0.24.tgz", + "integrity": "sha512-r2F3keTvr4Bunz3V+waRAvsHgqsVQGyIZFBebsNPWxBX1eh3IXgtBqxCR7vXTFyZonQ8VaiJH3YYEfAhyKsk9g==", "cpu": [ "x64" ], @@ -132,9 +132,9 @@ } }, "node_modules/@github/copilot-linux-arm64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.11.tgz", - "integrity": "sha512-/d8p6RlFYKj1Va2hekFIcYNMHWagcEkaxgcllUNXSyQLnmEtXUkaWtz62VKGWE+n/UMkEwCB6vI2xEwPTlUNBQ==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-arm64/-/copilot-linux-arm64-1.0.24.tgz", + "integrity": "sha512-B3oANXKKKLhnKYozXa/W+DxfCQAHJCs0QKR5rBwNrwJbf656twNgALSxWTSJk+1rEP6MrHCswUAcwjwZL7Q+FQ==", "cpu": [ "arm64" ], @@ -148,9 +148,9 @@ } }, "node_modules/@github/copilot-linux-x64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.11.tgz", - "integrity": "sha512-UujTRO3xkPFC1CybchBbCnaTEAG6JrH0etIst07JvfekMWgvRxbiCHQPpDPSzBCPiBcGu0gba0/IT+vUCORuIw==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-linux-x64/-/copilot-linux-x64-1.0.24.tgz", + "integrity": "sha512-NGTldizY54B+4Sfhu/GWoEQNMwqqUNgMwbSgBshFv+Hqy1EwuvNWKVov1Y0Vzhp4qAHc6ZxBk/OPIW8Ato9FUg==", "cpu": [ "x64" ], @@ -164,12 +164,12 @@ } }, "node_modules/@github/copilot-sdk": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.2.0.tgz", - "integrity": "sha512-fCEpD9W9xqcaCAJmatyNQ1PkET9P9liK2P4Vk0raDFoMXcvpIdqewa5JQeKtWCBUsN/HCz7ExkkFP8peQuo+DA==", + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@github/copilot-sdk/-/copilot-sdk-0.2.2.tgz", + "integrity": "sha512-VZCqS08YlUM90bUKJ7VLeIxgTTEHtfXBo84T1IUMNvXRREX2csjPH6Z+CPw3S2468RcCLvzBXcc9LtJJTLIWFw==", "license": "MIT", "dependencies": { - "@github/copilot": "^1.0.10", + "@github/copilot": "^1.0.21", "vscode-jsonrpc": "^8.2.1", "zod": "^4.3.6" }, @@ -187,9 +187,9 @@ } }, "node_modules/@github/copilot-win32-arm64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.11.tgz", - "integrity": "sha512-EOW8HUM+EmnHEZEa+iUMl4pP1+2eZUk2XCbynYiMehwX9sidc4BxEHp2RuxADSzFPTieQEWzgjQmHWrtet8pQg==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-arm64/-/copilot-win32-arm64-1.0.24.tgz", + "integrity": "sha512-/pd/kgef7/HIIg1SQq4q8fext39pDSC44jHB10KkhfgG1WaDFhQbc/aSSMQfxeldkRbQh6VANp8WtGQdwtMCBA==", "cpu": [ "arm64" ], @@ -203,9 +203,9 @@ } }, "node_modules/@github/copilot-win32-x64": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.11.tgz", - "integrity": "sha512-fKGkSNamzs3h9AbmswNvPYJBORCb2Y8CbusijU3C7fT3ohvqnHJwKo5iHhJXLOKZNOpFZgq9YKha410u9sIs6Q==", + "version": "1.0.24", + "resolved": "https://registry.npmjs.org/@github/copilot-win32-x64/-/copilot-win32-x64-1.0.24.tgz", + "integrity": "sha512-RDvOiSvyEJwELqErwANJTrdRuMIHkwPE4QK7Le7WsmaSKxiuS4H1Pa8Yxnd2FWrMsCHEbase23GJlymbnGYLXQ==", "cpu": [ "x64" ], diff --git a/remote/package.json b/remote/package.json index 89316ec375c..5b9d937b168 100644 --- a/remote/package.json +++ b/remote/package.json @@ -4,8 +4,8 @@ "private": true, "dependencies": { "@anthropic-ai/sandbox-runtime": "0.0.42", - "@github/copilot": "^1.0.11", - "@github/copilot-sdk": "^0.2.0", + "@github/copilot": "^1.0.24", + "@github/copilot-sdk": "^0.2.2", "@microsoft/1ds-core-js": "^3.2.13", "@microsoft/1ds-post-js": "^3.2.13", "@parcel/watcher": "^2.5.6", diff --git a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts index 18161f5ae19..4b9af424c00 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts @@ -467,6 +467,12 @@ export class CopilotAgent extends Disposable implements IAgent { const parsedPlugins = await this._plugins.getAppliedPlugins(); const sessionUri = AgentSession.uri(this.id, sessionId); + const sessionMetadata = await client.getSessionMetadata(sessionId).catch(err => { + this._logService.warn(`[Copilot:${sessionId}] getSessionMetadata failed`, err); + return undefined; + }); + this._logService.info(`[Copilot:${sessionId}] getSessionMetadata returned: ${JSON.stringify(sessionMetadata)}`); + const workingDirectory = typeof sessionMetadata?.context?.cwd === 'string' ? URI.file(sessionMetadata.context.cwd) : undefined; const shellManager = this._instantiationService.createInstance(ShellManager, sessionUri); const sessionConfig = this._buildSessionConfig(parsedPlugins, shellManager); @@ -475,6 +481,7 @@ export class CopilotAgent extends Disposable implements IAgent { try { const raw = await client.resumeSession(sessionId, { ...config, + workingDirectory: workingDirectory?.fsPath, }); return new CopilotSessionWrapper(raw); } catch (err) { @@ -499,7 +506,7 @@ export class CopilotAgent extends Disposable implements IAgent { } }; - const agentSession = this._createAgentSession(factory, undefined, sessionId, shellManager); + const agentSession = this._createAgentSession(factory, workingDirectory, sessionId, shellManager); this._plugins.setAppliedPlugins(agentSession, parsedPlugins); await agentSession.initializeSession(); diff --git a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHost.contribution.ts b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHost.contribution.ts index fac58eda1ff..dc801985753 100644 --- a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHost.contribution.ts +++ b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHost.contribution.ts @@ -8,6 +8,7 @@ import { AgentHostEnabledSettingId } from '../../../../platform/agentHost/common import { IConfigurationService } from '../../../../platform/configuration/common/configuration.js'; import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js'; import { IWorkbenchContribution, registerWorkbenchContribution2, WorkbenchPhase } from '../../../../workbench/common/contributions.js'; +import { IAgentHostSessionWorkingDirectoryResolver } from '../../../../workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionWorkingDirectoryResolver.js'; import { ISessionsProvidersService } from '../../../services/sessions/browser/sessionsProvidersService.js'; import { LocalAgentHostSessionsProvider } from './localAgentHostSessionsProvider.js'; @@ -29,6 +30,7 @@ class LocalAgentHostContribution extends Disposable implements IWorkbenchContrib @IConfigurationService configurationService: IConfigurationService, @IInstantiationService instantiationService: IInstantiationService, @ISessionsProvidersService sessionsProvidersService: ISessionsProvidersService, + @IAgentHostSessionWorkingDirectoryResolver workingDirectoryResolver: IAgentHostSessionWorkingDirectoryResolver, ) { super(); @@ -38,6 +40,12 @@ class LocalAgentHostContribution extends Disposable implements IWorkbenchContrib const provider = this._register(instantiationService.createInstance(LocalAgentHostSessionsProvider)); this._register(sessionsProvidersService.registerProvider(provider)); + for (const sessionType of provider.sessionTypes) { + this._register(workingDirectoryResolver.registerResolver(sessionType.id, sessionResource => { + const repository = provider.getSessionByResource(sessionResource)?.workspace.get()?.repositories[0]; + return repository?.workingDirectory ?? repository?.uri; + })); + } } } diff --git a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts index 7a1b2259d16..0857da8f54a 100644 --- a/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/localAgentHost/browser/localAgentHostSessionsProvider.ts @@ -283,6 +283,25 @@ export class LocalAgentHostSessionsProvider extends Disposable implements ISessi return sessions; } + getSessionByResource(resource: URI): ISession | undefined { + if (this._currentNewSession?.resource.toString() === resource.toString()) { + return this._currentNewSession; + } + + if (this._pendingSession?.resource.toString() === resource.toString()) { + return this._pendingSession; + } + + this._ensureSessionCache(); + for (const cached of this._sessionCache.values()) { + if (cached.resource.toString() === resource.toString()) { + return cached; + } + } + + return undefined; + } + // -- Session Lifecycle -- createNewSession(workspace: ISessionWorkspace): ISession { diff --git a/src/vs/sessions/contrib/localAgentHost/test/browser/localAgentHostSessionsProvider.test.ts b/src/vs/sessions/contrib/localAgentHost/test/browser/localAgentHostSessionsProvider.test.ts index 3675b895eef..430bdbce1d4 100644 --- a/src/vs/sessions/contrib/localAgentHost/test/browser/localAgentHostSessionsProvider.test.ts +++ b/src/vs/sessions/contrib/localAgentHost/test/browser/localAgentHostSessionsProvider.test.ts @@ -306,6 +306,29 @@ suite('LocalAgentHostSessionsProvider', () => { assert.strictEqual(session.sessionType, provider.sessionTypes[0].id); }); + test('getSessionByResource resolves current new session without listing it', () => { + const provider = createProvider(disposables, agentHost); + const workspace = { + label: 'my-project', + icon: { id: 'folder' }, + repositories: [{ uri: URI.parse('file:///home/user/project'), workingDirectory: undefined, detail: undefined, baseBranchName: undefined, baseBranchProtected: undefined }], + requiresWorkspaceTrust: true, + }; + + const session = provider.createNewSession(workspace); + const resolved = provider.getSessionByResource(session.resource); + + assert.deepStrictEqual({ + listedSessions: provider.getSessions().length, + resolvedResource: resolved?.resource.toString(), + resolvedWorkspaceLabel: resolved?.workspace.get()?.label, + }, { + listedSessions: 0, + resolvedResource: session.resource.toString(), + resolvedWorkspaceLabel: 'my-project', + }); + }); + test('createNewSession throws when no repository URI', () => { const provider = createProvider(disposables, agentHost); const workspace = { label: 'empty', icon: { id: 'folder' }, repositories: [], requiresWorkspaceTrust: false }; diff --git a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHost.contribution.ts b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHost.contribution.ts index 63ac2ef5ac7..78c0707ccdc 100644 --- a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHost.contribution.ts +++ b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHost.contribution.ts @@ -39,7 +39,6 @@ import { createRemoteAgentHarnessDescriptor, RemoteAgentCustomizationItemProvide import { RemoteAgentHostSessionsProvider } from './remoteAgentHostSessionsProvider.js'; import { SyncedCustomizationBundler } from './syncedCustomizationBundler.js'; import { ISSHRemoteAgentHostService } from '../../../../platform/agentHost/common/sshRemoteAgentHost.js'; -import { ISessionsManagementService } from '../../../services/sessions/common/sessionsManagement.js'; /** Per-connection state bundle, disposed when a connection is removed. */ class ConnectionState extends Disposable { @@ -86,7 +85,6 @@ export class RemoteAgentHostContribution extends Disposable implements IWorkbenc @IInstantiationService private readonly _instantiationService: IInstantiationService, @IAuthenticationService private readonly _authenticationService: IAuthenticationService, @IDefaultAccountService private readonly _defaultAccountService: IDefaultAccountService, - @ISessionsManagementService private readonly _sessionsManagementService: ISessionsManagementService, @ISessionsProvidersService private readonly _sessionsProvidersService: ISessionsProvidersService, @IConfigurationService private readonly _configurationService: IConfigurationService, @IAgentHostFileSystemService private readonly _agentHostFileSystemService: IAgentHostFileSystemService, @@ -339,6 +337,7 @@ export class RemoteAgentHostContribution extends Disposable implements IWorkbenc connState.store.add(agentStore); const sanitized = agentHostAuthority(address); + const providerId = `agenthost-${sanitized}`; const sessionType = `remote-${sanitized}-${agent.provider}`; const agentId = sessionType; const vendor = sessionType; @@ -349,17 +348,20 @@ export class RemoteAgentHostContribution extends Disposable implements IWorkbenc const sessionWorkingDirs = new Map(); agentStore.add(toDisposable(() => sessionWorkingDirs.clear())); - // Capture the working directory from the active session for new sessions - const resolveWorkingDirectory = (resourceKey: string): URI | undefined => { + // Capture the working directory from the session that is being created. + const resolveWorkingDirectory = (sessionResource: URI): URI | undefined => { + const resourceKey = sessionResource.toString(); const cached = sessionWorkingDirs.get(resourceKey); if (cached) { return cached; } - const activeSession = this._sessionsManagementService.activeSession.get(); - const repoUri = activeSession?.workspace.get()?.repositories[0]?.uri; - if (repoUri) { - sessionWorkingDirs.set(resourceKey, repoUri); - return repoUri; + const provider = this._sessionsProvidersService.getProvider(providerId); + const session = provider?.getSessionByResource(sessionResource); + const repository = session?.workspace.get()?.repositories[0]; + const workingDirectory = repository?.workingDirectory ?? repository?.uri; + if (workingDirectory) { + sessionWorkingDirs.set(resourceKey, workingDirectory); + return workingDirectory; } return undefined; }; diff --git a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts index b23ada46517..5f862aeda44 100644 --- a/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts +++ b/src/vs/sessions/contrib/remoteAgentHost/browser/remoteAgentHostSessionsProvider.ts @@ -370,6 +370,25 @@ export class RemoteAgentHostSessionsProvider extends Disposable implements ISess return sessions; } + getSessionByResource(resource: URI): ISession | undefined { + if (this._currentNewSession?.resource.toString() === resource.toString()) { + return this._chatToSession(this._currentNewSession); + } + + if (this._pendingSession?.resource.toString() === resource.toString()) { + return this._pendingSession; + } + + this._ensureSessionCache(); + for (const cached of this._sessionCache.values()) { + if (cached.resource.toString() === resource.toString()) { + return this._chatToSession(cached); + } + } + + return undefined; + } + // -- Session Lifecycle -- private _currentNewSession: IChatData | undefined; diff --git a/src/vs/sessions/contrib/remoteAgentHost/test/browser/remoteAgentHostSessionsProvider.test.ts b/src/vs/sessions/contrib/remoteAgentHost/test/browser/remoteAgentHostSessionsProvider.test.ts index a1d5f1cdd22..da0d384e034 100644 --- a/src/vs/sessions/contrib/remoteAgentHost/test/browser/remoteAgentHostSessionsProvider.test.ts +++ b/src/vs/sessions/contrib/remoteAgentHost/test/browser/remoteAgentHostSessionsProvider.test.ts @@ -369,6 +369,29 @@ suite('RemoteAgentHostSessionsProvider', () => { assert.strictEqual(session.sessionType, provider.sessionTypes[0].id); }); + test('getSessionByResource resolves current new session without listing it', () => { + const provider = createProvider(disposables, connection); + const workspace = { + label: 'my-project', + icon: { id: 'remote' }, + repositories: [{ uri: URI.parse('vscode-agent-host://auth/home/user/project'), workingDirectory: undefined, detail: undefined, baseBranchName: undefined, baseBranchProtected: undefined }], + requiresWorkspaceTrust: false, + }; + + const session = provider.createNewSession(workspace); + const resolved = provider.getSessionByResource(session.resource); + + assert.deepStrictEqual({ + listedSessions: provider.getSessions().length, + resolvedResource: resolved?.resource.toString(), + resolvedWorkspaceLabel: resolved?.workspace.get()?.label, + }, { + listedSessions: 0, + resolvedResource: session.resource.toString(), + resolvedWorkspaceLabel: 'my-project', + }); + }); + test('createNewSession throws when no repository URI', () => { const provider = createProvider(disposables, connection); const workspace = { label: 'empty', icon: { id: 'remote' }, repositories: [], requiresWorkspaceTrust: false }; diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts index aeb24fd5365..3616336f36d 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts @@ -37,6 +37,7 @@ import { IChatAgentData, IChatAgentImplementation, IChatAgentRequest, IChatAgent import { ToolInvocationPresentation } from '../../../common/tools/languageModelToolsService.js'; import { getAgentHostIcon } from '../agentSessions.js'; import { AgentHostEditingSession } from './agentHostEditingSession.js'; +import { IAgentHostSessionWorkingDirectoryResolver } from './agentHostSessionWorkingDirectoryResolver.js'; import { IAgentHostTerminalService } from '../../../../terminal/browser/agentHostTerminalService.js'; import { activeTurnToProgress, completedToolCallToEditParts, completedToolCallToSerialized, finalizeToolInvocation, getTerminalContentUri, toolCallStateToInvocation, turnsToHistory, updateRunningToolSpecificData, type IToolCallFileEdit } from './stateToProgressAdapter.js'; import { getToolKind } from '../../../../../../platform/agentHost/common/state/sessionReducers.js'; @@ -239,7 +240,7 @@ export interface IAgentHostSessionHandlerConfig { * Optional callback to resolve a working directory for a new session. * If not provided, falls back to the first workspace folder. */ - readonly resolveWorkingDirectory?: (resourceKey: string) => URI | undefined; + readonly resolveWorkingDirectory?: (sessionResource: URI) => URI | undefined; /** * Optional callback invoked when the server rejects an operation because * authentication is required. Should trigger interactive authentication @@ -286,6 +287,7 @@ export class AgentHostSessionHandler extends Disposable implements IChatSessionC @IInstantiationService private readonly _instantiationService: IInstantiationService, @ITerminalChatService private readonly _terminalChatService: ITerminalChatService, @IAgentHostTerminalService private readonly _agentHostTerminalService: IAgentHostTerminalService, + @IAgentHostSessionWorkingDirectoryResolver private readonly _workingDirectoryResolver: IAgentHostSessionWorkingDirectoryResolver, ) { super(); this._config = config; @@ -1798,8 +1800,8 @@ export class AgentHostSessionHandler extends Disposable implements IChatSessionC /** Creates a new backend session and subscribes to its state. */ private async _createAndSubscribe(sessionResource: URI, modelId?: string, fork?: { session: URI; turnIndex: number }): Promise { const rawModelId = this._extractRawModelId(modelId); - const resourceKey = sessionResource.path.substring(1); - const workingDirectory = this._config.resolveWorkingDirectory?.(resourceKey) + const workingDirectory = this._config.resolveWorkingDirectory?.(sessionResource) + ?? this._workingDirectoryResolver.resolve(sessionResource) ?? this._workspaceContextService.getWorkspace().folders[0]?.uri; this._logService.trace(`[AgentHost] Creating new session, model=${rawModelId ?? '(default)'}, provider=${this._config.provider}${fork ? `, fork from ${fork.session.toString()} at index ${fork.turnIndex}` : ''}`); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionWorkingDirectoryResolver.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionWorkingDirectoryResolver.ts new file mode 100644 index 00000000000..be02d0290f5 --- /dev/null +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionWorkingDirectoryResolver.ts @@ -0,0 +1,38 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +import { IDisposable, toDisposable } from '../../../../../../base/common/lifecycle.js'; +import { URI } from '../../../../../../base/common/uri.js'; +import { createDecorator } from '../../../../../../platform/instantiation/common/instantiation.js'; +import { InstantiationType, registerSingleton } from '../../../../../../platform/instantiation/common/extensions.js'; + +export const IAgentHostSessionWorkingDirectoryResolver = createDecorator('agentHostSessionWorkingDirectoryResolver'); + +export interface IAgentHostSessionWorkingDirectoryResolver { + readonly _serviceBrand: undefined; + registerResolver(sessionType: string, resolver: (sessionResource: URI) => URI | undefined): IDisposable; + resolve(sessionResource: URI): URI | undefined; +} + +class AgentHostSessionWorkingDirectoryResolver implements IAgentHostSessionWorkingDirectoryResolver { + declare readonly _serviceBrand: undefined; + + private readonly _resolvers = new Map URI | undefined>(); + + registerResolver(sessionType: string, resolver: (sessionResource: URI) => URI | undefined): IDisposable { + this._resolvers.set(sessionType, resolver); + return toDisposable(() => { + if (this._resolvers.get(sessionType) === resolver) { + this._resolvers.delete(sessionType); + } + }); + } + + resolve(sessionResource: URI): URI | undefined { + return this._resolvers.get(sessionResource.scheme)?.(sessionResource); + } +} + +registerSingleton(IAgentHostSessionWorkingDirectoryResolver, AgentHostSessionWorkingDirectoryResolver, InstantiationType.Delayed); diff --git a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts index 6ff0cd75133..803ebdc249b 100644 --- a/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts +++ b/src/vs/workbench/contrib/chat/test/browser/agentSessions/agentHostChatContribution.test.ts @@ -47,6 +47,7 @@ import { IStorageService, InMemoryStorageService } from '../../../../../../platf import { IAgentSubscription } from '../../../../../../platform/agentHost/common/state/agentSubscription.js'; import { ITerminalChatService } from '../../../../terminal/browser/terminal.js'; import { IAgentHostTerminalService } from '../../../../terminal/browser/agentHostTerminalService.js'; +import { IAgentHostSessionWorkingDirectoryResolver } from '../../../browser/agentSessions/agentHost/agentHostSessionWorkingDirectoryResolver.js'; // ---- Mock agent host service ------------------------------------------------ @@ -259,7 +260,7 @@ class MockChatAgentService extends mock() { // ---- Helpers ---------------------------------------------------------------- -function createTestServices(disposables: DisposableStore) { +function createTestServices(disposables: DisposableStore, workingDirectoryResolver?: { resolve(sessionResource: URI): URI | undefined }) { const instantiationService = disposables.add(new TestInstantiationService()); const agentHostService = new MockAgentHostService(); @@ -313,6 +314,10 @@ function createTestServices(disposables: DisposableStore) { instantiationService.stub(IAgentHostTerminalService, { reviveTerminal: async () => undefined!, }); + instantiationService.stub(IAgentHostSessionWorkingDirectoryResolver, { + registerResolver: () => toDisposable(() => { }), + resolve: sessionResource => workingDirectoryResolver?.resolve(sessionResource), + }); return { instantiationService, agentHostService, chatAgentService }; } @@ -1605,6 +1610,30 @@ suite('AgentHostChatContribution', () => { assert.strictEqual(agentHostService.createSessionCalls[0].workingDirectory?.toString(), URI.file('/custom/working/dir').toString()); })); + test('handler uses registered working directory resolver', () => runWithFakedTimers({ useFakeTimers: true }, async () => { + const resolvedWorkingDirectory = URI.file('/resolved/working/dir'); + const { instantiationService, agentHostService } = createTestServices(disposables, { + resolve: () => resolvedWorkingDirectory, + }); + + const handler = disposables.add(instantiationService.createInstance(AgentHostSessionHandler, { + provider: 'copilot' as const, + agentId: 'workdir-resolver-test', + sessionType: 'workdir-resolver-test', + fullName: 'Test', + description: 'test', + connection: agentHostService, + connectionAuthority: 'local', + })); + + const { turnPromise, session, turnId, fire } = await startTurn(handler, agentHostService, disposables); + fire({ type: 'session/turnComplete', session, turnId } as ISessionAction); + await turnPromise; + + assert.strictEqual(agentHostService.createSessionCalls.length, 1); + assert.strictEqual(agentHostService.createSessionCalls[0].workingDirectory?.toString(), resolvedWorkingDirectory.toString()); + })); + test('handler passes vscode-agent-host URI as-is to createSession', () => runWithFakedTimers({ useFakeTimers: true }, async () => { const { instantiationService, agentHostService } = createTestServices(disposables); From bcaee38daa403685df54639d5387b67b8ff33f94 Mon Sep 17 00:00:00 2001 From: Rob Lourens Date: Sat, 11 Apr 2026 17:44:14 -0700 Subject: [PATCH 2/2] Address Copilot review feedback (Written by Copilot) --- src/vs/platform/agentHost/node/copilot/copilotAgent.ts | 1 - .../browser/agentSessions/agentHost/agentHostSessionHandler.ts | 3 ++- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts index 4b9af424c00..7da12bf7c93 100644 --- a/src/vs/platform/agentHost/node/copilot/copilotAgent.ts +++ b/src/vs/platform/agentHost/node/copilot/copilotAgent.ts @@ -471,7 +471,6 @@ export class CopilotAgent extends Disposable implements IAgent { this._logService.warn(`[Copilot:${sessionId}] getSessionMetadata failed`, err); return undefined; }); - this._logService.info(`[Copilot:${sessionId}] getSessionMetadata returned: ${JSON.stringify(sessionMetadata)}`); const workingDirectory = typeof sessionMetadata?.context?.cwd === 'string' ? URI.file(sessionMetadata.context.cwd) : undefined; const shellManager = this._instantiationService.createInstance(ShellManager, sessionUri); const sessionConfig = this._buildSessionConfig(parsedPlugins, shellManager); diff --git a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts index 3616336f36d..e849ed6fd41 100644 --- a/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts +++ b/src/vs/workbench/contrib/chat/browser/agentSessions/agentHost/agentHostSessionHandler.ts @@ -238,7 +238,8 @@ export interface IAgentHostSessionHandlerConfig { readonly extensionDisplayName?: string; /** * Optional callback to resolve a working directory for a new session. - * If not provided, falls back to the first workspace folder. + * If not provided or unresolved, session resource resolvers are consulted before + * falling back to the first workspace folder. */ readonly resolveWorkingDirectory?: (sessionResource: URI) => URI | undefined; /**