Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
50 changes: 47 additions & 3 deletions src/__tests__/extension.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -139,9 +139,10 @@ vi.mock("../services/mcp/McpServerManager", () => ({
},
}))

vi.mock("../services/code-index/manager", () => ({
CodeIndexManager: {
getInstance: vi.fn().mockReturnValue(null),
vi.mock("../services/code-index/code-index-manager-registry", () => ({
CodeIndexManagerRegistry: {
getInstance: vi.fn().mockReturnValue(undefined),
disposeAll: vi.fn(),
},
}))

Expand Down Expand Up @@ -459,6 +460,49 @@ describe("extension.ts", () => {
vi.resetModules()
})

test("disposes the code index registry on deactivation", async () => {
const { CodeIndexManagerRegistry } = await import("../services/code-index/code-index-manager-registry")
const { activate, deactivate } = await import("../extension")
await activate(mockContext)
await deactivate()
expect(CodeIndexManagerRegistry.disposeAll).toHaveBeenCalledTimes(1)
})

test("logs aggregate code index disposal failures without duplicating their prefix", async () => {
const vscode = await import("vscode")
const { CodeIndexManagerRegistry } = await import("../services/code-index/code-index-manager-registry")
const { CodeIndexDisposalError } = await import("../services/code-index/errors/code-index-disposal-error")
const { TerminalRegistry } = await import("../integrations/terminal/TerminalRegistry")
const { activate, deactivate } = await import("../extension")
await activate(mockContext)
vi.mocked(CodeIndexManagerRegistry.disposeAll).mockImplementationOnce(() => {
throw new CodeIndexDisposalError([new Error("index cleanup failed")])
})
await expect(deactivate()).resolves.toBeUndefined()
const channel = vi.mocked(vscode.window.createOutputChannel).mock.results.at(-1)?.value
expect(channel?.appendLine).toHaveBeenCalledWith(
"CodeIndexDisposalError: Failed to dispose code index managers (1 errors):\n1. index cleanup failed",
)
expect(TerminalRegistry.cleanup).toHaveBeenCalledTimes(1)
})

test("labels unexpected code index disposal failures and continues cleanup", async () => {
const vscode = await import("vscode")
const { CodeIndexManagerRegistry } = await import("../services/code-index/code-index-manager-registry")
const { TerminalRegistry } = await import("../integrations/terminal/TerminalRegistry")
const { activate, deactivate } = await import("../extension")
await activate(mockContext)
vi.mocked(CodeIndexManagerRegistry.disposeAll).mockImplementationOnce(() => {
throw new Error("unexpected cleanup failure")
})
await expect(deactivate()).resolves.toBeUndefined()
const channel = vi.mocked(vscode.window.createOutputChannel).mock.results.at(-1)?.value
expect(channel?.appendLine).toHaveBeenCalledWith(
"Unexpected error while disposing code index managers: unexpected cleanup failure",
)
expect(TerminalRegistry.cleanup).toHaveBeenCalledTimes(1)
})

test("still runs terminal cleanup when telemetry shutdown rejects", async () => {
const { TelemetryService } = await import("@roo-code/telemetry")
const { Terminal } = await import("../integrations/terminal/Terminal")
Expand Down
4 changes: 2 additions & 2 deletions src/activate/__tests__/registerCommands.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -67,8 +67,8 @@ vi.mock("../../core/config/importExport", () => ({
importSettingsWithFeedback: vi.fn(),
}))

vi.mock("../../services/code-index/manager", () => ({
CodeIndexManager: {
vi.mock("../../services/code-index/code-index-manager-registry", () => ({
CodeIndexManagerRegistry: {
getInstance: vi.fn(),
},
}))
Expand Down
4 changes: 2 additions & 2 deletions src/activate/registerCommands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { ClineProvider } from "../core/webview/ClineProvider"
import { ContextProxy } from "../core/config/ContextProxy"
import { focusPanel } from "../utils/focusPanel"
import { handleNewTask } from "./handleTask"
import { CodeIndexManager } from "../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../services/code-index/code-index-manager-registry"
import { importSettingsWithFeedback } from "../core/config/importExport"
import { MdmService } from "../services/mdm/MdmService"
import { registerRipgrepDiagnosticCommand } from "../services/ripgrep/diagnostic"
Expand Down Expand Up @@ -227,7 +227,7 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit<Registe
// don't need to use that event).
// https://github.com/microsoft/vscode-extension-samples/blob/main/webview-sample/src/extension.ts
const contextProxy = await ContextProxy.getInstance(context)
const codeIndexManager = CodeIndexManager.getInstance(context)
const codeIndexManager = CodeIndexManagerRegistry.getInstance(context)

// Get the existing MDM service instance to ensure consistent policy enforcement
let mdmService: MdmService | undefined
Expand Down
4 changes: 2 additions & 2 deletions src/core/prompts/system.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import { formatLanguage } from "../../shared/language"
import { isEmpty } from "../../utils/object"

import { McpHub } from "../../services/mcp/McpHub"
import { CodeIndexManager } from "../../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import { SkillsManager } from "../../services/skills/SkillsManager"

import type { SystemPromptSettings } from "./types"
Expand Down Expand Up @@ -79,7 +79,7 @@ async function generatePrompt(
}
const shouldIncludeMcp = hasMcpGroup && hasMcpServers

const codeIndexManager = CodeIndexManager.getInstance(context, cwd)
const codeIndexManager = CodeIndexManagerRegistry.getInstance(context, cwd)

// Tool calling is native-only.
const effectiveProtocol = "native"
Expand Down
4 changes: 3 additions & 1 deletion src/core/task/__tests__/Task.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -130,7 +130,8 @@ vi.mock("p-wait-for", () => ({
default: vi.fn().mockImplementation(async () => Promise.resolve()),
}))

vi.mock("vscode", () => {
vi.mock("vscode", async () => {
const { makeUri } = await import("../../../test-utils/vscode")
const mockDisposable = { dispose: vi.fn() }
const mockEventEmitter = { event: vi.fn(), fire: vi.fn() }
const mockTextDocument = { uri: { fsPath: "/mock/workspace/path/file.ts" } }
Expand All @@ -139,6 +140,7 @@ vi.mock("vscode", () => {
const mockTabGroup = { tabs: [mockTab] }

return {
Uri: { file: vi.fn((filePath: string) => makeUri(filePath)) },
TabInputTextDiff: vi.fn(),
CodeActionKind: {
QuickFix: { value: "quickfix" },
Expand Down
4 changes: 2 additions & 2 deletions src/core/task/build-tools.ts
Original file line number Diff line number Diff line change
Expand Up @@ -96,8 +96,8 @@ export async function buildNativeToolsArrayWithRestrictions(options: BuildToolsO
const mcpHub = provider.getMcpHub()

// Get CodeIndexManager for feature checking.
const { CodeIndexManager } = await import("../../services/code-index/manager")
const codeIndexManager = CodeIndexManager.getInstance(provider.context, cwd)
const { CodeIndexManagerRegistry } = await import("../../services/code-index/code-index-manager-registry")
const codeIndexManager = CodeIndexManagerRegistry.getInstance(provider.context, cwd)

// Build settings object for tool filtering.
const filterSettings = {
Expand Down
4 changes: 2 additions & 2 deletions src/core/tools/CodebaseSearchTool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as vscode from "vscode"
import path from "path"

import { Task } from "../task/Task"
import { CodeIndexManager } from "../../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import { getWorkspacePath } from "../../utils/path"
import { formatResponse } from "../prompts/responses"
import { VectorStoreSearchResult } from "../../services/code-index/interfaces"
Expand Down Expand Up @@ -57,7 +57,7 @@ export class CodebaseSearchTool extends BaseTool<"codebase_search"> {
throw new Error("Extension context is not available.")
}

const manager = CodeIndexManager.getInstance(context)
const manager = CodeIndexManagerRegistry.getInstance(context)

if (!manager) {
throw new Error("CodeIndexManager is not available.")
Expand Down
5 changes: 3 additions & 2 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,8 @@ import { McpHub } from "../../services/mcp/McpHub"
import { McpServerManager } from "../../services/mcp/McpServerManager"
import { MarketplaceManager } from "../../services/marketplace"
import { ShadowCheckpointService } from "../../services/checkpoints/ShadowCheckpointService"
import { CodeIndexManager } from "../../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import type { CodeIndexManager } from "../../services/code-index/manager"
import type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
import { MdmService } from "../../services/mdm/MdmService"
import { SkillsManager } from "../../services/skills/SkillsManager"
Expand Down Expand Up @@ -3289,7 +3290,7 @@ export class ClineProvider
* @returns CodeIndexManager instance for the current workspace or the default one
*/
public getCurrentWorkspaceCodeIndexManager(): CodeIndexManager | undefined {
return CodeIndexManager.getInstance(this.context)
return CodeIndexManagerRegistry.getInstance(this.context)
}

/**
Expand Down
6 changes: 3 additions & 3 deletions src/core/webview/__tests__/ClineProvider.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3204,7 +3204,7 @@ describe("webviewMessageHandler no-floating-promises coverage", () => {
})

it("catches auto-enabled indexing failures and posts the resulting status", async () => {
const { CodeIndexManager } = await import("../../../services/code-index/manager")
const { CodeIndexManagerRegistry } = await import("../../../services/code-index/code-index-manager-registry")
let workspaceEnabled = false
const manager = createIndexManager({
setAutoEnableDefault: vi.fn().mockImplementation(async () => {
Expand All @@ -3214,8 +3214,8 @@ describe("webviewMessageHandler no-floating-promises coverage", () => {
})
Object.defineProperty(manager, "isWorkspaceEnabled", { get: () => workspaceEnabled })
const getAllInstances = vi
.spyOn(CodeIndexManager, "getAllInstances")
.mockReturnValue([manager] as unknown as ReturnType<typeof CodeIndexManager.getAllInstances>)
.spyOn(CodeIndexManagerRegistry, "getAllInstances")
.mockReturnValue([manager] as unknown as ReturnType<typeof CodeIndexManagerRegistry.getAllInstances>)
const provider = createProvider({
getCurrentWorkspaceCodeIndexManager: vi.fn().mockReturnValue(manager),
})
Expand Down
4 changes: 2 additions & 2 deletions src/core/webview/webviewMessageHandler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@ import { Package } from "../../shared/package"
import { type RouterName, toRouterName } from "../../shared/api"
import { MessageEnhancer } from "./messageEnhancer"

import { CodeIndexManager } from "../../services/code-index/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import { checkExistKey } from "../../shared/checkExistApiConfig"
import { getRouterRemovalMessage, getRouterUnavailableSignInMessage } from "../config/routerRemoval"
import { experimentDefault } from "../../shared/experiments"
Expand Down Expand Up @@ -3311,7 +3311,7 @@ export const webviewMessageHandler = async (
return
}
// Capture prior state for every manager before persisting the global change
const allManagers = CodeIndexManager.getAllInstances()
const allManagers = CodeIndexManagerRegistry.getAllInstances()
const priorStates = new Map(allManagers.map((m) => [m, m.isWorkspaceEnabled]))
await manager.setAutoEnableDefault(message.bool ?? true)
// Apply stop/start to every affected manager
Expand Down
2 changes: 1 addition & 1 deletion src/eslint-suppressions.json
Original file line number Diff line number Diff line change
Expand Up @@ -1301,7 +1301,7 @@
},
"services/code-index/__tests__/manager.spec.ts": {
"@typescript-eslint/no-explicit-any": {
"count": 89
"count": 81
}
},
"services/code-index/__tests__/orchestrator.spec.ts": {
Expand Down
30 changes: 22 additions & 8 deletions src/extension.ts
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,8 @@
import { openAiCodexOAuthManager } from "./integrations/openai-codex/oauth"
import { kimiCodeOAuthManager } from "./integrations/kimi-code/oauth"
import { McpServerManager } from "./services/mcp/McpServerManager"
import { CodeIndexManager } from "./services/code-index/manager"
import { CodeIndexManagerRegistry } from "./services/code-index/code-index-manager-registry"
import { CodeIndexDisposalError } from "./services/code-index/errors/code-index-disposal-error"
import { MdmService } from "./services/mdm/MdmService"
import { migrateSettings } from "./utils/migrateSettings"
import { autoImportSettings } from "./utils/autoImportSettings"
Expand Down Expand Up @@ -196,28 +197,24 @@
)

// Initialize code index managers for all workspace folders.
const codeIndexManagers: CodeIndexManager[] = []

if (vscode.workspace.workspaceFolders) {
for (const folder of vscode.workspace.workspaceFolders) {
const manager = CodeIndexManager.getInstance(context, folder.uri.fsPath)
const manager = CodeIndexManagerRegistry.getInstance(context, folder.uri.fsPath)
Comment thread
WebMad marked this conversation as resolved.

if (manager) {
codeIndexManagers.push(manager)

// Initialize in background; do not block extension activation
void manager.initialize(contextProxy).catch((error) => {
const message = error instanceof Error ? error.message : String(error)
outputChannel.appendLine(
`[CodeIndexManager] Error during background CodeIndexManager configuration/indexing for ${folder.uri.fsPath}: ${message}`,
)
})

context.subscriptions.push(manager)
}
}
}

context.subscriptions.push({ dispose: disposeCodeIndexManagers })

Check warning on line 216 in src/extension.ts

View workflow job for this annotation

GitHub Actions / mutation-diff

Mutation test advisory

Survived ObjectLiteral mutant (replacement: {}). See the job summary for the complete list and resolution guidance.

// Initialize the provider *before* the Roo Code Cloud service.
const provider = new ClineProvider(context, outputChannel, "sidebar", contextProxy, mdmService)

Expand Down Expand Up @@ -380,10 +377,27 @@
return new API(outputChannel, provider, socketPath, enableLogging)
}

function disposeCodeIndexManagers(): void {
try {
CodeIndexManagerRegistry.disposeAll()
Comment thread
WebMad marked this conversation as resolved.
} catch (error) {
if (error instanceof CodeIndexDisposalError) {
outputChannel.appendLine(`CodeIndexDisposalError: ${error.message}`)
return
}

outputChannel.appendLine(
`Unexpected error while disposing code index managers: ${error instanceof Error ? error.message : String(error)}`,
)
}
}

// This method is called when your extension is deactivated.
export async function deactivate() {
outputChannel.appendLine(`${Package.name} extension deactivated`)

disposeCodeIndexManagers()

if (cloudService && CloudService.hasInstance()) {
try {
if (settingsUpdatedHandler) {
Expand Down
Loading
Loading