diff --git a/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts b/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts index b6dd4f7be0b..274ae1f1858 100644 --- a/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts +++ b/packages/core/src/amazonq/webview/ui/apps/cwChatConnector.ts @@ -325,7 +325,7 @@ export class Connector extends BaseConnector { if ( !this.onChatAnswerUpdated || - !['accept-code-diff', 'reject-code-diff', 'confirm-tool-use'].includes(action.id) + !['accept-code-diff', 'reject-code-diff', 'run-shell-command', 'reject-shell-command'].includes(action.id) ) { return } @@ -363,17 +363,27 @@ export class Connector extends BaseConnector { answer.body = ' ' } break - case 'confirm-tool-use': - answer.buttons = [ - { - keepCardAfterClick: true, - text: 'Confirmed', - id: 'confirmed-tool-use', + case 'run-shell-command': + answer.header = { + icon: 'code-block' as MynahIconsType, + body: 'shell', + status: { + icon: 'ok' as MynahIconsType, + text: 'Accepted', status: 'success', - position: 'outside', - disabled: true, }, - ] + } + break + case 'reject-shell-command': + answer.header = { + icon: 'code-block' as MynahIconsType, + body: 'shell', + status: { + icon: 'cancel' as MynahIconsType, + text: 'Rejected', + status: 'error', + }, + } break default: break diff --git a/packages/core/src/codewhispererChat/controllers/chat/controller.ts b/packages/core/src/codewhispererChat/controllers/chat/controller.ts index 8df4cbcf903..b34717f8f18 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/controller.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/controller.ts @@ -818,13 +818,25 @@ export class ChatController { } } + private async rejectShellCommand(message: CustomFormActionMessage) { + const triggerId = randomUUID() + this.triggerEventsStorage.addTriggerEvent({ + id: triggerId, + tabID: message.tabID, + message: undefined, + type: 'chat_message', + context: undefined, + }) + await this.generateStaticTextResponse('reject-shell-command', triggerId) + } + private async processCustomFormAction(message: CustomFormActionMessage) { switch (message.action.id) { case 'submit-create-prompt': await this.handleCreatePrompt(message) break case 'accept-code-diff': - case 'confirm-tool-use': + case 'run-shell-command': case 'generic-tool-execution': await this.closeDiffView() await this.processToolUseMessage(message) @@ -832,6 +844,9 @@ export class ChatController { case 'reject-code-diff': await this.closeDiffView() break + case 'reject-shell-command': + await this.rejectShellCommand(message) + break case 'tool-unavailable': await this.processUnavailableToolUseMessage(message) break diff --git a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts index 1efab5c2d75..9011d69527e 100644 --- a/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts +++ b/packages/core/src/codewhispererChat/controllers/chat/messenger/messenger.ts @@ -50,7 +50,12 @@ import { noWriteTools, tools } from '../../../constants' import { Change } from 'diff' import { FsWriteParams } from '../../../tools/fsWrite' -export type StaticTextResponseType = 'quick-action-help' | 'onboarding-help' | 'transform' | 'help' +export type StaticTextResponseType = + | 'quick-action-help' + | 'onboarding-help' + | 'transform' + | 'help' + | 'reject-shell-command' export type MessengerResponseType = { $metadata: { requestId?: string; httpStatusCode?: number } @@ -269,12 +274,20 @@ export class Messenger { } if (!validation.requiresAcceptance) { - // Need separate id for read tool and safe bash command execution as 'confirm-tool-use' id is required to change button status from `Confirm` to `Confirmed` state in cwChatConnector.ts which will impact generic tool execution. - this.dispatcher.sendCustomFormActionMessage( - new CustomFormActionMessage(tabID, { - id: 'generic-tool-execution', - }) - ) + // Need separate id for read tool and safe bash command execution as 'run-shell-command' id is required to state in cwChatConnector.ts which will impact generic tool execution. + if (tool.type === ToolType.ExecuteBash) { + this.dispatcher.sendCustomFormActionMessage( + new CustomFormActionMessage(tabID, { + id: 'run-shell-command', + }) + ) + } else { + this.dispatcher.sendCustomFormActionMessage( + new CustomFormActionMessage(tabID, { + id: 'generic-tool-execution', + }) + ) + } } } else { // TODO: Handle the error @@ -500,12 +513,28 @@ export class Messenger { ) { const buttons: ChatItemButton[] = [] let fileList: ChatItemContent['fileList'] = undefined - if (validation.requiresAcceptance && toolUse?.name === ToolType.ExecuteBash) { - buttons.push({ - id: 'confirm-tool-use', - text: 'Confirm', - status: 'info', - }) + let shellCommandHeader = undefined + if (toolUse?.name === ToolType.ExecuteBash && message.startsWith('```shell')) { + if (validation.requiresAcceptance) { + buttons.push({ + id: 'run-shell-command', + text: 'Run', + status: 'main', + icon: 'play' as MynahIconsType, + }) + buttons.push({ + id: 'reject-shell-command', + text: 'Reject', + status: 'clear', + icon: 'cancel' as MynahIconsType, + }) + } + + shellCommandHeader = { + icon: 'code-block' as MynahIconsType, + body: 'shell', + buttons: buttons, + } if (validation.warning) { message = validation.warning + message @@ -564,16 +593,23 @@ export class Messenger { codeBlockLanguage: undefined, contextList: undefined, canBeVoted: false, - buttons: toolUse?.name === ToolType.FsWrite ? undefined : buttons, - fullWidth: toolUse?.name === ToolType.FsWrite, - padding: !(toolUse?.name === ToolType.FsWrite), + buttons: + toolUse?.name === ToolType.FsWrite || toolUse?.name === ToolType.ExecuteBash + ? undefined + : buttons, + fullWidth: toolUse?.name === ToolType.FsWrite || toolUse?.name === ToolType.ExecuteBash, + padding: !(toolUse?.name === ToolType.FsWrite || toolUse?.name === ToolType.ExecuteBash), header: toolUse?.name === ToolType.FsWrite ? { icon: 'code-block' as MynahIconsType, buttons: buttons, fileList: fileList } - : undefined, + : toolUse?.name === ToolType.ExecuteBash + ? shellCommandHeader + : undefined, codeBlockActions: // eslint-disable-next-line unicorn/no-null, prettier/prettier - toolUse?.name === ToolType.FsWrite ? { 'insert-to-cursor': null, copy: null } : undefined, + toolUse?.name === ToolType.FsWrite || toolUse?.name === ToolType.ExecuteBash + ? { 'insert-to-cursor': null, copy: null } + : undefined, }, tabID ) @@ -625,6 +661,10 @@ export class Messenger { ] followUpsHeader = 'Try Examples:' break + case 'reject-shell-command': + // need to update the string later + message = 'The shell command execution rejected. Abort.' + break } this.dispatcher.sendChatMessage( diff --git a/packages/core/src/codewhispererChat/tools/executeBash.ts b/packages/core/src/codewhispererChat/tools/executeBash.ts index 73da1126a88..84308390673 100644 --- a/packages/core/src/codewhispererChat/tools/executeBash.ts +++ b/packages/core/src/codewhispererChat/tools/executeBash.ts @@ -327,7 +327,6 @@ export class ExecuteBash { } public queueDescription(updates: Writable): void { - updates.write(`I will run the following shell command:\n`) updates.write('```shell\n' + this.command + '\n```') updates.end() }