Skip to content

Commit 3ce0189

Browse files
authored
Sessions: customization improvements (#298122)
* Refactor: unified IStorageSourceFilter replaces per-field filtering APIs Replace visibleStorageSources, getVisibleStorageSources(type), and excludedUserFileRoots with a single getStorageSourceFilter(type) returning IStorageSourceFilter with sources and includedUserFileRoots. - New IStorageSourceFilter interface with allowlist-based user root filtering - Shared applyStorageSourceFilter helper for list widget and counts - Sessions: hooks=workspace-only, prompts=all roots, others=CLI roots - AgenticPromptsService.getSourceFolders override for creation targeting - Remove chat.customizationsMenu.userStoragePath setting - Simplify resolveUserTargetDirectory to pure getSourceFolders delegate - Update all consumer call sites and tests * Fix sidebar/editor count mismatch and rename preferManualCreation Count functions now use the same data sources as loadItems(): - Agents: getCustomAgents() instead of listPromptFilesForStorage - Skills: findAgentSkills() - Prompts: getPromptSlashCommands() filtering out skills - Instructions: listPromptFiles() + listAgentInstructions() - Hooks: listPromptFiles() Rename preferManualCreation to isSessionsWindow for clarity. Add 50 tests for applyStorageSourceFilter and customizationCounts. * Add Developer: Customizations Debug command and fix hooks.json - Debug command opens untitled editor with full pipeline diagnostics - Rename 'Open Chat Customizations (Preview)' to 'Open Customizations (Preview)' - Fix hooks.json: add version field, use bash instead of command - Derive hook events from COPILOT_CLI_HOOK_TYPE_MAP schema automatically * Update AI_CUSTOMIZATIONS.md spec - Document IStorageSourceFilter, AgenticPromptsService, count consistency - Add debug panel section and updated file structure - Reflect isSessionsWindow rename * Remove verbose debug logs from list widget The Developer: Customizations Debug command provides better diagnostics. Remove noisy info-level logs that dump every item URI on every load. * Code review fixes: cache copilotRoot, remove dead getter, fix JSDoc * Add AI customizations manual test plan with 5 scenarios
1 parent 2c7d2ca commit 3ce0189

21 files changed

+1761
-303
lines changed

src/vs/sessions/AI_CUSTOMIZATIONS.md

Lines changed: 62 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ src/vs/workbench/contrib/chat/browser/aiCustomization/
1515
├── aiCustomizationManagementEditor.ts # SplitView list/editor
1616
├── aiCustomizationManagementEditorInput.ts # Singleton input
1717
├── aiCustomizationListWidget.ts # Search + grouped list
18+
├── aiCustomizationDebugPanel.ts # Debug diagnostics panel
1819
├── aiCustomizationWorkspaceService.ts # Core VS Code workspace service impl
1920
├── customizationCreatorService.ts # AI-guided creation flow
2021
├── mcpListWidget.ts # MCP servers section
@@ -23,7 +24,7 @@ src/vs/workbench/contrib/chat/browser/aiCustomization/
2324
└── aiCustomizationManagement.css
2425
2526
src/vs/workbench/contrib/chat/common/
26-
└── aiCustomizationWorkspaceService.ts # IAICustomizationWorkspaceService interface
27+
└── aiCustomizationWorkspaceService.ts # IAICustomizationWorkspaceService + IStorageSourceFilter
2728
```
2829

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

4344
```
4445
src/vs/sessions/contrib/chat/browser/
45-
└── aiCustomizationWorkspaceService.ts # Sessions workspace service override
46+
├── aiCustomizationWorkspaceService.ts # Sessions workspace service override
47+
└── promptsService.ts # AgenticPromptsService (CLI user roots)
4648
src/vs/sessions/contrib/sessions/browser/
47-
├── customizationCounts.ts # Source count utilities
49+
├── customizationCounts.ts # Source count utilities (type-aware)
4850
└── customizationsToolbar.contribution.ts # Sidebar customization links
4951
```
5052

5153
### IAICustomizationWorkspaceService
5254

5355
The `IAICustomizationWorkspaceService` interface controls per-window behavior:
5456

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

64+
### IStorageSourceFilter
65+
66+
A unified per-type filter controlling which storage sources and user file roots are visible.
67+
Replaces the old `visibleStorageSources`, `getVisibleStorageSources(type)`, and `excludedUserFileRoots`.
68+
69+
```typescript
70+
interface IStorageSourceFilter {
71+
sources: readonly PromptsStorage[]; // Which storage groups to display
72+
includedUserFileRoots?: readonly URI[]; // Allowlist for user roots (undefined = all)
73+
}
74+
```
75+
76+
The shared `applyStorageSourceFilter()` helper applies this filter to any `{uri, storage}` array.
77+
78+
**Sessions filter behavior by type:**
79+
80+
| Type | sources | includedUserFileRoots |
81+
|------|---------|----------------------|
82+
| Hooks | `[local]` | N/A |
83+
| Prompts | `[local, user]` | `undefined` (all roots) |
84+
| Agents, Skills, Instructions | `[local, user]` | `[~/.copilot, ~/.claude, ~/.agents]` |
85+
86+
**Core VS Code:** All types use `[local, user, extension, plugin]` with no user root filter.
87+
88+
### AgenticPromptsService (Sessions)
89+
90+
Sessions overrides `PromptsService` via `AgenticPromptsService` (in `promptsService.ts`):
91+
92+
- **Discovery**: `AgenticPromptFilesLocator` scopes workspace folders to the active session's worktree
93+
- **Creation targets**: `getSourceFolders()` override replaces VS Code profile user roots with `~/.copilot/{subfolder}` for CLI compatibility
94+
- **Hook folders**: Falls back to `.github/hooks` in the active worktree
95+
96+
### Count Consistency
97+
98+
`customizationCounts.ts` uses the **same data sources** as the list widget's `loadItems()`:
99+
100+
| Type | Data Source | Notes |
101+
|------|-------------|-------|
102+
| Agents | `getCustomAgents()` | Parsed agents, not raw files |
103+
| Skills | `findAgentSkills()` | Parsed skills with frontmatter |
104+
| Prompts | `getPromptSlashCommands()` | Filters out skill-type commands |
105+
| Instructions | `listPromptFiles()` + `listAgentInstructions()` | Includes AGENTS.md, CLAUDE.md etc. |
106+
| Hooks | `listPromptFiles()` | Raw hook files |
107+
108+
### Debug Panel
109+
110+
Toggle via Command Palette: "Toggle Customizations Debug Panel". Shows a 4-stage pipeline view:
111+
112+
1. **Raw PromptsService data** — per-storage file lists + type-specific extras
113+
2. **After applyStorageSourceFilter** — what was removed and why
114+
3. **Widget state** — allItems vs displayEntries with group counts
115+
4. **Source/resolved folders** — creation targets and discovery order
116+
62117
## Key Services
63118

64119
- **Prompt discovery**: `IPromptsService` — parsing, lifecycle, storage enumeration

src/vs/sessions/contrib/chat/browser/aiCustomizationWorkspaceService.ts

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -4,14 +4,15 @@
44
*--------------------------------------------------------------------------------------------*/
55

66
import { derived, IObservable } from '../../../../base/common/observable.js';
7+
import { joinPath } from '../../../../base/common/resources.js';
78
import { URI } from '../../../../base/common/uri.js';
8-
import { IAICustomizationWorkspaceService, AICustomizationManagementSection } from '../../../../workbench/contrib/chat/common/aiCustomizationWorkspaceService.js';
9+
import { IAICustomizationWorkspaceService, AICustomizationManagementSection, IStorageSourceFilter } from '../../../../workbench/contrib/chat/common/aiCustomizationWorkspaceService.js';
910
import { PromptsStorage } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
1011
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
1112
import { IInstantiationService } from '../../../../platform/instantiation/common/instantiation.js';
12-
import { IUserDataProfilesService } from '../../../../platform/userDataProfile/common/userDataProfile.js';
1313
import { CustomizationCreatorService } from '../../../../workbench/contrib/chat/browser/aiCustomization/customizationCreatorService.js';
1414
import { PromptsType } from '../../../../workbench/contrib/chat/common/promptSyntax/promptTypes.js';
15+
import { IPathService } from '../../../../workbench/services/path/common/pathService.js';
1516

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

2425
readonly activeProjectRoot: IObservable<URI | undefined>;
2526

26-
readonly excludedUserFileRoots: readonly URI[];
27+
/**
28+
* CLI-accessible user directories for customization file filtering and creation.
29+
*/
30+
private readonly _cliUserRoots: readonly URI[];
31+
32+
/**
33+
* Pre-built filter for types that should only show CLI-accessible user roots.
34+
*/
35+
private readonly _cliUserFilter: IStorageSourceFilter;
2736

2837
constructor(
2938
@ISessionsManagementService private readonly sessionsService: ISessionsManagementService,
3039
@IInstantiationService private readonly instantiationService: IInstantiationService,
31-
@IUserDataProfilesService userDataProfilesService: IUserDataProfilesService,
40+
@IPathService pathService: IPathService,
3241
) {
33-
this.excludedUserFileRoots = [userDataProfilesService.defaultProfile.promptsHome];
42+
const userHome = pathService.userHome({ preferLocal: true });
43+
this._cliUserRoots = [
44+
joinPath(userHome, '.copilot'),
45+
joinPath(userHome, '.claude'),
46+
joinPath(userHome, '.agents'),
47+
];
48+
this._cliUserFilter = {
49+
sources: [PromptsStorage.local, PromptsStorage.user],
50+
includedUserFileRoots: this._cliUserRoots,
51+
};
52+
3453
this.activeProjectRoot = derived(reader => {
3554
const session = this.sessionsService.activeSession.read(reader);
3655
return session?.worktree ?? session?.repository;
@@ -52,19 +71,30 @@ export class SessionsAICustomizationWorkspaceService implements IAICustomization
5271
// AICustomizationManagementSection.McpServers,
5372
];
5473

55-
readonly visibleStorageSources: readonly PromptsStorage[] = [
56-
PromptsStorage.local,
57-
PromptsStorage.user,
58-
];
74+
private static readonly _hooksFilter: IStorageSourceFilter = {
75+
sources: [PromptsStorage.local],
76+
};
5977

60-
getVisibleStorageSources(type: PromptsType): readonly PromptsStorage[] {
78+
private static readonly _allUserRootsFilter: IStorageSourceFilter = {
79+
sources: [PromptsStorage.local, PromptsStorage.user],
80+
};
81+
82+
getStorageSourceFilter(type: PromptsType): IStorageSourceFilter {
6183
if (type === PromptsType.hook) {
62-
return [PromptsStorage.local];
84+
return SessionsAICustomizationWorkspaceService._hooksFilter;
85+
}
86+
if (type === PromptsType.prompt) {
87+
// Prompts are shown from all user roots (including VS Code profile)
88+
return SessionsAICustomizationWorkspaceService._allUserRootsFilter;
6389
}
64-
return this.visibleStorageSources;
90+
// Other types only show user files from CLI-accessible roots (~/.copilot, ~/.claude, ~/.agents)
91+
return this._cliUserFilter;
6592
}
6693

67-
readonly preferManualCreation = true;
94+
/**
95+
* Returns the CLI-accessible user directories (~/.copilot, ~/.claude, ~/.agents).
96+
*/
97+
readonly isSessionsWindow = true;
6898

6999
async commitFiles(projectRoot: URI, fileUris: URI[]): Promise<void> {
70100
const session = this.sessionsService.getActiveSession();

src/vs/sessions/contrib/chat/browser/promptsService.ts

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,16 +13,47 @@ import { IFileService } from '../../../../platform/files/common/files.js';
1313
import { ILogService } from '../../../../platform/log/common/log.js';
1414
import { IWorkspaceContextService, IWorkspaceFolder } from '../../../../platform/workspace/common/workspace.js';
1515
import { HOOKS_SOURCE_FOLDER } from '../../../../workbench/contrib/chat/common/promptSyntax/config/promptFileLocations.js';
16+
import { PromptsType } from '../../../../workbench/contrib/chat/common/promptSyntax/promptTypes.js';
17+
import { IPromptPath, PromptsStorage } from '../../../../workbench/contrib/chat/common/promptSyntax/service/promptsService.js';
1618
import { IWorkbenchEnvironmentService } from '../../../../workbench/services/environment/common/environmentService.js';
1719
import { IPathService } from '../../../../workbench/services/path/common/pathService.js';
1820
import { ISearchService } from '../../../../workbench/services/search/common/search.js';
1921
import { IUserDataProfileService } from '../../../../workbench/services/userDataProfile/common/userDataProfile.js';
2022
import { ISessionsManagementService } from '../../sessions/browser/sessionsManagementService.js';
2123

2224
export class AgenticPromptsService extends PromptsService {
25+
private _copilotRoot: URI | undefined;
26+
2327
protected override createPromptFilesLocator(): PromptFilesLocator {
2428
return this.instantiationService.createInstance(AgenticPromptFilesLocator);
2529
}
30+
31+
private getCopilotRoot(): URI {
32+
if (!this._copilotRoot) {
33+
const pathService = this.instantiationService.invokeFunction(accessor => accessor.get(IPathService));
34+
this._copilotRoot = joinPath(pathService.userHome({ preferLocal: true }), '.copilot');
35+
}
36+
return this._copilotRoot;
37+
}
38+
39+
/**
40+
* Override to use ~/.copilot as the user-level source folder for creation,
41+
* instead of the VS Code profile's promptsHome.
42+
*/
43+
public override async getSourceFolders(type: PromptsType): Promise<readonly IPromptPath[]> {
44+
const folders = await super.getSourceFolders(type);
45+
const copilotRoot = this.getCopilotRoot();
46+
// Replace any user-storage folders with the CLI-accessible ~/.copilot root
47+
return folders.map(folder => {
48+
if (folder.storage === PromptsStorage.user) {
49+
const subfolder = getCliUserSubfolder(type);
50+
return subfolder
51+
? { ...folder, uri: joinPath(copilotRoot, subfolder) }
52+
: folder;
53+
}
54+
return folder;
55+
});
56+
}
2657
}
2758

2859
class AgenticPromptFilesLocator extends PromptFilesLocator {
@@ -91,3 +122,17 @@ class AgenticPromptFilesLocator extends PromptFilesLocator {
91122
}
92123
}
93124

125+
/**
126+
* Returns the subfolder name under ~/.copilot/ for a given customization type.
127+
* Used to determine the CLI-accessible user creation target.
128+
*/
129+
function getCliUserSubfolder(type: PromptsType): string | undefined {
130+
switch (type) {
131+
case PromptsType.instructions: return 'instructions';
132+
case PromptsType.skill: return 'skills';
133+
case PromptsType.agent: return 'agents';
134+
case PromptsType.prompt: return 'prompts';
135+
default: return undefined;
136+
}
137+
}
138+

src/vs/sessions/contrib/configuration/browser/configuration.contribution.ts

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,8 +30,6 @@ Registry.as<IConfigurationRegistry>(Extensions.Configuration).registerDefaultCon
3030
'github.copilot.chat.languageContext.typescript.enabled': true,
3131
'github.copilot.chat.cli.mcp.enabled': true,
3232

33-
'chat.customizationsMenu.userStoragePath': '~/.copilot',
34-
3533
'inlineChat.affordance': 'editor',
3634
'inlineChat.renderMode': 'hover',
3735

0 commit comments

Comments
 (0)