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
This commit is contained in:
Roo Code 2026-02-17 19:23:40 +00:00
parent dc243e4cf9
commit aa0064802e
3 changed files with 88 additions and 1 deletions

View file

@ -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<Registe
await delay(100)
await vscode.commands.executeCommand("workbench.action.lockEditorGroup")
// Notify the API (if registered) so it can forward events from this tab provider.
onTabProviderCreatedCallback?.(tabProvider)
return tabProvider
}

View file

@ -0,0 +1,68 @@
import { describe, it, expect, vi, beforeEach } from "vitest"
import * as vscode from "vscode"
import { API } from "../api"
import { ClineProvider } from "../../core/webview/ClineProvider"
import { setOnTabProviderCreated } from "../../activate/registerCommands"
vi.mock("vscode")
vi.mock("../../core/webview/ClineProvider")
// Capture the callback registered by the API constructor.
let capturedCallback: ((provider: ClineProvider) => 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()
})
})

View file

@ -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<RooCodeEvents> 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))