fix(webview): deterministic chat update ordering and safer throttles

Task: sort batched messageUpdated by ts; clear messageUpdateTimer in dispose; use provider.log; remove any-cast for ts

ClineProvider: void postMessage in throttle callbacks; clear indexStatusThrottleTimer in dispose
This commit is contained in:
Hannes Rudolph 2025-10-11 18:30:16 -06:00
parent 03d28f90f1
commit 458aac2eb5
2 changed files with 35 additions and 11 deletions

View file

@ -663,7 +663,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
}
// Batch UI updates within a short window to avoid overwhelming the webview
const ts = (message as any)?.ts as number | undefined
const ts = message.ts as number | undefined
if (typeof ts === "number") {
this.messageUpdateBuffer.set(ts, message)
} else {
@ -684,20 +684,24 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
this.messageUpdateBuffer = new Map()
this.messageUpdateTimer = undefined
const batch = Array.from(batchMap.values())
const batch = Array.from(batchMap.values()).sort((a, b) => (a.ts ?? 0) - (b.ts ?? 0))
const providerNow = this.providerRef.deref()
if (!providerNow) {
console.warn(
`[Task#updateClineMessage] Dropping ${batch.length} messageUpdated deltas: provider unavailable`,
)
this.providerRef
.deref()
?.log(
`[Task#updateClineMessage] Dropping ${batch.length} messageUpdated deltas: provider unavailable`,
)
return
}
if (!providerNow.isVisible()) {
// Drop deltas while hidden; UI will receive a full state sync on visibility
console.debug(
`[Task#updateClineMessage] Dropping ${batch.length} messageUpdated deltas while hidden`,
)
this.providerRef
.deref()
?.log(
`[Task#updateClineMessage] Dropping ${batch.length} messageUpdated deltas while hidden`,
)
return
}
@ -705,7 +709,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
await providerNow.postMessageToWebview({ type: "messageUpdated", clineMessage: m })
}
} catch (e) {
console.error("[Task#updateClineMessage] Failed to flush message updates:", e)
this.providerRef
.deref()
?.log(
`[Task#updateClineMessage] Failed to flush message updates: ${
e instanceof Error ? e.message : String(e)
}`,
)
}
}, this.MESSAGE_UPDATE_THROTTLE_MS)
}
@ -1581,6 +1591,13 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
public dispose(): void {
console.log(`[Task#dispose] disposing task ${this.taskId}.${this.instanceId}`)
// Clear pending batched message timer and buffer
if (this.messageUpdateTimer) {
clearTimeout(this.messageUpdateTimer)
this.messageUpdateTimer = undefined
}
this.messageUpdateBuffer.clear()
// Dispose message queue and remove event listeners.
try {
if (this.messageQueueStateChangedHandler) {

View file

@ -583,6 +583,13 @@ export class ClineProvider
async dispose() {
this.log("Disposing ClineProvider...")
// Clear throttled timers
if (this.indexStatusThrottleTimer) {
clearTimeout(this.indexStatusThrottleTimer)
this.indexStatusThrottleTimer = undefined
}
this.pendingIndexStatus = undefined
// Clear all tasks from the stack.
while (this.clineStack.length > 0) {
await this.removeClineFromStack()
@ -2478,7 +2485,7 @@ export class ClineProvider
this.pendingIndexStatus = undefined
this.indexStatusThrottleTimer = undefined
this.postMessageToWebview({
void this.postMessageToWebview({
type: "indexingStatusUpdate",
values,
})
@ -2491,7 +2498,7 @@ export class ClineProvider
}
// Send initial status for the current workspace
this.postMessageToWebview({
void this.postMessageToWebview({
type: "indexingStatusUpdate",
values: currentManager.getCurrentStatus(),
})