Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/vs/workbench/contrib/chat/browser/chat.contribution.ts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ import { ILanguageModelToolsService } from '../common/tools/languageModelToolsSe
import { agentPluginDiscoveryRegistry, IAgentPluginService } from '../common/plugins/agentPluginService.js';
import { ChatPromptFilesExtensionPointHandler } from '../common/promptSyntax/chatPromptFilesContribution.js';
import { PromptsConfig } from '../common/promptSyntax/config/config.js';
import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS, PromptFileSource } from '../common/promptSyntax/config/promptFileLocations.js';
import { INSTRUCTIONS_DEFAULT_SOURCE_FOLDER, INSTRUCTION_FILE_EXTENSION, LEGACY_MODE_DEFAULT_SOURCE_FOLDER, LEGACY_MODE_FILE_EXTENSION, PROMPT_DEFAULT_SOURCE_FOLDER, PROMPT_FILE_EXTENSION, DEFAULT_SKILL_SOURCE_FOLDERS, AGENTS_SOURCE_FOLDER, AGENT_FILE_EXTENSION, SKILL_FILENAME, CLAUDE_AGENTS_SOURCE_FOLDER, DEFAULT_HOOK_FILE_PATHS, DEFAULT_INSTRUCTIONS_SOURCE_FOLDERS, PromptFileSource, COPILOT_USER_AGENTS_SOURCE_FOLDER } from '../common/promptSyntax/config/promptFileLocations.js';
import { PromptLanguageFeaturesProvider } from '../common/promptSyntax/promptFileContributions.js';
import { AGENT_DOCUMENTATION_URL, INSTRUCTIONS_DOCUMENTATION_URL, PROMPT_DOCUMENTATION_URL, SKILL_DOCUMENTATION_URL, HOOK_DOCUMENTATION_URL, PromptsType } from '../common/promptSyntax/promptTypes.js';
import { hookFileSchema, HOOK_SCHEMA_URI } from '../common/promptSyntax/hookSchema.js';
Expand Down Expand Up @@ -884,6 +884,7 @@ configurationRegistry.registerConfiguration({
default: {
[AGENTS_SOURCE_FOLDER]: true,
[CLAUDE_AGENTS_SOURCE_FOLDER]: true,
[COPILOT_USER_AGENTS_SOURCE_FOLDER]: true,
},
additionalProperties: { type: 'boolean' },
propertyNames: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,11 @@ export const AGENTS_SOURCE_FOLDER = '.github/agents';
*/
export const CLAUDE_AGENTS_SOURCE_FOLDER = '.claude/agents';

/**
* Copilot user agents folder.
*/
export const COPILOT_USER_AGENTS_SOURCE_FOLDER = '~/.copilot/agents';

/**
* Claude rules folder.
*/
Expand Down Expand Up @@ -186,6 +191,7 @@ export const DEFAULT_AGENT_SOURCE_FOLDERS: readonly IPromptSourceFolder[] = [
{ path: AGENTS_SOURCE_FOLDER, source: PromptFileSource.GitHubWorkspace, storage: PromptsStorage.local },
{ path: CLAUDE_AGENTS_SOURCE_FOLDER, source: PromptFileSource.ClaudeWorkspace, storage: PromptsStorage.local },
{ path: '~/' + CLAUDE_AGENTS_SOURCE_FOLDER, source: PromptFileSource.ClaudePersonal, storage: PromptsStorage.user },
{ path: COPILOT_USER_AGENTS_SOURCE_FOLDER, source: PromptFileSource.CopilotPersonal, storage: PromptsStorage.user },
];

/**
Expand All @@ -204,7 +210,7 @@ export const DEFAULT_HOOK_FILE_PATHS: readonly IPromptSourceFolder[] = [
*/
function isInAgentsFolder(fileUri: URI): boolean {
const dir = dirname(fileUri.path);
return dir.endsWith('/' + AGENTS_SOURCE_FOLDER) || dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER);
return dir.endsWith('/' + AGENTS_SOURCE_FOLDER) || dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER) || isInCopilotAgentsFolder(fileUri);
}

/**
Expand All @@ -215,6 +221,14 @@ export function isInClaudeAgentsFolder(fileUri: URI): boolean {
return dir.endsWith('/' + CLAUDE_AGENTS_SOURCE_FOLDER);
}

/**
* Helper function to check if a file is directly in the ~/.copilot/agents/ folder.
*/
export function isInCopilotAgentsFolder(fileUri: URI): boolean {
const dir = dirname(fileUri.path);
return dir.endsWith(COPILOT_USER_AGENTS_SOURCE_FOLDER.substring(1));
Comment thread
DonJayamanne marked this conversation as resolved.
}

/**
* Helper function to check if a file is inside the .claude/rules/ folder (including subfolders).
* Claude rules files (.md) in this folder are treated as instruction files.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,10 +23,9 @@ import { ResourceMap } from '../../../../../../base/common/map.js';
import { IFileService } from '../../../../../../platform/files/common/files.js';
import { IPromptsService, Target } from '../service/promptsService.js';
import { ILabelService } from '../../../../../../platform/label/common/label.js';
import { AGENTS_SOURCE_FOLDER, CLAUDE_AGENTS_SOURCE_FOLDER, isInClaudeRulesFolder, LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js';
import { AGENTS_SOURCE_FOLDER, isInClaudeAgentsFolder, isInClaudeRulesFolder, isInCopilotAgentsFolder, LEGACY_MODE_FILE_EXTENSION } from '../config/promptFileLocations.js';
import { Lazy } from '../../../../../../base/common/lazy.js';
import { CancellationToken } from '../../../../../../base/common/cancellation.js';
import { dirname } from '../../../../../../base/common/resources.js';
import { URI } from '../../../../../../base/common/uri.js';

export const MARKERS_OWNER_ID = 'prompts-diagnostics-provider';
Expand Down Expand Up @@ -1023,10 +1022,12 @@ export function isVSCodeOrDefaultTarget(target: Target): boolean {
export function getTarget(promptType: PromptsType, header: PromptHeader | URI): Target {
const uri = header instanceof URI ? header : header.uri;
if (promptType === PromptsType.agent) {
const parentDir = dirname(uri);
if (parentDir.path.endsWith(`/${CLAUDE_AGENTS_SOURCE_FOLDER}`)) {
if (isInClaudeAgentsFolder(uri)) {
return Target.Claude;
}
if (isInCopilotAgentsFolder(uri)) {

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's not correct, for agents from .copilot/agents, we use the target property from the header

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will revert this chaneg

return Target.GitHubCopilot;
}
if (!(header instanceof URI)) {
const target = header.target;
if (target === Target.GitHubCopilot || target === Target.VSCode) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,36 @@ suite('promptFileLocations', function () {
assert.strictEqual(getPromptFileType(uri), undefined);
});

test('.md files in .claude/agents/ subfolder should NOT be recognized as agent files', () => {
const uri = URI.file('/workspace/.claude/agents/subfolder/test.md');
assert.strictEqual(getPromptFileType(uri), undefined);
});

test('.md files in ~/.copilot/agents/ subfolder should NOT be recognized as agent files', () => {
const uri = URI.file('/home/user/.copilot/agents/subfolder/test.md');
assert.strictEqual(getPromptFileType(uri), undefined);
});

test('.md files in .claude/agents/ folder should be recognized as agent files', () => {
const uri = URI.file('/workspace/.claude/agents/demonstrate.md');
assert.strictEqual(getPromptFileType(uri), PromptsType.agent);
});

test('README.md in .claude/agents/ should NOT be recognized as agent file', () => {
const uri = URI.file('/workspace/.claude/agents/README.md');
assert.strictEqual(getPromptFileType(uri), undefined);
});

test('.md files in ~/.copilot/agents/ folder should be recognized as agent files', () => {
const uri = URI.file('/home/user/.copilot/agents/my-agent.md');
assert.strictEqual(getPromptFileType(uri), PromptsType.agent);
});

test('README.md in ~/.copilot/agents/ should NOT be recognized as agent file', () => {
const uri = URI.file('/home/user/.copilot/agents/README.md');
assert.strictEqual(getPromptFileType(uri), undefined);
});
Comment thread
DonJayamanne marked this conversation as resolved.

test('.md files outside .github/agents/ should not be recognized as agent files', () => {
const uri = URI.file('/workspace/test/foo.md');
assert.strictEqual(getPromptFileType(uri), undefined);
Expand Down Expand Up @@ -130,6 +160,16 @@ suite('promptFileLocations', function () {
assert.strictEqual(getCleanPromptName(uri), 'demonstrate');
});

test('removes .md extension for files in .claude/agents/', () => {
const uri = URI.file('/workspace/.claude/agents/claude-agent.md');
assert.strictEqual(getCleanPromptName(uri), 'claude-agent');
});

test('removes .md extension for files in ~/.copilot/agents/', () => {
const uri = URI.file('/home/user/.copilot/agents/my-agent.md');
assert.strictEqual(getCleanPromptName(uri), 'my-agent');
});

test('README.md in .github/agents/ should keep .md extension', () => {
const uri = URI.file('/workspace/.github/agents/README.md');
assert.strictEqual(getCleanPromptName(uri), 'README.md');
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1177,6 +1177,56 @@ suite('PromptsService', () => {
);
});

test('copilot user agents from ~/.copilot/agents/ should have GitHubCopilot target', async () => {
const rootFolderName = 'copilot-user-agents';
const rootFolder = `/${rootFolderName}`;
const rootFolderUri = URI.file(rootFolder);

workspaceContextService.setWorkspace(testWorkspace(rootFolderUri));

await mockFiles(fileService, [
{
// Copilot user agent in ~/.copilot/agents/ (resolved from /home/user/.copilot/agents/)
path: '/home/user/.copilot/agents/copilot-user-agent.md',
contents: [
'---',
'description: \'Copilot user agent from home folder.\'',
'tools: [ read ]',
'---',
'I am a Copilot user agent.',
]
},
]);

const result = (await service.getCustomAgents(CancellationToken.None)).map(agent => ({ ...agent, uri: URI.from(agent.uri) }));
const expected: ICustomAgent[] = [
{
name: 'copilot-user-agent',
description: 'Copilot user agent from home folder.',
target: Target.GitHubCopilot,
tools: ['read'],
agentInstructions: {
content: 'I am a Copilot user agent.',
toolReferences: [],
metadata: undefined
},
handOffs: undefined,
model: undefined,
argumentHint: undefined,
visibility: { userInvocable: true, agentInvocable: true },
agents: undefined,
uri: URI.file('/home/user/.copilot/agents/copilot-user-agent.md'),
source: { storage: PromptsStorage.user }
},
];

assert.deepEqual(
result,
expected,
'Agents from ~/.copilot/agents/ must have Target.GitHubCopilot.',
);
});

test('agents with .md extension should be recognized, except README.md', async () => {
const rootFolderName = 'custom-agents-md-extension';
const rootFolder = `/${rootFolderName}`;
Expand Down
Loading