Skip to content

Commit 69a662a

Browse files
committed
Working POC for Auto Scans and Projects scans from Q chat Panel
1 parent 2915614 commit 69a662a

39 files changed

+2764
-14
lines changed

packages/amazonq/src/app/chat/activation.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,7 @@ function registerApps(appInitContext: amazonq.AmazonQAppInitContext) {
6464
amazonq.cwChatAppInit(appInitContext)
6565
amazonq.featureDevChatAppInit(appInitContext)
6666
amazonq.gumbyChatAppInit(appInitContext)
67+
amazonq.scanChatAppInit(appInitContext)
6768
}
6869

6970
/**

packages/core/package.nls.json

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -112,6 +112,7 @@
112112
"AWS.command.amazonq.optimizeCode": "Optimize",
113113
"AWS.command.amazonq.sendToPrompt": "Send to prompt",
114114
"AWS.command.amazonq.security.scan": "Run Project Scan",
115+
"AWS.command.amazonq.security.autoscan": "Run File Scan",
115116
"AWS.command.deploySamApplication": "Deploy SAM Application",
116117
"AWS.command.aboutToolkit": "About",
117118
"AWS.command.downloadLambda": "Download...",
@@ -311,6 +312,7 @@
311312
"AWS.amazonq.featureDev.answer.qGeneratedCode": "The Amazon Q Developer Agent for software development has generated code for you to review",
312313
"AWS.amazonq.featureDev.answer.howCodeCanBeImproved": "How can the code be improved?",
313314
"AWS.amazonq.featureDev.answer.approachCreation": "Ok, let me create a plan. This may take a few minutes.",
315+
"AWS.amazonq.scans.runProjectScans": "Scanning code in this workspace ...",
314316
"AWS.amazonq.featureDev.answer.updateCode": "Code has been updated. Would you like to work on another task?",
315317
"AWS.amazonq.featureDev.answer.sessionClosed": "Your session is now closed.",
316318
"AWS.amazonq.featureDev.answer.newTaskChanges": "What change would you like to make?",

packages/core/src/amazonq/index.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,7 @@ export { AmazonQChatViewProvider } from './webview/webView'
2222
export { init as cwChatAppInit } from '../codewhispererChat/app'
2323
export { init as featureDevChatAppInit } from '../amazonqFeatureDev/app'
2424
export { init as gumbyChatAppInit } from '../amazonqGumby/app'
25+
export { init as scanChatAppInit } from '../amazonqScans/app'
2526
export { activateBadge } from './util/viewBadgeHandler'
2627
export { amazonQHelpUrl } from '../shared/constants'
2728
export { listCodeWhispererCommandsWalkthrough } from '../codewhisperer/ui/statusBarMenu'
Lines changed: 224 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,224 @@
1+
/*!
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* This class is responsible for listening to and processing events
6+
* from the webview and translating them into events to be handled by the extension,
7+
* and events from the extension and translating them into events to be handled by the webview.
8+
*/
9+
10+
import { ChatItem, ChatItemType } from '@aws/mynah-ui'
11+
import { ExtensionMessage } from '../commands'
12+
import { TabOpenType, TabsStorage } from '../storages/tabsStorage'
13+
import { GumbyMessageType } from '../../../../amazonqGumby/chat/views/connector/connector' //TODO
14+
import { ChatPayload } from '../connector'
15+
16+
export interface ConnectorProps {
17+
sendMessageToExtension: (message: ExtensionMessage) => void
18+
onMessageReceived?: (tabID: string, messageData: any, needToShowAPIDocsTab: boolean) => void
19+
onAsyncEventProgress: (tabID: string, inProgress: boolean, message: string, messageId: string) => void
20+
onChatAnswerReceived?: (tabID: string, message: ChatItem) => void
21+
onChatAnswerUpdated?: (tabID: string, message: ChatItem) => void
22+
onQuickHandlerCommand: (tabID: string, command: string, eventId?: string) => void
23+
onError: (tabID: string, message: string, title: string) => void
24+
onWarning: (tabID: string, message: string, title: string) => void
25+
onUpdateAuthentication: (gumbyEnabled: boolean, authenticatingTabIDs: string[]) => void
26+
onChatInputEnabled: (tabID: string, enabled: boolean) => void
27+
onUpdatePlaceholder: (tabID: string, newPlaceholder: string) => void
28+
tabsStorage: TabsStorage
29+
}
30+
31+
export interface MessageData {
32+
tabID: string
33+
type: GumbyMessageType
34+
}
35+
36+
export class Connector {
37+
private readonly onAuthenticationUpdate
38+
private readonly sendMessageToExtension
39+
private readonly onError
40+
private readonly onChatAnswerReceived
41+
private readonly onChatAnswerUpdated
42+
private readonly chatInputEnabled
43+
private readonly onAsyncEventProgress
44+
private readonly onQuickHandlerCommand
45+
private readonly updatePlaceholder
46+
private readonly tabStorage
47+
48+
constructor(props: ConnectorProps) {
49+
this.sendMessageToExtension = props.sendMessageToExtension
50+
this.onChatAnswerReceived = props.onChatAnswerReceived
51+
this.onChatAnswerUpdated = props.onChatAnswerUpdated
52+
this.onError = props.onError
53+
this.chatInputEnabled = props.onChatInputEnabled
54+
this.onAsyncEventProgress = props.onAsyncEventProgress
55+
this.updatePlaceholder = props.onUpdatePlaceholder
56+
this.onQuickHandlerCommand = props.onQuickHandlerCommand
57+
this.onAuthenticationUpdate = props.onUpdateAuthentication
58+
this.tabStorage = props.tabsStorage
59+
}
60+
61+
onTabAdd = (tabID: string, tabOpenInteractionType?: TabOpenType): void => {
62+
this.sendMessageToExtension({
63+
tabID: tabID,
64+
command: 'new-tab-was-created',
65+
tabType: 'scan',
66+
tabOpenInteractionType,
67+
})
68+
}
69+
70+
onTabRemove(tabID: string) {
71+
this.sendMessageToExtension({
72+
tabID: tabID,
73+
command: 'tab-was-removed',
74+
tabType: 'scan',
75+
})
76+
}
77+
78+
private processChatPrompt = async (messageData: any, tabID: string): Promise<void> => {
79+
if (this.onChatAnswerReceived === undefined) {
80+
return
81+
}
82+
83+
const answer: ChatItem = {
84+
type: ChatItemType.AI_PROMPT,
85+
body: messageData.message,
86+
formItems: messageData.formItems,
87+
buttons: messageData.formButtons,
88+
followUp: undefined,
89+
status: 'info',
90+
canBeVoted: false,
91+
}
92+
93+
this.onChatAnswerReceived(tabID, answer)
94+
95+
return
96+
}
97+
98+
private processChatMessage = async (messageData: any): Promise<void> => {
99+
if (this.onChatAnswerReceived === undefined || this.onChatAnswerUpdated === undefined) {
100+
return
101+
}
102+
103+
if (messageData.message !== undefined) {
104+
const answer: ChatItem = {
105+
type: messageData.messageType,
106+
messageId: messageData.messageId ?? messageData.triggerID,
107+
body: messageData.message,
108+
buttons: messageData.buttons ?? [],
109+
canBeVoted: false,
110+
}
111+
112+
if (messageData.messageId !== undefined) {
113+
this.onChatAnswerUpdated(messageData.tabID, answer)
114+
return
115+
}
116+
117+
this.onChatAnswerReceived(messageData.tabID, answer)
118+
}
119+
}
120+
121+
transform = (tabID: string): void => {
122+
this.sendMessageToExtension({
123+
tabID: tabID,
124+
command: 'scan',
125+
chatMessage: 'transform',
126+
tabType: 'scan', //TODO
127+
})
128+
}
129+
130+
requestAnswer = (tabID: string, payload: ChatPayload) => {
131+
this.tabStorage.updateTabStatus(tabID, 'busy')
132+
this.sendMessageToExtension({
133+
tabID: tabID,
134+
command: 'chat-prompt',
135+
chatMessage: payload.chatMessage,
136+
chatCommand: payload.chatCommand,
137+
tabType: 'scan',
138+
})
139+
}
140+
141+
private processAuthNeededException = async (messageData: any): Promise<void> => {
142+
if (this.onChatAnswerReceived === undefined) {
143+
return
144+
}
145+
146+
this.onChatAnswerReceived(messageData.tabID, {
147+
type: ChatItemType.SYSTEM_PROMPT,
148+
body: messageData.message,
149+
})
150+
}
151+
152+
onCustomFormAction(
153+
tabId: string,
154+
action: {
155+
id: string
156+
text?: string | undefined
157+
formItemValues?: Record<string, string> | undefined
158+
}
159+
) {
160+
if (action === undefined) {
161+
return
162+
}
163+
164+
this.sendMessageToExtension({
165+
command: 'form-action-click',
166+
action: action.id,
167+
formSelectedValues: action.formItemValues,
168+
tabType: 'scan',
169+
tabID: tabId,
170+
})
171+
}
172+
173+
onResponseBodyLinkClick = (tabID: string, messageId: string, link: string): void => {
174+
this.sendMessageToExtension({
175+
command: 'response-body-link-click',
176+
tabID,
177+
messageId,
178+
link,
179+
tabType: 'scan',
180+
})
181+
}
182+
183+
private processExecuteCommand = async (messageData: any): Promise<void> => {
184+
this.onQuickHandlerCommand(messageData.tabID, messageData.command, messageData.eventId)
185+
}
186+
187+
// This handles messages received from the extension, to be forwarded to the webview
188+
handleMessageReceive = async (messageData: { type: GumbyMessageType } & Record<string, any>) => {
189+
switch (messageData.type) {
190+
case 'asyncEventProgressMessage':
191+
this.onAsyncEventProgress(
192+
messageData.tabID,
193+
messageData.inProgress,
194+
messageData.message,
195+
messageData.messageId
196+
)
197+
break
198+
case 'authNeededException':
199+
await this.processAuthNeededException(messageData)
200+
break
201+
case 'authenticationUpdateMessage':
202+
this.onAuthenticationUpdate(messageData.gumbyEnabled, messageData.authenticatingTabIDs)
203+
break
204+
case 'chatInputEnabledMessage':
205+
this.chatInputEnabled(messageData.tabID, messageData.enabled)
206+
break
207+
case 'chatMessage':
208+
await this.processChatMessage(messageData)
209+
break
210+
case 'chatPrompt':
211+
await this.processChatPrompt(messageData, messageData.tabID)
212+
break
213+
case 'errorMessage':
214+
this.onError(messageData.tabID, messageData.message, messageData.title)
215+
break
216+
case 'sendCommandMessage':
217+
await this.processExecuteCommand(messageData)
218+
break
219+
case 'updatePlaceholderMessage':
220+
this.updatePlaceholder(messageData.tabID, messageData.newPlaceholder)
221+
break
222+
}
223+
}
224+
}

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,5 +31,6 @@ type MessageCommand =
3131
| 'file-click'
3232
| 'form-action-click'
3333
| 'open-settings'
34+
| 'scan'
3435

3536
export type ExtensionMessage = Record<string, any> & { command: MessageCommand }

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

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ import { Connector as CWChatConnector } from './apps/cwChatConnector'
88
import { Connector as FeatureDevChatConnector } from './apps/featureDevChatConnector'
99
import { Connector as AmazonQCommonsConnector } from './apps/amazonqCommonsConnector'
1010
import { Connector as GumbyChatConnector } from './apps/gumbyChatConnector'
11+
import { Connector as ScanChatConnector } from './apps/scanChatConnector'
1112
import { ExtensionMessage } from './commands'
1213
import { TabType, TabsStorage } from './storages/tabsStorage'
1314
import { WelcomeFollowupType } from './apps/amazonqCommonsConnector'
@@ -61,6 +62,7 @@ export class Connector {
6162
private readonly cwChatConnector
6263
private readonly featureDevChatConnector
6364
private readonly gumbyChatConnector
65+
private readonly scanChatConnector
6466
private readonly tabsStorage
6567
private readonly amazonqCommonsConnector: AmazonQCommonsConnector
6668

@@ -72,6 +74,7 @@ export class Connector {
7274
this.cwChatConnector = new CWChatConnector(props as ConnectorProps)
7375
this.featureDevChatConnector = new FeatureDevChatConnector(props)
7476
this.gumbyChatConnector = new GumbyChatConnector(props)
77+
this.scanChatConnector = new ScanChatConnector(props)
7578
this.amazonqCommonsConnector = new AmazonQCommonsConnector({
7679
sendMessageToExtension: this.sendMessageToExtension,
7780
onWelcomeFollowUpClicked: props.onWelcomeFollowUpClicked,
@@ -97,6 +100,8 @@ export class Connector {
97100
break
98101
case 'gumby':
99102
this.gumbyChatConnector.onResponseBodyLinkClick(tabID, messageId, link)
103+
case 'scan':
104+
this.scanChatConnector.onResponseBodyLinkClick(tabID, messageId, link)
100105
}
101106
}
102107

@@ -112,6 +117,8 @@ export class Connector {
112117
switch (this.tabsStorage.getTab(tabID)?.type) {
113118
case 'gumby':
114119
return this.gumbyChatConnector.requestAnswer(tabID, payload)
120+
case 'scan':
121+
return this.scanChatConnector.requestAnswer(tabID, payload)
115122
}
116123
}
117124

@@ -151,6 +158,10 @@ export class Connector {
151158
this.gumbyChatConnector.transform(tabID)
152159
}
153160

161+
transformScans = (tabID: string): void => {
162+
this.scanChatConnector.transform(tabID)
163+
}
164+
154165
handleMessageReceive = async (message: MessageEvent): Promise<void> => {
155166
if (message.data === undefined) {
156167
return
@@ -190,6 +201,9 @@ export class Connector {
190201
case 'gumby':
191202
this.gumbyChatConnector.onTabAdd(tabID)
192203
break
204+
case 'scan':
205+
this.scanChatConnector.onTabAdd(tabID)
206+
break
193207
}
194208
}
195209

@@ -277,6 +291,9 @@ export class Connector {
277291
case 'gumby':
278292
this.gumbyChatConnector.onTabRemove(tabID)
279293
break
294+
case 'scan':
295+
this.scanChatConnector.onTabRemove(tabID)
296+
break
280297
}
281298
}
282299

@@ -406,6 +423,9 @@ export class Connector {
406423
case 'gumby':
407424
this.gumbyChatConnector.onCustomFormAction(tabId, action)
408425
break
426+
case 'scan':
427+
this.scanChatConnector.onCustomFormAction(tabId, action)
428+
break
409429
case 'cwc':
410430
if (action.id === `open-settings`) {
411431
this.sendMessageToExtension({

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

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -105,7 +105,7 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => {
105105
body: 'Authentication successful. Connected to Amazon Q.',
106106
})
107107

108-
if (tabsStorage.getTab(tabID)?.type === 'gumby') {
108+
if (tabsStorage.getTab(tabID)?.type === 'gumby' || tabsStorage.getTab(tabID)?.type === 'scan') {
109109
mynahUI.updateStore(tabID, {
110110
promptInputDisabledState: false,
111111
})
@@ -119,6 +119,8 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => {
119119
quickActionHandler.handle({ command: '/transform' }, tabID, eventId)
120120
} else if (command === 'aws.awsq.clearchat') {
121121
quickActionHandler.handle({ command: '/clear' }, tabID)
122+
} else if (command === 'aws.awsq.scan') {
123+
quickActionHandler.handle({ command: '/scan' }, tabID)
122124
}
123125
},
124126
onCWCContextCommandMessage: (message: ChatItem, command?: string): string | undefined => {
@@ -376,6 +378,11 @@ export const createMynahUI = (ideApi: any, amazonQEnabled: boolean) => {
376378
chatMessage: prompt.prompt ?? '',
377379
})
378380
return
381+
} else if (tabsStorage.getTab(tabID)?.type === 'scan') {
382+
connector.requestAnswer(tabID, {
383+
chatMessage: prompt.prompt ?? '',
384+
})
385+
return
379386
}
380387

381388
if (prompt.command !== undefined && prompt.command.trim() !== '') {

packages/core/src/amazonq/webview/ui/quickActions/generator.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,6 +40,10 @@ export class QuickActionGenerator {
4040
command: '/transform',
4141
description: 'Transform your Java 8 or 11 Maven project to Java 17',
4242
},
43+
{
44+
command: '/scan',
45+
description: 'Identify and fix code issues before committing',
46+
},
4347
]
4448
: []),
4549
],
@@ -77,6 +81,10 @@ export class QuickActionGenerator {
7781
description: "This command isn't available in /transform",
7882
unavailableItems: ['/dev', '/transform'],
7983
},
84+
scan: {
85+
description: "This command isn't available in /scan",
86+
unavailableItems: ['/dev', '/scan'],
87+
},
8088
unknown: {
8189
description: '',
8290
unavailableItems: [],

0 commit comments

Comments
 (0)