@@ -6,6 +6,7 @@ import * as path from 'path'
66import * as vscode from 'vscode'
77import { Event as VSCodeEvent , Uri , workspace , window , ViewColumn , Position , Selection } from 'vscode'
88import { EditorContextExtractor } from '../../editor/context/extractor'
9+ import { ConversationTracker } from '../../storages/conversationTracker'
910import { ChatSessionStorage } from '../../storages/chatSession'
1011import { Messenger , MessengerResponseType , StaticTextResponseType } from './messenger/messenger'
1112import {
@@ -414,8 +415,19 @@ export class ChatController {
414415 private async processStopResponseMessage ( message : StopResponseMessage ) {
415416 const session = this . sessionStorage . getSession ( message . tabID )
416417 session . tokenSource . cancel ( )
418+ session . setAgenticLoopInProgress ( false )
419+ session . setToolUseWithError ( undefined )
420+
421+ // Mark any active triggers as cancelled when stopping the response
422+ const triggerEvents = this . triggerEventsStorage . getTriggerEventsByTabID ( message . tabID )
423+ if ( triggerEvents && triggerEvents . length > 0 ) {
424+ const conversationTracker = ConversationTracker . getInstance ( )
425+ for ( const event of triggerEvents ) {
426+ conversationTracker . cancelTrigger ( event . id )
427+ }
428+ }
429+
417430 this . messenger . sendEmptyMessage ( message . tabID , '' , undefined )
418- this . chatHistoryDb . clearRecentHistory ( message . tabID )
419431 this . telemetryHelper . recordInteractionWithAgenticChat ( AgenticChatInteractionType . StopChat , message )
420432 }
421433
@@ -472,6 +484,13 @@ export class ChatController {
472484 }
473485
474486 private async processTabCloseMessage ( message : TabClosedMessage ) {
487+ // First cancel any active triggers to stop ongoing operations
488+ const conversationTracker = ConversationTracker . getInstance ( )
489+ conversationTracker . cancelTabTriggers ( message . tabID )
490+
491+ // Then clear all triggers to release resources
492+ conversationTracker . clearTabTriggers ( message . tabID )
493+
475494 this . sessionStorage . deleteSession ( message . tabID )
476495 this . triggerEventsStorage . removeTabEvents ( message . tabID )
477496 // this.telemetryHelper.recordCloseChat(message.tabID)
@@ -686,7 +705,13 @@ export class ChatController {
686705 this . editorContextExtractor
687706 . extractContextForTrigger ( 'ChatMessage' )
688707 . then ( async ( context ) => {
689- const triggerID = randomUUID ( )
708+ const triggerID = message . triggerId
709+
710+ // Check if this trigger has already been cancelled
711+ if ( this . isTriggerCancelled ( triggerID ) ) {
712+ return
713+ }
714+
690715 this . triggerEventsStorage . addTriggerEvent ( {
691716 id : triggerID ,
692717 tabID : message . tabID ,
@@ -696,13 +721,16 @@ export class ChatController {
696721 } )
697722 this . messenger . sendAsyncEventProgress ( tabID , true , '' )
698723 const session = this . sessionStorage . getSession ( tabID )
724+
725+ // Check if the session has been cancelled before proceeding
726+ if ( this . isTriggerCancelled ( triggerID ) ) {
727+ getLogger ( ) . debug ( `Tool execution cancelled for tabID: ${ tabID } ` )
728+ return
729+ }
730+
699731 const toolUseWithError = session . toolUseWithError
700- getLogger ( ) . debug (
701- `processToolUseMessage: ${ toolUseWithError ?. toolUse . name } :${ toolUseWithError ?. toolUse . toolUseId } with error: ${ toolUseWithError ?. error } `
702- )
703732 if ( ! toolUseWithError || ! toolUseWithError . toolUse ) {
704- // Turn off AgentLoop flag if there's no tool use
705- this . sessionStorage . setAgentLoopInProgress ( tabID , false )
733+ session . setAgenticLoopInProgress ( false )
706734 return
707735 }
708736 session . setToolUseWithError ( undefined )
@@ -742,7 +770,18 @@ export class ChatController {
742770 const backup = await tool . tool . getBackup ( )
743771 session . setFsWriteBackup ( toolUse . toolUseId , backup )
744772 }
745- const output = await ToolUtils . invoke ( tool , chatStream )
773+
774+ // Check again if cancelled before invoking the tool
775+ if ( this . isTriggerCancelled ( triggerID ) ) {
776+ getLogger ( ) . debug ( `Tool execution cancelled before invoke for tabID: ${ tabID } ` )
777+ return
778+ }
779+
780+ const output = await ToolUtils . invoke (
781+ tool ,
782+ chatStream ,
783+ ConversationTracker . getInstance ( ) . getTokenForTrigger ( triggerID )
784+ )
746785 ToolUtils . validateOutput ( output )
747786
748787 toolResults . push ( {
@@ -1039,7 +1078,17 @@ export class ChatController {
10391078
10401079 // Turn off AgentLoop flag in case of exception
10411080 if ( tabID ) {
1042- this . sessionStorage . setAgentLoopInProgress ( tabID , false )
1081+ const session = this . sessionStorage . getSession ( tabID )
1082+ session . setAgenticLoopInProgress ( false )
1083+
1084+ // Mark any active triggers as completed when there's an exception
1085+ const triggerEvents = this . triggerEventsStorage . getTriggerEventsByTabID ( tabID )
1086+ if ( triggerEvents && triggerEvents . length > 0 ) {
1087+ const conversationTracker = ConversationTracker . getInstance ( )
1088+ for ( const event of triggerEvents ) {
1089+ conversationTracker . cancelTrigger ( event . id )
1090+ }
1091+ }
10431092 }
10441093
10451094 this . messenger . sendErrorMessage ( errorMessage , tabID , requestID , statusCode )
@@ -1226,23 +1275,33 @@ export class ChatController {
12261275
12271276 private async processPromptMessageAsNewThread ( message : PromptMessage ) {
12281277 const session = this . sessionStorage . getSession ( message . tabID )
1278+ // If there's an existing conversation, ensure we dispose the previous token
1279+ if ( session . agenticLoopInProgress ) {
1280+ session . disposeTokenSource ( )
1281+ }
1282+
1283+ // Create a fresh token for this new conversation
1284+ session . createNewTokenSource ( )
1285+ session . setAgenticLoopInProgress ( true )
12291286 session . clearListOfReadFiles ( )
12301287 session . clearListOfReadFolders ( )
12311288 session . setShowDiffOnFileWrite ( false )
12321289 this . editorContextExtractor
12331290 . extractContextForTrigger ( 'ChatMessage' )
12341291 . then ( async ( context ) => {
12351292 const triggerID = randomUUID ( )
1293+
1294+ // Register the trigger ID with the token for cancellation tracking
1295+ const conversationTracker = ConversationTracker . getInstance ( )
1296+ conversationTracker . registerTrigger ( triggerID , session . tokenSource , message . tabID )
1297+
12361298 this . triggerEventsStorage . addTriggerEvent ( {
12371299 id : triggerID ,
12381300 tabID : message . tabID ,
12391301 message : message . message ,
12401302 type : 'chat_message' ,
12411303 context,
12421304 } )
1243-
1244- this . messenger . sendAsyncEventProgress ( message . tabID , true , '' )
1245-
12461305 await this . generateResponse (
12471306 {
12481307 message : message . message ?? '' ,
@@ -1438,16 +1497,6 @@ export class ChatController {
14381497 }
14391498
14401499 const tabID = triggerEvent . tabID
1441- if ( this . sessionStorage . isAgentLoopInProgress ( tabID ) ) {
1442- // If a response is already in progress, stop it first
1443- const stopResponseMessage : StopResponseMessage = {
1444- tabID : tabID ,
1445- }
1446- await this . processStopResponseMessage ( stopResponseMessage )
1447- }
1448-
1449- // Ensure AgentLoop flag is set to true during response generation
1450- this . sessionStorage . setAgentLoopInProgress ( tabID , true )
14511500
14521501 const credentialsState = await AuthUtil . instance . getChatAuthState ( )
14531502
@@ -1545,7 +1594,11 @@ export class ChatController {
15451594 session . setContext ( triggerPayload . context )
15461595 }
15471596 this . messenger . sendInitalStream ( tabID , triggerID )
1597+ this . messenger . sendAsyncEventProgress ( tabID , true , '' )
15481598 this . telemetryHelper . setConversationStreamStartTime ( tabID )
1599+ if ( this . isTriggerCancelled ( triggerID ) ) {
1600+ return
1601+ }
15491602 if ( isSsoConnection ( AuthUtil . instance . conn ) ) {
15501603 const { $metadata, generateAssistantResponseResponse } = await session . chatSso ( request )
15511604 response = {
@@ -1562,7 +1615,7 @@ export class ChatController {
15621615 this . telemetryHelper . recordEnterFocusConversation ( triggerEvent . tabID )
15631616 this . telemetryHelper . recordStartConversation ( triggerEvent , triggerPayload )
15641617
1565- if ( currentMessage && session . sessionIdentifier ) {
1618+ if ( currentMessage && session . sessionIdentifier && ! this . isTriggerCancelled ( triggerID ) ) {
15661619 this . chatHistoryDb . addMessage ( tabID , 'cwc' , session . sessionIdentifier , {
15671620 body : triggerPayload . message ,
15681621 type : 'prompt' as any ,
@@ -1577,22 +1630,14 @@ export class ChatController {
15771630 response . $metadata . requestId
15781631 } metadata: ${ inspect ( response . $metadata , { depth : 12 } ) } `
15791632 )
1580- this . cancelTokenSource = new vscode . CancellationTokenSource ( )
1581- await this . messenger . sendAIResponse (
1582- response ,
1583- session ,
1584- tabID ,
1585- triggerID ,
1586- triggerPayload ,
1587- this . cancelTokenSource . token
1588- )
15891633
1590- // Turn off AgentLoop flag after sending the AI response
1591- this . sessionStorage . setAgentLoopInProgress ( tabID , false )
1634+ if ( this . isTriggerCancelled ( triggerID ) ) {
1635+ return
1636+ }
1637+
1638+ await this . messenger . sendAIResponse ( response , session , tabID , triggerID , triggerPayload )
15921639 } catch ( e : any ) {
15931640 this . telemetryHelper . recordMessageResponseError ( triggerPayload , tabID , getHttpStatusCode ( e ) ?? 0 )
1594- // Turn off AgentLoop flag in case of exception
1595- this . sessionStorage . setAgentLoopInProgress ( tabID , false )
15961641 // clears session, record telemetry before this call
15971642 this . processException ( e , tabID )
15981643 }
@@ -1635,4 +1680,17 @@ export class ChatController {
16351680 return { relativeFilePath : filePath , lineRanges : mergedRanges }
16361681 } )
16371682 }
1683+
1684+ /**
1685+ * Check if a trigger has been cancelled and should not proceed
1686+ * @param triggerId The trigger ID to check
1687+ * @returns true if the trigger is cancelled and should not proceed
1688+ */
1689+ private isTriggerCancelled ( triggerId : string ) : boolean {
1690+ if ( ! triggerId ) {
1691+ return false
1692+ }
1693+ const conversationTracker = ConversationTracker . getInstance ( )
1694+ return conversationTracker . isTriggerCancelled ( triggerId )
1695+ }
16381696}
0 commit comments