feat: add optional persistent default mode setting

- Add setAsDefault parameter to handleModeSwitch() method
- Add setAsDefaultMode() method to update global state
- Add setDefaultMode message type and handler
- Update tests to use new setAsDefault parameter
- Maintain backward compatibility with existing behavior
- Address concern about removing persistent default mode functionality

This allows users to optionally set a mode as the global default
while maintaining multi-window mode isolation.
This commit is contained in:
Roo Code 2025-07-18 17:34:56 +00:00
parent 48d24f0446
commit e3fb12a8b3
5 changed files with 128 additions and 17 deletions

View file

@ -0,0 +1,9 @@
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
var nextConfig = {
webpack: function (config) {
config.resolve.extensionAlias = { ".js": [".ts", ".tsx", ".js", ".jsx"] };
return config;
},
};
exports.default = nextConfig;

View file

@ -802,8 +802,9 @@ export class ClineProvider
/**
* Handle switching to a new mode, including updating the associated API configuration
* @param newMode The mode to switch to
* @param setAsDefault Whether to set this mode as the global default for new windows
*/
public async handleModeSwitch(newMode: Mode) {
public async handleModeSwitch(newMode: Mode, setAsDefault: boolean = false) {
const cline = this.getCurrentCline()
if (cline) {
@ -814,6 +815,11 @@ export class ClineProvider
// Store mode in instance-specific storage to prevent cross-window interference
this.instanceMode = newMode
// If user wants to set this as the default mode, update global state
if (setAsDefault) {
await this.setAsDefaultMode(newMode)
}
// Load the saved API config for the new mode if it exists
const savedConfigId = await this.providerSettingsManager.getModeConfigId(newMode)
const listApiConfig = await this.providerSettingsManager.listConfig()
@ -844,6 +850,14 @@ export class ClineProvider
await this.postStateToWebview()
}
/**
* Set a mode as the global default for new windows
* @param mode The mode to set as default
*/
public async setAsDefaultMode(mode: Mode) {
await this.updateGlobalState("mode", mode)
}
// Provider Profile Management
getProviderProfileEntries(): ProviderSettingsEntry[] {
@ -1416,6 +1430,7 @@ export class ClineProvider
profileThresholds,
alwaysAllowFollowupQuestions,
followupAutoApproveTimeoutMs,
modeApiConfigs,
} = await this.getState()
const telemetryKey = process.env.POSTHOG_API_KEY
@ -1529,6 +1544,7 @@ export class ClineProvider
hasOpenedModeSelector: this.getGlobalState("hasOpenedModeSelector") ?? false,
alwaysAllowFollowupQuestions: alwaysAllowFollowupQuestions ?? false,
followupAutoApproveTimeoutMs: followupAutoApproveTimeoutMs ?? 60000,
modeApiConfigs: modeApiConfigs ?? {},
}
}

View file

@ -240,8 +240,8 @@ describe("ClineProvider - Multi-Window Mode Isolation", () => {
expect(initialState1.mode).toBe("code")
expect(initialState2.mode).toBe("code")
// Switch mode in provider1 to architect
await provider1.handleModeSwitch("architect")
// Switch mode in provider1 to architect (without setting as default)
await provider1.handleModeSwitch("architect", false)
// Check states after mode switch
const afterSwitchState1 = await provider1.getState()
@ -257,11 +257,11 @@ describe("ClineProvider - Multi-Window Mode Isolation", () => {
await provider1.resolveWebviewView(mockWebviewView1)
await provider2.resolveWebviewView(mockWebviewView2)
// Switch provider1 to architect mode
await provider1.handleModeSwitch("architect")
// Switch provider1 to architect mode (without setting as default)
await provider1.handleModeSwitch("architect", false)
// Switch provider2 to ask mode
await provider2.handleModeSwitch("ask")
// Switch provider2 to ask mode (without setting as default)
await provider2.handleModeSwitch("ask", false)
// Check final states
const finalState1 = await provider1.getState()
@ -273,8 +273,8 @@ describe("ClineProvider - Multi-Window Mode Isolation", () => {
})
test("new provider instances initialize with global mode, not instance-specific modes", async () => {
// Switch provider1 to architect mode
await provider1.handleModeSwitch("architect")
// Switch provider1 to architect mode (without setting as default)
await provider1.handleModeSwitch("architect", false)
// Create a new provider instance (simulating opening a new window)
const contextProxy3 = new ContextProxy(mockContext1)
@ -301,9 +301,9 @@ describe("ClineProvider - Multi-Window Mode Isolation", () => {
await provider1.resolveWebviewView(mockWebviewView1)
await provider2.resolveWebviewView(mockWebviewView2)
// Switch modes in both providers
await provider1.handleModeSwitch("architect")
await provider2.handleModeSwitch("ask")
// Switch modes in both providers (without setting as default)
await provider1.handleModeSwitch("architect", false)
await provider2.handleModeSwitch("ask", false)
// Get webview states
const webviewState1 = await provider1.getStateToPostToWebview()
@ -348,8 +348,8 @@ describe("ClineProvider - Multi-Window Mode Isolation", () => {
test("instance mode persists across state retrievals", async () => {
await provider1.resolveWebviewView(mockWebviewView1)
// Switch to architect mode
await provider1.handleModeSwitch("architect")
// Switch to architect mode (without setting as default)
await provider1.handleModeSwitch("architect", false)
// Get state multiple times
const state1 = await provider1.getState()
@ -369,8 +369,8 @@ describe("ClineProvider - Multi-Window Mode Isolation", () => {
const initialGlobalMode = mockContext1.globalState.get("mode")
expect(initialGlobalMode).toBe("code")
// Switch instance mode
await provider1.handleModeSwitch("architect")
// Switch instance mode (without setting as default)
await provider1.handleModeSwitch("architect", false)
// Global mode should remain unchanged
const finalGlobalMode = mockContext1.globalState.get("mode")
@ -413,4 +413,83 @@ describe("ClineProvider - Multi-Window Mode Isolation", () => {
// Should fallback to default mode
expect(state.mode).toBe(defaultModeSlug)
})
test("setting mode as default updates global state", async () => {
await provider1.resolveWebviewView(mockWebviewView1)
// Get initial global mode
const initialGlobalMode = mockContext1.globalState.get("mode")
expect(initialGlobalMode).toBe("code")
// Switch mode and set as default
await provider1.handleModeSwitch("architect", true)
// Global mode should be updated
const finalGlobalMode = mockContext1.globalState.get("mode")
expect(finalGlobalMode).toBe("architect")
// Instance should also have architect mode
const instanceState = await provider1.getState()
expect(instanceState.mode).toBe("architect")
})
test("new windows initialize with updated default mode", async () => {
await provider1.resolveWebviewView(mockWebviewView1)
// Switch mode and set as default in provider1
await provider1.handleModeSwitch("architect", true)
// Create a new provider instance (simulating opening a new window)
const contextProxy3 = new ContextProxy(mockContext1)
await contextProxy3.initialize()
const provider3 = new ClineProvider(mockContext1, mockOutputChannel, "sidebar", contextProxy3)
provider3.getMcpHub = vi.fn().mockReturnValue({
listTools: vi.fn().mockResolvedValue([]),
callTool: vi.fn().mockResolvedValue({ content: [] }),
listResources: vi.fn().mockResolvedValue([]),
readResource: vi.fn().mockResolvedValue({ contents: [] }),
getAllServers: vi.fn().mockReturnValue([]),
})
// New provider should initialize with the new default mode (architect)
const newProviderState = await provider3.getState()
expect(newProviderState.mode).toBe("architect")
})
test("setDefaultMode message handler updates global state", async () => {
await provider1.resolveWebviewView(mockWebviewView1)
// Get initial global mode
const initialGlobalMode = mockContext1.globalState.get("mode")
expect(initialGlobalMode).toBe("code")
// Get message handler
const messageHandler1 = (mockWebviewView1.webview.onDidReceiveMessage as any).mock.calls[0][0]
// Send setDefaultMode message
await messageHandler1({ type: "setDefaultMode", mode: "architect" })
// Global mode should be updated
const finalGlobalMode = mockContext1.globalState.get("mode")
expect(finalGlobalMode).toBe("architect")
})
test("mode switch without setAsDefault preserves global state", async () => {
await provider1.resolveWebviewView(mockWebviewView1)
// Get initial global mode
const initialGlobalMode = mockContext1.globalState.get("mode")
expect(initialGlobalMode).toBe("code")
// Switch mode without setting as default
await provider1.handleModeSwitch("architect", false)
// Global mode should remain unchanged
const finalGlobalMode = mockContext1.globalState.get("mode")
expect(finalGlobalMode).toBe("code")
// But instance should have architect mode
const instanceState = await provider1.getState()
expect(instanceState.mode).toBe("architect")
})
})

View file

@ -1133,7 +1133,13 @@ export const webviewMessageHandler = async (
}
break
case "mode":
await provider.handleModeSwitch(message.text as Mode)
await provider.handleModeSwitch(message.text as Mode, message.bool)
break
case "setDefaultMode":
if (message.text) {
await provider.setAsDefaultMode(message.text as Mode)
await provider.postStateToWebview()
}
break
case "updateSupportPrompt":
try {

View file

@ -128,6 +128,7 @@ export interface WebviewMessage {
| "requestDelaySeconds"
| "setApiConfigPassword"
| "mode"
| "setDefaultMode"
| "updatePrompt"
| "updateSupportPrompt"
| "getSystemPrompt"