@@ -218,6 +218,7 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
218218
219219 didFinishAbortingStream = false
220220 abandoned = false
221+ abortReason ?: ClineApiReqCancelReason
221222 isInitialized = false
222223 isPaused : boolean = false
223224 pausedModeSlug : string = defaultModeSlug
@@ -1282,6 +1283,16 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
12821283 modifiedClineMessages . splice ( lastRelevantMessageIndex + 1 )
12831284 }
12841285
1286+ // Remove any trailing reasoning-only UI messages that were not part of the persisted API conversation
1287+ while ( modifiedClineMessages . length > 0 ) {
1288+ const last = modifiedClineMessages [ modifiedClineMessages . length - 1 ]
1289+ if ( last . type === "say" && last . say === "reasoning" ) {
1290+ modifiedClineMessages . pop ( )
1291+ } else {
1292+ break
1293+ }
1294+ }
1295+
12851296 // Since we don't use `api_req_finished` anymore, we need to check if the
12861297 // last `api_req_started` has a cost value, if it doesn't and no
12871298 // cancellation reason to present, then we remove it since it indicates
@@ -1904,28 +1915,10 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
19041915 lastMessage . partial = false
19051916 // instead of streaming partialMessage events, we do a save and post like normal to persist to disk
19061917 console . log ( "updating partial message" , lastMessage )
1907- // await this.saveClineMessages()
19081918 }
19091919
1910- // Let assistant know their response was interrupted for when task is resumed
1911- await this . addToApiConversationHistory ( {
1912- role : "assistant" ,
1913- content : [
1914- {
1915- type : "text" ,
1916- text :
1917- assistantMessage +
1918- `\n\n[${
1919- cancelReason === "streaming_failed"
1920- ? "Response interrupted by API Error"
1921- : "Response interrupted by user"
1922- } ]`,
1923- } ,
1924- ] ,
1925- } )
1926-
19271920 // Update `api_req_started` to have cancelled and cost, so that
1928- // we can display the cost of the partial stream.
1921+ // we can display the cost of the partial stream and the cancellation reason
19291922 updateApiReqMsg ( cancelReason , streamingFailedMessage )
19301923 await this . saveClineMessages ( )
19311924
@@ -2209,24 +2202,23 @@ export class Task extends EventEmitter<TaskEvents> implements TaskLike {
22092202 // may have executed), so we just resort to replicating a
22102203 // cancel task.
22112204
2212- // Check if this was a user-initiated cancellation BEFORE calling abortTask
2213- // If this.abort is already true, it means the user clicked cancel, so we should
2214- // treat this as "user_cancelled" rather than "streaming_failed"
2215- const cancelReason = this . abort ? "user_cancelled" : "streaming_failed"
2205+ // Determine cancellation reason BEFORE aborting to ensure correct persistence
2206+ const cancelReason : ClineApiReqCancelReason = this . abort ? "user_cancelled" : "streaming_failed"
22162207
22172208 const streamingFailedMessage = this . abort
22182209 ? undefined
22192210 : ( error . message ?? JSON . stringify ( serializeError ( error ) , null , 2 ) )
22202211
2221- // Now call abortTask after determining the cancel reason.
2222- await this . abortTask ( )
2212+ // Persist interruption details first to both UI and API histories
22232213 await abortStream ( cancelReason , streamingFailedMessage )
22242214
2225- const history = await provider ?. getTaskWithId ( this . taskId )
2215+ // Record reason for provider to decide rehydration path
2216+ this . abortReason = cancelReason
22262217
2227- if ( history ) {
2228- await provider ?. createTaskWithHistoryItem ( history . historyItem )
2229- }
2218+ // Now abort (emits TaskAborted which provider listens to)
2219+ await this . abortTask ( )
2220+
2221+ // Do not rehydrate here; provider owns rehydration to avoid duplication races
22302222 }
22312223 } finally {
22322224 this . isStreaming = false
0 commit comments