diff --git a/src/core/config/ContextProxy.ts b/src/core/config/ContextProxy.ts index aa40477ad8..dd181d66f0 100644 --- a/src/core/config/ContextProxy.ts +++ b/src/core/config/ContextProxy.ts @@ -229,6 +229,10 @@ export class ContextProxy { public async export(): Promise { try { const globalSettings = globalSettingsExportSchema.parse(this.getValues()) + + // Exports should only contain global settings, so this skips project custom modes (those exist in the .roomode folder) + globalSettings.customModes = globalSettings.customModes?.filter((mode) => mode.source === "global") + return Object.fromEntries(Object.entries(globalSettings).filter(([_, value]) => value !== undefined)) } catch (error) { if (error instanceof ZodError) { diff --git a/src/core/config/__tests__/importExport.test.ts b/src/core/config/__tests__/importExport.test.ts index 038bf2ad80..8330187b3b 100644 --- a/src/core/config/__tests__/importExport.test.ts +++ b/src/core/config/__tests__/importExport.test.ts @@ -9,6 +9,7 @@ import { ProviderName } from "../../../schemas" import { importSettings, exportSettings } from "../importExport" import { ProviderSettingsManager } from "../ProviderSettingsManager" import { ContextProxy } from "../ContextProxy" +import { CustomModesManager } from "../CustomModesManager" // Mock VSCode modules jest.mock("vscode", () => ({ @@ -37,6 +38,7 @@ describe("importExport", () => { let mockProviderSettingsManager: jest.Mocked let mockContextProxy: jest.Mocked let mockExtensionContext: jest.Mocked + let mockCustomModesManager: jest.Mocked beforeEach(() => { // Reset all mocks @@ -56,6 +58,11 @@ describe("importExport", () => { export: jest.fn().mockImplementation(() => Promise.resolve({})), } as unknown as jest.Mocked + // Setup customModesManager mock + mockCustomModesManager = { + updateCustomMode: jest.fn(), + } as unknown as jest.Mocked + const map = new Map() mockExtensionContext = { @@ -74,6 +81,7 @@ describe("importExport", () => { const result = await importSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, + customModesManager: mockCustomModesManager, }) expect(result).toEqual({ success: false }) @@ -138,6 +146,7 @@ describe("importExport", () => { const result = await importSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, + customModesManager: mockCustomModesManager, }) expect(result.success).toBe(true) @@ -181,6 +190,7 @@ describe("importExport", () => { const result = await importSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, + customModesManager: mockCustomModesManager, }) expect(result).toEqual({ success: false }) @@ -202,6 +212,7 @@ describe("importExport", () => { const result = await importSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, + customModesManager: mockCustomModesManager, }) expect(result).toEqual({ success: false }) @@ -220,6 +231,7 @@ describe("importExport", () => { const result = await importSettings({ providerSettingsManager: mockProviderSettingsManager, contextProxy: mockContextProxy, + customModesManager: mockCustomModesManager, }) expect(result).toEqual({ success: false }) @@ -252,6 +264,7 @@ describe("importExport", () => { const result = await importSettings({ providerSettingsManager, contextProxy: mockContextProxy, + customModesManager: mockCustomModesManager, }) expect(result.success).toBe(true) @@ -261,6 +274,50 @@ describe("importExport", () => { }) }) + it("should call updateCustomMode for each custom mode in config", async () => { + ;(vscode.window.showOpenDialog as jest.Mock).mockResolvedValue([{ fsPath: "/mock/path/settings.json" }]) + const customModes = [ + { + slug: "mode1", + name: "Mode One", + roleDefinition: "Custom role one", + groups: [], + }, + { + slug: "mode2", + name: "Mode Two", + roleDefinition: "Custom role two", + groups: [], + }, + ] + const mockFileContent = JSON.stringify({ + providerProfiles: { + currentApiConfigName: "test", + apiConfigs: {}, + }, + globalSettings: { + mode: "code", + customModes, + }, + }) + ;(fs.readFile as jest.Mock).mockResolvedValue(mockFileContent) + mockProviderSettingsManager.export.mockResolvedValue({ + currentApiConfigName: "test", + apiConfigs: {}, + }) + mockProviderSettingsManager.listConfig.mockResolvedValue([]) + const result = await importSettings({ + providerSettingsManager: mockProviderSettingsManager, + contextProxy: mockContextProxy, + customModesManager: mockCustomModesManager, + }) + expect(result.success).toBe(true) + expect(mockCustomModesManager.updateCustomMode).toHaveBeenCalledTimes(customModes.length) + customModes.forEach((mode) => { + expect(mockCustomModesManager.updateCustomMode).toHaveBeenCalledWith(mode.slug, mode) + }) + }) + describe("exportSettings", () => { it("should not export settings when user cancels file selection", async () => { // Mock user canceling file selection diff --git a/src/core/config/importExport.ts b/src/core/config/importExport.ts index f8059160a2..e16314cb6c 100644 --- a/src/core/config/importExport.ts +++ b/src/core/config/importExport.ts @@ -8,13 +8,20 @@ import { z } from "zod" import { globalSettingsSchema } from "../../schemas" import { ProviderSettingsManager, providerProfilesSchema } from "./ProviderSettingsManager" import { ContextProxy } from "./ContextProxy" +import { CustomModesManager } from "./CustomModesManager" -type ImportExportOptions = { +type ImportOptions = { providerSettingsManager: ProviderSettingsManager contextProxy: ContextProxy + customModesManager: CustomModesManager } -export const importSettings = async ({ providerSettingsManager, contextProxy }: ImportExportOptions) => { +type ExportOptions = { + providerSettingsManager: ProviderSettingsManager + contextProxy: ContextProxy +} + +export const importSettings = async ({ providerSettingsManager, contextProxy, customModesManager }: ImportOptions) => { const uris = await vscode.window.showOpenDialog({ filters: { JSON: ["json"] }, canSelectMany: false, @@ -31,7 +38,6 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }: try { const previousProviderProfiles = await providerSettingsManager.export() - const { providerProfiles: newProviderProfiles, globalSettings } = schema.parse( JSON.parse(await fs.readFile(uris[0].fsPath, "utf-8")), ) @@ -48,6 +54,10 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }: }, } + await Promise.all( + (globalSettings.customModes ?? []).map((mode) => customModesManager.updateCustomMode(mode.slug, mode)), + ) + await providerSettingsManager.import(newProviderProfiles) await contextProxy.setValues(globalSettings) @@ -60,7 +70,7 @@ export const importSettings = async ({ providerSettingsManager, contextProxy }: } } -export const exportSettings = async ({ providerSettingsManager, contextProxy }: ImportExportOptions) => { +export const exportSettings = async ({ providerSettingsManager, contextProxy }: ExportOptions) => { const uri = await vscode.window.showSaveDialog({ filters: { JSON: ["json"] }, defaultUri: vscode.Uri.file(path.join(os.homedir(), "Documents", "roo-code-settings.json")), diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index b542fdb166..1ce9191e7c 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -364,6 +364,7 @@ export const webviewMessageHandler = async (provider: ClineProvider, message: We const { success } = await importSettings({ providerSettingsManager: provider.providerSettingsManager, contextProxy: provider.contextProxy, + customModesManager: provider.customModesManager, }) if (success) {