Update acp registry integration
Seto Elkahfi committed
Jul 1, 2026 at 22:46 UTC
d17795550f852e759989420be1b4a84f734ccb07
6 files changed
+171
-16
LICENSE
+1
-1
index 4705936..0b1dfad 100644
--- a/LICENSE
+++ b/LICENSE
@@ -1,6 +1,6 @@
MIT License
-Copyright (c) 2026 siGit Code & Deploy
+Copyright (c) 2026 Splitfire AB (siGit Code & Deploy)
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
README.md
+9
-2
index f8ca191..ff1d0c7 100644
--- a/README.md
+++ b/README.md
@@ -53,8 +53,15 @@ Commands (Command Palette):
| --- | --- |
| `siGit: Open Chat` | Reveal the chat view. |
| `siGit: New Session` | Start a fresh session with the active agent. |
-| `siGit: Select Agent` | Pick an agent from the registry. |
+| `siGit: Select Agent` | Pick a configured agent (or browse the registry) and switch to it. |
| `siGit: Restart Agent` | Restart the active agent process. |
+| `siGit: Browse Agent Registry` | Fetch the ACP registry, install an agent, and optionally use or default to it. |
+| `siGit: Refresh Agent Registry` | Force a fresh fetch of the registry catalog. |
+| `siGit: Remove Agent` | Unregister an installed agent from `sigit.agents`. |
+
+The chat view's title bar also surfaces **New Session** and **Select Agent** inline,
+with the registry commands (Browse / Refresh / Remove) and **Restart Agent** in the
+`⋯` overflow menu.
## Configuration
@@ -145,7 +152,7 @@ tab with **publish** left unchecked. It uploads the `.vsix` as a build artifact.
## License
-[MIT](./LICENSE) © 2026 siGit Code & Deploy
+[MIT](./LICENSE)
## Copyright
package.json
+52
-6
index 6a61dd9..01411eb 100644
--- a/package.json
+++ b/package.json
@@ -44,34 +44,80 @@
{
"command": "sigit.openChat",
"title": "siGit: Open Chat",
- "category": "siGit"
+ "category": "siGit",
+ "icon": "$(comment-discussion)"
},
{
"command": "sigit.newSession",
"title": "siGit: New Session",
- "category": "siGit"
+ "category": "siGit",
+ "icon": "$(add)"
},
{
"command": "sigit.selectAgent",
"title": "siGit: Select Agent",
- "category": "siGit"
+ "category": "siGit",
+ "icon": "$(server-process)"
},
{
"command": "sigit.restartAgent",
"title": "siGit: Restart Agent",
- "category": "siGit"
+ "category": "siGit",
+ "icon": "$(debug-restart)"
},
{
"command": "sigit.browseRegistry",
"title": "siGit: Browse Agent Registry",
- "category": "siGit"
+ "category": "siGit",
+ "icon": "$(cloud-download)"
},
{
"command": "sigit.refreshRegistry",
"title": "siGit: Refresh Agent Registry",
- "category": "siGit"
+ "category": "siGit",
+ "icon": "$(refresh)"
+ },
+ {
+ "command": "sigit.removeAgent",
+ "title": "siGit: Remove Agent",
+ "category": "siGit",
+ "icon": "$(trash)"
}
],
+ "menus": {
+ "view/title": [
+ {
+ "command": "sigit.newSession",
+ "when": "view == sigit.chat",
+ "group": "navigation@1"
+ },
+ {
+ "command": "sigit.selectAgent",
+ "when": "view == sigit.chat",
+ "group": "navigation@2"
+ },
+ {
+ "command": "sigit.browseRegistry",
+ "when": "view == sigit.chat",
+ "group": "1_agents@1"
+ },
+ {
+ "command": "sigit.refreshRegistry",
+ "when": "view == sigit.chat",
+ "group": "1_agents@2"
+ },
+ {
+ "command": "sigit.removeAgent",
+ "when": "view == sigit.chat",
+ "group": "1_agents@3"
+ },
+ {
+ "command": "sigit.restartAgent",
+ "when": "view == sigit.chat",
+ "group": "2_session@1"
+ }
+ ]
+ },
"viewsContainers": {
"activitybar": [
{
src/chatView.ts
+5
index 7065b59..8fead8f 100644
--- a/src/chatView.ts
+++ b/src/chatView.ts
@@ -53,6 +53,11 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
this.view?.show?.(true);
}
+ /** The key of the agent currently backing this view. */
+ get currentAgentKey(): string {
+ return this.activeAgentKey;
+ }
+
/** Start a fresh session, discarding any existing client. */
async newSession(): Promise<void> {
this.disposeClient();
src/extension.ts
+75
-2
index 67a0899..50b8747 100644
--- a/src/extension.ts
+++ b/src/extension.ts
@@ -1,7 +1,17 @@
import * as vscode from "vscode";
+import { listAgents } from "./agents";
import { ChatViewProvider } from "./chatView";
import { RegistryAgent } from "./catalog";
-import { fetchCatalog, installAgent, installedKeys, setDefaultAgent } from "./registry";
+import {
+ fetchCatalog,
+ installAgent,
+ installedKeys,
+ setDefaultAgent,
+ uninstallAgent
+} from "./registry";
+
+/** The always-available on-device agent; never removable. */
+const FALLBACK_AGENT_KEY = "sigit";
export function activate(context: vscode.ExtensionContext): void {
const provider = new ChatViewProvider(context);
@@ -21,7 +31,8 @@ export function activate(context: vscode.ExtensionContext): void {
vscode.commands.registerCommand("sigit.selectAgent", () => provider.selectAgent()),
vscode.commands.registerCommand("sigit.restartAgent", () => provider.restart()),
vscode.commands.registerCommand("sigit.browseRegistry", () => browseRegistry(context, provider)),
- vscode.commands.registerCommand("sigit.refreshRegistry", () => refreshRegistry(context))
+ vscode.commands.registerCommand("sigit.refreshRegistry", () => refreshRegistry(context)),
+ vscode.commands.registerCommand("sigit.removeAgent", () => removeAgent(provider))
);
}
@@ -93,6 +104,22 @@ async function browseRegistry(
`Installed "${agent.name}" — launches via ${agent.distribution}.`
);
}
+ } else {
+ const update = "Update";
+ const choice = await vscode.window.showInformationMessage(
+ `"${agent.name}" is already installed. Update its launch command from the registry?`,
+ update
+ );
+ if (choice === update) {
+ await installAgent(agent, { overwrite: true });
+ void vscode.window.showInformationMessage(
+ `Updated "${agent.name}"${agent.version ? ` to v${agent.version}` : ""}.`
+ );
+ // A running client may hold the stale command; restart if it's the active one.
+ if (provider.currentAgentKey === agent.key) {
+ await provider.useAgent(agent.key);
+ }
+ }
}
await offerToUse(agent, provider);
@@ -115,6 +142,52 @@ async function offerToUse(agent: RegistryAgent, provider: ChatViewProvider): Pro
}
}
+/** Pick an installed agent and remove it from `sigit.agents`. */
+async function removeAgent(provider: ChatViewProvider): Promise<void> {
+ const removable = listAgents().filter((agent) => agent.key !== FALLBACK_AGENT_KEY);
+ if (removable.length === 0) {
+ void vscode.window.showInformationMessage(
+ "No removable agents — only the on-device siGit agent is registered."
+ );
+ return;
+ }
+
+ const picked = await vscode.window.showQuickPick(
+ removable.map((agent) => ({
+ label: agent.name,
+ description: agent.key,
+ detail: `${agent.command} ${agent.args.join(" ")}`.trim(),
+ key: agent.key
+ })),
+ { placeHolder: "Select an agent to remove from your configuration" }
+ );
+ if (!picked) {
+ return;
+ }
+
+ const confirm = "Remove";
+ const choice = await vscode.window.showWarningMessage(
+ `Remove "${picked.label}" from your agent registry? This only unregisters it; no files are deleted.`,
+ { modal: true },
+ confirm
+ );
+ if (choice !== confirm) {
+ return;
+ }
+
+ const wasActive = provider.currentAgentKey === picked.key;
+ const removed = await uninstallAgent(picked.key);
+ if (!removed) {
+ void vscode.window.showWarningMessage(`"${picked.label}" was not in the registry.`);
+ return;
+ }
+ void vscode.window.showInformationMessage(`Removed "${picked.label}".`);
+ // If the removed agent was the one running, fall back to the on-device default.
+ if (wasActive) {
+ await provider.useAgent(FALLBACK_AGENT_KEY);
+ }
+}
+
/** Force a fresh fetch of the registry and report how many agents are available. */
async function refreshRegistry(context: vscode.ExtensionContext): Promise<void> {
try {
src/registry.ts
+29
-5
index 7330931..f75ef2e 100644
--- a/src/registry.ts
+++ b/src/registry.ts
@@ -34,7 +34,7 @@ function config(): vscode.WorkspaceConfiguration {
return vscode.workspace.getConfiguration("sigit");
}
-/** The configured registry URL, or the built-in siGit catalog. */
+/** The configured registry URL, or the default Agent Client Protocol catalog. */
export function registryUrl(): string {
const configured = config().get<string>("registry.url");
return configured && configured.trim() !== "" ? configured.trim() : DEFAULT_REGISTRY_URL;
@@ -89,13 +89,18 @@ export async function fetchCatalog(context: vscode.ExtensionContext): Promise<Fe
}
/**
- * Register a catalog agent into the user's `sigit.agents` configuration. Returns
- * `false` without writing when an agent with the same key is already installed.
+ * Register a catalog agent into the user's `sigit.agents` configuration. By
+ * default this is install-only: it returns `false` without writing when an
+ * agent with the same key is already present. Pass `overwrite: true` to update
+ * an existing entry in place (e.g. to pick up a newer catalog version).
*/
-export async function installAgent(agent: RegistryAgent): Promise<boolean> {
+export async function installAgent(
+ agent: RegistryAgent,
+ options: { overwrite?: boolean } = {}
+): Promise<boolean> {
const cfg = config();
const registry = { ...(cfg.get<Record<string, unknown>>("agents") ?? {}) };
- if (Object.prototype.hasOwnProperty.call(registry, agent.key)) {
+ if (!options.overwrite && Object.prototype.hasOwnProperty.call(registry, agent.key)) {
return false;
}
registry[agent.key] = {
@@ -108,6 +113,25 @@ export async function installAgent(agent: RegistryAgent): Promise<boolean> {
return true;
}
+/**
+ * Remove an agent from the user's `sigit.agents` configuration. Returns `false`
+ * when no entry with that key exists. When the removed agent was the configured
+ * default, the default is cleared so it falls back to the on-device `sigit`.
+ */
+export async function uninstallAgent(key: string): Promise<boolean> {
+ const cfg = config();
+ const registry = { ...(cfg.get<Record<string, unknown>>("agents") ?? {}) };
+ if (!Object.prototype.hasOwnProperty.call(registry, key)) {
+ return false;
+ }
+ delete registry[key];
+ await cfg.update("agents", registry, vscode.ConfigurationTarget.Global);
+ if (cfg.get<string>("agent.default") === key) {
+ await cfg.update("agent.default", undefined, vscode.ConfigurationTarget.Global);
+ }
+ return true;
+}
+
/** Make `key` the default agent (`sigit.agent.default`), user-wide. */
export async function setDefaultAgent(key: string): Promise<void> {
await config().update("agent.default", key, vscode.ConfigurationTarget.Global);