diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 168179f4df3..2b31688b4dc 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -2326,51 +2326,33 @@ export class ClineProvider extends EventEmitter implements }> { const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || [] const historyItem = history.find((item) => item.id === id) - if (!historyItem) { - throw new Error("Task not found in history") - } - - const taskDirPath = path.join(this.contextProxy.globalStorageUri.fsPath, "tasks", id) - const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory) - const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages) - - const fileExists = await fileExistsAtPath(apiConversationHistoryFilePath) - if (!fileExists) { - // Instead of silently deleting, throw a specific error - throw new Error("TASK_FILES_MISSING") - } - - const apiConversationHistory = JSON.parse(await fs.readFile(apiConversationHistoryFilePath, "utf8")) - return { - historyItem, - taskDirPath, - apiConversationHistoryFilePath, - uiMessagesFilePath, - apiConversationHistory, + if (historyItem) { + const taskDirPath = path.join(this.contextProxy.globalStorageUri.fsPath, "tasks", id) + const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory) + const uiMessagesFilePath = path.join(taskDirPath, GlobalFileNames.uiMessages) + const fileExists = await fileExistsAtPath(apiConversationHistoryFilePath) + if (fileExists) { + const apiConversationHistory = JSON.parse(await fs.readFile(apiConversationHistoryFilePath, "utf8")) + return { + historyItem, + taskDirPath, + apiConversationHistoryFilePath, + uiMessagesFilePath, + apiConversationHistory, + } + } } + // if we tried to get a task that doesn't exist, remove it from state + // FIXME: this seems to happen sometimes when the json file doesnt save to disk for some reason + await this.deleteTaskFromState(id) + throw new Error("Task not found") } async showTaskWithId(id: string) { if (id !== this.getCurrentCline()?.taskId) { - try { - const { historyItem } = await this.getTaskWithId(id) - await this.initClineWithHistoryItem(historyItem) - } catch (error) { - if (error.message === "TASK_FILES_MISSING") { - const response = await vscode.window.showWarningMessage( - t("common:warnings.missing_task_files"), - t("common:answers.remove"), - t("common:answers.keep"), - ) - - if (response === t("common:answers.remove")) { - await this.deleteTaskFromState(id) - await this.postStateToWebview() - } - return - } - throw error - } + // Non-current task. + const { historyItem } = await this.getTaskWithId(id) + await this.initClineWithHistoryItem(historyItem) // Clears existing task. } await this.postMessageToWebview({ type: "action", action: "chatButtonClicked" }) @@ -2857,28 +2839,4 @@ export class ClineProvider extends EventEmitter implements return properties } - - async validateTaskHistory() { - const history = ((await this.getGlobalState("taskHistory")) as HistoryItem[] | undefined) || [] - const validTasks: HistoryItem[] = [] - - for (const item of history) { - const taskDirPath = path.join(this.contextProxy.globalStorageUri.fsPath, "tasks", item.id) - const apiConversationHistoryFilePath = path.join(taskDirPath, GlobalFileNames.apiConversationHistory) - - if (await fileExistsAtPath(apiConversationHistoryFilePath)) { - validTasks.push(item) - } - } - - if (validTasks.length !== history.length) { - await this.updateGlobalState("taskHistory", validTasks) - await this.postStateToWebview() - - const removedCount = history.length - validTasks.length - if (removedCount > 0) { - await vscode.window.showInformationMessage(t("common:info.history_cleanup", { count: removedCount })) - } - } - } } diff --git a/src/core/webview/__tests__/ClineProvider.test.ts b/src/core/webview/__tests__/ClineProvider.test.ts index de87845eaa2..6edeb7ac2ce 100644 --- a/src/core/webview/__tests__/ClineProvider.test.ts +++ b/src/core/webview/__tests__/ClineProvider.test.ts @@ -55,78 +55,6 @@ jest.mock("../../contextProxy", () => { } }) -describe("validateTaskHistory", () => { - let provider: ClineProvider - let mockContext: vscode.ExtensionContext - let mockOutputChannel: vscode.OutputChannel - let mockUpdate: jest.Mock - - beforeEach(() => { - // Reset mocks - jest.clearAllMocks() - - mockUpdate = jest.fn() - - // Setup basic mocks - mockContext = { - globalState: { - get: jest.fn(), - update: mockUpdate, - keys: jest.fn().mockReturnValue([]), - }, - secrets: { get: jest.fn(), store: jest.fn(), delete: jest.fn() }, - extensionUri: {} as vscode.Uri, - globalStorageUri: { fsPath: "/test/path" }, - extension: { packageJSON: { version: "1.0.0" } }, - } as unknown as vscode.ExtensionContext - - mockOutputChannel = { appendLine: jest.fn() } as unknown as vscode.OutputChannel - provider = new ClineProvider(mockContext, mockOutputChannel) - }) - - test("should remove tasks with missing files", async () => { - // Mock the global state with some test data - const mockHistory = [ - { id: "task1", ts: Date.now() }, - { id: "task2", ts: Date.now() }, - ] - - // Setup mocks - jest.spyOn(mockContext.globalState, "get").mockReturnValue(mockHistory) - - // Mock fileExistsAtPath to only return true for task1 - const mockFs = require("../../../utils/fs") - mockFs.fileExistsAtPath = jest.fn().mockImplementation((path) => Promise.resolve(path.includes("task1"))) - - // Call validateTaskHistory - await provider.validateTaskHistory() - - // Verify the results - const expectedHistory = [expect.objectContaining({ id: "task1" })] - - expect(mockUpdate).toHaveBeenCalledWith("taskHistory", expect.arrayContaining(expectedHistory)) - expect(mockUpdate.mock.calls[0][1].length).toBe(1) - }) - - test("should handle empty history", async () => { - // Mock empty history - jest.spyOn(mockContext.globalState, "get").mockReturnValue([]) - - await provider.validateTaskHistory() - - expect(mockUpdate).toHaveBeenCalledWith("taskHistory", []) - }) - - test("should handle null history", async () => { - // Mock null history - jest.spyOn(mockContext.globalState, "get").mockReturnValue(null) - - await provider.validateTaskHistory() - - expect(mockUpdate).toHaveBeenCalledWith("taskHistory", []) - }) -}) - // Mock dependencies jest.mock("vscode") jest.mock("delay") diff --git a/src/extension.ts b/src/extension.ts index db2c5b378a0..05f8afe969b 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -64,11 +64,6 @@ export function activate(context: vscode.ExtensionContext) { const provider = new ClineProvider(context, outputChannel, "sidebar") telemetryService.setProvider(provider) - // Validate task history on extension activation - provider.validateTaskHistory().catch((error) => { - outputChannel.appendLine(`Failed to validate task history: ${error}`) - }) - context.subscriptions.push( vscode.window.registerWebviewViewProvider(ClineProvider.sideBarId, provider, { webviewOptions: { retainContextWhenHidden: true },