Compare commits

...

3 Commits

Author SHA1 Message Date
celestial-vault 2f2f7f2a1b merge conflicts 2025-06-06 18:58:23 -07:00
Elephant Lumps 70e8d8bc13 fix linter warning about empty proto object creation 2025-06-03 16:32:46 -07:00
Elephant Lumps 9b3d93d4d0 migrate workspaceUpdated 2025-06-03 16:26:31 -07:00
9 changed files with 96 additions and 21 deletions
+3
View File
@@ -55,6 +55,9 @@ service FileService {
// Opens a task's conversation history file on disk
rpc openTaskHistory(StringRequest) returns (Empty);
// Subscribe to workspace file updates
rpc subscribeToWorkspaceUpdates(EmptyRequest) returns (stream StringArray);
}
// Response for refreshRules operation
@@ -0,0 +1,58 @@
import { Controller } from "../index"
import { EmptyRequest, StringArray } from "@shared/proto/common"
import { StreamingResponseHandler, getRequestRegistry } from "../grpc-handler"
// Keep track of active subscriptions
const activeWorkspaceUpdateSubscriptions = new Set<StreamingResponseHandler>()
/**
* Subscribe to workspace file updates
* @param controller The controller instance
* @param request The empty request
* @param responseStream The streaming response handler
* @param requestId The ID of the request (passed by the gRPC handler)
*/
export async function subscribeToWorkspaceUpdates(
controller: Controller,
request: EmptyRequest,
responseStream: StreamingResponseHandler,
requestId?: string,
): Promise<void> {
// Add this subscription to the active subscriptions
activeWorkspaceUpdateSubscriptions.add(responseStream)
// Register cleanup when the connection is closed
const cleanup = () => {
activeWorkspaceUpdateSubscriptions.delete(responseStream)
}
// Register the cleanup function with the request registry if we have a requestId
if (requestId) {
getRequestRegistry().registerRequest(requestId, cleanup, { type: "workspace_update_subscription" }, responseStream)
}
}
/**
* Send a workspace update event to all active subscribers
* @param filePaths Array of file paths to send
*/
export async function sendWorkspaceUpdateEvent(filePaths: string[]): Promise<void> {
// Send the event to all active subscribers
const promises = Array.from(activeWorkspaceUpdateSubscriptions).map(async (responseStream) => {
try {
const event = StringArray.create({
values: filePaths,
})
await responseStream(
event,
false, // Not the last message
)
} catch (error) {
console.error("Error sending workspace update event:", error)
// Remove the subscription if there was an error
activeWorkspaceUpdateSubscriptions.delete(responseStream)
}
})
await Promise.all(promises)
}
+1 -1
View File
@@ -68,7 +68,7 @@ export class Controller {
this.outputChannel.appendLine("ClineProvider instantiated")
this.postMessage = postMessage
this.workspaceTracker = new WorkspaceTracker((msg) => this.postMessageToWebview(msg))
this.workspaceTracker = new WorkspaceTracker()
this.mcpHub = new McpHub(
() => ensureMcpServersDirectoryExists(),
() => ensureSettingsDirectoryExists(this.context),
@@ -50,7 +50,7 @@ export async function sendChatButtonClickedEvent(controllerId: string): Promise<
}
try {
const event: Empty = Empty.create({})
const event = Empty.create({})
await responseStream(
event,
false, // Not the last message
@@ -50,7 +50,7 @@ export async function sendHistoryButtonClickedEvent(webviewType?: WebviewProvide
}
try {
const event: Empty = Empty.create({})
const event = Empty.create({})
await responseStream(
event,
false, // Not the last message
@@ -41,7 +41,7 @@ export async function subscribeToMcpButtonClicked(
* @param webviewType The type of webview that triggered the event (SIDEBAR or TAB)
*/
export async function sendMcpButtonClickedEvent(webviewType?: WebviewProviderType): Promise<void> {
const event: Empty = Empty.create({})
const event = Empty.create({})
// Process all subscriptions, filtering based on the source
const promises = Array.from(mcpButtonClickedSubscriptions.entries()).map(async ([responseStream, providerType]) => {
+7 -10
View File
@@ -1,7 +1,7 @@
import * as vscode from "vscode"
import * as path from "path"
import { listFiles } from "@services/glob/list-files"
import { ExtensionMessage } from "@shared/ExtensionMessage"
import { sendWorkspaceUpdateEvent } from "@core/controller/file/subscribeToWorkspaceUpdates"
const cwd = vscode.workspace.workspaceFolders?.map((folder) => folder.uri.fsPath).at(0)
@@ -18,8 +18,7 @@ class WorkspaceTracker {
)
}
constructor(private readonly postMessageToWebview: (message: ExtensionMessage) => Promise<void>) {
this.postMessageToWebview = postMessageToWebview
constructor() {
this.registerListeners()
}
@@ -91,17 +90,15 @@ class WorkspaceTracker {
this.workspaceDidUpdate()
}
private workspaceDidUpdate() {
private async workspaceDidUpdate() {
if (!cwd) {
return
}
this.postMessageToWebview({
type: "workspaceUpdated",
filePaths: Array.from(new Set([...this.activeFiles, ...this.filePaths])).map((file) => {
const relativePath = path.relative(cwd, file).toPosix()
return file.endsWith("/") ? relativePath + "/" : relativePath
}),
const filePaths = Array.from(new Set([...this.activeFiles, ...this.filePaths])).map((file) => {
const relativePath = path.relative(cwd, file).toPosix()
return file.endsWith("/") ? relativePath + "/" : relativePath
})
await sendWorkspaceUpdateEvent(filePaths)
}
private normalizeFilePath(filePath: string): string {
-2
View File
@@ -17,7 +17,6 @@ export interface ExtensionMessage {
| "action"
| "state"
| "selectedImages"
| "workspaceUpdated"
| "openAiModels"
| "requestyModels"
| "mcpServers"
@@ -35,7 +34,6 @@ export interface ExtensionMessage {
ollamaModels?: string[]
lmStudioModels?: string[]
vsCodeLmModels?: { vendor?: string; family?: string; version?: string; id?: string }[]
filePaths?: string[]
openAiModels?: string[]
requestyModels?: Record<string, ModelInfo>
mcpServers?: McpServer[]
@@ -1,5 +1,12 @@
import React, { createContext, useCallback, useContext, useEffect, useRef, useState } from "react"
import { useEvent } from "react-use"
import {
StateServiceClient,
ModelsServiceClient,
UiServiceClient,
FileServiceClient,
McpServiceClient,
} from "../services/grpc-client"
import { EmptyRequest } from "@shared/proto/common"
import { UpdateSettingsRequest } from "@shared/proto/state"
import { WebviewProviderType as WebviewProviderTypeEnum, WebviewProviderTypeRequest } from "@shared/proto/ui"
@@ -19,7 +26,6 @@ import {
requestyDefaultModelInfo,
} from "../../../src/shared/api"
import { McpMarketplaceCatalog, McpServer, McpViewTab } from "../../../src/shared/mcp"
import { ModelsServiceClient, StateServiceClient, UiServiceClient, McpServiceClient } from "../services/grpc-client"
import { convertTextMateToHljs } from "../utils/textMateToHljs"
import { vscode } from "../utils/vscode"
import { OpenRouterCompatibleModelInfo } from "@shared/proto/models"
@@ -201,10 +207,6 @@ export const ExtensionStateContextProvider: React.FC<{
const handleMessage = useCallback((event: MessageEvent) => {
const message: ExtensionMessage = event.data
switch (message.type) {
case "workspaceUpdated": {
setFilePaths(message.filePaths ?? [])
break
}
case "openAiModels": {
const updatedModels = message.openAiModels ?? []
setOpenAiModels(updatedModels)
@@ -238,6 +240,7 @@ export const ExtensionStateContextProvider: React.FC<{
const mcpMarketplaceUnsubscribeRef = useRef<(() => void) | null>(null)
const themeSubscriptionRef = useRef<(() => void) | null>(null)
const openRouterModelsUnsubscribeRef = useRef<(() => void) | null>(null)
const workspaceUpdatesUnsubscribeRef = useRef<(() => void) | null>(null)
// Subscribe to state updates and UI events using the gRPC streaming API
useEffect(() => {
@@ -367,6 +370,18 @@ export const ExtensionStateContextProvider: React.FC<{
onComplete: () => {},
})
// Subscribe to workspace file updates
workspaceUpdatesUnsubscribeRef.current = FileServiceClient.subscribeToWorkspaceUpdates(EmptyRequest.create({}), {
onResponse: (response) => {
console.log("[DEBUG] Received workspace update event from gRPC stream")
setFilePaths(response.values || [])
},
onError: (error) => {
console.error("Error in workspace updates subscription:", error)
},
onComplete: () => {},
})
// Set up settings button clicked subscription
settingsButtonClickedSubscriptionRef.current = UiServiceClient.subscribeToSettingsButtonClicked(
WebviewProviderTypeRequest.create({
@@ -542,6 +557,10 @@ export const ExtensionStateContextProvider: React.FC<{
openRouterModelsUnsubscribeRef.current()
openRouterModelsUnsubscribeRef.current = null
}
if (workspaceUpdatesUnsubscribeRef.current) {
workspaceUpdatesUnsubscribeRef.current()
workspaceUpdatesUnsubscribeRef.current = null
}
}
}, [])