Skip to content

Commit 7ba48f2

Browse files
ethanyhouCopilot
andcommitted
Address comments.
Co-authored-by: Copilot <copilot@github.com>
1 parent 028b0b3 commit 7ba48f2

4 files changed

Lines changed: 62 additions & 9 deletions

File tree

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/events/CopilotEventConstants.java

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -160,4 +160,10 @@ public class CopilotEventConstants {
160160
* Event when a rate limit warning is received from the language server.
161161
*/
162162
public static final String TOPIC_RATE_LIMIT_WARNING = TOPIC_CHAT + "RATE_LIMIT_WARNING";
163+
164+
/**
165+
* Event when custom prompts, skills, agents, or instructions change on the language server. Clients should re-fetch
166+
* conversation templates on receipt.
167+
*/
168+
public static final String TOPIC_CHAT_DID_CHANGE_CUSTOMIZATION_FILES = TOPIC_CHAT + "DID_CHANGE_CUSTOMIZATION_FILES";
163169
}

com.microsoft.copilot.eclipse.core/src/com/microsoft/copilot/eclipse/core/lsp/CopilotLanguageClient.java

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -254,6 +254,28 @@ public void onRateLimitWarning(RateLimitWarningParams params) {
254254
}
255255
}
256256

257+
/**
258+
* Notify when custom skills change (global or workspace). Signal-only; clients re-fetch templates.
259+
*/
260+
@JsonNotification("copilot/customSkill/didChange")
261+
public void onDidChangeCustomSkill(Object params) {
262+
notifyCustomizationFilesChanged();
263+
}
264+
265+
/**
266+
* Notify when custom prompts change (global or workspace). Signal-only; clients re-fetch templates.
267+
*/
268+
@JsonNotification("copilot/customPrompt/didChange")
269+
public void onDidChangeCustomPrompt(Object params) {
270+
notifyCustomizationFilesChanged();
271+
}
272+
273+
private void notifyCustomizationFilesChanged() {
274+
if (eventBroker != null) {
275+
eventBroker.post(CopilotEventConstants.TOPIC_CHAT_DID_CHANGE_CUSTOMIZATION_FILES, null);
276+
}
277+
}
278+
257279
/**
258280
* Handles the Dynamic OAuth request for MCP.
259281
* Shows a dialog with multiple input fields and returns the user's input values.

com.microsoft.copilot.eclipse.ui.test/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionServiceTest.java

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@
1515
import java.util.concurrent.CompletableFuture;
1616

1717
import org.eclipse.core.runtime.jobs.Job;
18+
import org.eclipse.ui.IWorkbench;
19+
import org.eclipse.ui.PlatformUI;
1820
import org.junit.jupiter.api.AfterAll;
1921
import org.junit.jupiter.api.BeforeAll;
2022
import org.junit.jupiter.api.Test;
@@ -39,6 +41,7 @@ class ChatCompletionServiceTest {
3941

4042
private static ChatCompletionService chatCompletionService;
4143
private static MockedStatic<CopilotUi> copilotUiMock;
44+
private static MockedStatic<PlatformUI> platformUiMock;
4245

4346
@BeforeAll
4447
static void setUp() {
@@ -53,6 +56,12 @@ static void setUp() {
5356
copilotUiMock = Mockito.mockStatic(CopilotUi.class);
5457
copilotUiMock.when(CopilotUi::getPlugin).thenReturn(mockPlugin);
5558

59+
// Mock PlatformUI so the constructor can safely obtain an IEventBroker
60+
IWorkbench mockWorkbench = mock(IWorkbench.class);
61+
when(mockWorkbench.getService(any())).thenReturn(null);
62+
platformUiMock = Mockito.mockStatic(PlatformUI.class);
63+
platformUiMock.when(PlatformUI::getWorkbench).thenReturn(mockWorkbench);
64+
5665
ConversationTemplate template = new ConversationTemplate("test", null, null,
5766
List.of(CopilotScope.CHAT_PANEL), null);
5867
ConversationTemplate[] templates = new ConversationTemplate[] { template };
@@ -76,6 +85,9 @@ static void tearDown() {
7685
if (copilotUiMock != null) {
7786
copilotUiMock.close();
7887
}
88+
if (platformUiMock != null) {
89+
platformUiMock.close();
90+
}
7991
}
8092

8193
@Test

com.microsoft.copilot.eclipse.ui/src/com/microsoft/copilot/eclipse/ui/chat/services/ChatCompletionService.java

Lines changed: 22 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -19,13 +19,17 @@
1919
import org.eclipse.core.runtime.IStatus;
2020
import org.eclipse.core.runtime.Status;
2121
import org.eclipse.core.runtime.jobs.Job;
22+
import org.eclipse.e4.core.services.events.IEventBroker;
2223
import org.eclipse.jface.util.IPropertyChangeListener;
2324
import org.eclipse.lsp4j.WorkspaceFolder;
25+
import org.eclipse.ui.PlatformUI;
26+
import org.osgi.service.event.EventHandler;
2427

2528
import com.microsoft.copilot.eclipse.core.AuthStatusManager;
2629
import com.microsoft.copilot.eclipse.core.Constants;
2730
import com.microsoft.copilot.eclipse.core.CopilotAuthStatusListener;
2831
import com.microsoft.copilot.eclipse.core.CopilotCore;
32+
import com.microsoft.copilot.eclipse.core.events.CopilotEventConstants;
2933
import com.microsoft.copilot.eclipse.core.FeatureFlags;
3034
import com.microsoft.copilot.eclipse.core.lsp.CopilotLanguageServerConnection;
3135
import com.microsoft.copilot.eclipse.core.lsp.protocol.ChatMode;
@@ -43,9 +47,9 @@ public class ChatCompletionService implements CopilotAuthStatusListener {
4347
public static final String AGENT_MARK = "@";
4448
public static final String TEMPLATE_MARK = "/";
4549

46-
private volatile List<ConversationTemplate> templates = new ArrayList<>();
47-
private volatile List<ConversationAgent> agents = new ArrayList<>();
48-
private volatile Set<String> allCommands = new HashSet<>();
50+
private volatile List<ConversationTemplate> templates = List.of();
51+
private volatile List<ConversationAgent> agents = List.of();
52+
private volatile Set<String> allCommands = Set.of();
4953
// Exclude intelliJ sepcific slash commands
5054
private static final Set<String> EXCLUDED_COMMANDS = Set.of("help", "feedback");
5155
public static final String REFRESH_JOB_FAMILY =
@@ -54,6 +58,8 @@ public class ChatCompletionService implements CopilotAuthStatusListener {
5458
private AuthStatusManager authStatusManager;
5559
private IResourceChangeListener skillFileListener;
5660
private IPropertyChangeListener preferenceListener;
61+
private IEventBroker eventBroker;
62+
private EventHandler customPromptsChangedHandler;
5763

5864
private static final String SKILL_FILE_NAME = "SKILL.md";
5965
private static final String PROMPT_FILE_SUFFIX = ".prompt.md";
@@ -73,6 +79,12 @@ public ChatCompletionService(CopilotLanguageServerConnection lsConnection, AuthS
7379
}
7480
};
7581
CopilotUi.getPlugin().getLanguageServerSettingManager().registerPropertyChangeListener(preferenceListener);
82+
this.eventBroker = PlatformUI.getWorkbench().getService(IEventBroker.class);
83+
if (this.eventBroker != null) {
84+
this.customPromptsChangedHandler = event -> fetchAsync();
85+
this.eventBroker.subscribe(CopilotEventConstants.TOPIC_CHAT_DID_CHANGE_CUSTOMIZATION_FILES,
86+
customPromptsChangedHandler);
87+
}
7688
syncCommands(this.authStatusManager.getCopilotStatus());
7789
}
7890

@@ -140,9 +152,10 @@ private void initConversationTemplates() {
140152
}
141153

142154
// Atomically swap the cached data so readers always see a consistent snapshot.
143-
this.templates = newTemplates;
144-
this.agents = newAgents;
145-
this.allCommands = newCommands;
155+
// Publish immutable snapshots so readers cannot accidentally mutate a live collection.
156+
this.templates = List.copyOf(newTemplates);
157+
this.agents = List.copyOf(newAgents);
158+
this.allCommands = Set.copyOf(newCommands);
146159
}
147160

148161
/**
@@ -228,9 +241,9 @@ private void syncCommands(String status) {
228241
fetchAsync();
229242
break;
230243
default:
231-
this.allCommands = new HashSet<>();
232-
this.templates = new ArrayList<>();
233-
this.agents = new ArrayList<>();
244+
this.allCommands = Set.of();
245+
this.templates = List.of();
246+
this.agents = List.of();
234247
break;
235248
}
236249
}

0 commit comments

Comments
 (0)