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);