"use client"; import { useCallback, useMemo, useRef, useState } from "react"; import { Schema } from "effect"; import { PluginRuntimeResponseSchema, type PluginRuntimeView, } from "@local-studio/agent-runtime/plugin-runtime-contract"; import { ApiErrorResponseSchema } from "@local-studio/agent-runtime/api-contract"; import { Alert, Button, SearchInput, StatusPill, UiModal, UiModalHeader } from "@/ui"; import { Eye, X } from "@/ui/icon-registry"; import { useMountSubscription } from "@/hooks/use-mount-subscription"; import { SettingsButton, SettingsGroup, SettingsRow, SettingsValue, type StatusTone, } from "@/features/settings/settings-ui"; import { GoogleAccountModal } from "./google-account-modal"; import { ChatterboxVoiceModal } from "./chatterbox-voice-modal"; import { speechStatusLabel, speechStatusTone } from "./chatterbox-voice-model"; import { useSpeechStore, type SpeechSnapshot } from "./chatterbox-voice-store"; type PluginStatus = { label: string; tone: StatusTone }; function responseError(body: unknown, fallback: string): string { try { return Schema.decodeUnknownSync(ApiErrorResponseSchema)(body).error; } catch { return fallback; } } async function pluginResponse(response: Response, fallback: string) { const body: unknown = await response.json().catch(() => null); if (!response.ok) throw new Error(responseError(body, fallback)); return Schema.decodeUnknownSync(PluginRuntimeResponseSchema)(body); } function capabilitySummary(plugin: PluginRuntimeView): string { if (plugin.hostCapability?.capability === "speech") { return `local speech · voice cloning · v${plugin.version}`; } return [ plugin.provides.skills ? "skills" : null, plugin.provides.mcpServers || plugin.account ? `${plugin.tools.serverCount} ${plugin.account ? "remote " : ""}MCP ${plugin.tools.serverCount === 1 ? "server" : "servers"}` : null, plugin.provides.apps ? "account app" : null, `v${plugin.version}`, ] .filter((value): value is string => Boolean(value)) .join(" · "); } function pluginStatus(plugin: PluginRuntimeView, speech: SpeechSnapshot): PluginStatus { if (plugin.hostCapability?.capability === "speech") { if (!speech.available && !speech.loading) return { label: "Unavailable", tone: "danger" }; if (speech.status) { return { label: speechStatusLabel(speech.status), tone: speechStatusTone(speech.status), }; } if (speech.loading) return { label: "Checking", tone: "default" }; return { label: speech.error ? "Unavailable" : "Configure", tone: "warning" }; } if (plugin.account && !plugin.account.configured) return { label: "Setup", tone: "warning" }; if (plugin.account && !plugin.account.connected) return { label: "Sign in", tone: "warning" }; if (plugin.tools.state === "enabled") { return { label: `Observe · ${plugin.tools.allowedToolCount} ${plugin.tools.allowedToolCount === 1 ? "tool" : "tools"}`, tone: "good", }; } if (plugin.tools.state === "available") return { label: "Available", tone: "info" }; if (plugin.tools.state === "disabled") return { label: "Off", tone: "default" }; if (plugin.tools.state === "invalid") return { label: "Invalid manifest", tone: "danger" }; if (plugin.tools.state === "configuration_required" || plugin.provides.apps) { return { label: "Adapter needed", tone: "warning" }; } return { label: "Skills", tone: "default" }; } function activationAction(plugin: PluginRuntimeView): "account" | "connect" | "disconnect" | null { if (plugin.hostCapability) return null; if (plugin.account && !plugin.account.connected) return "account"; if (plugin.account) { return plugin.tools.state === "available" || plugin.tools.state === "disabled" ? "connect" : null; } if (plugin.tools.state === "enabled") return "disconnect"; if (plugin.tools.state === "available" || plugin.tools.state === "disabled") return "connect"; return null; } function PluginRowsSkeleton() { return ( <> {[0, 1, 2].map((index) => (
))} ); } type PluginRowAction = ReturnType; function pluginActionLabel(plugin: PluginRuntimeView, action: PluginRowAction): string { if (action === "account") return plugin.account?.configured ? "Sign in" : "Set up"; if (action === "connect") return "Connect"; return "Disconnect"; } function PluginRowActions({ plugin, action, busy, hostActionLabel, onConnect, onDisconnect, onAccount, onHostCapability, }: { plugin: PluginRuntimeView; action: PluginRowAction; busy: boolean; hostActionLabel: string; onConnect: () => void; onDisconnect: () => void; onAccount: () => void; onHostCapability: () => void; }) { const actionLabel = action ? pluginActionLabel(plugin, action) : ""; const handleAction = action === "account" ? onAccount : action === "connect" ? onConnect : onDisconnect; return ( <> {plugin.hostCapability ? ( {hostActionLabel} ) : null} {plugin.account?.connected ? ( Manage ) : null} {action ? ( {busy ? "Working" : actionLabel} ) : null} ); } function PluginRow({ plugin, speech, busy, onConnect, onDisconnect, onAccount, onHostCapability, }: { plugin: PluginRuntimeView; speech: SpeechSnapshot; busy: boolean; onConnect: () => void; onDisconnect: () => void; onAccount: () => void; onHostCapability: () => void; }) { const status = pluginStatus(plugin, speech); const action = activationAction(plugin); const hostActionLabel = speech.status?.install.phase === "ready" ? "Manage" : "Configure"; return ( {capabilitySummary(plugin)}} status={{status.label}} actions={ action || plugin.account?.connected || plugin.hostCapability ? ( ) : undefined } > {plugin.tools.reason ? (
{plugin.tools.reason}
) : null} {plugin.account?.email ? (
{plugin.account.email}
) : null}
); } export function PluginsSection() { const speech = useSpeechStore(); const [plugins, setPlugins] = useState([]); const [loaded, setLoaded] = useState(false); const [error, setError] = useState(""); const [query, setQuery] = useState(""); const [busyId, setBusyId] = useState(null); const [pending, setPending] = useState(null); const [accountPlugin, setAccountPlugin] = useState(null); const [speechPlugin, setSpeechPlugin] = useState(null); const requestGeneration = useRef(0); const loadPlugins = useCallback(() => { const generation = ++requestGeneration.current; return fetch("/api/agent/plugins", { cache: "no-store" }) .then(async (response) => { const payload = await pluginResponse(response, "Plugin discovery failed"); if (generation !== requestGeneration.current) return; setPlugins(payload.plugins); setError(""); }) .catch((loadError: unknown) => { if (generation !== requestGeneration.current) return; setError(loadError instanceof Error ? loadError.message : "Plugin discovery failed"); }) .finally(() => { if (generation === requestGeneration.current) setLoaded(true); }); }, []); useMountSubscription(() => { void loadPlugins(); }, [loadPlugins]); const handleAccountChanged = useCallback(() => { void loadPlugins(); }, [loadPlugins]); const visiblePlugins = useMemo(() => { const normalized = query.trim().toLowerCase(); if (!normalized) return plugins; return plugins.filter((plugin) => `${plugin.displayName} ${plugin.description} ${plugin.category} ${capabilitySummary(plugin)}` .toLowerCase() .includes(normalized), ); }, [plugins, query]); const setEnabled = async (plugin: PluginRuntimeView, enabled: boolean) => { const generation = ++requestGeneration.current; setBusyId(plugin.id); setError(""); try { const response = await fetch(`/api/agent/plugins/${encodeURIComponent(plugin.id)}`, { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ enabled }), }); const payload = await pluginResponse(response, "Plugin activation failed"); if (generation !== requestGeneration.current) return; setPlugins(payload.plugins); setPending(null); } catch (activationError) { if (generation !== requestGeneration.current) return; setError( activationError instanceof Error ? activationError.message : "Plugin activation failed", ); } finally { setBusyId((current) => (current === plugin.id ? null : current)); } }; return ( <>

Codex-compatible bundles discovered locally. Skills are available immediately; executable tools require explicit permission.

{error ? (
{error}
) : null} {loaded ? `${visiblePlugins.length} of ${plugins.length}` : "discovering"} } > {!loaded ? ( ) : visiblePlugins.length ? ( visiblePlugins.map((plugin) => ( setPending(plugin)} onDisconnect={() => void setEnabled(plugin, false)} onAccount={() => setAccountPlugin(plugin)} onHostCapability={() => setSpeechPlugin(plugin)} /> )) ) : (
{plugins.length ? `No plugins match “${query}”.` : "No plugin manifests found."}
)}
!busyId && setPending(null)} maxWidth="max-w-md" > } onClose={() => !busyId && setPending(null)} closeIcon={} />
Observe mode starts this plugin locally and exposes only tools it declares read-only. Desktop actions stay blocked until Local Studio has an action-time approval prompt.

The bundle remains in its installed location. Disconnecting stops exposing its tools to Workbench sessions.

{accountPlugin?.account?.provider === "google" ? ( setAccountPlugin(null)} onChanged={handleAccountChanged} /> ) : null} {speechPlugin?.hostCapability?.capability === "speech" ? ( setSpeechPlugin(null)} /> ) : null} ); }