Skip to content
Open
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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<? super Unit> continuation) {
Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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);
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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())));
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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())));
Expand All @@ -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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

/**
Expand Down Expand Up @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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.
Expand All @@ -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<? super Unit> 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()) {
Expand Down
Loading