@@ -124,6 +124,7 @@ export class Task extends EventEmitter<ClineEvents> {
124124 providerRef : WeakRef < ClineProvider >
125125 private readonly globalStoragePath : string
126126 abort : boolean = false
127+ private isCompleted : boolean = false
127128 didFinishAbortingStream = false
128129 abandoned = false
129130 isInitialized = false
@@ -209,6 +210,7 @@ export class Task extends EventEmitter<ClineEvents> {
209210 }
210211
211212 this . taskId = historyItem ? historyItem . id : crypto . randomUUID ( )
213+ this . isCompleted = historyItem ?. completed ?? false
212214 // normal use-case is usually retry similar history task with new workspace
213215 this . workspacePath = parentTask
214216 ? parentTask . workspacePath
@@ -342,14 +344,22 @@ export class Task extends EventEmitter<ClineEvents> {
342344 globalStoragePath : this . globalStoragePath ,
343345 } )
344346
345- const { historyItem , tokenUsage } = await taskMetadata ( {
347+ const metadataOptions : Parameters < typeof taskMetadata > [ 0 ] = {
346348 messages : this . clineMessages ,
347349 taskId : this . taskId ,
348350 taskNumber : this . taskNumber ,
349351 globalStoragePath : this . globalStoragePath ,
350352 workspace : this . cwd ,
351353 parentTaskId : this . parentTaskId ,
352- } )
354+ }
355+
356+ if ( this . isCompleted ) {
357+ metadataOptions . setCompleted = true
358+ } else {
359+ metadataOptions . unsetCompleted = true
360+ }
361+
362+ const { historyItem, tokenUsage } = await taskMetadata ( metadataOptions )
353363
354364 this . emit ( "taskTokenUsageUpdated" , this . taskId , tokenUsage )
355365
@@ -454,6 +464,12 @@ export class Task extends EventEmitter<ClineEvents> {
454464 await this . addToClineMessages ( { ts : askTs , type : "ask" , ask : type , text } )
455465 }
456466
467+ // If the AI is asking for a completion_result, it means it has attempted completion.
468+ // Mark as completed now. It will be persisted by saveClineMessages called within addToClineMessages or by the save below.
469+ if ( type === "completion_result" ) {
470+ this . isCompleted = true
471+ }
472+
457473 await pWaitFor ( ( ) => this . askResponse !== undefined || this . lastMessageTs !== askTs , { interval : 100 } )
458474
459475 if ( this . lastMessageTs !== askTs ) {
@@ -464,6 +480,16 @@ export class Task extends EventEmitter<ClineEvents> {
464480 }
465481
466482 const result = { response : this . askResponse ! , text : this . askResponseText , images : this . askResponseImages }
483+
484+ // If the task was marked as completed due to a "completion_result" ask,
485+ // but the user did not confirm with "yesButtonClicked" (e.g., they clicked "No" or provided new input),
486+ // then the task is no longer considered completed.
487+ if ( type === "completion_result" && result . response !== "yesButtonClicked" ) {
488+ this . isCompleted = false
489+ // This change will be persisted by the next call to saveClineMessages,
490+ // for example, when user feedback is added as a new message.
491+ }
492+
467493 this . askResponse = undefined
468494 this . askResponseText = undefined
469495 this . askResponseImages = undefined
@@ -472,6 +498,13 @@ export class Task extends EventEmitter<ClineEvents> {
472498 }
473499
474500 async handleWebviewAskResponse ( askResponse : ClineAskResponse , text ?: string , images ?: string [ ] ) {
501+ const lastAskMessage = this . clineMessages
502+ . slice ( )
503+ . reverse ( )
504+ . find ( ( m ) => m . type === "ask" )
505+ if ( this . isCompleted && askResponse === "messageResponse" && lastAskMessage ?. ask !== "completion_result" ) {
506+ this . isCompleted = false
507+ }
475508 this . askResponse = askResponse
476509 this . askResponseText = text
477510 this . askResponseImages = images
@@ -501,8 +534,8 @@ export class Task extends EventEmitter<ClineEvents> {
501534 }
502535
503536 if ( partial !== undefined ) {
537+ // Handles partial messages
504538 const lastMessage = this . clineMessages . at ( - 1 )
505-
506539 const isUpdatingPreviousPartial =
507540 lastMessage && lastMessage . partial && lastMessage . type === "say" && lastMessage . say === type
508541
@@ -521,7 +554,7 @@ export class Task extends EventEmitter<ClineEvents> {
521554 if ( ! options . isNonInteractive ) {
522555 this . lastMessageTs = sayTs
523556 }
524-
557+ // For a new partial message, completion is set only when it's finalized.
525558 await this . addToClineMessages ( { ts : sayTs , type : "say" , say : type , text, images, partial } )
526559 }
527560 } else {
@@ -532,6 +565,10 @@ export class Task extends EventEmitter<ClineEvents> {
532565 if ( ! options . isNonInteractive ) {
533566 this . lastMessageTs = lastMessage . ts
534567 }
568+ // If this is the final part of a "completion_result" message, mark as completed.
569+ if ( type === "completion_result" ) {
570+ this . isCompleted = true
571+ }
535572
536573 lastMessage . text = text
537574 lastMessage . images = images
@@ -551,7 +588,11 @@ export class Task extends EventEmitter<ClineEvents> {
551588 if ( ! options . isNonInteractive ) {
552589 this . lastMessageTs = sayTs
553590 }
554-
591+ // If this is a new, complete "completion_result" message (being added as partial initially but immediately finalized), mark as completed.
592+ // This case might be rare if "completion_result" is always non-partial or ask.
593+ if ( type === "completion_result" ) {
594+ this . isCompleted = true
595+ }
555596 await this . addToClineMessages ( { ts : sayTs , type : "say" , say : type , text, images } )
556597 }
557598 }
@@ -566,7 +607,10 @@ export class Task extends EventEmitter<ClineEvents> {
566607 if ( ! options . isNonInteractive ) {
567608 this . lastMessageTs = sayTs
568609 }
569-
610+ // If this is a new, non-partial "completion_result" message, mark as completed.
611+ if ( type === "completion_result" ) {
612+ this . isCompleted = true
613+ }
570614 await this . addToClineMessages ( { ts : sayTs , type : "say" , say : type , text, images, checkpoint } )
571615 }
572616 }
@@ -584,6 +628,9 @@ export class Task extends EventEmitter<ClineEvents> {
584628 // Start / Abort / Resume
585629
586630 private async startTask ( task ?: string , images ?: string [ ] ) : Promise < void > {
631+ if ( this . isCompleted && ( task || ( images && images . length > 0 ) ) ) {
632+ this . isCompleted = false
633+ }
587634 // `conversationHistory` (for API) and `clineMessages` (for webview)
588635 // need to be in sync.
589636 // If the extension process were killed, then on restart the
@@ -691,6 +738,9 @@ export class Task extends EventEmitter<ClineEvents> {
691738 let responseImages : string [ ] | undefined
692739 if ( response === "messageResponse" ) {
693740 await this . say ( "user_feedback" , text , images )
741+ if ( this . isCompleted ) {
742+ this . isCompleted = false
743+ }
694744 responseText = text
695745 responseImages = images
696746 }
0 commit comments