diff --git a/src/vs/workbench/contrib/chat/browser/actions/chatCustomizationDiagnosticsAction.ts b/src/vs/workbench/contrib/chat/browser/actions/chatCustomizationDiagnosticsAction.ts
index e6c881e8a0e..a5b99a144dd 100644
--- a/src/vs/workbench/contrib/chat/browser/actions/chatCustomizationDiagnosticsAction.ts
+++ b/src/vs/workbench/contrib/chat/browser/actions/chatCustomizationDiagnosticsAction.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import { CancellationToken } from '../../../../../base/common/cancellation.js';
+import { Schemas } from '../../../../../base/common/network.js';
import { ServicesAccessor } from '../../../../../editor/browser/editorExtensions.js';
import { localize2 } from '../../../../../nls.js';
import { Action2, MenuId, registerAction2 } from '../../../../../platform/actions/common/actions.js';
@@ -38,15 +39,20 @@ function encodePathForMarkdown(path: string): string {
* The returned path is URL encoded for use in markdown link targets.
*/
function getRelativePath(uri: URI, workspaceFolders: readonly IWorkspaceFolder[]): string {
+ // On desktop, vscode-userdata scheme maps 1:1 to file scheme paths via FileUserDataProvider.
+ // Convert to file scheme so relativePath() can compute paths correctly.
+ // On web, vscode-userdata uses IndexedDB so this conversion has no effect (different schemes won't match workspace folders).
+ const normalizedUri = uri.scheme === Schemas.vscodeUserData ? uri.with({ scheme: Schemas.file }) : uri;
+
for (const folder of workspaceFolders) {
- const relative = relativePath(folder.uri, uri);
+ const relative = relativePath(folder.uri, normalizedUri);
if (relative) {
return encodePathForMarkdown(relative);
}
}
// Fall back to fsPath if not under any workspace folder
// Use forward slashes for consistency in markdown links
- return encodePathForMarkdown(uri.fsPath.replace(/\\/g, '/'));
+ return encodePathForMarkdown(normalizedUri.fsPath.replace(/\\/g, '/'));
}
// Tree prefixes
@@ -117,8 +123,13 @@ export function registerChatCustomizationDiagnosticsAction() {
}, {
id: CHAT_CONFIG_MENU_ID,
when: ContextKeyExpr.and(ChatContextKeys.enabled, ContextKeyExpr.equals('view', ChatViewId)),
- order: 20,
+ order: 14,
group: '3_configure'
+ }, {
+ id: MenuId.ChatWelcomeContext,
+ group: '2_settings',
+ order: 0,
+ when: ChatContextKeys.inChatEditor.negate()
}]
});
}
@@ -423,12 +434,14 @@ export function formatStatusOutput(
// Count loaded and skipped files (overwritten counts as skipped)
let loadedCount = info.files.filter(f => f.status === 'loaded').length;
const skippedCount = info.files.filter(f => f.status === 'skipped' || f.status === 'overwritten').length;
- // Include special files in the loaded count
- if (info.type === PromptsType.agent && specialFiles.agentsMd.enabled) {
- loadedCount += specialFiles.agentsMd.files.length;
- }
- if (info.type === PromptsType.instructions && specialFiles.copilotInstructions.enabled) {
- loadedCount += specialFiles.copilotInstructions.files.length;
+ // Include special files in the loaded count for instructions
+ if (info.type === PromptsType.instructions) {
+ if (specialFiles.agentsMd.enabled) {
+ loadedCount += specialFiles.agentsMd.files.length;
+ }
+ if (specialFiles.copilotInstructions.enabled) {
+ loadedCount += specialFiles.copilotInstructions.files.length;
+ }
}
lines.push(`**${typeName}**${enabledStatus}
`);
@@ -558,8 +571,9 @@ export function formatStatusOutput(
hasContent = true;
}
- // Add special files for agents (AGENTS.md)
- if (info.type === PromptsType.agent) {
+ // Add special files for instructions (AGENTS.md and copilot-instructions.md)
+ if (info.type === PromptsType.instructions) {
+ // AGENTS.md
if (specialFiles.agentsMd.enabled && specialFiles.agentsMd.files.length > 0) {
lines.push(`AGENTS.md
`);
for (let i = 0; i < specialFiles.agentsMd.files.length; i++) {
@@ -575,10 +589,8 @@ export function formatStatusOutput(
lines.push(`AGENTS.md -
`);
hasContent = true;
}
- }
- // Add special files for instructions (copilot-instructions.md)
- if (info.type === PromptsType.instructions) {
+ // copilot-instructions.md
if (specialFiles.copilotInstructions.enabled && specialFiles.copilotInstructions.files.length > 0) {
lines.push(`${COPILOT_CUSTOM_INSTRUCTIONS_FILENAME}
`);
for (let i = 0; i < specialFiles.copilotInstructions.files.length; i++) {
diff --git a/src/vs/workbench/contrib/chat/test/browser/actions/chatCustomizationDiagnosticsAction.test.ts b/src/vs/workbench/contrib/chat/test/browser/actions/chatCustomizationDiagnosticsAction.test.ts
index 1aa7974773e..105de058437 100644
--- a/src/vs/workbench/contrib/chat/test/browser/actions/chatCustomizationDiagnosticsAction.test.ts
+++ b/src/vs/workbench/contrib/chat/test/browser/actions/chatCustomizationDiagnosticsAction.test.ts
@@ -4,6 +4,7 @@
*--------------------------------------------------------------------------------------------*/
import assert from 'assert';
+import { Schemas } from '../../../../../../base/common/network.js';
import { URI } from '../../../../../../base/common/uri.js';
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../base/test/common/utils.js';
import { formatStatusOutput, IFileStatusInfo, IPathInfo, ITypeStatusInfo } from '../../../browser/actions/chatCustomizationDiagnosticsAction.js';
@@ -91,7 +92,6 @@ suite('formatStatusOutput', () => {
'.github/agents
',
`${TREE_BRANCH} [\`code-reviewer.agent.md\`](${filePath('.github/agents/code-reviewer.agent.md')})
`,
`${TREE_END} [\`test-helper.agent.md\`](${filePath('.github/agents/test-helper.agent.md')})
`,
- 'AGENTS.md -
',
''
));
});
@@ -119,7 +119,6 @@ suite('formatStatusOutput', () => {
'.github/agents
',
`${TREE_BRANCH} [\`good-agent.agent.md\`](${filePath('.github/agents/good-agent.agent.md')})
`,
`${TREE_END} ${ICON_ERROR} [\`broken-agent.agent.md\`](${filePath('.github/agents/broken-agent.agent.md')}) - *Missing name attribute*
`,
- 'AGENTS.md -
',
''
));
});
@@ -151,7 +150,6 @@ suite('formatStatusOutput', () => {
`${TREE_END} [\`my-agent.agent.md\`](${filePath('.github/agents/my-agent.agent.md')})
`,
'~/.copilot/agents
',
`${TREE_END} ${ICON_WARN} [\`my-agent.agent.md\`](${filePath('home/.copilot/agents/my-agent.agent.md')}) - *Overwritten by higher priority file*
`,
- 'AGENTS.md -
',
''
));
});
@@ -229,16 +227,17 @@ suite('formatStatusOutput', () => {
'',
'.github/instructions
',
`${TREE_END} [\`testing.instructions.md\`](${filePath('.github/instructions/testing.instructions.md')})
`,
+ 'AGENTS.md -
',
'copilot-instructions.md
',
`${TREE_END} [\`copilot-instructions.md\`](${filePath('.github/copilot-instructions.md')})
`,
''
));
});
- test('agents with AGENTS.md enabled', () => {
+ test('instructions with AGENTS.md enabled', () => {
const statusInfos: ITypeStatusInfo[] = [{
- type: PromptsType.agent,
- paths: [createPath('.github/agents', true)],
+ type: PromptsType.instructions,
+ paths: [createPath('.github/instructions', true)],
files: [],
enabled: true
}];
@@ -254,13 +253,14 @@ suite('formatStatusOutput', () => {
'## Chat Customization Diagnostics',
'*WARNING: This file may contain sensitive information.*',
'',
- '**Custom Agents**
',
+ '**Instructions**
',
'*2 files loaded*',
'',
- '.github/agents
',
+ '.github/instructions
',
'AGENTS.md
',
`${TREE_BRANCH} [\`AGENTS.md\`](${filePath('AGENTS.md')})
`,
`${TREE_END} [\`AGENTS.md\`](${filePath('docs/AGENTS.md')})
`,
+ 'copilot-instructions.md -
',
''
));
});
@@ -286,7 +286,6 @@ suite('formatStatusOutput', () => {
'',
'.github/agents
',
`${ICON_ERROR} custom/agents - *Folder does not exist*
`,
- 'AGENTS.md -
',
''
));
});
@@ -310,7 +309,6 @@ suite('formatStatusOutput', () => {
'**Custom Agents**
',
'',
'.github/agents
',
- 'AGENTS.md -
',
''
));
});
@@ -341,7 +339,6 @@ suite('formatStatusOutput', () => {
`${TREE_END} [\`local-agent.agent.md\`](${filePath('.github/agents/local-agent.agent.md')})
`,
'Extension: my-publisher.my-extension
',
`${TREE_END} [\`ext-agent.agent.md\`](${filePath('extensions/my-publisher.my-extension/agents/ext-agent.agent.md')})
`,
- 'AGENTS.md -
',
''
));
});
@@ -406,13 +403,13 @@ suite('formatStatusOutput', () => {
'',
'.github/agents
',
`${TREE_END} [\`helper.agent.md\`](${filePath('.github/agents/helper.agent.md')})
`,
- 'AGENTS.md -
',
'',
'**Instructions**
',
'*1 file loaded*',
'',
'.github/instructions
',
`${TREE_END} [\`code-style.instructions.md\`](${filePath('.github/instructions/code-style.instructions.md')})
`,
+ 'AGENTS.md -
',
'copilot-instructions.md -
',
'',
'**Prompt Files**
',
@@ -483,4 +480,45 @@ suite('formatStatusOutput', () => {
assert.ok(output.includes('docs%20%26%20notes'), 'Ampersand should be URL-encoded');
assert.ok(output.includes('test%5B1%5D.prompt.md'), 'Brackets should be URL-encoded');
});
+
+ test('vscode-userdata scheme URIs are converted to file scheme for relative paths', () => {
+ // Create a workspace folder
+ const workspaceFolderUri = URI.file('/Users/test/workspace');
+ const workspaceFolder = {
+ uri: workspaceFolderUri,
+ name: 'workspace',
+ index: 0,
+ toResource: (relativePath: string) => URI.joinPath(workspaceFolderUri, relativePath)
+ };
+
+ // Create a vscode-userdata URI that maps to a path under the workspace
+ const userDataUri = URI.file('/Users/test/workspace/.github/agents/my-agent.agent.md').with({ scheme: Schemas.vscodeUserData });
+
+ const statusInfos: ITypeStatusInfo[] = [{
+ type: PromptsType.agent,
+ paths: [{
+ uri: URI.file('/Users/test/workspace/.github/agents'),
+ exists: true,
+ storage: PromptsStorage.local,
+ scanOrder: 1,
+ displayPath: '.github/agents',
+ isDefault: true
+ }],
+ files: [{
+ uri: userDataUri,
+ status: 'loaded',
+ name: 'my-agent.agent.md',
+ storage: PromptsStorage.local
+ }],
+ enabled: true
+ }];
+
+ const output = formatStatusOutput(statusInfos, emptySpecialFiles, [workspaceFolder]);
+
+ // The vscode-userdata URI should be converted to file scheme internally,
+ // allowing relative path computation against workspace folders
+ assert.ok(output.includes('.github/agents/my-agent.agent.md'), 'Should use relative path from workspace folder');
+ // Should not contain the full absolute path
+ assert.ok(!output.includes('/Users/test/workspace/.github'), 'Should not contain absolute path when relative path is available');
+ });
});