Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/skills/ui-action/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -296,4 +296,4 @@ action is usable from every probe.
### Keep one probe focused

Each JSON script represents one test case. Split unrelated behaviours into
separate probes so a single `FAILED-step…` screenshot tells you what broke.
separate probes so a single `FAILED-step…` screenshot tells you what broke.
12 changes: 12 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,18 @@ MCP support enables integrating external tools and services into Copilot workflo
- **Custom Agents** allow users to create personalized agents with specific instructions and behaviors.
- **Isolated Subagents** can be spawned by the main agent to handle specific tasks or contexts independently.
- **Plan Agent** can generate multi-step plans to accomplish complex tasks, breaking them down into manageable actions.
- **Skills** are reusable, specialized AI assistant templates that enrich chat context in Agent Mode. Skills are defined as `SKILL.md` files and can be scoped to a workspace or shared globally.

- Creating Skills

Place a `SKILL.md` file in any of these directories:

- **Project-scoped:** `.github/skills/<skill-name>/`, `.claude/skills/<skill-name>/`, `.agents/skills/<skill-name>/`
- **User-scoped (global):** `~/.copilot/skills/<skill-name>/`, `~/.claude/skills/<skill-name>/`, `~/.agents/skills/<skill-name>/`

Each `SKILL.md` file can include YAML front matter with metadata (name, description) followed by Markdown content that provides domain knowledge, workflows, or instructions for the AI assistant.

Skills are automatically discovered and available in Agent Mode. You can enable or disable skills in **Window → Preferences → Copilot → Chat → Enable Skills**.

For other available features in Eclipse, see the [Copilot feature matrix](https://docs.github.com/en/copilot/reference/copilot-feature-matrix?tool=eclipse).

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ private Constants() {
public static final String WORKSPACE_CONTEXT_ENABLED = "workspaceContextEnabled";
public static final String SUB_AGENT_ENABLED = "subAgentEnabled";
public static final String AGENT_MAX_REQUESTS = "agentMaxRequests";
public static final String ENABLE_SKILLS = "enableSkills";
public static final String MCP = "mcp";
public static final String MCP_REGISTRY_URL = "mcpRegistryUrl";
public static final String MCP_REGISTRY_VERSION = "v0.1";
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,10 @@ public class CopilotEventConstants {
* Event when a rate limit warning is received from the language server.
*/
public static final String TOPIC_RATE_LIMIT_WARNING = TOPIC_CHAT + "RATE_LIMIT_WARNING";

/**
* Event when custom prompts, skills, agents, or instructions change on the language server. Clients should re-fetch
* conversation templates on receipt.
*/
public static final String TOPIC_CHAT_DID_CHANGE_CUSTOMIZATION_FILES = TOPIC_CHAT + "DID_CHANGE_CUSTOMIZATION_FILES";
}
Original file line number Diff line number Diff line change
Expand Up @@ -136,11 +136,11 @@ public CompletableFuture<Object[]> getConversationContext(ConversationContextPar
public CompletableFuture<Object> invokeClientTool(InvokeClientToolParams params) {
return CompletableFuture.supplyAsync(() -> {
try {
CompletableFuture<LanguageModelToolResult[]> toolFuture =
CopilotCore.getPlugin().getChatEventsManager().invokeAgentTool(params);
CompletableFuture<LanguageModelToolResult[]> toolFuture = CopilotCore.getPlugin().getChatEventsManager()
.invokeAgentTool(params);
if (toolFuture == null) {
CopilotCore.LOGGER.error(
new IllegalStateException("invokeAgentTool returned null for tool: " + params.getName()));
CopilotCore.LOGGER
.error(new IllegalStateException("invokeAgentTool returned null for tool: " + params.getName()));
LanguageModelToolResult errorResult = new LanguageModelToolResult();
errorResult.addContent("Failed to invoke the tool: tool invocation returned null");
errorResult.setStatus(ToolInvocationStatus.error);
Expand Down Expand Up @@ -178,6 +178,8 @@ public CompletableFuture<Object[]> confirmClientTool(InvokeClientToolConfirmatio
});
}

// TODO: Should remove workspace-root folder as the projects are not directly under it in Eclipse, and can cause
// confusion in CLS.
@Override
public CompletableFuture<List<WorkspaceFolder>> workspaceFolders() {
// Ideally, we should return each IProject as a workspace folder, but given that when
Expand Down Expand Up @@ -255,9 +257,30 @@ public void onRateLimitWarning(RateLimitWarningParams params) {
}

/**
* Handles the Dynamic OAuth request for MCP.
* Shows a dialog with multiple input fields and returns the user's input values.
* Returns null if the user cancels the request.
* Notify when custom skills change (global or workspace). Signal-only; clients re-fetch templates.
*/
@JsonNotification("copilot/customSkill/didChange")
public void onDidChangeCustomSkill(Object params) {
notifyCustomizationFilesChanged();
}

/**
* Notify when custom prompts change (global or workspace). Signal-only; clients re-fetch templates.
*/
@JsonNotification("copilot/customPrompt/didChange")
public void onDidChangeCustomPrompt(Object params) {
notifyCustomizationFilesChanged();
}

private void notifyCustomizationFilesChanged() {
if (eventBroker != null) {
eventBroker.post(CopilotEventConstants.TOPIC_CHAT_DID_CHANGE_CUSTOMIZATION_FILES, null);
}
}

/**
* Handles the Dynamic OAuth request for MCP. Shows a dialog with multiple input fields and returns the user's input
* values. Returns null if the user cancels the request.
*/
@JsonRequest("copilot/dynamicOAuth")
public CompletableFuture<Map<String, String>> mcpOauth(McpOauthRequest request) {
Expand Down Expand Up @@ -303,13 +326,11 @@ public void onDidChangePolicy(DidChangePolicyParams params) {
}
if (flags.isSubAgentPolicyEnabled() != params.isSubAgentEnabled()) {
flags.setSubAgentPolicyEnabled(params.isSubAgentEnabled());
eventBroker.post(CopilotEventConstants.TOPIC_DID_CHANGE_SUB_AGENT_POLICY,
params.isSubAgentEnabled());
eventBroker.post(CopilotEventConstants.TOPIC_DID_CHANGE_SUB_AGENT_POLICY, params.isSubAgentEnabled());
}
if (flags.isCustomAgentPolicyEnabled() != params.isCustomAgentEnabled()) {
flags.setCustomAgentPolicyEnabled(params.isCustomAgentEnabled());
eventBroker.post(CopilotEventConstants.TOPIC_DID_CHANGE_CUSTOM_AGENT_POLICY,
params.isCustomAgentEnabled());
eventBroker.post(CopilotEventConstants.TOPIC_DID_CHANGE_CUSTOM_AGENT_POLICY, params.isCustomAgentEnabled());
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationMode;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationModesParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplate;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplatesParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTurnParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotStatusResult;
Expand Down Expand Up @@ -137,9 +138,11 @@ public interface CopilotLanguageServer extends LanguageServer {

/**
* List conversation templates.
*
* @param params includes workspace folders for discovering workspace-specific prompt files and skills
*/
@JsonRequest("conversation/templates")
CompletableFuture<ConversationTemplate[]> listTemplates(NullParams param);
CompletableFuture<ConversationTemplate[]> listTemplates(ConversationTemplatesParams params);

/**
* List conversation modes.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
import org.eclipse.lsp4j.ProgressParams;
import org.eclipse.lsp4j.Range;
import org.eclipse.lsp4j.TextDocumentIdentifier;
import org.eclipse.lsp4j.WorkspaceFolder;
import org.eclipse.lsp4j.jsonrpc.Endpoint;
import org.eclipse.lsp4j.jsonrpc.messages.Either;
import org.eclipse.lsp4j.services.LanguageServer;
Expand All @@ -45,6 +46,7 @@
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationMode;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationModesParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplate;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTemplatesParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.ConversationTurnParams;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotModel;
import com.microsoft.copilot.eclipse.core.lsp.protocol.CopilotStatusResult;
Expand Down Expand Up @@ -369,10 +371,12 @@ public CompletableFuture<ChatTurnResult> addConversationTurn(String workDoneToke

/**
* List the conversation templates.
*
* @param workspaceFolders workspace folders for discovering workspace-specific prompt files and skills
*/
public CompletableFuture<ConversationTemplate[]> listConversationTemplates() {
public CompletableFuture<ConversationTemplate[]> listConversationTemplates(List<WorkspaceFolder> workspaceFolders) {
Function<LanguageServer, CompletableFuture<ConversationTemplate[]>> fn = server -> {
return ((CopilotLanguageServer) server).listTemplates(new NullParams());
Comment thread
ethanyhou marked this conversation as resolved.
return ((CopilotLanguageServer) server).listTemplates(new ConversationTemplatesParams(workspaceFolders));
};
return this.languageServerWrapper.execute(fn);
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,78 +4,14 @@
package com.microsoft.copilot.eclipse.core.lsp.protocol;

import java.util.List;
import java.util.Objects;

import org.apache.commons.lang3.builder.ToStringBuilder;

/**
* Get the templates.
* Represents a conversation template returned by the language server.
*/
public class ConversationTemplate {


private String id;
private String description;
private String shortDescription;
private List<String> scopes;

public String getId() {
return id;
}

public void setId(String id) {
this.id = id;
}

public String getDescription() {
return description;
}

public void setDescription(String description) {
this.description = description;
}

public String getShortDescription() {
return shortDescription;
}

public void setShortDescription(String shortDescription) {
this.shortDescription = shortDescription;
}

public List<String> getScopes() {
return scopes;
}

public void setScopes(List<String> scopes) {
this.scopes = scopes;
}

@Override
public int hashCode() {
return Objects.hash(id, description, shortDescription, scopes);
}

@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ConversationTemplate that = (ConversationTemplate) obj;
return Objects.equals(id, that.id) && Objects.equals(description, that.description)
&& Objects.equals(shortDescription, that.shortDescription) && Objects.equals(scopes, that.scopes);
}

@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
builder.append("id", id);
builder.append("description", description);
builder.append("shortDescription", shortDescription);
builder.append("scopes", scopes);
return builder.toString();
}
public record ConversationTemplate(
String id,
String description,
String shortDescription,
List<String> scopes,
TemplateSource source) {
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

package com.microsoft.copilot.eclipse.core.lsp.protocol;

import java.util.Collections;
import java.util.List;

import org.eclipse.lsp4j.WorkspaceFolder;

/**
* Parameters for the {@code conversation/templates} request.
*
* @param workspaceFolders the workspace folders used to discover workspace-specific prompt files and skills
*/
public record ConversationTemplatesParams(List<WorkspaceFolder> workspaceFolders) {
/** Compact constructor that defaults {@code null} workspace folders to an empty list. */
public ConversationTemplatesParams {
workspaceFolders = workspaceFolders != null ? workspaceFolders : Collections.emptyList();
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ public class CopilotAgentSettings {

@SerializedName("maxToolCallingLoop")
private int agentMaxRequests;
private boolean enableSkills;

public int getAgentMaxRequests() {
return agentMaxRequests;
Expand All @@ -24,9 +25,17 @@ public void setAgentMaxRequests(int agentMaxRequests) {
this.agentMaxRequests = agentMaxRequests;
}

public boolean isEnableSkills() {
return enableSkills;
}

public void setEnableSkills(boolean enableSkills) {
this.enableSkills = enableSkills;
}

@Override
public int hashCode() {
return Objects.hash(agentMaxRequests);
return Objects.hash(agentMaxRequests, enableSkills);
}

@Override
Expand All @@ -38,13 +47,14 @@ public boolean equals(Object obj) {
return false;
}
CopilotAgentSettings other = (CopilotAgentSettings) obj;
return agentMaxRequests == other.agentMaxRequests;
return agentMaxRequests == other.agentMaxRequests && enableSkills == other.enableSkills;
}

@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
builder.append("agentMaxRequests", agentMaxRequests);
builder.append("enableSkills", enableSkills);
return builder.toString();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.

package com.microsoft.copilot.eclipse.core.lsp.protocol;

import com.google.gson.annotations.SerializedName;

/**
* Source of a conversation template.
*/
public enum TemplateSource {
@SerializedName("builtin")
BUILTIN,

@SerializedName("prompt")
PROMPT,

@SerializedName("skill")
SKILL
}
Loading
Loading