Skip to content

Commit 8fb3eff

Browse files
committed
fix for new user prompt stoppage
1 parent e9cfe3a commit 8fb3eff

File tree

13 files changed

+543
-73
lines changed

13 files changed

+543
-73
lines changed

packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -257,13 +257,12 @@ export class Connector extends BaseConnector {
257257
}
258258

259259
if (messageData.type === 'asyncEventProgressMessage') {
260-
const enableStopAction = true
261260
this.onAsyncEventProgress(
262261
messageData.tabID,
263262
messageData.inProgress,
264263
messageData.message ?? undefined,
265264
messageData.messageId ?? undefined,
266-
enableStopAction,
265+
messageData.inProgress,
267266
false
268267
)
269268
return

packages/core/src/amazonq/webview/ui/main.ts

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -691,6 +691,7 @@ export const createMynahUI = (
691691
onTabChange: connector.onTabChange,
692692
// TODO: update mynah-ui this type doesn't seem correct https://github.com/aws/mynah-ui/blob/3777a39eb534a91fd6b99d6cf421ce78ee5c7526/src/main.ts#L372
693693
onStopChatResponse: (tabID: string) => {
694+
console.log('stop response started')
694695
mynahUI.updateStore(tabID, {
695696
loadingChat: false,
696697
promptInputDisabledState: false,
@@ -702,6 +703,12 @@ export const createMynahUI = (
702703
return
703704
}
704705

706+
mynahUI.updateStore(tabID, {
707+
loadingChat: false,
708+
promptInputDisabledState: false,
709+
})
710+
connector.onStopChatResponse(tabID)
711+
705712
const tabType = tabsStorage.getTab(tabID)?.type
706713
if (tabType === 'featuredev') {
707714
mynahUI.addChatItem(tabID, {
@@ -736,6 +743,8 @@ export const createMynahUI = (
736743
return
737744
}
738745

746+
console.log('prompt entered: ', prompt)
747+
739748
/**
740749
* Update the tab title if coming from the welcome page
741750
* non cwc panels will have this updated automatically

packages/core/src/codewhisperer/client/codewhisperer.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,8 @@ export interface CodeWhispererConfig {
3131
}
3232

3333
export const defaultServiceConfig: CodeWhispererConfig = {
34-
region: 'us-east-1',
35-
endpoint: 'https://codewhisperer.us-east-1.amazonaws.com/',
34+
region: 'us-west-2',
35+
endpoint: 'https://rts.alpha-us-west-2.codewhisperer.ai.aws.dev/',
3636
}
3737

3838
export function getCodewhispererConfig(): CodeWhispererConfig {

packages/core/src/codewhispererChat/clients/chat/v0/chat.ts

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,8 @@ export class ChatSession {
3737
private _context: PromptMessage['context']
3838
private _pairProgrammingModeOn: boolean = true
3939
private _fsWriteBackups: Map<string, FsWriteBackup> = new Map()
40+
private _agenticLoopInProgress: boolean = false
41+
4042
/**
4143
* True if messages from local history have been sent to session.
4244
*/
@@ -50,6 +52,33 @@ export class ChatSession {
5052
return this.sessionId
5153
}
5254

55+
public get agenticLoopInProgress(): boolean {
56+
return this._agenticLoopInProgress
57+
}
58+
59+
public setAgenticLoopInProgress(value: boolean) {
60+
// When setting agenticLoop to false (ending the loop), dispose the current token source
61+
if (this._agenticLoopInProgress === true && value === false) {
62+
this.disposeTokenSource()
63+
// Create a new token source for future operations
64+
this.createNewTokenSource()
65+
}
66+
this._agenticLoopInProgress = value
67+
}
68+
69+
/**
70+
* Safely disposes the current token source if it exists
71+
*/
72+
disposeTokenSource() {
73+
if (this.tokenSource) {
74+
try {
75+
this.tokenSource.dispose()
76+
} catch (error) {
77+
// Ignore errors during disposal
78+
}
79+
}
80+
}
81+
5382
public get pairProgrammingModeOn(): boolean {
5483
return this._pairProgrammingModeOn
5584
}

packages/core/src/codewhispererChat/controllers/chat/controller.ts

Lines changed: 83 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import * as path from 'path'
66
import * as vscode from 'vscode'
77
import { Event as VSCodeEvent, Uri, workspace, window, ViewColumn, Position, Selection } from 'vscode'
88
import { EditorContextExtractor } from '../../editor/context/extractor'
9+
import { ConversationTracker } from '../../storages/conversationTracker'
910
import { ChatSessionStorage } from '../../storages/chatSession'
1011
import { Messenger, MessengerResponseType, StaticTextResponseType } from './messenger/messenger'
1112
import {
@@ -101,6 +102,7 @@ import { FsWriteParams } from '../../tools/fsWrite'
101102
import { tempDirPath } from '../../../shared/filesystemUtilities'
102103
import { Database } from '../../../shared/db/chatDb/chatDb'
103104
import { TabBarController } from './tabBarController'
105+
import { sleep } from '../../../shared'
104106

105107
export interface ChatControllerMessagePublishers {
106108
readonly processPromptChatMessage: MessagePublisher<PromptMessage>
@@ -407,9 +409,25 @@ export class ChatController {
407409

408410
private async processStopResponseMessage(message: StopResponseMessage) {
409411
const session = this.sessionStorage.getSession(message.tabID)
412+
const wasInAgenticLoop = session.agenticLoopInProgress
410413
session.tokenSource.cancel()
414+
console.log('process stop has been triggered')
415+
session.setAgenticLoopInProgress(false)
416+
session.setToolUseWithError(undefined)
417+
418+
// Mark any active triggers as completed when stopping the response
419+
const triggerEvents = this.triggerEventsStorage.getTriggerEventsByTabID(message.tabID)
420+
if (triggerEvents && triggerEvents.length > 0) {
421+
const conversationTracker = ConversationTracker.getInstance()
422+
triggerEvents.forEach((event) => {
423+
conversationTracker.markTriggerCompleted(event.id)
424+
})
425+
}
426+
427+
wasInAgenticLoop && (await sleep(1000))
428+
411429
this.messenger.sendEmptyMessage(message.tabID, '', undefined)
412-
this.chatHistoryStorage.getTabHistory(message.tabID).clearRecentHistory()
430+
// this.chatHistoryStorage.getTabHistory(message.tabID).clearRecentHistory()
413431
}
414432

415433
private async processTriggerTabIDReceived(message: TriggerTabIDReceived) {
@@ -465,6 +483,13 @@ export class ChatController {
465483
}
466484

467485
private async processTabCloseMessage(message: TabClosedMessage) {
486+
// First cancel any active triggers to stop ongoing operations
487+
const conversationTracker = ConversationTracker.getInstance()
488+
conversationTracker.cancelTabTriggers(message.tabID)
489+
490+
// Then clear all triggers to release resources
491+
conversationTracker.clearTabTriggers(message.tabID)
492+
468493
this.sessionStorage.deleteSession(message.tabID)
469494
this.chatHistoryStorage.deleteHistory(message.tabID)
470495
this.triggerEventsStorage.removeTabEvents(message.tabID)
@@ -680,7 +705,7 @@ export class ChatController {
680705
this.editorContextExtractor
681706
.extractContextForTrigger('ChatMessage')
682707
.then(async (context) => {
683-
const triggerID = randomUUID()
708+
const triggerID = message.triggerId ?? randomUUID()
684709
this.triggerEventsStorage.addTriggerEvent({
685710
id: triggerID,
686711
tabID: message.tabID,
@@ -690,10 +715,16 @@ export class ChatController {
690715
})
691716
this.messenger.sendAsyncEventProgress(tabID, true, '')
692717
const session = this.sessionStorage.getSession(tabID)
718+
719+
// Check if the session has been cancelled before proceeding
720+
if (session.tokenSource.token.isCancellationRequested) {
721+
getLogger().debug(`Tool execution cancelled for tabID: ${tabID}`)
722+
return
723+
}
724+
693725
const toolUseWithError = session.toolUseWithError
694726
if (!toolUseWithError || !toolUseWithError.toolUse || !toolUseWithError.toolUse.input) {
695727
// Turn off AgentLoop flag if there's no tool use
696-
this.sessionStorage.setAgentLoopInProgress(tabID, false)
697728
return
698729
}
699730
session.setToolUseWithError(undefined)
@@ -716,14 +747,33 @@ export class ChatController {
716747
try {
717748
await ToolUtils.validate(tool)
718749

719-
const chatStream = new ChatStream(this.messenger, tabID, triggerID, toolUse, {
720-
requiresAcceptance: false,
721-
})
750+
// Get the cancellation token from the session
751+
const cancellationToken = session.tokenSource.token
752+
753+
// Pass the cancellation token to ChatStream
754+
const chatStream = new ChatStream(
755+
this.messenger,
756+
tabID,
757+
triggerID,
758+
toolUse,
759+
{ requiresAcceptance: false },
760+
undefined,
761+
undefined,
762+
cancellationToken
763+
)
764+
722765
if (tool.type === ToolType.FsWrite && toolUse.toolUseId) {
723766
const backup = await tool.tool.getBackup()
724767
session.setFsWriteBackup(toolUse.toolUseId, backup)
725768
}
726-
const output = await ToolUtils.invoke(tool, chatStream)
769+
770+
// Check again if cancelled before invoking the tool
771+
if (cancellationToken.isCancellationRequested) {
772+
getLogger().debug(`Tool execution cancelled before invoke for tabID: ${tabID}`)
773+
return
774+
}
775+
776+
const output = await ToolUtils.invoke(tool, chatStream, cancellationToken)
727777
ToolUtils.validateOutput(output)
728778

729779
toolResults.push({
@@ -985,7 +1035,17 @@ export class ChatController {
9851035

9861036
// Turn off AgentLoop flag in case of exception
9871037
if (tabID) {
988-
this.sessionStorage.setAgentLoopInProgress(tabID, false)
1038+
const session = this.sessionStorage.getSession(tabID)
1039+
session.setAgenticLoopInProgress(false)
1040+
1041+
// Mark any active triggers as completed when there's an exception
1042+
const triggerEvents = this.triggerEventsStorage.getTriggerEventsByTabID(tabID)
1043+
if (triggerEvents && triggerEvents.length > 0) {
1044+
const conversationTracker = ConversationTracker.getInstance()
1045+
triggerEvents.forEach((event) => {
1046+
conversationTracker.markTriggerCompleted(event.id)
1047+
})
1048+
}
9891049
}
9901050

9911051
this.messenger.sendErrorMessage(errorMessage, tabID, requestID)
@@ -1171,22 +1231,33 @@ export class ChatController {
11711231

11721232
private async processPromptMessageAsNewThread(message: PromptMessage) {
11731233
const session = this.sessionStorage.getSession(message.tabID)
1234+
// If there's an existing conversation, ensure we dispose the previous token
1235+
if (session.agenticLoopInProgress) {
1236+
session.disposeTokenSource()
1237+
}
1238+
1239+
// Create a fresh token for this new conversation
1240+
session.createNewTokenSource()
1241+
session.setAgenticLoopInProgress(true)
11741242
session.clearListOfReadFiles()
11751243
session.setShowDiffOnFileWrite(false)
11761244
this.editorContextExtractor
11771245
.extractContextForTrigger('ChatMessage')
11781246
.then(async (context) => {
11791247
const triggerID = randomUUID()
1248+
1249+
// Register the trigger ID with the token for cancellation tracking
1250+
const conversationTracker = ConversationTracker.getInstance()
1251+
conversationTracker.registerTrigger(triggerID, session.tokenSource, message.tabID)
1252+
console.log('conversation tracker:', conversationTracker.getTokenForTrigger(triggerID))
1253+
11801254
this.triggerEventsStorage.addTriggerEvent({
11811255
id: triggerID,
11821256
tabID: message.tabID,
11831257
message: message.message,
11841258
type: 'chat_message',
11851259
context,
11861260
})
1187-
1188-
this.messenger.sendAsyncEventProgress(message.tabID, true, '')
1189-
11901261
await this.generateResponse(
11911262
{
11921263
message: message.message ?? '',
@@ -1381,16 +1452,6 @@ export class ChatController {
13811452
}
13821453

13831454
const tabID = triggerEvent.tabID
1384-
if (this.sessionStorage.isAgentLoopInProgress(tabID)) {
1385-
// If a response is already in progress, stop it first
1386-
const stopResponseMessage: StopResponseMessage = {
1387-
tabID: tabID,
1388-
}
1389-
await this.processStopResponseMessage(stopResponseMessage)
1390-
}
1391-
1392-
// Ensure AgentLoop flag is set to true during response generation
1393-
this.sessionStorage.setAgentLoopInProgress(tabID, true)
13941455

13951456
const credentialsState = await AuthUtil.instance.getChatAuthState()
13961457

@@ -1493,6 +1554,7 @@ export class ChatController {
14931554
session.setContext(triggerPayload.context)
14941555
}
14951556
this.messenger.sendInitalStream(tabID, triggerID)
1557+
this.messenger.sendAsyncEventProgress(tabID, true, '')
14961558
this.telemetryHelper.setConversationStreamStartTime(tabID)
14971559
if (isSsoConnection(AuthUtil.instance.conn)) {
14981560
const { $metadata, generateAssistantResponseResponse } = await session.chatSso(request)
@@ -1527,13 +1589,8 @@ export class ChatController {
15271589
} metadata: ${inspect(response.$metadata, { depth: 12 })}`
15281590
)
15291591
await this.messenger.sendAIResponse(response, session, tabID, triggerID, triggerPayload, chatHistory)
1530-
1531-
// Turn off AgentLoop flag after sending the AI response
1532-
this.sessionStorage.setAgentLoopInProgress(tabID, false)
15331592
} catch (e: any) {
15341593
this.telemetryHelper.recordMessageResponseError(triggerPayload, tabID, getHttpStatusCode(e) ?? 0)
1535-
// Turn off AgentLoop flag in case of exception
1536-
this.sessionStorage.setAgentLoopInProgress(tabID, false)
15371594
// clears session, record telemetry before this call
15381595
this.processException(e, tabID)
15391596
}

0 commit comments

Comments
 (0)