Просмотр исходного кода

feat: add confirmation dialog and proper cleanup for marketplace mode removal (#6136)

Co-authored-by: Daniel Riccio <[email protected]>
Hannes Rudolph 5 месяцев назад
Родитель
Сommit
1e17b3b3bb
44 измененных файлов с 861 добавлено и 251 удалено
  1. 58 1
      src/core/config/CustomModesManager.ts
  2. 1 1
      src/core/webview/ClineProvider.ts
  3. 37 0
      src/core/webview/webviewMessageHandler.ts
  4. 7 1
      src/i18n/locales/ca/common.json
  5. 7 1
      src/i18n/locales/de/common.json
  6. 7 1
      src/i18n/locales/en/common.json
  7. 7 1
      src/i18n/locales/es/common.json
  8. 7 1
      src/i18n/locales/fr/common.json
  9. 7 1
      src/i18n/locales/hi/common.json
  10. 7 1
      src/i18n/locales/id/common.json
  11. 7 1
      src/i18n/locales/it/common.json
  12. 7 1
      src/i18n/locales/ja/common.json
  13. 7 1
      src/i18n/locales/ko/common.json
  14. 7 1
      src/i18n/locales/nl/common.json
  15. 7 1
      src/i18n/locales/pl/common.json
  16. 7 1
      src/i18n/locales/pt-BR/common.json
  17. 7 1
      src/i18n/locales/ru/common.json
  18. 7 1
      src/i18n/locales/tr/common.json
  19. 7 1
      src/i18n/locales/vi/common.json
  20. 7 1
      src/i18n/locales/zh-CN/common.json
  21. 7 1
      src/i18n/locales/zh-TW/common.json
  22. 6 2
      src/services/marketplace/MarketplaceManager.ts
  23. 74 46
      src/services/marketplace/SimpleInstaller.ts
  24. 170 149
      src/services/marketplace/__tests__/SimpleInstaller.spec.ts
  25. 1 0
      src/shared/ExtensionMessage.ts
  26. 87 11
      webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx
  27. 19 2
      webview-ui/src/i18n/locales/ca/marketplace.json
  28. 19 2
      webview-ui/src/i18n/locales/de/marketplace.json
  29. 16 1
      webview-ui/src/i18n/locales/en/marketplace.json
  30. 19 2
      webview-ui/src/i18n/locales/es/marketplace.json
  31. 20 3
      webview-ui/src/i18n/locales/fr/marketplace.json
  32. 16 1
      webview-ui/src/i18n/locales/hi/marketplace.json
  33. 16 1
      webview-ui/src/i18n/locales/id/marketplace.json
  34. 16 1
      webview-ui/src/i18n/locales/it/marketplace.json
  35. 16 1
      webview-ui/src/i18n/locales/ja/marketplace.json
  36. 16 1
      webview-ui/src/i18n/locales/ko/marketplace.json
  37. 16 1
      webview-ui/src/i18n/locales/nl/marketplace.json
  38. 16 1
      webview-ui/src/i18n/locales/pl/marketplace.json
  39. 16 1
      webview-ui/src/i18n/locales/pt-BR/marketplace.json
  40. 16 1
      webview-ui/src/i18n/locales/ru/marketplace.json
  41. 16 1
      webview-ui/src/i18n/locales/tr/marketplace.json
  42. 16 1
      webview-ui/src/i18n/locales/vi/marketplace.json
  43. 16 1
      webview-ui/src/i18n/locales/zh-CN/marketplace.json
  44. 16 1
      webview-ui/src/i18n/locales/zh-TW/marketplace.json

+ 58 - 1
src/core/config/CustomModesManager.ts

@@ -1,6 +1,7 @@
 import * as vscode from "vscode"
 import * as path from "path"
 import * as fs from "fs/promises"
+import * as os from "os"
 
 import * as yaml from "yaml"
 import stripBom from "strip-bom"
@@ -501,7 +502,7 @@ export class CustomModesManager {
 		await this.onUpdate()
 	}
 
-	public async deleteCustomMode(slug: string): Promise<void> {
+	public async deleteCustomMode(slug: string, fromMarketplace = false): Promise<void> {
 		try {
 			const settingsPath = await this.getCustomModesFilePath()
 			const roomodesPath = await this.getWorkspaceRoomodes()
@@ -517,6 +518,9 @@ export class CustomModesManager {
 				throw new Error(t("common:customModes.errors.modeNotFound"))
 			}
 
+			// Determine which mode to use for rules folder path calculation
+			const modeToDelete = projectMode || globalMode
+
 			await this.queueWrite(async () => {
 				// Delete from project first if it exists there
 				if (projectMode && roomodesPath) {
@@ -528,6 +532,11 @@ export class CustomModesManager {
 					await this.updateModesInFile(settingsPath, (modes) => modes.filter((m) => m.slug !== slug))
 				}
 
+				// Delete associated rules folder
+				if (modeToDelete) {
+					await this.deleteRulesFolder(slug, modeToDelete, fromMarketplace)
+				}
+
 				// Clear cache when modes are deleted
 				this.clearCache()
 				await this.refreshMergedState()
@@ -538,6 +547,54 @@ export class CustomModesManager {
 		}
 	}
 
+	/**
+	 * Deletes the rules folder for a specific mode
+	 * @param slug - The mode slug
+	 * @param mode - The mode configuration to determine the scope
+	 */
+	private async deleteRulesFolder(slug: string, mode: ModeConfig, fromMarketplace = false): Promise<void> {
+		try {
+			// Determine the scope based on source (project or global)
+			const scope = mode.source || "global"
+
+			// Determine the rules folder path
+			let rulesFolderPath: string
+			if (scope === "project") {
+				const workspacePath = getWorkspacePath()
+				if (workspacePath) {
+					rulesFolderPath = path.join(workspacePath, ".roo", `rules-${slug}`)
+				} else {
+					return // No workspace, can't delete project rules
+				}
+			} else {
+				// Global scope - use OS home directory
+				const homeDir = os.homedir()
+				rulesFolderPath = path.join(homeDir, ".roo", `rules-${slug}`)
+			}
+
+			// Check if the rules folder exists and delete it
+			const rulesFolderExists = await fileExistsAtPath(rulesFolderPath)
+			if (rulesFolderExists) {
+				try {
+					await fs.rm(rulesFolderPath, { recursive: true, force: true })
+					logger.info(`Deleted rules folder for mode ${slug}: ${rulesFolderPath}`)
+				} catch (error) {
+					logger.error(`Failed to delete rules folder for mode ${slug}: ${error}`)
+					// Notify the user about the failure
+					const messageKey = fromMarketplace
+						? "common:marketplace.mode.rulesCleanupFailed"
+						: "common:customModes.errors.rulesCleanupFailed"
+					vscode.window.showWarningMessage(t(messageKey, { rulesFolderPath }))
+					// Continue even if folder deletion fails
+				}
+			}
+		} catch (error) {
+			logger.error(`Error deleting rules folder for mode ${slug}`, {
+				error: error instanceof Error ? error.message : String(error),
+			})
+		}
+	}
+
 	public async resetCustomModes(): Promise<void> {
 		try {
 			const filePath = await this.getCustomModesFilePath()

+ 1 - 1
src/core/webview/ClineProvider.ts

@@ -158,7 +158,7 @@ export class ClineProvider
 				this.log(`Failed to initialize MCP Hub: ${error}`)
 			})
 
-		this.marketplaceManager = new MarketplaceManager(this.context)
+		this.marketplaceManager = new MarketplaceManager(this.context, this.customModesManager)
 	}
 
 	// Adds a new Cline instance to clineStack, marking the start of a new task.

+ 37 - 0
src/core/webview/webviewMessageHandler.ts

@@ -2229,8 +2229,45 @@ export const webviewMessageHandler = async (
 				try {
 					await marketplaceManager.removeInstalledMarketplaceItem(message.mpItem, message.mpInstallOptions)
 					await provider.postStateToWebview()
+
+					// Send success message to webview
+					provider.postMessageToWebview({
+						type: "marketplaceRemoveResult",
+						success: true,
+						slug: message.mpItem.id,
+					})
 				} catch (error) {
 					console.error(`Error removing marketplace item: ${error}`)
+
+					// Show error message to user
+					vscode.window.showErrorMessage(
+						`Failed to remove marketplace item: ${error instanceof Error ? error.message : String(error)}`,
+					)
+
+					// Send error message to webview
+					provider.postMessageToWebview({
+						type: "marketplaceRemoveResult",
+						success: false,
+						error: error instanceof Error ? error.message : String(error),
+						slug: message.mpItem.id,
+					})
+				}
+			} else {
+				// MarketplaceManager not available or missing required parameters
+				const errorMessage = !marketplaceManager
+					? "Marketplace manager is not available"
+					: "Missing required parameters for marketplace item removal"
+				console.error(errorMessage)
+
+				vscode.window.showErrorMessage(errorMessage)
+
+				if (message.mpItem?.id) {
+					provider.postMessageToWebview({
+						type: "marketplaceRemoveResult",
+						success: false,
+						error: errorMessage,
+						slug: message.mpItem.id,
+					})
 				}
 			}
 			break

+ 7 - 1
src/i18n/locales/ca/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Error en eliminar el mode personalitzat: {{error}}",
 			"resetFailed": "Error en restablir els modes personalitzats: {{error}}",
 			"modeNotFound": "Error d'escriptura: Mode no trobat",
-			"noWorkspaceForProject": "No s'ha trobat cap carpeta d'espai de treball per al mode específic del projecte"
+			"noWorkspaceForProject": "No s'ha trobat cap carpeta d'espai de treball per al mode específic del projecte",
+			"rulesCleanupFailed": "El mode s'ha suprimit correctament, però no s'ha pogut suprimir la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis de suprimir manualment."
 		},
 		"scope": {
 			"project": "projecte",
 			"global": "global"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "El mode s'ha eliminat correctament, però no s'ha pogut eliminar la carpeta de regles a {{rulesFolderPath}}. És possible que l'hagis d'eliminar manualment."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "La teva organització requereix autenticació de Roo Code Cloud. Si us plau, inicia sessió per continuar.",

+ 7 - 1
src/i18n/locales/de/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Fehler beim Löschen des benutzerdefinierten Modus: {{error}}",
 			"resetFailed": "Fehler beim Zurücksetzen der benutzerdefinierten Modi: {{error}}",
 			"modeNotFound": "Schreibfehler: Modus nicht gefunden",
-			"noWorkspaceForProject": "Kein Arbeitsbereich-Ordner für projektspezifischen Modus gefunden"
+			"noWorkspaceForProject": "Kein Arbeitsbereich-Ordner für projektspezifischen Modus gefunden",
+			"rulesCleanupFailed": "Der Modus wurde erfolgreich gelöscht, aber der Regelordner unter {{rulesFolderPath}} konnte nicht gelöscht werden. Möglicherweise musst du ihn manuell löschen."
 		},
 		"scope": {
 			"project": "projekt",
 			"global": "global"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Der Modus wurde erfolgreich entfernt, aber der Regelordner unter {{rulesFolderPath}} konnte nicht gelöscht werden. Möglicherweise musst du ihn manuell löschen."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Deine Organisation erfordert eine Roo Code Cloud-Authentifizierung. Bitte melde dich an, um fortzufahren.",

+ 7 - 1
src/i18n/locales/en/common.json

@@ -136,13 +136,19 @@
 			"deleteFailed": "Failed to delete custom mode: {{error}}",
 			"resetFailed": "Failed to reset custom modes: {{error}}",
 			"modeNotFound": "Write error: Mode not found",
-			"noWorkspaceForProject": "No workspace folder found for project-specific mode"
+			"noWorkspaceForProject": "No workspace folder found for project-specific mode",
+			"rulesCleanupFailed": "Mode deleted successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually."
 		},
 		"scope": {
 			"project": "project",
 			"global": "global"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Mode removed successfully, but failed to delete rules folder at {{rulesFolderPath}}. You may need to delete it manually."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Your organization requires Roo Code Cloud authentication. Please sign in to continue.",

+ 7 - 1
src/i18n/locales/es/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Error al eliminar modo personalizado: {{error}}",
 			"resetFailed": "Error al restablecer modos personalizados: {{error}}",
 			"modeNotFound": "Error de escritura: Modo no encontrado",
-			"noWorkspaceForProject": "No se encontró carpeta de espacio de trabajo para modo específico del proyecto"
+			"noWorkspaceForProject": "No se encontró carpeta de espacio de trabajo para modo específico del proyecto",
+			"rulesCleanupFailed": "El modo se eliminó correctamente, pero no se pudo eliminar la carpeta de reglas en {{rulesFolderPath}}. Es posible que debas eliminarla manualmente."
 		},
 		"scope": {
 			"project": "proyecto",
 			"global": "global"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "El modo se eliminó correctamente, pero no se pudo eliminar la carpeta de reglas en {{rulesFolderPath}}. Es posible que debas eliminarla manually."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Tu organización requiere autenticación de Roo Code Cloud. Por favor, inicia sesión para continuar.",

+ 7 - 1
src/i18n/locales/fr/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Échec de la suppression du mode personnalisé : {{error}}",
 			"resetFailed": "Échec de la réinitialisation des modes personnalisés : {{error}}",
 			"modeNotFound": "Erreur d'écriture : Mode non trouvé",
-			"noWorkspaceForProject": "Aucun dossier d'espace de travail trouvé pour le mode spécifique au projet"
+			"noWorkspaceForProject": "Aucun dossier d'espace de travail trouvé pour le mode spécifique au projet",
+			"rulesCleanupFailed": "Le mode a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement."
 		},
 		"scope": {
 			"project": "projet",
 			"global": "global"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Le mode a été supprimé avec succès, mais la suppression du dossier de règles à l'adresse {{rulesFolderPath}} a échoué. Vous devrez peut-être le supprimer manuellement."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Votre organisation nécessite une authentification Roo Code Cloud. Veuillez vous connecter pour continuer.",

+ 7 - 1
src/i18n/locales/hi/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "कस्टम मोड डिलीट विफल: {{error}}",
 			"resetFailed": "कस्टम मोड रीसेट विफल: {{error}}",
 			"modeNotFound": "लेखन त्रुटि: मोड नहीं मिला",
-			"noWorkspaceForProject": "प्रोजेक्ट-विशिष्ट मोड के लिए वर्कस्पेस फ़ोल्डर नहीं मिला"
+			"noWorkspaceForProject": "प्रोजेक्ट-विशिष्ट मोड के लिए वर्कस्पेस फ़ोल्डर नहीं मिला",
+			"rulesCleanupFailed": "मोड सफलतापूर्वक हटा दिया गया, लेकिन {{rulesFolderPath}} पर नियम फ़ोल्डर को हटाने में विफल रहा। आपको इसे मैन्युअल रूप से हटाना पड़ सकता है।"
 		},
 		"scope": {
 			"project": "परियोजना",
 			"global": "वैश्विक"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "मोड सफलतापूर्वक हटा दिया गया, लेकिन {{rulesFolderPath}} पर नियम फ़ोल्डर को हटाने में विफल रहा। आपको इसे मैन्युअल रूप से हटाना पड़ सकता है।"
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "आपके संगठन को Roo Code Cloud प्रमाणीकरण की आवश्यकता है। कृपया जारी रखने के लिए साइन इन करें।",

+ 7 - 1
src/i18n/locales/id/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Gagal menghapus mode kustom: {{error}}",
 			"resetFailed": "Gagal mereset mode kustom: {{error}}",
 			"modeNotFound": "Kesalahan tulis: Mode tidak ditemukan",
-			"noWorkspaceForProject": "Tidak ditemukan folder workspace untuk mode khusus proyek"
+			"noWorkspaceForProject": "Tidak ditemukan folder workspace untuk mode khusus proyek",
+			"rulesCleanupFailed": "Mode berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual."
 		},
 		"scope": {
 			"project": "proyek",
 			"global": "global"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Mode berhasil dihapus, tetapi gagal menghapus folder aturan di {{rulesFolderPath}}. Kamu mungkin perlu menghapusnya secara manual."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Organisasi kamu memerlukan autentikasi Roo Code Cloud. Silakan masuk untuk melanjutkan.",

+ 7 - 1
src/i18n/locales/it/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Eliminazione modalità personalizzata fallita: {{error}}",
 			"resetFailed": "Reset modalità personalizzate fallito: {{error}}",
 			"modeNotFound": "Errore di scrittura: Modalità non trovata",
-			"noWorkspaceForProject": "Nessuna cartella workspace trovata per la modalità specifica del progetto"
+			"noWorkspaceForProject": "Nessuna cartella workspace trovata per la modalità specifica del progetto",
+			"rulesCleanupFailed": "La modalità è stata eliminata con successo, ma non è stato possibile eliminare la cartella delle regole in {{rulesFolderPath}}. Potrebbe essere necessario eliminarla manualmente."
 		},
 		"scope": {
 			"project": "progetto",
 			"global": "globale"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "La modalità è stata rimossa con successo, ma non è stato possibile eliminare la cartella delle regole in {{rulesFolderPath}}. Potrebbe essere necessario eliminarla manualmente."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "La tua organizzazione richiede l'autenticazione Roo Code Cloud. Accedi per continuare.",

+ 7 - 1
src/i18n/locales/ja/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "カスタムモードの削除に失敗しました:{{error}}",
 			"resetFailed": "カスタムモードのリセットに失敗しました:{{error}}",
 			"modeNotFound": "書き込みエラー:モードが見つかりません",
-			"noWorkspaceForProject": "プロジェクト固有モード用のワークスペースフォルダーが見つかりません"
+			"noWorkspaceForProject": "プロジェクト固有モード用のワークスペースフォルダーが見つかりません",
+			"rulesCleanupFailed": "モードは正常に削除されましたが、{{rulesFolderPath}} にあるルールフォルダの削除に失敗しました。手動で削除する必要がある場合があります。"
 		},
 		"scope": {
 			"project": "プロジェクト",
 			"global": "グローバル"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "モードは正常に削除されましたが、{{rulesFolderPath}} にあるルールフォルダの削除に失敗しました。手動で削除する必要がある場合があります。"
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "あなたの組織では Roo Code Cloud 認証が必要です。続行するにはサインインしてください。",

+ 7 - 1
src/i18n/locales/ko/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "사용자 정의 모드 삭제 실패: {{error}}",
 			"resetFailed": "사용자 정의 모드 재설정 실패: {{error}}",
 			"modeNotFound": "쓰기 오류: 모드를 찾을 수 없습니다",
-			"noWorkspaceForProject": "프로젝트별 모드용 작업 공간 폴더를 찾을 수 없습니다"
+			"noWorkspaceForProject": "프로젝트별 모드용 작업 공간 폴더를 찾을 수 없습니다",
+			"rulesCleanupFailed": "모드가 성공적으로 삭제되었지만 {{rulesFolderPath}}의 규칙 폴더를 삭제하지 못했습니다. 수동으로 삭제해야 할 수도 있습니다."
 		},
 		"scope": {
 			"project": "프로젝트",
 			"global": "글로벌"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "모드가 성공적으로 제거되었지만 {{rulesFolderPath}}의 규칙 폴더를 삭제하지 못했습니다. 수동으로 삭제해야 할 수도 있습니다."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "조직에서 Roo Code Cloud 인증이 필요합니다. 계속하려면 로그인하세요.",

+ 7 - 1
src/i18n/locales/nl/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Aangepaste modus verwijderen mislukt: {{error}}",
 			"resetFailed": "Aangepaste modi resetten mislukt: {{error}}",
 			"modeNotFound": "Schrijffout: Modus niet gevonden",
-			"noWorkspaceForProject": "Geen workspace map gevonden voor projectspecifieke modus"
+			"noWorkspaceForProject": "Geen workspace map gevonden voor projectspecifieke modus",
+			"rulesCleanupFailed": "Modus succesvol verwijderd, maar het verwijderen van de regelsmap op {{rulesFolderPath}} is mislukt. Je moet deze mogelijk handmatig verwijderen."
 		},
 		"scope": {
 			"project": "project",
 			"global": "globaal"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Modus succesvol verwijderd, maar het verwijderen van de regelsmap op {{rulesFolderPath}} is mislukt. Je moet deze mogelijk handmatig verwijderen."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Je organisatie vereist Roo Code Cloud-authenticatie. Log in om door te gaan.",

+ 7 - 1
src/i18n/locales/pl/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Usunięcie trybu niestandardowego nie powiodło się: {{error}}",
 			"resetFailed": "Resetowanie trybów niestandardowych nie powiodło się: {{error}}",
 			"modeNotFound": "Błąd zapisu: Tryb nie został znaleziony",
-			"noWorkspaceForProject": "Nie znaleziono folderu obszaru roboczego dla trybu specyficznego dla projektu"
+			"noWorkspaceForProject": "Nie znaleziono folderu obszaru roboczego dla trybu specyficznego dla projektu",
+			"rulesCleanupFailed": "Tryb został pomyślnie usunięty, ale nie udało się usunąć folderu reguł w {{rulesFolderPath}}. Może być konieczne ręczne usunięcie."
 		},
 		"scope": {
 			"project": "projekt",
 			"global": "globalny"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Tryb został pomyślnie usunięty, ale nie udało się usunąć folderu reguł w {{rulesFolderPath}}. Może być konieczne ręczne usunięcie."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Twoja organizacja wymaga uwierzytelnienia Roo Code Cloud. Zaloguj się, aby kontynuować.",

+ 7 - 1
src/i18n/locales/pt-BR/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Falha ao excluir modo personalizado: {{error}}",
 			"resetFailed": "Falha ao redefinir modos personalizados: {{error}}",
 			"modeNotFound": "Erro de escrita: Modo não encontrado",
-			"noWorkspaceForProject": "Nenhuma pasta de workspace encontrada para modo específico do projeto"
+			"noWorkspaceForProject": "Nenhuma pasta de workspace encontrada para modo específico do projeto",
+			"rulesCleanupFailed": "O modo foi excluído com sucesso, mas falhou ao excluir a pasta de regras em {{rulesFolderPath}}. Você pode precisar excluí-la manualmente."
 		},
 		"scope": {
 			"project": "projeto",
 			"global": "global"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "O modo foi removido com sucesso, mas falhou ao excluir a pasta de regras em {{rulesFolderPath}}. Você pode precisar excluí-la manualmente."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Sua organização requer autenticação do Roo Code Cloud. Faça login para continuar.",

+ 7 - 1
src/i18n/locales/ru/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Не удалось удалить пользовательский режим: {{error}}",
 			"resetFailed": "Не удалось сбросить пользовательские режимы: {{error}}",
 			"modeNotFound": "Ошибка записи: Режим не найден",
-			"noWorkspaceForProject": "Не найдена папка рабочего пространства для режима, специфичного для проекта"
+			"noWorkspaceForProject": "Не найдена папка рабочего пространства для режима, специфичного для проекта",
+			"rulesCleanupFailed": "Режим успешно удален, но не удалось удалить папку правил в {{rulesFolderPath}}. Возможно, вам придется удалить ее вручную."
 		},
 		"scope": {
 			"project": "проект",
 			"global": "глобальный"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Режим успешно удален, но не удалось удалить папку правил в {{rulesFolderPath}}. Возможно, вам придется удалить ее вручную."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Ваша организация требует аутентификации Roo Code Cloud. Войдите в систему, чтобы продолжить.",

+ 7 - 1
src/i18n/locales/tr/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Özel mod silme başarısız: {{error}}",
 			"resetFailed": "Özel modları sıfırlama başarısız: {{error}}",
 			"modeNotFound": "Yazma hatası: Mod bulunamadı",
-			"noWorkspaceForProject": "Proje özel modu için çalışma alanı klasörü bulunamadı"
+			"noWorkspaceForProject": "Proje özel modu için çalışma alanı klasörü bulunamadı",
+			"rulesCleanupFailed": "Mod başarıyla silindi, ancak {{rulesFolderPath}} konumundaki kurallar klasörü silinemedi. Manuel olarak silmeniz gerekebilir."
 		},
 		"scope": {
 			"project": "proje",
 			"global": "küresel"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Mod başarıyla kaldırıldı, ancak {{rulesFolderPath}} konumundaki kurallar klasörü silinemedi. Manuel olarak silmeniz gerekebilir."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Kuruluşunuz Roo Code Cloud kimlik doğrulaması gerektiriyor. Devam etmek için giriş yapın.",

+ 7 - 1
src/i18n/locales/vi/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "Xóa chế độ tùy chỉnh thất bại: {{error}}",
 			"resetFailed": "Đặt lại chế độ tùy chỉnh thất bại: {{error}}",
 			"modeNotFound": "Lỗi ghi: Không tìm thấy chế độ",
-			"noWorkspaceForProject": "Không tìm thấy thư mục workspace cho chế độ dành riêng cho dự án"
+			"noWorkspaceForProject": "Không tìm thấy thư mục workspace cho chế độ dành riêng cho dự án",
+			"rulesCleanupFailed": "Đã xóa chế độ thành công, nhưng không thể xóa thư mục quy tắc tại {{rulesFolderPath}}. Bạn có thể cần xóa thủ công."
 		},
 		"scope": {
 			"project": "dự án",
 			"global": "toàn cầu"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "Đã xóa chế độ thành công, nhưng không thể xóa thư mục quy tắc tại {{rulesFolderPath}}. Bạn có thể cần xóa thủ công."
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "Tổ chức của bạn yêu cầu xác thực Roo Code Cloud. Vui lòng đăng nhập để tiếp tục.",

+ 7 - 1
src/i18n/locales/zh-CN/common.json

@@ -152,13 +152,19 @@
 			"deleteFailed": "删除自定义模式失败:{{error}}",
 			"resetFailed": "重置自定义模式失败:{{error}}",
 			"modeNotFound": "写入错误:未找到模式",
-			"noWorkspaceForProject": "未找到项目特定模式的工作区文件夹"
+			"noWorkspaceForProject": "未找到项目特定模式的工作区文件夹",
+			"rulesCleanupFailed": "模式删除成功,但无法删除位于 {{rulesFolderPath}} 的规则文件夹。您可能需要手动删除。"
 		},
 		"scope": {
 			"project": "项目",
 			"global": "全局"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "模式已成功移除,但无法删除位于 {{rulesFolderPath}} 的规则文件夹。您可能需要手动删除。"
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "您的组织需要 Roo Code Cloud 身份验证。请登录以继续。",

+ 7 - 1
src/i18n/locales/zh-TW/common.json

@@ -147,13 +147,19 @@
 			"deleteFailed": "刪除自訂模式失敗:{{error}}",
 			"resetFailed": "重設自訂模式失敗:{{error}}",
 			"modeNotFound": "寫入錯誤:未找到模式",
-			"noWorkspaceForProject": "未找到專案特定模式的工作區資料夾"
+			"noWorkspaceForProject": "未找到專案特定模式的工作區資料夾",
+			"rulesCleanupFailed": "模式已成功刪除,但無法刪除位於 {{rulesFolderPath}} 的規則資料夾。您可能需要手動刪除。"
 		},
 		"scope": {
 			"project": "專案",
 			"global": "全域"
 		}
 	},
+	"marketplace": {
+		"mode": {
+			"rulesCleanupFailed": "模式已成功移除,但無法刪除位於 {{rulesFolderPath}} 的規則資料夾。您可能需要手動刪除。"
+		}
+	},
 	"mdm": {
 		"errors": {
 			"cloud_auth_required": "您的組織需要 Roo Code Cloud 身份驗證。請登入以繼續。",

+ 6 - 2
src/services/marketplace/MarketplaceManager.ts

@@ -9,14 +9,18 @@ import { GlobalFileNames } from "../../shared/globalFileNames"
 import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
 import { t } from "../../i18n"
 import { TelemetryService } from "@roo-code/telemetry"
+import type { CustomModesManager } from "../../core/config/CustomModesManager"
 
 export class MarketplaceManager {
 	private configLoader: RemoteConfigLoader
 	private installer: SimpleInstaller
 
-	constructor(private readonly context: vscode.ExtensionContext) {
+	constructor(
+		private readonly context: vscode.ExtensionContext,
+		private readonly customModesManager?: CustomModesManager,
+	) {
 		this.configLoader = new RemoteConfigLoader()
-		this.installer = new SimpleInstaller(context)
+		this.installer = new SimpleInstaller(context, customModesManager)
 	}
 
 	async getMarketplaceItems(): Promise<{ items: MarketplaceItem[]; errors?: string[] }> {

+ 74 - 46
src/services/marketplace/SimpleInstaller.ts

@@ -5,6 +5,7 @@ import * as yaml from "yaml"
 import type { MarketplaceItem, MarketplaceItemType, InstallMarketplaceItemOptions, McpParameter } from "@roo-code/types"
 import { GlobalFileNames } from "../../shared/globalFileNames"
 import { ensureSettingsDirectoryExists } from "../../utils/globalContext"
+import type { CustomModesManager } from "../../core/config/CustomModesManager"
 
 export interface InstallOptions extends InstallMarketplaceItemOptions {
 	target: "project" | "global"
@@ -12,7 +13,10 @@ export interface InstallOptions extends InstallMarketplaceItemOptions {
 }
 
 export class SimpleInstaller {
-	constructor(private readonly context: vscode.ExtensionContext) {}
+	constructor(
+		private readonly context: vscode.ExtensionContext,
+		private readonly customModesManager?: CustomModesManager,
+	) {}
 
 	async installItem(item: MarketplaceItem, options: InstallOptions): Promise<{ filePath: string; line?: number }> {
 		const { target } = options
@@ -40,6 +44,48 @@ export class SimpleInstaller {
 			throw new Error("Mode content should not be an array")
 		}
 
+		// If CustomModesManager is available, use importModeWithRules
+		if (this.customModesManager) {
+			// Transform marketplace content to import format (wrap in customModes array)
+			const importData = {
+				customModes: [yaml.parse(item.content)],
+			}
+			const importYaml = yaml.stringify(importData)
+
+			// Call customModesManager.importModeWithRules
+			const result = await this.customModesManager.importModeWithRules(importYaml, target)
+
+			if (!result.success) {
+				throw new Error(result.error || "Failed to import mode")
+			}
+
+			// Return the file path and line number for VS Code to open
+			const filePath = await this.getModeFilePath(target)
+
+			// Try to find the line number where the mode was added
+			let line: number | undefined
+			try {
+				const fileContent = await fs.readFile(filePath, "utf-8")
+				const lines = fileContent.split("\n")
+				const modeData = yaml.parse(item.content)
+
+				// Find the line containing the slug of the added mode
+				if (modeData?.slug) {
+					const slugLineIndex = lines.findIndex(
+						(l) => l.includes(`slug: ${modeData.slug}`) || l.includes(`slug: "${modeData.slug}"`),
+					)
+					if (slugLineIndex >= 0) {
+						line = slugLineIndex + 1 // Convert to 1-based line number
+					}
+				}
+			} catch (error) {
+				// If we can't find the line number, that's okay
+			}
+
+			return { filePath, line }
+		}
+
+		// Fallback to original implementation if CustomModesManager is not available
 		const filePath = await this.getModeFilePath(target)
 		const modeData = yaml.parse(item.content)
 
@@ -248,56 +294,38 @@ export class SimpleInstaller {
 	}
 
 	private async removeMode(item: MarketplaceItem, target: "project" | "global"): Promise<void> {
-		const filePath = await this.getModeFilePath(target)
-
-		try {
-			const existing = await fs.readFile(filePath, "utf-8")
-			let existingData: any
-
-			try {
-				const parsed = yaml.parse(existing)
-				// Ensure we have a valid object
-				existingData = parsed && typeof parsed === "object" ? parsed : {}
-			} catch (parseError) {
-				// If we can't parse the file, we can't safely remove a mode
-				const fileName = target === "project" ? ".roomodes" : "custom-modes.yaml"
-				throw new Error(
-					`Cannot remove mode: The ${fileName} file contains invalid YAML. ` +
-						`Please fix the syntax errors before removing modes.`,
-				)
-			}
+		if (!this.customModesManager) {
+			throw new Error("CustomModesManager is not available")
+		}
 
-			// Ensure customModes array exists
-			if (!existingData.customModes) {
-				existingData.customModes = []
-			}
+		// Parse the item content to get the slug
+		let content: string
+		if (Array.isArray(item.content)) {
+			// Array of McpInstallationMethod objects - use first method
+			content = item.content[0].content
+		} else {
+			content = item.content || ""
+		}
 
-			// Parse the item content to get the slug
-			let content: string
-			if (Array.isArray(item.content)) {
-				// Array of McpInstallationMethod objects - use first method
-				content = item.content[0].content
-			} else {
-				content = item.content
-			}
-			const modeData = yaml.parse(content || "")
+		let modeSlug: string
+		try {
+			const modeData = yaml.parse(content)
+			modeSlug = modeData.slug
+		} catch (error) {
+			throw new Error("Invalid mode content: unable to parse YAML")
+		}
 
-			if (!modeData.slug) {
-				return // Nothing to remove if no slug
-			}
+		if (!modeSlug) {
+			throw new Error("Mode missing slug identifier")
+		}
 
-			// Remove mode with matching slug
-			existingData.customModes = existingData.customModes.filter((mode: any) => mode.slug !== modeData.slug)
+		// Get the current modes to determine the source
+		const modes = await this.customModesManager.getCustomModes()
+		const mode = modes.find((m) => m.slug === modeSlug)
 
-			// Always write back the file, even if empty
-			await fs.writeFile(filePath, yaml.stringify(existingData, { lineWidth: 0 }), "utf-8")
-		} catch (error: any) {
-			if (error.code === "ENOENT") {
-				// File doesn't exist, nothing to remove
-				return
-			}
-			throw error
-		}
+		// Use CustomModesManager to delete the mode configuration
+		// This also handles rules folder deletion
+		await this.customModesManager.deleteCustomMode(modeSlug, true)
 	}
 
 	private async removeMcp(item: MarketplaceItem, target: "project" | "global"): Promise<void> {

+ 170 - 149
src/services/marketplace/__tests__/SimpleInstaller.spec.ts

@@ -4,10 +4,19 @@ import { SimpleInstaller } from "../SimpleInstaller"
 import * as fs from "fs/promises"
 import * as yaml from "yaml"
 import * as vscode from "vscode"
+import * as os from "os"
 import type { MarketplaceItem } from "@roo-code/types"
+import type { CustomModesManager } from "../../../core/config/CustomModesManager"
 import * as path from "path"
+import { fileExistsAtPath } from "../../../utils/fs"
 
-vi.mock("fs/promises")
+vi.mock("fs/promises", () => ({
+	readFile: vi.fn(),
+	writeFile: vi.fn(),
+	mkdir: vi.fn(),
+	rm: vi.fn(),
+}))
+vi.mock("os")
 vi.mock("vscode", () => ({
 	workspace: {
 		workspaceFolders: [
@@ -20,20 +29,33 @@ vi.mock("vscode", () => ({
 	},
 }))
 vi.mock("../../../utils/globalContext")
+vi.mock("../../../utils/fs")
 
-const mockFs = fs as any
+const mockFs = vi.mocked(fs)
 
 describe("SimpleInstaller", () => {
 	let installer: SimpleInstaller
 	let mockContext: vscode.ExtensionContext
+	let mockCustomModesManager: CustomModesManager
 
 	beforeEach(() => {
 		mockContext = {} as vscode.ExtensionContext
-		installer = new SimpleInstaller(mockContext)
+		mockCustomModesManager = {
+			deleteCustomMode: vi.fn().mockResolvedValue(undefined),
+			importModeWithRules: vi.fn().mockResolvedValue({ success: true }),
+			getCustomModes: vi.fn().mockResolvedValue([]),
+		} as any
+		installer = new SimpleInstaller(mockContext, mockCustomModesManager)
 		vi.clearAllMocks()
 
 		// Mock mkdir to always succeed
 		mockFs.mkdir.mockResolvedValue(undefined as any)
+		// Mock rm to always succeed
+		mockFs.rm.mockResolvedValue(undefined as any)
+		// Mock os.homedir
+		vi.mocked(os.homedir).mockReturnValue("/home/user")
+		// Mock fileExistsAtPath to return false by default
+		vi.mocked(fileExistsAtPath).mockResolvedValue(false)
 	})
 
 	describe("installMode", () => {
@@ -50,128 +72,69 @@ describe("SimpleInstaller", () => {
 			}),
 		}
 
-		it("should install mode when .roomodes file does not exist", async () => {
-			// Mock file not found error
+		it("should install mode using CustomModesManager", async () => {
+			// Mock file not found error for getModeFilePath
 			const notFoundError = new Error("File not found") as any
 			notFoundError.code = "ENOENT"
 			mockFs.readFile.mockRejectedValueOnce(notFoundError)
-			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
 
 			const result = await installer.installItem(mockModeItem, { target: "project" })
 
 			expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes"))
-			expect(mockFs.writeFile).toHaveBeenCalled()
-
-			// Verify the written content contains the new mode
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-			expect(writtenData.customModes).toHaveLength(1)
-			expect(writtenData.customModes[0].slug).toBe("test")
+			expect(mockCustomModesManager.importModeWithRules).toHaveBeenCalled()
+
+			// Verify the import was called with correct YAML structure
+			const importCall = (mockCustomModesManager.importModeWithRules as any).mock.calls[0]
+			const importedYaml = importCall[0]
+			const importedData = yaml.parse(importedYaml)
+			expect(importedData.customModes).toHaveLength(1)
+			expect(importedData.customModes[0].slug).toBe("test")
 		})
 
-		it("should install mode when .roomodes contains valid YAML", async () => {
-			const existingContent = yaml.stringify({
-				customModes: [{ slug: "existing", name: "Existing Mode", roleDefinition: "Existing", groups: [] }],
+		it("should handle import failure from CustomModesManager", async () => {
+			mockCustomModesManager.importModeWithRules = vi.fn().mockResolvedValue({
+				success: false,
+				error: "Import failed",
 			})
 
-			mockFs.readFile.mockResolvedValueOnce(existingContent)
-			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
-
-			await installer.installItem(mockModeItem, { target: "project" })
-
-			expect(mockFs.writeFile).toHaveBeenCalled()
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-
-			// Should contain both existing and new mode
-			expect(writtenData.customModes).toHaveLength(2)
-			expect(writtenData.customModes.find((m: any) => m.slug === "existing")).toBeDefined()
-			expect(writtenData.customModes.find((m: any) => m.slug === "test")).toBeDefined()
+			await expect(installer.installItem(mockModeItem, { target: "project" })).rejects.toThrow("Import failed")
 		})
 
-		it("should handle empty .roomodes file", async () => {
-			// Empty file content
-			mockFs.readFile.mockResolvedValueOnce("")
-			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
-
-			const result = await installer.installItem(mockModeItem, { target: "project" })
+		it("should throw error for array content in mode", async () => {
+			const arrayContentMode: MarketplaceItem = {
+				...mockModeItem,
+				content: ["content1", "content2"] as any,
+			}
 
-			expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes"))
-			expect(mockFs.writeFile).toHaveBeenCalled()
-
-			// Verify the written content contains the new mode
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-			expect(writtenData.customModes).toHaveLength(1)
-			expect(writtenData.customModes[0].slug).toBe("test")
-		})
-
-		it("should handle .roomodes file with null content", async () => {
-			// File exists but yaml.parse returns null
-			mockFs.readFile.mockResolvedValueOnce("---\n")
-			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
-
-			const result = await installer.installItem(mockModeItem, { target: "project" })
-
-			expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes"))
-			expect(mockFs.writeFile).toHaveBeenCalled()
-
-			// Verify the written content contains the new mode
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-			expect(writtenData.customModes).toHaveLength(1)
-			expect(writtenData.customModes[0].slug).toBe("test")
-		})
-
-		it("should handle .roomodes file without customModes property", async () => {
-			// File has valid YAML but no customModes property
-			const contentWithoutCustomModes = yaml.stringify({ someOtherProperty: "value" })
-			mockFs.readFile.mockResolvedValueOnce(contentWithoutCustomModes)
-			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
-
-			const result = await installer.installItem(mockModeItem, { target: "project" })
-
-			expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes"))
-			expect(mockFs.writeFile).toHaveBeenCalled()
-
-			// Verify the written content contains the new mode and preserves other properties
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-			expect(writtenData.customModes).toHaveLength(1)
-			expect(writtenData.customModes[0].slug).toBe("test")
-			expect(writtenData.someOtherProperty).toBe("value")
+			await expect(installer.installItem(arrayContentMode, { target: "project" })).rejects.toThrow(
+				"Mode content should not be an array",
+			)
 		})
 
-		it("should throw error when .roomodes contains invalid YAML", async () => {
-			const invalidYaml = "invalid: yaml: content: {"
-
-			mockFs.readFile.mockResolvedValueOnce(invalidYaml)
+		it("should throw error for missing content", async () => {
+			const noContentMode: MarketplaceItem = {
+				...mockModeItem,
+				content: undefined as any,
+			}
 
-			await expect(installer.installItem(mockModeItem, { target: "project" })).rejects.toThrow(
-				"Cannot install mode: The .roomodes file contains invalid YAML",
+			await expect(installer.installItem(noContentMode, { target: "project" })).rejects.toThrow(
+				"Mode item missing content",
 			)
-
-			// Should NOT write to file
-			expect(mockFs.writeFile).not.toHaveBeenCalled()
 		})
 
-		it("should replace existing mode with same slug", async () => {
-			const existingContent = yaml.stringify({
-				customModes: [{ slug: "test", name: "Old Test Mode", roleDefinition: "Old role", groups: [] }],
-			})
+		it("should work without CustomModesManager (fallback)", async () => {
+			const installerWithoutManager = new SimpleInstaller(mockContext)
 
-			mockFs.readFile.mockResolvedValueOnce(existingContent)
+			// Mock file not found
+			const notFoundError = new Error("File not found") as any
+			notFoundError.code = "ENOENT"
+			mockFs.readFile.mockRejectedValueOnce(notFoundError)
 			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
 
-			await installer.installItem(mockModeItem, { target: "project" })
+			const result = await installerWithoutManager.installItem(mockModeItem, { target: "project" })
 
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-
-			// Should contain only one mode with updated content
-			expect(writtenData.customModes).toHaveLength(1)
-			expect(writtenData.customModes[0].slug).toBe("test")
-			expect(writtenData.customModes[0].name).toBe("Test Mode") // New name
+			expect(result.filePath).toBe(path.join("/test/workspace", ".roomodes"))
+			expect(mockFs.writeFile).toHaveBeenCalled()
 		})
 	})
 
@@ -254,75 +217,133 @@ describe("SimpleInstaller", () => {
 			}),
 		}
 
-		it("should throw error when .roomodes contains invalid YAML during removal", async () => {
-			const invalidYaml = "invalid: yaml: content: {"
+		it("should use CustomModesManager to delete mode and clean up rules folder", async () => {
+			// Mock that the mode exists with project source
+			vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
+				{ slug: "test", name: "Test Mode", source: "project" } as any,
+			])
+
+			await installer.removeItem(mockModeItem, { target: "project" })
 
-			mockFs.readFile.mockResolvedValueOnce(invalidYaml)
+			// Should call deleteCustomMode with fromMarketplace flag set to true
+			expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
+			// The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller
+			expect(fileExistsAtPath).not.toHaveBeenCalled()
+			expect(mockFs.rm).not.toHaveBeenCalled()
+		})
 
-			await expect(installer.removeItem(mockModeItem, { target: "project" })).rejects.toThrow(
-				"Cannot remove mode: The .roomodes file contains invalid YAML",
-			)
+		it("should handle global mode removal with rules cleanup", async () => {
+			// Mock that the mode exists with global source
+			vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
+				{ slug: "test", name: "Test Mode", source: "global" } as any,
+			])
 
-			// Should NOT write to file
-			expect(mockFs.writeFile).not.toHaveBeenCalled()
+			await installer.removeItem(mockModeItem, { target: "global" })
+
+			// Should call deleteCustomMode with fromMarketplace flag set to true
+			expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
+			// The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller
+			expect(fileExistsAtPath).not.toHaveBeenCalled()
+			expect(mockFs.rm).not.toHaveBeenCalled()
 		})
 
-		it("should do nothing when file does not exist", async () => {
-			const notFoundError = new Error("File not found") as any
-			notFoundError.code = "ENOENT"
-			mockFs.readFile.mockRejectedValueOnce(notFoundError)
+		it("should handle case when rules folder does not exist", async () => {
+			// Mock that the mode exists
+			vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
+				{ slug: "test", name: "Test Mode", source: "project" } as any,
+			])
 
-			// Should not throw
 			await installer.removeItem(mockModeItem, { target: "project" })
 
-			expect(mockFs.writeFile).not.toHaveBeenCalled()
+			// Should call deleteCustomMode with fromMarketplace flag set to true
+			expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
+			// The rules folder deletion is now handled by CustomModesManager, not SimpleInstaller
+			expect(fileExistsAtPath).not.toHaveBeenCalled()
+			expect(mockFs.rm).not.toHaveBeenCalled()
 		})
 
-		it("should handle empty .roomodes file during removal", async () => {
-			// Empty file content
-			mockFs.readFile.mockResolvedValueOnce("")
-			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
+		it("should throw error if deleteCustomMode fails", async () => {
+			// Mock that the mode exists
+			vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([
+				{ slug: "test", name: "Test Mode", source: "project" } as any,
+			])
+			// Mock that deleteCustomMode fails
+			mockCustomModesManager.deleteCustomMode = vi.fn().mockRejectedValueOnce(new Error("Permission denied"))
 
-			// Should not throw
-			await installer.removeItem(mockModeItem, { target: "project" })
+			// Should throw the error from deleteCustomMode
+			await expect(installer.removeItem(mockModeItem, { target: "project" })).rejects.toThrow("Permission denied")
 
-			// Should write back a valid structure with empty customModes
-			expect(mockFs.writeFile).toHaveBeenCalled()
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-			expect(writtenData.customModes).toEqual([])
+			expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
 		})
 
-		it("should handle .roomodes file with null content during removal", async () => {
-			// File exists but yaml.parse returns null
-			mockFs.readFile.mockResolvedValueOnce("---\n")
-			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
+		it("should handle mode not found in custom modes list", async () => {
+			// Mock that the mode doesn't exist in the list
+			vi.mocked(mockCustomModesManager.getCustomModes).mockResolvedValueOnce([])
 
-			// Should not throw
 			await installer.removeItem(mockModeItem, { target: "project" })
 
-			// Should write back a valid structure with empty customModes
-			expect(mockFs.writeFile).toHaveBeenCalled()
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-			expect(writtenData.customModes).toEqual([])
+			expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test", true)
+			// Should not attempt to delete rules folder
+			expect(fileExistsAtPath).not.toHaveBeenCalled()
+			expect(mockFs.rm).not.toHaveBeenCalled()
 		})
 
-		it("should handle .roomodes file without customModes property during removal", async () => {
-			// File has valid YAML but no customModes property
-			const contentWithoutCustomModes = yaml.stringify({ someOtherProperty: "value" })
-			mockFs.readFile.mockResolvedValueOnce(contentWithoutCustomModes)
-			mockFs.writeFile.mockResolvedValueOnce(undefined as any)
+		it("should throw error when mode content is invalid YAML", async () => {
+			const invalidModeItem: MarketplaceItem = {
+				...mockModeItem,
+				content: "invalid: yaml: content: {",
+			}
 
-			// Should not throw
-			await installer.removeItem(mockModeItem, { target: "project" })
+			await expect(installer.removeItem(invalidModeItem, { target: "project" })).rejects.toThrow(
+				"Invalid mode content: unable to parse YAML",
+			)
 
-			// Should write back the file with the same content (no modes to remove)
-			expect(mockFs.writeFile).toHaveBeenCalled()
-			const writtenContent = mockFs.writeFile.mock.calls[0][1] as string
-			const writtenData = yaml.parse(writtenContent)
-			expect(writtenData.customModes).toEqual([])
-			expect(writtenData.someOtherProperty).toBe("value")
+			expect(mockCustomModesManager.deleteCustomMode).not.toHaveBeenCalled()
+		})
+
+		it("should throw error when mode has no slug", async () => {
+			const noSlugModeItem: MarketplaceItem = {
+				...mockModeItem,
+				content: yaml.stringify({
+					name: "Test Mode",
+					roleDefinition: "Test role",
+					groups: ["read"],
+				}),
+			}
+
+			await expect(installer.removeItem(noSlugModeItem, { target: "project" })).rejects.toThrow(
+				"Mode missing slug identifier",
+			)
+
+			expect(mockCustomModesManager.deleteCustomMode).not.toHaveBeenCalled()
+		})
+
+		it("should handle array content format", async () => {
+			const arrayContentItem: MarketplaceItem = {
+				...mockModeItem,
+				content: [
+					{
+						content: yaml.stringify({
+							slug: "test-array",
+							name: "Test Array Mode",
+							roleDefinition: "Test role",
+							groups: ["read"],
+						}),
+					},
+				] as any,
+			}
+
+			await installer.removeItem(arrayContentItem, { target: "project" })
+
+			expect(mockCustomModesManager.deleteCustomMode).toHaveBeenCalledWith("test-array", true)
+		})
+
+		it("should throw error when CustomModesManager is not available", async () => {
+			const installerWithoutManager = new SimpleInstaller(mockContext)
+
+			await expect(installerWithoutManager.removeItem(mockModeItem, { target: "project" })).rejects.toThrow(
+				"CustomModesManager is not available",
+			)
 		})
 	})
 })

+ 1 - 0
src/shared/ExtensionMessage.ts

@@ -102,6 +102,7 @@ export interface ExtensionMessage {
 		| "indexCleared"
 		| "codebaseIndexConfig"
 		| "marketplaceInstallResult"
+		| "marketplaceRemoveResult"
 		| "marketplaceData"
 		| "shareTaskSuccess"
 		| "codeIndexSettingsSaved"

+ 87 - 11
webview-ui/src/components/marketplace/components/MarketplaceItemCard.tsx

@@ -1,4 +1,4 @@
-import React, { useMemo, useState } from "react"
+import React, { useMemo, useState, useEffect } from "react"
 import { MarketplaceItem, TelemetryEventName } from "@roo-code/types"
 import { vscode } from "@/utils/vscode"
 import { telemetryClient } from "@/utils/TelemetryClient"
@@ -10,6 +10,16 @@ import { Button } from "@/components/ui/button"
 import { StandardTooltip } from "@/components/ui"
 import { MarketplaceInstallModal } from "./MarketplaceInstallModal"
 import { useExtensionState } from "@/context/ExtensionStateContext"
+import {
+	AlertDialog,
+	AlertDialogAction,
+	AlertDialogCancel,
+	AlertDialogContent,
+	AlertDialogDescription,
+	AlertDialogFooter,
+	AlertDialogHeader,
+	AlertDialogTitle,
+} from "@/components/ui"
 
 interface ItemInstalledMetadata {
 	type: string
@@ -29,6 +39,30 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({ item,
 	const { t } = useAppTranslation()
 	const { cwd } = useExtensionState()
 	const [showInstallModal, setShowInstallModal] = useState(false)
+	const [showRemoveConfirm, setShowRemoveConfirm] = useState(false)
+	const [removeTarget, setRemoveTarget] = useState<"project" | "global">("project")
+	const [removeError, setRemoveError] = useState<string | null>(null)
+
+	// Listen for removal result messages
+	useEffect(() => {
+		const handleMessage = (event: MessageEvent) => {
+			const message = event.data
+			if (message.type === "marketplaceRemoveResult" && message.slug === item.id) {
+				if (message.success) {
+					// Removal succeeded - refresh marketplace data
+					vscode.postMessage({
+						type: "fetchMarketplaceData",
+					})
+				} else {
+					// Removal failed - show error message to user
+					setRemoveError(message.error || t("marketplace:items.unknownError"))
+				}
+			}
+		}
+
+		window.addEventListener("message", handleMessage)
+		return () => window.removeEventListener("message", handleMessage)
+	}, [item.id, t])
 
 	const typeLabel = useMemo(() => {
 		const labels: Partial<Record<MarketplaceItem["type"], string>> = {
@@ -92,16 +126,8 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({ item,
 									onClick={() => {
 										// Determine which installation to remove (prefer project over global)
 										const target = isInstalledInProject ? "project" : "global"
-										vscode.postMessage({
-											type: "removeInstalledMarketplaceItem",
-											mpItem: item,
-											mpInstallOptions: { target },
-										})
-
-										// Request fresh marketplace data to update installed status
-										vscode.postMessage({
-											type: "fetchMarketplaceData",
-										})
+										setRemoveTarget(target)
+										setShowRemoveConfirm(true)
 									}}>
 									{t("marketplace:items.card.remove")}
 								</Button>
@@ -116,6 +142,13 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({ item,
 								{t("marketplace:items.card.install")}
 							</Button>
 						)}
+
+						{/* Error message display */}
+						{removeError && (
+							<div className="text-vscode-errorForeground text-sm mt-2">
+								{t("marketplace:items.removeFailed", { error: removeError })}
+							</div>
+						)}
 					</div>
 				</div>
 
@@ -169,6 +202,49 @@ export const MarketplaceItemCard: React.FC<MarketplaceItemCardProps> = ({ item,
 				onClose={() => setShowInstallModal(false)}
 				hasWorkspace={!!cwd}
 			/>
+
+			{/* Remove Confirmation Dialog */}
+			<AlertDialog open={showRemoveConfirm} onOpenChange={setShowRemoveConfirm}>
+				<AlertDialogContent>
+					<AlertDialogHeader>
+						<AlertDialogTitle>
+							{item.type === "mode"
+								? t("marketplace:removeConfirm.mode.title")
+								: t("marketplace:removeConfirm.mcp.title")}
+						</AlertDialogTitle>
+						<AlertDialogDescription>
+							{item.type === "mode" ? (
+								<>
+									{t("marketplace:removeConfirm.mode.message", { modeName: item.name })}
+									<div className="mt-2 text-sm">
+										{t("marketplace:removeConfirm.mode.rulesWarning")}
+									</div>
+								</>
+							) : (
+								t("marketplace:removeConfirm.mcp.message", { mcpName: item.name })
+							)}
+						</AlertDialogDescription>
+					</AlertDialogHeader>
+					<AlertDialogFooter>
+						<AlertDialogCancel>{t("marketplace:removeConfirm.cancel")}</AlertDialogCancel>
+						<AlertDialogAction
+							onClick={() => {
+								// Clear any previous error
+								setRemoveError(null)
+
+								vscode.postMessage({
+									type: "removeInstalledMarketplaceItem",
+									mpItem: item,
+									mpInstallOptions: { target: removeTarget },
+								})
+
+								setShowRemoveConfirm(false)
+							}}>
+							{t("marketplace:removeConfirm.confirm")}
+						</AlertDialogAction>
+					</AlertDialogFooter>
+				</AlertDialogContent>
+			</AlertDialog>
 		</>
 	)
 }

+ 19 - 2
webview-ui/src/i18n/locales/ca/marketplace.json

@@ -70,8 +70,12 @@
 			"installed": "Instal·lat",
 			"removeProjectTooltip": "Eliminar del projecte actual",
 			"removeGlobalTooltip": "Eliminar de la configuració global",
-			"actionsMenuLabel": "Més accions"
-		}
+			"actionsMenuLabel": "Més accions",
+			"official": "Oficial",
+			"verified": "Verificat"
+		},
+		"removeFailed": "No s'ha pogut eliminar l'element: {{error}}",
+		"unknownError": "S'ha produït un error desconegut"
 	},
 	"install": {
 		"title": "Instal·lar {{name}}",
@@ -125,6 +129,19 @@
 			"maxSources": "Màxim de {{max}} fonts permeses"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Eliminar el mode",
+			"message": "Estàs segur que vols eliminar el mode \"{{modeName}}\"?",
+			"rulesWarning": "Això també eliminarà qualsevol fitxer de regles associat per a aquest mode."
+		},
+		"mcp": {
+			"title": "Eliminar el servidor MCP",
+			"message": "Estàs segur que vols eliminar el servidor MCP \"{{mcpName}}\"?"
+		},
+		"cancel": "Cancel·lar",
+		"confirm": "Eliminar"
+	},
 	"footer": {
 		"issueText": "Has trobat un problema amb un element del marketplace o tens suggeriments per a nous elements? <0>Obre una incidència de GitHub</0> per fer-nos-ho saber!"
 	}

+ 19 - 2
webview-ui/src/i18n/locales/de/marketplace.json

@@ -70,8 +70,12 @@
 			"installed": "Installiert",
 			"removeProjectTooltip": "Aus aktuellem Projekt entfernen",
 			"removeGlobalTooltip": "Aus globaler Konfiguration entfernen",
-			"actionsMenuLabel": "Weitere Aktionen"
-		}
+			"actionsMenuLabel": "Weitere Aktionen",
+			"official": "Offiziell",
+			"verified": "Verifiziert"
+		},
+		"removeFailed": "Element konnte nicht entfernt werden: {{error}}",
+		"unknownError": "Unbekannter Fehler aufgetreten"
 	},
 	"install": {
 		"title": "{{name}} installieren",
@@ -125,6 +129,19 @@
 			"maxSources": "Maximal {{max}} Quellen erlaubt"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Modus entfernen",
+			"message": "Bist du sicher, dass du den Modus „{{modeName}}“ entfernen möchtest?",
+			"rulesWarning": "Dadurch werden auch alle zugehörigen Regeldateien für diesen Modus entfernt."
+		},
+		"mcp": {
+			"title": "MCP-Server entfernen",
+			"message": "Bist du sicher, dass du den MCP-Server „{{mcpName}}“ entfernen möchtest?"
+		},
+		"cancel": "Abbrechen",
+		"confirm": "Entfernen"
+	},
 	"footer": {
 		"issueText": "Problem mit einem Marketplace-Element gefunden oder Vorschläge für neue? <0>Öffne ein GitHub-Issue</0>, um es uns mitzuteilen!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/en/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Remove from current project",
 			"removeGlobalTooltip": "Remove from global configuration",
 			"actionsMenuLabel": "More actions"
-		}
+		},
+		"removeFailed": "Failed to remove item: {{error}}",
+		"unknownError": "Unknown error occurred"
 	},
 	"install": {
 		"title": "Install {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "Maximum of {{max}} sources allowed"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Remove Mode",
+			"message": "Are you sure you want to remove the mode \"{{modeName}}\"?",
+			"rulesWarning": "This will also remove any associated rules files for this mode."
+		},
+		"mcp": {
+			"title": "Remove MCP Server",
+			"message": "Are you sure you want to remove the MCP server \"{{mcpName}}\"?"
+		},
+		"cancel": "Cancel",
+		"confirm": "Remove"
+	},
 	"footer": {
 		"issueText": "Found a problem with a marketplace item or have suggestions for new ones? <0>Open a GitHub issue</0> to let us know!"
 	}

+ 19 - 2
webview-ui/src/i18n/locales/es/marketplace.json

@@ -70,8 +70,12 @@
 			"installed": "Instalado",
 			"removeProjectTooltip": "Eliminar del proyecto actual",
 			"removeGlobalTooltip": "Eliminar de la configuración global",
-			"actionsMenuLabel": "Más acciones"
-		}
+			"actionsMenuLabel": "Más acciones",
+			"official": "Oficial",
+			"verified": "Verificado"
+		},
+		"removeFailed": "No se pudo eliminar el elemento: {{error}}",
+		"unknownError": "Ocurrió un error desconocido"
 	},
 	"install": {
 		"title": "Instalar {{name}}",
@@ -125,6 +129,19 @@
 			"maxSources": "Máximo de {{max}} fuentes permitidas"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Eliminar modo",
+			"message": "¿Estás seguro de que quieres eliminar el modo \"{{modeName}}\"?",
+			"rulesWarning": "Esto también eliminará cualquier archivo de reglas asociado para este modo."
+		},
+		"mcp": {
+			"title": "Eliminar servidor MCP",
+			"message": "¿Estás seguro de que quieres eliminar el servidor MCP \"{{mcpName}}\"?"
+		},
+		"cancel": "Cancelar",
+		"confirm": "Eliminar"
+	},
 	"footer": {
 		"issueText": "¿Encontraste un problema con un elemento del marketplace o tienes sugerencias para nuevos? ¡<0>Abre un issue en GitHub</0> para hacérnoslo saber!"
 	}

+ 20 - 3
webview-ui/src/i18n/locales/fr/marketplace.json

@@ -27,7 +27,7 @@
 		},
 		"tags": {
 			"label": "Filtrer par étiquettes :",
-			"clear": "Effacer les étiquettes",
+			"clear": "Cliquez pour supprimer '{{count}}' des filtres",
 			"placeholder": "Tapez pour rechercher et sélectionner des étiquettes...",
 			"noResults": "Aucune étiquette correspondante trouvée",
 			"selected": "Affichage des éléments avec l'une des étiquettes sélectionnées",
@@ -70,8 +70,12 @@
 			"installed": "Installé",
 			"removeProjectTooltip": "Supprimer du projet actuel",
 			"removeGlobalTooltip": "Supprimer de la configuration globale",
-			"actionsMenuLabel": "Plus d'actions"
-		}
+			"actionsMenuLabel": "Plus d'actions",
+			"official": "Officiel",
+			"verified": "Vérifié"
+		},
+		"removeFailed": "Échec de la suppression de l'élément : {{error}}",
+		"unknownError": "Une erreur inconnue est survenue"
 	},
 	"install": {
 		"title": "Installer {{name}}",
@@ -125,6 +129,19 @@
 			"maxSources": "Maximum de {{max}} sources autorisées"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Supprimer le mode",
+			"message": "Êtes-vous sûr de vouloir supprimer le mode « {{modeName}} » ?",
+			"rulesWarning": "Cela supprimera également tous les fichiers de règles associés à ce mode."
+		},
+		"mcp": {
+			"title": "Supprimer le serveur MCP",
+			"message": "Êtes-vous sûr de vouloir supprimer le serveur MCP « {{mcpName}} » ?"
+		},
+		"cancel": "Annuler",
+		"confirm": "Supprimer"
+	},
 	"footer": {
 		"issueText": "Vous avez trouvé un problème avec un élément du marketplace ou avez des suggestions pour de nouveaux éléments ? <0>Ouvrez une issue GitHub</0> pour nous le faire savoir !"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/hi/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "वर्तमान प्रोजेक्ट से हटाएं",
 			"removeGlobalTooltip": "ग्लोबल कॉन्फ़िगरेशन से हटाएं",
 			"actionsMenuLabel": "अधिक क्रियाएं"
-		}
+		},
+		"removeFailed": "आइटम हटाने में विफल: {{error}}",
+		"unknownError": "अज्ञात त्रुटि हुई"
 	},
 	"install": {
 		"title": "{{name}} इंस्टॉल करें",
@@ -125,6 +127,19 @@
 			"maxSources": "अधिकतम {{max}} स्रोतों की अनुमति है"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "मोड हटाएं",
+			"message": "क्या आप वाकई मोड \"{{modeName}}\" को हटाना चाहते हैं?",
+			"rulesWarning": "यह इस मोड के लिए किसी भी संबंधित नियम फ़ाइल को भी हटा देगा।"
+		},
+		"mcp": {
+			"title": "MCP सर्वर हटाएं",
+			"message": "क्या आप वाकई MCP सर्वर \"{{mcpName}}\" को हटाना चाहते हैं?"
+		},
+		"cancel": "रद्द करें",
+		"confirm": "हटाएं"
+	},
 	"footer": {
 		"issueText": "कोई marketplace आइटम के साथ समस्या है या नए आइटम के लिए सुझाव हैं? <0>GitHub issue खोलें</0> हमें बताने के लिए!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/id/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Hapus dari proyek saat ini",
 			"removeGlobalTooltip": "Hapus dari konfigurasi global",
 			"actionsMenuLabel": "Aksi lainnya"
-		}
+		},
+		"removeFailed": "Gagal menghapus item: {{error}}",
+		"unknownError": "Terjadi kesalahan yang tidak diketahui"
 	},
 	"install": {
 		"title": "Instal {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "Maksimal {{max}} sumber diizinkan"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Hapus Mode",
+			"message": "Apakah Anda yakin ingin menghapus mode \"{{modeName}}\"?",
+			"rulesWarning": "Ini juga akan menghapus semua file aturan terkait untuk mode ini."
+		},
+		"mcp": {
+			"title": "Hapus Server MCP",
+			"message": "Apakah Anda yakin ingin menghapus server MCP \"{{mcpName}}\"?"
+		},
+		"cancel": "Batal",
+		"confirm": "Hapus"
+	},
 	"footer": {
 		"issueText": "Menemukan masalah dengan item marketplace atau punya saran untuk yang baru? <0>Buka GitHub issue</0> untuk memberi tahu kami!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/it/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Rimuovi dal progetto corrente",
 			"removeGlobalTooltip": "Rimuovi dalla configurazione globale",
 			"actionsMenuLabel": "Altre azioni"
-		}
+		},
+		"removeFailed": "Impossibile rimuovere l'elemento: {{error}}",
+		"unknownError": "Si è verificato un errore sconosciuto"
 	},
 	"install": {
 		"title": "Installa {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "Massimo {{max}} fonti consentite"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Rimuovi modalità",
+			"message": "Sei sicuro di voler rimuovere la modalità \"{{modeName}}\"?",
+			"rulesWarning": "Questo rimuoverà anche eventuali file di regole associati per questa modalità."
+		},
+		"mcp": {
+			"title": "Rimuovi server MCP",
+			"message": "Sei sicuro di voler rimuovere il server MCP \"{{mcpName}}\"?"
+		},
+		"cancel": "Annulla",
+		"confirm": "Rimuovi"
+	},
 	"footer": {
 		"issueText": "Hai trovato un problema con un elemento del marketplace o hai suggerimenti per nuovi elementi? <0>Apri un issue GitHub</0> per farcelo sapere!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/ja/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "現在のプロジェクトから削除",
 			"removeGlobalTooltip": "グローバル設定から削除",
 			"actionsMenuLabel": "その他のアクション"
-		}
+		},
+		"removeFailed": "アイテムの削除に失敗しました: {{error}}",
+		"unknownError": "不明なエラーが発生しました"
 	},
 	"install": {
 		"title": "{{name}}をインストール",
@@ -125,6 +127,19 @@
 			"maxSources": "最大{{max}}個のソースが許可されています"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "モードを削除",
+			"message": "モード「{{modeName}}」を本当に削除しますか?",
+			"rulesWarning": "これにより、このモードに関連するルールファイルも削除されます。"
+		},
+		"mcp": {
+			"title": "MCPサーバーを削除",
+			"message": "MCPサーバー「{{mcpName}}」を本当に削除しますか?"
+		},
+		"cancel": "キャンセル",
+		"confirm": "削除"
+	},
 	"footer": {
 		"issueText": "Marketplaceアイテムで問題を見つけた、または新しいアイテムの提案がありますか?<0>GitHub issueを開いて</0>お知らせください!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/ko/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "현재 프로젝트에서 삭제",
 			"removeGlobalTooltip": "전역 설정에서 삭제",
 			"actionsMenuLabel": "추가 작업"
-		}
+		},
+		"removeFailed": "항목을 제거하지 못했습니다: {{error}}",
+		"unknownError": "알 수 없는 오류가 발생했습니다"
 	},
 	"install": {
 		"title": "{{name}} 설치",
@@ -125,6 +127,19 @@
 			"maxSources": "최대 {{max}}개의 소스가 허용됩니다"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "모드 제거",
+			"message": "정말로 '{{modeName}}' 모드를 제거하시겠습니까?",
+			"rulesWarning": "이렇게 하면 이 모드와 관련된 모든 규칙 파일도 제거됩니다."
+		},
+		"mcp": {
+			"title": "MCP 서버 제거",
+			"message": "정말로 '{{mcpName}}' MCP 서버를 제거하시겠습니까?"
+		},
+		"cancel": "취소",
+		"confirm": "제거"
+	},
 	"footer": {
 		"issueText": "Marketplace 아이템에 문제가 있거나 새로운 아이템에 대한 제안이 있나요? <0>GitHub issue를 열어서</0> 알려주세요!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/nl/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Verwijderen uit huidig project",
 			"removeGlobalTooltip": "Verwijderen uit globale configuratie",
 			"actionsMenuLabel": "Meer acties"
-		}
+		},
+		"removeFailed": "Item verwijderen mislukt: {{error}}",
+		"unknownError": "Onbekende fout opgetreden"
 	},
 	"install": {
 		"title": "{{name}} installeren",
@@ -125,6 +127,19 @@
 			"maxSources": "Maximaal {{max}} bronnen toegestaan"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Modus verwijderen",
+			"message": "Weet je zeker dat je de modus \"{{modeName}}\" wilt verwijderen?",
+			"rulesWarning": "Dit verwijdert ook alle bijbehorende regelbestanden voor deze modus."
+		},
+		"mcp": {
+			"title": "MCP-server verwijderen",
+			"message": "Weet je zeker dat je de MCP-server \"{{mcpName}}\" wilt verwijderen?"
+		},
+		"cancel": "Annuleren",
+		"confirm": "Verwijderen"
+	},
 	"footer": {
 		"issueText": "Heb je een probleem gevonden met een marketplace-item of heb je suggesties voor nieuwe items? <0>Open een GitHub issue</0> om het ons te laten weten!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/pl/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Usuń z bieżącego projektu",
 			"removeGlobalTooltip": "Usuń z konfiguracji globalnej",
 			"actionsMenuLabel": "Więcej akcji"
-		}
+		},
+		"removeFailed": "Nie udało się usunąć elementu: {{error}}",
+		"unknownError": "Wystąpił nieznany błąd"
 	},
 	"install": {
 		"title": "Zainstaluj {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "Maksymalnie {{max}} źródeł dozwolonych"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Usuń tryb",
+			"message": "Czy na pewno chcesz usunąć tryb „{{modeName}}”?",
+			"rulesWarning": "Spowoduje to również usunięcie wszelkich powiązanych plików reguł dla tego trybu."
+		},
+		"mcp": {
+			"title": "Usuń serwer MCP",
+			"message": "Czy na pewno chcesz usunąć serwer MCP „{{mcpName}}”?"
+		},
+		"cancel": "Anuluj",
+		"confirm": "Usuń"
+	},
 	"footer": {
 		"issueText": "Znalazłeś problem z elementem marketplace lub masz sugestie dotyczące nowych elementów? <0>Otwórz issue na GitHub</0>, aby nam o tym powiedzieć!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/pt-BR/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Remover do projeto atual",
 			"removeGlobalTooltip": "Remover da configuração global",
 			"actionsMenuLabel": "Mais ações"
-		}
+		},
+		"removeFailed": "Falha ao remover o item: {{error}}",
+		"unknownError": "Ocorreu um erro desconhecido"
 	},
 	"install": {
 		"title": "Instalar {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "Máximo de {{max}} fontes permitidas"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Remover Modo",
+			"message": "Tem certeza de que deseja remover o modo \"{{modeName}}\"?",
+			"rulesWarning": "Isso também removerá todos os arquivos de regras associados a este modo."
+		},
+		"mcp": {
+			"title": "Remover Servidor MCP",
+			"message": "Tem certeza de que deseja remover o servidor MCP \"{{mcpName}}\"?"
+		},
+		"cancel": "Cancelar",
+		"confirm": "Remover"
+	},
 	"footer": {
 		"issueText": "Encontrou um problema com um item do marketplace ou tem sugestões para novos itens? <0>Abra um issue no GitHub</0> para nos avisar!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/ru/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Удалить из текущего проекта",
 			"removeGlobalTooltip": "Удалить из глобальной конфигурации",
 			"actionsMenuLabel": "Дополнительные действия"
-		}
+		},
+		"removeFailed": "Не удалось удалить элемент: {{error}}",
+		"unknownError": "Произошла неизвестная ошибка"
 	},
 	"install": {
 		"title": "Установить {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "Максимум {{max}} источников разрешено"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Удалить режим",
+			"message": "Вы уверены, что хотите удалить режим «{{modeName}}»?",
+			"rulesWarning": "Это также удалит все связанные файлы правил для этого режима."
+		},
+		"mcp": {
+			"title": "Удалить сервер MCP",
+			"message": "Вы уверены, что хотите удалить сервер MCP «{{mcpName}}»?"
+		},
+		"cancel": "Отмена",
+		"confirm": "Удалить"
+	},
 	"footer": {
 		"issueText": "Нашли проблему с элементом marketplace или есть предложения для новых элементов? <0>Откройте issue на GitHub</0>, чтобы сообщить нам!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/tr/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Mevcut projeden kaldır",
 			"removeGlobalTooltip": "Global yapılandırmadan kaldır",
 			"actionsMenuLabel": "Daha fazla eylem"
-		}
+		},
+		"removeFailed": "Öğe kaldırılamadı: {{error}}",
+		"unknownError": "Bilinmeyen bir hata oluştu"
 	},
 	"install": {
 		"title": "{{name}} Yükle",
@@ -125,6 +127,19 @@
 			"maxSources": "Maksimum {{max}} kaynak izin verilir"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Modu Kaldır",
+			"message": "\"{{modeName}}\" modunu kaldırmak istediğinizden emin misiniz?",
+			"rulesWarning": "Bu, bu mod için ilişkili tüm kural dosyalarını da kaldıracaktır."
+		},
+		"mcp": {
+			"title": "MCP Sunucusunu Kaldır",
+			"message": "\"{{mcpName}}\" MCP sunucusunu kaldırmak istediğinizden emin misiniz?"
+		},
+		"cancel": "İptal",
+		"confirm": "Kaldır"
+	},
 	"footer": {
 		"issueText": "Bir marketplace öğesi ile ilgili sorun bulduğun veya yeni öğeler için önerilerin var mı? <0>GitHub'da issue aç</0> ve bize bildir!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/vi/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "Xóa khỏi dự án hiện tại",
 			"removeGlobalTooltip": "Xóa khỏi cấu hình toàn cục",
 			"actionsMenuLabel": "Thêm hành động"
-		}
+		},
+		"removeFailed": "Không thể xóa mục: {{error}}",
+		"unknownError": "Đã xảy ra lỗi không xác định"
 	},
 	"install": {
 		"title": "Cài đặt {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "Tối đa {{max}} nguồn được phép"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "Xóa chế độ",
+			"message": "Bạn có chắc chắn muốn xóa chế độ \"{{modeName}}\" không?",
+			"rulesWarning": "Thao tác này cũng sẽ xóa mọi tệp quy tắc được liên kết cho chế độ này."
+		},
+		"mcp": {
+			"title": "Xóa máy chủ MCP",
+			"message": "Bạn có chắc chắn muốn xóa máy chủ MCP \"{{mcpName}}\" không?"
+		},
+		"cancel": "Hủy",
+		"confirm": "Xóa"
+	},
 	"footer": {
 		"issueText": "Bạn tìm thấy vấn đề với mục marketplace hoặc có đề xuất cho mục mới? <0>Mở issue GitHub</0> để cho chúng tôi biết!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/zh-CN/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "从当前项目中移除",
 			"removeGlobalTooltip": "从全局配置中移除",
 			"actionsMenuLabel": "更多操作"
-		}
+		},
+		"removeFailed": "删除失败: {{error}}",
+		"unknownError": "发生未知错误"
 	},
 	"install": {
 		"title": "安装 {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "最多允许 {{max}} 个源"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "删除模式",
+			"message": "您确定要删除\"{{modeName}}\"模式吗?",
+			"rulesWarning": "这也将删除此模式的任何关联规则文件。"
+		},
+		"mcp": {
+			"title": "删除 MCP 服务器",
+			"message": "您确定要删除 MCP 服务器\"{{mcpName}}\"吗?"
+		},
+		"cancel": "取消",
+		"confirm": "删除"
+	},
 	"footer": {
 		"issueText": "发现 marketplace 项目问题或有新项目建议?<0>在 GitHub 开启 issue</0> 告诉我们!"
 	}

+ 16 - 1
webview-ui/src/i18n/locales/zh-TW/marketplace.json

@@ -71,7 +71,9 @@
 			"removeProjectTooltip": "從目前專案中移除",
 			"removeGlobalTooltip": "從全域設定中移除",
 			"actionsMenuLabel": "更多動作"
-		}
+		},
+		"removeFailed": "移除物品失敗: {{error}}",
+		"unknownError": "發生未知錯誤"
 	},
 	"install": {
 		"title": "安裝 {{name}}",
@@ -125,6 +127,19 @@
 			"maxSources": "最多允許 {{max}} 個來源"
 		}
 	},
+	"removeConfirm": {
+		"mode": {
+			"title": "移除模式",
+			"message": "您確定要移除「{{modeName}}」模式嗎?",
+			"rulesWarning": "這也會移除此模式的任何相關規則檔案。"
+		},
+		"mcp": {
+			"title": "移除 MCP 伺服器",
+			"message": "您確定要移除 MCP 伺服器「{{mcpName}}」嗎?"
+		},
+		"cancel": "取消",
+		"confirm": "移除"
+	},
 	"footer": {
 		"issueText": "發現 marketplace 項目問題或有新項目建議?<0>在 GitHub 開啟 issue</0> 告訴我們!"
 	}