mirror of
https://github.com/RooVetGit/Roo-Code.git
synced 2026-08-28 05:27:24 +00:00
fix(webview): throttle state/message updates; drop hidden-panel deltas; throttle indexing updates to prevent grey screens during long tasks
This commit is contained in:
parent
3a47c55a2e
commit
1f7e78ea2c
2 changed files with 121 additions and 15 deletions
|
|
@ -277,6 +277,11 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
public readonly messageQueueService: MessageQueueService
|
||||
private messageQueueStateChangedHandler: (() => void) | undefined
|
||||
|
||||
// Throttled chat row update batching to reduce webview message flood
|
||||
private messageUpdateBuffer: Map<number, ClineMessage> = new Map()
|
||||
private messageUpdateTimer?: NodeJS.Timeout
|
||||
private readonly MESSAGE_UPDATE_THROTTLE_MS = 33 // ~30 FPS
|
||||
|
||||
// Streaming
|
||||
isWaitingForFirstChunk = false
|
||||
isStreaming = false
|
||||
|
|
@ -644,9 +649,12 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
|
||||
private async updateClineMessage(message: ClineMessage) {
|
||||
const provider = this.providerRef.deref()
|
||||
await provider?.postMessageToWebview({ type: "messageUpdated", clineMessage: message })
|
||||
|
||||
// Emit internal event immediately (used for token usage, etc)
|
||||
this.emit(RooCodeEventName.Message, { action: "updated", message })
|
||||
|
||||
// Telemetry capture remains unchanged below
|
||||
|
||||
const shouldCaptureMessage = message.partial !== true && CloudService.isEnabled()
|
||||
|
||||
if (shouldCaptureMessage) {
|
||||
|
|
@ -655,6 +663,37 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
|
|||
properties: { taskId: this.taskId, message },
|
||||
})
|
||||
}
|
||||
|
||||
// If provider is unavailable, or panel is hidden, skip UI delta updates.
|
||||
// The UI will resync on next visibility or full state push.
|
||||
if (!provider || (typeof (provider as any).isVisible === "function" && !(provider as any).isVisible())) {
|
||||
return
|
||||
}
|
||||
|
||||
// Batch UI updates within a short window to avoid overwhelming the webview
|
||||
const ts = (message as any)?.ts as number | undefined
|
||||
if (typeof ts === "number") {
|
||||
this.messageUpdateBuffer.set(ts, message)
|
||||
} else {
|
||||
// Fallback: no timestamp, just send immediately
|
||||
await provider.postMessageToWebview({ type: "messageUpdated", clineMessage: message })
|
||||
return
|
||||
}
|
||||
|
||||
if (!this.messageUpdateTimer) {
|
||||
this.messageUpdateTimer = setTimeout(async () => {
|
||||
try {
|
||||
const batch = Array.from(this.messageUpdateBuffer.values())
|
||||
this.messageUpdateBuffer.clear()
|
||||
this.messageUpdateTimer = undefined
|
||||
for (const m of batch) {
|
||||
await provider.postMessageToWebview({ type: "messageUpdated", clineMessage: m })
|
||||
}
|
||||
} catch (e) {
|
||||
console.error("[Task#updateClineMessage] Failed to flush message updates:", e)
|
||||
}
|
||||
}, this.MESSAGE_UPDATE_THROTTLE_MS)
|
||||
}
|
||||
}
|
||||
|
||||
private async saveClineMessages() {
|
||||
|
|
|
|||
|
|
@ -148,6 +148,17 @@ export class ClineProvider
|
|||
public readonly providerSettingsManager: ProviderSettingsManager
|
||||
public readonly customModesManager: CustomModesManager
|
||||
|
||||
// Throttling/backpressure for heavy webview messages
|
||||
private stateFlushQueued = false
|
||||
private stateQueueDirty = false
|
||||
private stateFlushPromise?: Promise<void>
|
||||
private readonly STATE_THROTTLE_MS = 33 // ~30 FPS coalescing
|
||||
|
||||
// Throttle noisy indexing status updates
|
||||
private indexStatusThrottleTimer?: NodeJS.Timeout
|
||||
private pendingIndexStatus?: IndexProgressUpdate
|
||||
private readonly INDEX_STATUS_THROTTLE_MS = 100
|
||||
|
||||
constructor(
|
||||
readonly context: vscode.ExtensionContext,
|
||||
private readonly outputChannel: vscode.OutputChannel,
|
||||
|
|
@ -799,6 +810,8 @@ export class ClineProvider
|
|||
const viewStateDisposable = webviewView.onDidChangeViewState(() => {
|
||||
if (this.view?.visible) {
|
||||
this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
|
||||
// Push latest state when tab becomes visible to avoid stale UI without flooding while hidden
|
||||
void this.postStateToWebview()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -808,6 +821,8 @@ export class ClineProvider
|
|||
const visibilityDisposable = webviewView.onDidChangeVisibility(() => {
|
||||
if (this.view?.visible) {
|
||||
this.postMessageToWebview({ type: "action", action: "didBecomeVisible" })
|
||||
// Ensure UI sync after becoming visible
|
||||
void this.postStateToWebview()
|
||||
}
|
||||
})
|
||||
|
||||
|
|
@ -971,7 +986,16 @@ export class ClineProvider
|
|||
}
|
||||
|
||||
public async postMessageToWebview(message: ExtensionMessage) {
|
||||
await this.view?.webview.postMessage(message)
|
||||
try {
|
||||
// Reduce background chatter: drop chat row updates when panel isn't visible
|
||||
if (message.type === "messageUpdated" && !this.isVisible()) {
|
||||
return
|
||||
}
|
||||
if (!this.view) return
|
||||
await this.view.webview.postMessage(message)
|
||||
} catch (error) {
|
||||
this.log(`[postMessageToWebview] failed: ${error instanceof Error ? error.message : String(error)}`)
|
||||
}
|
||||
}
|
||||
|
||||
private async getHMRHtmlContent(webview: vscode.Webview): Promise<string> {
|
||||
|
|
@ -1602,13 +1626,40 @@ export class ClineProvider
|
|||
}
|
||||
|
||||
async postStateToWebview() {
|
||||
const state = await this.getStateToPostToWebview()
|
||||
this.postMessageToWebview({ type: "state", state })
|
||||
// Coalesce multiple rapid callers into a single flush, awaited by all
|
||||
this.stateQueueDirty = true
|
||||
if (!this.stateFlushQueued) {
|
||||
this.stateFlushQueued = true
|
||||
this.stateFlushPromise = this.flushStateQueue()
|
||||
}
|
||||
return this.stateFlushPromise
|
||||
}
|
||||
|
||||
// Check MDM compliance and send user to account tab if not compliant
|
||||
// Only redirect if there's an actual MDM policy requiring authentication
|
||||
if (this.mdmService?.requiresCloudAuth() && !this.checkMdmCompliance()) {
|
||||
await this.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
|
||||
private async flushStateQueue(): Promise<void> {
|
||||
try {
|
||||
// Small delay window to coalesce bursts from multiple sources
|
||||
await delay(this.STATE_THROTTLE_MS)
|
||||
|
||||
// Drain while new calls arrive during awaits
|
||||
do {
|
||||
this.stateQueueDirty = false
|
||||
|
||||
const state = await this.getStateToPostToWebview()
|
||||
await this.postMessageToWebview({ type: "state", state })
|
||||
|
||||
// Check MDM compliance and send user to account tab if not compliant
|
||||
// Only redirect if there's an actual MDM policy requiring authentication
|
||||
if (this.mdmService?.requiresCloudAuth() && !this.checkMdmCompliance()) {
|
||||
await this.postMessageToWebview({ type: "action", action: "cloudButtonClicked" })
|
||||
}
|
||||
|
||||
// Allow any additional state requests queued during the await to be coalesced
|
||||
if (this.stateQueueDirty) {
|
||||
await delay(this.STATE_THROTTLE_MS)
|
||||
}
|
||||
} while (this.stateQueueDirty)
|
||||
} finally {
|
||||
this.stateFlushQueued = false
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -2288,6 +2339,11 @@ export class ClineProvider
|
|||
return this._workspaceTracker
|
||||
}
|
||||
|
||||
// Public visibility helper for rate limiting background chatter
|
||||
public isVisible(): boolean {
|
||||
return this.view?.visible === true
|
||||
}
|
||||
|
||||
get viewLaunched() {
|
||||
return this.isViewLaunched
|
||||
}
|
||||
|
|
@ -2409,13 +2465,24 @@ export class ClineProvider
|
|||
if (currentManager) {
|
||||
this.codeIndexStatusSubscription = currentManager.onProgressUpdate((update: IndexProgressUpdate) => {
|
||||
// Only send updates if this manager is still the current one
|
||||
if (currentManager === this.getCurrentWorkspaceCodeIndexManager()) {
|
||||
// Get the full status from the manager to ensure we have all fields correctly formatted
|
||||
const fullStatus = currentManager.getCurrentStatus()
|
||||
this.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values: fullStatus,
|
||||
})
|
||||
if (currentManager !== this.getCurrentWorkspaceCodeIndexManager()) {
|
||||
return
|
||||
}
|
||||
|
||||
// Throttle: coalesce frequent progress updates into ~10 Hz
|
||||
this.pendingIndexStatus = currentManager.getCurrentStatus()
|
||||
|
||||
if (!this.indexStatusThrottleTimer) {
|
||||
this.indexStatusThrottleTimer = setTimeout(() => {
|
||||
const values = this.pendingIndexStatus ?? currentManager.getCurrentStatus()
|
||||
this.pendingIndexStatus = undefined
|
||||
this.indexStatusThrottleTimer = undefined
|
||||
|
||||
this.postMessageToWebview({
|
||||
type: "indexingStatusUpdate",
|
||||
values,
|
||||
})
|
||||
}, this.INDEX_STATUS_THROTTLE_MS)
|
||||
}
|
||||
})
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue