diff --git a/src/core/Cline.ts b/src/core/Cline.ts index f9458c7b6c..27f8e62356 100644 --- a/src/core/Cline.ts +++ b/src/core/Cline.ts @@ -142,6 +142,7 @@ export class Cline extends EventEmitter { private askResponse?: ClineAskResponse private askResponseText?: string private askResponseImages?: string[] + private lastMessageTs?: number // Not private since it needs to be accessible by tools consecutiveMistakeCount: number = 0 consecutiveMistakeCountForApplyDiff: Map = new Map() @@ -332,17 +333,7 @@ export class Cline extends EventEmitter { } private async addToClineMessages(message: ClineMessage) { - // Find the correct position to insert the message based on timestamp - const insertIndex = this.clineMessages.findIndex((existingMsg) => existingMsg.ts > message.ts) - - if (insertIndex === -1) { - // If no message with a later timestamp is found, append to the end - this.clineMessages.push(message) - } else { - // Insert the message at the correct position to maintain chronological order - this.clineMessages.splice(insertIndex, 0, message) - } - + this.clineMessages.push(message) await this.providerRef.deref()?.postStateToWebview() this.emit("message", { action: "created", message }) await this.saveClineMessages() @@ -450,6 +441,7 @@ export class Cline extends EventEmitter { // This is a new partial message, so add it with partial // state. askTs = Date.now() + this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text, partial }) throw new Error("Current ask promise was ignored (#2)") } @@ -468,6 +460,8 @@ export class Cline extends EventEmitter { So in this case we must make sure that the message ts is never altered after first setting it. */ askTs = lastMessage.ts + this.lastMessageTs = askTs + // lastMessage.ts = askTs lastMessage.text = text lastMessage.partial = false lastMessage.progressStatus = progressStatus @@ -479,6 +473,7 @@ export class Cline extends EventEmitter { this.askResponseText = undefined this.askResponseImages = undefined askTs = Date.now() + this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text }) } } @@ -488,10 +483,18 @@ export class Cline extends EventEmitter { this.askResponseText = undefined this.askResponseImages = undefined askTs = Date.now() + this.lastMessageTs = askTs await this.addToClineMessages({ ts: askTs, type: "ask", ask: type, text }) } - await pWaitFor(() => this.askResponse !== undefined, { interval: 100 }) + await pWaitFor(() => this.askResponse !== undefined || this.lastMessageTs !== askTs, { interval: 100 }) + + if (this.lastMessageTs !== askTs) { + // Could happen if we send multiple asks in a row i.e. with + // command_output. It's important that when we know an ask could + // fail, it is handled gracefully. + throw new Error("Current ask promise was ignored") + } const result = { response: this.askResponse!, text: this.askResponseText, images: this.askResponseImages } this.askResponse = undefined @@ -519,8 +522,6 @@ export class Cline extends EventEmitter { throw new Error(`[Cline#say] task ${this.taskId}.${this.instanceId} aborted`) } - const sayTs = (checkpoint?.startTime as number) ?? Date.now() - if (partial !== undefined) { const lastMessage = this.clineMessages.at(-1) const isUpdatingPreviousPartial = @@ -535,6 +536,8 @@ export class Cline extends EventEmitter { this.updateClineMessage(lastMessage) } else { // this is a new partial message, so add it with partial state + const sayTs = Date.now() + this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, partial }) } } else { @@ -542,6 +545,8 @@ export class Cline extends EventEmitter { if (isUpdatingPreviousPartial) { // This is the complete version of a previously partial // message, so replace the partial with the complete version. + this.lastMessageTs = lastMessage.ts + // lastMessage.ts = sayTs lastMessage.text = text lastMessage.images = images lastMessage.partial = false @@ -553,11 +558,15 @@ export class Cline extends EventEmitter { this.updateClineMessage(lastMessage) } else { // This is a new and complete message, so add it like normal. + const sayTs = Date.now() + this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images }) } } } else { // this is a new non-partial message, so add it like normal + const sayTs = Date.now() + this.lastMessageTs = sayTs await this.addToClineMessages({ ts: sayTs, type: "say", say: type, text, images, checkpoint }) } } @@ -2394,16 +2403,14 @@ export class Cline extends EventEmitter { } }) - service.on("checkpoint", ({ isFirst, fromHash: from, toHash: to, startTime }) => { + service.on("checkpoint", ({ isFirst, fromHash: from, toHash: to }) => { try { this.providerRef.deref()?.postMessageToWebview({ type: "currentCheckpointUpdated", text: to }) - this.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to, startTime }).catch( - (err) => { - log("[Cline#initializeCheckpoints] caught unexpected error in say('checkpoint_saved')") - console.error(err) - }, - ) + this.say("checkpoint_saved", to, undefined, undefined, { isFirst, from, to }).catch((err) => { + log("[Cline#initializeCheckpoints] caught unexpected error in say('checkpoint_saved')") + console.error(err) + }) } catch (err) { log( "[Cline#initializeCheckpoints] caught unexpected error in on('checkpoint'), disabling checkpoints", diff --git a/src/services/checkpoints/ShadowCheckpointService.ts b/src/services/checkpoints/ShadowCheckpointService.ts index 85d0c27279..fc7153bab9 100644 --- a/src/services/checkpoints/ShadowCheckpointService.ts +++ b/src/services/checkpoints/ShadowCheckpointService.ts @@ -218,7 +218,7 @@ export abstract class ShadowCheckpointService extends EventEmitter { const duration = Date.now() - startTime if (isFirst || result.commit) { - this.emit("checkpoint", { type: "checkpoint", isFirst, fromHash, toHash, duration, startTime }) + this.emit("checkpoint", { type: "checkpoint", isFirst, fromHash, toHash, duration }) } if (result.commit) { diff --git a/src/services/checkpoints/types.ts b/src/services/checkpoints/types.ts index e3e96c9119..81611e81ec 100644 --- a/src/services/checkpoints/types.ts +++ b/src/services/checkpoints/types.ts @@ -29,7 +29,6 @@ export interface CheckpointEventMap { fromHash: string toHash: string duration: number - startTime: number } restore: { type: "restore"; commitHash: string; duration: number } error: { type: "error"; error: Error }