mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-09-08 22:21:23 +00:00
feat: implement workspace-specific index settings storage
- Add workspace storage methods to ContextProxy - Update CodeIndexConfigManager to use workspace storage instead of global - Add migration logic to move existing global settings to workspace - Update webview message handler to save settings per workspace - Add comprehensive tests for workspace-specific storage functionality Fixes #6406
This commit is contained in:
parent
fd5bdc7cb1
commit
6d414c3525
4 changed files with 131 additions and 15 deletions
|
|
@ -128,6 +128,22 @@ export class ContextProxy {
|
|||
return Object.fromEntries(GLOBAL_STATE_KEYS.map((key) => [key, this.getGlobalState(key)]))
|
||||
}
|
||||
|
||||
/**
|
||||
* ExtensionContext.workspaceState
|
||||
* https://code.visualstudio.com/api/references/vscode-api#ExtensionContext.workspaceState
|
||||
*/
|
||||
|
||||
getWorkspaceState<T = any>(key: string): T | undefined
|
||||
getWorkspaceState<T = any>(key: string, defaultValue: T): T
|
||||
getWorkspaceState<T = any>(key: string, defaultValue?: T): T | undefined {
|
||||
const value = this.originalContext.workspaceState.get<T>(key)
|
||||
return value !== undefined ? value : defaultValue
|
||||
}
|
||||
|
||||
updateWorkspaceState<T = any>(key: string, value: T) {
|
||||
return this.originalContext.workspaceState.update(key, value)
|
||||
}
|
||||
|
||||
/**
|
||||
* ExtensionContext.secrets
|
||||
* https://code.visualstudio.com/api/references/vscode-api#ExtensionContext.secrets
|
||||
|
|
|
|||
|
|
@ -1990,12 +1990,12 @@ export const webviewMessageHandler = async (
|
|||
|
||||
try {
|
||||
// Check if embedder provider has changed
|
||||
const currentConfig = getGlobalState("codebaseIndexConfig") || {}
|
||||
const currentConfig = provider.contextProxy.getWorkspaceState("codebaseIndexConfig") || {}
|
||||
const embedderProviderChanged =
|
||||
currentConfig.codebaseIndexEmbedderProvider !== settings.codebaseIndexEmbedderProvider
|
||||
|
||||
// Save global state settings atomically
|
||||
const globalStateConfig = {
|
||||
// Save workspace state settings atomically
|
||||
const workspaceStateConfig = {
|
||||
...currentConfig,
|
||||
codebaseIndexEnabled: settings.codebaseIndexEnabled,
|
||||
codebaseIndexQdrantUrl: settings.codebaseIndexQdrantUrl,
|
||||
|
|
@ -2008,8 +2008,8 @@ export const webviewMessageHandler = async (
|
|||
codebaseIndexSearchMinScore: settings.codebaseIndexSearchMinScore,
|
||||
}
|
||||
|
||||
// Save global state first
|
||||
await updateGlobalState("codebaseIndexConfig", globalStateConfig)
|
||||
// Save workspace state first
|
||||
await provider.contextProxy.updateWorkspaceState("codebaseIndexConfig", workspaceStateConfig)
|
||||
|
||||
// Save secrets directly using context proxy
|
||||
if (settings.codeIndexOpenAiKey !== undefined) {
|
||||
|
|
@ -2041,7 +2041,7 @@ export const webviewMessageHandler = async (
|
|||
await provider.postMessageToWebview({
|
||||
type: "codeIndexSettingsSaved",
|
||||
success: true,
|
||||
settings: globalStateConfig,
|
||||
settings: workspaceStateConfig,
|
||||
})
|
||||
|
||||
// Update webview state
|
||||
|
|
|
|||
|
|
@ -30,6 +30,8 @@ describe("CodeIndexConfigManager", () => {
|
|||
// Setup mock ContextProxy
|
||||
mockContextProxy = {
|
||||
getGlobalState: vi.fn(),
|
||||
getWorkspaceState: vi.fn(),
|
||||
updateWorkspaceState: vi.fn(),
|
||||
getSecret: vi.fn().mockReturnValue(undefined),
|
||||
refreshSecrets: vi.fn().mockResolvedValue(undefined),
|
||||
updateGlobalState: vi.fn(),
|
||||
|
|
@ -1811,4 +1813,89 @@ describe("CodeIndexConfigManager", () => {
|
|||
})
|
||||
})
|
||||
})
|
||||
|
||||
describe("workspace-specific storage", () => {
|
||||
it("should use workspace storage instead of global storage", async () => {
|
||||
const mockWorkspaceConfig = {
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://workspace-qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-large",
|
||||
}
|
||||
|
||||
mockContextProxy.getWorkspaceState.mockReturnValue(mockWorkspaceConfig)
|
||||
mockContextProxy.getGlobalState.mockReturnValue(undefined)
|
||||
setupSecretMocks({
|
||||
codeIndexOpenAiKey: "test-openai-key",
|
||||
codeIndexQdrantApiKey: "test-qdrant-key",
|
||||
})
|
||||
|
||||
const result = await configManager.loadConfiguration()
|
||||
|
||||
expect(mockContextProxy.getWorkspaceState).toHaveBeenCalledWith("codebaseIndexConfig")
|
||||
expect(result.currentConfig.qdrantUrl).toBe("http://workspace-qdrant.local")
|
||||
expect(result.currentConfig.modelId).toBe("text-embedding-3-large")
|
||||
})
|
||||
|
||||
it("should migrate global config to workspace storage when workspace config doesn't exist", async () => {
|
||||
const mockGlobalConfig = {
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://global-qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
}
|
||||
|
||||
mockContextProxy.getWorkspaceState.mockReturnValue(undefined)
|
||||
mockContextProxy.getGlobalState.mockReturnValue(mockGlobalConfig)
|
||||
setupSecretMocks({
|
||||
codeIndexOpenAiKey: "test-openai-key",
|
||||
})
|
||||
|
||||
const result = await configManager.loadConfiguration()
|
||||
|
||||
// Should have migrated global config to workspace
|
||||
expect(mockContextProxy.updateWorkspaceState).toHaveBeenCalledWith("codebaseIndexConfig", mockGlobalConfig)
|
||||
expect(result.currentConfig.qdrantUrl).toBe("http://global-qdrant.local")
|
||||
expect(result.currentConfig.modelId).toBe("text-embedding-3-small")
|
||||
})
|
||||
|
||||
it("should use default config when neither workspace nor global config exists", async () => {
|
||||
mockContextProxy.getWorkspaceState.mockReturnValue(undefined)
|
||||
mockContextProxy.getGlobalState.mockReturnValue(undefined)
|
||||
mockContextProxy.getSecret.mockReturnValue(undefined)
|
||||
|
||||
const result = await configManager.loadConfiguration()
|
||||
|
||||
expect(result.currentConfig.qdrantUrl).toBe("http://localhost:6333")
|
||||
expect(result.currentConfig.embedderProvider).toBe("openai")
|
||||
expect(result.currentConfig.isConfigured).toBe(false)
|
||||
})
|
||||
|
||||
it("should prefer workspace config over global config when both exist", async () => {
|
||||
const mockWorkspaceConfig = {
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://workspace-qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "ollama",
|
||||
codebaseIndexEmbedderBaseUrl: "http://workspace-ollama.local",
|
||||
}
|
||||
|
||||
const mockGlobalConfig = {
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://global-qdrant.local",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderModelId: "text-embedding-3-small",
|
||||
}
|
||||
|
||||
mockContextProxy.getWorkspaceState.mockReturnValue(mockWorkspaceConfig)
|
||||
mockContextProxy.getGlobalState.mockReturnValue(mockGlobalConfig)
|
||||
|
||||
const result = await configManager.loadConfiguration()
|
||||
|
||||
// Should use workspace config, not global
|
||||
expect(result.currentConfig.qdrantUrl).toBe("http://workspace-qdrant.local")
|
||||
expect(result.currentConfig.embedderProvider).toBe("ollama")
|
||||
// Should NOT have called updateWorkspaceState since workspace config already exists
|
||||
expect(mockContextProxy.updateWorkspaceState).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
})
|
||||
|
|
|
|||
|
|
@ -41,15 +41,28 @@ export class CodeIndexConfigManager {
|
|||
* This eliminates code duplication between initializeWithCurrentConfig() and loadConfiguration().
|
||||
*/
|
||||
private _loadAndSetConfiguration(): void {
|
||||
// Load configuration from storage
|
||||
const codebaseIndexConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig") ?? {
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://localhost:6333",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderBaseUrl: "",
|
||||
codebaseIndexEmbedderModelId: "",
|
||||
codebaseIndexSearchMinScore: undefined,
|
||||
codebaseIndexSearchMaxResults: undefined,
|
||||
// Load configuration from workspace storage first, then fall back to global storage for migration
|
||||
let codebaseIndexConfig = this.contextProxy?.getWorkspaceState("codebaseIndexConfig")
|
||||
|
||||
// If no workspace config exists, check for global config and migrate it
|
||||
if (!codebaseIndexConfig) {
|
||||
const globalConfig = this.contextProxy?.getGlobalState("codebaseIndexConfig")
|
||||
if (globalConfig) {
|
||||
// Migrate global config to workspace
|
||||
this.contextProxy?.updateWorkspaceState("codebaseIndexConfig", globalConfig)
|
||||
codebaseIndexConfig = globalConfig
|
||||
} else {
|
||||
// Use default configuration
|
||||
codebaseIndexConfig = {
|
||||
codebaseIndexEnabled: true,
|
||||
codebaseIndexQdrantUrl: "http://localhost:6333",
|
||||
codebaseIndexEmbedderProvider: "openai",
|
||||
codebaseIndexEmbedderBaseUrl: "",
|
||||
codebaseIndexEmbedderModelId: "",
|
||||
codebaseIndexSearchMinScore: undefined,
|
||||
codebaseIndexSearchMaxResults: undefined,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const {
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue