diff --git a/opencode-bridge.ts b/opencode-bridge.ts index 99c0bdc9..4f5a2c6f 100644 --- a/opencode-bridge.ts +++ b/opencode-bridge.ts @@ -502,8 +502,26 @@ export class OpenCodeConnection { async listCommands() { this._requireClient(); - const res = await this._client.command.list(); - return res.data ?? []; + const [commandsRes, skillsRes] = await Promise.allSettled([ + this._client.command.list(), + this._client.app.skills(), + ]); + const commands = commandsRes.status === "fulfilled" ? (commandsRes.value.data ?? []) : []; + const skills = skillsRes.status === "fulfilled" ? (skillsRes.value.data ?? []) : []; + const seen = new Set(commands.map((command) => command.name)); + + for (const skill of skills) { + if (!skill?.name || seen.has(skill.name)) continue; + seen.add(skill.name); + commands.push({ + name: skill.name, + description: skill.description, + source: "skill", + template: `/${skill.name}`, + }); + } + + return commands; } async sendCommand(sessionId, command, args, model, agent, variant) { @@ -1056,6 +1074,16 @@ export function setupOpenCodeBridge(ipcMain, _getWindows) { } } + async function refreshLocalOpenCodeAfterSkillsChange() { + for (const state of windowStates.values()) { + for (const conn of state.projectRegistry.values()) conn.teardown(); + state.projectRegistry.clear(); + } + const stopped = await stopLocalOpenCodeServer(); + if (!stopped.success) return stopped; + return await startLocalOpenCodeServer(); + } + function makeProjectKey(windowState, workspaceId, directory) { return windowState.projectRegistry.createProjectKey(workspaceId, directory); } @@ -2149,7 +2177,9 @@ export function setupOpenCodeBridge(ipcMain, _getWindows) { success: false, error: "npx not found. Install Node.js first.", }; - return await spawnSkillsInstall(event, cli, source, directory, !!globalScope); + const result = await spawnSkillsInstall(event, cli, source, directory, !!globalScope); + if (result.success) await refreshLocalOpenCodeAfterSkillsChange(); + return result; } catch (err) { return { success: false, error: err.message }; } @@ -2189,7 +2219,9 @@ export function setupOpenCodeBridge(ipcMain, _getWindows) { resolve(null); }); }); - return { success: code === 0, exitCode: code }; + const result = { success: code === 0, exitCode: code }; + if (result.success) await refreshLocalOpenCodeAfterSkillsChange(); + return result; } catch (err) { return { success: false, error: err.message }; } @@ -2231,7 +2263,9 @@ export function setupOpenCodeBridge(ipcMain, _getWindows) { resolve(null); }); }); - return { success: code === 0, exitCode: code }; + const result = { success: code === 0, exitCode: code }; + if (result.success) await refreshLocalOpenCodeAfterSkillsChange(); + return result; } catch (err) { return { success: false, error: err.message }; } diff --git a/src/components/DiscoverPlugins.tsx b/src/components/DiscoverPlugins.tsx index c011a773..a01a651f 100644 --- a/src/components/DiscoverPlugins.tsx +++ b/src/components/DiscoverPlugins.tsx @@ -22,7 +22,7 @@ import { } from "lucide-react"; import { useTranslation } from "react-i18next"; import { usePluginsPlatform } from "@/hooks/use-plugins-platform"; -import { useConnectionState } from "@/hooks/use-agent-state"; +import { useActions, useConnectionState } from "@/hooks/use-agent-state"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Input } from "@/components/ui/input"; @@ -315,6 +315,7 @@ export function DiscoverPlugins() { const pluginsApi = usePluginsPlatform(); const catalogApi = pluginsApi?.marketplace; const { activeDirectory } = useConnectionState(); + const { refreshProviders } = useActions(); const shell = useDesktopShell(); const [query, setQuery] = useState(""); @@ -457,6 +458,7 @@ export function DiscoverPlugins() { try { await action(); await fetchInstalled(); + await refreshProviders(); } catch {} setBusyKeys((prev) => { const next = new Set(prev); @@ -464,7 +466,7 @@ export function DiscoverPlugins() { return next; }); }, - [fetchInstalled], + [fetchInstalled, refreshProviders], ); // Install diff --git a/src/components/settings/PluginsSettings.tsx b/src/components/settings/PluginsSettings.tsx index 51882547..43e10331 100644 --- a/src/components/settings/PluginsSettings.tsx +++ b/src/components/settings/PluginsSettings.tsx @@ -5,7 +5,7 @@ import { DiscoverPlugins } from "@/components/DiscoverPlugins"; import { Badge } from "@/components/ui/badge"; import { Button } from "@/components/ui/button"; import { Spinner } from "@/components/ui/spinner"; -import { useConnectionState } from "@/hooks/use-agent-state"; +import { useActions, useConnectionState } from "@/hooks/use-agent-state"; import { usePluginsPlatform } from "@/hooks/use-plugins-platform"; import type { InstalledPluginInfo } from "@/types/electron"; @@ -122,6 +122,7 @@ function InstalledPluginsView() { const { t } = useTranslation(); const skillsApi = usePluginsPlatform(); const { activeDirectory } = useConnectionState(); + const { refreshProviders } = useActions(); const scopedDirectory = activeDirectory ?? undefined; const [installedPlugins, setInstalledPlugins] = useState([]); @@ -162,9 +163,10 @@ function InstalledPluginsView() { try { await skillsApi.remove(plugin.name, scopedDirectory, plugin.scope === "global"); await refresh(); + await refreshProviders(); } catch {} }, - [skillsApi, scopedDirectory, refresh], + [skillsApi, scopedDirectory, refresh, refreshProviders], ); const handleUpdateOne = useCallback( @@ -173,9 +175,10 @@ function InstalledPluginsView() { try { await skillsApi.update(plugin.name, scopedDirectory, plugin.scope === "global"); await refresh(); + await refreshProviders(); } catch {} }, - [skillsApi, scopedDirectory, refresh], + [skillsApi, scopedDirectory, refresh, refreshProviders], ); const handleGroupAction = useCallback( @@ -190,9 +193,10 @@ function InstalledPluginsView() { } } await refresh(); + await refreshProviders(); } catch {} }, - [skillsApi, scopedDirectory, refresh], + [skillsApi, scopedDirectory, refresh, refreshProviders], ); const toggleGroup = useCallback((key: string) => {