mirror of
https://github.com/microsoft/vscode.git
synced 2026-04-19 08:08:39 +01:00
* feat: enhance AICustomizationListWidget with grouping and badge support for external customization items * feat: add user data profile service and infer storage from URI in AICustomizationListWidget * Copilot CLI session 8af2fd4a-10fe-4bba-b408-f1b90cebc8dc changes * docs: add chatSessionCustomizationProvider API chain to customizations editor skill Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * fix: address PR review feedback - Remove duplicate sectionToIcon, reuse getSectionIcon instance method - Use Map for O(1) groupKey lookups instead of O(n²) includes/find - Check active project root in inferStorageFromUri for Sessions window - Set pluginUri on provider items and use it for storage inference - Remove redundant plugin check from inferStorageFromUri (handled by caller) Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
198 lines
12 KiB
Markdown
198 lines
12 KiB
Markdown
---
|
||
name: chat-customizations-editor
|
||
description: Use when working on the Chat Customizations editor — the management UI for agents, skills, instructions, hooks, prompts, MCP servers, and plugins.
|
||
---
|
||
|
||
# Chat Customizations Editor
|
||
|
||
Split-view management pane for AI customization items across workspace, user, extension, and plugin storage. Supports harness-based filtering (Local, Copilot CLI, Claude).
|
||
|
||
## Spec
|
||
|
||
**`src/vs/sessions/AI_CUSTOMIZATIONS.md`** — always read before making changes, always update after.
|
||
|
||
## Key Folders
|
||
|
||
| Folder | What |
|
||
|--------|------|
|
||
| `src/vs/workbench/contrib/chat/common/` | `ICustomizationHarnessService`, `ISectionOverride`, `IStorageSourceFilter` — shared interfaces and filter helpers |
|
||
| `src/vs/workbench/contrib/chat/browser/aiCustomization/` | Management editor, list widgets (prompts, MCP, plugins), harness service registration |
|
||
| `src/vs/sessions/contrib/chat/browser/` | Sessions-window overrides (harness service, workspace service) |
|
||
| `src/vs/sessions/contrib/sessions/browser/` | Sessions tree view counts and toolbar |
|
||
|
||
When changing harness descriptor interfaces or factory functions, verify both core and sessions registrations compile.
|
||
|
||
## Key Interfaces
|
||
|
||
- **`IHarnessDescriptor`** — drives all UI behavior declaratively (hidden sections, button overrides, file filters, agent gating). See spec for full field reference.
|
||
- **`ISectionOverride`** — per-section button customization (command invocation, root file creation, type labels, file extensions).
|
||
- **`IStorageSourceFilter`** — controls which storage sources and user roots are visible per harness/type.
|
||
- **`IExternalCustomizationItemProvider`** / **`IExternalCustomizationItem`** — internal interfaces (in `customizationHarnessService.ts`) for extension-contributed providers that supply items directly. These mirror the proposed extension API types.
|
||
|
||
Principle: the UI widgets read everything from the descriptor — no harness-specific conditionals in widget code.
|
||
|
||
## Extension API (`chatSessionCustomizationProvider`)
|
||
|
||
The proposed API in `src/vscode-dts/vscode.proposed.chatSessionCustomizationProvider.d.ts` lets extensions register customization providers. Changes to `IExternalCustomizationItem` or `IExternalCustomizationItemProvider` must be kept in sync across the full chain:
|
||
|
||
| Layer | File | Type |
|
||
|-------|------|------|
|
||
| Extension API | `vscode.proposed.chatSessionCustomizationProvider.d.ts` | `ChatSessionCustomizationItem` |
|
||
| IPC DTO | `extHost.protocol.ts` | `IChatSessionCustomizationItemDto` |
|
||
| ExtHost mapping | `extHostChatAgents2.ts` | `$provideChatSessionCustomizations()` |
|
||
| MainThread mapping | `mainThreadChatAgents2.ts` | `provideChatSessionCustomizations` callback |
|
||
| Internal interface | `customizationHarnessService.ts` | `IExternalCustomizationItem` |
|
||
|
||
When adding fields to `IExternalCustomizationItem`, update all five layers. The proposed API `.d.ts` is additive-only (new optional fields are backward-compatible and do not require a version bump).
|
||
|
||
## Testing
|
||
|
||
Component explorer fixtures (see `component-fixtures` skill): `aiCustomizationListWidget.fixture.ts`, `aiCustomizationManagementEditor.fixture.ts` under `src/vs/workbench/test/browser/componentFixtures/`.
|
||
|
||
### Screenshotting specific tabs
|
||
|
||
The management editor fixture supports a `selectedSection` option to render any tab. Each tab has Dark/Light variants auto-generated by `defineThemedFixtureGroup`.
|
||
|
||
**Available fixture IDs** (use with `mcp_component-exp_screenshot`):
|
||
|
||
| Fixture ID pattern | Tab shown |
|
||
|---|---|
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/AgentsTab/{Dark,Light}` | Agents |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/SkillsTab/{Dark,Light}` | Skills |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/InstructionsTab/{Dark,Light}` | Instructions |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/HooksTab/{Dark,Light}` | Hooks |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/PromptsTab/{Dark,Light}` | Prompts |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/McpServersTab/{Dark,Light}` | MCP Servers |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/PluginsTab/{Dark,Light}` | Plugins |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/LocalHarness/{Dark,Light}` | Default (Agents, Local harness) |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/CliHarness/{Dark,Light}` | Default (Agents, CLI harness) |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/ClaudeHarness/{Dark,Light}` | Default (Agents, Claude harness) |
|
||
| `chat/aiCustomizations/aiCustomizationManagementEditor/Sessions/{Dark,Light}` | Sessions window variant |
|
||
|
||
**Adding a new tab fixture:** Add a variant to the `defineThemedFixtureGroup` in `aiCustomizationManagementEditor.fixture.ts`:
|
||
```typescript
|
||
MyNewTab: defineComponentFixture({
|
||
labels: { kind: 'screenshot' },
|
||
render: ctx => renderEditor(ctx, {
|
||
harness: CustomizationHarness.VSCode,
|
||
selectedSection: AICustomizationManagementSection.MySection,
|
||
}),
|
||
}),
|
||
```
|
||
|
||
The `selectedSection` calls `editor.selectSectionById()` after `setInput`, which navigates to the specified tab and re-layouts.
|
||
|
||
### Populating test data
|
||
|
||
Each customization type requires its own mock path in `createMockPromptsService`:
|
||
- **Agents** — `getCustomAgents()` returns agent objects
|
||
- **Skills** — `findAgentSkills()` returns `IAgentSkill[]`
|
||
- **Prompts** — `getPromptSlashCommands()` returns `IChatPromptSlashCommand[]`
|
||
- **Instructions/Hooks** — `listPromptFiles()` filtered by `PromptsType`
|
||
- **MCP Servers** — `mcpWorkspaceServers`/`mcpUserServers` arrays passed to `IMcpWorkbenchService` mock
|
||
- **Plugins** — `IPluginMarketplaceService.installedPlugins` and `IAgentPluginService.plugins` observables
|
||
|
||
All test data lives in `allFiles` (prompt-based items) and the `mcpWorkspace/UserServers` arrays. Add enough items per category (8+) to invoke scrolling.
|
||
|
||
### Exercising built-in grouping
|
||
|
||
The list widget regroups items from the default chat extension under a "Built-in" header. Three things must be in place for fixtures to exercise this:
|
||
1. Include `BUILTIN_STORAGE` in the harness descriptor's visible sources
|
||
2. Mock `IProductService.defaultChatAgent.chatExtensionId` (e.g., `'GitHub.copilot-chat'`)
|
||
3. Give mock items extension provenance via `extensionId` / `extensionDisplayName` matching that ID
|
||
|
||
Without all three, built-in regrouping silently doesn't run and the fixture only shows flat lists.
|
||
|
||
### Editor contribution service mocks
|
||
|
||
The management editor embeds a `CodeEditorWidget`. Electron-side editor contributions (e.g., `AgentFeedbackEditorWidgetContribution`) are instantiated automatically and crash if their injected services aren't registered. The fixture must mock at minimum:
|
||
- `IAgentFeedbackService` — needs `onDidChangeFeedback`, `onDidChangeNavigation` as `Event.None`
|
||
- `ICodeReviewService` — needs `getReviewState()` / `getPRReviewState()` returning idle observables
|
||
- `IChatEditingService` — needs `editingSessionsObs` as empty observable
|
||
- `IAgentSessionsService` — needs `model.sessions` as empty array
|
||
|
||
These are cross-layer imports from `vs/sessions/` — use `// eslint-disable-next-line local/code-import-patterns` on the import lines.
|
||
|
||
### CI regression gates
|
||
|
||
Key fixtures have `blocksCi: true` in their labels. The `screenshot-test.yml` GitHub Action captures screenshots on every PR to `main` and **fails the CI status check** if any `blocks-ci`-labeled fixture's screenshot changes. This catches layout regressions automatically.
|
||
|
||
Currently gated fixtures: `LocalHarness`, `McpServersTab`, `McpServersTabNarrow`, `AgentsTabNarrow`. When adding a new section or layout-critical fixture, add `blocksCi: true`:
|
||
|
||
```typescript
|
||
MyFixture: defineComponentFixture({
|
||
labels: { kind: 'screenshot', blocksCi: true },
|
||
render: ctx => renderEditor(ctx, { ... }),
|
||
}),
|
||
```
|
||
|
||
Don't add `blocksCi` to every fixture — only ones that cover critical layout paths (default view, section with list + footer, narrow viewport). Too many gated fixtures creates noisy CI.
|
||
|
||
### Screenshot stability
|
||
|
||
Scrollbar fade transitions cause screenshot instability — the scrollbar shifts from `visible` to `invisible fade` class ~2 seconds after a programmatic scroll. After calling `revealLastItem()` or any scroll action, wait for the transition to complete before the fixture's render promise resolves:
|
||
|
||
```typescript
|
||
await new Promise(resolve => setTimeout(resolve, 2400));
|
||
// Then optionally poll until .scrollbar.vertical loses the 'visible' class
|
||
```
|
||
|
||
### Running unit tests
|
||
|
||
```bash
|
||
./scripts/test.sh --grep "applyStorageSourceFilter|customizationCounts"
|
||
npm run compile-check-ts-native && npm run valid-layers-check
|
||
```
|
||
|
||
See the `sessions` skill for sessions-window specific guidance.
|
||
|
||
## Debugging Layout in the Real Product
|
||
|
||
Component fixtures use mock data and a fixed container size. Layout bugs caused by reflow timing, real data shapes, or narrow window sizes often **don't reproduce in fixtures**. When a user reports a broken layout, debug in the live Code OSS product.
|
||
|
||
For launching Code OSS with CDP and connecting `agent-browser`, see the **`launch` skill**. Use `--user-data-dir /tmp/code-oss-debug` to avoid colliding with an already-running instance from another worktree.
|
||
|
||
### Navigating to the customizations editor
|
||
|
||
After connecting, use `snapshot -i` to find the "Open Customizations" button (in the Chat panel header), then click it. To switch sections, use `eval` with a DOM click since sidebar items aren't interactive refs:
|
||
|
||
```bash
|
||
npx agent-browser eval "const items = [...document.querySelectorAll('.section-list-item')]; \
|
||
items.find(el => el.textContent?.includes('MCP'))?.click();"
|
||
```
|
||
|
||
### Inspecting widget layout
|
||
|
||
`agent-browser eval` doesn't always print return values. Use `document.title` as a return channel:
|
||
|
||
```bash
|
||
npx agent-browser eval "const w = document.querySelector('.mcp-list-widget'); \
|
||
const lc = w?.querySelector('.mcp-list-container'); \
|
||
const rows = lc?.querySelectorAll('.monaco-list-row'); \
|
||
document.title = 'DBG:rows=' + (rows?.length ?? -1) \
|
||
+ ',listH=' + (lc?.offsetHeight ?? -1) \
|
||
+ ',seStH=' + (lc?.querySelector('.monaco-scrollable-element')?.style?.height ?? '') \
|
||
+ ',wH=' + (w?.offsetHeight ?? -1);"
|
||
npx agent-browser eval "document.title" 2>&1
|
||
```
|
||
|
||
Key diagnostics:
|
||
- **`rows`** — fewer than expected means `list.layout()` never received the correct viewport height.
|
||
- **`seStH`** — empty means the list was never properly laid out.
|
||
- **`listH` vs `wH`** — list container height should be widget height minus search bar minus footer.
|
||
|
||
### Common layout issues
|
||
|
||
| Symptom | Root cause | Fix pattern |
|
||
|---------|-----------|-------------|
|
||
| List shows 0-1 rows in a tall container | `layout()` bailed out because `offsetHeight` returned 0 during `display:none → visible` transition | Defer layout via `DOM.getWindow(this.element).requestAnimationFrame(...)` |
|
||
| Badge or row content clips at right edge | Widget container missing `overflow: hidden` | Add `overflow: hidden` to the widget's CSS class |
|
||
| Items visible in fixture but not in product | Fixture uses many mock items; real product has few | Add fixture variants with fewer items or narrower dimensions (`width`/`height` options) |
|
||
|
||
### Fixture vs real product gaps
|
||
|
||
Fixtures render at a fixed size (default 900×600) with many mock items. They won't catch:
|
||
- **Reflow timing** — the real product's `display:none → visible` transition may not have reflowed before `layout()` fires
|
||
- **Narrow windows** — add narrow fixture variants (e.g., `width: 550, height: 400`)
|
||
- **Real data counts** — a user with 1 MCP server sees very different layout than a fixture with 12
|