diff --git a/src/activate/registerCommands.ts b/src/activate/registerCommands.ts index f02ee8309a3..445c5dc0c48 100644 --- a/src/activate/registerCommands.ts +++ b/src/activate/registerCommands.ts @@ -31,6 +31,19 @@ export function getVisibleProviderOrLog(outputChannel: vscode.OutputChannel): Cl let sidebarPanel: vscode.WebviewView | undefined = undefined let tabPanel: vscode.WebviewPanel | undefined = undefined +// Callback invoked when a tab provider is created via openClineInNewTab. +// This allows the API to register event listeners on dynamically created tab providers. +let onTabProviderCreatedCallback: ((provider: ClineProvider) => void) | undefined + +/** + * Register a callback that will be invoked whenever a new tab provider is + * created via `openClineInNewTab`. Used by the API to forward events from + * tab providers to the `RooCodeAPI` EventEmitter. + */ +export function setOnTabProviderCreated(callback: (provider: ClineProvider) => void): void { + onTabProviderCreatedCallback = callback +} + /** * Get the currently active panel * @returns WebviewPanelꈖWebviewView @@ -270,5 +283,8 @@ export const openClineInNewTab = async ({ context, outputChannel }: Omit void) | undefined + +vi.mock("../../activate/registerCommands", () => ({ + openClineInNewTab: vi.fn(), + setOnTabProviderCreated: vi.fn((cb: (provider: ClineProvider) => void) => { + capturedCallback = cb + }), +})) + +describe("API - Tab Provider Event Registration", () => { + let mockOutputChannel: vscode.OutputChannel + let mockSidebarProvider: ClineProvider + let api: API + + beforeEach(() => { + capturedCallback = undefined + + mockOutputChannel = { + appendLine: vi.fn(), + } as unknown as vscode.OutputChannel + + mockSidebarProvider = { + context: {} as vscode.ExtensionContext, + on: vi.fn(), + postMessageToWebview: vi.fn(), + getCurrentTaskStack: vi.fn().mockReturnValue([]), + getCurrentTask: vi.fn().mockReturnValue(undefined), + viewLaunched: true, + } as unknown as ClineProvider + + api = new API(mockOutputChannel, mockSidebarProvider, undefined, false) + }) + + it("should call setOnTabProviderCreated during construction", () => { + expect(setOnTabProviderCreated).toHaveBeenCalledWith(expect.any(Function)) + expect(capturedCallback).toBeDefined() + }) + + it("should register listeners on tab providers created via commands", () => { + const mockTabProvider = { + on: vi.fn(), + context: {} as vscode.ExtensionContext, + } as unknown as ClineProvider + + // Simulate a tab provider being created via command + capturedCallback!(mockTabProvider) + + // registerListeners calls provider.on(RooCodeEventName.TaskCreated, ...) + // so we verify that on() was called on the tab provider + expect(mockTabProvider.on).toHaveBeenCalled() + }) + + it("should register listeners on the sidebar provider during construction", () => { + // The sidebar provider should also have listeners registered + expect(mockSidebarProvider.on).toHaveBeenCalled() + }) +}) diff --git a/src/extension/api.ts b/src/extension/api.ts index 25c81a65896..270ed650cd2 100644 --- a/src/extension/api.ts +++ b/src/extension/api.ts @@ -25,7 +25,7 @@ import { CloudService } from "@roo-code/cloud" import { Package } from "../shared/package" import { ClineProvider } from "../core/webview/ClineProvider" -import { openClineInNewTab } from "../activate/registerCommands" +import { openClineInNewTab, setOnTabProviderCreated } from "../activate/registerCommands" import { getCommands } from "../services/command/commands" import { getModels } from "../api/providers/fetchers/modelCache" @@ -62,6 +62,9 @@ export class API extends EventEmitter implements RooCodeAPI { this.registerListeners(this.sidebarProvider) + // Ensure tab providers created via commands also get event listeners. + setOnTabProviderCreated((provider) => this.registerListeners(provider)) + if (socketPath) { const ipc = (this.ipc = new IpcServer(socketPath, this.log)) @@ -183,7 +186,6 @@ export class API extends EventEmitter implements RooCodeAPI { await vscode.commands.executeCommand("workbench.action.closeAllEditors") provider = await openClineInNewTab({ context: this.context, outputChannel: this.outputChannel }) - this.registerListeners(provider) } else { await vscode.commands.executeCommand(`${Package.name}.SidebarProvider.focus`)