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
69 changes: 62 additions & 7 deletions src/vs/sessions/AI_CUSTOMIZATIONS.md
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ src/vs/workbench/contrib/chat/browser/aiCustomization/
├── aiCustomizationManagementEditor.ts # SplitView list/editor
├── aiCustomizationManagementEditorInput.ts # Singleton input
├── aiCustomizationListWidget.ts # Search + grouped list
├── aiCustomizationDebugPanel.ts # Debug diagnostics panel
├── aiCustomizationWorkspaceService.ts # Core VS Code workspace service impl
├── customizationCreatorService.ts # AI-guided creation flow
├── mcpListWidget.ts # MCP servers section
Expand All @@ -23,7 +24,7 @@ src/vs/workbench/contrib/chat/browser/aiCustomization/
└── aiCustomizationManagement.css

src/vs/workbench/contrib/chat/common/
└── aiCustomizationWorkspaceService.ts # IAICustomizationWorkspaceService interface
└── aiCustomizationWorkspaceService.ts # IAICustomizationWorkspaceService + IStorageSourceFilter
```

The tree view and overview live in `vs/sessions` (sessions window only):
Expand All @@ -42,23 +43,77 @@ Sessions-specific overrides:

```
src/vs/sessions/contrib/chat/browser/
└── aiCustomizationWorkspaceService.ts # Sessions workspace service override
├── aiCustomizationWorkspaceService.ts # Sessions workspace service override
└── promptsService.ts # AgenticPromptsService (CLI user roots)
src/vs/sessions/contrib/sessions/browser/
├── customizationCounts.ts # Source count utilities
├── customizationCounts.ts # Source count utilities (type-aware)
└── customizationsToolbar.contribution.ts # Sidebar customization links
```

### IAICustomizationWorkspaceService

The `IAICustomizationWorkspaceService` interface controls per-window behavior:

| Property | Core VS Code | Sessions Window |
| Property / Method | Core VS Code | Sessions Window |
|----------|-------------|----------|
| `managementSections` | All sections except Models | Same |
| `visibleStorageSources` | workspace, user, extension, plugin | workspace, user only |
| `preferManualCreation` | `false` (AI generation primary) | `true` (file creation primary) |
| `managementSections` | All sections except Models | Same minus MCP |
| `getStorageSourceFilter(type)` | All sources, no user root filter | Per-type (see below) |
| `isSessionsWindow` | `false` | `true` |
| `activeProjectRoot` | First workspace folder | Active session worktree |

### IStorageSourceFilter

A unified per-type filter controlling which storage sources and user file roots are visible.
Replaces the old `visibleStorageSources`, `getVisibleStorageSources(type)`, and `excludedUserFileRoots`.

```typescript
interface IStorageSourceFilter {
sources: readonly PromptsStorage[]; // Which storage groups to display
includedUserFileRoots?: readonly URI[]; // Allowlist for user roots (undefined = all)
}
```

The shared `applyStorageSourceFilter()` helper applies this filter to any `{uri, storage}` array.

**Sessions filter behavior by type:**

| Type | sources | includedUserFileRoots |
|------|---------|----------------------|
| Hooks | `[local]` | N/A |
| Prompts | `[local, user]` | `undefined` (all roots) |
| Agents, Skills, Instructions | `[local, user]` | `[~/.copilot, ~/.claude, ~/.agents]` |

**Core VS Code:** All types use `[local, user, extension, plugin]` with no user root filter.

### AgenticPromptsService (Sessions)

Sessions overrides `PromptsService` via `AgenticPromptsService` (in `promptsService.ts`):

- **Discovery**: `AgenticPromptFilesLocator` scopes workspace folders to the active session's worktree
- **Creation targets**: `getSourceFolders()` override replaces VS Code profile user roots with `~/.copilot/{subfolder}` for CLI compatibility
- **Hook folders**: Falls back to `.github/hooks` in the active worktree

### Count Consistency

`customizationCounts.ts` uses the **same data sources** as the list widget's `loadItems()`:

| Type | Data Source | Notes |
|------|-------------|-------|
| Agents | `getCustomAgents()` | Parsed agents, not raw files |
| Skills | `findAgentSkills()` | Parsed skills with frontmatter |
| Prompts | `getPromptSlashCommands()` | Filters out skill-type commands |
| Instructions | `listPromptFiles()` + `listAgentInstructions()` | Includes AGENTS.md, CLAUDE.md etc. |
| Hooks | `listPromptFiles()` | Raw hook files |

### Debug Panel

Toggle via Command Palette: "Toggle Customizations Debug Panel". Shows a 4-stage pipeline view:

1. **Raw PromptsService data** — per-storage file lists + type-specific extras
2. **After applyStorageSourceFilter** — what was removed and why
3. **Widget state** — allItems vs displayEntries with group counts
4. **Source/resolved folders** — creation targets and discovery order

## Key Services

- **Prompt discovery**: `IPromptsService` — parsing, lifecycle, storage enumeration
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,14 +4,15 @@
*--------------------------------------------------------------------------------------------*/

import { derived, IObservable } from '../../../../base/common/observable.js';
import { joinPath } from '../../../../base/common/resources.js';
import { URI } from '../../../../base/common/uri.js';
import { IAICustomizationWorkspaceService, AICustomizationManagementSection } from '../../../../workbench/contrib/chat/common/aiCustomizationWorkspaceService.js';
import { IAICustomizationWorkspaceService, AICustomizationManagementSection, IStorageSourceFilter } from '../../../../workbench/contrib/chat/common/aiCustomizationWorkspaceService.js';
import { PromptsStorage } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
import { CustomizationCreatorService } from '../../../../workbench/contrib/chat/browser/aiCustomization/customizationCreatorService.js';
import { PromptsType } from '../../../../workbench/contrib/chat/common/promptSyntax/promptTypes.js';
import { IPathService } from '../../../../workbench/services/path/common/pathService.js';

/**
* Agent Sessions override of IAICustomizationWorkspaceService.
Expand All @@ -23,14 +24,32 @@ export class SessionsAICustomizationWorkspaceService implements IAICustomization

readonly activeProjectRoot: IObservable<URI | undefined>;

readonly excludedUserFileRoots: readonly URI[];
/**
* CLI-accessible user directories for customization file filtering and creation.
*/
private readonly _cliUserRoots: readonly URI[];

/**
* Pre-built filter for types that should only show CLI-accessible user roots.
*/
private readonly _cliUserFilter: IStorageSourceFilter;

constructor(
@ISessionsManagementService private readonly sessionsService: ISessionsManagementService,
@IInstantiationService private readonly instantiationService: IInstantiationService,
@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
@IPathService pathService: IPathService,
) {
this.excludedUserFileRoots = [userDataProfilesService.defaultProfile.promptsHome];
const userHome = pathService.userHome({ preferLocal: true });
this._cliUserRoots = [
joinPath(userHome, '.copilot'),
joinPath(userHome, '.claude'),
joinPath(userHome, '.agents'),
];
this._cliUserFilter = {
sources: [PromptsStorage.local, PromptsStorage.user],
includedUserFileRoots: this._cliUserRoots,
};

this.activeProjectRoot = derived(reader => {
const session = this.sessionsService.activeSession.read(reader);
return session?.worktree ?? session?.repository;
Expand All @@ -52,19 +71,30 @@ export class SessionsAICustomizationWorkspaceService implements IAICustomization
// AICustomizationManagementSection.McpServers,
];

readonly visibleStorageSources: readonly PromptsStorage[] = [
PromptsStorage.local,
PromptsStorage.user,
];
private static readonly _hooksFilter: IStorageSourceFilter = {
sources: [PromptsStorage.local],
};

getVisibleStorageSources(type: PromptsType): readonly PromptsStorage[] {
private static readonly _allUserRootsFilter: IStorageSourceFilter = {
sources: [PromptsStorage.local, PromptsStorage.user],
};

getStorageSourceFilter(type: PromptsType): IStorageSourceFilter {
if (type === PromptsType.hook) {
return [PromptsStorage.local];
return SessionsAICustomizationWorkspaceService._hooksFilter;
}
if (type === PromptsType.prompt) {
// Prompts are shown from all user roots (including VS Code profile)
return SessionsAICustomizationWorkspaceService._allUserRootsFilter;
}
return this.visibleStorageSources;
// Other types only show user files from CLI-accessible roots (~/.copilot, ~/.claude, ~/.agents)
return this._cliUserFilter;
}

readonly preferManualCreation = true;
/**
* Returns the CLI-accessible user directories (~/.copilot, ~/.claude, ~/.agents).
*/
readonly isSessionsWindow = true;

async commitFiles(projectRoot: URI, fileUris: URI[]): Promise<void> {
const session = this.sessionsService.getActiveSession();
Expand Down
45 changes: 45 additions & 0 deletions src/vs/sessions/contrib/chat/browser/promptsService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,47 @@ import { IFileService } from '../../../../platform/files/common/files.js';
import { ILogService } from '../../../../platform/log/common/log.js';
import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';
import { HOOKS_SOURCE_FOLDER } from '../../../../workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.js';
import { PromptsType } from '../../../../workbench/contrib/chat/common/promptSyntax/promptTypes.js';
import { IPromptPath, PromptsStorage } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
import { IWorkbenchEnvironmentService } from '../../../../workbench/services/environment/common/environmentService.js';
import { IPathService } from '../../../../workbench/services/path/common/pathService.js';
import { ISearchService } from '../../../../workbench/services/search/common/search.js';
import { IUserDataProfileService } from '../../../../workbench/services/userDataProfile/common/userDataProfile.js';
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';

export class AgenticPromptsService extends PromptsService {
private _copilotRoot: URI | undefined;

protected override createPromptFilesLocator(): PromptFilesLocator {
return this.instantiationService.createInstance(AgenticPromptFilesLocator);
}

private getCopilotRoot(): URI {
if (!this._copilotRoot) {
const pathService = this.instantiationService.invokeFunction(accessor => accessor.get(IPathService));
this._copilotRoot = joinPath(pathService.userHome({ preferLocal: true }), '.copilot');
}
return this._copilotRoot;
}

/**
* Override to use ~/.copilot as the user-level source folder for creation,
* instead of the VS Code profile's promptsHome.
*/
public override async getSourceFolders(type: PromptsType): Promise<readonly IPromptPath[]> {
const folders = await super.getSourceFolders(type);
const copilotRoot = this.getCopilotRoot();
// Replace any user-storage folders with the CLI-accessible ~/.copilot root
return folders.map(folder => {
if (folder.storage === PromptsStorage.user) {
const subfolder = getCliUserSubfolder(type);
return subfolder
? { ...folder, uri: joinPath(copilotRoot, subfolder) }
: folder;
}
return folder;
});
}
}

class AgenticPromptFilesLocator extends PromptFilesLocator {
Expand Down Expand Up @@ -91,3 +122,17 @@ class AgenticPromptFilesLocator extends PromptFilesLocator {
}
}

/**
* Returns the subfolder name under ~/.copilot/ for a given customization type.
* Used to determine the CLI-accessible user creation target.
*/
function getCliUserSubfolder(type: PromptsType): string | undefined {
switch (type) {
case PromptsType.instructions: return 'instructions';
case PromptsType.skill: return 'skills';
case PromptsType.agent: return 'agents';
case PromptsType.prompt: return 'prompts';
default: return undefined;
}
}

Original file line number Diff line number Diff line change
Expand Up @@ -30,8 +30,6 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerDefaultCon
'github.copilot.chat.languageContext.typescript.enabled': true,
'github.copilot.chat.cli.mcp.enabled': true,

'chat.customizationsMenu.userStoragePath': '~/.copilot',

'inlineChat.affordance': 'editor',
'inlineChat.renderMode': 'hover',

Expand Down
Loading
Loading