Skip to content

Commit 0e2b02f

Browse files
committed
feat(vscode): add commands to register the chat plugins marketplace
Two new Command Palette commands let you pull the RobotCode chat plugins marketplace into VS Code without editing settings by hand: - "RobotCode: Add Chat Plugins Marketplace" - "RobotCode: Remove Chat Plugins Marketplace" They add or remove robotcodedev/robotframework-agent-plugins in the user-level `chat.plugins.marketplaces` setting, so GitHub Copilot Chat can discover and install RobotCode plugins (and future ones from that marketplace). This is the marketplace route — separate from the plugin the extension already bundles via `contributes.chatPlugins`. If you install the robotcode plugin from the marketplace, disable the bundled one by turning off `robotcode.ai.enableChatPlugins` to avoid running two copies of the same plugin. Only the applicable command is shown: Add when the marketplace is not registered, Remove when it is. Removing also clears the entry when it was added as a GitHub URL rather than the owner/repo shorthand.
1 parent 4018a4f commit 0e2b02f

4 files changed

Lines changed: 157 additions & 0 deletions

File tree

package.json

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1426,9 +1426,29 @@
14261426
"dark": "./resources/dark/restart-kernel.svg",
14271427
"light": "./resources/light/restart-kernel.svg"
14281428
}
1429+
},
1430+
{
1431+
"title": "Add Chat Plugins Marketplace",
1432+
"category": "RobotCode",
1433+
"command": "robotcode.chatPlugins.addMarketplace"
1434+
},
1435+
{
1436+
"title": "Remove Chat Plugins Marketplace",
1437+
"category": "RobotCode",
1438+
"command": "robotcode.chatPlugins.removeMarketplace"
14291439
}
14301440
],
14311441
"menus": {
1442+
"commandPalette": [
1443+
{
1444+
"command": "robotcode.chatPlugins.addMarketplace",
1445+
"when": "!robotcode.chatPlugins.marketplaceRegistered"
1446+
},
1447+
{
1448+
"command": "robotcode.chatPlugins.removeMarketplace",
1449+
"when": "robotcode.chatPlugins.marketplaceRegistered"
1450+
}
1451+
],
14321452
"explorer/context": [
14331453
{
14341454
"command": "robotcode.createNewFile",
Lines changed: 129 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,129 @@
1+
import * as vscode from "vscode";
2+
import {
3+
CHAT_CONFIG_SECTION,
4+
CHAT_PLUGINS_MARKETPLACE_ID,
5+
CHAT_PLUGINS_MARKETPLACES,
6+
CONFIG_AI_ENABLE_CHAT_PLUGINS,
7+
CONFIG_SECTION,
8+
CONTEXT_CHAT_PLUGINS_MARKETPLACE_REGISTERED,
9+
} from "./config";
10+
11+
// Forms of the marketplace reference we recognize when checking/removing an
12+
// existing entry: the `owner/repo` shorthand plus the common git-URL variants.
13+
const MARKETPLACE_ALIASES = [
14+
CHAT_PLUGINS_MARKETPLACE_ID,
15+
`https://github.com/${CHAT_PLUGINS_MARKETPLACE_ID}`,
16+
`https://github.com/${CHAT_PLUGINS_MARKETPLACE_ID}.git`,
17+
`git@github.com:${CHAT_PLUGINS_MARKETPLACE_ID}`,
18+
`git@github.com:${CHAT_PLUGINS_MARKETPLACE_ID}.git`,
19+
].map((s) => s.toLowerCase());
20+
21+
export class ChatPluginsManager implements vscode.Disposable {
22+
private readonly _disposables: vscode.Disposable;
23+
24+
constructor(
25+
public readonly extensionContext: vscode.ExtensionContext,
26+
public readonly outputChannel: vscode.OutputChannel,
27+
) {
28+
this._disposables = vscode.Disposable.from(
29+
vscode.commands.registerCommand("robotcode.chatPlugins.addMarketplace", () => this.addMarketplace()),
30+
vscode.commands.registerCommand("robotcode.chatPlugins.removeMarketplace", () => this.removeMarketplace()),
31+
vscode.workspace.onDidChangeConfiguration((event) => {
32+
if (event.affectsConfiguration(`${CHAT_CONFIG_SECTION}.${CHAT_PLUGINS_MARKETPLACES}`)) {
33+
ChatPluginsManager.updateContextKey();
34+
}
35+
}),
36+
);
37+
38+
ChatPluginsManager.updateContextKey();
39+
}
40+
41+
private static readMarketplaces(): string[] {
42+
return vscode.workspace.getConfiguration(CHAT_CONFIG_SECTION).get<string[]>(CHAT_PLUGINS_MARKETPLACES, []);
43+
}
44+
45+
private static matchesOurMarketplace(entry: string): boolean {
46+
return MARKETPLACE_ALIASES.includes(entry.trim().toLowerCase());
47+
}
48+
49+
private static isRegistered(list: string[]): boolean {
50+
return list.some((entry) => ChatPluginsManager.matchesOurMarketplace(entry));
51+
}
52+
53+
private static isBundledPluginEnabled(): boolean {
54+
return vscode.workspace.getConfiguration(CONFIG_SECTION).get<boolean>(CONFIG_AI_ENABLE_CHAT_PLUGINS, true);
55+
}
56+
57+
private static updateContextKey(): void {
58+
void vscode.commands.executeCommand(
59+
"setContext",
60+
CONTEXT_CHAT_PLUGINS_MARKETPLACE_REGISTERED,
61+
ChatPluginsManager.isRegistered(ChatPluginsManager.readMarketplaces()),
62+
);
63+
}
64+
65+
private async writeMarketplaces(list: string[]): Promise<boolean> {
66+
try {
67+
await vscode.workspace
68+
.getConfiguration(CHAT_CONFIG_SECTION)
69+
.update(CHAT_PLUGINS_MARKETPLACES, list, vscode.ConfigurationTarget.Global);
70+
ChatPluginsManager.updateContextKey();
71+
return true;
72+
} catch (error) {
73+
this.outputChannel.appendLine(`Failed to update ${CHAT_CONFIG_SECTION}.${CHAT_PLUGINS_MARKETPLACES}: ${error}`);
74+
await vscode.window.showErrorMessage(
75+
`Could not update the chat plugin marketplaces setting. This requires GitHub Copilot Chat (or another agent that provides "${CHAT_CONFIG_SECTION}.${CHAT_PLUGINS_MARKETPLACES}") to be installed.`,
76+
);
77+
return false;
78+
}
79+
}
80+
81+
private async addMarketplace(): Promise<void> {
82+
const list = ChatPluginsManager.readMarketplaces();
83+
if (ChatPluginsManager.isRegistered(list)) {
84+
await vscode.window.showInformationMessage("The RobotCode chat plugins marketplace is already added.");
85+
return;
86+
}
87+
88+
if (!(await this.writeMarketplaces([...list, CHAT_PLUGINS_MARKETPLACE_ID]))) {
89+
return;
90+
}
91+
92+
if (!ChatPluginsManager.isBundledPluginEnabled()) {
93+
await vscode.window.showInformationMessage(
94+
`Added the RobotCode chat plugins marketplace (${CHAT_PLUGINS_MARKETPLACE_ID}).`,
95+
);
96+
return;
97+
}
98+
99+
// The extension already bundles this plugin, so installing it from the
100+
// marketplace would run two copies. Offer to switch the bundled one off.
101+
const disable = "Disable Bundled Plugin";
102+
const choice = await vscode.window.showInformationMessage(
103+
`Added the RobotCode chat plugins marketplace (${CHAT_PLUGINS_MARKETPLACE_ID}). The extension also bundles this plugin — if you install it from the marketplace, disable the bundled copy to avoid running it twice.`,
104+
disable,
105+
);
106+
if (choice === disable) {
107+
await vscode.workspace
108+
.getConfiguration(CONFIG_SECTION)
109+
.update(CONFIG_AI_ENABLE_CHAT_PLUGINS, false, vscode.ConfigurationTarget.Global);
110+
}
111+
}
112+
113+
private async removeMarketplace(): Promise<void> {
114+
const list = ChatPluginsManager.readMarketplaces();
115+
if (!ChatPluginsManager.isRegistered(list)) {
116+
await vscode.window.showInformationMessage("The RobotCode chat plugins marketplace is not added.");
117+
return;
118+
}
119+
120+
const next = list.filter((entry) => !ChatPluginsManager.matchesOurMarketplace(entry));
121+
if (await this.writeMarketplaces(next)) {
122+
await vscode.window.showInformationMessage("Removed the RobotCode chat plugins marketplace.");
123+
}
124+
}
125+
126+
dispose(): void {
127+
this._disposables.dispose();
128+
}
129+
}

vscode-client/extension/config.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,9 @@ export const CONFIG_ANALYSIS_DIAGNOSTICMODE_OPENFILESONLY = "openFilesOnly";
55
export const CONFIG_ANALYSIS_DIAGNOSTICMODE_WORKSPACE = "workspace";
66
export const CONFIG_PROFILES = "profiles";
77
export const CONFIG_AI_ENABLE_LANGUAGE_MODEL_TOOLS = "ai.enableLanguageModelTools";
8+
export const CONFIG_AI_ENABLE_CHAT_PLUGINS = "ai.enableChatPlugins";
9+
10+
export const CHAT_CONFIG_SECTION = "chat";
11+
export const CHAT_PLUGINS_MARKETPLACES = "plugins.marketplaces";
12+
export const CHAT_PLUGINS_MARKETPLACE_ID = "robotcodedev/robotframework-agent-plugins";
13+
export const CONTEXT_CHAT_PLUGINS_MARKETPLACE_REGISTERED = "robotcode.chatPlugins.marketplaceRegistered";

vscode-client/extension/index.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { TestControllerManager } from "./testcontrollermanager";
66
import { KeywordsTreeViewProvider } from "./keywordsTreeViewProvider";
77
import { LanguageToolsManager } from "./languageToolsManager";
88
import { LanguageModelToolsManager } from "./languageModelToolsManager";
9+
import { ChatPluginsManager } from "./chatPluginsManager";
910
import { NotebookManager } from "./notebook";
1011
import path from "path";
1112

@@ -99,6 +100,7 @@ export async function activateAsync(context: vscode.ExtensionContext): Promise<v
99100
testControllerManger,
100101
new LanguageToolsManager(context, languageClientManger, pythonManager, testControllerManger, outputChannel),
101102
new LanguageModelToolsManager(context, languageClientManger, outputChannel),
103+
new ChatPluginsManager(context, outputChannel),
102104
new NotebookManager(context, pythonManager, languageClientManger, outputChannel),
103105
vscode.commands.registerCommand("robotcode.showDocumentation", async (url: string) => {
104106
if (url.indexOf("&theme=%24%7Btheme%7D") > 0) {

0 commit comments

Comments
 (0)