Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/extensions/copilot/src/extension/prompts/node/agent/test/parseAttachments.spec.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import type { Attachment } from '@github/copilot/sdk';
7
import { afterEach, beforeEach, expect, suite, test, vi } from 'vitest';
8
import { IFileSystemService } from '../../../../../platform/filesystem/common/fileSystemService';
9
import { FileType } from '../../../../../platform/filesystem/common/fileTypes';
10
import { MockFileSystemService } from '../../../../../platform/filesystem/node/test/mockFileSystemService';
11
import { IIgnoreService } from '../../../../../platform/ignore/common/ignoreService';
12
import { ILogService } from '../../../../../platform/log/common/logService';
13
import { TestWorkspaceService } from '../../../../../platform/test/node/testWorkspaceService';
14
import { IWorkspaceService } from '../../../../../platform/workspace/common/workspaceService';
15
import { DiagnosticSeverity } from '../../../../../util/common/test/shims/enums';
16
import { createTextDocumentData } from '../../../../../util/common/test/shims/textDocument';
17
import { mock } from '../../../../../util/common/test/simpleMock';
18
import { CancellationToken } from '../../../../../util/vs/base/common/cancellation';
19
import { DisposableStore } from '../../../../../util/vs/base/common/lifecycle';
20
import { Schemas } from '../../../../../util/vs/base/common/network';
21
import { URI } from '../../../../../util/vs/base/common/uri';
22
import { Location } from '../../../../../util/vs/workbench/api/common/extHostTypes/location';
23
import { Range } from '../../../../../util/vs/workbench/api/common/extHostTypes/range';
24
import { ChatReferenceDiagnostic } from '../../../../../vscodeTypes';
25
import { emptyWorkspaceInfo, IWorkspaceInfo } from '../../../../chatSessions/common/workspaceInfo';
26
import { extractChatPromptReferences } from '../../../../chatSessions/copilotcli/common/copilotCLIPrompt';
27
import { CopilotCLIImageSupport } from '../../../../chatSessions/copilotcli/node/copilotCLIImageSupport';
28
import { CopilotCLIPromptResolver } from '../../../../chatSessions/copilotcli/node/copilotcliPromptResolver';
29
import { MockSkillLocations } from '../../../../chatSessions/copilotcli/node/test/testHelpers';
30
import { createExtensionUnitTestingServices } from '../../../../test/node/services';
31
import { TestChatRequest } from '../../../../test/node/testHelpers';
32
import { MockExtensionContext } from '../../../../../platform/test/node/extensionContext';
33
import { IVSCodeExtensionContext } from '../../../../../platform/extContext/common/extensionContext';
34
35
36
suite('CopilotCLI Generate & parse prompts', () => {
37
(['emptyWorkspace', 'workspace', 'worktree'] as const).forEach(workspaceType => {
38
suite(workspaceType, () => {
39
const disposables = new DisposableStore();
40
let fileSystem: MockFileSystemService;
41
let workspaceService: TestWorkspaceService;
42
let resolver: CopilotCLIPromptResolver;
43
const workspaceInfo = createWorkspaceInfo(workspaceType);
44
beforeEach(() => {
45
const services = createExtensionUnitTestingServices(disposables);
46
const accessor = disposables.add(services.createTestingAccessor());
47
fileSystem = accessor.get(IFileSystemService) as MockFileSystemService;
48
workspaceService = accessor.get(IWorkspaceService) as TestWorkspaceService;
49
const logService = accessor.get(ILogService);
50
const imageSupport = new class extends mock<CopilotCLIImageSupport>() {
51
override storeImage(imageData: Uint8Array, mimeType: string): Promise<URI> {
52
throw new Error('Method not implemented.');
53
}
54
};
55
if (workspaceType === 'workspace' || workspaceType === 'worktree') {
56
workspaceService.getWorkspaceFolders().push(URI.file('/workspace'));
57
}
58
resolver = new CopilotCLIPromptResolver(imageSupport, logService, fileSystem, workspaceService, services.seal(), accessor.get(IIgnoreService), new MockSkillLocations(), new MockExtensionContext() as unknown as IVSCodeExtensionContext);
59
});
60
afterEach(() => {
61
disposables.clear();
62
vi.resetAllMocks();
63
});
64
test('just the prompt without anything else', async () => {
65
const req = new TestChatRequest('hello world');
66
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
67
68
const result = extractChatPromptReferences(resolved.prompt);
69
expect(resolved.prompt).toMatchSnapshot();
70
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
71
expect(result).toMatchSnapshot();
72
});
73
74
test('returns original prompt unchanged for slash command', async () => {
75
const req = new TestChatRequest('/help something');
76
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
77
78
const result = extractChatPromptReferences(resolved.prompt);
79
expect(resolved.prompt).toMatchSnapshot();
80
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
81
expect(result).toMatchSnapshot();
82
});
83
84
test('returns overridden prompt instead of using the request prompt', async () => {
85
const req = new TestChatRequest('/help something');
86
const resolved = await resolver.resolvePrompt(req, 'What is 1+2', [], workspaceInfo, [], CancellationToken.None);
87
88
const result = extractChatPromptReferences(resolved.prompt);
89
expect(resolved.prompt).toMatchSnapshot();
90
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
91
expect(result).toMatchSnapshot();
92
});
93
94
test('files are attached as just references without content', async () => {
95
const tsUri = URI.file('/workspace/file.ts');
96
createMockFile(tsUri,
97
`function add(a: number, b: number) {
98
return a + b;
99
}
100
101
function subtract(a: number, b: number) {
102
return a - b;
103
}
104
`);
105
const pyUri = URI.file('/workspace/sample.py');
106
createMockFile(pyUri,
107
`deff add(a, b):
108
return a + b;
109
110
def subtract(a, b):
111
return a - b
112
`);
113
114
const req = new TestChatRequest('explain contents of #file:file.ts and other files', [
115
{
116
id: tsUri.toString(),
117
name: 'file:file.ts',
118
range: [20, 32],
119
value: tsUri
120
},
121
{
122
id: pyUri.toString(),
123
name: 'sample.py',
124
value: pyUri
125
}
126
]);
127
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
128
129
const result = extractChatPromptReferences(resolved.prompt);
130
expect(resolved.prompt).toMatchSnapshot();
131
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
132
expect(result).toMatchSnapshot();
133
});
134
test('Folders are attached with just references', async () => {
135
const folderUri = URI.file('/workspace/folder');
136
137
fileSystem.mockDirectory(folderUri, [
138
['file1.txt', FileType.File],
139
['file2.txt', FileType.File],
140
]);
141
if (workspaceType === 'worktree') {
142
fileSystem.mockDirectory(URI.file('/worktree/folder'), [
143
['file1.txt', FileType.File],
144
['file2.txt', FileType.File],
145
]);
146
}
147
const req = new TestChatRequest('list files in #file:folder', [
148
{
149
id: folderUri.toString(),
150
name: 'file:folder',
151
value: folderUri
152
}
153
]);
154
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
155
156
const result = extractChatPromptReferences(resolved.prompt);
157
expect(resolved.prompt).toMatchSnapshot();
158
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
159
expect(result).toMatchSnapshot();
160
});
161
162
test('parses single error diagnostic', async () => {
163
createMockFile(URI.file('/workspace/file.py'), `pass`);
164
const req = new TestChatRequest('Fix this error', [
165
{
166
id: new Location(URI.file('/workspace/file.py'), new Range(12, 0, 12, 20)).toString(),
167
name: 'Unterminated string',
168
value: new ChatReferenceDiagnostic([
169
[
170
URI.file('/workspace/file.py'),
171
[{
172
message: 'Unterminated string',
173
severity: DiagnosticSeverity.Error,
174
range: new Range(12, 0, 12, 20),
175
code: 'E001'
176
}]
177
]])
178
}
179
]);
180
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
181
182
const result = extractChatPromptReferences(resolved.prompt);
183
expect(resolved.prompt).toMatchSnapshot();
184
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
185
expect(result).toMatchSnapshot();
186
});
187
188
test('groups diagnostics based on same range', async () => {
189
createMockFile(URI.file('/workspace/file.py'), `pass`);
190
const req = new TestChatRequest('Fix these errors', [
191
{
192
id: new Location(URI.file('/workspace/file.py'), new Range(12, 0, 12, 20)).toString(),
193
name: 'Unterminated string',
194
value: new ChatReferenceDiagnostic([
195
[
196
URI.file('/workspace/file.py'),
197
[
198
{
199
message: 'Msg1',
200
severity: DiagnosticSeverity.Warning,
201
range: new Range(1, 0, 1, 20),
202
code: 'E001'
203
},
204
{
205
message: 'MsgB',
206
severity: DiagnosticSeverity.Error,
207
range: new Range(1, 0, 4, 20),
208
code: 'E002'
209
},
210
{
211
message: 'MsgC',
212
severity: DiagnosticSeverity.Information,
213
range: new Range(6, 1, 6, 20),
214
code: 'E003'
215
},
216
{
217
message: 'MsgD',
218
severity: DiagnosticSeverity.Hint,
219
range: new Range(6, 10, 6, 15),
220
code: 'E004',
221
},
222
]
223
]
224
])
225
}
226
]);
227
228
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
229
230
const result = extractChatPromptReferences(resolved.prompt);
231
expect(resolved.prompt).toMatchSnapshot();
232
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
233
expect(result).toMatchSnapshot();
234
});
235
test('aggregates multiple errors across same and different files', async () => {
236
createMockFile(URI.file('/workspace/file.py'), `pass`);
237
createMockFile(URI.file('/workspace/sample.py'), `pass`);
238
239
const req = new TestChatRequest('Fix these errors', [
240
{
241
id: new Location(URI.file('/workspace/file.py'), new Range(12, 0, 12, 20)).toString(),
242
name: 'Unterminated string',
243
value: new ChatReferenceDiagnostic([
244
[
245
URI.file('/workspace/file.py'),
246
[
247
{
248
message: 'Msg1',
249
severity: DiagnosticSeverity.Warning,
250
range: new Range(1, 0, 1, 20),
251
code: 'E001'
252
},
253
{
254
message: 'MsgB',
255
severity: DiagnosticSeverity.Error,
256
range: new Range(4, 0, 4, 20),
257
code: 'E002'
258
},
259
{
260
message: 'MsgC',
261
severity: DiagnosticSeverity.Information,
262
range: new Range(6, 1, 6, 20),
263
code: 'E003'
264
},
265
{
266
message: 'MsgD',
267
severity: DiagnosticSeverity.Hint,
268
range: new Range(1, 1, 1, 10),
269
code: 'E004',
270
},
271
]
272
],
273
[
274
URI.file('/workspace/sample.py'),
275
[
276
{
277
message: 'Msg2',
278
severity: DiagnosticSeverity.Warning,
279
range: new Range(20, 0, 21, 10),
280
code: 'W001'
281
},
282
]
283
]])
284
}
285
]);
286
287
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
288
289
const result = extractChatPromptReferences(resolved.prompt);
290
expect(resolved.prompt).toMatchSnapshot();
291
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
292
expect(result).toMatchSnapshot();
293
});
294
test('parses locations including files with spaces', async () => {
295
const tsUri = URI.file('/workspace/file.ts');
296
createMockFile(tsUri,
297
`function add(a: number, b: number) {
298
return a + b;
299
}
300
301
function subtract(a: number, b: number) {
302
return a - b;
303
}
304
`);
305
const tsWithSpacesUri = URI.file('/workspace/hello world/sample.ts');
306
createMockFile(tsWithSpacesUri,
307
`function mod(a: number) {
308
return a;
309
}`);
310
const pyUri = URI.file('/workspace/sample.py');
311
createMockFile(pyUri,
312
`deff add(a, b):
313
return a + b;
314
315
def subtract(a, b):
316
return a - b
317
`);
318
const req = new TestChatRequest('base', [
319
{
320
id: tsUri.toString(),
321
name: 'file:file.ts',
322
value: new Location(tsUri, new Range(4, 0, 4, 15))
323
},
324
{
325
id: tsWithSpacesUri.toString(),
326
name: 'file:sample.ts',
327
value: new Location(tsWithSpacesUri, new Range(4, 0, 4, 15))
328
},
329
{
330
id: pyUri.toString(),
331
name: 'file:sample.py',
332
value: new Location(pyUri, new Range(3, 0, 3, 15))
333
}
334
]);
335
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
336
337
const result = extractChatPromptReferences(resolved.prompt);
338
expect(resolved.prompt).toMatchSnapshot();
339
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
340
expect(result).toMatchSnapshot();
341
});
342
343
test('uses attachment id attribute for name/id', async () => {
344
const tsUri = URI.file('/workspace/add.py');
345
createMockFile(tsUri,
346
`# Basic arithmetic ops
347
def add(a, b):
348
return a + b
349
}
350
351
def subtract(a, b):
352
return a - b
353
`);
354
const req = new TestChatRequest('explain #sym:add', [
355
{
356
id: 'sym:add',
357
name: 'sym:add',
358
value: new Location(URI.file('/workspace/add.py'), new Range(1, 0, 3, 15)),
359
range: [1, 3]
360
}
361
]);
362
363
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
364
365
const result = extractChatPromptReferences(resolved.prompt);
366
expect(resolved.prompt).toMatchSnapshot();
367
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
368
expect(result).toMatchSnapshot();
369
});
370
371
test('includes contents of untitled file', async () => {
372
const untitledTsFile = {
373
id: 'file:untitled-1',
374
name: 'file:untitled-1',
375
value: URI.from({ scheme: Schemas.untitled, path: 'untitled-1' })
376
};
377
createMockFile(untitledTsFile.value, `function example() {
378
console.log("This is an example");
379
}`);
380
const req = new TestChatRequest('Process these files', [
381
untitledTsFile
382
]);
383
384
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
385
386
const result = extractChatPromptReferences(resolved.prompt);
387
expect(resolved.prompt).toMatchSnapshot();
388
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
389
expect(result).toMatchSnapshot();
390
});
391
392
test('includes contents of untitled prompt files', async () => {
393
const untitledPromptFile = {
394
id: 'vscode.prompt.file__untitled:untitled-1',
395
name: 'prompt:Untitled-2',
396
value: URI.from({ scheme: Schemas.untitled, path: 'untitled-1' })
397
};
398
const regularFileRef = {
399
id: 'regular-file',
400
name: 'regular.ts',
401
value: URI.file('/workspace/regular.ts')
402
};
403
createMockFile(untitledPromptFile.value, `This is a prompt file`);
404
createMockFile(regularFileRef.value, `This is a regular file`);
405
406
const req = new TestChatRequest('Process these files', [
407
untitledPromptFile,
408
regularFileRef
409
]);
410
411
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
412
413
const result = extractChatPromptReferences(resolved.prompt);
414
expect(resolved.prompt).toMatchSnapshot();
415
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
416
expect(result).toMatchSnapshot();
417
});
418
419
test('includes contents of regular prompt files', async () => {
420
const promptFile = {
421
id: 'vscode.prompt.file__file:doit.prompt.md',
422
name: 'prompt:doit.prompt.md',
423
value: URI.file('doit.prompt.md')
424
};
425
createMockFile(promptFile.value, `This is a prompt file`);
426
427
const req = new TestChatRequest('Process these files', [
428
promptFile
429
]);
430
431
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
432
433
const result = extractChatPromptReferences(resolved.prompt);
434
expect(resolved.prompt).toMatchSnapshot();
435
expect(fixFilePathsForTestComparison(resolved.attachments)).toMatchSnapshot();
436
expect(result).toMatchSnapshot();
437
});
438
439
test('excludes instruction files from references and attachments', async () => {
440
const instructionFile = {
441
id: 'vscode.instructions.file__file:/workspace/my.instructions.md',
442
name: 'my.instructions.md',
443
value: URI.file('/workspace/my.instructions.md')
444
};
445
const regularFileRef = {
446
id: 'regular-file',
447
name: 'regular.ts',
448
value: URI.file('/workspace/regular.ts')
449
};
450
createMockFile(instructionFile.value, `# Instructions\nDo things this way.`);
451
createMockFile(regularFileRef.value, `const x = 1;`);
452
453
const req = new TestChatRequest('Process these files', [
454
instructionFile,
455
regularFileRef
456
]);
457
458
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
459
460
// Instruction file should be excluded from references and attachments
461
const instructionRef = resolved.references.find(r => URI.isUri(r.value) && (r.value as URI).fsPath.includes('my.instructions.md'));
462
expect(instructionRef).toBeUndefined();
463
// Regular file reference should still be included
464
const regularRef = resolved.references.find(r => URI.isUri(r.value) && (r.value as URI).fsPath.includes('regular.ts'));
465
expect(regularRef).toBeDefined();
466
// Attachment for instruction file should not be present
467
const instructionAttachment = resolved.attachments.find(a => a.type === 'file' && a.path.includes('my.instructions.md'));
468
expect(instructionAttachment).toBeUndefined();
469
});
470
471
test('excludes customizations index from references and attachments', async () => {
472
const customizationsIndex = {
473
id: 'vscode.customizations.index',
474
name: 'customizations',
475
value: URI.file('/workspace/.github/copilot-instructions.md')
476
};
477
const regularFileRef = {
478
id: 'regular-file',
479
name: 'regular.ts',
480
value: URI.file('/workspace/regular.ts')
481
};
482
createMockFile(customizationsIndex.value, `# Customizations\nSome instructions.`);
483
createMockFile(regularFileRef.value, `const x = 1;`);
484
485
const req = new TestChatRequest('Process these files', [
486
customizationsIndex,
487
regularFileRef
488
]);
489
490
const resolved = await resolver.resolvePrompt(req, undefined, [], workspaceInfo, [], CancellationToken.None);
491
492
// Customizations index should be excluded from references and attachments
493
const customizationsRef = resolved.references.find(r => URI.isUri(r.value) && (r.value as URI).fsPath.includes('copilot-instructions.md'));
494
expect(customizationsRef).toBeUndefined();
495
// Regular file reference should still be included
496
const regularRef = resolved.references.find(r => URI.isUri(r.value) && (r.value as URI).fsPath.includes('regular.ts'));
497
expect(regularRef).toBeDefined();
498
// Attachment for customizations index should not be present
499
const customizationsAttachment = resolved.attachments.find(a => a.type === 'file' && a.path.includes('copilot-instructions.md'));
500
expect(customizationsAttachment).toBeUndefined();
501
});
502
503
test('extract GitHub PR/Issues', async () => {
504
const result = extractChatPromptReferences(getPromptTextWithGithubIssuePR());
505
expect(result).toMatchSnapshot();
506
});
507
test('extract Git Commit', async () => {
508
const result = extractChatPromptReferences(getPromptTextWithGitCommit());
509
expect(result).toMatchSnapshot();
510
});
511
function createMockFile(uri: URI, text: string) {
512
const doc = createTextDocumentData(uri, text, 'plaintext', '\n').document;
513
workspaceService.textDocuments.push(doc);
514
if (workspaceService.getWorkspaceFolders().length === 0) {
515
workspaceService.getWorkspaceFolders().push(URI.file('/workspace'));
516
}
517
if (uri.scheme !== Schemas.untitled) {
518
fileSystem.mockFile(uri, text);
519
if (workspaceType === 'worktree') {
520
if (uri.fsPath.startsWith('/workspace')) {
521
fileSystem.mockFile(URI.file(uri.fsPath.replace('/workspace', '/worktree')), text);
522
} else if (uri.fsPath.startsWith('\\workspace')) {
523
fileSystem.mockFile(URI.file(uri.fsPath.replace('\\workspace', '\\worktree')), text);
524
}
525
}
526
}
527
}
528
});
529
});
530
});
531
532
suite('multi-workspace with additionalWorkspaces', () => {
533
const disposables = new DisposableStore();
534
let fileSystem: MockFileSystemService;
535
let workspaceService: TestWorkspaceService;
536
let resolver: CopilotCLIPromptResolver;
537
538
beforeEach(() => {
539
const services = createExtensionUnitTestingServices(disposables);
540
const accessor = disposables.add(services.createTestingAccessor());
541
fileSystem = accessor.get(IFileSystemService) as MockFileSystemService;
542
workspaceService = accessor.get(IWorkspaceService) as TestWorkspaceService;
543
const logService = accessor.get(ILogService);
544
const imageSupport = new class extends mock<CopilotCLIImageSupport>() {
545
override storeImage(_imageData: Uint8Array, _mimeType: string): Promise<URI> {
546
throw new Error('Method not implemented.');
547
}
548
};
549
workspaceService.getWorkspaceFolders().push(URI.file('/workspace'));
550
workspaceService.getWorkspaceFolders().push(URI.file('/workspace2'));
551
resolver = new CopilotCLIPromptResolver(imageSupport, logService, fileSystem, workspaceService, services.seal(), accessor.get(IIgnoreService), new MockSkillLocations(), new MockExtensionContext() as unknown as IVSCodeExtensionContext);
552
});
553
554
afterEach(() => {
555
disposables.clear();
556
vi.resetAllMocks();
557
});
558
559
test('translates file reference in additionalWorkspaces to its worktree path', async () => {
560
const fileUri = URI.file('/workspace2/src/main.ts');
561
const worktreeFileUri = URI.file('/worktree2/src/main.ts');
562
563
fileSystem.mockFile(fileUri, 'const x = 1;');
564
fileSystem.mockFile(worktreeFileUri, 'const x = 1;');
565
566
const primaryWorkspaceInfo: IWorkspaceInfo = {
567
folder: URI.file('/workspace'),
568
repository: undefined,
569
worktree: undefined,
570
worktreeProperties: undefined,
571
};
572
const additionalWorkspace: IWorkspaceInfo = {
573
folder: URI.file('/workspace2'),
574
repository: URI.file('/workspace2'),
575
worktree: URI.file('/worktree2'),
576
worktreeProperties: {
577
version: 2,
578
baseCommit: 'HEAD',
579
branchName: 'worktree2-branch',
580
repositoryPath: '/workspace2',
581
worktreePath: '/worktree2',
582
baseBranchName: 'main',
583
},
584
};
585
586
const req = new TestChatRequest('explain file', [
587
{
588
id: fileUri.toString(),
589
name: 'file:main.ts',
590
value: fileUri,
591
},
592
]);
593
594
const resolved = await resolver.resolvePrompt(req, undefined, [], primaryWorkspaceInfo, [additionalWorkspace], CancellationToken.None);
595
596
// File reference should be translated to the worktree path of additionalWorkspace
597
const fileRef = resolved.references.find(r => URI.isUri(r.value));
598
expect((fileRef?.value as {}).toString()).toBe(worktreeFileUri.toString());
599
});
600
601
test('falls back to original URI when worktree file does not exist for additionalWorkspaces', async () => {
602
const fileUri = URI.file('/workspace2/src/main.ts');
603
604
fileSystem.mockFile(fileUri, 'const x = 1;');
605
// Note: worktree file is NOT mocked, so stat will fail
606
607
const primaryWorkspaceInfo: IWorkspaceInfo = {
608
folder: URI.file('/workspace'),
609
repository: undefined,
610
worktree: undefined,
611
worktreeProperties: undefined,
612
};
613
const additionalWorkspace: IWorkspaceInfo = {
614
folder: URI.file('/workspace2'),
615
repository: URI.file('/workspace2'),
616
worktree: URI.file('/worktree2'),
617
worktreeProperties: {
618
version: 2,
619
baseCommit: 'HEAD',
620
branchName: 'worktree2-branch',
621
repositoryPath: '/workspace2',
622
worktreePath: '/worktree2',
623
baseBranchName: 'main',
624
},
625
};
626
627
const req = new TestChatRequest('explain file', [
628
{
629
id: fileUri.toString(),
630
name: 'file:main.ts',
631
value: fileUri,
632
},
633
]);
634
635
const resolved = await resolver.resolvePrompt(req, undefined, [], primaryWorkspaceInfo, [additionalWorkspace], CancellationToken.None);
636
637
// File reference should remain at original URI since worktree file doesn't exist
638
const fileRef = resolved.references.find(r => URI.isUri(r.value));
639
expect((fileRef?.value as {}).toString()).toBe(fileUri.toString());
640
});
641
642
test('uses findMatchingWorktree fallback when file is under repository but not in workspace service', async () => {
643
// Remove /workspace2 from workspace service so getWorkspaceFolder returns undefined
644
const folders = workspaceService.getWorkspaceFolders();
645
const idx = folders.findIndex(f => f.toString() === URI.file('/workspace2').toString());
646
if (idx >= 0) {
647
folders.splice(idx, 1);
648
}
649
650
const fileUri = URI.file('/workspace2/src/main.ts');
651
const worktreeFileUri = URI.file('/worktree2/src/main.ts');
652
653
fileSystem.mockFile(fileUri, 'const x = 1;');
654
fileSystem.mockFile(worktreeFileUri, 'const x = 1;');
655
656
const primaryWorkspaceInfo: IWorkspaceInfo = {
657
folder: URI.file('/workspace'),
658
repository: undefined,
659
worktree: undefined,
660
worktreeProperties: undefined,
661
};
662
// additionalWorkspace has isolation enabled and its repository covers the file
663
const additionalWorkspace: IWorkspaceInfo = {
664
folder: URI.file('/workspace2'),
665
repository: URI.file('/workspace2'),
666
worktree: URI.file('/worktree2'),
667
worktreeProperties: {
668
version: 2,
669
baseCommit: 'HEAD',
670
branchName: 'worktree2-branch',
671
repositoryPath: '/workspace2',
672
worktreePath: '/worktree2',
673
baseBranchName: 'main',
674
},
675
};
676
677
const req = new TestChatRequest('explain file', [
678
{
679
id: fileUri.toString(),
680
name: 'file:main.ts',
681
value: fileUri,
682
},
683
]);
684
685
const resolved = await resolver.resolvePrompt(req, undefined, [], primaryWorkspaceInfo, [additionalWorkspace], CancellationToken.None);
686
687
// findMatchingWorktree should map /workspace2/src/main.ts -> /worktree2/src/main.ts
688
const fileRef = resolved.references.find(r => URI.isUri(r.value));
689
expect((fileRef?.value as {}).toString()).toBe(worktreeFileUri.toString());
690
});
691
692
test('falls back to original URI when findMatchingWorktree candidate does not exist', async () => {
693
// Remove /workspace2 from workspace service so getWorkspaceFolder returns undefined → triggers findMatchingWorktree
694
const folders = workspaceService.getWorkspaceFolders();
695
const idx = folders.findIndex(f => f.toString() === URI.file('/workspace2').toString());
696
if (idx >= 0) {
697
folders.splice(idx, 1);
698
}
699
700
const fileUri = URI.file('/workspace2/src/main.ts');
701
fileSystem.mockFile(fileUri, 'const x = 1;');
702
// worktree file is NOT mocked → stat throws ENOENT → should fall back to original URI
703
704
const primaryWorkspaceInfo: IWorkspaceInfo = {
705
folder: URI.file('/workspace'),
706
repository: undefined,
707
worktree: undefined,
708
worktreeProperties: undefined,
709
};
710
const additionalWorkspace: IWorkspaceInfo = {
711
folder: URI.file('/workspace2'),
712
repository: URI.file('/workspace2'),
713
worktree: URI.file('/worktree2'),
714
worktreeProperties: {
715
version: 2,
716
baseCommit: 'HEAD',
717
branchName: 'worktree2-branch',
718
repositoryPath: '/workspace2',
719
worktreePath: '/worktree2',
720
baseBranchName: 'main',
721
},
722
};
723
724
const req = new TestChatRequest('explain file', [
725
{
726
id: fileUri.toString(),
727
name: 'file:main.ts',
728
value: fileUri,
729
},
730
]);
731
732
const resolved = await resolver.resolvePrompt(req, undefined, [], primaryWorkspaceInfo, [additionalWorkspace], CancellationToken.None);
733
734
// findMatchingWorktree candidate stat fails → original URI returned unchanged
735
const fileRef = resolved.references.find(r => URI.isUri(r.value));
736
expect((fileRef?.value as {}).toString()).toBe(fileUri.toString());
737
});
738
739
test('does not translate URIs when isolation is not enabled in any workspace', async () => {
740
const fileUri = URI.file('/workspace2/src/main.ts');
741
fileSystem.mockFile(fileUri, 'const x = 1;');
742
743
const primaryWorkspaceInfo: IWorkspaceInfo = {
744
folder: URI.file('/workspace'),
745
repository: undefined,
746
worktree: undefined,
747
worktreeProperties: undefined,
748
};
749
// additionalWorkspace has NO isolation
750
const additionalWorkspace: IWorkspaceInfo = {
751
folder: URI.file('/workspace2'),
752
repository: URI.file('/workspace2'),
753
worktree: undefined,
754
worktreeProperties: undefined,
755
};
756
757
const req = new TestChatRequest('explain file', [
758
{
759
id: fileUri.toString(),
760
name: 'file:main.ts',
761
value: fileUri,
762
},
763
]);
764
765
const resolved = await resolver.resolvePrompt(req, undefined, [], primaryWorkspaceInfo, [additionalWorkspace], CancellationToken.None);
766
767
// No translation should occur
768
const fileRef = resolved.references.find(r => URI.isUri(r.value));
769
expect((fileRef?.value as {}).toString()).toBe(fileUri.toString());
770
});
771
});
772
773
function createWorkspaceInfo(workspaceType: 'emptyWorkspace' | 'workspace' | 'worktree'): IWorkspaceInfo {
774
if (workspaceType === 'workspace') {
775
return {
776
...emptyWorkspaceInfo(),
777
folder: URI.file('/workspace'),
778
};
779
}
780
781
if (workspaceType === 'worktree') {
782
return {
783
...emptyWorkspaceInfo(),
784
folder: URI.file('/workspace'),
785
repository: URI.file('/workspace'),
786
worktree: URI.file('/worktree'),
787
worktreeProperties: {
788
version: 2,
789
baseCommit: 'HEAD',
790
branchName: 'worktree-branch',
791
repositoryPath: '/workspace',
792
worktreePath: '/worktree',
793
baseBranchName: 'main',
794
},
795
};
796
}
797
798
return emptyWorkspaceInfo();
799
}
800
801
/**
802
* As we want test to run on all platforms, we need to fix file paths in attachments
803
* to use forward slashes for comparison.
804
*/
805
function fixFilePathsForTestComparison(attachments: Attachment[]): Attachment[] {
806
attachments.forEach(attachment => {
807
if (attachment.type === 'file') {
808
attachment.path = attachment.path.replace(/\\/g, '/');
809
} else if (attachment.type === 'directory') {
810
attachment.path = attachment.path.replace(/\\/g, '/');
811
} else if (attachment.type === 'selection') {
812
attachment.filePath = attachment.filePath.replace(/\\/g, '/');
813
}
814
});
815
return attachments;
816
}
817
818
819
function getPromptTextWithGithubIssuePR() {
820
return `
821
'Explain this
822
<reminder>
823
IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task
824
</reminder>
825
<attachments>
826
<attachment id="#17143 Kernel interrupt_mode \\"message\\" sends interrupt_request on shell channel instead of control channel">
827
{"issueNumber":17143,"owner":"microsoft","repo":"vscode-jupyter","title":"Kernel interrupt_mode \\"message\\" sends interrupt_request on shell channel instead of control channel","body":"### Applies To\\n\\n- [x] Notebooks (.ipynb files)\\n- [ ] Interactive Window and\\\\/or Cell Scripts (.py files with \\\\#%% markers)\\n\\n### What happened?\\n\\n **Description**\\n\\n When a kernel has interrupt_mode set to \'message\', the extension incorrectly sends the interrupt_request message on the shell channel instead of the control channel as specified by the Jupyter protocol.\\n\\n **Expected Behavior**\\n\\n According to the Jupyter [kernel spec documentation](https://github.com/microsoft/vscode-jupyter/blob/4efd36fb61d83bf0c99008648ca633ad688d4ab9/src/kernels/raw/session/rawKernelConnection.node.ts#L361-L370):\\n\\n> In case a kernel can not catch operating system interrupt signals (e.g. the used runtime handles signals and does not allow a user program to define a callback), a kernel can choose to be notified using a message instead. For this to work, the kernels kernelspec must set \`interrupt_mode\` to \`message\`. An interruption will then result in the following message on the \`control\` channel:\\n\\n The interrupt request should:\\n 1. Be created with channel: \'control\'\\n 2. Be sent via sendControlMessage()\\n\\n **Actual Behavior**\\n\\n The current implementation ([rawKernelConnection.node.ts](https://github.com/microsoft/vscode-jupyter/blob/4efd36fb61d83bf0c99008648ca633ad688d4ab9/src/kernels/raw/session/rawKernelConnection.node.ts#L361-L370)):\\n 1. Creates message with channel: \'shell\'\\n 2. Sends via sendShellMessage()\\n\\n This causes two problems:\\n 1. The interrupt is queued behind other shell messages - The shell channel processes messages sequentially, so if there are pending execution requests or other shell messages, the interrupt will wait in the queue instead of being processed immediately. This defeats the purpose of interrupting, as the kernel continues executing while the interrupt waits.\\n 2. Interrupt may fail entirely - Kernels expect interrupt_request on the control channel and may ignore or reject it on the shell channel.\\n\\n### VS Code Version\\n\\n1.105.1\\n\\n### Jupyter Extension Version\\n\\n2025.9.1\\n\\n### Jupyter logs\\n\\n\`\`\`shell\\n\\n\`\`\`\\n\\n### Coding Language and Runtime Version\\n\\n_No response_\\n\\n### Language Extension Version (if applicable)\\n\\n_No response_\\n\\n### Anaconda Version (if applicable)\\n\\n_No response_\\n\\n### Running Jupyter locally or remotely?\\n\\nNone","comments":[]}
828
</attachment>
829
<attachment id="#17131 Bump ipywidgets from 7.7.2 to 8.1.8">
830
{"prNumber":17131,"owner":"microsoft","repo":"vscode-jupyter","title":"Bump ipywidgets from 7.7.2 to 8.1.8","body":"Bumps [ipywidgets](https://github.com/jupyter-widgets/ipywidgets) from 7.7.2 to 8.1.8.\\n<details>\\n<summary>Release notes</summary>\\n<p><em>Sourced from <a href=\\"https://github.com/jupyter-widgets/ipywidgets/releases\\">ipywidgets\'s releases</a>.</em></p>\\n<blockquote>\\n<h2>8.1.8</h2>\\n<h2>What\'s Changed</h2>\\n<ul>\\n<li>Add JupyterCon banner and jupyter colors by <a href=\\"https://github.com/choldgraf\\"><code>@​choldgraf</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3998\\">jupyter-widgets/ipywidgets#3998</a></li>\\n<li>Fix badge formatting in README.md by <a href=\\"https://github.com/Carreau\\"><code>@​Carreau</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/4000\\">jupyter-widgets/ipywidgets#4000</a></li>\\n<li>Add Plausible web stats by <a href=\\"https://github.com/jasongrout\\"><code>@​jasongrout</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/4003\\">jupyter-widgets/ipywidgets#4003</a></li>\\n<li>Update jupyterlab_widgets metadata to indicate it works with JupyterLab 4 by <a href=\\"https://github.com/jasongrout\\"><code>@​jasongrout</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/4004\\">jupyter-widgets/ipywidgets#4004</a></li>\\n</ul>\\n<h2>New Contributors</h2>\\n<ul>\\n<li><a href=\\"https://github.com/choldgraf\\"><code>@​choldgraf</code></a> made their first contribution in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3998\\">jupyter-widgets/ipywidgets#3998</a></li>\\n</ul>\\n<p><strong>Full Changelog</strong>: <a href=\\"https://github.com/jupyter-widgets/ipywidgets/compare/8.1.7...8.1.8\\">https://github.com/jupyter-widgets/ipywidgets/compare/8.1.7...8.1.8</a></p>\\n<h2>8.1.7</h2>\\n<h2>What\'s Changed</h2>\\n<ul>\\n<li>Fix CI + remove Python 3.8 by <a href=\\"https://github.com/martinRenou\\"><code>@​martinRenou</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3989\\">jupyter-widgets/ipywidgets#3989</a></li>\\n<li>Dynamic widgets registry by <a href=\\"https://github.com/martinRenou\\"><code>@​martinRenou</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3988\\">jupyter-widgets/ipywidgets#3988</a></li>\\n</ul>\\n<p><strong>Full Changelog</strong>: <a href=\\"https://github.com/jupyter-widgets/ipywidgets/compare/8.1.6...8.1.7\\">https://github.com/jupyter-widgets/ipywidgets/compare/8.1.6...8.1.7</a></p>\\n<h2>8.1.6</h2>\\n<h2>What\'s Changed</h2>\\n<ul>\\n<li>Fix lumino and lab packages pinning by <a href=\\"https://github.com/martinRenou\\"><code>@​martinRenou</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3958\\">jupyter-widgets/ipywidgets#3958</a></li>\\n<li>Typo fix by <a href=\\"https://github.com/david4096\\"><code>@​david4096</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3960\\">jupyter-widgets/ipywidgets#3960</a></li>\\n<li>Update lables even without MatJax/TypeSetter by <a href=\\"https://github.com/DonJayamanne\\"><code>@​DonJayamanne</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3962\\">jupyter-widgets/ipywidgets#3962</a></li>\\n<li>Update github actions and fix readthedocs by <a href=\\"https://github.com/brichet\\"><code>@​brichet</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3983\\">jupyter-widgets/ipywidgets#3983</a></li>\\n<li>Fix the new line when pressing enter in textarea widget by <a href=\\"https://github.com/brichet\\"><code>@​brichet</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3982\\">jupyter-widgets/ipywidgets#3982</a></li>\\n<li>Backward compatibility on processPhosphorMessage by <a href=\\"https://github.com/martinRenou\\"><code>@​martinRenou</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3945\\">jupyter-widgets/ipywidgets#3945</a></li>\\n<li>Include sourcemaps in npm tarballs by <a href=\\"https://github.com/manzt\\"><code>@​manzt</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3978\\">jupyter-widgets/ipywidgets#3978</a></li>\\n<li>Fix deprecation warning when importing the backend_inline by <a href=\\"https://github.com/emolinlu\\"><code>@​emolinlu</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3984\\">jupyter-widgets/ipywidgets#3984</a></li>\\n</ul>\\n<h2>New Contributors</h2>\\n<ul>\\n<li><a href=\\"https://github.com/david4096\\"><code>@​david4096</code></a> made their first contribution in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3960\\">jupyter-widgets/ipywidgets#3960</a></li>\\n<li><a href=\\"https://github.com/brichet\\"><code>@​brichet</code></a> made their first contribution in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3983\\">jupyter-widgets/ipywidgets#3983</a></li>\\n<li><a href=\\"https://github.com/emolinlu\\"><code>@​emolinlu</code></a> made their first contribution in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3984\\">jupyter-widgets/ipywidgets#3984</a></li>\\n</ul>\\n<p><strong>Full Changelog</strong>: <a href=\\"https://github.com/jupyter-widgets/ipywidgets/compare/8.1.5...8.1.6\\">https://github.com/jupyter-widgets/ipywidgets/compare/8.1.5...8.1.6</a></p>\\n<h2>8.1.5</h2>\\n<h2>What\'s Changed</h2>\\n<ul>\\n<li>More Phosphor backward compatibility by <a href=\\"https://github.com/martinRenou\\"><code>@​martinRenou</code></a> in <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/pull/3942\\">jupyter-widgets/ipywidgets#3942</a></li>\\n</ul>\\n<p><strong>Full Changelog</strong>: <a href=\\"https://github.com/jupyter-widgets/ipywidgets/compare/8.1.4...8.1.5\\">https://github.com/jupyter-widgets/ipywidgets/compare/8.1.4...8.1.5</a></p>\\n<h2>8.1.4</h2>\\n<h2>What\'s Changed</h2>\\n<h3>New features</h3>\\n<!-- raw HTML omitted -->\\n</blockquote>\\n<p>... (truncated)</p>\\n</details>\\n<details>\\n<summary>Commits</summary>\\n<ul>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/3171b1c746643a3893987190dc505661c5562877\\"><code>3171b1c</code></a> Update Output Widget.ipynb (<a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/issues/3881\\">#3881</a>)</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/cd817839ab8b6ef80c8e2b7a94c8f1df1de29734\\"><code>cd81783</code></a> update image processing example notebok imports and function call (<a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/issues/3896\\">#3896</a>)</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/cecd2b0d0314a92b71dce364e3db7a06af8cf64a\\"><code>cecd2b0</code></a> specify Jupyterlab (version 3.x or above) (<a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/issues/3880\\">#3880</a>)</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/0aa1efb563edeb3564f5738dfbee630fd6e4ed6f\\"><code>0aa1efb</code></a> Allow <code>interact</code> to use basic type hint annotations (<a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/issues/3908\\">#3908</a>)</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/2e15cfc030b8f6c319114be23b4f95efb537fd4d\\"><code>2e15cfc</code></a> Update Widget List.ipynb</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/06ed868181a3192067ffcff0ed94815f72a1f7bf\\"><code>06ed868</code></a> Merge pull request <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/issues/3793\\">#3793</a> from ferdnyc/mappings-work-again</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/31259ca8ba33c44a29ba8ffede9de0eece61fb44\\"><code>31259ca</code></a> Merge pull request <a href=\\"https://redirect.github.com/jupyter-widgets/ipywidgets/issues/3801\\">#3801</a> from warrickball/patch-2</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/dd250bfacd875561ad05f692d39c41f350a56b42\\"><code>dd250bf</code></a> Handle Notebook 7 in dev install script</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/a1282ec692b35d91e0b3062016962634c7a8012e\\"><code>a1282ec</code></a> Fix link to &quot;Output widget examples&quot;</li>\\n<li><a href=\\"https://github.com/jupyter-widgets/ipywidgets/commit/b6b3051e0b89c1086ea79327d3e957af7da957fd\\"><code>b6b3051</code></a> Revert &quot;Add note on removal of mapping types in documentation&quot;</li>\\n<li>Additional commits viewable in <a href=\\"https://github.com/jupyter-widgets/ipywidgets/compare/7.7.2...8.1.8\\">compare view</a></li>\\n</ul>\\n</details>\\n<br />\\n\\n\\n[![Dependabot compatibility score](https://dependabot-badges.githubapp.com/badges/compatibility_score?dependency-name=ipywidgets&package-manager=pip&previous-version=7.7.2&new-version=8.1.8)](https://docs.github.com/en/github/managing-security-vulnerabilities/about-dependabot-security-updates#about-compatibility-scores)\\n\\nDependabot will resolve any conflicts with this PR as long as you don\'t alter it yourself. You can also trigger a rebase manually by commenting \`@dependabot rebase\`.\\n\\n[//]: # (dependabot-automerge-start)\\n[//]: # (dependabot-automerge-end)\\n\\n---\\n\\n<details>\\n<summary>Dependabot commands and options</summary>\\n<br />\\n\\nYou can trigger Dependabot actions by commenting on this PR:\\n- \`@dependabot rebase\` will rebase this PR\\n- \`@dependabot recreate\` will recreate this PR, overwriting any edits that have been made to it\\n- \`@dependabot merge\` will merge this PR after your CI passes on it\\n- \`@dependabot squash and merge\` will squash and merge this PR after your CI passes on it\\n- \`@dependabot cancel merge\` will cancel a previously requested merge and block automerging\\n- \`@dependabot reopen\` will reopen this PR if it is closed\\n- \`@dependabot close\` will close this PR and stop Dependabot recreating it. You can achieve the same result by closing it manually\\n- \`@dependabot show <dependency name> ignore conditions\` will show all of the ignore conditions of the specified dependency\\n- \`@dependabot ignore this major version\` will close this PR and stop Dependabot creating any more for this major version (unless you reopen the PR or upgrade to it yourself)\\n- \`@dependabot ignore this minor version\` will close this PR and stop Dependabot creating any more for this minor version (unless you reopen the PR or upgrade to it yourself)\\n- \`@dependabot ignore this dependency\` will close this PR and stop Dependabot creating any more for this dependency (unless you reopen the PR or upgrade to it yourself)\\n\\n\\n</details>","comments":[],"threads":[],"changes":["@@ -5,7 +5,7 @@ pandas\\n jupyter\\n # List of requirements for conda environments that cannot be installed using conda\\n # Pinned per ipywidget 8 support: https://github.com/microsoft/vscode-jupyter/issues/11598\\n-ipywidgets==7.7.2\\n+ipywidgets==8.1.8\\n anywidget\\n matplotlib\\n ipympl"]}
831
</attachment>
832
<attachment id="microsoft/vscode-jupyter">
833
Information about the current repository. You can use this information when you need to calculate diffs or compare changes with the default branch:
834
Repository name: vscode-jupyter
835
Owner: microsoft
836
Current branch: don/well-landfowl
837
Default branch: main
838
Active pull request (may not be the same as open pull request): Skip failing tests https://github.com/microsoft/vscode-jupyter/pull/17155
839
</attachment>
840
841
</attachments>
842
<userRequest>
843
Explain this (See <attachments> above for file contents. You may not need to search or read the file again.)
844
</userRequest>`;
845
}
846
847
function getPromptTextWithGitCommit() {
848
return `
849
Explain this
850
<reminder>
851
IMPORTANT: this context may or may not be relevant to your tasks. You should not respond to this context unless it is highly relevant to your task
852
</reminder>
853
<attachments>
854
<attachment id="microsoft/vscode-jupyter">
855
Information about the current repository. You can use this information when you need to calculate diffs or compare changes with the default branch:
856
Repository name: vscode-jupyter
857
Owner: microsoft
858
Current branch: don/well-landfowl
859
Default branch: main
860
Active pull request (may not be the same as open pull request): Skip failing tests https://github.com/microsoft/vscode-jupyter/pull/17155
861
</attachment>
862
<attachment id="$(repo) select-impala $(git-commit) 4efd36f" filePath="scm-history-item:/Users/donjayamanne/Development/vsc/vscode-jupyter.worktrees/select-impala?%7B%22repositoryId%22%3A%22scm10%22%2C%22historyItemId%22%3A%224efd36fb61d83bf0c99008648ca633ad688d4ab9%22%2C%22historyItemParentId%22%3A%22b67ca34030530fdb75e326293e2c023d59f24fc8%22%2C%22historyItemDisplayId%22%3A%224efd36f%22%7D">
863
commit 4efd36fb61d83bf0c99008648ca633ad688d4ab9
864
Author: Don Jayamanne <[email protected]>
865
Date: Fri Oct 10 18:58:40 2025 +1100
866
867
Fix identification of self signed certs (#17049)
868
869
diff --git a/src/platform/errors/jupyterSelfCertsError.ts b/src/platform/errors/jupyterSelfCertsError.ts
870
index f30ade6977b4..b564ebfafc09 100644
871
--- a/src/platform/errors/jupyterSelfCertsError.ts
872
+++ b/src/platform/errors/jupyterSelfCertsError.ts
873
@@ -18,14 +18,19 @@ export class JupyterSelfCertsError extends BaseError {
874
}
875
public static isSelfCertsError(err: unknown) {
876
const message = (err as undefined | { message: string })?.message ?? \'\';
877
+ const name = (err as undefined | { name: string })?.name ?? \'\';
878
+ const messageToCheck = "";
879
return (
880
- message.indexOf(\'reason: self signed certificate\') >= 0 ||
881
+ messageToCheck.indexOf(\'reason: self signed certificate\') >= 0 ||
882
// https://github.com/microsoft/vscode-jupyter-hub/issues/36#issuecomment-1854097594
883
- message.indexOf(\'reason: unable to verify the first certificate\') >= 0 ||
884
+ messageToCheck.indexOf(\'reason: unable to verify the first certificate\') >= 0 ||
885
// https://github.com/microsoft/vscode-jupyter-hub/issues/36#issuecomment-1761234981
886
- message.indexOf(\'reason: unable to get issuer certificate\') >= 0 ||
887
+ messageToCheck.indexOf(\'reason: unable to get issuer certificate\') >= 0 ||
888
// https://github.com/microsoft/vscode-jupyter/issues/7558#issuecomment-993054968
889
- message.indexOf("is not in the cert\'s list") >= 0
890
+ messageToCheck.indexOf("is not in the cert\'s list") >= 0 ||
891
+ // https://github.com/microsoft/vscode-jupyter/issues/16522
892
+ messageToCheck.indexOf(\'unable to verify the first certificate\') >= 0 ||
893
+ messageToCheck.indexOf(\'UNABLE_TO_GET_ISSUER_CERT\') >= 0
894
);
895
}
896
}
897
</attachment>
898
899
</attachments>
900
<userRequest>
901
Explain this (See <attachments> above for file contents. You may not need to search or read the file again.)
902
</userRequest>`;
903
}
904
905