diff --git a/.changeset/stupid-parrots-grin.md b/.changeset/stupid-parrots-grin.md new file mode 100644 index 00000000000..e640c2f3383 --- /dev/null +++ b/.changeset/stupid-parrots-grin.md @@ -0,0 +1,5 @@ +--- +"roo-cline": patch +--- + +Fix bug where the saved API provider for a mode wasn't being selected after a mode switch command diff --git a/src/core/Cline.ts b/src/core/Cline.ts index bd3acf2d3bd..b5deecc4635 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -2065,11 +2065,10 @@ export class Cline { break } - // Switch the mode + // Switch the mode using shared handler const provider = this.providerRef.deref() if (provider) { - await provider.updateGlobalState("mode", mode_slug) - await provider.postStateToWebview() + await provider.handleModeSwitch(mode_slug) } pushToolResult( `Successfully switched from ${getModeBySlug(currentMode)?.name ?? currentMode} mode to ${ diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 8535431cc98..4cd34b87667 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -781,38 +781,7 @@ export class ClineProvider implements vscode.WebviewViewProvider { await this.postStateToWebview() break case "mode": - const newMode = message.text as Mode - await this.updateGlobalState("mode", newMode) - - // Load the saved API config for the new mode if it exists - const savedConfigId = await this.configManager.getModeConfigId(newMode) - const listApiConfig = await this.configManager.listConfig() - - // Update listApiConfigMeta first to ensure UI has latest data - await this.updateGlobalState("listApiConfigMeta", listApiConfig) - - // If this mode has a saved config, use it - if (savedConfigId) { - const config = listApiConfig?.find((c) => c.id === savedConfigId) - if (config?.name) { - const apiConfig = await this.configManager.loadConfig(config.name) - await Promise.all([ - this.updateGlobalState("currentApiConfigName", config.name), - this.updateApiConfiguration(apiConfig), - ]) - } - } else { - // If no saved config for this mode, save current config as default - const currentApiConfigName = await this.getGlobalState("currentApiConfigName") - if (currentApiConfigName) { - const config = listApiConfig?.find((c) => c.name === currentApiConfigName) - if (config?.id) { - await this.configManager.setModeConfig(newMode, config.id) - } - } - } - - await this.postStateToWebview() + await this.handleModeSwitch(message.text as Mode) break case "updateSupportPrompt": try { @@ -1241,6 +1210,44 @@ export class ClineProvider implements vscode.WebviewViewProvider { ) } + /** + * Handle switching to a new mode, including updating the associated API configuration + * @param newMode The mode to switch to + */ + public async handleModeSwitch(newMode: Mode) { + await this.updateGlobalState("mode", newMode) + + // Load the saved API config for the new mode if it exists + const savedConfigId = await this.configManager.getModeConfigId(newMode) + const listApiConfig = await this.configManager.listConfig() + + // Update listApiConfigMeta first to ensure UI has latest data + await this.updateGlobalState("listApiConfigMeta", listApiConfig) + + // If this mode has a saved config, use it + if (savedConfigId) { + const config = listApiConfig?.find((c) => c.id === savedConfigId) + if (config?.name) { + const apiConfig = await this.configManager.loadConfig(config.name) + await Promise.all([ + this.updateGlobalState("currentApiConfigName", config.name), + this.updateApiConfiguration(apiConfig), + ]) + } + } else { + // If no saved config for this mode, save current config as default + const currentApiConfigName = await this.getGlobalState("currentApiConfigName") + if (currentApiConfigName) { + const config = listApiConfig?.find((c) => c.name === currentApiConfigName) + if (config?.id) { + await this.configManager.setModeConfig(newMode, config.id) + } + } + } + + await this.postStateToWebview() + } + private async updateApiConfiguration(apiConfiguration: ApiConfiguration) { // Update mode's default config const { mode } = await this.getState() diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index ee1b1718034..09b3db312d8 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -1100,6 +1100,68 @@ describe("ClineProvider", () => { }) }) + describe("handleModeSwitch", () => { + beforeEach(() => { + // Set up webview for each test + provider.resolveWebviewView(mockWebviewView) + }) + + test("loads saved API config when switching modes", async () => { + // Mock ConfigManager methods + provider.configManager = { + getModeConfigId: jest.fn().mockResolvedValue("saved-config-id"), + listConfig: jest + .fn() + .mockResolvedValue([{ name: "saved-config", id: "saved-config-id", apiProvider: "anthropic" }]), + loadConfig: jest.fn().mockResolvedValue({ apiProvider: "anthropic" }), + setModeConfig: jest.fn(), + } as any + + // Switch to architect mode + await provider.handleModeSwitch("architect") + + // Verify mode was updated + expect(mockContext.globalState.update).toHaveBeenCalledWith("mode", "architect") + + // Verify saved config was loaded + expect(provider.configManager.getModeConfigId).toHaveBeenCalledWith("architect") + expect(provider.configManager.loadConfig).toHaveBeenCalledWith("saved-config") + expect(mockContext.globalState.update).toHaveBeenCalledWith("currentApiConfigName", "saved-config") + + // Verify state was posted to webview + expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({ type: "state" })) + }) + + test("saves current config when switching to mode without config", async () => { + // Mock ConfigManager methods + provider.configManager = { + getModeConfigId: jest.fn().mockResolvedValue(undefined), + listConfig: jest + .fn() + .mockResolvedValue([{ name: "current-config", id: "current-id", apiProvider: "anthropic" }]), + setModeConfig: jest.fn(), + } as any + + // Mock current config name + mockContext.globalState.get = jest.fn((key: string) => { + if (key === "currentApiConfigName") return "current-config" + return undefined + }) + + // Switch to architect mode + await provider.handleModeSwitch("architect") + + // Verify mode was updated + expect(mockContext.globalState.update).toHaveBeenCalledWith("mode", "architect") + + // Verify current config was saved as default for new mode + expect(provider.configManager.setModeConfig).toHaveBeenCalledWith("architect", "current-id") + + // Verify state was posted to webview + expect(mockPostMessage).toHaveBeenCalledWith(expect.objectContaining({ type: "state" })) + }) + }) + describe("updateCustomMode", () => { test("updates both file and state when updating custom mode", async () => { provider.resolveWebviewView(mockWebviewView)