-
Notifications
You must be signed in to change notification settings - Fork 37.3k
Add slash command to troubleshoot agent info #286267
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Pull request overview
This PR adds a new /agentInfo slash command to help troubleshoot and diagnose Copilot customization settings. The command provides comprehensive diagnostics information about the user's customization setup in a single, accessible view.
Key Changes
- Adds a new
/agentInfoslash command that displays diagnostic information about instructions, skills, agents, and other customization files - Shows system information, workspace configuration, and active customization files with their paths
- Implements progressive rendering using multiple
progress.reportcalls to provide a responsive user experience
Comments suppressed due to low confidence (2)
src/vs/workbench/contrib/chat/browser/chat.contribution.ts:1273
- The error object is directly concatenated into the markdown string without proper formatting or message extraction. This pattern is repeated throughout the function (lines 1273, 1306, 1342, 1380, 1416) and could expose internal error details to users. Consider using a consistent error formatting approach that extracts error messages safely.
content += `*Error: ${e}*\n\n`;
src/vs/workbench/contrib/chat/browser/chat.contribution.ts:1202
- The folder name is inserted directly into markdown table cells without escaping special characters. If the folder name contains pipe characters (|) or other markdown special characters, it will break the table formatting. This issue also appears in lines 1284, 1318-1320, 1353-1356, and 1394-1396. Consider escaping these values before inserting them into the markdown table.
for (const folder of workspace.folders) {
content += `| ${folder.name} | ${folder.uri.fsPath} |\n`;
| let content = '# Copilot Customization Diagnostics\n\n'; | ||
| content += '*This shows where instructions, skills, agents, and other customization files are loaded from.*\n\n'; | ||
|
|
||
| // System Information | ||
| content += '## System Information\n\n'; | ||
| content += `| Property | Value |\n`; | ||
| content += `|----------|-------|\n`; | ||
| content += `| Generated | ${new Date().toISOString()} |\n`; | ||
| content += `| Product | ${productService.nameLong} ${productService.version} |\n`; | ||
| content += `| Commit | ${productService.commit || 'n/a'} |\n\n`; | ||
|
|
||
| // Workspace Information | ||
| content += '## Workspace Information\n\n'; | ||
| if (workspace.folders.length > 0) { | ||
| content += '| Folder | Path |\n'; | ||
| content += '|--------|------|\n'; | ||
| for (const folder of workspace.folders) { | ||
| content += `| ${folder.name} | ${folder.uri.fsPath} |\n`; | ||
| } | ||
| content += '\n'; | ||
| } else { | ||
| content += '*No workspace folders open*\n\n'; | ||
| } | ||
|
|
||
| content += `**User Home Directory**: \`${userHome.fsPath}\`\n\n`; | ||
|
|
||
| // Configuration Settings | ||
| content += '## Configuration Settings\n\n'; | ||
| content += '| Setting | Value |\n'; | ||
| content += '|---------|-------|\n'; | ||
| content += `| ${PromptsConfig.USE_COPILOT_INSTRUCTION_FILES} | ${configurationService.getValue(PromptsConfig.USE_COPILOT_INSTRUCTION_FILES)} |\n`; | ||
| content += `| ${PromptsConfig.USE_AGENT_MD} | ${configurationService.getValue(PromptsConfig.USE_AGENT_MD)} |\n`; | ||
| content += `| ${PromptsConfig.USE_NESTED_AGENT_MD} | ${configurationService.getValue(PromptsConfig.USE_NESTED_AGENT_MD)} |\n`; | ||
| content += `| ${PromptsConfig.USE_AGENT_SKILLS} | ${configurationService.getValue(PromptsConfig.USE_AGENT_SKILLS)} |\n\n`; | ||
|
|
||
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | ||
|
|
||
| // Instruction Files Section | ||
| content = '## Instruction Files\n\n'; | ||
| content += '### Lookup Paths\n\n'; | ||
| const instructionLocations = PromptsConfig.promptSourceFolders(configurationService, PromptsType.instructions); | ||
| if (instructionLocations.length === 0) { | ||
| instructionLocations.push(INSTRUCTIONS_DEFAULT_SOURCE_FOLDER); | ||
| } | ||
| content += '| Source | Path |\n'; | ||
| content += '|--------|------|\n'; | ||
| for (const folder of workspace.folders) { | ||
| for (const location of instructionLocations) { | ||
| content += `| Workspace: ${folder.name} | \`${folder.uri.fsPath}/${location}\` |\n`; | ||
| } | ||
| } | ||
| content += `| User Data | (Stored in VS Code user data profile) |\n\n`; | ||
|
|
||
| content += '### Active Instruction Files\n\n'; | ||
| try { | ||
| const instructionFiles = await promptsService.listPromptFiles(PromptsType.instructions, token); | ||
| if (instructionFiles.length > 0) { | ||
| content += '| Name | Storage | Path |\n'; | ||
| content += '|------|---------|------|\n'; | ||
| for (const file of instructionFiles) { | ||
| const label = promptsService.getPromptLocationLabel(file); | ||
| content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`; | ||
| } | ||
| content += '\n'; | ||
| } else { | ||
| content += '*No instruction files found*\n\n'; | ||
| } | ||
| } catch (e) { | ||
| content += `*Error loading instruction files: ${e}*\n\n`; | ||
| } | ||
|
|
||
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | ||
|
|
||
| // Copilot Instructions | ||
| content = '### Copilot Instructions (.github/copilot-instructions.md)\n\n'; | ||
| try { | ||
| const copilotInstructions = await promptsService.listCopilotInstructionsMDs(token); | ||
| if (copilotInstructions.length > 0) { | ||
| content += '| Path |\n'; | ||
| content += '|------|\n'; | ||
| for (const uri of copilotInstructions) { | ||
| content += `| \`${uri.fsPath}\` |\n`; | ||
| } | ||
| content += '\n'; | ||
| } else { | ||
| content += '*No copilot-instructions.md files found*\n\n'; | ||
| } | ||
| } catch (e) { | ||
| content += `*Error: ${e}*\n\n`; | ||
| } | ||
|
|
||
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | ||
|
|
||
| // AGENTS.md Files | ||
| content = '## AGENTS.md Files\n\n'; | ||
| content += '### Lookup Paths\n\n'; | ||
| content += '| Source | Path |\n'; | ||
| content += '|--------|------|\n'; | ||
| for (const folder of workspace.folders) { | ||
| content += `| Workspace Root: ${folder.name} | \`${folder.uri.fsPath}/AGENTS.md\` |\n`; | ||
| } | ||
| if (configurationService.getValue(PromptsConfig.USE_NESTED_AGENT_MD)) { | ||
| content += `| Nested (all subfolders) | \`**/AGENTS.md\` |\n`; | ||
| } | ||
| content += '\n'; | ||
|
|
||
| content += '### Active AGENTS.md Files\n\n'; | ||
| try { | ||
| const includeNested = configurationService.getValue<boolean>(PromptsConfig.USE_NESTED_AGENT_MD) ?? false; | ||
| const agentMDs = await promptsService.listAgentMDs(token, includeNested); | ||
| if (agentMDs.length > 0) { | ||
| content += '| Path |\n'; | ||
| content += '|------|\n'; | ||
| for (const uri of agentMDs) { | ||
| content += `| \`${uri.fsPath}\` |\n`; | ||
| } | ||
| content += '\n'; | ||
| } else { | ||
| content += '*No AGENTS.md files found*\n\n'; | ||
| } | ||
| } catch (e) { | ||
| content += `*Error: ${e}*\n\n`; | ||
| } | ||
|
|
||
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | ||
|
|
||
| // Custom Agents Section | ||
| content = '## Custom Agents\n\n'; | ||
| content += '### Lookup Paths\n\n'; | ||
| const agentLocations = PromptsConfig.promptSourceFolders(configurationService, PromptsType.agent); | ||
| agentLocations.push(AGENTS_SOURCE_FOLDER); | ||
| content += '| Source | Path |\n'; | ||
| content += '|--------|------|\n'; | ||
| for (const folder of workspace.folders) { | ||
| for (const location of [...new Set(agentLocations)]) { | ||
| content += `| Workspace: ${folder.name} | \`${folder.uri.fsPath}/${location}\` |\n`; | ||
| } | ||
| } | ||
| content += `| User Data | (Stored in VS Code user data profile) |\n\n`; | ||
|
|
||
| content += '### Active Custom Agents\n\n'; | ||
| try { | ||
| const customAgents = await promptsService.getCustomAgents(token); | ||
| if (customAgents.length > 0) { | ||
| content += '| Name | Description | Source | Path |\n'; | ||
| content += '|------|-------------|--------|------|\n'; | ||
| for (const agent of customAgents) { | ||
| const sourceLabel = agent.source.storage === 'extension' | ||
| ? `Extension: ${agent.source.extensionId.value}` | ||
| : agent.source.storage; | ||
| content += `| ${agent.name} | ${agent.description || '-'} | ${sourceLabel} | \`${agent.uri.fsPath}\` |\n`; | ||
| } | ||
| content += '\n'; | ||
| } else { | ||
| content += '*No custom agents found*\n\n'; | ||
| } | ||
| } catch (e) { | ||
| content += `*Error: ${e}*\n\n`; | ||
| } | ||
|
|
||
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | ||
|
|
||
| // Agent Skills Section | ||
| content = '## Agent Skills\n\n'; | ||
| content += '### Lookup Paths\n\n'; | ||
| content += '**Workspace Skills:**\n\n'; | ||
| content += '| Source | Path |\n'; | ||
| content += '|--------|------|\n'; | ||
| for (const folder of workspace.folders) { | ||
| for (const { path, type } of DEFAULT_AGENT_SKILLS_WORKSPACE_FOLDERS) { | ||
| content += `| ${type} | \`${folder.uri.fsPath}/${path}/<skill-name>/SKILL.md\` |\n`; | ||
| } | ||
| } | ||
| content += '\n**Personal Skills (User Home):**\n\n'; | ||
| content += '| Source | Path |\n'; | ||
| content += '|--------|------|\n'; | ||
| for (const { path, type } of DEFAULT_AGENT_SKILLS_USER_HOME_FOLDERS) { | ||
| content += `| ${type} | \`${userHome.fsPath}/${path}/<skill-name>/SKILL.md\` |\n`; | ||
| } | ||
| content += '\n'; | ||
|
|
||
| content += '### Active Agent Skills\n\n'; | ||
| try { | ||
| const skills = await promptsService.findAgentSkills(CancellationToken.None); | ||
| if (skills && skills.length > 0) { | ||
| content += '| Name | Description | Type | Path |\n'; | ||
| content += '|------|-------------|------|------|\n'; | ||
| for (const skill of skills) { | ||
| content += `| ${skill.name} | ${skill.description || '-'} | ${skill.type} | \`${skill.uri.fsPath}\` |\n`; | ||
| } | ||
| content += '\n'; | ||
| } else { | ||
| content += '*No agent skills found (skills may be disabled via settings)*\n\n'; | ||
| } | ||
| } catch (e) { | ||
| content += `*Error: ${e}*\n\n`; | ||
| } | ||
|
|
||
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | ||
|
|
||
| // Prompt Files Section | ||
| content = '## Prompt Files\n\n'; | ||
| content += '### Lookup Paths\n\n'; | ||
| const promptLocations = PromptsConfig.promptSourceFolders(configurationService, PromptsType.prompt); | ||
| if (promptLocations.length === 0) { | ||
| promptLocations.push(PROMPT_DEFAULT_SOURCE_FOLDER); | ||
| } | ||
| content += '| Source | Path |\n'; | ||
| content += '|--------|------|\n'; | ||
| for (const folder of workspace.folders) { | ||
| for (const location of promptLocations) { | ||
| content += `| Workspace: ${folder.name} | \`${folder.uri.fsPath}/${location}\` |\n`; | ||
| } | ||
| } | ||
| content += `| User Data | (Stored in VS Code user data profile) |\n\n`; | ||
|
|
||
| content += '### Active Prompt Files\n\n'; | ||
| try { | ||
| const promptFiles = await promptsService.listPromptFiles(PromptsType.prompt, token); | ||
| if (promptFiles.length > 0) { | ||
| content += '| Name | Storage | Path |\n'; | ||
| content += '|------|---------|------|\n'; | ||
| for (const file of promptFiles) { | ||
| const label = promptsService.getPromptLocationLabel(file); | ||
| content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`; | ||
| } | ||
| content += '\n'; | ||
| } else { | ||
| content += '*No prompt files found*\n\n'; | ||
| } | ||
| } catch (e) { | ||
| content += `*Error: ${e}*\n\n`; | ||
| } | ||
|
|
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
All user-facing strings in the markdown output should be localized using the nls.localize() method. The strings include section headers, table headers, messages, and labels. This is consistent with VS Code's localization requirements where all strings visible to users must be externalized.
|
|
||
| content += '### Active Agent Skills\n\n'; | ||
| try { | ||
| const skills = await promptsService.findAgentSkills(CancellationToken.None); |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The function call uses CancellationToken.None instead of the token parameter passed to the slash command handler. This is inconsistent with other async calls in the same function that properly use the token parameter (lines 1240, 1261, 1294, 1327, 1403). Using the provided token allows for proper cancellation handling when the user cancels the operation.
| const skills = await promptsService.findAgentSkills(CancellationToken.None); | |
| const skills = await promptsService.findAgentSkills(token); |
| content += `| Workspace: ${folder.name} | \`${folder.uri.fsPath}/${location}\` |\n`; | ||
| } | ||
| } | ||
| content += `| User Data | (Stored in VS Code user data profile) |\n\n`; | ||
|
|
||
| content += '### Active Instruction Files\n\n'; | ||
| try { | ||
| const instructionFiles = await promptsService.listPromptFiles(PromptsType.instructions, token); | ||
| if (instructionFiles.length > 0) { | ||
| content += '| Name | Storage | Path |\n'; | ||
| content += '|------|---------|------|\n'; | ||
| for (const file of instructionFiles) { | ||
| const label = promptsService.getPromptLocationLabel(file); | ||
| content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`; |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The folder name and file names are inserted directly into markdown table cells without escaping special characters. If these names contain pipe characters (|), backticks (`), or other markdown special characters, they will break the table formatting. Consider escaping these values before inserting them into the markdown table.
This issue also appears in the following locations of the same file:
- line 1201
| content += '| Source | Path |\n'; | ||
| content += '|--------|------|\n'; | ||
| for (const folder of workspace.folders) { | ||
| for (const location of [...new Set(agentLocations)]) { |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Creating a new Set on every iteration of the outer loop is inefficient. The Set should be created once before the loops begin. Move the Set creation outside the nested loops to avoid creating multiple Sets unnecessarily.
| content += '| Source | Path |\n'; | |
| content += '|--------|------|\n'; | |
| for (const folder of workspace.folders) { | |
| for (const location of [...new Set(agentLocations)]) { | |
| const uniqueAgentLocations = [...new Set(agentLocations)]; | |
| content += '| Source | Path |\n'; | |
| content += '|--------|------|\n'; | |
| for (const folder of workspace.folders) { | |
| for (const location of uniqueAgentLocations) { |
| } catch (e) { | ||
| content += `*Error loading instruction files: ${e}*\n\n`; |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The error object is directly concatenated into the markdown string without proper formatting or message extraction. This could expose internal error stack traces or object representations to users. Consider using a more user-friendly error message format, such as extracting the error message with proper checks for Error instances.
This issue also appears in the following locations of the same file:
- line 1273
| for (const agent of customAgents) { | ||
| const sourceLabel = agent.source.storage === 'extension' | ||
| ? `Extension: ${agent.source.extensionId.value}` | ||
| : agent.source.storage; | ||
| content += `| ${agent.name} | ${agent.description || '-'} | ${sourceLabel} | \`${agent.uri.fsPath}\` |\n`; |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
The agent name and description are inserted directly into markdown table cells without escaping special characters. If these values contain pipe characters (|), backticks (`), or other markdown special characters, they will break the table formatting. This same issue exists in the skills table (lines 1372-1373) and prompt files table (lines 1407-1409). Consider escaping these values before inserting them into the markdown table.
| try { | ||
| const promptFiles = await promptsService.listPromptFiles(PromptsType.prompt, token); | ||
| if (promptFiles.length > 0) { | ||
| content += '| Name | Storage | Path |\n'; | ||
| content += '|------|---------|------|\n'; | ||
| for (const file of promptFiles) { | ||
| const label = promptsService.getPromptLocationLabel(file); | ||
| content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`; | ||
| } | ||
| content += '\n'; | ||
| } else { | ||
| content += '*No prompt files found*\n\n'; | ||
| } | ||
| } catch (e) { | ||
| content += `*Error: ${e}*\n\n`; | ||
| } | ||
|
|
||
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | ||
|
|
||
| await timeout(200); | ||
| })); | ||
| } |
Copilot
AI
Jan 7, 2026
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This large function (over 240 lines) could be refactored into smaller, focused helper functions. Consider extracting sections like "System Information", "Workspace Information", "Instruction Files", "Custom Agents", "Agent Skills", and "Prompt Files" into separate private methods. This would improve readability, testability, and maintainability.
| try { | |
| const promptFiles = await promptsService.listPromptFiles(PromptsType.prompt, token); | |
| if (promptFiles.length > 0) { | |
| content += '| Name | Storage | Path |\n'; | |
| content += '|------|---------|------|\n'; | |
| for (const file of promptFiles) { | |
| const label = promptsService.getPromptLocationLabel(file); | |
| content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`; | |
| } | |
| content += '\n'; | |
| } else { | |
| content += '*No prompt files found*\n\n'; | |
| } | |
| } catch (e) { | |
| content += `*Error: ${e}*\n\n`; | |
| } | |
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | |
| await timeout(200); | |
| })); | |
| } | |
| content = await this.appendPromptFilesSection(content, promptsService, token); | |
| progress.report({ content: new MarkdownString(content), kind: 'markdownContent' }); | |
| await timeout(200); | |
| })); | |
| } | |
| private async appendPromptFilesSection(content: string, promptsService: IPromptsService, token: CancellationToken): Promise<string> { | |
| try { | |
| const promptFiles = await promptsService.listPromptFiles(PromptsType.prompt, token); | |
| if (promptFiles.length > 0) { | |
| content += '| Name | Storage | Path |\n'; | |
| content += '|------|---------|------|\n'; | |
| for (const file of promptFiles) { | |
| const label = promptsService.getPromptLocationLabel(file); | |
| content += `| ${file.uri.path.split('/').pop()} | ${file.storage} | ${label} |\n`; | |
| } | |
| content += '\n'; | |
| } else { | |
| content += '*No prompt files found*\n\n'; | |
| } | |
| } catch (e) { | |
| content += `*Error: ${e}*\n\n`; | |
| } | |
| return content; | |
| } |
No description provided.