Skip to content

Commit b38994f

Browse files
celestial-vaultElephant Lumps
andauthored
Migrate mcpButtonClicked protobus (RooCodeInc#3975)
* migrate mcpButtonClicked * changeset * only send event to matching webview type --------- Co-authored-by: Elephant Lumps <[email protected]>
1 parent d846c2c commit b38994f

File tree

7 files changed

+123
-26
lines changed

7 files changed

+123
-26
lines changed

.changeset/fifty-shoes-tease.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
---
2+
"claude-dev": minor
3+
---
4+
5+
Migrate mcpButtonClicked to protobus

proto/ui.proto

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,18 @@ option java_multiple_files = true;
66

77
import "common.proto";
88

9+
// Enum for webview provider types
10+
enum WebviewProviderType {
11+
SIDEBAR = 0;
12+
TAB = 1;
13+
}
14+
15+
// Define a new message type for webview provider info
16+
message WebviewProviderTypeRequest {
17+
Metadata metadata = 1;
18+
WebviewProviderType providerType = 2;
19+
}
20+
921
// UiService provides methods for managing UI interactions
1022
service UiService {
1123
// Scrolls to a specific settings section in the settings view
@@ -16,4 +28,7 @@ service UiService {
1628

1729
// Subscribe to addToInput events (when user adds content via context menu)
1830
rpc subscribeToAddToInput(EmptyRequest) returns (stream String);
31+
32+
// Subscribe to MCP button clicked events
33+
rpc subscribeToMcpButtonClicked(WebviewProviderTypeRequest) returns (stream Empty);
1934
}
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
import { Controller } from "../index"
2+
import { Empty } from "@shared/proto/common"
3+
import { WebviewProviderType, WebviewProviderTypeRequest } from "@shared/proto/ui"
4+
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
5+
6+
// Track subscriptions with their provider type
7+
const mcpButtonClickedSubscriptions = new Map<StreamingResponseHandler, WebviewProviderType>()
8+
9+
/**
10+
* Subscribe to mcpButtonClicked events
11+
* @param controller The controller instance
12+
* @param request The webview provider type request
13+
* @param responseStream The streaming response handler
14+
* @param requestId The ID of the request (passed by the gRPC handler)
15+
*/
16+
export async function subscribeToMcpButtonClicked(
17+
controller: Controller,
18+
request: WebviewProviderTypeRequest,
19+
responseStream: StreamingResponseHandler,
20+
requestId?: string,
21+
): Promise<void> {
22+
const providerType = request.providerType
23+
console.log(`[DEBUG] set up mcpButtonClicked subscription for ${WebviewProviderType[providerType]} webview`)
24+
25+
// Store the subscription with its provider type
26+
mcpButtonClickedSubscriptions.set(responseStream, providerType)
27+
28+
// Register cleanup when the connection is closed
29+
const cleanup = () => {
30+
mcpButtonClickedSubscriptions.delete(responseStream)
31+
}
32+
33+
// Register the cleanup function with the request registry if we have a requestId
34+
if (requestId) {
35+
getRequestRegistry().registerRequest(requestId, cleanup, { type: "mcpButtonClicked_subscription" }, responseStream)
36+
}
37+
}
38+
39+
/**
40+
* Send a mcpButtonClicked event to active subscribers based on webview type
41+
* @param webviewType The type of webview that triggered the event (SIDEBAR or TAB)
42+
*/
43+
export async function sendMcpButtonClickedEvent(webviewType?: WebviewProviderType): Promise<void> {
44+
const event: Empty = {}
45+
46+
// Process all subscriptions, filtering based on the source
47+
const promises = Array.from(mcpButtonClickedSubscriptions.entries()).map(async ([responseStream, providerType]) => {
48+
// Only send to subscribers of the same type as the event source
49+
if (webviewType !== providerType) {
50+
return // Skip subscribers of different types
51+
}
52+
53+
try {
54+
await responseStream(event, false)
55+
} catch (error) {
56+
console.error(`Error sending mcpButtonClicked event to ${WebviewProviderType[providerType]}:`, error)
57+
mcpButtonClickedSubscriptions.delete(responseStream)
58+
}
59+
})
60+
61+
await Promise.all(promises)
62+
}

src/extension.ts

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,12 @@ import assert from "node:assert"
1111
import { posthogClientProvider } from "./services/posthog/PostHogClientProvider"
1212
import { WebviewProvider } from "./core/webview"
1313
import { Controller } from "./core/controller"
14+
import { sendMcpButtonClickedEvent } from "./core/controller/ui/subscribeToMcpButtonClicked"
1415
import { ErrorService } from "./services/error/ErrorService"
1516
import { initializeTestMode, cleanupTestMode } from "./services/test/TestMode"
1617
import { telemetryService } from "./services/posthog/telemetry/TelemetryService"
1718
import { v4 as uuidv4 } from "uuid"
19+
import { WebviewProviderType as WebviewProviderTypeEnum } from "@shared/proto/ui"
1820
import { WebviewProviderType } from "./shared/webview/types"
1921
/*
2022
Built using https://github.com/microsoft/vscode-webview-ui-toolkit
@@ -107,17 +109,13 @@ export async function activate(context: vscode.ExtensionContext) {
107109

108110
context.subscriptions.push(
109111
vscode.commands.registerCommand("cline.mcpButtonClicked", (webview: any) => {
110-
const openMcp = (instance?: WebviewProvider) =>
111-
instance?.controller.postMessageToWebview({
112-
type: "action",
113-
action: "mcpButtonClicked",
114-
})
112+
console.log("[DEBUG] mcpButtonClicked", webview)
113+
// Pass the webview type to the event sender
115114
const isSidebar = !webview
116-
if (isSidebar) {
117-
openMcp(WebviewProvider.getSidebarInstance())
118-
} else {
119-
WebviewProvider.getTabInstances().forEach(openMcp)
120-
}
115+
const webviewType = isSidebar ? WebviewProviderTypeEnum.SIDEBAR : WebviewProviderTypeEnum.TAB
116+
117+
// Will send to appropriate subscribers based on the source webview type
118+
sendMcpButtonClickedEvent(webviewType)
121119
}),
122120
)
123121

src/shared/ExtensionMessage.ts

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,6 @@ export interface ExtensionMessage {
4040
text?: string
4141
action?:
4242
| "chatButtonClicked"
43-
| "mcpButtonClicked"
4443
| "settingsButtonClicked"
4544
| "historyButtonClicked"
4645
| "didBecomeVisible"

webview-ui/src/App.tsx

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -47,11 +47,6 @@ const AppContent = () => {
4747
}
4848
}, [shouldShowAnnouncement])
4949

50-
useEffect(() => {
51-
const providerType = window.WEBVIEW_PROVIDER_TYPE || WebviewProviderType.TAB
52-
console.log("[DEBUG] webviewProviderType", providerType)
53-
}, [])
54-
5550
if (!didHydrateState) {
5651
return null
5752
}

webview-ui/src/context/ExtensionStateContext.tsx

Lines changed: 33 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
1+
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from "react"
2+
import { useEvent } from "react-use"
3+
import { StateServiceClient, UiServiceClient, ModelsServiceClient } from "../services/grpc-client"
4+
import { EmptyRequest } from "@shared/proto/common"
5+
import { WebviewProviderType as WebviewProviderTypeEnum, WebviewProviderTypeRequest } from "@shared/proto/ui"
16
import { DEFAULT_AUTO_APPROVAL_SETTINGS } from "@shared/AutoApprovalSettings"
27
import { DEFAULT_BROWSER_SETTINGS } from "@shared/BrowserSettings"
38
import { ChatSettings, DEFAULT_CHAT_SETTINGS } from "@shared/ChatSettings"
49
import { DEFAULT_PLATFORM, ExtensionMessage, ExtensionState } from "@shared/ExtensionMessage"
510
import { TelemetrySetting } from "@shared/TelemetrySetting"
611
import { findLastIndex } from "@shared/array"
7-
import { EmptyRequest } from "@shared/proto/common"
8-
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from "react"
9-
import { useEvent } from "react-use"
1012
import {
1113
ApiConfiguration,
1214
ModelInfo,
@@ -16,7 +18,6 @@ import {
1618
requestyDefaultModelInfo,
1719
} from "../../../src/shared/api"
1820
import { McpMarketplaceCatalog, McpServer, McpViewTab } from "../../../src/shared/mcp"
19-
import { ModelsServiceClient, StateServiceClient } from "../services/grpc-client"
2021
import { convertTextMateToHljs } from "../utils/textMateToHljs"
2122
import { vscode } from "../utils/vscode"
2223

@@ -192,9 +193,6 @@ export const ExtensionStateContextProvider: React.FC<{
192193
switch (message.type) {
193194
case "action": {
194195
switch (message.action!) {
195-
case "mcpButtonClicked":
196-
navigateToMcp(message.tab)
197-
break
198196
case "settingsButtonClicked":
199197
navigateToSettings()
200198
break
@@ -271,10 +269,11 @@ export const ExtensionStateContextProvider: React.FC<{
271269

272270
useEvent("message", handleMessage)
273271

274-
// Reference to store the state subscription cancellation function
272+
// References to store subscription cancellation functions
275273
const stateSubscriptionRef = useRef<(() => void) | null>(null)
274+
const mcpButtonUnsubscribeRef = useRef<(() => void) | null>(null)
276275

277-
// Subscribe to state updates using the new gRPC streaming API
276+
// Subscribe to state updates and UI events using the gRPC streaming API
278277
useEffect(() => {
279278
// Set up state subscription
280279
stateSubscriptionRef.current = StateServiceClient.subscribeToState(EmptyRequest.create({}), {
@@ -346,15 +345,39 @@ export const ExtensionStateContextProvider: React.FC<{
346345
},
347346
})
348347

348+
// Subscribe to MCP button clicked events with webview type
349+
mcpButtonUnsubscribeRef.current = UiServiceClient.subscribeToMcpButtonClicked(
350+
WebviewProviderTypeRequest.create({
351+
providerType:
352+
window.WEBVIEW_PROVIDER_TYPE === "sidebar" ? WebviewProviderTypeEnum.SIDEBAR : WebviewProviderTypeEnum.TAB,
353+
}),
354+
{
355+
onResponse: () => {
356+
console.log("[DEBUG] Received mcpButtonClicked event from gRPC stream")
357+
navigateToMcp()
358+
},
359+
onError: (error) => {
360+
console.error("Error in mcpButtonClicked subscription:", error)
361+
},
362+
onComplete: () => {
363+
console.log("mcpButtonClicked subscription completed")
364+
},
365+
},
366+
)
367+
349368
// Still send the webviewDidLaunch message for other initialization
350369
vscode.postMessage({ type: "webviewDidLaunch" })
351370

352-
// Clean up subscription when component unmounts
371+
// Clean up subscriptions when component unmounts
353372
return () => {
354373
if (stateSubscriptionRef.current) {
355374
stateSubscriptionRef.current()
356375
stateSubscriptionRef.current = null
357376
}
377+
if (mcpButtonUnsubscribeRef.current) {
378+
mcpButtonUnsubscribeRef.current()
379+
mcpButtonUnsubscribeRef.current = null
380+
}
358381
}
359382
}, [])
360383

0 commit comments

Comments
 (0)