From aa0064802edb9df5c389aec69df826ba29849af4 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 17 Feb 2026 19:23:40 +0000 Subject: [PATCH 1/2] fix: register API event listeners on tab providers created via commands When opening Roo Code in a new tab via command palette (openInNewTab or popoutButtonClicked), the returned ClineProvider was discarded without calling registerListeners(). This meant events like taskCreated, message, taskCompleted etc. were never forwarded through the RooCodeAPI EventEmitter. This fix adds a callback hook (setOnTabProviderCreated) in registerCommands that the API registers during construction. When openClineInNewTab creates a new tab provider, it invokes this callback so the API can attach its event forwarding listeners. Fixes #11507 --- src/activate/registerCommands.ts | 16 +++++ .../__tests__/api-tab-events.spec.ts | 68 +++++++++++++++++++ src/extension/api.ts | 5 +- 3 files changed, 88 insertions(+), 1 deletion(-) create mode 100644 src/extension/__tests__/api-tab-events.spec.ts 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..34d02645617 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)) From a6a9f1569a02a92a8a7c5f7e034a518bd158b486 Mon Sep 17 00:00:00 2001 From: Roo Code Date: Tue, 17 Feb 2026 19:36:42 +0000 Subject: [PATCH 2/2] fix: remove duplicate registerListeners call in startNewTask The setOnTabProviderCreated callback already registers listeners on tab providers created via openClineInNewTab. The explicit registerListeners call in startNewTask caused double event registration for tab providers created through that code path. --- src/extension/api.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/src/extension/api.ts b/src/extension/api.ts index 34d02645617..270ed650cd2 100644 --- a/src/extension/api.ts +++ b/src/extension/api.ts @@ -186,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`)