Skip to content
Merged
59 changes: 58 additions & 1 deletion src/core/config/CustomModesManager.ts
Original file line number Diff line number Diff line change
@@ -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"
Expand Down Expand Up @@ -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()
Expand All @@ -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) {
Expand All @@ -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()
Expand All @@ -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()
Expand Down
2 changes: 1 addition & 1 deletion src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
37 changes: 37 additions & 0 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/locales/ca/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/de/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/en/common.json
Original file line number Diff line number Diff line change
Expand Up @@ -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.",
Expand Down
8 changes: 7 additions & 1 deletion src/i18n/locales/es/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/fr/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/hi/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/id/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/it/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/ja/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/ko/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion src/i18n/locales/nl/common.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading