@sigit / sigit-vscode / commits / d177955

Update acp registry integration

Seto Elkahfi committed Jul 1, 2026 at 22:46 UTC d17795550f852e759989420be1b4a84f734ccb07
6 files changed +171 -16
LICENSE
+1 -1
@@ -1,6 +1,6 @@
1 MIT License
2
3 -Copyright (c) 2026 siGit Code & Deploy
3 +Copyright (c) 2026 Splitfire AB (siGit Code & Deploy)
4
5 Permission is hereby granted, free of charge, to any person obtaining a copy
6 of this software and associated documentation files (the "Software"), to deal
README.md
+9 -2
@@ -53,8 +53,15 @@ Commands (Command Palette):
53 | --- | --- |
54 | `siGit: Open Chat` | Reveal the chat view. |
55 | `siGit: New Session` | Start a fresh session with the active agent. |
56 -| `siGit: Select Agent` | Pick an agent from the registry. |
56 +| `siGit: Select Agent` | Pick a configured agent (or browse the registry) and switch to it. |
57 | `siGit: Restart Agent` | Restart the active agent process. |
58 +| `siGit: Browse Agent Registry` | Fetch the ACP registry, install an agent, and optionally use or default to it. |
59 +| `siGit: Refresh Agent Registry` | Force a fresh fetch of the registry catalog. |
60 +| `siGit: Remove Agent` | Unregister an installed agent from `sigit.agents`. |
61 +
62 +The chat view's title bar also surfaces **New Session** and **Select Agent** inline,
63 +with the registry commands (Browse / Refresh / Remove) and **Restart Agent** in the
64 +`⋯` overflow menu.
65
66 ## Configuration
67
@@ -145,7 +152,7 @@ tab with **publish** left unchecked. It uploads the `.vsix` as a build artifact.
152
153 ## License
154
148 -[MIT](./LICENSE) © 2026 siGit Code & Deploy
155 +[MIT](./LICENSE)
156
157 ## Copyright
158
package.json
+52 -6
@@ -44,34 +44,80 @@
44 {
45 "command": "sigit.openChat",
46 "title": "siGit: Open Chat",
47 - "category": "siGit"
47 + "category": "siGit",
48 + "icon": "$(comment-discussion)"
49 },
50 {
51 "command": "sigit.newSession",
52 "title": "siGit: New Session",
52 - "category": "siGit"
53 + "category": "siGit",
54 + "icon": "$(add)"
55 },
56 {
57 "command": "sigit.selectAgent",
58 "title": "siGit: Select Agent",
57 - "category": "siGit"
59 + "category": "siGit",
60 + "icon": "$(server-process)"
61 },
62 {
63 "command": "sigit.restartAgent",
64 "title": "siGit: Restart Agent",
62 - "category": "siGit"
65 + "category": "siGit",
66 + "icon": "$(debug-restart)"
67 },
68 {
69 "command": "sigit.browseRegistry",
70 "title": "siGit: Browse Agent Registry",
67 - "category": "siGit"
71 + "category": "siGit",
72 + "icon": "$(cloud-download)"
73 },
74 {
75 "command": "sigit.refreshRegistry",
76 "title": "siGit: Refresh Agent Registry",
72 - "category": "siGit"
77 + "category": "siGit",
78 + "icon": "$(refresh)"
79 + },
80 + {
81 + "command": "sigit.removeAgent",
82 + "title": "siGit: Remove Agent",
83 + "category": "siGit",
84 + "icon": "$(trash)"
85 }
86 ],
87 + "menus": {
88 + "view/title": [
89 + {
90 + "command": "sigit.newSession",
91 + "when": "view == sigit.chat",
92 + "group": "navigation@1"
93 + },
94 + {
95 + "command": "sigit.selectAgent",
96 + "when": "view == sigit.chat",
97 + "group": "navigation@2"
98 + },
99 + {
100 + "command": "sigit.browseRegistry",
101 + "when": "view == sigit.chat",
102 + "group": "1_agents@1"
103 + },
104 + {
105 + "command": "sigit.refreshRegistry",
106 + "when": "view == sigit.chat",
107 + "group": "1_agents@2"
108 + },
109 + {
110 + "command": "sigit.removeAgent",
111 + "when": "view == sigit.chat",
112 + "group": "1_agents@3"
113 + },
114 + {
115 + "command": "sigit.restartAgent",
116 + "when": "view == sigit.chat",
117 + "group": "2_session@1"
118 + }
119 + ]
120 + },
121 "viewsContainers": {
122 "activitybar": [
123 {
src/chatView.ts
+5
@@ -53,6 +53,11 @@ export class ChatViewProvider implements vscode.WebviewViewProvider {
53 this.view?.show?.(true);
54 }
55
56 + /** The key of the agent currently backing this view. */
57 + get currentAgentKey(): string {
58 + return this.activeAgentKey;
59 + }
60 +
61 /** Start a fresh session, discarding any existing client. */
62 async newSession(): Promise<void> {
63 this.disposeClient();
src/extension.ts
+75 -2
@@ -1,7 +1,17 @@
1 import * as vscode from "vscode";
2 +import { listAgents } from "./agents";
3 import { ChatViewProvider } from "./chatView";
4 import { RegistryAgent } from "./catalog";
4 -import { fetchCatalog, installAgent, installedKeys, setDefaultAgent } from "./registry";
5 +import {
6 + fetchCatalog,
7 + installAgent,
8 + installedKeys,
9 + setDefaultAgent,
10 + uninstallAgent
11 +} from "./registry";
12 +
13 +/** The always-available on-device agent; never removable. */
14 +const FALLBACK_AGENT_KEY = "sigit";
15
16 export function activate(context: vscode.ExtensionContext): void {
17 const provider = new ChatViewProvider(context);
@@ -21,7 +31,8 @@ export function activate(context: vscode.ExtensionContext): void {
31 vscode.commands.registerCommand("sigit.selectAgent", () => provider.selectAgent()),
32 vscode.commands.registerCommand("sigit.restartAgent", () => provider.restart()),
33 vscode.commands.registerCommand("sigit.browseRegistry", () => browseRegistry(context, provider)),
24 - vscode.commands.registerCommand("sigit.refreshRegistry", () => refreshRegistry(context))
34 + vscode.commands.registerCommand("sigit.refreshRegistry", () => refreshRegistry(context)),
35 + vscode.commands.registerCommand("sigit.removeAgent", () => removeAgent(provider))
36 );
37 }
38
@@ -93,6 +104,22 @@ async function browseRegistry(
104 `Installed "${agent.name}" — launches via ${agent.distribution}.`
105 );
106 }
107 + } else {
108 + const update = "Update";
109 + const choice = await vscode.window.showInformationMessage(
110 + `"${agent.name}" is already installed. Update its launch command from the registry?`,
111 + update
112 + );
113 + if (choice === update) {
114 + await installAgent(agent, { overwrite: true });
115 + void vscode.window.showInformationMessage(
116 + `Updated "${agent.name}"${agent.version ? ` to v${agent.version}` : ""}.`
117 + );
118 + // A running client may hold the stale command; restart if it's the active one.
119 + if (provider.currentAgentKey === agent.key) {
120 + await provider.useAgent(agent.key);
121 + }
122 + }
123 }
124
125 await offerToUse(agent, provider);
@@ -115,6 +142,52 @@ async function offerToUse(agent: RegistryAgent, provider: ChatViewProvider): Pro
142 }
143 }
144
145 +/** Pick an installed agent and remove it from `sigit.agents`. */
146 +async function removeAgent(provider: ChatViewProvider): Promise<void> {
147 + const removable = listAgents().filter((agent) => agent.key !== FALLBACK_AGENT_KEY);
148 + if (removable.length === 0) {
149 + void vscode.window.showInformationMessage(
150 + "No removable agents — only the on-device siGit agent is registered."
151 + );
152 + return;
153 + }
154 +
155 + const picked = await vscode.window.showQuickPick(
156 + removable.map((agent) => ({
157 + label: agent.name,
158 + description: agent.key,
159 + detail: `${agent.command} ${agent.args.join(" ")}`.trim(),
160 + key: agent.key
161 + })),
162 + { placeHolder: "Select an agent to remove from your configuration" }
163 + );
164 + if (!picked) {
165 + return;
166 + }
167 +
168 + const confirm = "Remove";
169 + const choice = await vscode.window.showWarningMessage(
170 + `Remove "${picked.label}" from your agent registry? This only unregisters it; no files are deleted.`,
171 + { modal: true },
172 + confirm
173 + );
174 + if (choice !== confirm) {
175 + return;
176 + }
177 +
178 + const wasActive = provider.currentAgentKey === picked.key;
179 + const removed = await uninstallAgent(picked.key);
180 + if (!removed) {
181 + void vscode.window.showWarningMessage(`"${picked.label}" was not in the registry.`);
182 + return;
183 + }
184 + void vscode.window.showInformationMessage(`Removed "${picked.label}".`);
185 + // If the removed agent was the one running, fall back to the on-device default.
186 + if (wasActive) {
187 + await provider.useAgent(FALLBACK_AGENT_KEY);
188 + }
189 +}
190 +
191 /** Force a fresh fetch of the registry and report how many agents are available. */
192 async function refreshRegistry(context: vscode.ExtensionContext): Promise<void> {
193 try {
src/registry.ts
+29 -5
@@ -34,7 +34,7 @@ function config(): vscode.WorkspaceConfiguration {
34 return vscode.workspace.getConfiguration("sigit");
35 }
36
37 -/** The configured registry URL, or the built-in siGit catalog. */
37 +/** The configured registry URL, or the default Agent Client Protocol catalog. */
38 export function registryUrl(): string {
39 const configured = config().get<string>("registry.url");
40 return configured && configured.trim() !== "" ? configured.trim() : DEFAULT_REGISTRY_URL;
@@ -89,13 +89,18 @@ export async function fetchCatalog(context: vscode.ExtensionContext): Promise<Fe
89 }
90
91 /**
92 - * Register a catalog agent into the user's `sigit.agents` configuration. Returns
93 - * `false` without writing when an agent with the same key is already installed.
92 + * Register a catalog agent into the user's `sigit.agents` configuration. By
93 + * default this is install-only: it returns `false` without writing when an
94 + * agent with the same key is already present. Pass `overwrite: true` to update
95 + * an existing entry in place (e.g. to pick up a newer catalog version).
96 */
95 -export async function installAgent(agent: RegistryAgent): Promise<boolean> {
97 +export async function installAgent(
98 + agent: RegistryAgent,
99 + options: { overwrite?: boolean } = {}
100 +): Promise<boolean> {
101 const cfg = config();
102 const registry = { ...(cfg.get<Record<string, unknown>>("agents") ?? {}) };
98 - if (Object.prototype.hasOwnProperty.call(registry, agent.key)) {
103 + if (!options.overwrite && Object.prototype.hasOwnProperty.call(registry, agent.key)) {
104 return false;
105 }
106 registry[agent.key] = {
@@ -108,6 +113,25 @@ export async function installAgent(agent: RegistryAgent): Promise<boolean> {
113 return true;
114 }
115
116 +/**
117 + * Remove an agent from the user's `sigit.agents` configuration. Returns `false`
118 + * when no entry with that key exists. When the removed agent was the configured
119 + * default, the default is cleared so it falls back to the on-device `sigit`.
120 + */
121 +export async function uninstallAgent(key: string): Promise<boolean> {
122 + const cfg = config();
123 + const registry = { ...(cfg.get<Record<string, unknown>>("agents") ?? {}) };
124 + if (!Object.prototype.hasOwnProperty.call(registry, key)) {
125 + return false;
126 + }
127 + delete registry[key];
128 + await cfg.update("agents", registry, vscode.ConfigurationTarget.Global);
129 + if (cfg.get<string>("agent.default") === key) {
130 + await cfg.update("agent.default", undefined, vscode.ConfigurationTarget.Global);
131 + }
132 + return true;
133 +}
134 +
135 /** Make `key` the default agent (`sigit.agent.default`), user-wide. */
136 export async function setDefaultAgent(key: string): Promise<void> {
137 await config().update("agent.default", key, vscode.ConfigurationTarget.Global);