Path: blob/main/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationDebugPanel.ts
13406 views
/*---------------------------------------------------------------------------------------------1* Copyright (c) Microsoft Corporation. All rights reserved.2* Licensed under the MIT License. See License.txt in the project root for license information.3*--------------------------------------------------------------------------------------------*/45import { CancellationToken } from '../../../../../base/common/cancellation.js';6import { URI } from '../../../../../base/common/uri.js';7import { IPromptsService, PromptsStorage, IPromptPath } from '../../common/promptSyntax/service/promptsService.js';8import { PromptsType } from '../../common/promptSyntax/promptTypes.js';9import { IAICustomizationWorkspaceService, applyStorageSourceFilter, IStorageSourceFilter } from '../../common/aiCustomizationWorkspaceService.js';10import { AICustomizationManagementSection, sectionToPromptType } from './aiCustomizationManagement.js';11import { ICustomizationHarnessService, ICustomizationItemProvider, IHarnessDescriptor } from '../../common/customizationHarnessService.js';12import { IAgentPluginService } from '../../common/plugins/agentPluginService.js';1314/**15* Snapshot of the list widget's internal state, passed in to avoid coupling.16*/17export interface IDebugWidgetState {18readonly allItems: readonly { readonly name?: string; readonly storage?: PromptsStorage; readonly groupKey?: string }[];19readonly displayEntries: readonly { type: string; label?: string; count?: number; collapsed?: boolean }[];20}2122/**23* Generates a debug diagnostics report for the AI Customization list widget.24*25* The report follows the unified pipeline:26* 1. Provider output — what the active provider returns27* 2. Raw PromptsService data — lower-level service output (when no extension provider)28* 3. Widget state — normalized items and display entries after grouping29* 4. Source folders — where files are discovered from30*/31export async function generateCustomizationDebugReport(32section: AICustomizationManagementSection,33promptsService: IPromptsService,34workspaceService: IAICustomizationWorkspaceService,35widgetState: IDebugWidgetState,36activeDescriptor?: IHarnessDescriptor,37promptsServiceItemProvider?: ICustomizationItemProvider,38harnessService?: ICustomizationHarnessService,39agentPluginService?: IAgentPluginService,40): Promise<string> {41const promptType = sectionToPromptType(section);42const filter = workspaceService.getStorageSourceFilter(promptType);43const lines: string[] = [];4445lines.push(`== Customization Debug: ${section} (${promptType}) ==`);46lines.push(`Window: ${workspaceService.isSessionsWindow ? 'Sessions' : 'Core VS Code'}`);47lines.push(`Active root: ${workspaceService.getActiveProjectRoot()?.fsPath ?? '(none)'}`);48lines.push(`Sections: [${workspaceService.managementSections.join(', ')}]`);49lines.push(`Filter sources: [${filter.sources.join(', ')}]`);5051// Active harness descriptor52if (activeDescriptor) {53lines.push('');54lines.push('--- Active Harness ---');55lines.push(` id: ${activeDescriptor.id}`);56lines.push(` label: ${activeDescriptor.label}`);57lines.push(` hasItemProvider: ${!!activeDescriptor.itemProvider}`);58lines.push(` hasDisableProvider: ${!!activeDescriptor.syncProvider}`);59lines.push(` hiddenSections: ${activeDescriptor.hiddenSections ? `[${activeDescriptor.hiddenSections.join(', ')}]` : '(none)'}`);60lines.push(` workspaceSubpaths: ${activeDescriptor.workspaceSubpaths ? `[${activeDescriptor.workspaceSubpaths.join(', ')}]` : '(none)'}`);61lines.push(` hideGenerateButton: ${activeDescriptor.hideGenerateButton ?? false}`);62lines.push(` requiredAgentId: ${activeDescriptor.requiredAgentId ?? '(none)'}`);63lines.push(` instructionFileFilter: ${activeDescriptor.instructionFileFilter ? `[${activeDescriptor.instructionFileFilter.join(', ')}]` : '(none)'}`);64}65lines.push('');66if (filter.includedUserFileRoots) {67lines.push(`Filter includedUserFileRoots:`);68for (const r of filter.includedUserFileRoots) {69lines.push(` ${r.fsPath}`);70}71} else {72lines.push(`Filter includedUserFileRoots: (all)`);73}74lines.push('');7576// Determine which provider the widget actually uses (mirrors getItemSource logic)77const extensionProvider = activeDescriptor?.itemProvider;78const effectiveProvider = extensionProvider ?? promptsServiceItemProvider;7980// Stage 1: Provider output81if (effectiveProvider) {82let providerLabel: string;83if (extensionProvider) {84providerLabel = 'Extension Provider';85} else {86providerLabel = 'PromptsService Adapter (fallback — no extension provider registered)';87}88await appendProviderData(lines, effectiveProvider, promptType, providerLabel);89} else {90lines.push('--- Stage 1: No provider available ---');91lines.push('');92}9394// Stage 2: Raw PromptsService data — always useful for diagnostics95if (!extensionProvider) {96await appendRawServiceData(lines, promptsService, promptType);97await appendFilteredData(lines, promptsService, promptType, filter);98}99100// Stage 3: Widget state101appendWidgetState(lines, widgetState);102103// Stage 4: Source folders104await appendSourceFolders(lines, promptsService, promptType);105106// Stage 5: All registered harnesses107if (harnessService) {108appendAllHarnesses(lines, harnessService);109}110111// Stage 6: Installed plugins112if (agentPluginService) {113appendInstalledPlugins(lines, agentPluginService);114}115116return lines.join('\n');117}118119interface IPromptFilesByStorage {120readonly localFiles: readonly IPromptPath[];121readonly userFiles: readonly IPromptPath[];122readonly extensionFiles: readonly IPromptPath[];123}124125async function getPromptFilesByStorage(promptsService: IPromptsService, promptType: PromptsType): Promise<IPromptFilesByStorage> {126const [localFiles, userFiles, extensionFiles] = await Promise.all([127promptsService.listPromptFilesForStorage(promptType, PromptsStorage.local, CancellationToken.None),128promptsService.listPromptFilesForStorage(promptType, PromptsStorage.user, CancellationToken.None),129promptsService.listPromptFilesForStorage(promptType, PromptsStorage.extension, CancellationToken.None),130]);131132return { localFiles, userFiles, extensionFiles };133}134135async function appendProviderData(lines: string[], provider: ICustomizationItemProvider, promptType: PromptsType, label: string): Promise<void> {136lines.push(`--- Stage 1: Provider Output (${label}) ---`);137138const allItems = await provider.provideChatSessionCustomizations(CancellationToken.None);139if (!allItems) {140lines.push(' Provider returned undefined');141lines.push('');142return;143}144145lines.push(` Total items from provider: ${allItems.length}`);146147// Group by type for summary148const byType = new Map<string, typeof allItems>();149for (const item of allItems) {150const existing = byType.get(item.type) ?? [];151existing.push(item);152byType.set(item.type, existing);153}154for (const [type, items] of byType) {155lines.push(` ${type}: ${items.length} items`);156for (const item of items) {157const path = item.uri.scheme === 'file' ? item.uri.fsPath : item.uri.toString();158lines.push(` ${item.name} — ${path}`);159if (item.description) {160lines.push(` desc: ${item.description}`);161}162if (item.storage) {163lines.push(` storage: ${item.storage}`);164}165if (item.groupKey) {166lines.push(` groupKey: ${item.groupKey}`);167}168if (item.itemKey) {169lines.push(` itemKey: ${item.itemKey}`);170}171if (item.extensionId) {172lines.push(` extensionId: ${item.extensionId}`);173}174if (item.pluginUri) {175lines.push(` pluginUri: ${item.pluginUri.toString()}`);176}177if (item.badge) {178lines.push(` badge: ${item.badge}`);179}180if (item.status) {181lines.push(` status: ${item.status}${item.statusMessage ? ` (${item.statusMessage})` : ''}`);182}183if (item.enabled === false) {184lines.push(` enabled: false`);185}186}187}188189const sectionItems = allItems.filter(i => i.type === promptType);190lines.push(` Items matching current section (${promptType}): ${sectionItems.length}`);191lines.push('');192}193194async function appendRawServiceData(lines: string[], promptsService: IPromptsService, promptType: PromptsType): Promise<void> {195lines.push('--- Stage 2a: Raw PromptsService Data ---');196197const { localFiles, userFiles, extensionFiles } = await getPromptFilesByStorage(promptsService, promptType);198199lines.push(` listPromptFilesForStorage(local): ${localFiles.length} files`);200appendFileList(lines, localFiles);201202lines.push(` listPromptFilesForStorage(user): ${userFiles.length} files`);203appendFileList(lines, userFiles);204205lines.push(` listPromptFilesForStorage(ext): ${extensionFiles.length} files`);206appendFileList(lines, extensionFiles);207208const allFiles = await promptsService.listPromptFiles(promptType, CancellationToken.None);209lines.push(` listPromptFiles (merged): ${allFiles.length} files`);210211if (promptType === PromptsType.instructions) {212const agentInstructions = await promptsService.listAgentInstructions(CancellationToken.None, undefined);213lines.push(` listAgentInstructions (extra): ${agentInstructions.length} files`);214appendFileList(lines, agentInstructions);215}216217if (promptType === PromptsType.skill) {218const skills = await promptsService.findAgentSkills(CancellationToken.None);219lines.push(` findAgentSkills: ${skills?.length ?? 0} skills`);220for (const s of skills ?? []) {221lines.push(` ${s.name ?? '?'} [${s.storage}] ${s.uri.fsPath}`);222}223}224225if (promptType === PromptsType.agent) {226const agents = await promptsService.getCustomAgents(CancellationToken.None);227lines.push(` getCustomAgents: ${agents.length} agents`);228for (const a of agents) {229lines.push(` ${a.name} [${a.source.storage}] ${a.uri.fsPath}`);230}231}232233if (promptType === PromptsType.prompt) {234const commands = await promptsService.getPromptSlashCommands(CancellationToken.None);235lines.push(` getPromptSlashCommands: ${commands.length} commands`);236for (const c of commands) {237lines.push(` /${c.name} [${c.storage}] ${c.uri.fsPath} (type=${c.type})`);238}239}240241lines.push('');242}243244async function appendFilteredData(lines: string[], promptsService: IPromptsService, promptType: PromptsType, filter: IStorageSourceFilter): Promise<void> {245lines.push('--- Stage 2b: After applyStorageSourceFilter ---');246247const { localFiles, userFiles, extensionFiles } = await getPromptFilesByStorage(promptsService, promptType);248const all: IPromptPath[] = [...localFiles, ...userFiles, ...extensionFiles];249const filtered = applyStorageSourceFilter(all, filter);250lines.push(` Input: ${all.length} → Filtered: ${filtered.length}`);251lines.push(` local: ${filtered.filter(f => f.storage === PromptsStorage.local).length}`);252lines.push(` user: ${filtered.filter(f => f.storage === PromptsStorage.user).length}`);253lines.push(` extension: ${filtered.filter(f => f.storage === PromptsStorage.extension).length}`);254255const removedCount = all.length - filtered.length;256if (removedCount > 0) {257const filteredUris = new Set(filtered.map(f => f.uri.toString()));258const removed = all.filter(f => !filteredUris.has(f.uri.toString()));259lines.push(` Removed (${removedCount}):`);260for (const f of removed) {261lines.push(` [${f.storage}] ${f.uri.fsPath}`);262}263}264265lines.push('');266}267268function appendWidgetState(lines: string[], state: IDebugWidgetState): void {269lines.push('--- Stage 3: Widget State (loadItems → filterItems) ---');270lines.push(` allItems (after loadItems): ${state.allItems.length}`);271lines.push(` local: ${state.allItems.filter(i => i.storage === PromptsStorage.local).length}`);272lines.push(` user: ${state.allItems.filter(i => i.storage === PromptsStorage.user).length}`);273lines.push(` extension: ${state.allItems.filter(i => i.storage === PromptsStorage.extension).length}`);274lines.push(` plugin: ${state.allItems.filter(i => i.storage === PromptsStorage.plugin).length}`);275276for (const item of state.allItems) {277lines.push(` - ${item.name} [storage=${item.storage ?? '?'}, groupKey=${item.groupKey ?? '(none)'}]`);278}279280lines.push(` displayEntries (after filterItems): ${state.displayEntries.length}`);281const fileEntries = state.displayEntries.filter(e => e.type === 'file-item');282lines.push(` file items shown: ${fileEntries.length}`);283const groupEntries = state.displayEntries.filter(e => e.type === 'group-header');284for (const g of groupEntries) {285lines.push(` group "${g.label}": count=${g.count}, collapsed=${g.collapsed}`);286}287lines.push('');288}289290async function appendSourceFolders(lines: string[], promptsService: IPromptsService, promptType: PromptsType): Promise<void> {291lines.push('--- Stage 4: Source Folders (creation targets) ---');292const sourceFolders = await promptsService.getSourceFolders(promptType);293for (const sf of sourceFolders) {294lines.push(` [${sf.storage}] ${sf.uri.fsPath}`);295}296297try {298const resolvedFolders = await promptsService.getResolvedSourceFolders(promptType);299lines.push('');300lines.push('--- Resolved Source Folders (discovery order) ---');301for (const rf of resolvedFolders) {302lines.push(` [${rf.storage}] ${rf.uri.fsPath} (source=${rf.source})`);303}304} catch {305// getResolvedSourceFolders may not exist for all types306}307}308309function appendFileList(lines: string[], files: readonly { uri: URI }[]): void {310for (const f of files) {311lines.push(` ${f.uri.fsPath}`);312}313}314315function appendAllHarnesses(lines: string[], harnessService: ICustomizationHarnessService): void {316lines.push('--- Stage 5: All Registered Harnesses ---');317const activeId = harnessService.activeHarness.get();318const harnesses = harnessService.availableHarnesses.get();319lines.push(` Active: ${activeId}`);320lines.push(` Total harnesses: ${harnesses.length}`);321for (const h of harnesses) {322const isActive = h.id === activeId ? ' (ACTIVE)' : '';323lines.push(` [${h.id}]${isActive} "${h.label}"`);324lines.push(` hasItemProvider: ${!!h.itemProvider}`);325lines.push(` hasDisableProvider: ${!!h.syncProvider}`);326lines.push(` hiddenSections: ${h.hiddenSections ? `[${h.hiddenSections.join(', ')}]` : '(none)'}`);327lines.push(` hideGenerateButton: ${h.hideGenerateButton ?? false}`);328lines.push(` pluginActions: ${h.pluginActions?.length ?? 0}`);329if (h.pluginActions) {330for (const a of h.pluginActions) {331lines.push(` - ${a.id}: ${a.label}`);332}333}334}335lines.push('');336}337338function appendInstalledPlugins(lines: string[], agentPluginService: IAgentPluginService): void {339lines.push('--- Stage 6: Installed Plugins ---');340const plugins = agentPluginService.plugins.get();341lines.push(` Total: ${plugins.length}`);342for (const p of plugins) {343lines.push(` [${p.label}] ${p.uri.toString()}`);344lines.push(` fromMarketplace: ${p.fromMarketplace}`);345}346lines.push('');347}348349350