From b818f6b8386cf11cafdd75081d6703808bd77d44 Mon Sep 17 00:00:00 2001 From: iainRedro Date: Mon, 10 Mar 2025 20:27:08 +0000 Subject: [PATCH 1/3] Fix mode state sharing when switching between open vscode windows --- src/core/__tests__/contextProxy.test.ts | 138 ++++++++++++++++++- src/core/contextProxy.ts | 171 ++++++++++++++++++++++-- 2 files changed, 296 insertions(+), 13 deletions(-) diff --git a/src/core/__tests__/contextProxy.test.ts b/src/core/__tests__/contextProxy.test.ts index e44f3e45b3c..ac317e15bbe 100644 --- a/src/core/__tests__/contextProxy.test.ts +++ b/src/core/__tests__/contextProxy.test.ts @@ -1,5 +1,5 @@ import * as vscode from "vscode" -import { ContextProxy } from "../contextProxy" +import { ContextProxy, WINDOW_SPECIFIC_KEYS } from "../contextProxy" import { logger } from "../../utils/logging" import { GLOBAL_STATE_KEYS, SECRET_KEYS } from "../../shared/globalState" @@ -19,6 +19,14 @@ jest.mock("vscode", () => ({ Production: 2, Test: 3, }, + env: { + sessionId: "test-session-id", + machineId: "test-machine-id" + }, + workspace: { + name: "test-workspace-name", + workspaceFolders: [{ uri: { toString: () => "test-workspace" } }], + }, })) describe("ContextProxy", () => { @@ -75,7 +83,14 @@ describe("ContextProxy", () => { it("should initialize state cache with all global state keys", () => { expect(mockGlobalState.get).toHaveBeenCalledTimes(GLOBAL_STATE_KEYS.length) for (const key of GLOBAL_STATE_KEYS) { - expect(mockGlobalState.get).toHaveBeenCalledWith(key) + if (WINDOW_SPECIFIC_KEYS.includes(key as any)) { + // For window-specific keys like 'mode', the key is prefixed + expect(mockGlobalState.get).toHaveBeenCalledWith( + expect.stringMatching(new RegExp(`^window:.+:${key}$`)), + ) + } else { + expect(mockGlobalState.get).toHaveBeenCalledWith(key) + } } }) @@ -290,7 +305,15 @@ describe("ContextProxy", () => { // Should have called update with undefined for each key for (const key of GLOBAL_STATE_KEYS) { - expect(mockGlobalState.update).toHaveBeenCalledWith(key, undefined) + if (WINDOW_SPECIFIC_KEYS.includes(key as any)) { + // For window-specific keys like 'mode', the key is prefixed + expect(mockGlobalState.update).toHaveBeenCalledWith( + expect.stringMatching(new RegExp(`^window:.+:${key}$`)), + undefined, + ) + } else { + expect(mockGlobalState.update).toHaveBeenCalledWith(key, undefined) + } } // Total calls should include initial setup + reset operations @@ -328,4 +351,113 @@ describe("ContextProxy", () => { expect(initSecretCache).toHaveBeenCalledTimes(1) }) }) + + describe("Window-specific state", () => { + it("should use window-specific key for mode", async () => { + // Ensure 'mode' is in window specific keys + expect(WINDOW_SPECIFIC_KEYS).toContain("mode") + + // Test update method with 'mode' key + await proxy.updateGlobalState("mode", "debug") + + // Verify it's called with window-specific key + expect(mockGlobalState.update).toHaveBeenCalledWith(expect.stringMatching(/^window:.+:mode$/), "debug") + }) + + it("should use regular key for non-window-specific state", async () => { + // Test update method with a regular key + await proxy.updateGlobalState("apiProvider", "test-provider") + + // Verify it's called with regular key + expect(mockGlobalState.update).toHaveBeenCalledWith("apiProvider", "test-provider") + }) + + it("should consistently use same key format for get/update operations", async () => { + // Set mock values for testing + const windowKeyPattern = /^window:.+:mode$/ + mockGlobalState.get.mockImplementation((key: string) => { + if (windowKeyPattern.test(key)) return "window-debug-mode" + if (key === "mode") return "global-debug-mode" + return undefined + }) + + // Update a window-specific value + await proxy.updateGlobalState("mode", "test-mode") + + // The key used in update should match pattern + const updateCallArg = mockGlobalState.update.mock.calls[0][0] + expect(updateCallArg).toMatch(windowKeyPattern) + + // Re-init to load values + proxy["initializeStateCache"]() + + // Verify we get the window-specific value back + const value = proxy.getGlobalState("mode") + + // We should get the window-specific value, not the global one + expect(mockGlobalState.get).toHaveBeenCalledWith(expect.stringMatching(windowKeyPattern)) + expect(value).not.toBe("global-debug-mode") + }) + }) + + describe("Enhanced window ID generation", () => { + it("should generate a window ID that includes workspace name", () => { + // Access the private method using type assertion + const generateWindowId = (proxy as any).generateWindowId.bind(proxy); + const windowId = generateWindowId(); + + // Should include the workspace name from our mock + expect(windowId).toContain("test-workspace-name"); + }); + + it("should generate a window ID that includes machine ID", () => { + // Access the private method using type assertion + const generateWindowId = (proxy as any).generateWindowId.bind(proxy); + const windowId = generateWindowId(); + + // Should include the machine ID from our mock + expect(windowId).toContain("test-machine-id"); + }); + + it("should use the fallback mechanism if generateWindowId fails", () => { + // Create a proxy instance with a failing generateWindowId method + const spyOnGenerate = jest.spyOn(ContextProxy.prototype as any, "generateWindowId") + .mockImplementation(() => ""); + + // Create a new proxy to trigger the constructor with our mock + const testProxy = new ContextProxy(mockContext); + + // Should have called ensureUniqueWindowId with a fallback + expect(spyOnGenerate).toHaveBeenCalled(); + + // The windowId should use the fallback format (random ID) + // We can't test the exact value, but we can verify it's not empty + expect((testProxy as any).windowId).not.toBe(""); + + // Restore original implementation + spyOnGenerate.mockRestore(); + }); + + it("should create consistent session hash for same input", () => { + // Access the private method using type assertion + const createSessionHash = (proxy as any).createSessionHash.bind(proxy); + + const hash1 = createSessionHash("test-input"); + const hash2 = createSessionHash("test-input"); + + // Same input should produce same hash within the same session + expect(hash1).toBe(hash2); + }); + + it("should create different session hashes for different inputs", () => { + // Access the private method using type assertion + const createSessionHash = (proxy as any).createSessionHash.bind(proxy); + + const hash1 = createSessionHash("test-input-1"); + const hash2 = createSessionHash("test-input-2"); + + // Different inputs should produce different hashes + expect(hash1).not.toBe(hash2); + }); + }); }) diff --git a/src/core/contextProxy.ts b/src/core/contextProxy.ts index 52197d99f3a..3cf1068331d 100644 --- a/src/core/contextProxy.ts +++ b/src/core/contextProxy.ts @@ -2,16 +2,26 @@ import * as vscode from "vscode" import { logger } from "../utils/logging" import { GLOBAL_STATE_KEYS, SECRET_KEYS } from "../shared/globalState" +// Keys that should be stored per-window rather than globally +export const WINDOW_SPECIFIC_KEYS = ["mode"] as const +export type WindowSpecificKey = (typeof WINDOW_SPECIFIC_KEYS)[number] + export class ContextProxy { private readonly originalContext: vscode.ExtensionContext private stateCache: Map private secretCache: Map + private windowId: string + private readonly instanceCreationTime: Date = new Date() constructor(context: vscode.ExtensionContext) { // Initialize properties first this.originalContext = context this.stateCache = new Map() this.secretCache = new Map() + + // Generate a unique ID for this window instance + this.windowId = this.ensureUniqueWindowId() + logger.debug(`ContextProxy created with windowId: ${this.windowId}`) // Initialize state cache with all defined global state keys this.initializeStateCache() @@ -22,12 +32,125 @@ export class ContextProxy { logger.debug("ContextProxy created") } + /** + * Ensures we have a unique window ID, with fallback mechanisms if primary generation fails + * @returns A string ID unique to this VS Code window + */ + private ensureUniqueWindowId(): string { + // Try to get a stable ID first + let id = this.generateWindowId(); + + // If all else fails, use a purely random ID as ultimate fallback + // This will not be stable across restarts but ensures uniqueness + if (!id) { + id = `random_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; + logger.warn("Failed to generate stable window ID, using random ID instead"); + } + + return id; + } + + /** + * Generates a unique identifier for the current VS Code window + * This is used to namespace certain global state values to prevent + * conflicts when using multiple VS Code windows. + * + * The ID generation uses multiple sources to ensure uniqueness even in + * environments where workspace folders might be identical (like DevContainers). + * + * @returns A string ID unique to this VS Code window + */ + private generateWindowId(): string { + try { + // Get all available identifying information + const folders = vscode.workspace.workspaceFolders || []; + const workspaceName = vscode.workspace.name || "unknown"; + const folderPaths = folders.map(folder => folder.uri.toString()).join('|'); + + // Generate a stable, pseudorandom ID based on the workspace information + // This will be consistent for the same workspace but different across workspaces + const baseId = `${workspaceName}|${folderPaths}`; + + // Add machine-specific information (will differ between host and containers) + // env.machineId is stable across VS Code sessions on the same machine + const machineSpecificId = vscode.env.machineId || ""; + + // Add a session component that distinguishes multiple windows with the same workspace + // Creates a stable but reasonably unique hash + const sessionHash = this.createSessionHash(baseId); + + // Combine all components + return `${baseId}|${machineSpecificId}|${sessionHash}`; + } catch (error) { + logger.error("Error generating window ID:", error); + return ""; // Empty string triggers the fallback in ensureUniqueWindowId + } + } + + /** + * Creates a stable hash from input string and window-specific properties + * that will be different for different VS Code windows even with identical projects + */ + private createSessionHash(input: string): string { + try { + // Use a combination of: + // 1. The extension instance creation time + const timestamp = this.instanceCreationTime.getTime(); + + // 2. VS Code window-specific info we can derive + // Using vscode.env.sessionId which changes on each VS Code window startup + const sessionInfo = vscode.env.sessionId || ""; + + // 3. Calculate a simple hash + const hashStr = `${input}|${sessionInfo}|${timestamp}`; + let hash = 0; + for (let i = 0; i < hashStr.length; i++) { + const char = hashStr.charCodeAt(i); + hash = ((hash << 5) - hash) + char; + hash = hash & hash; // Convert to 32bit integer + } + + // Return a hexadecimal representation + return Math.abs(hash).toString(16).substring(0, 8); + } catch (error) { + logger.error("Error creating session hash:", error); + return Math.random().toString(36).substring(2, 10); // Random fallback + } + } + + /** + * Checks if a key should be stored per-window + * @param key The key to check + * @returns True if the key should be stored per-window, false otherwise + */ + private isWindowSpecificKey(key: string): boolean { + return WINDOW_SPECIFIC_KEYS.includes(key as WindowSpecificKey) + } + + /** + * Converts a regular key to a window-specific key + * @param key The original key + * @returns The window-specific key with window ID prefix + */ + private getWindowSpecificKey(key: string): string { + return `window:${this.windowId}:${key}` + } + // Helper method to initialize state cache private initializeStateCache(): void { for (const key of GLOBAL_STATE_KEYS) { try { - const value = this.originalContext.globalState.get(key) - this.stateCache.set(key, value) + if (this.isWindowSpecificKey(key)) { + // For window-specific keys, get the value using the window-specific key + const windowKey = this.getWindowSpecificKey(key) + const value = this.originalContext.globalState.get(windowKey) + this.stateCache.set(key, value) + logger.debug(`Loaded window-specific key ${key} as ${windowKey} with value: ${value}`) + } else { + // For global keys, use the regular key + const value = this.originalContext.globalState.get(key) + this.stateCache.set(key, value) + } } catch (error) { logger.error(`Error loading global ${key}: ${error instanceof Error ? error.message : String(error)}`) } @@ -70,13 +193,30 @@ export class ContextProxy { getGlobalState(key: string): T | undefined getGlobalState(key: string, defaultValue: T): T getGlobalState(key: string, defaultValue?: T): T | undefined { - const value = this.stateCache.get(key) as T | undefined - return value !== undefined ? value : (defaultValue as T | undefined) + // Check for window-specific key + if (this.isWindowSpecificKey(key)) { + // Use the cached value if it exists (it would have been loaded with the window-specific key) + const value = this.stateCache.get(key) as T | undefined + return value !== undefined ? value : (defaultValue as T | undefined) + } else { + // For regular global keys, use the regular cached value + const value = this.stateCache.get(key) as T | undefined + return value !== undefined ? value : (defaultValue as T | undefined) + } } updateGlobalState(key: string, value: T): Thenable { + // Update in-memory cache this.stateCache.set(key, value) - return this.originalContext.globalState.update(key, value) + + // Update in VSCode storage with appropriate key + if (this.isWindowSpecificKey(key)) { + const windowKey = this.getWindowSpecificKey(key) + logger.debug(`Updating window-specific key ${key} as ${windowKey} with value: ${JSON.stringify(value)}`) + return this.originalContext.globalState.update(windowKey, value) + } else { + return this.originalContext.globalState.update(key, value) + } } getSecret(key: string): string | undefined { @@ -140,16 +280,27 @@ export class ContextProxy { this.stateCache.clear() this.secretCache.clear() + // Create an array for all reset promises + const resetPromises: Thenable[] = [] + // Reset all global state values to undefined - const stateResetPromises = GLOBAL_STATE_KEYS.map((key) => - this.originalContext.globalState.update(key, undefined), - ) + for (const key of GLOBAL_STATE_KEYS) { + if (this.isWindowSpecificKey(key)) { + // For window-specific keys, reset using the window-specific key + const windowKey = this.getWindowSpecificKey(key) + resetPromises.push(this.originalContext.globalState.update(windowKey, undefined)) + } else { + resetPromises.push(this.originalContext.globalState.update(key, undefined)) + } + } // Delete all secrets - const secretResetPromises = SECRET_KEYS.map((key) => this.originalContext.secrets.delete(key)) + for (const key of SECRET_KEYS) { + resetPromises.push(this.originalContext.secrets.delete(key)) + } // Wait for all reset operations to complete - await Promise.all([...stateResetPromises, ...secretResetPromises]) + await Promise.all(resetPromises) this.initializeStateCache() this.initializeSecretCache() From 89c5d87842d49a5b7643e87ff958e69d9c36b7eb Mon Sep 17 00:00:00 2001 From: iainRedro Date: Tue, 11 Mar 2025 10:40:52 +0000 Subject: [PATCH 2/3] Refine for PR (Fix mode state sharing when switching between open vscode windows) --- src/core/__tests__/contextProxy.test.ts | 131 ++++++++++++++---------- src/core/contextProxy.ts | 110 ++++++++++---------- 2 files changed, 136 insertions(+), 105 deletions(-) diff --git a/src/core/__tests__/contextProxy.test.ts b/src/core/__tests__/contextProxy.test.ts index ac317e15bbe..1f654112851 100644 --- a/src/core/__tests__/contextProxy.test.ts +++ b/src/core/__tests__/contextProxy.test.ts @@ -21,7 +21,7 @@ jest.mock("vscode", () => ({ }, env: { sessionId: "test-session-id", - machineId: "test-machine-id" + machineId: "test-machine-id", }, workspace: { name: "test-workspace-name", @@ -130,9 +130,35 @@ describe("ContextProxy", () => { expect(mockGlobalState.update).toHaveBeenCalledWith("test-key", "new-value") // Should have stored the value in cache - const storedValue = await proxy.getGlobalState("test-key") + const storedValue = proxy.getGlobalState("test-key") expect(storedValue).toBe("new-value") }) + + it("should handle window-specific keys correctly", async () => { + // Test with a window-specific key + await proxy.updateGlobalState("mode", "test-mode") + + // Should have called update with window-specific key + expect(mockGlobalState.update).toHaveBeenCalledWith(expect.stringMatching(/^window:.+:mode$/), "test-mode") + + // Should have stored the value in cache with the original key + const storedValue = proxy.getGlobalState("mode") + expect(storedValue).toBe("test-mode") + }) + + it("should throw and not update cache if storage update fails", async () => { + // Mock a failure in the storage update + mockGlobalState.update.mockRejectedValueOnce(new Error("Storage update failed")) + + // Set initial cache value + proxy["stateCache"].set("error-key", "initial-value") + + // Attempt to update should fail + await expect(proxy.updateGlobalState("error-key", "new-value")).rejects.toThrow("Storage update failed") + + // Cache should still have the initial value + expect(proxy.getGlobalState("error-key")).toBe("initial-value") + }) }) describe("getSecret", () => { @@ -356,22 +382,22 @@ describe("ContextProxy", () => { it("should use window-specific key for mode", async () => { // Ensure 'mode' is in window specific keys expect(WINDOW_SPECIFIC_KEYS).toContain("mode") - + // Test update method with 'mode' key await proxy.updateGlobalState("mode", "debug") - + // Verify it's called with window-specific key expect(mockGlobalState.update).toHaveBeenCalledWith(expect.stringMatching(/^window:.+:mode$/), "debug") }) - + it("should use regular key for non-window-specific state", async () => { // Test update method with a regular key await proxy.updateGlobalState("apiProvider", "test-provider") - + // Verify it's called with regular key expect(mockGlobalState.update).toHaveBeenCalledWith("apiProvider", "test-provider") }) - + it("should consistently use same key format for get/update operations", async () => { // Set mock values for testing const windowKeyPattern = /^window:.+:mode$/ @@ -380,20 +406,20 @@ describe("ContextProxy", () => { if (key === "mode") return "global-debug-mode" return undefined }) - + // Update a window-specific value await proxy.updateGlobalState("mode", "test-mode") - + // The key used in update should match pattern const updateCallArg = mockGlobalState.update.mock.calls[0][0] expect(updateCallArg).toMatch(windowKeyPattern) - + // Re-init to load values proxy["initializeStateCache"]() - + // Verify we get the window-specific value back const value = proxy.getGlobalState("mode") - + // We should get the window-specific value, not the global one expect(mockGlobalState.get).toHaveBeenCalledWith(expect.stringMatching(windowKeyPattern)) expect(value).not.toBe("global-debug-mode") @@ -403,61 +429,62 @@ describe("ContextProxy", () => { describe("Enhanced window ID generation", () => { it("should generate a window ID that includes workspace name", () => { // Access the private method using type assertion - const generateWindowId = (proxy as any).generateWindowId.bind(proxy); - const windowId = generateWindowId(); - + const generateWindowId = (proxy as any).generateWindowId.bind(proxy) + const windowId = generateWindowId() + // Should include the workspace name from our mock - expect(windowId).toContain("test-workspace-name"); - }); - + expect(windowId).toContain("test-workspace-name") + }) + it("should generate a window ID that includes machine ID", () => { // Access the private method using type assertion - const generateWindowId = (proxy as any).generateWindowId.bind(proxy); - const windowId = generateWindowId(); - + const generateWindowId = (proxy as any).generateWindowId.bind(proxy) + const windowId = generateWindowId() + // Should include the machine ID from our mock - expect(windowId).toContain("test-machine-id"); - }); - + expect(windowId).toContain("test-machine-id") + }) + it("should use the fallback mechanism if generateWindowId fails", () => { // Create a proxy instance with a failing generateWindowId method - const spyOnGenerate = jest.spyOn(ContextProxy.prototype as any, "generateWindowId") - .mockImplementation(() => ""); - + const spyOnGenerate = jest + .spyOn(ContextProxy.prototype as any, "generateWindowId") + .mockImplementation(() => "") + // Create a new proxy to trigger the constructor with our mock - const testProxy = new ContextProxy(mockContext); - + const testProxy = new ContextProxy(mockContext) + // Should have called ensureUniqueWindowId with a fallback - expect(spyOnGenerate).toHaveBeenCalled(); - + expect(spyOnGenerate).toHaveBeenCalled() + // The windowId should use the fallback format (random ID) // We can't test the exact value, but we can verify it's not empty - expect((testProxy as any).windowId).not.toBe(""); - + expect((testProxy as any).windowId).not.toBe("") + // Restore original implementation - spyOnGenerate.mockRestore(); - }); - + spyOnGenerate.mockRestore() + }) + it("should create consistent session hash for same input", () => { // Access the private method using type assertion - const createSessionHash = (proxy as any).createSessionHash.bind(proxy); - - const hash1 = createSessionHash("test-input"); - const hash2 = createSessionHash("test-input"); - + const createSessionHash = (proxy as any).createSessionHash.bind(proxy) + + const hash1 = createSessionHash("test-input") + const hash2 = createSessionHash("test-input") + // Same input should produce same hash within the same session - expect(hash1).toBe(hash2); - }); - + expect(hash1).toBe(hash2) + }) + it("should create different session hashes for different inputs", () => { // Access the private method using type assertion - const createSessionHash = (proxy as any).createSessionHash.bind(proxy); - - const hash1 = createSessionHash("test-input-1"); - const hash2 = createSessionHash("test-input-2"); - + const createSessionHash = (proxy as any).createSessionHash.bind(proxy) + + const hash1 = createSessionHash("test-input-1") + const hash2 = createSessionHash("test-input-2") + // Different inputs should produce different hashes - expect(hash1).not.toBe(hash2); - }); - }); + expect(hash1).not.toBe(hash2) + }) + }) }) diff --git a/src/core/contextProxy.ts b/src/core/contextProxy.ts index 3cf1068331d..8913752b0b1 100644 --- a/src/core/contextProxy.ts +++ b/src/core/contextProxy.ts @@ -18,7 +18,7 @@ export class ContextProxy { this.originalContext = context this.stateCache = new Map() this.secretCache = new Map() - + // Generate a unique ID for this window instance this.windowId = this.ensureUniqueWindowId() logger.debug(`ContextProxy created with windowId: ${this.windowId}`) @@ -38,16 +38,16 @@ export class ContextProxy { */ private ensureUniqueWindowId(): string { // Try to get a stable ID first - let id = this.generateWindowId(); - + let id = this.generateWindowId() + // If all else fails, use a purely random ID as ultimate fallback // This will not be stable across restarts but ensures uniqueness if (!id) { - id = `random_${Date.now()}_${Math.random().toString(36).substring(2, 9)}`; - logger.warn("Failed to generate stable window ID, using random ID instead"); + id = `random_${Date.now()}_${Math.random().toString(36).substring(2, 9)}` + logger.warn("Failed to generate stable window ID, using random ID instead") } - - return id; + + return id } /** @@ -63,27 +63,27 @@ export class ContextProxy { private generateWindowId(): string { try { // Get all available identifying information - const folders = vscode.workspace.workspaceFolders || []; - const workspaceName = vscode.workspace.name || "unknown"; - const folderPaths = folders.map(folder => folder.uri.toString()).join('|'); - + const folders = vscode.workspace.workspaceFolders || [] + const workspaceName = vscode.workspace.name || "unknown" + const folderPaths = folders.map((folder) => folder.uri.toString()).join("|") + // Generate a stable, pseudorandom ID based on the workspace information // This will be consistent for the same workspace but different across workspaces - const baseId = `${workspaceName}|${folderPaths}`; - + const baseId = `${workspaceName}|${folderPaths}` + // Add machine-specific information (will differ between host and containers) // env.machineId is stable across VS Code sessions on the same machine - const machineSpecificId = vscode.env.machineId || ""; - + const machineSpecificId = vscode.env.machineId || "" + // Add a session component that distinguishes multiple windows with the same workspace // Creates a stable but reasonably unique hash - const sessionHash = this.createSessionHash(baseId); - + const sessionHash = this.createSessionHash(baseId) + // Combine all components - return `${baseId}|${machineSpecificId}|${sessionHash}`; + return `${baseId}|${machineSpecificId}|${sessionHash}` } catch (error) { - logger.error("Error generating window ID:", error); - return ""; // Empty string triggers the fallback in ensureUniqueWindowId + logger.error("Error generating window ID:", error) + return "" // Empty string triggers the fallback in ensureUniqueWindowId } } @@ -95,26 +95,26 @@ export class ContextProxy { try { // Use a combination of: // 1. The extension instance creation time - const timestamp = this.instanceCreationTime.getTime(); - + const timestamp = this.instanceCreationTime.getTime() + // 2. VS Code window-specific info we can derive // Using vscode.env.sessionId which changes on each VS Code window startup - const sessionInfo = vscode.env.sessionId || ""; - + const sessionInfo = vscode.env.sessionId || "" + // 3. Calculate a simple hash - const hashStr = `${input}|${sessionInfo}|${timestamp}`; - let hash = 0; + const hashStr = `${input}|${sessionInfo}|${timestamp}` + let hash = 0 for (let i = 0; i < hashStr.length; i++) { - const char = hashStr.charCodeAt(i); - hash = ((hash << 5) - hash) + char; - hash = hash & hash; // Convert to 32bit integer + const char = hashStr.charCodeAt(i) + hash = (hash << 5) - hash + char + hash = hash & hash // Convert to 32bit integer } - + // Return a hexadecimal representation - return Math.abs(hash).toString(16).substring(0, 8); + return Math.abs(hash).toString(16).substring(0, 8) } catch (error) { - logger.error("Error creating session hash:", error); - return Math.random().toString(36).substring(2, 10); // Random fallback + logger.error("Error creating session hash:", error) + return Math.random().toString(36).substring(2, 10) // Random fallback } } @@ -193,29 +193,33 @@ export class ContextProxy { getGlobalState(key: string): T | undefined getGlobalState(key: string, defaultValue: T): T getGlobalState(key: string, defaultValue?: T): T | undefined { - // Check for window-specific key - if (this.isWindowSpecificKey(key)) { - // Use the cached value if it exists (it would have been loaded with the window-specific key) - const value = this.stateCache.get(key) as T | undefined - return value !== undefined ? value : (defaultValue as T | undefined) - } else { - // For regular global keys, use the regular cached value - const value = this.stateCache.get(key) as T | undefined - return value !== undefined ? value : (defaultValue as T | undefined) - } + // The cache already contains the correct value regardless of whether + // this is a window-specific key (handled during initialization and updates) + const value = this.stateCache.get(key) as T | undefined + return value !== undefined ? value : (defaultValue as T | undefined) } - updateGlobalState(key: string, value: T): Thenable { - // Update in-memory cache - this.stateCache.set(key, value) + async updateGlobalState(key: string, value: T): Promise { + try { + // Determine the storage key + const storageKey = this.isWindowSpecificKey(key) ? this.getWindowSpecificKey(key) : key - // Update in VSCode storage with appropriate key - if (this.isWindowSpecificKey(key)) { - const windowKey = this.getWindowSpecificKey(key) - logger.debug(`Updating window-specific key ${key} as ${windowKey} with value: ${JSON.stringify(value)}`) - return this.originalContext.globalState.update(windowKey, value) - } else { - return this.originalContext.globalState.update(key, value) + if (this.isWindowSpecificKey(key)) { + logger.debug( + `Updating window-specific key ${key} as ${storageKey} with value: ${JSON.stringify(value)}`, + ) + } + + // Update in VSCode storage first + await this.originalContext.globalState.update(storageKey, value) + + // Only update cache if storage update succeeded + this.stateCache.set(key, value) + } catch (error) { + logger.error( + `Failed to update global state for key ${key}: ${error instanceof Error ? error.message : String(error)}`, + ) + throw error // Re-throw to allow callers to handle the error } } From 04406a8f773439f6713ad05e236dc829678d4691 Mon Sep 17 00:00:00 2001 From: iainRedro Date: Tue, 11 Mar 2025 10:51:58 +0000 Subject: [PATCH 3/3] Bugfix for PR (This code will make roo save nothing, and make vscode.ExtensionContext wasted) --- src/core/contextProxy.ts | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/core/contextProxy.ts b/src/core/contextProxy.ts index 8913752b0b1..2e83895cfc7 100644 --- a/src/core/contextProxy.ts +++ b/src/core/contextProxy.ts @@ -141,9 +141,18 @@ export class ContextProxy { for (const key of GLOBAL_STATE_KEYS) { try { if (this.isWindowSpecificKey(key)) { - // For window-specific keys, get the value using the window-specific key + // For window-specific keys, first try to get the value using the window-specific key const windowKey = this.getWindowSpecificKey(key) - const value = this.originalContext.globalState.get(windowKey) + let value = this.originalContext.globalState.get(windowKey) + + // If no window-specific value exists, try to get a global value as fallback + if (value === undefined) { + value = this.originalContext.globalState.get(key) + logger.debug( + `No window-specific value found for ${windowKey}, using global value for ${key}: ${value}`, + ) + } + this.stateCache.set(key, value) logger.debug(`Loaded window-specific key ${key} as ${windowKey} with value: ${value}`) } else {