diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 9abddc6d96..c376a3dfa0 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -139,6 +139,11 @@ export class ClineProvider private pendingOperations: Map = new Map() private static readonly PENDING_OPERATION_TIMEOUT_MS = 30000 // 30 seconds + // Tab-specific state for mode and model + private tabSpecificMode?: Mode + private tabSpecificApiConfigName?: string + private readonly instanceId: string = Math.random().toString(36).substring(7) + public isViewLaunched = false public settingsImportedAt?: number public readonly latestAnnouncementId = "sep-2025-roo-code-cloud" // Roo Code Cloud announcement @@ -1161,7 +1166,8 @@ export class ClineProvider } } - await this.updateGlobalState("mode", newMode) + // Update tab-specific mode instead of global state + this.tabSpecificMode = newMode this.emit(RooCodeEventName.ModeChanged, newMode) @@ -1177,11 +1183,11 @@ export class ClineProvider const profile = listApiConfig.find(({ id }) => id === savedConfigId) if (profile?.name) { - await this.activateProviderProfile({ name: profile.name }) + await this.activateProviderProfileForTab({ name: profile.name }) } } else { // If no saved config for this mode, save current config as default. - const currentApiConfigName = this.getGlobalState("currentApiConfigName") + const currentApiConfigName = this.tabSpecificApiConfigName || this.getGlobalState("currentApiConfigName") if (currentApiConfigName) { const config = listApiConfig.find((c) => c.name === currentApiConfigName) @@ -1318,6 +1324,39 @@ export class ClineProvider } } + // New method for tab-specific provider profile activation + async activateProviderProfileForTab(args: { name: string } | { id: string }) { + const { name, id, ...providerSettings } = await this.providerSettingsManager.activateProfile(args) + + // Update tab-specific API config name + this.tabSpecificApiConfigName = name + + // Update list metadata globally (this is fine to share) + await this.contextProxy.setValue("listApiConfigMeta", await this.providerSettingsManager.listConfig()) + + // Store provider settings in context for this tab's use + // Note: We don't update global currentApiConfigName to avoid affecting other tabs + + const mode = this.tabSpecificMode || this.getGlobalState("mode") || defaultModeSlug + + if (id) { + await this.providerSettingsManager.setModeConfig(mode, id) + } + + // Change the provider for the current task. + const task = this.getCurrentTask() + + if (task) { + task.api = buildApiHandler(providerSettings) + } + + await this.postStateToWebview() + + if (providerSettings.apiProvider) { + this.emit(RooCodeEventName.ProviderProfileChanged, { name, provider: providerSettings.apiProvider }) + } + } + async updateCustomInstructions(instructions?: string) { // User may be clearing the field. await this.updateGlobalState("customInstructions", instructions || undefined) @@ -1855,10 +1894,10 @@ export class ClineProvider enableMcpServerCreation: enableMcpServerCreation ?? true, alwaysApproveResubmit: alwaysApproveResubmit ?? false, requestDelaySeconds: requestDelaySeconds ?? 10, - currentApiConfigName: currentApiConfigName ?? "default", + currentApiConfigName: this.tabSpecificApiConfigName ?? currentApiConfigName ?? "default", listApiConfigMeta: listApiConfigMeta ?? [], pinnedApiConfigs: pinnedApiConfigs ?? {}, - mode: mode ?? defaultModeSlug, + mode: this.tabSpecificMode ?? mode ?? defaultModeSlug, customModePrompts: customModePrompts ?? {}, customSupportPrompts: customSupportPrompts ?? {}, enhancementApiConfigId, @@ -2070,13 +2109,13 @@ export class ClineProvider terminalZshP10k: stateValues.terminalZshP10k ?? false, terminalZdotdir: stateValues.terminalZdotdir ?? false, terminalCompressProgressBar: stateValues.terminalCompressProgressBar ?? true, - mode: stateValues.mode ?? defaultModeSlug, + mode: this.tabSpecificMode ?? stateValues.mode ?? defaultModeSlug, language: stateValues.language ?? formatLanguage(vscode.env.language), mcpEnabled: stateValues.mcpEnabled ?? true, enableMcpServerCreation: stateValues.enableMcpServerCreation ?? true, alwaysApproveResubmit: stateValues.alwaysApproveResubmit ?? false, requestDelaySeconds: Math.max(5, stateValues.requestDelaySeconds ?? 10), - currentApiConfigName: stateValues.currentApiConfigName ?? "default", + currentApiConfigName: this.tabSpecificApiConfigName ?? stateValues.currentApiConfigName ?? "default", listApiConfigMeta: stateValues.listApiConfigMeta ?? [], pinnedApiConfigs: stateValues.pinnedApiConfigs ?? {}, modeApiConfigs: stateValues.modeApiConfigs ?? ({} as Record), diff --git a/src/core/webview/webviewMessageHandler.ts b/src/core/webview/webviewMessageHandler.ts index abdfae29fa..a161973bf8 100644 --- a/src/core/webview/webviewMessageHandler.ts +++ b/src/core/webview/webviewMessageHandler.ts @@ -1848,7 +1848,12 @@ export const webviewMessageHandler = async ( case "loadApiConfiguration": if (message.text) { try { - await provider.activateProviderProfile({ name: message.text }) + // Use tab-specific activation method if available + if (typeof provider.activateProviderProfileForTab === "function") { + await provider.activateProviderProfileForTab({ name: message.text }) + } else { + await provider.activateProviderProfile({ name: message.text }) + } } catch (error) { provider.log( `Error load api configuration: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`, @@ -1860,7 +1865,12 @@ export const webviewMessageHandler = async ( case "loadApiConfigurationById": if (message.text) { try { - await provider.activateProviderProfile({ id: message.text }) + // Use tab-specific activation method if available + if (typeof provider.activateProviderProfileForTab === "function") { + await provider.activateProviderProfileForTab({ id: message.text }) + } else { + await provider.activateProviderProfile({ id: message.text }) + } } catch (error) { provider.log( `Error load api configuration by ID: ${JSON.stringify(error, Object.getOwnPropertyNames(error), 2)}`,