关于 chat-customizations-editor
chat-customizations-editor 是用于维护 VS Code AI 自定义项管理编辑器的开发技能,面向修改聊天自定义功能及其测试的开发者。它涵盖多种存储来源、按 harness 筛选、扩展提供者接口,以及组件夹具和布局调试。
主要功能
- 01管理提示词、MCP 服务和插件等项目
- 02支持 workspace、用户、扩展和插件存储
- 03按 Local、Copilot CLI 和 Claude harness 筛选
- 04说明扩展自定义项提供者接口链路
- 05提供组件夹具与布局调试指引
适用场景
- 01修改自定义项管理器的分区或列表
- 02接入扩展提供的自定义项
- 03验证 MCP 服务标签页的界面与布局
- 04排查真实产品中的管理器布局问题
MCP 相关
管理编辑器包含 MCP Servers 分区,可展示工作区和用户级 MCP 服务。文档说明相关组件夹具可用于截图验证。
安装
复制下面的命令,在终端中运行。
npx skills add https:/ / github.com/ microsoft/ vscode --skill chat-customizations-editor安装后在 Claude Code 中输入/chat-customizations-editor确认已加载。
详细介绍
聊天自定义项编辑器
用于管理工作区、用户、扩展和插件存储中的 AI 自定义项的分栏视图。支持按运行环境筛选(Local、Copilot CLI、Claude)。
规范
src/vs/sessions/AI_CUSTOMIZATIONS.md — 阅读此文件以了解所有权和接口契约。仅在这些契约发生变化时更新;行为和回归问题应放在有针对性的测试中。
关键文件夹
| 文件夹 | 内容 |
|---|---|
src/vs/workbench/contrib/chat/common/ | ICustomizationHarnessService、ISectionOverride、ICustomizationItemProvider — 共享接口 |
src/vs/workbench/contrib/chat/browser/aiCustomization/ | 管理编辑器、列表组件(提示词、MCP、插件)、运行环境服务注册 |
src/vs/sessions/contrib/chat/browser/ | Sessions 窗口覆盖项(运行环境服务、工作区服务) |
src/vs/sessions/contrib/sessions/browser/ | Sessions 树视图计数和工具栏 |
更改运行环境描述符接口或工厂函数时,请确认核心和 Sessions 注册都能通过编译。
关键接口
IHarnessDescriptor— 以声明方式驱动运行环境行为(隐藏分区、按钮覆盖项、条目提供器、Agent 门控)。稳定的所有权契约请参阅规范。ISectionOverride— 按分区自定义按钮(命令调用、根文件创建、类型标签、文件扩展名)。ICustomizationItemProvider/ICustomizationItem— 内部接口(位于customizationHarnessService.ts),供扩展贡献的、直接提供条目的提供器使用。这些接口与提议中的扩展 API 类型一致。
原则:UI 组件从描述符中读取所有内容 — 组件代码中不应包含特定运行环境的条件判断。
扩展 API(chatSessionCustomizationProvider)
src/vscode-dts/vscode.proposed.chatSessionCustomizationProvider.d.ts 中的提议 API 允许扩展注册自定义项提供器。对 ICustomizationItem 或 ICustomizationItemProvider 的更改必须在整个链路中保持同步:
| 层 | 文件 | 类型 |
|---|---|---|
| 扩展 API | vscode.proposed.chatSessionCustomizationProvider.d.ts | ChatSessionCustomizationItem |
| IPC DTO | extHost.protocol.ts | IChatSessionCustomizationItemDto |
| ExtHost 映射 | extHostChatAgents2.ts | $provideChatSessionCustomizations() |
| MainThread 映射 | mainThreadChatAgents2.ts | provideChatSessionCustomizations 回调 |
| 内部接口 | customizationHarnessService.ts | ICustomizationItem |
向 ICustomizationItem 添加字段时,请更新以上五层。提议 API 的 .d.ts 只能以增量方式扩展(新增可选字段向后兼容,无需提升版本号)。
测试
组件浏览器测试夹具(参见 component-fixtures 技能):src/vs/workbench/test/browser/componentFixtures/ 下的 aiCustomizationListWidget.fixture.ts、aiCustomizationManagementEditor.fixture.ts。
管理分区必须复用编辑器共享的内容面板表面样式和通用树/列表基元。由注册表贡献的分区应与相邻分区的背景、边框、内嵌边距、标题/搜索对齐方式、行状态和分组标题保持一致,而不是采用特定分区的近似样式。
截取特定选项卡
管理编辑器夹具支持 selectedSection 选项,可用于渲染任意选项卡。每个选项卡的深色/浅色变体由 defineThemedFixtureGroup 自动生成。
可用的夹具 ID(与 mcp_component-exp_screenshot 搭配使用):
| 夹具 ID 模式 | 显示的选项卡 |
|---|---|
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/CanvasesTab/{Dark,Light} | 带有扩展父项、Canvas 子项和仅父项启用状态的源分区 |
chat/aiCustomizations/aiCustomizationManagementEditor/LocalHarness/{Dark,Light} | 默认(Agents,本地 harness) |
chat/aiCustomizations/aiCustomizationManagementEditor/CliHarness/{Dark,Light} | 默认(Agents,CLI harness) |
chat/aiCustomizations/aiCustomizationManagementEditor/ClaudeHarness/{Dark,Light} | 默认(Agents,Claude harness) |
chat/aiCustomizations/aiCustomizationManagementEditor/Sessions/{Dark,Light} | Sessions 窗口变体 |
添加新的选项卡夹具: 在 aiCustomizationManagementEditor.fixture.ts 中向 defineThemedFixtureGroup 添加一个变体:
MyNewTab: defineComponentFixture({
labels: { kind: 'screenshot' },
render: ctx => renderEditor(ctx, {
harness: CustomizationHarness.VSCode,
selectedSection: AICustomizationManagementSection.MySection,
}),
}),
selectedSection 会在 setInput 后调用 editor.selectSectionById(),导航到指定选项卡并重新布局。
填充测试数据
每种自定义类型都需要在 createMockPromptsService 中配置自己的模拟路径:
- Agents —
getCustomAgents()返回 Agent 对象 - Skills —
findAgentSkills()返回IAgentSkill[] - Prompts —
getPromptSlashCommands()返回IChatPromptSlashCommand[] - Instructions/Hooks — 按
PromptsType筛选listPromptFiles() - MCP Servers — 传递给
IMcpWorkbenchService模拟对象的mcpWorkspaceServers/mcpUserServers数组 - Plugins —
IPluginMarketplaceService.installedPlugins和IAgentPluginService.plugins可观测对象
所有测试数据都存放在 allFiles(基于提示词的项目)以及 mcpWorkspace/UserServers 数组中。每个类别至少添加足够的项目(8 个以上),以触发滚动。
测试内置分组
列表组件会将来自默认聊天扩展的项目重新分组到“Built-in”标题下。要让夹具覆盖此功能,必须满足以下三项:
- 在 harness 描述符的可见源中包含
BUILTIN_STORAGE - 模拟
IProductService.defaultChatAgent.chatExtensionId(例如,'GitHub.copilot-chat') - 为模拟项目提供与该 ID 匹配的扩展来源信息,通过
extensionId/extensionDisplayName设置
缺少其中任何一项,内置分组都不会运行,夹具只会显示平铺列表。
编辑器扩展服务模拟
管理编辑器内嵌了一个 CodeEditorWidget。Electron 端的编辑器扩展(例如 AgentFeedbackEditorWidgetContribution)会自动实例化,如果未注册其注入的服务,就会崩溃。测试夹具至少必须模拟:
IAgentFeedbackService— 需要将onDidChangeFeedback、onDidChangeNavigation、onDidAddFeedback、onDidConvertFeedback、onDidAddReply、onDidSubmitFeedback设为Event.NoneICodeReviewService— 需要让getReviewState()/getPRReviewState()返回空闲状态的可观察对象IChatEditingService— 需要将editingSessionsObs设为空的可观察对象IAgentSessionsService— 需要将model.sessions设为空数组
这些是来自 vs/sessions/ 的跨层导入——在导入行使用 // eslint-disable-next-line local/code-import-patterns。
CI 回归检查门槛
关键测试夹具的标签中设有 blocksCi: true。component-fixtures.yml GitHub Action 会在每个针对 main 的 PR 上截取屏幕截图;如果任何带有 blocks-ci 标签的测试夹具截图发生变化,CI 状态检查就会失败。这能自动捕获布局回归问题。
目前设有检查门槛的测试夹具:LocalHarness、McpServersTab、McpServersTabNarrow、AgentsTabNarrow。添加新区块或对布局至关重要的测试夹具时,请添加 blocksCi: true:
MyFixture: defineComponentFixture({
labels: { kind: 'screenshot', blocksCi: true },
render: ctx => renderEditor(ctx, { ... }),
}),
不要给每个测试夹具都添加 blocksCi——只添加到覆盖关键布局路径的测试夹具(默认视图、带列表和页脚的区块、窄视口)。设置检查门槛的测试夹具过多,会让 CI 产生大量干扰信息。
截图稳定性
滚动条淡出过渡会导致截图不稳定——在程序化滚动约 2 秒后,滚动条的类会从 visible 变为 invisible fade。调用 revealLastItem() 或任何滚动操作后,请等待过渡完成,再解析测试夹具的渲染 Promise:
await new Promise(resolve => setTimeout(resolve, 2400));
// Then optionally poll until .scrollbar.vertical loses the 'visible' class
运行单元测试
./scripts/test.sh --grep "applyStorageSourceFilter|customizationCounts"
npm run typecheck-client && npm run valid-layers-check
有关 sessions 窗口的具体指导,请参阅 sessions 技能。
在实际产品中调试布局
组件测试夹具使用模拟数据和固定的容器尺寸。因重排时机、真实数据形态或窗口尺寸过窄导致的布局问题,往往无法在测试夹具中复现。用户报告布局异常时,请在实际运行的 Code OSS 产品中调试。
有关如何使用 CDP 启动 Code OSS 并连接 @playwright/cli,请参阅 launch 技能。使用 --user-data-dir /tmp/code-oss-debug,避免与另一个工作树中已运行的实例冲突。
导航到自定义设置编辑器
连接后,使用 snapshot 查找“打开自定义设置”按钮(位于聊天面板标题栏中),然后点击它。要切换区块,请使用 eval 通过 DOM 点击,因为侧边栏项目不是可交互的引用:
npx @playwright/cli eval "(() => { const items = [...document.querySelectorAll('.section-list-item')]; items.find(el => el.textContent?.includes('MCP'))?.click(); })()"
检查组件布局
@playwright/cli eval 会将表达式包装在 () => (...) 中——多语句代码请使用 IIFE。使用 document.title 作为返回通道:
npx @playwright/cli 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 @playwright/cli eval "document.title" 2>&1
关键诊断项:
rows— 数量少于预期,表示list.layout()从未收到正确的视口高度。seStH— 为空表示列表从未正确布局。listH与wH— 列表容器高度应为组件高度减去搜索栏和页脚的高度。
常见布局问题
| 症状 | 根本原因 | 修复方式 |
|---|---|---|
| 高容器中只显示 0–1 行 | 在 display:none → visible 过渡期间,offsetHeight 返回了 0,导致 layout() 提前退出 | 通过 DOM.getWindow(this.element).requestAnimationFrame(...) 延迟布局 |
| 徽标或行内容在右侧边缘被截断 | 组件容器缺少 overflow: hidden | 将 overflow: hidden 添加到组件的 CSS 类中 |
| 测试夹具中可见但产品中不可见 | 测试夹具使用了许多模拟项目;实际产品中的项目较少 | 添加项目较少或尺寸更窄的测试夹具变体(width/height 选项) |
Fixture 与真实产品之间的差异
Fixture 以固定尺寸(默认 900×600)渲染,并包含许多模拟项。它们无法发现以下问题:
- 重排时机 — 真实产品的
display:none → visible转换可能尚未触发重排,layout()就已执行 - 窄窗口 — 添加窄窗口 Fixture 变体(例如,
width: 550, height: 400) - 真实数据数量 — 只有 1 个 MCP 服务的用户,其布局与包含 12 个服务的 Fixture 大不相同