Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
microsoft
GitHub Repository: microsoft/vscode
Path: blob/main/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationDebugPanel.ts
13406 views
1
/*---------------------------------------------------------------------------------------------
2
* Copyright (c) Microsoft Corporation. All rights reserved.
3
* Licensed under the MIT License. See License.txt in the project root for license information.
4
*--------------------------------------------------------------------------------------------*/
5
6
import { CancellationToken } from '../../../../../base/common/cancellation.js';
7
import { URI } from '../../../../../base/common/uri.js';
8
import { IPromptsService, PromptsStorage, IPromptPath } from '../../common/promptSyntax/service/promptsService.js';
9
import { PromptsType } from '../../common/promptSyntax/promptTypes.js';
10
import { IAICustomizationWorkspaceService, applyStorageSourceFilter, IStorageSourceFilter } from '../../common/aiCustomizationWorkspaceService.js';
11
import { AICustomizationManagementSection, sectionToPromptType } from './aiCustomizationManagement.js';
12
import { ICustomizationHarnessService, ICustomizationItemProvider, IHarnessDescriptor } from '../../common/customizationHarnessService.js';
13
import { IAgentPluginService } from '../../common/plugins/agentPluginService.js';
14
15
/**
16
* Snapshot of the list widget's internal state, passed in to avoid coupling.
17
*/
18
export interface IDebugWidgetState {
19
readonly allItems: readonly { readonly name?: string; readonly storage?: PromptsStorage; readonly groupKey?: string }[];
20
readonly displayEntries: readonly { type: string; label?: string; count?: number; collapsed?: boolean }[];
21
}
22
23
/**
24
* Generates a debug diagnostics report for the AI Customization list widget.
25
*
26
* The report follows the unified pipeline:
27
* 1. Provider output — what the active provider returns
28
* 2. Raw PromptsService data — lower-level service output (when no extension provider)
29
* 3. Widget state — normalized items and display entries after grouping
30
* 4. Source folders — where files are discovered from
31
*/
32
export async function generateCustomizationDebugReport(
33
section: AICustomizationManagementSection,
34
promptsService: IPromptsService,
35
workspaceService: IAICustomizationWorkspaceService,
36
widgetState: IDebugWidgetState,
37
activeDescriptor?: IHarnessDescriptor,
38
promptsServiceItemProvider?: ICustomizationItemProvider,
39
harnessService?: ICustomizationHarnessService,
40
agentPluginService?: IAgentPluginService,
41
): Promise<string> {
42
const promptType = sectionToPromptType(section);
43
const filter = workspaceService.getStorageSourceFilter(promptType);
44
const lines: string[] = [];
45
46
lines.push(`== Customization Debug: ${section} (${promptType}) ==`);
47
lines.push(`Window: ${workspaceService.isSessionsWindow ? 'Sessions' : 'Core VS Code'}`);
48
lines.push(`Active root: ${workspaceService.getActiveProjectRoot()?.fsPath ?? '(none)'}`);
49
lines.push(`Sections: [${workspaceService.managementSections.join(', ')}]`);
50
lines.push(`Filter sources: [${filter.sources.join(', ')}]`);
51
52
// Active harness descriptor
53
if (activeDescriptor) {
54
lines.push('');
55
lines.push('--- Active Harness ---');
56
lines.push(` id: ${activeDescriptor.id}`);
57
lines.push(` label: ${activeDescriptor.label}`);
58
lines.push(` hasItemProvider: ${!!activeDescriptor.itemProvider}`);
59
lines.push(` hasDisableProvider: ${!!activeDescriptor.syncProvider}`);
60
lines.push(` hiddenSections: ${activeDescriptor.hiddenSections ? `[${activeDescriptor.hiddenSections.join(', ')}]` : '(none)'}`);
61
lines.push(` workspaceSubpaths: ${activeDescriptor.workspaceSubpaths ? `[${activeDescriptor.workspaceSubpaths.join(', ')}]` : '(none)'}`);
62
lines.push(` hideGenerateButton: ${activeDescriptor.hideGenerateButton ?? false}`);
63
lines.push(` requiredAgentId: ${activeDescriptor.requiredAgentId ?? '(none)'}`);
64
lines.push(` instructionFileFilter: ${activeDescriptor.instructionFileFilter ? `[${activeDescriptor.instructionFileFilter.join(', ')}]` : '(none)'}`);
65
}
66
lines.push('');
67
if (filter.includedUserFileRoots) {
68
lines.push(`Filter includedUserFileRoots:`);
69
for (const r of filter.includedUserFileRoots) {
70
lines.push(` ${r.fsPath}`);
71
}
72
} else {
73
lines.push(`Filter includedUserFileRoots: (all)`);
74
}
75
lines.push('');
76
77
// Determine which provider the widget actually uses (mirrors getItemSource logic)
78
const extensionProvider = activeDescriptor?.itemProvider;
79
const effectiveProvider = extensionProvider ?? promptsServiceItemProvider;
80
81
// Stage 1: Provider output
82
if (effectiveProvider) {
83
let providerLabel: string;
84
if (extensionProvider) {
85
providerLabel = 'Extension Provider';
86
} else {
87
providerLabel = 'PromptsService Adapter (fallback — no extension provider registered)';
88
}
89
await appendProviderData(lines, effectiveProvider, promptType, providerLabel);
90
} else {
91
lines.push('--- Stage 1: No provider available ---');
92
lines.push('');
93
}
94
95
// Stage 2: Raw PromptsService data — always useful for diagnostics
96
if (!extensionProvider) {
97
await appendRawServiceData(lines, promptsService, promptType);
98
await appendFilteredData(lines, promptsService, promptType, filter);
99
}
100
101
// Stage 3: Widget state
102
appendWidgetState(lines, widgetState);
103
104
// Stage 4: Source folders
105
await appendSourceFolders(lines, promptsService, promptType);
106
107
// Stage 5: All registered harnesses
108
if (harnessService) {
109
appendAllHarnesses(lines, harnessService);
110
}
111
112
// Stage 6: Installed plugins
113
if (agentPluginService) {
114
appendInstalledPlugins(lines, agentPluginService);
115
}
116
117
return lines.join('\n');
118
}
119
120
interface IPromptFilesByStorage {
121
readonly localFiles: readonly IPromptPath[];
122
readonly userFiles: readonly IPromptPath[];
123
readonly extensionFiles: readonly IPromptPath[];
124
}
125
126
async function getPromptFilesByStorage(promptsService: IPromptsService, promptType: PromptsType): Promise<IPromptFilesByStorage> {
127
const [localFiles, userFiles, extensionFiles] = await Promise.all([
128
promptsService.listPromptFilesForStorage(promptType, PromptsStorage.local, CancellationToken.None),
129
promptsService.listPromptFilesForStorage(promptType, PromptsStorage.user, CancellationToken.None),
130
promptsService.listPromptFilesForStorage(promptType, PromptsStorage.extension, CancellationToken.None),
131
]);
132
133
return { localFiles, userFiles, extensionFiles };
134
}
135
136
async function appendProviderData(lines: string[], provider: ICustomizationItemProvider, promptType: PromptsType, label: string): Promise<void> {
137
lines.push(`--- Stage 1: Provider Output (${label}) ---`);
138
139
const allItems = await provider.provideChatSessionCustomizations(CancellationToken.None);
140
if (!allItems) {
141
lines.push(' Provider returned undefined');
142
lines.push('');
143
return;
144
}
145
146
lines.push(` Total items from provider: ${allItems.length}`);
147
148
// Group by type for summary
149
const byType = new Map<string, typeof allItems>();
150
for (const item of allItems) {
151
const existing = byType.get(item.type) ?? [];
152
existing.push(item);
153
byType.set(item.type, existing);
154
}
155
for (const [type, items] of byType) {
156
lines.push(` ${type}: ${items.length} items`);
157
for (const item of items) {
158
const path = item.uri.scheme === 'file' ? item.uri.fsPath : item.uri.toString();
159
lines.push(` ${item.name} — ${path}`);
160
if (item.description) {
161
lines.push(` desc: ${item.description}`);
162
}
163
if (item.storage) {
164
lines.push(` storage: ${item.storage}`);
165
}
166
if (item.groupKey) {
167
lines.push(` groupKey: ${item.groupKey}`);
168
}
169
if (item.itemKey) {
170
lines.push(` itemKey: ${item.itemKey}`);
171
}
172
if (item.extensionId) {
173
lines.push(` extensionId: ${item.extensionId}`);
174
}
175
if (item.pluginUri) {
176
lines.push(` pluginUri: ${item.pluginUri.toString()}`);
177
}
178
if (item.badge) {
179
lines.push(` badge: ${item.badge}`);
180
}
181
if (item.status) {
182
lines.push(` status: ${item.status}${item.statusMessage ? ` (${item.statusMessage})` : ''}`);
183
}
184
if (item.enabled === false) {
185
lines.push(` enabled: false`);
186
}
187
}
188
}
189
190
const sectionItems = allItems.filter(i => i.type === promptType);
191
lines.push(` Items matching current section (${promptType}): ${sectionItems.length}`);
192
lines.push('');
193
}
194
195
async function appendRawServiceData(lines: string[], promptsService: IPromptsService, promptType: PromptsType): Promise<void> {
196
lines.push('--- Stage 2a: Raw PromptsService Data ---');
197
198
const { localFiles, userFiles, extensionFiles } = await getPromptFilesByStorage(promptsService, promptType);
199
200
lines.push(` listPromptFilesForStorage(local): ${localFiles.length} files`);
201
appendFileList(lines, localFiles);
202
203
lines.push(` listPromptFilesForStorage(user): ${userFiles.length} files`);
204
appendFileList(lines, userFiles);
205
206
lines.push(` listPromptFilesForStorage(ext): ${extensionFiles.length} files`);
207
appendFileList(lines, extensionFiles);
208
209
const allFiles = await promptsService.listPromptFiles(promptType, CancellationToken.None);
210
lines.push(` listPromptFiles (merged): ${allFiles.length} files`);
211
212
if (promptType === PromptsType.instructions) {
213
const agentInstructions = await promptsService.listAgentInstructions(CancellationToken.None, undefined);
214
lines.push(` listAgentInstructions (extra): ${agentInstructions.length} files`);
215
appendFileList(lines, agentInstructions);
216
}
217
218
if (promptType === PromptsType.skill) {
219
const skills = await promptsService.findAgentSkills(CancellationToken.None);
220
lines.push(` findAgentSkills: ${skills?.length ?? 0} skills`);
221
for (const s of skills ?? []) {
222
lines.push(` ${s.name ?? '?'} [${s.storage}] ${s.uri.fsPath}`);
223
}
224
}
225
226
if (promptType === PromptsType.agent) {
227
const agents = await promptsService.getCustomAgents(CancellationToken.None);
228
lines.push(` getCustomAgents: ${agents.length} agents`);
229
for (const a of agents) {
230
lines.push(` ${a.name} [${a.source.storage}] ${a.uri.fsPath}`);
231
}
232
}
233
234
if (promptType === PromptsType.prompt) {
235
const commands = await promptsService.getPromptSlashCommands(CancellationToken.None);
236
lines.push(` getPromptSlashCommands: ${commands.length} commands`);
237
for (const c of commands) {
238
lines.push(` /${c.name} [${c.storage}] ${c.uri.fsPath} (type=${c.type})`);
239
}
240
}
241
242
lines.push('');
243
}
244
245
async function appendFilteredData(lines: string[], promptsService: IPromptsService, promptType: PromptsType, filter: IStorageSourceFilter): Promise<void> {
246
lines.push('--- Stage 2b: After applyStorageSourceFilter ---');
247
248
const { localFiles, userFiles, extensionFiles } = await getPromptFilesByStorage(promptsService, promptType);
249
const all: IPromptPath[] = [...localFiles, ...userFiles, ...extensionFiles];
250
const filtered = applyStorageSourceFilter(all, filter);
251
lines.push(` Input: ${all.length} → Filtered: ${filtered.length}`);
252
lines.push(` local: ${filtered.filter(f => f.storage === PromptsStorage.local).length}`);
253
lines.push(` user: ${filtered.filter(f => f.storage === PromptsStorage.user).length}`);
254
lines.push(` extension: ${filtered.filter(f => f.storage === PromptsStorage.extension).length}`);
255
256
const removedCount = all.length - filtered.length;
257
if (removedCount > 0) {
258
const filteredUris = new Set(filtered.map(f => f.uri.toString()));
259
const removed = all.filter(f => !filteredUris.has(f.uri.toString()));
260
lines.push(` Removed (${removedCount}):`);
261
for (const f of removed) {
262
lines.push(` [${f.storage}] ${f.uri.fsPath}`);
263
}
264
}
265
266
lines.push('');
267
}
268
269
function appendWidgetState(lines: string[], state: IDebugWidgetState): void {
270
lines.push('--- Stage 3: Widget State (loadItems → filterItems) ---');
271
lines.push(` allItems (after loadItems): ${state.allItems.length}`);
272
lines.push(` local: ${state.allItems.filter(i => i.storage === PromptsStorage.local).length}`);
273
lines.push(` user: ${state.allItems.filter(i => i.storage === PromptsStorage.user).length}`);
274
lines.push(` extension: ${state.allItems.filter(i => i.storage === PromptsStorage.extension).length}`);
275
lines.push(` plugin: ${state.allItems.filter(i => i.storage === PromptsStorage.plugin).length}`);
276
277
for (const item of state.allItems) {
278
lines.push(` - ${item.name} [storage=${item.storage ?? '?'}, groupKey=${item.groupKey ?? '(none)'}]`);
279
}
280
281
lines.push(` displayEntries (after filterItems): ${state.displayEntries.length}`);
282
const fileEntries = state.displayEntries.filter(e => e.type === 'file-item');
283
lines.push(` file items shown: ${fileEntries.length}`);
284
const groupEntries = state.displayEntries.filter(e => e.type === 'group-header');
285
for (const g of groupEntries) {
286
lines.push(` group "${g.label}": count=${g.count}, collapsed=${g.collapsed}`);
287
}
288
lines.push('');
289
}
290
291
async function appendSourceFolders(lines: string[], promptsService: IPromptsService, promptType: PromptsType): Promise<void> {
292
lines.push('--- Stage 4: Source Folders (creation targets) ---');
293
const sourceFolders = await promptsService.getSourceFolders(promptType);
294
for (const sf of sourceFolders) {
295
lines.push(` [${sf.storage}] ${sf.uri.fsPath}`);
296
}
297
298
try {
299
const resolvedFolders = await promptsService.getResolvedSourceFolders(promptType);
300
lines.push('');
301
lines.push('--- Resolved Source Folders (discovery order) ---');
302
for (const rf of resolvedFolders) {
303
lines.push(` [${rf.storage}] ${rf.uri.fsPath} (source=${rf.source})`);
304
}
305
} catch {
306
// getResolvedSourceFolders may not exist for all types
307
}
308
}
309
310
function appendFileList(lines: string[], files: readonly { uri: URI }[]): void {
311
for (const f of files) {
312
lines.push(` ${f.uri.fsPath}`);
313
}
314
}
315
316
function appendAllHarnesses(lines: string[], harnessService: ICustomizationHarnessService): void {
317
lines.push('--- Stage 5: All Registered Harnesses ---');
318
const activeId = harnessService.activeHarness.get();
319
const harnesses = harnessService.availableHarnesses.get();
320
lines.push(` Active: ${activeId}`);
321
lines.push(` Total harnesses: ${harnesses.length}`);
322
for (const h of harnesses) {
323
const isActive = h.id === activeId ? ' (ACTIVE)' : '';
324
lines.push(` [${h.id}]${isActive} "${h.label}"`);
325
lines.push(` hasItemProvider: ${!!h.itemProvider}`);
326
lines.push(` hasDisableProvider: ${!!h.syncProvider}`);
327
lines.push(` hiddenSections: ${h.hiddenSections ? `[${h.hiddenSections.join(', ')}]` : '(none)'}`);
328
lines.push(` hideGenerateButton: ${h.hideGenerateButton ?? false}`);
329
lines.push(` pluginActions: ${h.pluginActions?.length ?? 0}`);
330
if (h.pluginActions) {
331
for (const a of h.pluginActions) {
332
lines.push(` - ${a.id}: ${a.label}`);
333
}
334
}
335
}
336
lines.push('');
337
}
338
339
function appendInstalledPlugins(lines: string[], agentPluginService: IAgentPluginService): void {
340
lines.push('--- Stage 6: Installed Plugins ---');
341
const plugins = agentPluginService.plugins.get();
342
lines.push(` Total: ${plugins.length}`);
343
for (const p of plugins) {
344
lines.push(` [${p.label}] ${p.uri.toString()}`);
345
lines.push(` fromMarketplace: ${p.fromMarketplace}`);
346
}
347
lines.push('');
348
}
349
350