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
32 changes: 29 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,31 @@ 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("continues cleanup when disposing the code index registry fails", 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("index cleanup failed")
})
await expect(deactivate()).resolves.toBeUndefined()
const channel = vi.mocked(vscode.window.createOutputChannel).mock.results.at(-1)?.value
expect(channel?.appendLine).toHaveBeenCalledWith(
"Failed to dispose code index managers: index cleanup failed",
)
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
37 changes: 24 additions & 13 deletions src/core/webview/ClineProvider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -85,8 +85,10 @@ 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 type { IndexProgressUpdate } from "../../services/code-index/interfaces/manager"
import { CodeIndexManagerRegistry } from "../../services/code-index/code-index-manager-registry"
import { CodeIndexWebviewMessageHandler } from "../../services/code-index/code-index-webview-message-handler"
import type { CodeIndexScope } from "../../services/code-index/code-index-scope"
import type { CodeIndexManager } from "../../services/code-index/manager"
import { MdmService } from "../../services/mdm/MdmService"
import { SkillsManager } from "../../services/skills/SkillsManager"

Expand All @@ -109,6 +111,7 @@ import { CustomModesManager } from "../config/CustomModesManager"
import { Task } from "../task/Task"

import { webviewMessageHandler } from "./webviewMessageHandler"
import { WebviewMessageHandlerRegistry } from "./WebviewMessageHandlerRegistry"
import type { ClineMessage, TodoItem } from "@roo-code/types"
import {
type ApiMessage,
Expand Down Expand Up @@ -312,6 +315,7 @@ export class ClineProvider
public readonly latestAnnouncementId = "sep-2026-v3.82.0-gateway-portability-free-models" // v3.82.0 portable Zoo Gateway keys, free MiniMax-M3, and new models
public readonly providerSettingsManager: ProviderSettingsManager
public readonly customModesManager: CustomModesManager
private readonly webviewMessageHandlerRegistry: WebviewMessageHandlerRegistry

constructor(
readonly context: vscode.ExtensionContext,
Expand All @@ -326,6 +330,9 @@ export class ClineProvider
ClineProvider.PENDING_OPERATION_TIMEOUT_MS,
(message) => this.log(message),
)
this.webviewMessageHandlerRegistry = new WebviewMessageHandlerRegistry([
new CodeIndexWebviewMessageHandler(this),
])

ClineProvider.activeInstances.add(this)

Expand Down Expand Up @@ -1701,7 +1708,7 @@ export class ClineProvider
*/
private setWebviewMessageListener(webview: vscode.Webview) {
const onReceiveMessage = async (message: WebviewMessage) =>
webviewMessageHandler(this, message, this.marketplaceManager)
webviewMessageHandler(this, message, this.marketplaceManager, this.webviewMessageHandlerRegistry)

const messageDisposable = webview.onDidReceiveMessage(onReceiveMessage)
this.webviewDisposables.push(messageDisposable)
Expand Down Expand Up @@ -3289,15 +3296,21 @@ export class ClineProvider
* @returns CodeIndexManager instance for the current workspace or the default one
*/
public getCurrentWorkspaceCodeIndexManager(): CodeIndexManager | undefined {
return CodeIndexManager.getInstance(this.context)
return this.getCurrentWorkspaceCodeIndexScope()?.codeIndexManager
}

public getCurrentWorkspaceCodeIndexScope(): CodeIndexScope | undefined {
return CodeIndexManagerRegistry.getCodeIndexScope(this.context)
}

/**
* Updates the code index status subscription to listen to the current workspace manager
*/
private updateCodeIndexStatusSubscription(): void {
// Get the current workspace manager
const currentManager = this.getCurrentWorkspaceCodeIndexManager()
// Get the current workspace manager and its interface controller
const currentScope = this.getCurrentWorkspaceCodeIndexScope()
const currentManager = currentScope?.codeIndexManager
const currentController = currentScope?.codeIndexController

// If the manager hasn't changed, no need to update subscription
if (currentManager === this.codeIndexManager) {
Expand All @@ -3313,16 +3326,14 @@ export class ClineProvider
// Update the current workspace manager reference
this.codeIndexManager = currentManager

// Subscribe to the new manager's progress updates if it exists
if (currentManager) {
this.codeIndexStatusSubscription = currentManager.onProgressUpdate((update: IndexProgressUpdate) => {
// Subscribe to the complete interface state exposed by the controller.
if (currentManager && currentController) {
this.codeIndexStatusSubscription = currentController.onDidChangeCodeIndexState((codeIndexState) => {
// Only send updates if this manager is still the current one
if (currentManager === this.getCurrentWorkspaceCodeIndexManager()) {
// Get the full status from the manager to ensure we have all fields correctly formatted
const fullStatus = currentManager.getCurrentStatus()
void this.postMessageToWebview({
type: "indexingStatusUpdate",
values: fullStatus,
values: codeIndexState,
})
}
})
Expand All @@ -3334,7 +3345,7 @@ export class ClineProvider
// Send initial status for the current workspace
void this.postMessageToWebview({
type: "indexingStatusUpdate",
values: currentManager.getCurrentStatus(),
values: currentController.codeIndexState,
})
}
}
Expand Down
20 changes: 20 additions & 0 deletions src/core/webview/WebviewMessageHandlerRegistry.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import type { WebviewMessage } from "@roo-code/types"

export interface WebviewMessageFeatureHandler {
canHandle(message: WebviewMessage): boolean
handle(message: WebviewMessage): Promise<void>
}

export class WebviewMessageHandlerRegistry {
public constructor(private readonly webviewMessageFeatureHandlers: readonly WebviewMessageFeatureHandler[]) {}

public async handle(message: WebviewMessage): Promise<boolean> {
const webviewMessageFeatureHandler = this.webviewMessageFeatureHandlers.find((candidate) =>
candidate.canHandle(message),
)
if (!webviewMessageFeatureHandler) return false

await webviewMessageFeatureHandler.handle(message)
return true
}
}
Loading
Loading