@@ -136,15 +136,11 @@ export class MessageManager {
136136 *
137137 * Note on timestamp handling:
138138 * Due to async execution during streaming, clineMessage timestamps may not
139- * perfectly align with API message timestamps. There are two race condition scenarios:
140- *
141- * 1. Original race: clineMessage timestamp is BEFORE the assistant API message
142- * (tool execution happens concurrently with stream completion)
143- * Solution: Find the first API user message at or after the cutoff
144- *
145- * 2. Inverse race: API user message (tool_result) timestamp is BEFORE the clineMessage
146- * (the tool_result was logged before the user_feedback clineMessage)
147- * Solution: Find the last assistant message before cutoff and use that as boundary
139+ * perfectly align with API message timestamps. Specifically, a "user_feedback"
140+ * clineMessage can have a timestamp BEFORE the assistant API message that
141+ * triggered it (because tool execution happens concurrently with stream
142+ * completion). To handle this race condition, we find the first API user
143+ * message at or after the cutoff and use its timestamp as the actual boundary.
148144 */
149145 private async truncateApiHistoryWithCleanup (
150146 cutoffTs : number ,
@@ -163,35 +159,20 @@ export class MessageManager {
163159 let actualCutoff : number = cutoffTs
164160
165161 if ( ! hasExactMatch && hasMessageBeforeCutoff ) {
166- // No exact match but there are earlier messages - check for race conditions
167- //
168- // First, check for "inverse race" pattern:
169- // Find the last assistant message before cutoff
170- const lastAssistantBeforeCutoff = this . findLastAssistantBeforeCutoff ( apiHistory , cutoffTs )
171-
172- if ( lastAssistantBeforeCutoff !== undefined ) {
173- // Check if there are user messages between the last assistant and cutoff
174- // This indicates an "inverse race" where the user's tool_result was logged
175- // before the clineMessage timestamp
176- const hasUserBetweenAssistantAndCutoff = apiHistory . some (
177- ( m ) =>
178- m . ts !== undefined && m . ts > lastAssistantBeforeCutoff && m . ts < cutoffTs && m . role === "user" ,
179- )
180-
181- if ( hasUserBetweenAssistantAndCutoff ) {
182- // Inverse race detected: use the assistant timestamp + 1 as cutoff
183- // This ensures we keep the assistant response but remove the user
184- // message that belongs to the turn being deleted
185- actualCutoff = lastAssistantBeforeCutoff + 1
186- } else {
187- // No inverse race, check for original race condition
188- // Look for the first API user message at or after the cutoff
189- actualCutoff = this . findFirstUserCutoff ( apiHistory , cutoffTs )
190- }
191- } else {
192- // No assistant before cutoff, use original race condition logic
193- actualCutoff = this . findFirstUserCutoff ( apiHistory , cutoffTs )
162+ // No exact match but there are earlier messages means we might have a race
163+ // condition where the clineMessage timestamp is earlier than any API message
164+ // due to async execution. In this case, look for the first API user message
165+ // at or after the cutoff to use as the actual boundary.
166+ // This ensures assistant messages that preceded the user's response are preserved.
167+ const firstUserMsgIndexToRemove = apiHistory . findIndex (
168+ ( m ) => m . ts !== undefined && m . ts >= cutoffTs && m . role === "user" ,
169+ )
170+
171+ if ( firstUserMsgIndexToRemove !== - 1 ) {
172+ // Use the user message's timestamp as the actual cutoff
173+ actualCutoff = apiHistory [ firstUserMsgIndexToRemove ] . ts !
194174 }
175+ // else: no user message found, use original cutoffTs (fallback)
195176 }
196177
197178 // Step 2: Filter by the actual cutoff timestamp
@@ -234,38 +215,4 @@ export class MessageManager {
234215 await this . task . overwriteApiConversationHistory ( apiHistory )
235216 }
236217 }
237-
238- /**
239- * Find the timestamp of the last assistant message before the cutoff.
240- * Returns undefined if no assistant message exists before cutoff.
241- */
242- private findLastAssistantBeforeCutoff ( apiHistory : ApiMessage [ ] , cutoffTs : number ) : number | undefined {
243- let lastAssistantTs : number | undefined
244-
245- for ( const msg of apiHistory ) {
246- if ( msg . ts !== undefined && msg . ts < cutoffTs && msg . role === "assistant" ) {
247- if ( lastAssistantTs === undefined || msg . ts > lastAssistantTs ) {
248- lastAssistantTs = msg . ts
249- }
250- }
251- }
252-
253- return lastAssistantTs
254- }
255-
256- /**
257- * Find the cutoff based on the first user message at or after the original cutoff.
258- * Falls back to the original cutoff if no user message is found.
259- */
260- private findFirstUserCutoff ( apiHistory : ApiMessage [ ] , cutoffTs : number ) : number {
261- const firstUserMsgIndex = apiHistory . findIndex (
262- ( m ) => m . ts !== undefined && m . ts >= cutoffTs && m . role === "user" ,
263- )
264-
265- if ( firstUserMsgIndex !== - 1 ) {
266- return apiHistory [ firstUserMsgIndex ] . ts !
267- }
268-
269- return cutoffTs
270- }
271218}
0 commit comments