diff --git a/src/core/task/Task.ts b/src/core/task/Task.ts index 9df9a225d1..c8ba18b481 100644 --- a/src/core/task/Task.ts +++ b/src/core/task/Task.ts @@ -544,7 +544,12 @@ export class Task extends EventEmitter { private async addToClineMessages(message: ClineMessage) { this.clineMessages.push(message) const provider = this.providerRef.deref() - await provider?.postStateToWebview() + + // Use incremental update instead of posting full state for better performance + await provider?.postIncrementalUpdateToWebview({ + newMessages: [message], + }) + this.emit("message", { action: "created", message }) await this.saveClineMessages() @@ -566,7 +571,12 @@ export class Task extends EventEmitter { private async updateClineMessage(message: ClineMessage) { const provider = this.providerRef.deref() - await provider?.postMessageToWebview({ type: "messageUpdated", clineMessage: message }) + + // Use incremental update for message updates as well + await provider?.postIncrementalUpdateToWebview({ + updatedMessages: [message], + }) + this.emit("message", { action: "updated", message }) const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled() @@ -598,7 +608,13 @@ export class Task extends EventEmitter { this.emit("taskTokenUsageUpdated", this.taskId, tokenUsage) - await this.providerRef.deref()?.updateTaskHistory(historyItem) + const provider = this.providerRef.deref() + await provider?.updateTaskHistory(historyItem) + + // Send incremental update for task history + await provider?.postIncrementalUpdateToWebview({ + taskHistoryUpdate: historyItem, + }) } catch (error) { console.error("Failed to save Roo messages:", error) } @@ -926,6 +942,7 @@ export class Task extends EventEmitter { // messages from previous session). this.clineMessages = [] this.apiConversationHistory = [] + // Use full state update for initial task start await this.providerRef.deref()?.postStateToWebview() await this.say("text", task, images) diff --git a/src/core/webview/ClineProvider.ts b/src/core/webview/ClineProvider.ts index 99c2a514b2..4b942efc4e 100644 --- a/src/core/webview/ClineProvider.ts +++ b/src/core/webview/ClineProvider.ts @@ -1340,6 +1340,22 @@ export class ClineProvider } } + /** + * Posts incremental updates to the webview instead of the full state. + * This significantly improves performance for long conversations by only + * sending new or updated messages instead of the entire conversation history. + */ + async postIncrementalUpdateToWebview(options: { + newMessages?: import("@roo-code/types").ClineMessage[] + updatedMessages?: import("@roo-code/types").ClineMessage[] + taskHistoryUpdate?: import("@roo-code/types").HistoryItem + }) { + await this.postMessageToWebview({ + type: "incrementalStateUpdate", + incrementalUpdate: options, + }) + } + /** * Fetches marketplace data on demand to avoid blocking main state updates */ diff --git a/src/shared/ExtensionMessage.ts b/src/shared/ExtensionMessage.ts index 930edeac73..370adc781d 100644 --- a/src/shared/ExtensionMessage.ts +++ b/src/shared/ExtensionMessage.ts @@ -62,6 +62,7 @@ export interface ExtensionMessage { type: | "action" | "state" + | "incrementalStateUpdate" | "selectedImages" | "theme" | "workspaceUpdated" @@ -135,6 +136,11 @@ export interface ExtensionMessage { | "switchTab" invoke?: "newChat" | "sendMessage" | "primaryButtonClick" | "secondaryButtonClick" | "setChatBoxMessage" state?: ExtensionState + incrementalUpdate?: { + newMessages?: ClineMessage[] + updatedMessages?: ClineMessage[] + taskHistoryUpdate?: HistoryItem + } images?: string[] filePaths?: string[] openedTabs?: Array<{ diff --git a/webview-ui/src/context/ExtensionStateContext.tsx b/webview-ui/src/context/ExtensionStateContext.tsx index da7ab63358..dc5b0d0939 100644 --- a/webview-ui/src/context/ExtensionStateContext.tsx +++ b/webview-ui/src/context/ExtensionStateContext.tsx @@ -312,6 +312,48 @@ export const ExtensionStateContextProvider: React.FC<{ children: React.ReactNode } break } + case "incrementalStateUpdate": { + const update = message.incrementalUpdate! + setState((prevState) => { + let newClineMessages = [...prevState.clineMessages] + + // Add new messages + if (update.newMessages) { + newClineMessages.push(...update.newMessages) + } + + // Update existing messages + if (update.updatedMessages) { + for (const updatedMessage of update.updatedMessages) { + const index = findLastIndex(newClineMessages, (msg) => msg.ts === updatedMessage.ts) + if (index !== -1) { + newClineMessages[index] = updatedMessage + } + } + } + + // Update task history if provided + let newTaskHistory = prevState.taskHistory + if (update.taskHistoryUpdate) { + const historyIndex = newTaskHistory.findIndex( + (item) => item.id === update.taskHistoryUpdate!.id, + ) + if (historyIndex !== -1) { + newTaskHistory = [...newTaskHistory] + newTaskHistory[historyIndex] = update.taskHistoryUpdate + } else { + newTaskHistory = [...newTaskHistory, update.taskHistoryUpdate] + } + } + + return { + ...prevState, + clineMessages: newClineMessages, + taskHistory: newTaskHistory, + } + }) + break + } case "theme": { if (message.text) { setTheme(convertTextMateToHljs(JSON.parse(message.text)))