diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/common/AppModPluginInstaller.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/common/AppModPluginInstaller.java index 3640832996..75972cddeb 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/common/AppModPluginInstaller.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/common/AppModPluginInstaller.java @@ -107,8 +107,8 @@ public static void showInstallConfirmation(@Nonnull Project project, boolean for : "Install this plugin to automate migrating your apps to Azure with Copilot."; } else { message = forUpgrade - ? "To upgrade your apps, you'll need two plugins: GitHub Copilot and app modernization." - : "To migrate to Azure, you'll need two plugins: GitHub Copilot and app modernization."; + ? "To upgrade your apps, you'll need to install GitHub Copilot modernization." + : "To migrate to Azure, you'll need to install GitHub Copilot modernization."; } AppModUtils.logTelemetryEvent("plugin." + action + ".install-prompt-shown", Map.of("copilotInstalled", String.valueOf(copilotInstalled))); if (Messages.showOkCancelDialog(project, message, title, "Install", "Cancel", Messages.getQuestionIcon()) == Messages.OK) { diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/JavaUpgradeCheckStartupActivity.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/JavaUpgradeCheckStartupActivity.java index 023d7c58db..d5b2a73a7d 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/JavaUpgradeCheckStartupActivity.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/JavaUpgradeCheckStartupActivity.java @@ -10,6 +10,9 @@ import com.intellij.openapi.project.DumbService; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.ProjectActivity; +import com.intellij.ide.plugins.IdeaPluginDescriptor; +import com.intellij.ide.plugins.PluginManagerCore; +import com.intellij.openapi.extensions.PluginId; import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.dao.JavaUpgradeIssue; import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaUpgradeIssuesCache; import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaVersionNotificationService; @@ -32,6 +35,7 @@ public class JavaUpgradeCheckStartupActivity implements ProjectActivity, DumbAwa // Additional delay after smart mode to ensure Maven/Gradle sync is complete private static final long POST_INDEXING_DELAY_SECONDS = 3; + private static final String COPILOT_PLUGIN_ID = "com.github.copilot"; @Override public Object execute(@Nonnull Project project, @Nonnull Continuation continuation) { @@ -67,6 +71,12 @@ private void performJavaUpgradeCheck(@Nonnull Project project) { if (project.isDisposed()) { return; } + + // Warm up Copilot's lazy chat-mode indexing once per project open. Copilot only scans + // custom agents from .github/agents/ when its chat-mode registry is explicitly refreshed + // (otherwise not until the chat panel is first opened), so we trigger that refresh now — + // before the user clicks a fix action — so the agent is resolvable on the very first click. + warmUpCopilotChatModes(project); // Refresh the cache (this populates JDK and dependency issues for use by inspections) final JavaUpgradeIssuesCache cache = JavaUpgradeIssuesCache.getInstance(project); @@ -101,4 +111,20 @@ private void performJavaUpgradeCheck(@Nonnull Project project) { log.error("Error performing Java upgrade check for project: {}", project.getName(), e); } } + + private void warmUpCopilotChatModes(@Nonnull Project project) { + try { + final IdeaPluginDescriptor copilot = PluginManagerCore.getPlugin(PluginId.getId(COPILOT_PLUGIN_ID)); + if (copilot == null || !copilot.isEnabled() || copilot.getPluginClassLoader() == null) { + return; + } + // Actively trigger Copilot to (re)scan custom agents so its chat-mode registry is populated + // before the first fix-action click. Merely reading the chatModes StateFlow does NOT populate + // it — only refreshChatModes() does — which is why a cold first click previously missed the agent. + JavaVersionNotificationService.triggerChatModesRefresh(project, copilot.getPluginClassLoader()); + } catch (Throwable e) { + // Best effort only; the fix action still falls back to its own URI path. + log.warn("Failed to warm up Copilot Chat modes: {}", project.getName(), e); + } + } } diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyInProblemsViewAction.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyInProblemsViewAction.java index ca1fc05af1..2dc05fbdbf 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyInProblemsViewAction.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyInProblemsViewAction.java @@ -11,7 +11,6 @@ import com.intellij.openapi.vfs.VirtualFile; import com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller; import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.dao.VulnerabilityInfo; -import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaUpgradeIssuesCache; import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaVersionNotificationService; import com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.ProblemsViewUtils; import com.microsoft.azure.toolkit.intellij.appmod.utils.AppModUtils; @@ -47,13 +46,15 @@ public void actionPerformed(@NotNull AnActionEvent e) { if (vulnerabilityInfo == null) { JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt( project, - SCAN_AND_RESOLVE_CVES_PROMPT + SCAN_AND_RESOLVE_CVES_PROMPT, + APPMOD_CVE_AGENT_NAME ); } else { JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt( project, String.format(FIX_VULNERABLE_DEPENDENCY_WITH_COPILOT_PROMPT, - vulnerabilityInfo.getDependencyCoordinate()) + vulnerabilityInfo.getDependencyCoordinate()), + APPMOD_CVE_AGENT_NAME ); } AppModUtils.logTelemetryEvent("openCopilotChatForCveFixDependencyInProblemsViewAction", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled()))); @@ -82,19 +83,17 @@ public void update(@NotNull AnActionEvent e) { final VirtualFile file = e.getData(CommonDataKeys.VIRTUAL_FILE); final boolean isBuildFile = isBuildFile(file); - if (!isBuildFile || !isCVEIssue(description)) { - e.getPresentation().setEnabledAndVisible(false); - return; - } - final var issue = JavaUpgradeIssuesCache.getInstance(project).findCveIssue(vulnerabilityInfo.getGroupId() + ":" + vulnerabilityInfo.getArtifactId()); - if (issue == null) { + if (!isBuildFile || !isCVEIssue(description) || vulnerabilityInfo == null) { e.getPresentation().setEnabledAndVisible(false); return; } e.getPresentation().setEnabledAndVisible(true); // e.getPresentation().setText(SCAN_AND_RESOLVE_CVES_WITH_COPILOT_DISPLAY_NAME); + final String baseText = getTemplatePresentation().getText(); if (!AppModPluginInstaller.isAppModPluginInstalled()) { - e.getPresentation().setText(e.getPresentation().getText() + AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN); + e.getPresentation().setText(baseText + AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN); + } else { + e.getPresentation().setText(baseText); } } catch (Throwable ex) { // In case of any error, hide the action diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyIntentionAction.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyIntentionAction.java index 8f3114202b..72d0f48222 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyIntentionAction.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixDependencyIntentionAction.java @@ -103,7 +103,7 @@ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws // Try to extract dependency information from the current context final String prompt = buildPromptFromContext(editor, file); - JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt); + JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_CVE_AGENT_NAME); AppModUtils.logTelemetryEvent("openCveFixDependencyCopilotChatFromIntentionAction"); } catch (Throwable e) { log.error("Failed to invoke CveFixDependencyIntentionAction: ", e); diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixInProblemsViewAction.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixInProblemsViewAction.java index fe05c38851..1c5efe6d96 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixInProblemsViewAction.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixInProblemsViewAction.java @@ -46,7 +46,8 @@ public void actionPerformed(@NotNull AnActionEvent e) { JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt( project, - SCAN_AND_RESOLVE_CVES_PROMPT + SCAN_AND_RESOLVE_CVES_PROMPT, + APPMOD_CVE_AGENT_NAME ); AppModUtils.logTelemetryEvent("openCopilotChatForCveFixInProblemsViewAction", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled()))); } catch (Throwable ex) { diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixIntentionAction.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixIntentionAction.java index 01fc11e809..4c7c4ec637 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixIntentionAction.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/CveFixIntentionAction.java @@ -24,6 +24,7 @@ import java.util.Map; +import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_CVE_AGENT_NAME; import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.SCAN_AND_RESOLVE_CVES_PROMPT; import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.SCAN_AND_RESOLVE_CVES_WITH_COPILOT_DISPLAY_NAME; @@ -107,7 +108,7 @@ public void invoke(@NotNull Project project, Editor editor, PsiFile file) throws // Try to extract dependency information from the current context final String prompt = buildPromptFromContext(); - JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt); + JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_CVE_AGENT_NAME); AppModUtils.logTelemetryEvent("openCveFixCopilotChatFromIntentionAction", Map.of("AppModPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled()))); } catch (Throwable e) { log.error("Failed to invoke CveFixIntentionAction: ", e); diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeContextMenuAction.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeContextMenuAction.java index 4f56c35018..c61814e4c7 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeContextMenuAction.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeContextMenuAction.java @@ -21,6 +21,7 @@ import static com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller.TO_INSTALL_APP_MODE_PLUGIN; import static com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller.isAppModPluginInstalled; +import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_UPGRADE_AGENT_NAME; import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.UPGRADE_JAVA_AND_FRAMEWORK_PROMPT; /** @@ -56,8 +57,11 @@ public void update(@NotNull AnActionEvent e) { isMavenBuildFile(file) || isGradleBuildFile(file); } + final String baseText = getTemplatePresentation().getText(); if (!isAppModPluginInstalled()) { - e.getPresentation().setText(e.getPresentation().getText() + TO_INSTALL_APP_MODE_PLUGIN); + e.getPresentation().setText(baseText + TO_INSTALL_APP_MODE_PLUGIN); + } else { + e.getPresentation().setText(baseText); } if (visible){ AppModUtils.logTelemetryEvent("showJavaUpgradeContextMenuAction", Map.of("appmodPluginInstalled", String.valueOf(isAppModPluginInstalled()))); @@ -81,7 +85,7 @@ public void actionPerformed(@NotNull AnActionEvent e) { String prompt = buildUpgradePrompt(); // Open Copilot chat with the upgrade prompt - JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt); + JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_UPGRADE_AGENT_NAME); AppModUtils.logTelemetryEvent("openJavaUpgradeCopilotChatFromContextMenu", Map.of("appmodPluginInstalled", String.valueOf(isAppModPluginInstalled()))); } catch (Throwable ex) { // Log error but do not crash diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeQuickFix.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeQuickFix.java index ba63225d7d..fe265da1e3 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeQuickFix.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/JavaUpgradeQuickFix.java @@ -18,6 +18,7 @@ import java.util.Map; +import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.APPMOD_UPGRADE_AGENT_NAME; import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.UPGRADE_JAVA_FRAMEWORK_PROMPT; /** @@ -55,7 +56,7 @@ public String getName() { public void applyFix(@NotNull Project project, @NotNull ProblemDescriptor descriptor) { try { String prompt = buildPromptForIssue(issue); - JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt); + JavaVersionNotificationService.getInstance().openCopilotChatWithPrompt(project, prompt, APPMOD_UPGRADE_AGENT_NAME); AppModUtils.logTelemetryEvent("openCopilotChatForJavaUpgradeQuickFix", Map.of("appmodPluginInstalled", String.valueOf(AppModPluginInstaller.isAppModPluginInstalled()))); } catch (Throwable ex) { log.error("Failed to apply Java upgrade quick fix", ex); diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/UpgradeActionRegistrar.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/UpgradeActionRegistrar.java index d0d58a23ce..a41c567e7e 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/UpgradeActionRegistrar.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/action/UpgradeActionRegistrar.java @@ -6,10 +6,15 @@ package com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.action; import com.intellij.openapi.actionSystem.ActionManager; +import com.intellij.openapi.actionSystem.ActionPlaces; +import com.intellij.openapi.actionSystem.ActionPopupMenu; import com.intellij.openapi.actionSystem.AnAction; import com.intellij.openapi.actionSystem.DefaultActionGroup; import com.intellij.openapi.actionSystem.Presentation; import com.intellij.openapi.actionSystem.Separator; +import com.intellij.openapi.actionSystem.ex.ActionManagerEx; +import com.intellij.openapi.actionSystem.ex.ActionPopupMenuListener; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.project.Project; import com.intellij.openapi.startup.ProjectActivity; import com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller; @@ -21,6 +26,8 @@ import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.Nullable; +import java.util.concurrent.atomic.AtomicBoolean; + /** * Registers the Upgrade action into the GitHub Copilot context menu at runtime. * This is needed because the Copilot plugin creates its context menu groups dynamically. @@ -31,17 +38,67 @@ public class UpgradeActionRegistrar implements ProjectActivity { private static final String UPGRADE_ACTION_ID = "AzureToolkit.JavaUpgradeContextMenu"; private static final String PROJECT_VIEW_POPUP_MENU = "ProjectViewPopupMenu"; + // Application-level guard so we install the popup listener only once per IDE process, + // even if multiple projects are opened (this ProjectActivity runs per-project). + private static final AtomicBoolean POPUP_LISTENER_INSTALLED = new AtomicBoolean(false); + @Nullable @Override public Object execute(@NotNull Project project, @NotNull Continuation continuation) { try{ + // Eager attempt: works on 2nd+ project open within the same IDE process, + // after the GitHub Copilot plugin has populated its dynamic submenu. discoverAndRegisterAction(); + // Lazy fallback (fixes the first-open race): re-attempt the registration + // every time the Project View popup is created. The Copilot submenu is + // guaranteed to exist by the time the user right-clicks, and the call + // is cheap + idempotent thanks to the containsAction guard. + installLazyRegistrationListener(); } catch (Throwable e) { log.error("Failed to register Upgrade action in Copilot context menu.", e); } return Unit.INSTANCE; } + /** + * Installs an application-scoped {@link ActionPopupMenuListener} (only once per IDE + * process) that re-runs {@link #discoverAndRegisterAction()} whenever the Project + * View popup menu is opened. This is the lazy fallback for the first project open + * after IDE launch, where {@link ProjectActivity}s from us and from the GitHub Copilot + * plugin race and our discovery can miss Copilot's not-yet-created submenu. + */ + private void installLazyRegistrationListener() { + if (!POPUP_LISTENER_INSTALLED.compareAndSet(false, true)) { + return; + } + try { + ActionManagerEx.getInstanceEx().addActionPopupMenuListener(new ActionPopupMenuListener() { + @Override + public void actionPopupMenuCreated(@NotNull ActionPopupMenu menu) { + // Only react to the Project View right-click popup; ignore all + // other popups (editor, tool windows, etc.) to keep this cheap. + if (!ActionPlaces.PROJECT_VIEW_POPUP.equals(menu.getPlace())) { + return; + } + try { + discoverAndRegisterAction(); + } catch (Throwable ex) { + log.warn("Lazy registration of Upgrade action into Copilot submenu failed.", ex); + } + } + + @Override + public void actionPopupMenuReleased(@NotNull ActionPopupMenu menu) { + // no-op + } + }, ApplicationManager.getApplication()); + } catch (Throwable e) { + // Roll back the flag so a later project open can try installing again. + POPUP_LISTENER_INSTALLED.set(false); + log.warn("Failed to install lazy registration listener for Upgrade action.", e); + } + } + private void discoverAndRegisterAction() { // Only proceed if Copilot plugin is installed if (!AppModPluginInstaller.isCopilotInstalled()) { diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/service/JavaVersionNotificationService.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/service/JavaVersionNotificationService.java index 4ea59332a0..437c933a54 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/service/JavaVersionNotificationService.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/service/JavaVersionNotificationService.java @@ -14,6 +14,7 @@ import com.intellij.notification.Notifications; import com.intellij.openapi.actionSystem.AnActionEvent; import com.intellij.openapi.actionSystem.DataContext; +import com.intellij.openapi.application.ApplicationManager; import com.intellij.openapi.extensions.PluginId; import com.intellij.openapi.project.Project; import com.microsoft.azure.toolkit.intellij.appmod.common.AppModPluginInstaller; @@ -25,6 +26,11 @@ import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.utils.Constants.*; import static com.microsoft.azure.toolkit.intellij.appmod.javaupgrade.service.JavaUpgradeIssuesDetectionService.*; import java.lang.reflect.Method; +import java.lang.reflect.Modifier; +import java.util.ArrayDeque; +import java.util.Deque; +import java.util.HashSet; +import java.util.Set; import com.microsoft.azure.toolkit.lib.common.telemetry.AzureTelemeter; import kotlin.Unit; @@ -32,9 +38,11 @@ import lombok.extern.slf4j.Slf4j; import org.jetbrains.annotations.NotNull; import javax.annotation.Nonnull; +import javax.annotation.Nullable; import java.util.List; import java.util.Map; import java.util.Objects; +import java.util.concurrent.ConcurrentHashMap; /** * Service to display notifications about outdated Java project versions. @@ -48,10 +56,25 @@ public class JavaVersionNotificationService { private static final String NOTIFICATIONS_ENABLED_KEY = "azure.toolkit.java.version.notifications.enabled"; private static final String DEFERRED_UNTIL_KEY = "azure.toolkit.java.version.deferred_until"; private static final long DEFER_INTERVAL_MS = 10 * 24 * 60 * 60 * 1000L; // 10 days in milliseconds - private static final String DEFAULT_MODEL_NAME = "Claude Sonnet 4.5"; + private static final String DEFAULT_MODEL_NAME = "Claude Sonnet 4.6"; // GitHub Copilot plugin ID private static final String COPILOT_PLUGIN_ID = "com.github.copilot"; + // App Modernization plugin (registers the custom agents we want to pre-select). + private static final String APPMOD_PLUGIN_ID = "com.github.copilot.appmod"; + // Resolved from the Copilot plugin via reflection (older versions don't expose it). + private static final String COPILOT_CHAT_MODE_SERVICE_CLASS = "com.github.copilot.agent.chatMode.ChatModeService"; + // Copilot indexes project-scope .github/agents/*.agent.md asynchronously after project open; + // the first action can miss the agent for quite a while, so retry off-EDT for up to ~20 s. + private static final int AGENT_RESOLVE_RETRY_ATTEMPTS = 100; + private static final long AGENT_RESOLVE_RETRY_DELAY_MS = 200L; + + // Successfully resolved agent URIs, keyed by "|". Once an agent + // is resolved we never need to pay the retry cost again for that project, so repeated clicks are instant. + private static final Map RESOLVED_AGENT_URIS = new ConcurrentHashMap<>(); + // Keys with an in-flight resolution (same key format). Lets rapid repeat clicks share/skip a single + // ~20 s retry loop instead of each spawning its own sleeping thread. + private static final Set IN_FLIGHT_AGENT_RESOLUTIONS = ConcurrentHashMap.newKeySet(); private static JavaVersionNotificationService instance; @@ -293,7 +316,7 @@ private boolean isUpgradeSupported(@Nonnull JavaUpgradeIssue issue) { */ private void openCopilotChatWithUpgradePrompt(@Nonnull Project project, @Nonnull JavaUpgradeIssue issue) { final String prompt = buildUpgradePrompt(issue); - openCopilotChatWithPrompt(project, prompt); + openCopilotChatWithPrompt(project, prompt, APPMOD_UPGRADE_AGENT_NAME); } /** @@ -317,7 +340,7 @@ public void openCopilotChatWithPrompt(@Nonnull Project project, @Nonnull String // } // Fallback to reflection for cross-version compatibility - if (tryReflectionCopilotCall(project, prompt)) { + if (tryReflectionCopilotCall(project, prompt, null, null)) { return; // Success via reflection } @@ -331,6 +354,92 @@ public void openCopilotChatWithPrompt(@Nonnull Project project, @Nonnull String } } + /** + * Same as {@link #openCopilotChatWithPrompt(Project, String)} but pre-selects a Copilot custom + * chat-mode (agent) by name (e.g. {@code "modernize-java-security"}). The URI is resolved on a + * pooled background thread (with a short bounded retry to absorb Copilot's lazy {@code + * .github/agents/} indexing on first project open) to keep the EDT responsive. When the agent + * is not registered (older Copilot, appmod plugin not installed, etc.) we silently fall back + * to plain Agent Mode. + */ + public void openCopilotChatWithPrompt(@Nonnull Project project, @Nonnull String prompt, + @Nullable String customAgentName) { + if (customAgentName == null || customAgentName.isBlank()) { + openCopilotChatWithPrompt(project, prompt); + return; + } + // Capture this once so users without the appmod plugin don't pay the retry cost — we'll just + // prompt to install on the EDT below. + final boolean appmodInstalled = isAppModPluginInstalled(); + // De-dup in-flight resolution: while one pooled thread is already resolving this agent, drop + // rapid repeat clicks instead of each spawning its own ~20 s sleeping thread. + final String cacheKey = project.getLocationHash() + "|" + customAgentName; + if (appmodInstalled && RESOLVED_AGENT_URIS.get(cacheKey) == null && !IN_FLIGHT_AGENT_RESOLUTIONS.add(cacheKey)) { + return; + } + ApplicationManager.getApplication().executeOnPooledThread(() -> { + Object uri = appmodInstalled ? RESOLVED_AGENT_URIS.get(cacheKey) : null; + if (appmodInstalled && uri == null) { + final IdeaPluginDescriptor copilot = PluginManagerCore.getPlugin(PluginId.getId(COPILOT_PLUGIN_ID)); + if (copilot != null && copilot.isEnabled()) { + final ClassLoader cl = copilot.getPluginClassLoader(); + if (cl != null) { + // Copilot populates its chat-mode registry lazily (otherwise only after the chat + // panel is first opened). Without this active trigger the very first click finds an + // empty registry, the agent URI isn't honored, and the chat opens in default Agent + // Mode — so kick a refresh, then poll until the agent shows up. + triggerChatModesRefresh(project, cl); + for (int i = 0; i < AGENT_RESOLVE_RETRY_ATTEMPTS && !project.isDisposed(); i++) { + uri = resolveCustomAgentUri(project, cl, customAgentName); + if (uri != null) { + break; + } + try { + Thread.sleep(AGENT_RESOLVE_RETRY_DELAY_MS); + } catch (InterruptedException ie) { + Thread.currentThread().interrupt(); + break; + } + } + if (uri == null && !project.isDisposed()) { + log.info("openCopilotChatWithPrompt: agent '{}' not in ChatModeService after {} attempts ({} ms); will try file-based fallback.", + customAgentName, AGENT_RESOLVE_RETRY_ATTEMPTS, AGENT_RESOLVE_RETRY_ATTEMPTS * AGENT_RESOLVE_RETRY_DELAY_MS); + } + } + } + // Fallback: ChatModeService is populated lazily by Copilot only after the first + // chat-panel open (~17 s after triggering query()), so the first click after project + // open will miss the agent via that path. Bypass it by pointing withAgentMode(...) + // directly at the .agent.md file on disk; Copilot loads the definition on demand. + if (uri == null && !project.isDisposed()) { + uri = tryConstructFileBasedAgentUri(project, customAgentName); + if (uri != null) { + log.info("openCopilotChatWithPrompt: ChatModeService didn't expose '{}'; using file-based fallback uri={}", customAgentName, uri); + } + } + if (uri != null) { + // Cache so subsequent clicks open instantly without re-running the retry loop. + RESOLVED_AGENT_URIS.put(cacheKey, uri); + } + } + if (appmodInstalled) { + IN_FLIGHT_AGENT_RESOLUTIONS.remove(cacheKey); + } + final Object preResolvedAgentUri = uri; + AzureTaskManager.getInstance().runLater(() -> { + if (!appmodInstalled) { + AppModPluginInstaller.showInstallConfirmation(project, true, () -> AppModPluginInstaller.installPlugin(project, true)); + return; + } + if (tryReflectionCopilotCall(project, prompt, customAgentName, preResolvedAgentUri)) { + return; + } + log.info("Failed to open Copilot chat via both direct and reflection methods."); + showGenericUpgradeGuidance(project, prompt); + }); + }); + } + /** * Tries to call CopilotChatService directly (works when compile-time and runtime versions match). * @return true if successful, false if an error occurred @@ -358,9 +467,14 @@ public void openCopilotChatWithPrompt(@Nonnull Project project, @Nonnull String /** * Tries to call CopilotChatService via reflection for cross-version compatibility. + * @param customAgentName optional name of the Copilot custom chat-mode to select; null for default Agent Mode + * @param preResolvedAgentUri optional URI already resolved off-EDT for {@code customAgentName}; + * when non-null, the lookup against ChatModeService is skipped * @return true if successful, false if an error occurred */ - private boolean tryReflectionCopilotCall(@Nonnull Project project, @Nonnull String prompt) { + private boolean tryReflectionCopilotCall(@Nonnull Project project, @Nonnull String prompt, + @Nullable String customAgentName, + @Nullable Object preResolvedAgentUri) { try { // Get the Copilot plugin's classloader to load its classes final IdeaPluginDescriptor copilotPlugin = PluginManagerCore.getPlugin(PluginId.getId(COPILOT_PLUGIN_ID)); @@ -388,7 +502,6 @@ private boolean tryReflectionCopilotCall(@Nonnull Project project, @Nonnull Stri Function1 queryBuilder = builder -> { try { builder.getClass().getMethod("withInput", String.class).invoke(builder, prompt); - builder.getClass().getMethod("withAgentMode").invoke(builder); builder.getClass().getMethod("withNewSession").invoke(builder); withModelCompatibility(builder, DEFAULT_MODEL_NAME); Method withSessionIdReceiverMethod = findMethodByName(builder.getClass(), "withSessionIdReceiver"); @@ -396,6 +509,7 @@ private boolean tryReflectionCopilotCall(@Nonnull Project project, @Nonnull Stri Function1 sessionIdReceiver = sessionId -> Unit.INSTANCE; withSessionIdReceiverMethod.invoke(builder, sessionIdReceiver); } + applyAgentMode(builder, project, copilotClassLoader, customAgentName, preResolvedAgentUri); } catch (Exception ex) { // Error configuring query builder via reflection log.error("Error configuring Copilot query via reflection: " + ex.getMessage()); @@ -413,6 +527,258 @@ private boolean tryReflectionCopilotCall(@Nonnull Project project, @Nonnull Stri return false; } + /** + * Switches the builder into Agent Mode, optionally selecting a custom agent by URI. Mirrors: + *
{@code
+     * val uri = project.service().chatModes.value.firstOrNull { it.name == name }?.uri
+     * if (uri != null) withAgentMode(uri) else withAgentMode()
+     * }
+ * Falls back to no-arg {@code withAgentMode()} on any failure so the chat still opens. + */ + private static void applyAgentMode(@Nonnull Object builder, @Nonnull Project project, + @Nonnull ClassLoader copilotClassLoader, + @Nullable String customAgentName, + @Nullable Object preResolvedAgentUri) { + Object agentUri = preResolvedAgentUri; + if (agentUri == null && customAgentName != null && !customAgentName.isBlank()) { + // Caller didn't pre-resolve (or the off-EDT lookup returned null); try once here. + agentUri = resolveCustomAgentUri(project, copilotClassLoader, customAgentName); + } + try { + if (agentUri != null) { + final Method withAgentMode = findAccessibleMethod(builder.getClass(), "withAgentMode", 0); + if (withAgentMode != null) { + withAgentMode.invoke(builder); + } + final Method withAgentModeUri = findAccessibleMethod(builder.getClass(), "withAgentMode", 1); + if (withAgentModeUri != null) { + // Copilot may declare withAgentMode as taking URI or String depending on version; + // coerce our agent URI to whichever the installed plugin expects. + final Object coerced = coerceToParameterType(agentUri, withAgentModeUri.getParameterTypes()[0]); + if (coerced != null) { + withAgentModeUri.invoke(builder, coerced); + log.info("applyAgentMode: selected Copilot custom agent '{}' via withAgentMode(uri) — uri={}", customAgentName, agentUri); + return; + } + log.warn("Resolved Copilot agent '{}' uri={} but cannot coerce to withAgentMode parameter type {}; using default Agent Mode.", + customAgentName, agentUri, withAgentModeUri.getParameterTypes()[0].getName()); + } else { + log.warn("Resolved Copilot agent '{}' but withAgentMode(uri) is not exposed by this Copilot version; using default Agent Mode.", + customAgentName); + } + } else { + // agentUri == null: fallback to default Agent Mode + if (customAgentName != null && !customAgentName.isBlank()) { + log.info("applyAgentMode: Copilot custom agent '{}' was not resolvable (not in chatModes and no on-disk file found); falling back to default Agent Mode.", + customAgentName); + } + final Method withAgentMode = findAccessibleMethod(builder.getClass(), "withAgentMode", 0); + if (withAgentMode != null) { + withAgentMode.invoke(builder); + log.info("applyAgentMode: activated default Agent Mode"); + } + } + } catch (Exception ex) { + log.warn("Failed to apply Agent Mode via reflection: " + ex.getMessage(), ex); + } + } + + /** + * Probes the well-known on-disk locations of {@code .agent.md} and returns a {@link java.net.URI} + * if found. Used as a fallback when {@link #resolveCustomAgentUri} can't see the agent yet because + * Copilot's {@code ChatModeService} populates the list lazily on first chat-panel open. + * + *

Search order: + *

    + *
  1. Project-scope: {@code /.github/agents/.agent.md}
  2. + *
  3. Plugin-scope: {@code /mcp-server/dist/entrypoints/agents/.agent.md}
  4. + *
+ */ + @Nullable + private static java.net.URI tryConstructFileBasedAgentUri(@Nonnull Project project, @Nonnull String customAgentName) { + try { + final String basePath = project.getBasePath(); + if (basePath != null) { + final java.nio.file.Path projectFile = java.nio.file.Paths.get( + basePath, ".github", "agents", customAgentName + ".agent.md"); + if (java.nio.file.Files.isRegularFile(projectFile)) { + return toCopilotUri(projectFile); + } + } + final IdeaPluginDescriptor appmod = PluginManagerCore.getPlugin(PluginId.getId(APPMOD_PLUGIN_ID)); + if (appmod != null && appmod.getPluginPath() != null) { + final java.nio.file.Path pluginFile = appmod.getPluginPath() + .resolve(java.nio.file.Paths.get("mcp-server", "dist", "entrypoints", "agents", + customAgentName + ".agent.md")); + if (java.nio.file.Files.isRegularFile(pluginFile)) { + return toCopilotUri(pluginFile); + } + } + } catch (Exception ex) { + log.warn("tryConstructFileBasedAgentUri failed for '" + customAgentName + "': " + ex.getMessage()); + } + return null; + } + + /** + * Returns a {@link java.net.URI} for {@code path} in the exact form Copilot uses for its chat-mode + * registry (VS Code convention: lowercase drive letter, percent-encoded colon on Windows). Copilot + * does string-equality matching against this registry; a Java-standard {@code file:///C:/...} URI + * is silently ignored even when it points to the same file. Non-Windows paths are returned as-is. + * + *

Limitation: only local drive-letter paths ({@code C:\...}) are rewritten. A Windows UNC path + * ({@code \\server\share\...}) maps to {@code file://server/share/...} (no drive letter), doesn't + * match the rewrite shape, and is returned as the Java-standard URI. If Copilot stored that agent + * under a different UNC spelling, the string-equality match would miss and we'd fall back to plain + * Agent Mode. This only affects projects/plugins hosted on a network share and is left unhandled + * because Copilot's canonical UNC form isn't verifiable here; the file-based fallback simply no-ops. + */ + @Nonnull + private static java.net.URI toCopilotUri(@Nonnull java.nio.file.Path path) throws java.net.URISyntaxException { + final java.net.URI standard = path.toUri(); + final String s = standard.toString(); + // Match file:///:/... and rewrite to file:///%3A/... + if (s.length() >= 11 && s.startsWith("file:///") && s.charAt(9) == ':' && Character.isLetter(s.charAt(8))) { + return new java.net.URI("file:///" + Character.toLowerCase(s.charAt(8)) + "%3A" + s.substring(10)); + } + // Non-Windows paths and Windows UNC paths (file://server/share/...) fall through unchanged; see Javadoc. + return standard; + } + + /** + * Best-effort coercion of {@code value} into an instance of {@code paramType}. Returns {@code null} + * when no safe conversion is possible. Handles the cases that actually occur in practice for + * Copilot's {@code withAgentMode(...)}: {@link java.net.URI} ↔ {@link String}. + */ + @Nullable + private static Object coerceToParameterType(@Nullable Object value, @Nonnull Class paramType) { + if (value == null) { + return null; + } + if (paramType.isInstance(value)) { + return value; + } + if (paramType == String.class) { + return value.toString(); + } + if (paramType == java.net.URI.class && value instanceof String) { + try { + return new java.net.URI((String) value); + } catch (Exception ignored) { + return null; + } + } + return null; + } + + + /** + * Reflectively calls {@code ChatModeService.refreshChatModes()} to make Copilot (re)scan custom + * agents from {@code .github/agents/} and plugin-provided locations. Copilot populates its chat-mode + * registry lazily — otherwise only after the chat panel is first opened — so without this trigger the + * very first fix-action click finds an empty registry, the resolved agent URI isn't honored, and the + * chat falls back to default Agent Mode. The refresh is asynchronous; callers poll {@code getChatModes} + * afterward. Best-effort: older Copilot builds may not expose the method, in which case this no-ops. + */ + public static void triggerChatModesRefresh(@Nonnull Project project, @Nonnull ClassLoader copilotClassLoader) { + try { + final Class chatModeServiceClass = copilotClassLoader.loadClass(COPILOT_CHAT_MODE_SERVICE_CLASS); + final Object chatModeService = project.getService(chatModeServiceClass); + if (chatModeService == null) { + return; + } + final Method refresh = findAccessibleMethod(chatModeService.getClass(), "refreshChatModes", 0); + if (refresh != null) { + refresh.invoke(chatModeService); + } else { + log.info("triggerChatModesRefresh: refreshChatModes() not exposed by this Copilot version."); + } + } catch (ClassNotFoundException ex) { + // Older Copilot without ChatModeService; nothing to refresh. + log.info("Older Copilot without ChatModeService"); + } catch (Exception ex) { + log.info("triggerChatModesRefresh failed: " + ex.getMessage()); + } + } + + /** + * Reflectively resolves {@code chatModes.value.firstOrNull { it.name == name }?.uri} on + * Copilot's {@code ChatModeService}. Returns {@code null} when the service is missing + * (older Copilot), the agent is not yet registered, or any reflection step fails. + */ + @Nullable + private static Object resolveCustomAgentUri(@Nonnull Project project, + @Nonnull ClassLoader copilotClassLoader, + @Nonnull String customAgentName) { + try { + final Class chatModeServiceClass = copilotClassLoader.loadClass(COPILOT_CHAT_MODE_SERVICE_CLASS); + final Object chatModeService = project.getService(chatModeServiceClass); + if (chatModeService == null) { + return null; + } + final Method getChatModes = findAccessibleMethod(chatModeService.getClass(), "getChatModes", 0); + if (getChatModes == null) { + return null; + } + final Object flow = getChatModes.invoke(chatModeService); + if (flow == null) { + return null; + } + // StateFlow#getValue() may live on a package-private subclass (e.g. DerivedStateFlow); + // findAccessibleMethod walks up to the public StateFlow interface. + final Method getValue = findAccessibleMethod(flow.getClass(), "getValue", 0); + final Object modes = getValue != null ? getValue.invoke(flow) : flow; + if (!(modes instanceof Iterable)) { + return null; + } + for (Object mode : (Iterable) modes) { + if (mode == null) continue; + final Method getName = findAccessibleMethod(mode.getClass(), "getName", 0); + if (getName == null) continue; + if (customAgentName.equals(String.valueOf(getName.invoke(mode)))) { + final Method getUri = findAccessibleMethod(mode.getClass(), "getUri", 0); + return getUri == null ? null : getUri.invoke(mode); + } + } + } catch (ClassNotFoundException ex) { + // Older Copilot without ChatModeService; caller will fall back to plain Agent Mode. + } catch (Exception ex) { + log.warn("Failed to resolve Copilot custom agent '" + customAgentName + "': " + ex.getMessage()); + } + return null; + } + + /** + * Returns a publicly-invokable {@link Method} matching {@code name} and exact {@code parameterCount}. + * Walks the runtime class, its superclasses and all interfaces (BFS) and returns the first match + * whose declaring class is public — required because some Copilot return types are + * package-private (e.g. Kotlin's {@code DerivedStateFlow}) and {@link Method#invoke} on a method + * whose declaring class is non-public throws {@link IllegalAccessException}. + */ + @Nullable + public static Method findAccessibleMethod(@Nonnull Class clazz, @Nonnull String name, int parameterCount) { + final Deque> queue = new ArrayDeque<>(); + final Set> seen = new HashSet<>(); + queue.add(clazz); + while (!queue.isEmpty()) { + final Class c = queue.poll(); + if (c == null || !seen.add(c)) continue; + if (Modifier.isPublic(c.getModifiers())) { + for (Method m : c.getDeclaredMethods()) { + if (!m.isSynthetic() + && name.equals(m.getName()) + && m.getParameterCount() == parameterCount + && Modifier.isPublic(m.getModifiers())) { + return m; + } + } + } + if (c.getSuperclass() != null) queue.add(c.getSuperclass()); + for (Class iface : c.getInterfaces()) queue.add(iface); + } + return null; + } + /** * Shows generic guidance for upgrading when Copilot is not available. * @param project The project context diff --git a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/utils/Constants.java b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/utils/Constants.java index 194d0e38e4..e8a6c0bc8a 100644 --- a/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/utils/Constants.java +++ b/PluginsAndFeatures/azure-toolkit-for-intellij/azure-intellij-plugin-appmod/src/main/java/com/microsoft/azure/toolkit/intellij/appmod/javaupgrade/utils/Constants.java @@ -4,7 +4,7 @@ public class Constants { public static final String UPGRADE_JAVA_AND_FRAMEWORK_PROMPT = "Upgrade java runtime and java framework dependencies of this project to the latest LTS version using java upgrade tools by invoking #appmod-generate-upgrade-plan"; public static final String UPGRADE_JAVA_VERSION_PROMPT = "Upgrade Java runtime from version %s to the latest LTS version using java upgrade tools by invoking #appmod-generate-upgrade-plan"; public static final String UPGRADE_JAVA_FRAMEWORK_PROMPT = "Upgrade %s from version %s to the latest LTS version using java upgrade tools by invoking #appmod-generate-upgrade-plan"; - public static final String SCAN_AND_RESOLVE_CVES_PROMPT = "run CVE scan for this project using java upgrade tools by invoking #validate_cves_for_java"; + public static final String SCAN_AND_RESOLVE_CVES_PROMPT = "run CVE scan for this project using java upgrade tools by invoking #appmod-validate-cves-for-java"; public static final String UPGRADE_JDK_WITH_COPILOT_DISPLAY_NAME = "Upgrade JDK with Copilot"; public static final String UPGRADE_SPRING_BOOT_WITH_COPILOT_DISPLAY_NAME = "Upgrade Spring Boot with Copilot"; public static final String UPGRADE_SPRING_FRAMEWORK_WITH_COPILOT_DISPLAY_NAME = "Upgrade Spring Framework with Copilot"; @@ -13,4 +13,6 @@ public class Constants { public static final String FIX_VULNERABLE_DEPENDENCY_WITH_COPILOT_PROMPT = "Fix the vulnerable dependency %s by using #appmod-validate-cves-for-java"; public static final String FIX_VULNERABLE_DEPENDENCY_WITH_COPILOT_DISPLAY_NAME = "Fix the vulnerable dependency with Copilot"; public static final String ISSUE_DISPLAY_NAME = "Your project uses %s %s. Consider upgrading to %s to the latest LTS version for better performance and support"; + public static final String APPMOD_CVE_AGENT_NAME = "modernize-java-security"; + public static final String APPMOD_UPGRADE_AGENT_NAME = "modernize-java-upgrade"; }