@@ -11,8 +11,9 @@ import { ResourceMap } from '../../../../../../../base/common/map.js';
1111import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js' ;
1212import { IFileContent , IReadFileOptions } from '../../../../../../../platform/files/common/files.js' ;
1313import { IWebContentExtractorService , WebContentExtractResult } from '../../../../../../../platform/webContentExtractor/common/webContentExtractor.js' ;
14+ import { testWorkspace } from '../../../../../../../platform/workspace/test/common/testWorkspace.js' ;
1415import { FetchWebPageTool } from '../../../../electron-browser/builtInTools/fetchPageTool.js' ;
15- import { TestFileService } from '../../../../../../test/common/workbenchTestServices.js' ;
16+ import { TestContextService , TestFileService } from '../../../../../../test/common/workbenchTestServices.js' ;
1617import { MockTrustedDomainService } from '../../../../../url/test/browser/mockTrustedDomainService.js' ;
1718import { InternalFetchWebPageToolId } from '../../../../common/tools/builtinTools/tools.js' ;
1819import { MockChatService } from '../../../common/chatService/mockChatService.js' ;
@@ -91,6 +92,7 @@ suite('FetchWebPageTool', () => {
9192 new ExtendedTestFileService ( fileContentMap ) ,
9293 new MockTrustedDomainService ( ) ,
9394 new MockChatService ( ) ,
95+ new TestContextService ( ) ,
9496 ) ;
9597
9698 const testUrls = [
@@ -140,6 +142,7 @@ suite('FetchWebPageTool', () => {
140142 new ExtendedTestFileService ( new ResourceMap < string | VSBuffer > ( ) ) ,
141143 new MockTrustedDomainService ( [ ] ) ,
142144 new MockChatService ( ) ,
145+ new TestContextService ( ) ,
143146 ) ;
144147
145148 // Test empty array
@@ -189,6 +192,7 @@ suite('FetchWebPageTool', () => {
189192 new ExtendedTestFileService ( fileContentMap ) ,
190193 new MockTrustedDomainService ( ) ,
191194 new MockChatService ( ) ,
195+ new TestContextService ( ) ,
192196 ) ;
193197
194198 const preparation = await tool . prepareToolInvocation (
@@ -203,6 +207,100 @@ suite('FetchWebPageTool', () => {
203207 assert . ok ( messageText . includes ( 'invalid://invalid' ) , 'Should mention invalid URL' ) ;
204208 } ) ;
205209
210+ test ( 'should not show confirmation dialog for file URIs inside the workspace' , async ( ) => {
211+ // Use a workspace rooted at /workspaceRoot
212+ const workspaceRoot = URI . file ( '/workspaceRoot' ) ;
213+ const workspaceContextService = new TestContextService ( testWorkspace ( workspaceRoot ) ) ;
214+
215+ const fileContentMap = new ResourceMap < string | VSBuffer > ( [
216+ [ URI . file ( '/workspaceRoot/plan.md' ) , 'Plan content' ] ,
217+ [ URI . file ( '/workspaceRoot/subdir/notes.txt' ) , 'Notes content' ] ,
218+ ] ) ;
219+
220+ const tool = new FetchWebPageTool (
221+ new TestWebContentExtractorService ( new ResourceMap < string > ( ) ) ,
222+ new ExtendedTestFileService ( fileContentMap ) ,
223+ new MockTrustedDomainService ( [ ] ) ,
224+ new MockChatService ( ) ,
225+ workspaceContextService ,
226+ ) ;
227+
228+ // File inside workspace - should NOT trigger confirmation
229+ const preparation = await tool . prepareToolInvocation (
230+ { parameters : { urls : [ URI . file ( '/workspaceRoot/plan.md' ) . toString ( ) ] } , toolCallId : 'test-file-in-ws' , chatSessionResource : undefined } ,
231+ CancellationToken . None
232+ ) ;
233+ assert . ok ( preparation , 'Should return prepared invocation' ) ;
234+ assert . strictEqual ( preparation . confirmationMessages ?. title , undefined , 'File inside workspace should not show confirmation dialog' ) ;
235+ assert . strictEqual ( preparation . confirmationMessages ?. confirmResults , false , 'File inside workspace should not require post-confirmation' ) ;
236+ } ) ;
237+
238+ test ( 'should show confirmation dialog for file URIs outside the workspace' , async ( ) => {
239+ // Use a workspace rooted at /workspaceRoot
240+ const workspaceRoot = URI . file ( '/workspaceRoot' ) ;
241+ const workspaceContextService = new TestContextService ( testWorkspace ( workspaceRoot ) ) ;
242+
243+ const fileContentMap = new ResourceMap < string | VSBuffer > ( [
244+ [ URI . file ( '/tmp/external-plan.md' ) , 'External plan content' ] ,
245+ ] ) ;
246+
247+ const tool = new FetchWebPageTool (
248+ new TestWebContentExtractorService ( new ResourceMap < string > ( ) ) ,
249+ new ExtendedTestFileService ( fileContentMap ) ,
250+ new MockTrustedDomainService ( [ ] ) ,
251+ new MockChatService ( ) ,
252+ workspaceContextService ,
253+ ) ;
254+
255+ // File outside workspace - should still trigger confirmation
256+ const preparation = await tool . prepareToolInvocation (
257+ { parameters : { urls : [ URI . file ( '/tmp/external-plan.md' ) . toString ( ) ] } , toolCallId : 'test-file-outside-ws' , chatSessionResource : undefined } ,
258+ CancellationToken . None
259+ ) ;
260+ assert . ok ( preparation , 'Should return prepared invocation' ) ;
261+ assert . ok ( preparation . confirmationMessages ?. title , 'File outside workspace should show confirmation dialog' ) ;
262+ assert . strictEqual ( preparation . confirmationMessages ?. confirmResults , true , 'File outside workspace should require post-confirmation' ) ;
263+ } ) ;
264+
265+ test ( 'workspace file mixed with untrusted web URI: only web URI triggers confirmation' , async ( ) => {
266+ const workspaceRoot = URI . file ( '/workspaceRoot' ) ;
267+ const workspaceContextService = new TestContextService ( testWorkspace ( workspaceRoot ) ) ;
268+
269+ const webContentMap = new ResourceMap < string > ( [
270+ [ URI . parse ( 'https://example.com' ) , 'Web content' ]
271+ ] ) ;
272+ const fileContentMap = new ResourceMap < string | VSBuffer > ( [
273+ [ URI . file ( '/workspaceRoot/plan.md' ) , 'Plan content' ]
274+ ] ) ;
275+
276+ const tool = new FetchWebPageTool (
277+ new TestWebContentExtractorService ( webContentMap ) ,
278+ new ExtendedTestFileService ( fileContentMap ) ,
279+ new MockTrustedDomainService ( [ ] ) , // No trusted domains
280+ new MockChatService ( ) ,
281+ workspaceContextService ,
282+ ) ;
283+
284+ // Mix: one untrusted web URI + one workspace file URI
285+ const preparation = await tool . prepareToolInvocation (
286+ {
287+ parameters : { urls : [ 'https://example.com' , URI . file ( '/workspaceRoot/plan.md' ) . toString ( ) ] } ,
288+ toolCallId : 'test-mixed' ,
289+ chatSessionResource : undefined
290+ } ,
291+ CancellationToken . None
292+ ) ;
293+ assert . ok ( preparation , 'Should return prepared invocation' ) ;
294+ // Confirmation should only be for the web URI
295+ assert . ok ( preparation . confirmationMessages ?. title , 'Should show confirmation for untrusted web URI' ) ;
296+ // The confirmation message should mention only the web URI, not the workspace file
297+ const msgValue = typeof preparation . confirmationMessages ?. message === 'string'
298+ ? preparation . confirmationMessages . message
299+ : preparation . confirmationMessages ?. message ?. value ?? '' ;
300+ assert . ok ( ! msgValue . includes ( '/workspaceRoot/' ) , 'Confirmation message should not mention workspace file' ) ;
301+ assert . ok ( msgValue . includes ( 'example.com' ) , 'Confirmation message should mention web URI' ) ;
302+ } ) ;
303+
206304 test ( 'should approve when all URLs were mentioned in chat' , async ( ) => {
207305 const webContentMap = new ResourceMap < string > ( [
208306 [ URI . parse ( 'https://valid.com' ) , 'Valid content' ]
@@ -227,6 +325,7 @@ suite('FetchWebPageTool', () => {
227325 } ;
228326 } ,
229327 } ) ,
328+ new TestContextService ( ) ,
230329 ) ;
231330
232331 const preparation1 = await tool . prepareToolInvocation (
@@ -261,6 +360,7 @@ suite('FetchWebPageTool', () => {
261360 new ExtendedTestFileService ( fileContentMap ) ,
262361 new MockTrustedDomainService ( ) ,
263362 new MockChatService ( ) ,
363+ new TestContextService ( ) ,
264364 ) ;
265365
266366 const result = await tool . invoke (
@@ -309,6 +409,7 @@ suite('FetchWebPageTool', () => {
309409 new ExtendedTestFileService ( fileContentMap ) ,
310410 new MockTrustedDomainService ( ) ,
311411 new MockChatService ( ) ,
412+ new TestContextService ( ) ,
312413 ) ;
313414
314415 const result = await tool . invoke (
@@ -350,6 +451,7 @@ suite('FetchWebPageTool', () => {
350451 new ExtendedTestFileService ( fileContentMap ) ,
351452 new MockTrustedDomainService ( ) ,
352453 new MockChatService ( ) ,
454+ new TestContextService ( ) ,
353455 ) ;
354456
355457 const result = await tool . invoke (
@@ -397,6 +499,7 @@ suite('FetchWebPageTool', () => {
397499 new ExtendedTestFileService ( fileContentMap ) ,
398500 new MockTrustedDomainService ( ) ,
399501 new MockChatService ( ) ,
502+ new TestContextService ( ) ,
400503 ) ;
401504
402505 const result = await tool . invoke (
@@ -463,6 +566,7 @@ suite('FetchWebPageTool', () => {
463566 new ExtendedTestFileService ( fileContentMap ) ,
464567 new MockTrustedDomainService ( ) ,
465568 new MockChatService ( ) ,
569+ new TestContextService ( ) ,
466570 ) ;
467571
468572 const result = await tool . invoke (
@@ -503,6 +607,7 @@ suite('FetchWebPageTool', () => {
503607 new ExtendedTestFileService ( fileContentMap ) ,
504608 new MockTrustedDomainService ( ) ,
505609 new MockChatService ( ) ,
610+ new TestContextService ( ) ,
506611 ) ;
507612
508613 const result = await tool . invoke (
@@ -547,6 +652,7 @@ suite('FetchWebPageTool', () => {
547652 new ExtendedTestFileService ( fileContentMap ) ,
548653 new MockTrustedDomainService ( ) ,
549654 new MockChatService ( ) ,
655+ new TestContextService ( ) ,
550656 ) ;
551657
552658 const testUrls = [
@@ -606,6 +712,7 @@ suite('FetchWebPageTool', () => {
606712 new ExtendedTestFileService ( new ResourceMap < string | VSBuffer > ( ) ) ,
607713 new MockTrustedDomainService ( [ ] ) ,
608714 new MockChatService ( ) ,
715+ new TestContextService ( ) ,
609716 ) ;
610717
611718 const testUrls = [
@@ -642,6 +749,7 @@ suite('FetchWebPageTool', () => {
642749 new ExtendedTestFileService ( fileContentMap ) ,
643750 new MockTrustedDomainService ( ) ,
644751 new MockChatService ( ) ,
752+ new TestContextService ( ) ,
645753 ) ;
646754
647755 const testUrls = [
@@ -684,6 +792,7 @@ suite('FetchWebPageTool', () => {
684792 new ExtendedTestFileService ( fileContentMap ) ,
685793 new MockTrustedDomainService ( ) ,
686794 new MockChatService ( ) ,
795+ new TestContextService ( ) ,
687796 ) ;
688797
689798 const testUrls = [
@@ -732,6 +841,7 @@ suite('FetchWebPageTool', () => {
732841 new ExtendedTestFileService ( new ResourceMap < string | VSBuffer > ( ) ) , // Empty - all file ,
733842 new MockTrustedDomainService ( [ ] ) ,
734843 new MockChatService ( ) ,
844+ new TestContextService ( ) ,
735845 ) ;
736846
737847 const testUrls = [
@@ -766,6 +876,7 @@ suite('FetchWebPageTool', () => {
766876 new ExtendedTestFileService ( new ResourceMap < string | VSBuffer > ( ) ) ,
767877 new MockTrustedDomainService ( [ ] ) ,
768878 new MockChatService ( ) ,
879+ new TestContextService ( ) ,
769880 ) ;
770881
771882 const result = await tool . invoke (
@@ -792,6 +903,7 @@ suite('FetchWebPageTool', () => {
792903 new ExtendedTestFileService ( fileContentMap ) ,
793904 new MockTrustedDomainService ( ) ,
794905 new MockChatService ( ) ,
906+ new TestContextService ( ) ,
795907 ) ;
796908
797909 const result = await tool . invoke (
@@ -829,6 +941,7 @@ suite('FetchWebPageTool', () => {
829941 new ExtendedTestFileService ( new ResourceMap < string | VSBuffer > ( ) ) ,
830942 new MockTrustedDomainService ( ) ,
831943 new MockChatService ( ) ,
944+ new TestContextService ( ) ,
832945 ) ;
833946
834947 const result = await tool . invoke (
@@ -856,6 +969,7 @@ suite('FetchWebPageTool', () => {
856969 new ExtendedTestFileService ( new ResourceMap < string | VSBuffer > ( ) ) ,
857970 new MockTrustedDomainService ( ) ,
858971 new MockChatService ( ) ,
972+ new TestContextService ( ) ,
859973 ) ;
860974
861975 const result = await tool . invoke (
@@ -888,6 +1002,7 @@ suite('FetchWebPageTool', () => {
8881002 new ExtendedTestFileService ( new ResourceMap < string | VSBuffer > ( ) ) ,
8891003 new MockTrustedDomainService ( ) ,
8901004 new MockChatService ( ) ,
1005+ new TestContextService ( ) ,
8911006 ) ;
8921007
8931008 const result = await tool . invoke (
@@ -914,6 +1029,7 @@ suite('FetchWebPageTool', () => {
9141029 new ExtendedTestFileService ( new ResourceMap < string | VSBuffer > ( ) ) ,
9151030 new MockTrustedDomainService ( ) ,
9161031 new MockChatService ( ) ,
1032+ new TestContextService ( ) ,
9171033 ) ;
9181034
9191035 const result = await tool . invoke (
0 commit comments