From 2bddfa571a18d8c8c39a966b951c2fc8e9db55b9 Mon Sep 17 00:00:00 2001 From: Paul Date: Mon, 2 Mar 2026 14:36:19 -0800 Subject: [PATCH] Fix skill delete behaviour (#298814) --- .../aiCustomizationManagement.contribution.ts | 27 ++++++++++++++++--- .../promptSyntax/pickers/promptFilePickers.ts | 17 ++++++++---- 2 files changed, 35 insertions(+), 9 deletions(-) diff --git a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagement.contribution.ts b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagement.contribution.ts index 0cd2c243b0f..7cdad4e6440 100644 --- a/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagement.contribution.ts +++ b/src/vs/workbench/contrib/chat/browser/aiCustomization/aiCustomizationManagement.contribution.ts @@ -35,7 +35,7 @@ import { ContextKeyExpr } from '../../../../../platform/contextkey/common/contex import { ChatConfiguration } from '../../common/constants.js'; import { IFileService } from '../../../../../platform/files/common/files.js'; import { IDialogService } from '../../../../../platform/dialogs/common/dialogs.js'; -import { basename } from '../../../../../base/common/resources.js'; +import { basename, dirname } from '../../../../../base/common/resources.js'; import { Schemas } from '../../../../../base/common/network.js'; import { isWindows, isMacintosh } from '../../../../../base/common/platform.js'; @@ -119,6 +119,16 @@ function extractStorage(context: AICustomizationContext): PromptsStorage | undef return context.storage; } +/** + * Extracts prompt type from context. + */ +function extractPromptType(context: AICustomizationContext): PromptsType | undefined { + if (URI.isUri(context) || typeof context === 'string') { + return undefined; + } + return context.promptType; +} + // Open file action const OPEN_AI_CUSTOMIZATION_MGMT_FILE_ID = 'aiCustomizationManagement.openFile'; registerAction2(class extends Action2 { @@ -193,8 +203,11 @@ registerAction2(class extends Action2 { const dialogService = accessor.get(IDialogService); const uri = extractURI(context); - const fileName = basename(uri); const storage = extractStorage(context); + const promptType = extractPromptType(context); + const isSkill = promptType === PromptsType.skill; + // For skills, use the parent folder name since skills are structured as /SKILL.md. + const fileName = isSkill ? basename(dirname(uri)) : basename(uri); // Extension and plugin files cannot be deleted if (storage === PromptsStorage.extension || storage === PromptsStorage.plugin) { @@ -206,15 +219,21 @@ registerAction2(class extends Action2 { } // Confirm deletion + const message = isSkill + ? localize('confirmDeleteSkill', "Are you sure you want to delete skill '{0}' and its folder?", fileName) + : localize('confirmDelete', "Are you sure you want to delete '{0}'?", fileName); const confirmation = await dialogService.confirm({ - message: localize('confirmDelete', "Are you sure you want to delete '{0}'?", fileName), + message, detail: localize('confirmDeleteDetail', "This action cannot be undone."), primaryButton: localize('delete', "Delete"), type: 'warning', }); if (confirmation.confirmed) { - await fileService.del(uri, { useTrash: true }); + // For skills, delete the parent folder (e.g. .github/skills/my-skill/) + // since each skill is a folder containing SKILL.md. + const deleteTarget = isSkill ? dirname(uri) : uri; + await fileService.del(deleteTarget, { useTrash: true, recursive: isSkill }); } } }); diff --git a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts index 54b363863fb..aaf3a9b1340 100644 --- a/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts +++ b/src/vs/workbench/contrib/chat/browser/promptSyntax/pickers/promptFilePickers.ts @@ -8,7 +8,7 @@ import { URI } from '../../../../../../base/common/uri.js'; import { Codicon } from '../../../../../../base/common/codicons.js'; import { ThemeIcon } from '../../../../../../base/common/themables.js'; import { AgentFileType, IExtensionPromptPath, IPromptPath, IPromptsService, PromptsStorage } from '../../../common/promptSyntax/service/promptsService.js'; -import { dirname, extUri, joinPath } from '../../../../../../base/common/resources.js'; +import { basename, dirname, extUri, joinPath } from '../../../../../../base/common/resources.js'; import { DisposableStore } from '../../../../../../base/common/lifecycle.js'; import { IFileService } from '../../../../../../platform/files/common/files.js'; import { IOpenerService } from '../../../../../../platform/opener/common/opener.js'; @@ -651,16 +651,23 @@ export class PromptFilePickers { // don't close the main prompt selection dialog by the confirmation dialog return await this.keepQuickPickOpen(quickPick, async () => { - const filename = getCleanPromptName(value); - const message = localize('commands.prompts.use.select-dialog.delete-prompt.confirm.message', "Are you sure you want to delete '{0}'?", filename); + const isSkill = options.type === PromptsType.skill; + // For skills, use the parent folder name as the display name + // since skills are structured as /SKILL.md. + const filename = isSkill ? basename(dirname(value)) : item.label; + const message = isSkill + ? localize('commands.prompts.use.select-dialog.delete-skill.confirm.message', "Are you sure you want to delete skill '{0}' and its folder?", filename) + : localize('commands.prompts.use.select-dialog.delete-prompt.confirm.message', "Are you sure you want to delete '{0}'?", filename); const { confirmed } = await this._dialogService.confirm({ message }); // if prompt deletion was not confirmed, nothing to do if (!confirmed) { return false; } - // prompt deletion was confirmed so delete the prompt file - await this._fileService.del(value); + // For skills, delete the parent folder (e.g. .github/skills/my-skill/) + // since each skill is a folder containing SKILL.md. + const deleteTarget = isSkill ? dirname(value) : value; + await this._fileService.del(deleteTarget, { recursive: isSkill, useTrash: true }); return true; });