From 50242a816cb79039d959041bb461da23462329b6 Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Thu, 1 Oct 2026 13:27:07 +0300 Subject: [PATCH 1/6] fix(code-index): make startup embedder validation non-blocking --- .../code-index-status-manager.spec.ts | 5 +- .../embedder-readiness-manager.spec.ts | 100 ++++++++++++++++++ .../code-index/__tests__/manager.spec.ts | 63 +++-------- .../code-index/code-index-workspace-scope.ts | 3 + .../code-index/embedder-readiness-manager.ts | 35 ++++++ src/services/code-index/manager.ts | 13 +-- 6 files changed, 159 insertions(+), 60 deletions(-) create mode 100644 src/services/code-index/__tests__/embedder-readiness-manager.spec.ts create mode 100644 src/services/code-index/embedder-readiness-manager.ts diff --git a/src/services/code-index/__tests__/code-index-status-manager.spec.ts b/src/services/code-index/__tests__/code-index-status-manager.spec.ts index c73fb4f03b..4b1a585a09 100644 --- a/src/services/code-index/__tests__/code-index-status-manager.spec.ts +++ b/src/services/code-index/__tests__/code-index-status-manager.spec.ts @@ -8,6 +8,7 @@ import { } from "../../../test-utils/vscode" import { CodeIndexManager } from "../manager" import { CodeIndexStateManager } from "../state-manager" +import { EmbedderReadinessManager } from "../embedder-readiness-manager" import { CodeIndexStatusManager, type CodeIndexStatus } from "../code-index-status-manager" // Reload the real workspace resolver against this suite's VS Code mock, @@ -40,11 +41,13 @@ function makeSource(workspacePath: string) { emit = () => listener(status) return subscription } + const stateManager = new CodeIndexStateManager() const manager = new CodeIndexManager( workspacePath, makeUri(workspacePath), makeExtensionContext(), - new CodeIndexStateManager(), + stateManager, + new EmbedderReadinessManager(stateManager), ) Object.defineProperty(manager, "onProgressUpdate", { value: vi.fn(event), configurable: true }) return Object.assign(manager, { diff --git a/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts b/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts new file mode 100644 index 0000000000..86f24ecaed --- /dev/null +++ b/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts @@ -0,0 +1,100 @@ +import { EmbedderReadinessManager } from "../embedder-readiness-manager" +import type { IndexingState } from "../state-manager" +import type { CodeIndexServiceFactory } from "../service-factory" +import type { IEmbedder } from "../interfaces" + +describe("EmbedderReadinessManager", () => { + const setup = () => { + const stateManager = { + state: "Standby" as IndexingState, + setSystemState: vi.fn(), + } + const embedder: IEmbedder = { + embedderInfo: { name: "ollama" }, + createEmbeddings: vi.fn(), + validateConfiguration: vi.fn(), + } + const serviceFactory = { + validateEmbedder: vi.fn(), + } as unknown as CodeIndexServiceFactory + return { stateManager, manager: new EmbedderReadinessManager(stateManager), serviceFactory, embedder } + } + + it("reports the latest validation failure while the manager remains in standby", async () => { + const { stateManager, manager, serviceFactory, embedder } = setup() + vi.mocked(serviceFactory.validateEmbedder).mockResolvedValue({ + valid: false, + error: "Current validation failure", + }) + + void manager.validate(serviceFactory, embedder) + await Promise.resolve() + + expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith("Error", "Current validation failure") + }) + + it("reports an unexpected validation rejection while the manager remains in standby", async () => { + const { stateManager, manager, serviceFactory, embedder } = setup() + vi.mocked(serviceFactory.validateEmbedder).mockRejectedValue(new Error("Validation crashed")) + + void manager.validate(serviceFactory, embedder) + await Promise.resolve() + await Promise.resolve() + + expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith("Error", "Validation crashed") + }) + + it("ignores validation results after another operation changes the status", async () => { + const { stateManager, manager, serviceFactory, embedder } = setup() + let finishValidation!: (result: { valid: boolean; error?: string }) => void + vi.mocked(serviceFactory.validateEmbedder).mockReturnValue( + new Promise((resolve) => { + finishValidation = resolve + }), + ) + void manager.validate(serviceFactory, embedder) + + stateManager.state = "Indexed" + finishValidation({ valid: false, error: "Stale validation failure" }) + await Promise.resolve() + + expect(stateManager.setSystemState).not.toHaveBeenCalled() + }) + + it("ignores an older result when a newer validation has started", async () => { + const { stateManager, manager, serviceFactory, embedder } = setup() + let finishFirstValidation!: (result: { valid: boolean; error?: string }) => void + vi.mocked(serviceFactory.validateEmbedder) + .mockReturnValueOnce( + new Promise((resolve) => { + finishFirstValidation = resolve + }), + ) + .mockResolvedValueOnce({ valid: true }) + void manager.validate(serviceFactory, embedder) + void manager.validate(serviceFactory, embedder) + await Promise.resolve() + + finishFirstValidation({ valid: false, error: "Older validation failure" }) + await Promise.resolve() + + expect(stateManager.setSystemState).not.toHaveBeenCalled() + }) + + it("ignores a pending result after invalidation", async () => { + const { stateManager, manager, serviceFactory, embedder } = setup() + let finishValidation!: (result: { valid: boolean; error?: string }) => void + vi.mocked(serviceFactory.validateEmbedder).mockReturnValue( + new Promise((resolve) => { + finishValidation = resolve + }), + ) + void manager.validate(serviceFactory, embedder) + + manager.invalidate() + finishValidation({ valid: false, error: "Invalidated validation failure" }) + await Promise.resolve() + + expect(stateManager.setSystemState).not.toHaveBeenCalled() + }) +}) diff --git a/src/services/code-index/__tests__/manager.spec.ts b/src/services/code-index/__tests__/manager.spec.ts index 952e205fdd..ab98005bfc 100644 --- a/src/services/code-index/__tests__/manager.spec.ts +++ b/src/services/code-index/__tests__/manager.spec.ts @@ -474,63 +474,24 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { ;(manager as any)._configManager = mockConfigManager }) - it("should validate embedder during _recreateServices when validation succeeds", async () => { - // Arrange - mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({ valid: true }) + it("should create indexing services without waiting for startup embedder validation", async () => { + let finishValidation!: (result: { valid: boolean; error?: string }) => void + mockServiceFactoryInstance.validateEmbedder.mockReturnValue( + new Promise((resolve) => { + finishValidation = resolve + }), + ) - // Act - directly call the private method for testing await (manager as any)._recreateServices() - // Assert expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled() - const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder - expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder) + expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(mockEmbedder) expect(mockStateManager.setSystemState).not.toHaveBeenCalledWith("Error", expect.any(String)) - }) - - it("should set error state when embedder validation fails", async () => { - // Arrange - mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({ - valid: false, - error: "embeddings:validation.authenticationFailed", - }) - - // Act & Assert - await expect((manager as any)._recreateServices()).rejects.toThrow( - "embeddings:validation.authenticationFailed", - ) - - // Assert other expectations - expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled() - const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder - expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder) - expect(mockStateManager.setSystemState).toHaveBeenCalledWith( - "Error", - "embeddings:validation.authenticationFailed", - ) - }) - - it("should set generic error state when embedder validation throws", async () => { - // Arrange - // Since the real service factory catches exceptions, we should mock it to resolve with an error - mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({ - valid: false, - error: "embeddings:validation.configurationError", - }) - - // Act & Assert - await expect((manager as any)._recreateServices()).rejects.toThrow( - "embeddings:validation.configurationError", - ) + expect((manager as any)._orchestrator).toBeDefined() + expect((manager as any)._searchService).toBeDefined() - // Assert other expectations - expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled() - const createdEmbedder = mockServiceFactoryInstance.createServices.mock.results[0].value.embedder - expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(createdEmbedder) - expect(mockStateManager.setSystemState).toHaveBeenCalledWith( - "Error", - "embeddings:validation.configurationError", - ) + finishValidation({ valid: true }) + await Promise.resolve() }) it("should handle embedder creation failure", async () => { diff --git a/src/services/code-index/code-index-workspace-scope.ts b/src/services/code-index/code-index-workspace-scope.ts index c8481f6595..b6f348b1ff 100644 --- a/src/services/code-index/code-index-workspace-scope.ts +++ b/src/services/code-index/code-index-workspace-scope.ts @@ -3,6 +3,7 @@ import type * as vscode from "vscode" import { CodeIndexManager } from "./manager" import { CodeIndexStateManager } from "./state-manager" import { WorkspaceIndexingEnablementManager } from "./workspace-indexing-enablement-manager" +import { EmbedderReadinessManager } from "./embedder-readiness-manager" /** Owns code-index services for one workspace; initialization remains with existing callers. */ export class CodeIndexWorkspaceScope implements vscode.Disposable { @@ -38,11 +39,13 @@ export class CodeIndexWorkspaceScope implements vscode.Disposable { throw new Error("Code index workspace scope is already initialized") } this._stateManager = new CodeIndexStateManager() + const embedderReadinessManager = new EmbedderReadinessManager(this._stateManager) this._codeIndexManager = new CodeIndexManager( this.workspacePath, this.folderUri, this.context, this._stateManager, + embedderReadinessManager, ) this._workspaceIndexingEnablementManager = new WorkspaceIndexingEnablementManager(this._codeIndexManager) this._isInitialized = true diff --git a/src/services/code-index/embedder-readiness-manager.ts b/src/services/code-index/embedder-readiness-manager.ts new file mode 100644 index 0000000000..38cd45fb77 --- /dev/null +++ b/src/services/code-index/embedder-readiness-manager.ts @@ -0,0 +1,35 @@ +import type { CodeIndexStateManager } from "./state-manager" +import type { IEmbedder } from "./interfaces" +import type { CodeIndexServiceFactory } from "./service-factory" + +type ValidationResult = { valid: boolean; error?: string } + +/** Tracks embedder readiness without blocking initialization and discards stale results. */ +export class EmbedderReadinessManager { + private generation = 0 + + public constructor(private readonly stateManager: Pick) {} + + public async validate(serviceFactory: CodeIndexServiceFactory, embedder: IEmbedder): Promise { + const generation = ++this.generation + try { + const result: ValidationResult = await serviceFactory.validateEmbedder(embedder) + if (!this.canApply(generation) || result.valid) return + this.stateManager.setSystemState("Error", result.error || "Embedder configuration validation failed") + } catch (error) { + if (!this.canApply(generation)) return + this.stateManager.setSystemState( + "Error", + error instanceof Error ? error.message : "Embedder configuration validation failed", + ) + } + } + + public invalidate(): void { + this.generation++ + } + + private canApply(generation: number): boolean { + return generation === this.generation && this.stateManager.state === "Standby" + } +} diff --git a/src/services/code-index/manager.ts b/src/services/code-index/manager.ts index bb186dc105..fb7a7496a2 100644 --- a/src/services/code-index/manager.ts +++ b/src/services/code-index/manager.ts @@ -16,6 +16,7 @@ import path from "path" import { t } from "../../i18n" import { TelemetryService } from "@roo-code/telemetry" import { TelemetryEventName } from "@roo-code/types" +import { EmbedderReadinessManager } from "./embedder-readiness-manager" export class CodeIndexManager { // Specialized class instances @@ -40,6 +41,7 @@ export class CodeIndexManager { folderUri: vscode.Uri, context: vscode.ExtensionContext, stateManager: CodeIndexStateManager, + private readonly embedderReadinessManager: EmbedderReadinessManager, ) { this.workspacePath = workspacePath this._folderUri = folderUri @@ -234,6 +236,7 @@ export class CodeIndexManager { * Stops any in-progress indexing operation and the file watcher. */ public stopIndexing(): void { + this.embedderReadinessManager.invalidate() if (this._sembleProvider) { this._sembleProvider.stopIndexing() return @@ -276,6 +279,7 @@ export class CodeIndexManager { } this._isRecoveringFromError = true + this.embedderReadinessManager.invalidate() try { // Clear error state this._stateManager.setSystemState("Standby", "") @@ -415,14 +419,7 @@ export class CodeIndexManager { ignoreInstance, rooIgnoreController, ) - - // Validate embedder configuration before proceeding - const validationResult = await this._serviceFactory.validateEmbedder(embedder) - if (!validationResult.valid) { - const errorMessage = validationResult.error || "Embedder configuration validation failed" - this._stateManager.setSystemState("Error", errorMessage) - throw new Error(errorMessage) - } + void this.embedderReadinessManager.validate(this._serviceFactory, embedder) // (Re)Initialize orchestrator this._orchestrator = new CodeIndexOrchestrator( From d5b4b34953996e417433286ee917fcf8f2cde752 Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Thu, 1 Oct 2026 13:38:30 +0300 Subject: [PATCH 2/6] test(code-index): update scope constructor expectations for readiness manager --- .../code-index-manager-registry.spec.ts | 50 ++++++++++++++++--- .../code-index-workspace-scope.spec.ts | 8 +++ 2 files changed, 52 insertions(+), 6 deletions(-) diff --git a/src/services/code-index/__tests__/code-index-manager-registry.spec.ts b/src/services/code-index/__tests__/code-index-manager-registry.spec.ts index 0d5012a641..2db00a8300 100644 --- a/src/services/code-index/__tests__/code-index-manager-registry.spec.ts +++ b/src/services/code-index/__tests__/code-index-manager-registry.spec.ts @@ -4,6 +4,7 @@ import { CodeIndexManager } from "../manager" import { CodeIndexManagerRegistry } from "../code-index-manager-registry" import { CodeIndexWorkspaceScope } from "../code-index-workspace-scope" import { CodeIndexStateManager } from "../state-manager" +import { EmbedderReadinessManager } from "../embedder-readiness-manager" vi.mock("../state-manager") @@ -60,7 +61,13 @@ describe("CodeIndexManagerRegistry", () => { it("uses the first workspace when there is no active editor", () => { CodeIndexManagerRegistry.getOrCreate(context) - expect(CodeIndexManager).toHaveBeenCalledWith("/first", first.uri, context, expect.any(CodeIndexStateManager)) + expect(CodeIndexManager).toHaveBeenCalledWith( + "/first", + first.uri, + context, + expect.any(CodeIndexStateManager), + expect.any(EmbedderReadinessManager), + ) }) it("prefers the active editor's workspace", () => { @@ -68,20 +75,38 @@ describe("CodeIndexManagerRegistry", () => { Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: editor }) vi.mocked(vscode.workspace.getWorkspaceFolder).mockReturnValue(second) expect(CodeIndexManagerRegistry.getOrCreate(context)).toBeDefined() - expect(CodeIndexManager).toHaveBeenCalledWith("/second", second.uri, context, expect.any(CodeIndexStateManager)) + expect(CodeIndexManager).toHaveBeenCalledWith( + "/second", + second.uri, + context, + expect.any(CodeIndexStateManager), + expect.any(EmbedderReadinessManager), + ) }) it("falls back to the first workspace for an editor outside all folders", () => { Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: makeTextEditor() }) CodeIndexManagerRegistry.getOrCreate(context) - expect(CodeIndexManager).toHaveBeenCalledWith("/first", first.uri, context, expect.any(CodeIndexStateManager)) + expect(CodeIndexManager).toHaveBeenCalledWith( + "/first", + first.uri, + context, + expect.any(CodeIndexStateManager), + expect.any(EmbedderReadinessManager), + ) }) it("gives an explicit path priority over the active editor", () => { Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: makeTextEditor() }) vi.mocked(vscode.workspace.getWorkspaceFolder).mockReturnValue(first) expect(CodeIndexManagerRegistry.getOrCreate(context, "/second")).toBeDefined() - expect(CodeIndexManager).toHaveBeenCalledWith("/second", second.uri, context, expect.any(CodeIndexStateManager)) + expect(CodeIndexManager).toHaveBeenCalledWith( + "/second", + second.uri, + context, + expect.any(CodeIndexStateManager), + expect.any(EmbedderReadinessManager), + ) }) it("preserves the actual remote workspace URI", () => { @@ -91,7 +116,13 @@ describe("CodeIndexManagerRegistry", () => { value: [{ uri, name: "remote", index: 0 }], }) CodeIndexManagerRegistry.getOrCreate(context, "/remote") - expect(CodeIndexManager).toHaveBeenCalledWith("/remote", uri, context, expect.any(CodeIndexStateManager)) + expect(CodeIndexManager).toHaveBeenCalledWith( + "/remote", + uri, + context, + expect.any(CodeIndexStateManager), + expect.any(EmbedderReadinessManager), + ) expect(vi.mocked(CodeIndexManager).mock.calls[0][1]).toBe(uri) expect(vscode.Uri.file).not.toHaveBeenCalled() }) @@ -102,7 +133,13 @@ describe("CodeIndexManagerRegistry", () => { vi.mocked(vscode.Uri.file).mockReturnValue(uri) CodeIndexManagerRegistry.getOrCreate(context, uri.fsPath) expect(vscode.Uri.file).toHaveBeenCalledWith(uri.fsPath) - expect(CodeIndexManager).toHaveBeenCalledWith(uri.fsPath, uri, context, expect.any(CodeIndexStateManager)) + expect(CodeIndexManager).toHaveBeenCalledWith( + uri.fsPath, + uri, + context, + expect.any(CodeIndexStateManager), + expect.any(EmbedderReadinessManager), + ) }) it("constructs a file URI for an explicit path not matching any open workspace folder", () => { @@ -116,6 +153,7 @@ describe("CodeIndexManagerRegistry", () => { uri, context, expect.any(CodeIndexStateManager), + expect.any(EmbedderReadinessManager), ) }) diff --git a/src/services/code-index/__tests__/code-index-workspace-scope.spec.ts b/src/services/code-index/__tests__/code-index-workspace-scope.spec.ts index 417d7284c1..a1f4a297bf 100644 --- a/src/services/code-index/__tests__/code-index-workspace-scope.spec.ts +++ b/src/services/code-index/__tests__/code-index-workspace-scope.spec.ts @@ -2,6 +2,7 @@ import { makeExtensionContext, makeUri } from "../../../test-utils/vscode" import { CodeIndexManager } from "../manager" import { CodeIndexWorkspaceScope } from "../code-index-workspace-scope" import { CodeIndexStateManager } from "../state-manager" +import { EmbedderReadinessManager } from "../embedder-readiness-manager" import { WorkspaceIndexingEnablementManager } from "../workspace-indexing-enablement-manager" vi.mock("../state-manager") @@ -57,6 +58,7 @@ describe("CodeIndexWorkspaceScope", () => { uri, context, vi.mocked(CodeIndexStateManager).mock.instances[0], + expect.any(EmbedderReadinessManager), ) expect(scope.codeIndexManager).toBe(manager) expect(() => scope.init()).toThrow("Code index workspace scope is already initialized") @@ -98,6 +100,9 @@ describe("CodeIndexWorkspaceScope", () => { const calls = vi.mocked(CodeIndexManager).mock.calls expect(CodeIndexStateManager).toHaveBeenCalledTimes(2) expect(calls[1][3]).not.toBe(calls[0][3]) + expect(calls[1][4]).not.toBe(calls[0][4]) + expect(calls[0][4]["stateManager"]).toBe(calls[0][3]) + expect(calls[1][4]["stateManager"]).toBe(calls[1][3]) expect(scope["_stateManager"]).toBe(calls[1][3]) }) @@ -113,6 +118,9 @@ describe("CodeIndexWorkspaceScope", () => { expect(calls[0][3]).toBe(vi.mocked(CodeIndexStateManager).mock.instances[0]) expect(calls[1][3]).toBe(vi.mocked(CodeIndexStateManager).mock.instances[1]) expect(calls[1][3]).not.toBe(calls[0][3]) + expect(calls[1][4]).not.toBe(calls[0][4]) + expect(calls[0][4]["stateManager"]).toBe(calls[0][3]) + expect(calls[1][4]["stateManager"]).toBe(calls[1][3]) }) it("clears its reference even when manager disposal throws", () => { From 72c72af6cc56d5966877a3a8f9ab8c7d5d53ff5a Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Thu, 1 Oct 2026 13:44:51 +0300 Subject: [PATCH 3/6] test(code-index): include readiness dependency in search workspace assertion --- src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts b/src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts index f2c4cc2aed..b3bce8efc7 100644 --- a/src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts +++ b/src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts @@ -7,6 +7,7 @@ import { CodebaseSearchTool } from "../CodebaseSearchTool" import { CodeIndexManagerRegistry } from "../../../services/code-index/code-index-manager-registry" import { CodeIndexManager } from "../../../services/code-index/manager" import { CodeIndexStateManager } from "../../../services/code-index/state-manager" +import { EmbedderReadinessManager } from "../../../services/code-index/embedder-readiness-manager" import { getWorkspacePath } from "../../../utils/path" import { makeExtensionContext, makeTextDocument, makeTextEditor, makeUri } from "../../../test-utils/vscode" @@ -199,6 +200,7 @@ describe("CodebaseSearchTool workspace selection", () => { expect.objectContaining({ fsPath: "/external-task" }), provider.context, expect.any(CodeIndexStateManager), + expect.any(EmbedderReadinessManager), ) expect(vscode.Uri.file).toHaveBeenCalledExactlyOnceWith("/external-task") const manager = CodeIndexManagerRegistry.getOrCreate(provider.context, "/external-task")! From aaeb28ca3159489242ae7543f92a8124bf544cd1 Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Thu, 1 Oct 2026 14:02:46 +0300 Subject: [PATCH 4/6] test(code-index): cover stale validation rejections and strengthen service assertions --- src/eslint-suppressions.json | 2 +- .../embedder-readiness-manager.spec.ts | 27 +++++++++++++++++++ .../code-index/__tests__/manager.spec.ts | 8 +++--- 3 files changed, 33 insertions(+), 4 deletions(-) diff --git a/src/eslint-suppressions.json b/src/eslint-suppressions.json index 24db0bf433..6ca8a4e134 100644 --- a/src/eslint-suppressions.json +++ b/src/eslint-suppressions.json @@ -1301,7 +1301,7 @@ }, "services/code-index/__tests__/manager.spec.ts": { "@typescript-eslint/no-explicit-any": { - "count": 87 + "count": 84 } }, "services/code-index/__tests__/orchestrator.spec.ts": { diff --git a/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts b/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts index 86f24ecaed..b9b3f986cb 100644 --- a/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts +++ b/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts @@ -81,6 +81,33 @@ describe("EmbedderReadinessManager", () => { expect(stateManager.setSystemState).not.toHaveBeenCalled() }) + it.each(["invalidation", "newer validation", "status change"] as const)( + "ignores a pending rejection after %s", + async (scenario) => { + const { stateManager, manager, serviceFactory, embedder } = setup() + let rejectValidation!: (error: Error) => void + vi.mocked(serviceFactory.validateEmbedder).mockReturnValueOnce( + new Promise((_, reject) => { + rejectValidation = reject + }), + ) + const validation = manager.validate(serviceFactory, embedder) + + if (scenario === "invalidation") { + manager.invalidate() + } else if (scenario === "newer validation") { + vi.mocked(serviceFactory.validateEmbedder).mockResolvedValueOnce({ valid: true }) + await manager.validate(serviceFactory, embedder) + } else { + stateManager.state = "Indexed" + } + + rejectValidation(new Error("Stale rejection")) + await expect(validation).resolves.toBeUndefined() + expect(stateManager.setSystemState).not.toHaveBeenCalled() + }, + ) + it("ignores a pending result after invalidation", async () => { const { stateManager, manager, serviceFactory, embedder } = setup() let finishValidation!: (result: { valid: boolean; error?: string }) => void diff --git a/src/services/code-index/__tests__/manager.spec.ts b/src/services/code-index/__tests__/manager.spec.ts index ab98005bfc..e537a6be6d 100644 --- a/src/services/code-index/__tests__/manager.spec.ts +++ b/src/services/code-index/__tests__/manager.spec.ts @@ -3,6 +3,8 @@ import { makeExtensionContext } from "../../../test-utils/vscode" import { CodeIndexManager } from "../manager" import { CodeIndexManagerRegistry } from "../code-index-manager-registry" import { CodeIndexServiceFactory } from "../service-factory" +import { CodeIndexOrchestrator } from "../orchestrator" +import { CodeIndexSearchService } from "../search-service" import type { MockedClass } from "vitest" import * as path from "path" import { providerIdentifiers } from "@roo-code/types/provider-identifiers" @@ -482,13 +484,13 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { }), ) - await (manager as any)._recreateServices() + await manager["_recreateServices"]() expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled() expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(mockEmbedder) expect(mockStateManager.setSystemState).not.toHaveBeenCalledWith("Error", expect.any(String)) - expect((manager as any)._orchestrator).toBeDefined() - expect((manager as any)._searchService).toBeDefined() + expect(manager["_orchestrator"]).toBeInstanceOf(CodeIndexOrchestrator) + expect(manager["_searchService"]).toBeInstanceOf(CodeIndexSearchService) finishValidation({ valid: true }) await Promise.resolve() From 50cbd4c5b9a8e2af49a84e4e44a8d41c590d6112 Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Thu, 1 Oct 2026 14:38:30 +0300 Subject: [PATCH 5/6] test(code-index): cover readiness validation fallback errors --- .../embedder-readiness-manager.spec.ts | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts b/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts index b9b3f986cb..123c105295 100644 --- a/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts +++ b/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts @@ -33,6 +33,30 @@ describe("EmbedderReadinessManager", () => { expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith("Error", "Current validation failure") }) + it.each([undefined, ""])("uses the fallback for a validation failure with message %j", async (error) => { + const { stateManager, manager, serviceFactory, embedder } = setup() + vi.mocked(serviceFactory.validateEmbedder).mockResolvedValue({ valid: false, error }) + + await manager.validate(serviceFactory, embedder) + + expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith( + "Error", + "Embedder configuration validation failed", + ) + }) + + it("uses the fallback for a non-Error rejection", async () => { + const { stateManager, manager, serviceFactory, embedder } = setup() + vi.mocked(serviceFactory.validateEmbedder).mockRejectedValue("Unexpected rejection") + + await expect(manager.validate(serviceFactory, embedder)).resolves.toBeUndefined() + + expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith( + "Error", + "Embedder configuration validation failed", + ) + }) + it("reports an unexpected validation rejection while the manager remains in standby", async () => { const { stateManager, manager, serviceFactory, embedder } = setup() vi.mocked(serviceFactory.validateEmbedder).mockRejectedValue(new Error("Validation crashed")) From 505a7992427d9bae797a0a4c9544dbc22954cde4 Mon Sep 17 00:00:00 2001 From: gubin-dev Date: Fri, 2 Oct 2026 01:25:23 +0300 Subject: [PATCH 6/6] fix(code-index): remove automatic embedder preflight validation --- .../CodebaseSearchTool.workspace.spec.ts | 2 - .../code-index-manager-registry.spec.ts | 50 +----- .../code-index-status-manager.spec.ts | 5 +- .../code-index-workspace-scope.spec.ts | 8 - .../embedder-readiness-manager.spec.ts | 151 ------------------ .../code-index/__tests__/manager.spec.ts | 19 +-- .../code-index/code-index-workspace-scope.ts | 3 - .../code-index/embedder-readiness-manager.ts | 35 ---- src/services/code-index/manager.ts | 5 - 9 files changed, 14 insertions(+), 264 deletions(-) delete mode 100644 src/services/code-index/__tests__/embedder-readiness-manager.spec.ts delete mode 100644 src/services/code-index/embedder-readiness-manager.ts diff --git a/src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts b/src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts index b3bce8efc7..f2c4cc2aed 100644 --- a/src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts +++ b/src/core/tools/__tests__/CodebaseSearchTool.workspace.spec.ts @@ -7,7 +7,6 @@ import { CodebaseSearchTool } from "../CodebaseSearchTool" import { CodeIndexManagerRegistry } from "../../../services/code-index/code-index-manager-registry" import { CodeIndexManager } from "../../../services/code-index/manager" import { CodeIndexStateManager } from "../../../services/code-index/state-manager" -import { EmbedderReadinessManager } from "../../../services/code-index/embedder-readiness-manager" import { getWorkspacePath } from "../../../utils/path" import { makeExtensionContext, makeTextDocument, makeTextEditor, makeUri } from "../../../test-utils/vscode" @@ -200,7 +199,6 @@ describe("CodebaseSearchTool workspace selection", () => { expect.objectContaining({ fsPath: "/external-task" }), provider.context, expect.any(CodeIndexStateManager), - expect.any(EmbedderReadinessManager), ) expect(vscode.Uri.file).toHaveBeenCalledExactlyOnceWith("/external-task") const manager = CodeIndexManagerRegistry.getOrCreate(provider.context, "/external-task")! diff --git a/src/services/code-index/__tests__/code-index-manager-registry.spec.ts b/src/services/code-index/__tests__/code-index-manager-registry.spec.ts index 2db00a8300..0d5012a641 100644 --- a/src/services/code-index/__tests__/code-index-manager-registry.spec.ts +++ b/src/services/code-index/__tests__/code-index-manager-registry.spec.ts @@ -4,7 +4,6 @@ import { CodeIndexManager } from "../manager" import { CodeIndexManagerRegistry } from "../code-index-manager-registry" import { CodeIndexWorkspaceScope } from "../code-index-workspace-scope" import { CodeIndexStateManager } from "../state-manager" -import { EmbedderReadinessManager } from "../embedder-readiness-manager" vi.mock("../state-manager") @@ -61,13 +60,7 @@ describe("CodeIndexManagerRegistry", () => { it("uses the first workspace when there is no active editor", () => { CodeIndexManagerRegistry.getOrCreate(context) - expect(CodeIndexManager).toHaveBeenCalledWith( - "/first", - first.uri, - context, - expect.any(CodeIndexStateManager), - expect.any(EmbedderReadinessManager), - ) + expect(CodeIndexManager).toHaveBeenCalledWith("/first", first.uri, context, expect.any(CodeIndexStateManager)) }) it("prefers the active editor's workspace", () => { @@ -75,38 +68,20 @@ describe("CodeIndexManagerRegistry", () => { Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: editor }) vi.mocked(vscode.workspace.getWorkspaceFolder).mockReturnValue(second) expect(CodeIndexManagerRegistry.getOrCreate(context)).toBeDefined() - expect(CodeIndexManager).toHaveBeenCalledWith( - "/second", - second.uri, - context, - expect.any(CodeIndexStateManager), - expect.any(EmbedderReadinessManager), - ) + expect(CodeIndexManager).toHaveBeenCalledWith("/second", second.uri, context, expect.any(CodeIndexStateManager)) }) it("falls back to the first workspace for an editor outside all folders", () => { Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: makeTextEditor() }) CodeIndexManagerRegistry.getOrCreate(context) - expect(CodeIndexManager).toHaveBeenCalledWith( - "/first", - first.uri, - context, - expect.any(CodeIndexStateManager), - expect.any(EmbedderReadinessManager), - ) + expect(CodeIndexManager).toHaveBeenCalledWith("/first", first.uri, context, expect.any(CodeIndexStateManager)) }) it("gives an explicit path priority over the active editor", () => { Object.defineProperty(vscode.window, "activeTextEditor", { configurable: true, value: makeTextEditor() }) vi.mocked(vscode.workspace.getWorkspaceFolder).mockReturnValue(first) expect(CodeIndexManagerRegistry.getOrCreate(context, "/second")).toBeDefined() - expect(CodeIndexManager).toHaveBeenCalledWith( - "/second", - second.uri, - context, - expect.any(CodeIndexStateManager), - expect.any(EmbedderReadinessManager), - ) + expect(CodeIndexManager).toHaveBeenCalledWith("/second", second.uri, context, expect.any(CodeIndexStateManager)) }) it("preserves the actual remote workspace URI", () => { @@ -116,13 +91,7 @@ describe("CodeIndexManagerRegistry", () => { value: [{ uri, name: "remote", index: 0 }], }) CodeIndexManagerRegistry.getOrCreate(context, "/remote") - expect(CodeIndexManager).toHaveBeenCalledWith( - "/remote", - uri, - context, - expect.any(CodeIndexStateManager), - expect.any(EmbedderReadinessManager), - ) + expect(CodeIndexManager).toHaveBeenCalledWith("/remote", uri, context, expect.any(CodeIndexStateManager)) expect(vi.mocked(CodeIndexManager).mock.calls[0][1]).toBe(uri) expect(vscode.Uri.file).not.toHaveBeenCalled() }) @@ -133,13 +102,7 @@ describe("CodeIndexManagerRegistry", () => { vi.mocked(vscode.Uri.file).mockReturnValue(uri) CodeIndexManagerRegistry.getOrCreate(context, uri.fsPath) expect(vscode.Uri.file).toHaveBeenCalledWith(uri.fsPath) - expect(CodeIndexManager).toHaveBeenCalledWith( - uri.fsPath, - uri, - context, - expect.any(CodeIndexStateManager), - expect.any(EmbedderReadinessManager), - ) + expect(CodeIndexManager).toHaveBeenCalledWith(uri.fsPath, uri, context, expect.any(CodeIndexStateManager)) }) it("constructs a file URI for an explicit path not matching any open workspace folder", () => { @@ -153,7 +116,6 @@ describe("CodeIndexManagerRegistry", () => { uri, context, expect.any(CodeIndexStateManager), - expect.any(EmbedderReadinessManager), ) }) diff --git a/src/services/code-index/__tests__/code-index-status-manager.spec.ts b/src/services/code-index/__tests__/code-index-status-manager.spec.ts index 4b1a585a09..c73fb4f03b 100644 --- a/src/services/code-index/__tests__/code-index-status-manager.spec.ts +++ b/src/services/code-index/__tests__/code-index-status-manager.spec.ts @@ -8,7 +8,6 @@ import { } from "../../../test-utils/vscode" import { CodeIndexManager } from "../manager" import { CodeIndexStateManager } from "../state-manager" -import { EmbedderReadinessManager } from "../embedder-readiness-manager" import { CodeIndexStatusManager, type CodeIndexStatus } from "../code-index-status-manager" // Reload the real workspace resolver against this suite's VS Code mock, @@ -41,13 +40,11 @@ function makeSource(workspacePath: string) { emit = () => listener(status) return subscription } - const stateManager = new CodeIndexStateManager() const manager = new CodeIndexManager( workspacePath, makeUri(workspacePath), makeExtensionContext(), - stateManager, - new EmbedderReadinessManager(stateManager), + new CodeIndexStateManager(), ) Object.defineProperty(manager, "onProgressUpdate", { value: vi.fn(event), configurable: true }) return Object.assign(manager, { diff --git a/src/services/code-index/__tests__/code-index-workspace-scope.spec.ts b/src/services/code-index/__tests__/code-index-workspace-scope.spec.ts index a1f4a297bf..417d7284c1 100644 --- a/src/services/code-index/__tests__/code-index-workspace-scope.spec.ts +++ b/src/services/code-index/__tests__/code-index-workspace-scope.spec.ts @@ -2,7 +2,6 @@ import { makeExtensionContext, makeUri } from "../../../test-utils/vscode" import { CodeIndexManager } from "../manager" import { CodeIndexWorkspaceScope } from "../code-index-workspace-scope" import { CodeIndexStateManager } from "../state-manager" -import { EmbedderReadinessManager } from "../embedder-readiness-manager" import { WorkspaceIndexingEnablementManager } from "../workspace-indexing-enablement-manager" vi.mock("../state-manager") @@ -58,7 +57,6 @@ describe("CodeIndexWorkspaceScope", () => { uri, context, vi.mocked(CodeIndexStateManager).mock.instances[0], - expect.any(EmbedderReadinessManager), ) expect(scope.codeIndexManager).toBe(manager) expect(() => scope.init()).toThrow("Code index workspace scope is already initialized") @@ -100,9 +98,6 @@ describe("CodeIndexWorkspaceScope", () => { const calls = vi.mocked(CodeIndexManager).mock.calls expect(CodeIndexStateManager).toHaveBeenCalledTimes(2) expect(calls[1][3]).not.toBe(calls[0][3]) - expect(calls[1][4]).not.toBe(calls[0][4]) - expect(calls[0][4]["stateManager"]).toBe(calls[0][3]) - expect(calls[1][4]["stateManager"]).toBe(calls[1][3]) expect(scope["_stateManager"]).toBe(calls[1][3]) }) @@ -118,9 +113,6 @@ describe("CodeIndexWorkspaceScope", () => { expect(calls[0][3]).toBe(vi.mocked(CodeIndexStateManager).mock.instances[0]) expect(calls[1][3]).toBe(vi.mocked(CodeIndexStateManager).mock.instances[1]) expect(calls[1][3]).not.toBe(calls[0][3]) - expect(calls[1][4]).not.toBe(calls[0][4]) - expect(calls[0][4]["stateManager"]).toBe(calls[0][3]) - expect(calls[1][4]["stateManager"]).toBe(calls[1][3]) }) it("clears its reference even when manager disposal throws", () => { diff --git a/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts b/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts deleted file mode 100644 index 123c105295..0000000000 --- a/src/services/code-index/__tests__/embedder-readiness-manager.spec.ts +++ /dev/null @@ -1,151 +0,0 @@ -import { EmbedderReadinessManager } from "../embedder-readiness-manager" -import type { IndexingState } from "../state-manager" -import type { CodeIndexServiceFactory } from "../service-factory" -import type { IEmbedder } from "../interfaces" - -describe("EmbedderReadinessManager", () => { - const setup = () => { - const stateManager = { - state: "Standby" as IndexingState, - setSystemState: vi.fn(), - } - const embedder: IEmbedder = { - embedderInfo: { name: "ollama" }, - createEmbeddings: vi.fn(), - validateConfiguration: vi.fn(), - } - const serviceFactory = { - validateEmbedder: vi.fn(), - } as unknown as CodeIndexServiceFactory - return { stateManager, manager: new EmbedderReadinessManager(stateManager), serviceFactory, embedder } - } - - it("reports the latest validation failure while the manager remains in standby", async () => { - const { stateManager, manager, serviceFactory, embedder } = setup() - vi.mocked(serviceFactory.validateEmbedder).mockResolvedValue({ - valid: false, - error: "Current validation failure", - }) - - void manager.validate(serviceFactory, embedder) - await Promise.resolve() - - expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith("Error", "Current validation failure") - }) - - it.each([undefined, ""])("uses the fallback for a validation failure with message %j", async (error) => { - const { stateManager, manager, serviceFactory, embedder } = setup() - vi.mocked(serviceFactory.validateEmbedder).mockResolvedValue({ valid: false, error }) - - await manager.validate(serviceFactory, embedder) - - expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith( - "Error", - "Embedder configuration validation failed", - ) - }) - - it("uses the fallback for a non-Error rejection", async () => { - const { stateManager, manager, serviceFactory, embedder } = setup() - vi.mocked(serviceFactory.validateEmbedder).mockRejectedValue("Unexpected rejection") - - await expect(manager.validate(serviceFactory, embedder)).resolves.toBeUndefined() - - expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith( - "Error", - "Embedder configuration validation failed", - ) - }) - - it("reports an unexpected validation rejection while the manager remains in standby", async () => { - const { stateManager, manager, serviceFactory, embedder } = setup() - vi.mocked(serviceFactory.validateEmbedder).mockRejectedValue(new Error("Validation crashed")) - - void manager.validate(serviceFactory, embedder) - await Promise.resolve() - await Promise.resolve() - - expect(stateManager.setSystemState).toHaveBeenCalledExactlyOnceWith("Error", "Validation crashed") - }) - - it("ignores validation results after another operation changes the status", async () => { - const { stateManager, manager, serviceFactory, embedder } = setup() - let finishValidation!: (result: { valid: boolean; error?: string }) => void - vi.mocked(serviceFactory.validateEmbedder).mockReturnValue( - new Promise((resolve) => { - finishValidation = resolve - }), - ) - void manager.validate(serviceFactory, embedder) - - stateManager.state = "Indexed" - finishValidation({ valid: false, error: "Stale validation failure" }) - await Promise.resolve() - - expect(stateManager.setSystemState).not.toHaveBeenCalled() - }) - - it("ignores an older result when a newer validation has started", async () => { - const { stateManager, manager, serviceFactory, embedder } = setup() - let finishFirstValidation!: (result: { valid: boolean; error?: string }) => void - vi.mocked(serviceFactory.validateEmbedder) - .mockReturnValueOnce( - new Promise((resolve) => { - finishFirstValidation = resolve - }), - ) - .mockResolvedValueOnce({ valid: true }) - void manager.validate(serviceFactory, embedder) - void manager.validate(serviceFactory, embedder) - await Promise.resolve() - - finishFirstValidation({ valid: false, error: "Older validation failure" }) - await Promise.resolve() - - expect(stateManager.setSystemState).not.toHaveBeenCalled() - }) - - it.each(["invalidation", "newer validation", "status change"] as const)( - "ignores a pending rejection after %s", - async (scenario) => { - const { stateManager, manager, serviceFactory, embedder } = setup() - let rejectValidation!: (error: Error) => void - vi.mocked(serviceFactory.validateEmbedder).mockReturnValueOnce( - new Promise((_, reject) => { - rejectValidation = reject - }), - ) - const validation = manager.validate(serviceFactory, embedder) - - if (scenario === "invalidation") { - manager.invalidate() - } else if (scenario === "newer validation") { - vi.mocked(serviceFactory.validateEmbedder).mockResolvedValueOnce({ valid: true }) - await manager.validate(serviceFactory, embedder) - } else { - stateManager.state = "Indexed" - } - - rejectValidation(new Error("Stale rejection")) - await expect(validation).resolves.toBeUndefined() - expect(stateManager.setSystemState).not.toHaveBeenCalled() - }, - ) - - it("ignores a pending result after invalidation", async () => { - const { stateManager, manager, serviceFactory, embedder } = setup() - let finishValidation!: (result: { valid: boolean; error?: string }) => void - vi.mocked(serviceFactory.validateEmbedder).mockReturnValue( - new Promise((resolve) => { - finishValidation = resolve - }), - ) - void manager.validate(serviceFactory, embedder) - - manager.invalidate() - finishValidation({ valid: false, error: "Invalidated validation failure" }) - await Promise.resolve() - - expect(stateManager.setSystemState).not.toHaveBeenCalled() - }) -}) diff --git a/src/services/code-index/__tests__/manager.spec.ts b/src/services/code-index/__tests__/manager.spec.ts index e537a6be6d..5038cc247e 100644 --- a/src/services/code-index/__tests__/manager.spec.ts +++ b/src/services/code-index/__tests__/manager.spec.ts @@ -476,24 +476,19 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { ;(manager as any)._configManager = mockConfigManager }) - it("should create indexing services without waiting for startup embedder validation", async () => { - let finishValidation!: (result: { valid: boolean; error?: string }) => void - mockServiceFactoryInstance.validateEmbedder.mockReturnValue( - new Promise((resolve) => { - finishValidation = resolve - }), - ) + it("should create indexing services without a startup embedder validation request", async () => { + mockServiceFactoryInstance.validateEmbedder.mockResolvedValue({ + valid: false, + error: "Embedder unavailable", + }) await manager["_recreateServices"]() expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled() - expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalledWith(mockEmbedder) + expect(mockServiceFactoryInstance.validateEmbedder).not.toHaveBeenCalled() expect(mockStateManager.setSystemState).not.toHaveBeenCalledWith("Error", expect.any(String)) expect(manager["_orchestrator"]).toBeInstanceOf(CodeIndexOrchestrator) expect(manager["_searchService"]).toBeInstanceOf(CodeIndexSearchService) - - finishValidation({ valid: true }) - await Promise.resolve() }) it("should handle embedder creation failure", async () => { @@ -647,7 +642,7 @@ describe("CodeIndexManager - handleSettingsChange regression", () => { // Assert - manager should be initialized again expect(manager.isInitialized).toBe(true) expect(mockServiceFactoryInstance.createServices).toHaveBeenCalled() - expect(mockServiceFactoryInstance.validateEmbedder).toHaveBeenCalled() + expect(mockServiceFactoryInstance.validateEmbedder).not.toHaveBeenCalled() }) it("should be safe to call when not in error state (idempotent)", async () => { diff --git a/src/services/code-index/code-index-workspace-scope.ts b/src/services/code-index/code-index-workspace-scope.ts index b6f348b1ff..c8481f6595 100644 --- a/src/services/code-index/code-index-workspace-scope.ts +++ b/src/services/code-index/code-index-workspace-scope.ts @@ -3,7 +3,6 @@ import type * as vscode from "vscode" import { CodeIndexManager } from "./manager" import { CodeIndexStateManager } from "./state-manager" import { WorkspaceIndexingEnablementManager } from "./workspace-indexing-enablement-manager" -import { EmbedderReadinessManager } from "./embedder-readiness-manager" /** Owns code-index services for one workspace; initialization remains with existing callers. */ export class CodeIndexWorkspaceScope implements vscode.Disposable { @@ -39,13 +38,11 @@ export class CodeIndexWorkspaceScope implements vscode.Disposable { throw new Error("Code index workspace scope is already initialized") } this._stateManager = new CodeIndexStateManager() - const embedderReadinessManager = new EmbedderReadinessManager(this._stateManager) this._codeIndexManager = new CodeIndexManager( this.workspacePath, this.folderUri, this.context, this._stateManager, - embedderReadinessManager, ) this._workspaceIndexingEnablementManager = new WorkspaceIndexingEnablementManager(this._codeIndexManager) this._isInitialized = true diff --git a/src/services/code-index/embedder-readiness-manager.ts b/src/services/code-index/embedder-readiness-manager.ts deleted file mode 100644 index 38cd45fb77..0000000000 --- a/src/services/code-index/embedder-readiness-manager.ts +++ /dev/null @@ -1,35 +0,0 @@ -import type { CodeIndexStateManager } from "./state-manager" -import type { IEmbedder } from "./interfaces" -import type { CodeIndexServiceFactory } from "./service-factory" - -type ValidationResult = { valid: boolean; error?: string } - -/** Tracks embedder readiness without blocking initialization and discards stale results. */ -export class EmbedderReadinessManager { - private generation = 0 - - public constructor(private readonly stateManager: Pick) {} - - public async validate(serviceFactory: CodeIndexServiceFactory, embedder: IEmbedder): Promise { - const generation = ++this.generation - try { - const result: ValidationResult = await serviceFactory.validateEmbedder(embedder) - if (!this.canApply(generation) || result.valid) return - this.stateManager.setSystemState("Error", result.error || "Embedder configuration validation failed") - } catch (error) { - if (!this.canApply(generation)) return - this.stateManager.setSystemState( - "Error", - error instanceof Error ? error.message : "Embedder configuration validation failed", - ) - } - } - - public invalidate(): void { - this.generation++ - } - - private canApply(generation: number): boolean { - return generation === this.generation && this.stateManager.state === "Standby" - } -} diff --git a/src/services/code-index/manager.ts b/src/services/code-index/manager.ts index fb7a7496a2..aa382e2131 100644 --- a/src/services/code-index/manager.ts +++ b/src/services/code-index/manager.ts @@ -16,7 +16,6 @@ import path from "path" import { t } from "../../i18n" import { TelemetryService } from "@roo-code/telemetry" import { TelemetryEventName } from "@roo-code/types" -import { EmbedderReadinessManager } from "./embedder-readiness-manager" export class CodeIndexManager { // Specialized class instances @@ -41,7 +40,6 @@ export class CodeIndexManager { folderUri: vscode.Uri, context: vscode.ExtensionContext, stateManager: CodeIndexStateManager, - private readonly embedderReadinessManager: EmbedderReadinessManager, ) { this.workspacePath = workspacePath this._folderUri = folderUri @@ -236,7 +234,6 @@ export class CodeIndexManager { * Stops any in-progress indexing operation and the file watcher. */ public stopIndexing(): void { - this.embedderReadinessManager.invalidate() if (this._sembleProvider) { this._sembleProvider.stopIndexing() return @@ -279,7 +276,6 @@ export class CodeIndexManager { } this._isRecoveringFromError = true - this.embedderReadinessManager.invalidate() try { // Clear error state this._stateManager.setSystemState("Standby", "") @@ -419,7 +415,6 @@ export class CodeIndexManager { ignoreInstance, rooIgnoreController, ) - void this.embedderReadinessManager.validate(this._serviceFactory, embedder) // (Re)Initialize orchestrator this._orchestrator = new CodeIndexOrchestrator(