@burdenoff/microfe-vibecontrols 2026.602.4 → 2026.602.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/components/agents/OpenInBrowserEditorButton.js +1 -1
- package/dist/components/agents/OpenInBrowserEditorButton.js.map +1 -1
- package/dist/components/agents/PluginUIPanel.js +3 -1
- package/dist/components/agents/PluginUIPanel.js.map +1 -1
- package/dist/components/ai/PluginHarnessPicker.js +50 -46
- package/dist/components/ai/PluginHarnessPicker.js.map +1 -1
- package/dist/components/security/SecurityRunButton.js +60 -0
- package/dist/components/security/SecurityRunButton.js.map +1 -0
- package/dist/components/security/SecurityTabPanel.js +42 -26
- package/dist/components/security/SecurityTabPanel.js.map +1 -1
- package/dist/components/security/hooks.js +28 -11
- package/dist/components/security/hooks.js.map +1 -1
- package/dist/components/security/index.js +1 -0
- package/dist/constants/pluginCatalog.js +18 -3
- package/dist/constants/pluginCatalog.js.map +1 -1
- package/package.json +1 -1
|
@@ -80,7 +80,7 @@ function h({ agentId: h, tunnelUrl: g, profile: _, workspacePath: v, mode: y = "
|
|
|
80
80
|
ttlSeconds: 60
|
|
81
81
|
} })).data?.mintAgentCapability?.token;
|
|
82
82
|
if (!r) throw Error("Failed to mint editor capability");
|
|
83
|
-
let i = `${g.replace(/\/$/, "")}/code-server
|
|
83
|
+
let i = `${g.replace(/\/$/, "")}/code-server/?vt=${encodeURIComponent(r)}`;
|
|
84
84
|
y === "iframe" && b ? b(i) : window.open(i, "_blank"), setTimeout(() => w("idle"), 2e3);
|
|
85
85
|
} catch (e) {
|
|
86
86
|
E(e instanceof Error ? e.message : "Failed to open editor"), w("error");
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"OpenInBrowserEditorButton.js","names":[],"sources":["../../../src/components/agents/OpenInBrowserEditorButton.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { Code, Loader2, AlertCircle, Check } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useAgentCodeServerStatusLazyQuery,\n useAgentCodeServerStartMutation,\n useAgentCodeServerStopMutation,\n useMintAgentCapabilityMutation,\n} from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ntype EditorState = 'idle' | 'checking' | 'installing' | 'starting' | 'ready' | 'error';\n\nexport interface OpenInBrowserEditorButtonProps {\n /**\n * Agent ID — F4 Wave B replaced the direct browser fetch (which relied\n * on `agentApiKey` headers) with svc-mediated `agentCodeServer*`\n * mutations. The agent's tunnel URL is still used for the iframe `src`,\n * but every control-plane call now routes through the gateway.\n */\n agentId: string | null;\n /** Agent tunnel URL (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Canonical CLI profile name on the remote agent. Maps to the\n * `/api/profiles/<profile>/code-server/...` URL prefix.\n */\n profile: string;\n /** Directory to open as workspace root. Defaults to agent home if not set. */\n workspacePath?: string;\n /** How to open — new browser tab or call back with URL for iframe embedding */\n mode?: 'newtab' | 'iframe';\n /** Called with the ready URL when mode is 'iframe' */\n onIframeUrl?: (url: string) => void;\n /** Additional CSS class names */\n className?: string;\n}\n\ninterface CodeServerStatusPayload {\n installed?: boolean;\n installing?: boolean;\n running?: boolean;\n port?: number;\n error?: string;\n}\n\n// Code-server is a single named instance per profile; the proxy resolver\n// requires a `name`. Keep the legacy default — operators who installed\n// the plugin pre-rename still have an instance keyed under \"default\".\nconst CODE_SERVER_INSTANCE = 'default';\n\n// ── Component ──────────────────────────────────────────────────────────\n\nexport function OpenInBrowserEditorButton({\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode = 'newtab',\n onIframeUrl,\n className,\n}: OpenInBrowserEditorButtonProps) {\n const tr = useTr();\n const [state, setState] = useState<EditorState>('idle');\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\n\n const [fetchStatus] = useAgentCodeServerStatusLazyQuery({ fetchPolicy: 'network-only' });\n const [startCodeServer] = useAgentCodeServerStartMutation();\n const [stopCodeServer] = useAgentCodeServerStopMutation();\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n\n const probe = useCallback(\n async (\n candidate: string\n ): Promise<{\n effective: string | null;\n status: CodeServerStatusPayload | null;\n missing: boolean;\n }> => {\n const res = await fetchStatus({\n variables: { agentId: agentId!, profile: candidate, name: CODE_SERVER_INSTANCE },\n });\n const proxy = res.data?.agentCodeServerStatus;\n if (!proxy) {\n return { effective: null, status: null, missing: false };\n }\n if (proxy.status === 404) {\n return { effective: null, status: null, missing: true };\n }\n if (!proxy.ok) {\n throw new Error(proxy.error || `Status check failed: HTTP ${proxy.status}`);\n }\n return {\n effective: candidate,\n status: (proxy.data as CodeServerStatusPayload | null) ?? {},\n missing: false,\n };\n },\n [fetchStatus, agentId]\n );\n\n const handleClick = useCallback(async () => {\n if (!agentId || !tunnelUrl) {\n setErrorMsg('Agent not selected');\n setState('error');\n return;\n }\n\n setErrorMsg(null);\n\n try {\n // Step 1: Probe the active profile, falling back to \"default\" if the\n // current profile reports 404 (code-server isn't a per-profile\n // service — its management API is profile-scoped though).\n setState('checking');\n const candidates = profile === 'default' ? ['default'] : [profile, 'default'];\n let effectiveProfile: string | null = null;\n let status: CodeServerStatusPayload | null = null;\n for (const candidate of candidates) {\n const { effective, status: probed, missing } = await probe(candidate);\n if (missing) continue;\n if (effective) {\n effectiveProfile = effective;\n status = probed;\n break;\n }\n }\n if (!effectiveProfile || !status) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 2: code-server needs to be installed up-front via the\n // Plugins tab. The deprecated direct-browser install fetch isn't\n // proxied; surface a clear message instead of silently hanging.\n if (!status.installed) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 3: Start if not running, or restart with a fresh workspace path.\n if (!status.running) {\n setState('starting');\n const startRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n ...(workspacePath ? { path: workspacePath } : {}),\n },\n });\n const startProxy = startRes.data?.agentCodeServerStart;\n if (!startProxy?.ok) {\n throw new Error(startProxy?.error || `Start failed: HTTP ${startProxy?.status ?? 0}`);\n }\n await new Promise((r) => setTimeout(r, 1000));\n } else if (workspacePath) {\n setState('starting');\n // No dedicated restart proxy yet — stop, then start with the new path.\n await stopCodeServer({\n variables: { agentId, profile: effectiveProfile, name: CODE_SERVER_INSTANCE },\n });\n const restartRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n path: workspacePath,\n },\n });\n const restartProxy = restartRes.data?.agentCodeServerStart;\n if (!restartProxy?.ok) {\n throw new Error(\n restartProxy?.error || `Restart failed: HTTP ${restartProxy?.status ?? 0}`\n );\n }\n await new Promise((r) => setTimeout(r, 1000));\n }\n\n // Step 4: Mint a short-lived IFRAME_PLUGIN capability so the\n // editor URL doesn't embed the long-lived `agentApiKey`. The\n // agent's `/code-server/` bootstrap exchanges the `#vt=` fragment\n // for a scoped HttpOnly cookie before proxying.\n setState('ready');\n const mintRes = await mintAgentCapability({\n variables: {\n agentId,\n profile: effectiveProfile,\n scope: AgentCapabilityScope.IframePlugin,\n resourceId: `code-server:${CODE_SERVER_INSTANCE}`,\n ttlSeconds: 60,\n },\n });\n const token = mintRes.data?.mintAgentCapability?.token;\n if (!token) {\n throw new Error('Failed to mint editor capability');\n }\n const base = tunnelUrl.replace(/\\/$/, '');\n const editorUrl = `${base}/code-server/#vt=${encodeURIComponent(token)}`;\n\n if (mode === 'iframe' && onIframeUrl) {\n onIframeUrl(editorUrl);\n } else {\n window.open(editorUrl, '_blank');\n }\n\n // Reset after a moment\n setTimeout(() => setState('idle'), 2000);\n } catch (err) {\n setErrorMsg(err instanceof Error ? err.message : 'Failed to open editor');\n setState('error');\n }\n }, [\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode,\n onIframeUrl,\n tr,\n probe,\n startCodeServer,\n stopCodeServer,\n mintAgentCapability,\n ]);\n\n const isDisabled =\n !agentId || !tunnelUrl || (state !== 'idle' && state !== 'error' && state !== 'ready');\n\n const label = (() => {\n switch (state) {\n case 'checking':\n return tr('vibecontrols.agents.editor.checking', 'Checking...');\n case 'installing':\n return tr('vibecontrols.agents.editor.installing', 'Installing...');\n case 'starting':\n return tr('vibecontrols.agents.editor.starting', 'Starting...');\n case 'ready':\n return tr('vibecontrols.agents.editor.opened', 'Opened');\n case 'error':\n return tr('vibecontrols.agents.editor.retry', 'Retry');\n default:\n return tr('vibecontrols.agents.editor.openEditor', 'Open Editor');\n }\n })();\n\n const Icon = (() => {\n switch (state) {\n case 'checking':\n case 'installing':\n case 'starting':\n return Loader2;\n case 'ready':\n return Check;\n case 'error':\n return AlertCircle;\n default:\n return Code;\n }\n })();\n\n const isSpinning = state === 'checking' || state === 'installing' || state === 'starting';\n\n return (\n <div className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={isDisabled}\n className={`flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm rounded-md transition-colors disabled:opacity-50 ${\n state === 'error'\n ? 'bg-status-error-bg text-status-error-text hover:opacity-80'\n : state === 'ready'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-sunken/80'\n } ${className || ''}`}\n title={errorMsg || 'Open VS Code in browser via code-server'}\n >\n <Icon className={`size-3.5 sm:w-4 sm:h-4 ${isSpinning ? 'animate-spin' : ''}`} />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n\n {/* Error popover. Anchored to the right edge (the button sits at the\n header's right side) with a fixed width so the message wraps as\n normal prose instead of being squeezed into a narrow column by the\n inline-flex wrapper. */}\n {state === 'error' && errorMsg && (\n <div\n role=\"alert\"\n className=\"absolute top-full right-0 mt-1.5 z-50 w-72 rounded-md border border-status-error-text/30 bg-bg-surface p-3 text-xs leading-relaxed text-status-error-text whitespace-normal break-words shadow-lg\"\n >\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n <span>{errorMsg}</span>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"OpenInBrowserEditorButton.js","names":[],"sources":["../../../src/components/agents/OpenInBrowserEditorButton.tsx"],"sourcesContent":["import { useState, useCallback } from 'react';\nimport { Code, Loader2, AlertCircle, Check } from 'lucide-react';\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n useAgentCodeServerStatusLazyQuery,\n useAgentCodeServerStartMutation,\n useAgentCodeServerStopMutation,\n useMintAgentCapabilityMutation,\n} from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\ntype EditorState = 'idle' | 'checking' | 'installing' | 'starting' | 'ready' | 'error';\n\nexport interface OpenInBrowserEditorButtonProps {\n /**\n * Agent ID — F4 Wave B replaced the direct browser fetch (which relied\n * on `agentApiKey` headers) with svc-mediated `agentCodeServer*`\n * mutations. The agent's tunnel URL is still used for the iframe `src`,\n * but every control-plane call now routes through the gateway.\n */\n agentId: string | null;\n /** Agent tunnel URL (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Canonical CLI profile name on the remote agent. Maps to the\n * `/api/profiles/<profile>/code-server/...` URL prefix.\n */\n profile: string;\n /** Directory to open as workspace root. Defaults to agent home if not set. */\n workspacePath?: string;\n /** How to open — new browser tab or call back with URL for iframe embedding */\n mode?: 'newtab' | 'iframe';\n /** Called with the ready URL when mode is 'iframe' */\n onIframeUrl?: (url: string) => void;\n /** Additional CSS class names */\n className?: string;\n}\n\ninterface CodeServerStatusPayload {\n installed?: boolean;\n installing?: boolean;\n running?: boolean;\n port?: number;\n error?: string;\n}\n\n// Code-server is a single named instance per profile; the proxy resolver\n// requires a `name`. Keep the legacy default — operators who installed\n// the plugin pre-rename still have an instance keyed under \"default\".\nconst CODE_SERVER_INSTANCE = 'default';\n\n// ── Component ──────────────────────────────────────────────────────────\n\nexport function OpenInBrowserEditorButton({\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode = 'newtab',\n onIframeUrl,\n className,\n}: OpenInBrowserEditorButtonProps) {\n const tr = useTr();\n const [state, setState] = useState<EditorState>('idle');\n const [errorMsg, setErrorMsg] = useState<string | null>(null);\n\n const [fetchStatus] = useAgentCodeServerStatusLazyQuery({ fetchPolicy: 'network-only' });\n const [startCodeServer] = useAgentCodeServerStartMutation();\n const [stopCodeServer] = useAgentCodeServerStopMutation();\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n\n const probe = useCallback(\n async (\n candidate: string\n ): Promise<{\n effective: string | null;\n status: CodeServerStatusPayload | null;\n missing: boolean;\n }> => {\n const res = await fetchStatus({\n variables: { agentId: agentId!, profile: candidate, name: CODE_SERVER_INSTANCE },\n });\n const proxy = res.data?.agentCodeServerStatus;\n if (!proxy) {\n return { effective: null, status: null, missing: false };\n }\n if (proxy.status === 404) {\n return { effective: null, status: null, missing: true };\n }\n if (!proxy.ok) {\n throw new Error(proxy.error || `Status check failed: HTTP ${proxy.status}`);\n }\n return {\n effective: candidate,\n status: (proxy.data as CodeServerStatusPayload | null) ?? {},\n missing: false,\n };\n },\n [fetchStatus, agentId]\n );\n\n const handleClick = useCallback(async () => {\n if (!agentId || !tunnelUrl) {\n setErrorMsg('Agent not selected');\n setState('error');\n return;\n }\n\n setErrorMsg(null);\n\n try {\n // Step 1: Probe the active profile, falling back to \"default\" if the\n // current profile reports 404 (code-server isn't a per-profile\n // service — its management API is profile-scoped though).\n setState('checking');\n const candidates = profile === 'default' ? ['default'] : [profile, 'default'];\n let effectiveProfile: string | null = null;\n let status: CodeServerStatusPayload | null = null;\n for (const candidate of candidates) {\n const { effective, status: probed, missing } = await probe(candidate);\n if (missing) continue;\n if (effective) {\n effectiveProfile = effective;\n status = probed;\n break;\n }\n }\n if (!effectiveProfile || !status) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 2: code-server needs to be installed up-front via the\n // Plugins tab. The deprecated direct-browser install fetch isn't\n // proxied; surface a clear message instead of silently hanging.\n if (!status.installed) {\n throw new Error(\n tr(\n 'vibecontrols.agents.editor.notInstalled',\n \"Code-server isn't installed on this agent. Install it from the Plugins tab to enable Open Editor.\"\n )\n );\n }\n\n // Step 3: Start if not running, or restart with a fresh workspace path.\n if (!status.running) {\n setState('starting');\n const startRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n ...(workspacePath ? { path: workspacePath } : {}),\n },\n });\n const startProxy = startRes.data?.agentCodeServerStart;\n if (!startProxy?.ok) {\n throw new Error(startProxy?.error || `Start failed: HTTP ${startProxy?.status ?? 0}`);\n }\n await new Promise((r) => setTimeout(r, 1000));\n } else if (workspacePath) {\n setState('starting');\n // No dedicated restart proxy yet — stop, then start with the new path.\n await stopCodeServer({\n variables: { agentId, profile: effectiveProfile, name: CODE_SERVER_INSTANCE },\n });\n const restartRes = await startCodeServer({\n variables: {\n agentId,\n profile: effectiveProfile,\n name: CODE_SERVER_INSTANCE,\n path: workspacePath,\n },\n });\n const restartProxy = restartRes.data?.agentCodeServerStart;\n if (!restartProxy?.ok) {\n throw new Error(\n restartProxy?.error || `Restart failed: HTTP ${restartProxy?.status ?? 0}`\n );\n }\n await new Promise((r) => setTimeout(r, 1000));\n }\n\n // Step 4: Mint a short-lived IFRAME_PLUGIN capability so the\n // editor URL doesn't embed the long-lived `agentApiKey`. The agent\n // consumes the `?vt=` query on the first `/code-server/` request and\n // mints a scoped proxy session cookie server-side before proxying.\n setState('ready');\n const mintRes = await mintAgentCapability({\n variables: {\n agentId,\n profile: effectiveProfile,\n scope: AgentCapabilityScope.IframePlugin,\n resourceId: `code-server:${CODE_SERVER_INSTANCE}`,\n ttlSeconds: 60,\n },\n });\n const token = mintRes.data?.mintAgentCapability?.token;\n if (!token) {\n throw new Error('Failed to mint editor capability');\n }\n const base = tunnelUrl.replace(/\\/$/, '');\n // code-server reads the capability from the `?vt=` QUERY (GET-only): the\n // agent consumes it server-side on the first `/code-server/` request to\n // mint the proxy session cookie. A `#vt=` fragment is never sent to the\n // server, so the editor would 401 before it could bootstrap (code-server\n // has no client-side fragment-exchange shim, unlike `/ui/*` / `/terminal`).\n const editorUrl = `${base}/code-server/?vt=${encodeURIComponent(token)}`;\n\n if (mode === 'iframe' && onIframeUrl) {\n onIframeUrl(editorUrl);\n } else {\n window.open(editorUrl, '_blank');\n }\n\n // Reset after a moment\n setTimeout(() => setState('idle'), 2000);\n } catch (err) {\n setErrorMsg(err instanceof Error ? err.message : 'Failed to open editor');\n setState('error');\n }\n }, [\n agentId,\n tunnelUrl,\n profile,\n workspacePath,\n mode,\n onIframeUrl,\n tr,\n probe,\n startCodeServer,\n stopCodeServer,\n mintAgentCapability,\n ]);\n\n const isDisabled =\n !agentId || !tunnelUrl || (state !== 'idle' && state !== 'error' && state !== 'ready');\n\n const label = (() => {\n switch (state) {\n case 'checking':\n return tr('vibecontrols.agents.editor.checking', 'Checking...');\n case 'installing':\n return tr('vibecontrols.agents.editor.installing', 'Installing...');\n case 'starting':\n return tr('vibecontrols.agents.editor.starting', 'Starting...');\n case 'ready':\n return tr('vibecontrols.agents.editor.opened', 'Opened');\n case 'error':\n return tr('vibecontrols.agents.editor.retry', 'Retry');\n default:\n return tr('vibecontrols.agents.editor.openEditor', 'Open Editor');\n }\n })();\n\n const Icon = (() => {\n switch (state) {\n case 'checking':\n case 'installing':\n case 'starting':\n return Loader2;\n case 'ready':\n return Check;\n case 'error':\n return AlertCircle;\n default:\n return Code;\n }\n })();\n\n const isSpinning = state === 'checking' || state === 'installing' || state === 'starting';\n\n return (\n <div className=\"relative inline-flex\">\n <button\n type=\"button\"\n onClick={handleClick}\n disabled={isDisabled}\n className={`flex items-center gap-1.5 px-2.5 py-1.5 sm:px-3 sm:py-2 text-xs sm:text-sm rounded-md transition-colors disabled:opacity-50 ${\n state === 'error'\n ? 'bg-status-error-bg text-status-error-text hover:opacity-80'\n : state === 'ready'\n ? 'bg-status-success-bg text-status-success-text'\n : 'bg-bg-sunken text-text-primary hover:bg-bg-sunken/80'\n } ${className || ''}`}\n title={errorMsg || 'Open VS Code in browser via code-server'}\n >\n <Icon className={`size-3.5 sm:w-4 sm:h-4 ${isSpinning ? 'animate-spin' : ''}`} />\n <span className=\"hidden sm:inline\">{label}</span>\n </button>\n\n {/* Error popover. Anchored to the right edge (the button sits at the\n header's right side) with a fixed width so the message wraps as\n normal prose instead of being squeezed into a narrow column by the\n inline-flex wrapper. */}\n {state === 'error' && errorMsg && (\n <div\n role=\"alert\"\n className=\"absolute top-full right-0 mt-1.5 z-50 w-72 rounded-md border border-status-error-text/30 bg-bg-surface p-3 text-xs leading-relaxed text-status-error-text whitespace-normal break-words shadow-lg\"\n >\n <div className=\"flex items-start gap-2\">\n <AlertCircle className=\"mt-0.5 size-4 shrink-0\" />\n <span>{errorMsg}</span>\n </div>\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
|
|
@@ -26,7 +26,9 @@ function w({ availablePlugins: S, tunnelUrl: w, agentId: T, profile: E }) {
|
|
|
26
26
|
resourceId: e,
|
|
27
27
|
ttlSeconds: 60
|
|
28
28
|
} })).data?.mintAgentCapability?.token;
|
|
29
|
-
|
|
29
|
+
if (!n) return a;
|
|
30
|
+
let r = new URL(a);
|
|
31
|
+
return e === "code-server" || r.pathname.startsWith("/code-server") ? (r.searchParams.set("vt", n), r.toString()) : `${a}#vt=${encodeURIComponent(n)}`;
|
|
30
32
|
} catch {
|
|
31
33
|
return a;
|
|
32
34
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginUIPanel.js","names":[],"sources":["../../../src/components/agents/PluginUIPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useCallback, useRef, useEffect } from 'react';\nimport { useMintAgentCapabilityMutation } from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\nimport {\n X,\n Maximize2,\n Minimize2,\n ExternalLink,\n Loader2,\n AlertCircle,\n RotateCcw,\n Wifi,\n WifiOff,\n GripVertical,\n Plus,\n Monitor,\n ChevronDown,\n} from 'lucide-react';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface PluginUITab {\n /** Unique tab ID (allows duplicate plugins in multiple tabs) */\n id: string;\n /** The plugin name (e.g. \"ui-ai\") — may be null if nothing selected yet */\n pluginName: string | null;\n /** Display name shown on the tab */\n name: string;\n /** The full URL to load in the iframe */\n uiUrl: string | null;\n}\n\nexport interface UIPluginOption {\n /** The plugin name key (e.g. \"ui-ai\") */\n pluginName: string;\n /** Display name */\n displayName: string;\n /** Description shown in dropdown */\n description?: string;\n /** Version */\n version?: string;\n /** Custom URL to use instead of the default /ui/{pluginName} path (e.g. for code-server proxy) */\n customUrl?: string;\n}\n\ninterface PluginUIPanelProps {\n /** Available UI plugins the user can pick from */\n availablePlugins: UIPluginOption[];\n /** Base URL of the agent tunnel (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Agent ID — F4 Wave B replaced the legacy `?apiKey=` URL embed +\n * postMessage handoff with `mintAgentCapability(IFRAME_PLUGIN)`. Each\n * plugin tab loads with `#vt=<token>` so the long-lived agent secret\n * never reaches the browser.\n */\n agentId: string | null;\n /** Canonical CLI profile name; required for capability mint scoping. */\n profile: string;\n}\n\nlet tabIdCounter = 0;\nfunction nextTabId(): string {\n return `ui-tab-${++tabIdCounter}`;\n}\n\n/**\n * Embedded browser-like panel for plugin UIs.\n * Each tab has a dropdown to select which plugin to load. The same plugin can\n * be loaded in multiple tabs, each with its own navigation state.\n */\nexport function PluginUIPanel({\n availablePlugins,\n tunnelUrl,\n agentId,\n profile,\n}: PluginUIPanelProps) {\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [tabs, setTabs] = useState<PluginUITab[]>(() => {\n // Start with one blank tab\n return [{ id: nextTabId(), pluginName: null, name: 'New Tab', uiUrl: null }];\n });\n const [activeTabId, setActiveTabId] = useState<string>(tabs[0].id);\n const [maximized, setMaximized] = useState(false);\n\n // Connection status per tab\n const [connectionStatus, setConnectionStatus] = useState<\n Record<string, 'connecting' | 'connected' | 'error'>\n >({});\n\n // Dropdown open state per tab\n const [dropdownOpen, setDropdownOpen] = useState<string | null>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Drag-to-resize state\n const [resizeHeight, setResizeHeight] = useState<number | null>(null);\n const resizingRef = useRef(false);\n const startYRef = useRef(0);\n const startHeightRef = useRef(0);\n const panelRef = useRef<HTMLDivElement>(null);\n\n // Iframe refs for auth postMessage\n const iframeRefs = useRef<Record<string, HTMLIFrameElement | null>>({});\n\n const activeTab = tabs.find((t) => t.id === activeTabId) || tabs[0] || null;\n\n // Build UI URL for a plugin — F4 Wave B: append a short-lived\n // IFRAME_PLUGIN capability as `#vt=<token>` so the agent's `/ui/<name>`\n // bootstrap can exchange it for a scoped HttpOnly cookie. No\n // long-lived secret in the URL or postMessage.\n const buildUiUrl = useCallback(\n async (pluginName: string, customUrl?: string): Promise<string | null> => {\n if (!tunnelUrl || !agentId) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n\n // Use customUrl if provided (e.g. for code-server proxy path)\n const urlStr = customUrl\n ? customUrl.startsWith('http')\n ? customUrl\n : `${base}${customUrl}`\n : `${base}/ui/${pluginName}`;\n\n const finalUrl = new URL(urlStr).toString();\n\n try {\n const res = await mintAgentCapability({\n variables: {\n agentId,\n profile: profile ?? 'default',\n scope: AgentCapabilityScope.IframePlugin,\n // Pass the bare plugin name — the svc maps to `/ui/<plugin>`\n // which matches the agent's iframe-token allow-list. Previous\n // `ui:<plugin>` form produced an invalid `/ui/ui:<plugin>`\n // prefix that the agent now rejects (audit-B P0-SEC-03).\n resourceId: pluginName,\n ttlSeconds: 60,\n },\n });\n const token = res.data?.mintAgentCapability?.token;\n if (!token) return finalUrl;\n // Fragment-only — never sent to proxies/logs/Referer.\n return `${finalUrl}#vt=${encodeURIComponent(token)}`;\n } catch {\n return finalUrl;\n }\n },\n [tunnelUrl, agentId, profile, mintAgentCapability]\n );\n\n // Display name for a plugin name\n const getDisplayName = useCallback(\n (pluginName: string): string => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n return (\n opt?.displayName ||\n pluginName\n .replace(/^ui-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n },\n [availablePlugins]\n );\n\n // ── Tab Actions ───────────────────────────────────────────────────\n\n const handleAddTab = useCallback(() => {\n const newTab: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n setTabs((prev) => [...prev, newTab]);\n setActiveTabId(newTab.id);\n setDropdownOpen(newTab.id);\n }, []);\n\n const handleCloseTab = useCallback(\n (tabId: string) => {\n setTabs((prev) => {\n const next = prev.filter((t) => t.id !== tabId);\n // If we closed the last tab, add a blank one\n if (next.length === 0) {\n const blank: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n next.push(blank);\n setActiveTabId(blank.id);\n } else if (activeTabId === tabId) {\n // Select the previous tab or the first one\n const closedIdx = prev.findIndex((t) => t.id === tabId);\n const newIdx = Math.max(0, closedIdx - 1);\n setActiveTabId(next[Math.min(newIdx, next.length - 1)].id);\n }\n return next;\n });\n // Clean up refs\n delete iframeRefs.current[tabId];\n setConnectionStatus((prev) => {\n const next = { ...prev };\n delete next[tabId];\n return next;\n });\n },\n [activeTabId]\n );\n\n const handleSelectPlugin = useCallback(\n async (tabId: string, pluginName: string) => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n const displayName = getDisplayName(pluginName);\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n setDropdownOpen(null);\n const uiUrl = await buildUiUrl(pluginName, opt?.customUrl);\n setTabs((prev) =>\n prev.map((t) => (t.id === tabId ? { ...t, pluginName, name: displayName, uiUrl } : t))\n );\n },\n [availablePlugins, buildUiUrl, getDisplayName]\n );\n\n // ── Auth ──────────────────────────────────────────────────────────\n // F4 Wave B: auth is now handled by the `#vt=` fragment in the iframe\n // src. The agent exchanges it for a scoped cookie on first load. No\n // postMessage handoff needed.\n\n const handleIframeLoad = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connected' }));\n }, []);\n\n const handleIframeError = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'error' }));\n }, []);\n\n const handleReload = useCallback((tabId: string) => {\n const iframe = iframeRefs.current[tabId];\n if (iframe) {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n }, []);\n\n const handleOpenExternal = useCallback(\n (tabId: string) => {\n const tab = tabs.find((t) => t.id === tabId);\n if (!tab?.uiUrl) return;\n window.open(tab.uiUrl, '_blank');\n },\n [tabs]\n );\n\n // ── Close dropdown on outside click ───────────────────────────────\n\n useEffect(() => {\n if (!dropdownOpen) return;\n const handler = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setDropdownOpen(null);\n }\n };\n document.addEventListener('mousedown', handler);\n return () => document.removeEventListener('mousedown', handler);\n }, [dropdownOpen]);\n\n // ── Drag-to-resize ────────────────────────────────────────────────\n\n const handleResizeStart = useCallback(\n (e: React.MouseEvent) => {\n if (maximized) return;\n e.preventDefault();\n resizingRef.current = true;\n startYRef.current = e.clientY;\n startHeightRef.current = panelRef.current?.offsetHeight || 500;\n document.body.style.cursor = 'ns-resize';\n document.body.style.userSelect = 'none';\n },\n [maximized]\n );\n\n useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n if (!resizingRef.current) return;\n const delta = startYRef.current - e.clientY;\n const newHeight = Math.max(\n 250,\n Math.min(startHeightRef.current + delta, window.innerHeight - 150)\n );\n setResizeHeight(newHeight);\n };\n const handleMouseUp = () => {\n if (resizingRef.current) {\n resizingRef.current = false;\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n }\n };\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n return () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n }, []);\n\n // ── Render helpers ────────────────────────────────────────────────\n\n const renderStatusDot = (tabId: string) => {\n const status = connectionStatus[tabId];\n if (status === 'connected')\n return <Wifi className=\"size-3 text-status-success-text shrink-0\" />;\n if (status === 'error') return <WifiOff className=\"size-3 text-status-error-text shrink-0\" />;\n if (status === 'connecting')\n return <Loader2 className=\"size-3 animate-spin text-status-warning-text shrink-0\" />;\n return <Monitor className=\"size-3 text-text-muted shrink-0\" />;\n };\n\n const panelHeight = maximized ? 'h-[calc(100vh-200px)]' : resizeHeight ? undefined : 'h-[500px]';\n const panelStyle = !maximized && resizeHeight ? { height: `${resizeHeight}px` } : undefined;\n\n return (\n <div\n ref={panelRef}\n className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken\"\n >\n {/* Drag handle */}\n <div\n onMouseDown={handleResizeStart}\n className=\"h-1 cursor-ns-resize hover:bg-action-primary-bg/30 transition-colors flex items-center justify-center group\"\n title=\"Drag to resize\"\n >\n <GripVertical className=\"w-4 h-3 text-text-muted opacity-0 group-hover:opacity-100 transition-opacity rotate-90\" />\n </div>\n\n {/* ── Tab Bar ────────────────────────────────────────────────── */}\n <div className=\"flex items-center bg-bg-secondary border-b border-border-default overflow-x-auto\">\n {tabs.map((tab) => {\n const isActive = tab.id === activeTab?.id;\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setActiveTabId(tab.id))();\n }\n }}\n key={tab.id}\n className={`group flex items-center gap-1.5 pl-3 pr-1.5 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[200px] shrink-0 ${\n isActive\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => setActiveTabId(tab.id)}\n >\n {renderStatusDot(tab.id)}\n <span className=\"truncate\">{tab.name}</span>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleCloseTab(tab.id);\n }}\n className=\"ml-auto opacity-0 group-hover:opacity-100 hover:text-status-error-text transition-opacity shrink-0 p-0.5\"\n title=\"Close tab\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n );\n })}\n\n {/* Add tab button */}\n <button\n type=\"button\"\n onClick={handleAddTab}\n className=\"flex items-center justify-center size-8 shrink-0 text-text-secondary hover:text-text-primary hover:bg-bg-surface/50 transition-colors\"\n title=\"New tab\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n\n {/* Spacer */}\n <div className=\"flex-1 min-w-0\" />\n\n {/* Global panel actions */}\n <div className=\"flex items-center gap-0.5 px-2 shrink-0\">\n {activeTab?.uiUrl && (\n <>\n <button\n type=\"button\"\n onClick={() => activeTab && handleReload(activeTab.id)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Reload\"\n >\n <RotateCcw className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => activeTab && handleOpenExternal(activeTab.id)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Open in new browser tab\"\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n </>\n )}\n <button\n type=\"button\"\n onClick={() => {\n setMaximized((prev) => !prev);\n setResizeHeight(null);\n }}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={maximized ? 'Restore size' : 'Maximize'}\n >\n {maximized ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n </button>\n </div>\n </div>\n\n {/* ── Address Bar (Plugin Selector Dropdown) ─────────────────── */}\n {activeTab && (\n <div className=\"flex items-center gap-2 px-3 py-1.5 bg-bg-surface border-b border-border-default\">\n <Monitor className=\"size-4 text-text-muted shrink-0\" />\n <div className=\"relative flex-1 min-w-0\" ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(dropdownOpen === activeTab.id ? null : activeTab.id)}\n className=\"w-full flex items-center justify-between gap-2 px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-left hover:border-action-primary-bg/50 focus:outline-none focus:ring-1 focus:ring-action-primary-bg transition-colors\"\n >\n <span\n className={`truncate ${activeTab.pluginName ? 'text-text-primary' : 'text-text-muted'}`}\n >\n {activeTab.pluginName\n ? `${activeTab.name} — ${activeTab.pluginName}`\n : 'Select a plugin to load...'}\n </span>\n <ChevronDown\n className={`size-3.5 text-text-muted shrink-0 transition-transform ${dropdownOpen === activeTab.id ? 'rotate-180' : ''}`}\n />\n </button>\n\n {/* Dropdown list */}\n {dropdownOpen === activeTab.id && (\n <div className=\"absolute z-50 top-full left-0 right-0 mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg overflow-hidden max-h-64 overflow-y-auto\">\n {availablePlugins.length > 0 ? (\n availablePlugins.map((opt) => {\n const isSelected = activeTab.pluginName === opt.pluginName;\n return (\n <button\n type=\"button\"\n key={opt.pluginName}\n onClick={() => handleSelectPlugin(activeTab.id, opt.pluginName)}\n className={`w-full text-left px-3 py-2.5 hover:bg-bg-sunken transition-colors border-b border-border-subtle last:border-b-0 ${\n isSelected ? 'bg-action-primary-bg/10' : ''\n }`}\n >\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">\n {opt.displayName}\n </span>\n {opt.version && (\n <span className=\"text-xs text-text-muted\">\n {opt.version === 'latest' ? opt.version : `v${opt.version}`}\n </span>\n )}\n </div>\n {opt.description && (\n <p className=\"text-xs text-text-secondary mt-0.5\">{opt.description}</p>\n )}\n <p className=\"text-xs text-text-muted font-mono mt-0.5\">{opt.pluginName}</p>\n </button>\n );\n })\n ) : (\n <div className=\"px-3 py-4 text-center text-sm text-text-muted\">\n No UI plugins available\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* ── Content Area ───────────────────────────────────────────── */}\n <div\n className={`${panelHeight || ''} bg-bg-inverse text-text-inverse relative`}\n style={panelStyle}\n >\n {tabs.map((tab) => (\n <div\n key={tab.id}\n className={`absolute inset-0 ${tab.id === activeTab?.id ? 'block' : 'hidden'}`}\n >\n {!tab.pluginName ? (\n // Empty state — no plugin selected\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-sm\">\n <Monitor className=\"size-10 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm font-medium\">No plugin selected</p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Use the dropdown above to pick a plugin UI to load in this tab.\n </p>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(tab.id)}\n className=\"mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-muted transition-colors inline-flex items-center gap-2\"\n >\n <ChevronDown className=\"size-3.5\" />\n Select Plugin\n </button>\n </div>\n </div>\n ) : !tab.uiUrl ? (\n // No tunnel\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <AlertCircle className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">Agent tunnel not active</p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Cannot load plugin UI without an active tunnel connection.\n </p>\n </div>\n </div>\n ) : (\n // Iframe\n <iframe\n ref={(el) => {\n iframeRefs.current[tab.id] = el;\n }}\n src={tab.uiUrl}\n title={`Plugin UI — ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n onLoad={() => handleIframeLoad(tab.id)}\n onError={() => handleIframeError(tab.id)}\n />\n )}\n </div>\n ))}\n\n {/* Connection status overlay */}\n {activeTab && connectionStatus[activeTab.id] === 'connecting' && activeTab.uiUrl && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-warning-text pointer-events-none\">\n <Loader2 className=\"size-3 animate-spin\" />\n Connecting...\n </div>\n )}\n {activeTab && connectionStatus[activeTab.id] === 'error' && activeTab.uiUrl && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-error-text\">\n <WifiOff className=\"size-3\" />\n Disconnected\n <button\n type=\"button\"\n onClick={() => handleReload(activeTab.id)}\n className=\"underline hover:text-status-error-text/80 transition-colors\"\n >\n Reconnect\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginUIPanel.js","names":[],"sources":["../../../src/components/agents/PluginUIPanel.tsx"],"sourcesContent":["import { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { useState, useCallback, useRef, useEffect } from 'react';\nimport { useMintAgentCapabilityMutation } from '@/generated/wspace-operations';\nimport { AgentCapabilityScope } from '@/generated/wspace-types';\nimport {\n X,\n Maximize2,\n Minimize2,\n ExternalLink,\n Loader2,\n AlertCircle,\n RotateCcw,\n Wifi,\n WifiOff,\n GripVertical,\n Plus,\n Monitor,\n ChevronDown,\n} from 'lucide-react';\n\n// ── Types ──────────────────────────────────────────────────────────────\n\nexport interface PluginUITab {\n /** Unique tab ID (allows duplicate plugins in multiple tabs) */\n id: string;\n /** The plugin name (e.g. \"ui-ai\") — may be null if nothing selected yet */\n pluginName: string | null;\n /** Display name shown on the tab */\n name: string;\n /** The full URL to load in the iframe */\n uiUrl: string | null;\n}\n\nexport interface UIPluginOption {\n /** The plugin name key (e.g. \"ui-ai\") */\n pluginName: string;\n /** Display name */\n displayName: string;\n /** Description shown in dropdown */\n description?: string;\n /** Version */\n version?: string;\n /** Custom URL to use instead of the default /ui/{pluginName} path (e.g. for code-server proxy) */\n customUrl?: string;\n}\n\ninterface PluginUIPanelProps {\n /** Available UI plugins the user can pick from */\n availablePlugins: UIPluginOption[];\n /** Base URL of the agent tunnel (e.g. \"https://xxx.trycloudflare.com\") */\n tunnelUrl: string | null;\n /**\n * Agent ID — F4 Wave B replaced the legacy `?apiKey=` URL embed +\n * postMessage handoff with `mintAgentCapability(IFRAME_PLUGIN)`. Each\n * plugin tab loads with `#vt=<token>` so the long-lived agent secret\n * never reaches the browser.\n */\n agentId: string | null;\n /** Canonical CLI profile name; required for capability mint scoping. */\n profile: string;\n}\n\nlet tabIdCounter = 0;\nfunction nextTabId(): string {\n return `ui-tab-${++tabIdCounter}`;\n}\n\n/**\n * Embedded browser-like panel for plugin UIs.\n * Each tab has a dropdown to select which plugin to load. The same plugin can\n * be loaded in multiple tabs, each with its own navigation state.\n */\nexport function PluginUIPanel({\n availablePlugins,\n tunnelUrl,\n agentId,\n profile,\n}: PluginUIPanelProps) {\n const [mintAgentCapability] = useMintAgentCapabilityMutation();\n const { t } = useI18n();\n const tr = (key: string, fallback: string) => {\n const v = t(key);\n return v === key ? fallback : v;\n };\n const [tabs, setTabs] = useState<PluginUITab[]>(() => {\n // Start with one blank tab\n return [{ id: nextTabId(), pluginName: null, name: 'New Tab', uiUrl: null }];\n });\n const [activeTabId, setActiveTabId] = useState<string>(tabs[0].id);\n const [maximized, setMaximized] = useState(false);\n\n // Connection status per tab\n const [connectionStatus, setConnectionStatus] = useState<\n Record<string, 'connecting' | 'connected' | 'error'>\n >({});\n\n // Dropdown open state per tab\n const [dropdownOpen, setDropdownOpen] = useState<string | null>(null);\n const dropdownRef = useRef<HTMLDivElement>(null);\n\n // Drag-to-resize state\n const [resizeHeight, setResizeHeight] = useState<number | null>(null);\n const resizingRef = useRef(false);\n const startYRef = useRef(0);\n const startHeightRef = useRef(0);\n const panelRef = useRef<HTMLDivElement>(null);\n\n // Iframe refs for auth postMessage\n const iframeRefs = useRef<Record<string, HTMLIFrameElement | null>>({});\n\n const activeTab = tabs.find((t) => t.id === activeTabId) || tabs[0] || null;\n\n // Build UI URL for a plugin — F4 Wave B: append a short-lived\n // IFRAME_PLUGIN capability as `#vt=<token>` so the agent's `/ui/<name>`\n // bootstrap can exchange it for a scoped HttpOnly cookie. No\n // long-lived secret in the URL or postMessage.\n const buildUiUrl = useCallback(\n async (pluginName: string, customUrl?: string): Promise<string | null> => {\n if (!tunnelUrl || !agentId) return null;\n const base = tunnelUrl.replace(/\\/$/, '');\n\n // Use customUrl if provided (e.g. for code-server proxy path)\n const urlStr = customUrl\n ? customUrl.startsWith('http')\n ? customUrl\n : `${base}${customUrl}`\n : `${base}/ui/${pluginName}`;\n\n const finalUrl = new URL(urlStr).toString();\n\n try {\n const res = await mintAgentCapability({\n variables: {\n agentId,\n profile: profile ?? 'default',\n scope: AgentCapabilityScope.IframePlugin,\n // Pass the bare plugin name — the svc maps to `/ui/<plugin>`\n // which matches the agent's iframe-token allow-list. Previous\n // `ui:<plugin>` form produced an invalid `/ui/ui:<plugin>`\n // prefix that the agent now rejects (audit-B P0-SEC-03).\n resourceId: pluginName,\n ttlSeconds: 60,\n },\n });\n const token = res.data?.mintAgentCapability?.token;\n if (!token) return finalUrl;\n // code-server's reverse proxy reads the capability from the `?vt=`\n // QUERY (GET-only) and consumes it server-side to mint its session\n // cookie — it has no client-side fragment-exchange shim. `/ui/<plugin>`\n // (and `/terminal`) DO have that shim, so they keep the `#vt=`\n // fragment, which is never sent to proxies/logs/Referer.\n const builtUrl = new URL(finalUrl);\n const isCodeServer =\n pluginName === 'code-server' || builtUrl.pathname.startsWith('/code-server');\n if (isCodeServer) {\n builtUrl.searchParams.set('vt', token);\n return builtUrl.toString();\n }\n return `${finalUrl}#vt=${encodeURIComponent(token)}`;\n } catch {\n return finalUrl;\n }\n },\n [tunnelUrl, agentId, profile, mintAgentCapability]\n );\n\n // Display name for a plugin name\n const getDisplayName = useCallback(\n (pluginName: string): string => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n return (\n opt?.displayName ||\n pluginName\n .replace(/^ui-/, '')\n .replace(/-/g, ' ')\n .replace(/\\b\\w/g, (c) => c.toUpperCase())\n );\n },\n [availablePlugins]\n );\n\n // ── Tab Actions ───────────────────────────────────────────────────\n\n const handleAddTab = useCallback(() => {\n const newTab: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n setTabs((prev) => [...prev, newTab]);\n setActiveTabId(newTab.id);\n setDropdownOpen(newTab.id);\n }, []);\n\n const handleCloseTab = useCallback(\n (tabId: string) => {\n setTabs((prev) => {\n const next = prev.filter((t) => t.id !== tabId);\n // If we closed the last tab, add a blank one\n if (next.length === 0) {\n const blank: PluginUITab = {\n id: nextTabId(),\n pluginName: null,\n name: 'New Tab',\n uiUrl: null,\n };\n next.push(blank);\n setActiveTabId(blank.id);\n } else if (activeTabId === tabId) {\n // Select the previous tab or the first one\n const closedIdx = prev.findIndex((t) => t.id === tabId);\n const newIdx = Math.max(0, closedIdx - 1);\n setActiveTabId(next[Math.min(newIdx, next.length - 1)].id);\n }\n return next;\n });\n // Clean up refs\n delete iframeRefs.current[tabId];\n setConnectionStatus((prev) => {\n const next = { ...prev };\n delete next[tabId];\n return next;\n });\n },\n [activeTabId]\n );\n\n const handleSelectPlugin = useCallback(\n async (tabId: string, pluginName: string) => {\n const opt = availablePlugins.find((p) => p.pluginName === pluginName);\n const displayName = getDisplayName(pluginName);\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n setDropdownOpen(null);\n const uiUrl = await buildUiUrl(pluginName, opt?.customUrl);\n setTabs((prev) =>\n prev.map((t) => (t.id === tabId ? { ...t, pluginName, name: displayName, uiUrl } : t))\n );\n },\n [availablePlugins, buildUiUrl, getDisplayName]\n );\n\n // ── Auth ──────────────────────────────────────────────────────────\n // F4 Wave B: auth is now handled by the `#vt=` fragment in the iframe\n // src. The agent exchanges it for a scoped cookie on first load. No\n // postMessage handoff needed.\n\n const handleIframeLoad = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connected' }));\n }, []);\n\n const handleIframeError = useCallback((tabId: string) => {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'error' }));\n }, []);\n\n const handleReload = useCallback((tabId: string) => {\n const iframe = iframeRefs.current[tabId];\n if (iframe) {\n setConnectionStatus((prev) => ({ ...prev, [tabId]: 'connecting' }));\n const src = iframe.src;\n iframe.src = 'about:blank';\n requestAnimationFrame(() => {\n iframe.src = src;\n });\n }\n }, []);\n\n const handleOpenExternal = useCallback(\n (tabId: string) => {\n const tab = tabs.find((t) => t.id === tabId);\n if (!tab?.uiUrl) return;\n window.open(tab.uiUrl, '_blank');\n },\n [tabs]\n );\n\n // ── Close dropdown on outside click ───────────────────────────────\n\n useEffect(() => {\n if (!dropdownOpen) return;\n const handler = (e: MouseEvent) => {\n if (dropdownRef.current && !dropdownRef.current.contains(e.target as Node)) {\n setDropdownOpen(null);\n }\n };\n document.addEventListener('mousedown', handler);\n return () => document.removeEventListener('mousedown', handler);\n }, [dropdownOpen]);\n\n // ── Drag-to-resize ────────────────────────────────────────────────\n\n const handleResizeStart = useCallback(\n (e: React.MouseEvent) => {\n if (maximized) return;\n e.preventDefault();\n resizingRef.current = true;\n startYRef.current = e.clientY;\n startHeightRef.current = panelRef.current?.offsetHeight || 500;\n document.body.style.cursor = 'ns-resize';\n document.body.style.userSelect = 'none';\n },\n [maximized]\n );\n\n useEffect(() => {\n const handleMouseMove = (e: MouseEvent) => {\n if (!resizingRef.current) return;\n const delta = startYRef.current - e.clientY;\n const newHeight = Math.max(\n 250,\n Math.min(startHeightRef.current + delta, window.innerHeight - 150)\n );\n setResizeHeight(newHeight);\n };\n const handleMouseUp = () => {\n if (resizingRef.current) {\n resizingRef.current = false;\n document.body.style.cursor = '';\n document.body.style.userSelect = '';\n }\n };\n document.addEventListener('mousemove', handleMouseMove);\n document.addEventListener('mouseup', handleMouseUp);\n return () => {\n document.removeEventListener('mousemove', handleMouseMove);\n document.removeEventListener('mouseup', handleMouseUp);\n };\n }, []);\n\n // ── Render helpers ────────────────────────────────────────────────\n\n const renderStatusDot = (tabId: string) => {\n const status = connectionStatus[tabId];\n if (status === 'connected')\n return <Wifi className=\"size-3 text-status-success-text shrink-0\" />;\n if (status === 'error') return <WifiOff className=\"size-3 text-status-error-text shrink-0\" />;\n if (status === 'connecting')\n return <Loader2 className=\"size-3 animate-spin text-status-warning-text shrink-0\" />;\n return <Monitor className=\"size-3 text-text-muted shrink-0\" />;\n };\n\n const panelHeight = maximized ? 'h-[calc(100vh-200px)]' : resizeHeight ? undefined : 'h-[500px]';\n const panelStyle = !maximized && resizeHeight ? { height: `${resizeHeight}px` } : undefined;\n\n return (\n <div\n ref={panelRef}\n className=\"border border-border-default rounded-lg overflow-hidden bg-bg-sunken\"\n >\n {/* Drag handle */}\n <div\n onMouseDown={handleResizeStart}\n className=\"h-1 cursor-ns-resize hover:bg-action-primary-bg/30 transition-colors flex items-center justify-center group\"\n title=\"Drag to resize\"\n >\n <GripVertical className=\"w-4 h-3 text-text-muted opacity-0 group-hover:opacity-100 transition-opacity rotate-90\" />\n </div>\n\n {/* ── Tab Bar ────────────────────────────────────────────────── */}\n <div className=\"flex items-center bg-bg-secondary border-b border-border-default overflow-x-auto\">\n {tabs.map((tab) => {\n const isActive = tab.id === activeTab?.id;\n return (\n <div\n role=\"button\"\n tabIndex={0}\n onKeyDown={(e) => {\n if (e.key === 'Enter' || e.key === ' ') {\n e.preventDefault();\n (() => setActiveTabId(tab.id))();\n }\n }}\n key={tab.id}\n className={`group flex items-center gap-1.5 pl-3 pr-1.5 py-1.5 text-xs border-r border-border-subtle cursor-pointer transition-colors min-w-0 max-w-[200px] shrink-0 ${\n isActive\n ? 'bg-bg-surface text-text-primary border-b-2 border-b-action-primary-bg'\n : 'text-text-secondary hover:text-text-primary hover:bg-bg-surface/50'\n }`}\n onClick={() => setActiveTabId(tab.id)}\n >\n {renderStatusDot(tab.id)}\n <span className=\"truncate\">{tab.name}</span>\n <button\n type=\"button\"\n onClick={(e) => {\n e.stopPropagation();\n handleCloseTab(tab.id);\n }}\n className=\"ml-auto opacity-0 group-hover:opacity-100 hover:text-status-error-text transition-opacity shrink-0 p-0.5\"\n title=\"Close tab\"\n >\n <X className=\"size-3\" />\n </button>\n </div>\n );\n })}\n\n {/* Add tab button */}\n <button\n type=\"button\"\n onClick={handleAddTab}\n className=\"flex items-center justify-center size-8 shrink-0 text-text-secondary hover:text-text-primary hover:bg-bg-surface/50 transition-colors\"\n title=\"New tab\"\n >\n <Plus className=\"size-3.5\" />\n </button>\n\n {/* Spacer */}\n <div className=\"flex-1 min-w-0\" />\n\n {/* Global panel actions */}\n <div className=\"flex items-center gap-0.5 px-2 shrink-0\">\n {activeTab?.uiUrl && (\n <>\n <button\n type=\"button\"\n onClick={() => activeTab && handleReload(activeTab.id)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Reload\"\n >\n <RotateCcw className=\"size-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => activeTab && handleOpenExternal(activeTab.id)}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title=\"Open in new browser tab\"\n >\n <ExternalLink className=\"size-3.5\" />\n </button>\n </>\n )}\n <button\n type=\"button\"\n onClick={() => {\n setMaximized((prev) => !prev);\n setResizeHeight(null);\n }}\n className=\"p-1 text-text-secondary hover:text-text-primary transition-colors\"\n title={maximized ? 'Restore size' : 'Maximize'}\n >\n {maximized ? <Minimize2 className=\"size-3.5\" /> : <Maximize2 className=\"size-3.5\" />}\n </button>\n </div>\n </div>\n\n {/* ── Address Bar (Plugin Selector Dropdown) ─────────────────── */}\n {activeTab && (\n <div className=\"flex items-center gap-2 px-3 py-1.5 bg-bg-surface border-b border-border-default\">\n <Monitor className=\"size-4 text-text-muted shrink-0\" />\n <div className=\"relative flex-1 min-w-0\" ref={dropdownRef}>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(dropdownOpen === activeTab.id ? null : activeTab.id)}\n className=\"w-full flex items-center justify-between gap-2 px-3 py-1.5 text-sm bg-bg-sunken border border-border-default rounded-md text-left hover:border-action-primary-bg/50 focus:outline-none focus:ring-1 focus:ring-action-primary-bg transition-colors\"\n >\n <span\n className={`truncate ${activeTab.pluginName ? 'text-text-primary' : 'text-text-muted'}`}\n >\n {activeTab.pluginName\n ? `${activeTab.name} — ${activeTab.pluginName}`\n : 'Select a plugin to load...'}\n </span>\n <ChevronDown\n className={`size-3.5 text-text-muted shrink-0 transition-transform ${dropdownOpen === activeTab.id ? 'rotate-180' : ''}`}\n />\n </button>\n\n {/* Dropdown list */}\n {dropdownOpen === activeTab.id && (\n <div className=\"absolute z-50 top-full left-0 right-0 mt-1 bg-bg-surface border border-border-default rounded-md shadow-lg overflow-hidden max-h-64 overflow-y-auto\">\n {availablePlugins.length > 0 ? (\n availablePlugins.map((opt) => {\n const isSelected = activeTab.pluginName === opt.pluginName;\n return (\n <button\n type=\"button\"\n key={opt.pluginName}\n onClick={() => handleSelectPlugin(activeTab.id, opt.pluginName)}\n className={`w-full text-left px-3 py-2.5 hover:bg-bg-sunken transition-colors border-b border-border-subtle last:border-b-0 ${\n isSelected ? 'bg-action-primary-bg/10' : ''\n }`}\n >\n <div className=\"flex items-center justify-between\">\n <span className=\"text-sm font-medium text-text-primary\">\n {opt.displayName}\n </span>\n {opt.version && (\n <span className=\"text-xs text-text-muted\">\n {opt.version === 'latest' ? opt.version : `v${opt.version}`}\n </span>\n )}\n </div>\n {opt.description && (\n <p className=\"text-xs text-text-secondary mt-0.5\">{opt.description}</p>\n )}\n <p className=\"text-xs text-text-muted font-mono mt-0.5\">{opt.pluginName}</p>\n </button>\n );\n })\n ) : (\n <div className=\"px-3 py-4 text-center text-sm text-text-muted\">\n No UI plugins available\n </div>\n )}\n </div>\n )}\n </div>\n </div>\n )}\n\n {/* ── Content Area ───────────────────────────────────────────── */}\n <div\n className={`${panelHeight || ''} bg-bg-inverse text-text-inverse relative`}\n style={panelStyle}\n >\n {tabs.map((tab) => (\n <div\n key={tab.id}\n className={`absolute inset-0 ${tab.id === activeTab?.id ? 'block' : 'hidden'}`}\n >\n {!tab.pluginName ? (\n // Empty state — no plugin selected\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center max-w-sm\">\n <Monitor className=\"size-10 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm font-medium\">No plugin selected</p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Use the dropdown above to pick a plugin UI to load in this tab.\n </p>\n <button\n type=\"button\"\n onClick={() => setDropdownOpen(tab.id)}\n className=\"mt-4 px-4 py-2 bg-bg-sunken text-text-primary text-sm rounded-md hover:bg-bg-muted transition-colors inline-flex items-center gap-2\"\n >\n <ChevronDown className=\"size-3.5\" />\n Select Plugin\n </button>\n </div>\n </div>\n ) : !tab.uiUrl ? (\n // No tunnel\n <div className=\"flex items-center justify-center h-full\">\n <div className=\"text-center\">\n <AlertCircle className=\"size-8 text-text-secondary mx-auto mb-3\" />\n <p className=\"text-text-tertiary text-sm\">Agent tunnel not active</p>\n <p className=\"text-text-secondary text-xs mt-1\">\n Cannot load plugin UI without an active tunnel connection.\n </p>\n </div>\n </div>\n ) : (\n // Iframe\n <iframe\n ref={(el) => {\n iframeRefs.current[tab.id] = el;\n }}\n src={tab.uiUrl}\n title={`Plugin UI — ${tab.name}`}\n className=\"w-full h-full border-0\"\n allow=\"clipboard-read; clipboard-write\"\n onLoad={() => handleIframeLoad(tab.id)}\n onError={() => handleIframeError(tab.id)}\n />\n )}\n </div>\n ))}\n\n {/* Connection status overlay */}\n {activeTab && connectionStatus[activeTab.id] === 'connecting' && activeTab.uiUrl && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-warning-text pointer-events-none\">\n <Loader2 className=\"size-3 animate-spin\" />\n Connecting...\n </div>\n )}\n {activeTab && connectionStatus[activeTab.id] === 'error' && activeTab.uiUrl && (\n <div className=\"absolute bottom-3 right-3 flex items-center gap-2 px-3 py-1.5 bg-bg-overlay backdrop-blur-sm rounded-md text-xs text-status-error-text\">\n <WifiOff className=\"size-3\" />\n Disconnected\n <button\n type=\"button\"\n onClick={() => handleReload(activeTab.id)}\n className=\"underline hover:text-status-error-text/80 transition-colors\"\n >\n Reconnect\n </button>\n </div>\n )}\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;AA8DA,IAAI,IAAe;AACnB,SAAS,IAAoB;AAC3B,QAAO,UAAU,EAAE;;AAQrB,SAAgB,EAAc,EAC5B,qBACA,cACA,YACA,cACqB;CACrB,IAAM,CAAC,KAAuB,GAAgC,EACxD,EAAE,UAAM,GAAS,EAKjB,CAAC,GAAM,KAAW,QAEf,CAAC;EAAE,IAAI,GAAW;EAAE,YAAY;EAAM,MAAM;EAAW,OAAO;EAAM,CAAC,CAC5E,EACI,CAAC,GAAa,KAAkB,EAAiB,EAAK,GAAG,GAAG,EAC5D,CAAC,GAAW,KAAgB,EAAS,GAAM,EAG3C,CAAC,GAAkB,KAAuB,EAE9C,EAAE,CAAC,EAGC,CAAC,GAAc,KAAmB,EAAwB,KAAK,EAC/D,IAAc,EAAuB,KAAK,EAG1C,CAAC,GAAc,KAAmB,EAAwB,KAAK,EAC/D,IAAc,EAAO,GAAM,EAC3B,IAAY,EAAO,EAAE,EACrB,IAAiB,EAAO,EAAE,EAC1B,IAAW,EAAuB,KAAK,EAGvC,IAAa,EAAiD,EAAE,CAAC,EAEjE,IAAY,EAAK,MAAM,MAAM,EAAE,OAAO,EAAY,IAAI,EAAK,MAAM,MAMjE,IAAa,EACjB,OAAO,GAAoB,MAA+C;AACxE,MAAI,CAAC,KAAa,CAAC,EAAS,QAAO;EACnC,IAAM,IAAO,EAAU,QAAQ,OAAO,GAAG,EAGnC,IAAS,IACX,EAAU,WAAW,OAAO,GAC1B,IACA,GAAG,IAAO,MACZ,GAAG,EAAK,MAAM,KAEZ,IAAW,IAAI,IAAI,EAAO,CAAC,UAAU;AAE3C,MAAI;GAcF,IAAM,KAbM,MAAM,EAAoB,EACpC,WAAW;IACT;IACA,SAAS,KAAW;IACpB,OAAO,EAAqB;IAK5B,YAAY;IACZ,YAAY;IACb,EACF,CAAC,EACgB,MAAM,qBAAqB;AAC7C,OAAI,CAAC,EAAO,QAAO;GAMnB,IAAM,IAAW,IAAI,IAAI,EAAS;AAOlC,UALE,MAAe,iBAAiB,EAAS,SAAS,WAAW,eAAe,IAE5E,EAAS,aAAa,IAAI,MAAM,EAAM,EAC/B,EAAS,UAAU,IAErB,GAAG,EAAS,MAAM,mBAAmB,EAAM;UAC5C;AACN,UAAO;;IAGX;EAAC;EAAW;EAAS;EAAS;EAAoB,CACnD,EAGK,IAAiB,GACpB,MACa,EAAiB,MAAM,MAAM,EAAE,eAAe,EAAW,EAE9D,eACL,EACG,QAAQ,QAAQ,GAAG,CACnB,QAAQ,MAAM,IAAI,CAClB,QAAQ,UAAU,MAAM,EAAE,aAAa,CAAC,EAG/C,CAAC,EAAiB,CACnB,EAIK,IAAe,QAAkB;EACrC,IAAM,IAAsB;GAC1B,IAAI,GAAW;GACf,YAAY;GACZ,MAAM;GACN,OAAO;GACR;AAGD,EAFA,GAAS,MAAS,CAAC,GAAG,GAAM,EAAO,CAAC,EACpC,EAAe,EAAO,GAAG,EACzB,EAAgB,EAAO,GAAG;IACzB,EAAE,CAAC,EAEA,IAAiB,GACpB,MAAkB;AAuBjB,EAtBA,GAAS,MAAS;GAChB,IAAM,IAAO,EAAK,QAAQ,MAAM,EAAE,OAAO,EAAM;AAE/C,OAAI,EAAK,WAAW,GAAG;IACrB,IAAM,IAAqB;KACzB,IAAI,GAAW;KACf,YAAY;KACZ,MAAM;KACN,OAAO;KACR;AAED,IADA,EAAK,KAAK,EAAM,EAChB,EAAe,EAAM,GAAG;cACf,MAAgB,GAAO;IAEhC,IAAM,IAAY,EAAK,WAAW,MAAM,EAAE,OAAO,EAAM,EACjD,IAAS,KAAK,IAAI,GAAG,IAAY,EAAE;AACzC,MAAe,EAAK,KAAK,IAAI,GAAQ,EAAK,SAAS,EAAE,EAAE,GAAG;;AAE5D,UAAO;IACP,EAEF,OAAO,EAAW,QAAQ,IAC1B,GAAqB,MAAS;GAC5B,IAAM,IAAO,EAAE,GAAG,GAAM;AAExB,UADA,OAAO,EAAK,IACL;IACP;IAEJ,CAAC,EAAY,CACd,EAEK,IAAqB,EACzB,OAAO,GAAe,MAAuB;EAC3C,IAAM,IAAM,EAAiB,MAAM,MAAM,EAAE,eAAe,EAAW,EAC/D,IAAc,EAAe,EAAW;AAE9C,EADA,GAAqB,OAAU;GAAE,GAAG;IAAO,IAAQ;GAAc,EAAE,EACnE,EAAgB,KAAK;EACrB,IAAM,IAAQ,MAAM,EAAW,GAAY,GAAK,UAAU;AAC1D,KAAS,MACP,EAAK,KAAK,MAAO,EAAE,OAAO,IAAQ;GAAE,GAAG;GAAG;GAAY,MAAM;GAAa;GAAO,GAAG,EAAG,CACvF;IAEH;EAAC;EAAkB;EAAY;EAAe,CAC/C,EAOK,IAAmB,GAAa,MAAkB;AACtD,KAAqB,OAAU;GAAE,GAAG;IAAO,IAAQ;GAAa,EAAE;IACjE,EAAE,CAAC,EAEA,KAAoB,GAAa,MAAkB;AACvD,KAAqB,OAAU;GAAE,GAAG;IAAO,IAAQ;GAAS,EAAE;IAC7D,EAAE,CAAC,EAEA,IAAe,GAAa,MAAkB;EAClD,IAAM,IAAS,EAAW,QAAQ;AAClC,MAAI,GAAQ;AACV,MAAqB,OAAU;IAAE,GAAG;KAAO,IAAQ;IAAc,EAAE;GACnE,IAAM,IAAM,EAAO;AAEnB,GADA,EAAO,MAAM,eACb,4BAA4B;AAC1B,MAAO,MAAM;KACb;;IAEH,EAAE,CAAC,EAEA,KAAqB,GACxB,MAAkB;EACjB,IAAM,IAAM,EAAK,MAAM,MAAM,EAAE,OAAO,EAAM;AACvC,KAAK,SACV,OAAO,KAAK,EAAI,OAAO,SAAS;IAElC,CAAC,EAAK,CACP;AAID,SAAgB;AACd,MAAI,CAAC,EAAc;EACnB,IAAM,KAAW,MAAkB;AACjC,GAAI,EAAY,WAAW,CAAC,EAAY,QAAQ,SAAS,EAAE,OAAe,IACxE,EAAgB,KAAK;;AAIzB,SADA,SAAS,iBAAiB,aAAa,EAAQ,QAClC,SAAS,oBAAoB,aAAa,EAAQ;IAC9D,CAAC,EAAa,CAAC;CAIlB,IAAM,KAAoB,GACvB,MAAwB;AACnB,QACJ,EAAE,gBAAgB,EAClB,EAAY,UAAU,IACtB,EAAU,UAAU,EAAE,SACtB,EAAe,UAAU,EAAS,SAAS,gBAAgB,KAC3D,SAAS,KAAK,MAAM,SAAS,aAC7B,SAAS,KAAK,MAAM,aAAa;IAEnC,CAAC,EAAU,CACZ;AAED,SAAgB;EACd,IAAM,KAAmB,MAAkB;AACzC,OAAI,CAAC,EAAY,QAAS;GAC1B,IAAM,IAAQ,EAAU,UAAU,EAAE;AAKpC,KAJkB,KAAK,IACrB,KACA,KAAK,IAAI,EAAe,UAAU,GAAO,OAAO,cAAc,IAAI,CACnE,CACyB;KAEtB,UAAsB;AAC1B,GAAI,EAAY,YACd,EAAY,UAAU,IACtB,SAAS,KAAK,MAAM,SAAS,IAC7B,SAAS,KAAK,MAAM,aAAa;;AAKrC,SAFA,SAAS,iBAAiB,aAAa,EAAgB,EACvD,SAAS,iBAAiB,WAAW,EAAc,QACtC;AAEX,GADA,SAAS,oBAAoB,aAAa,EAAgB,EAC1D,SAAS,oBAAoB,WAAW,EAAc;;IAEvD,EAAE,CAAC;CAIN,IAAM,MAAmB,MAAkB;EACzC,IAAM,IAAS,EAAiB;AAMhC,SALI,MAAW,cACN,kBAAC,GAAD,EAAM,WAAU,4CAA6C,CAAA,GAClE,MAAW,UAAgB,kBAAC,GAAD,EAAS,WAAU,0CAA2C,CAAA,GACzF,MAAW,eACN,kBAAC,GAAD,EAAS,WAAU,yDAA0D,CAAA,GAC/E,kBAAC,GAAD,EAAS,WAAU,mCAAoC,CAAA;IAG1D,KAAc,IAAY,0BAA0B,IAAe,KAAA,IAAY,aAC/E,KAAa,CAAC,KAAa,IAAe,EAAE,QAAQ,GAAG,EAAa,KAAK,GAAG,KAAA;AAElF,QACE,kBAAC,OAAD;EACE,KAAK;EACL,WAAU;YAFZ;GAKE,kBAAC,OAAD;IACE,aAAa;IACb,WAAU;IACV,OAAM;cAEN,kBAAC,GAAD,EAAc,WAAU,0FAA2F,CAAA;IAC/G,CAAA;GAGN,kBAAC,OAAD;IAAK,WAAU;cAAf;KACG,EAAK,KAAK,MAGP,kBAAC,OAAD;MACE,MAAK;MACL,UAAU;MACV,YAAY,MAAM;AAChB,QAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,SACjC,EAAE,gBAAgB,EACX,EAAe,EAAI,GAAG;;MAIjC,WAAW,4JAZE,EAAI,OAAO,GAAW,KAc7B,0EACA;MAEN,eAAe,EAAe,EAAI,GAAG;gBAfvC;OAiBG,GAAgB,EAAI,GAAG;OACxB,kBAAC,QAAD;QAAM,WAAU;kBAAY,EAAI;QAAY,CAAA;OAC5C,kBAAC,UAAD;QACE,MAAK;QACL,UAAU,MAAM;AAEd,SADA,EAAE,iBAAiB,EACnB,EAAe,EAAI,GAAG;;QAExB,WAAU;QACV,OAAM;kBAEN,kBAAC,GAAD,EAAG,WAAU,UAAW,CAAA;QACjB,CAAA;OACL;QArBC,EAAI,GAqBL,CAER;KAGF,kBAAC,UAAD;MACE,MAAK;MACL,SAAS;MACT,WAAU;MACV,OAAM;gBAEN,kBAAC,GAAD,EAAM,WAAU,YAAa,CAAA;MACtB,CAAA;KAGT,kBAAC,OAAD,EAAK,WAAU,kBAAmB,CAAA;KAGlC,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACG,GAAW,SACV,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,KAAa,EAAa,EAAU,GAAG;OACtD,WAAU;OACV,OAAM;iBAEN,kBAAC,GAAD,EAAW,WAAU,YAAa,CAAA;OAC3B,CAAA,EACT,kBAAC,UAAD;OACE,MAAK;OACL,eAAe,KAAa,GAAmB,EAAU,GAAG;OAC5D,WAAU;OACV,OAAM;iBAEN,kBAAC,GAAD,EAAc,WAAU,YAAa,CAAA;OAC9B,CAAA,CACR,EAAA,CAAA,EAEL,kBAAC,UAAD;OACE,MAAK;OACL,eAAe;AAEb,QADA,GAAc,MAAS,CAAC,EAAK,EAC7B,EAAgB,KAAK;;OAEvB,WAAU;OACV,OAAO,IAAY,iBAAiB;iBAEvB,EAAZ,IAAa,IAAqC,GAAtC,EAAW,WAAU,YAAa,CAAqC;OAC7E,CAAA,CACL;;KACF;;GAGL,KACC,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,GAAD,EAAS,WAAU,mCAAoC,CAAA,EACvD,kBAAC,OAAD;KAAK,WAAU;KAA0B,KAAK;eAA9C,CACE,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAgB,MAAiB,EAAU,KAAK,OAAO,EAAU,GAAG;MACnF,WAAU;gBAHZ,CAKE,kBAAC,QAAD;OACE,WAAW,YAAY,EAAU,aAAa,sBAAsB;iBAEnE,EAAU,aACP,GAAG,EAAU,KAAK,KAAK,EAAU,eACjC;OACC,CAAA,EACP,kBAAC,GAAD,EACE,WAAW,0DAA0D,MAAiB,EAAU,KAAK,eAAe,MACpH,CAAA,CACK;SAGR,MAAiB,EAAU,MAC1B,kBAAC,OAAD;MAAK,WAAU;gBACZ,EAAiB,SAAS,IACzB,EAAiB,KAAK,MAGlB,kBAAC,UAAD;OACE,MAAK;OAEL,eAAe,EAAmB,EAAU,IAAI,EAAI,WAAW;OAC/D,WAAW,mHANI,EAAU,eAAe,EAAI,aAO7B,4BAA4B;iBAL7C;QAQE,kBAAC,OAAD;SAAK,WAAU;mBAAf,CACE,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAI;UACA,CAAA,EACN,EAAI,WACH,kBAAC,QAAD;UAAM,WAAU;oBACb,EAAI,YAAY,WAAW,EAAI,UAAU,IAAI,EAAI;UAC7C,CAAA,CAEL;;QACL,EAAI,eACH,kBAAC,KAAD;SAAG,WAAU;mBAAsC,EAAI;SAAgB,CAAA;QAEzE,kBAAC,KAAD;SAAG,WAAU;mBAA4C,EAAI;SAAe,CAAA;QACrE;SApBF,EAAI,WAoBF,CAEX,GAEF,kBAAC,OAAD;OAAK,WAAU;iBAAgD;OAEzD,CAAA;MAEJ,CAAA,CAEJ;OACF;;GAIR,kBAAC,OAAD;IACE,WAAW,GAAG,MAAe,GAAG;IAChC,OAAO;cAFT;KAIG,EAAK,KAAK,MACT,kBAAC,OAAD;MAEE,WAAW,oBAAoB,EAAI,OAAO,GAAW,KAAK,UAAU;gBAElE,EAAI,aAmBD,EAAI,QAaP,kBAAC,UAAD;OACE,MAAM,MAAO;AACX,UAAW,QAAQ,EAAI,MAAM;;OAE/B,KAAK,EAAI;OACT,OAAO,eAAe,EAAI;OAC1B,WAAU;OACV,OAAM;OACN,cAAc,EAAiB,EAAI,GAAG;OACtC,eAAe,GAAkB,EAAI,GAAG;OACxC,CAAA,GArBF,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,IAAD,EAAa,WAAU,2CAA4C,CAAA;SACnE,kBAAC,KAAD;UAAG,WAAU;oBAA6B;UAA2B,CAAA;SACrE,kBAAC,KAAD;UAAG,WAAU;oBAAmC;UAE5C,CAAA;SACA;;OACF,CAAA,GA3BN,kBAAC,OAAD;OAAK,WAAU;iBACb,kBAAC,OAAD;QAAK,WAAU;kBAAf;SACE,kBAAC,GAAD,EAAS,WAAU,4CAA6C,CAAA;SAChE,kBAAC,KAAD;UAAG,WAAU;oBAAyC;UAAsB,CAAA;SAC5E,kBAAC,KAAD;UAAG,WAAU;oBAAmC;UAE5C,CAAA;SACJ,kBAAC,UAAD;UACE,MAAK;UACL,eAAe,EAAgB,EAAI,GAAG;UACtC,WAAU;oBAHZ,CAKE,kBAAC,GAAD,EAAa,WAAU,YAAa,CAAA,EAAA,gBAE7B;;SACL;;OACF,CAAA;MA0BJ,EA/CC,EAAI,GA+CL,CACN;KAGD,KAAa,EAAiB,EAAU,QAAQ,gBAAgB,EAAU,SACzE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,GAAD,EAAS,WAAU,uBAAwB,CAAA,EAAA,gBAEvC;;KAEP,KAAa,EAAiB,EAAU,QAAQ,WAAW,EAAU,SACpE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACE,kBAAC,GAAD,EAAS,WAAU,UAAW,CAAA;;OAE9B,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAa,EAAU,GAAG;QACzC,WAAU;kBACX;QAEQ,CAAA;OACL;;KAEJ;;GACF"}
|
|
@@ -6,64 +6,64 @@ import { useCallback as i, useEffect as a, useRef as o, useState as s } from "re
|
|
|
6
6
|
import { Check as c, CheckCircle2 as l, Download as u, Loader2 as d, Puzzle as f, Trash2 as p } from "lucide-react";
|
|
7
7
|
import { Fragment as m, jsx as h, jsxs as g } from "react/jsx-runtime";
|
|
8
8
|
//#region src/components/ai/PluginHarnessPicker.tsx
|
|
9
|
-
function _({ packages: _, agentId: v, title: y, description: b, onChange: x }) {
|
|
10
|
-
let { installed:
|
|
9
|
+
function _({ packages: _, agentId: v, title: y, description: b, onChange: x, renderInstalledAction: S }) {
|
|
10
|
+
let { installed: C, installing: w, install: T, refetch: E, loading: D, error: O } = n(_, v), [k] = e(), [A, j] = s(null), [M, N] = s({}), [P, F] = s({}), I = o(/* @__PURE__ */ new Map());
|
|
11
11
|
a(() => {
|
|
12
|
-
let e =
|
|
12
|
+
let e = I.current;
|
|
13
13
|
return () => {
|
|
14
14
|
for (let t of e.values()) clearTimeout(t);
|
|
15
15
|
e.clear();
|
|
16
16
|
};
|
|
17
17
|
}, []);
|
|
18
|
-
let
|
|
19
|
-
if (await
|
|
20
|
-
|
|
18
|
+
let L = i(async (e) => {
|
|
19
|
+
if (await T(e)) {
|
|
20
|
+
F((t) => ({
|
|
21
21
|
...t,
|
|
22
22
|
[e]: !0
|
|
23
23
|
}));
|
|
24
|
-
let t =
|
|
24
|
+
let t = I.current.get(e);
|
|
25
25
|
t && clearTimeout(t);
|
|
26
26
|
let n = setTimeout(() => {
|
|
27
|
-
|
|
27
|
+
F((t) => {
|
|
28
28
|
if (!t[e]) return t;
|
|
29
29
|
let n = { ...t };
|
|
30
30
|
return delete n[e], n;
|
|
31
|
-
}),
|
|
31
|
+
}), I.current.delete(e);
|
|
32
32
|
}, 1500);
|
|
33
|
-
|
|
33
|
+
I.current.set(e, n), x?.();
|
|
34
34
|
}
|
|
35
|
-
}, [
|
|
35
|
+
}, [T, x]), R = i(async (e) => {
|
|
36
36
|
if (v) {
|
|
37
|
-
|
|
37
|
+
j(e), N((t) => ({
|
|
38
38
|
...t,
|
|
39
39
|
[e]: ""
|
|
40
40
|
}));
|
|
41
41
|
try {
|
|
42
|
-
let { data: t } = await
|
|
42
|
+
let { data: t } = await k({ variables: {
|
|
43
43
|
agentId: v,
|
|
44
44
|
packageName: e
|
|
45
45
|
} });
|
|
46
46
|
if (!t?.removeAgentPlugin.success) {
|
|
47
|
-
|
|
47
|
+
N((n) => ({
|
|
48
48
|
...n,
|
|
49
49
|
[e]: t?.removeAgentPlugin.error || "Remove failed"
|
|
50
50
|
}));
|
|
51
51
|
return;
|
|
52
52
|
}
|
|
53
|
-
await
|
|
53
|
+
await E(), x?.();
|
|
54
54
|
} catch (t) {
|
|
55
|
-
|
|
55
|
+
N((n) => ({
|
|
56
56
|
...n,
|
|
57
57
|
[e]: t instanceof Error ? t.message : String(t)
|
|
58
58
|
}));
|
|
59
59
|
} finally {
|
|
60
|
-
|
|
60
|
+
j(null);
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
63
|
}, [
|
|
64
64
|
v,
|
|
65
|
-
|
|
66
|
-
|
|
65
|
+
k,
|
|
66
|
+
E,
|
|
67
67
|
x
|
|
68
68
|
]);
|
|
69
69
|
return _.length === 0 ? null : /* @__PURE__ */ g("section", {
|
|
@@ -77,15 +77,15 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x }) {
|
|
|
77
77
|
className: "text-sm text-text-secondary mt-1",
|
|
78
78
|
children: b
|
|
79
79
|
})] }),
|
|
80
|
-
|
|
80
|
+
O && /* @__PURE__ */ h("div", {
|
|
81
81
|
role: "alert",
|
|
82
82
|
className: "rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text",
|
|
83
|
-
children:
|
|
83
|
+
children: O
|
|
84
84
|
}),
|
|
85
85
|
/* @__PURE__ */ h("div", {
|
|
86
86
|
className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3",
|
|
87
87
|
children: _.map((e) => {
|
|
88
|
-
let n = r(e), i = !!
|
|
88
|
+
let n = r(e), i = !!C[e], a = w === e, o = A === e, s = a || o || D, _ = !!P[e], y = M[e];
|
|
89
89
|
return /* @__PURE__ */ g(t, {
|
|
90
90
|
padding: "md",
|
|
91
91
|
className: "flex flex-col gap-3",
|
|
@@ -120,31 +120,35 @@ function _({ packages: _, agentId: v, title: y, description: b, onChange: x }) {
|
|
|
120
120
|
]
|
|
121
121
|
})]
|
|
122
122
|
}),
|
|
123
|
-
/* @__PURE__ */
|
|
123
|
+
/* @__PURE__ */ g("div", {
|
|
124
124
|
className: "flex items-center gap-2",
|
|
125
|
-
children:
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
125
|
+
children: [
|
|
126
|
+
i && _ ? /* @__PURE__ */ g("button", {
|
|
127
|
+
type: "button",
|
|
128
|
+
disabled: !0,
|
|
129
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg text-status-success-text border border-status-success-border cursor-default",
|
|
130
|
+
"data-testid": `plugin-installed-${e}`,
|
|
131
|
+
"aria-live": "polite",
|
|
132
|
+
children: [/* @__PURE__ */ h(l, { className: "size-3.5" }), "Installed"]
|
|
133
|
+
}) : i ? /* @__PURE__ */ g("button", {
|
|
134
|
+
type: "button",
|
|
135
|
+
disabled: s || !v,
|
|
136
|
+
onClick: () => R(e),
|
|
137
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed",
|
|
138
|
+
"data-testid": `plugin-remove-${e}`,
|
|
139
|
+
children: [o ? /* @__PURE__ */ h(d, { className: "size-3.5 animate-spin" }) : /* @__PURE__ */ h(p, { className: "size-3.5" }), "Remove"]
|
|
140
|
+
}) : null,
|
|
141
|
+
i && !_ && S?.(e),
|
|
142
|
+
!i && /* @__PURE__ */ h("button", {
|
|
143
|
+
type: "button",
|
|
144
|
+
disabled: s || !v,
|
|
145
|
+
onClick: () => L(e),
|
|
146
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed",
|
|
147
|
+
"data-testid": `plugin-install-${e}`,
|
|
148
|
+
"aria-busy": a,
|
|
149
|
+
children: a ? /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(d, { className: "size-3.5 animate-spin" }), "Installing…"] }) : /* @__PURE__ */ g(m, { children: [/* @__PURE__ */ h(u, { className: "size-3.5" }), "Install"] })
|
|
150
|
+
})
|
|
151
|
+
]
|
|
148
152
|
}),
|
|
149
153
|
y && /* @__PURE__ */ h("p", {
|
|
150
154
|
className: "text-xs text-status-error-text",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"PluginHarnessPicker.js","names":[],"sources":["../../../src/components/ai/PluginHarnessPicker.tsx"],"sourcesContent":["/**\n * <PluginHarnessPicker />\n *\n * Grid of installable VibeControls agent plugins with per-card\n * Install / Installed / Remove state. Driven by the existing\n * `usePluginAvailability` hook (same code path as `<PluginGate>`), so\n * the Plugins tab on the Agent details page and this picker stay in\n * sync automatically.\n *\n * The default use-case is the AI workbench: once the base\n * `@vibecontrols/vibe-plugin-ai` plugin is installed, this picker lists\n * every AI provider harness (claude, codex, copilot, gemini, …) so the\n * user can add / remove harnesses without leaving the AI page.\n *\n * The component is feature-neutral — pass any `packages` array (e.g.\n * session providers, tunnel providers) to reuse the same UI.\n */\n\nimport { useCallback, useEffect, useRef, useState } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { Card } from '../shared/Card';\n\nexport interface PluginHarnessPickerProps {\n /** Package names to offer. Metadata is resolved via `getPluginMeta`. */\n packages: readonly string[];\n /** Active agent id. When null, the picker renders in a disabled state. */\n agentId: string | null;\n /** Optional section heading (rendered above the grid). */\n title?: string;\n /** Optional section description. */\n description?: string;\n /** Fired after a successful install or remove so parents can refetch. */\n onChange?: () => void;\n}\n\nexport function PluginHarnessPicker({\n packages,\n agentId,\n title,\n description,\n onChange,\n}: PluginHarnessPickerProps) {\n // Plugins are agent-wide (BOFF-2612); no profile threading needed.\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId\n );\n const [removePlugin] = useRemoveAgentPluginMutation();\n const [removingPkg, setRemovingPkg] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<Record<string, string>>({});\n // Per-package \"just installed\" pulse — flips on for ~1.5s after a\n // successful install so the button shows a clear success affordance\n // (checkmark + \"Installed\") before the card transitions to its\n // installed/Remove state. Without this the button silently flips from\n // spinner to Remove and users miss the success signal.\n const [justInstalled, setJustInstalled] = useState<Record<string, boolean>>({});\n const justInstalledTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = justInstalledTimers.current;\n return () => {\n for (const t of timers.values()) clearTimeout(t);\n timers.clear();\n };\n }, []);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) {\n setJustInstalled((prev) => ({ ...prev, [pkg]: true }));\n const existing = justInstalledTimers.current.get(pkg);\n if (existing) clearTimeout(existing);\n const timer = setTimeout(() => {\n setJustInstalled((prev) => {\n if (!prev[pkg]) return prev;\n const next = { ...prev };\n delete next[pkg];\n return next;\n });\n justInstalledTimers.current.delete(pkg);\n }, 1500);\n justInstalledTimers.current.set(pkg, timer);\n onChange?.();\n }\n },\n [install, onChange]\n );\n\n const handleRemove = useCallback(\n async (pkg: string) => {\n if (!agentId) return;\n setRemovingPkg(pkg);\n setRemoveError((e) => ({ ...e, [pkg]: '' }));\n try {\n const { data } = await removePlugin({\n variables: { agentId, packageName: pkg },\n });\n if (!data?.removeAgentPlugin.success) {\n setRemoveError((e) => ({\n ...e,\n [pkg]: data?.removeAgentPlugin.error || 'Remove failed',\n }));\n return;\n }\n await refetch();\n onChange?.();\n } catch (e) {\n setRemoveError((err) => ({\n ...err,\n [pkg]: e instanceof Error ? e.message : String(e),\n }));\n } finally {\n setRemovingPkg(null);\n }\n },\n [agentId, removePlugin, refetch, onChange]\n );\n\n if (packages.length === 0) return null;\n\n return (\n <section className=\"flex flex-col gap-3\" data-testid=\"plugin-harness-picker\">\n {(title || description) && (\n <div>\n {title && <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>}\n {description && <p className=\"text-sm text-text-secondary mt-1\">{description}</p>}\n </div>\n )}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {packages.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalled = !!installed[pkg];\n const isInstalling = installing === pkg;\n const isRemoving = removingPkg === pkg;\n const isBusy = isInstalling || isRemoving || loading;\n const showJustInstalled = !!justInstalled[pkg];\n const err = removeError[pkg];\n return (\n <Card key={pkg} padding=\"md\" className=\"flex flex-col gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start gap-2 flex-wrap\">\n {/*\n Plugin display name must wrap freely up to a\n reasonable hard cap so the column never blows up\n the modal layout. break-words allows wrapping on\n arbitrary characters when a name has no spaces;\n max-w bound corresponds to ~100 char-cells (a\n generous upper bound on real-world display names).\n */}\n <h4 className=\"font-medium text-text-primary break-words max-w-[100ch]\">\n {meta.displayName}\n </h4>\n {isInstalled && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg text-status-success-text shrink-0\">\n <Check className=\"size-3\" /> installed\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder mt-1 break-all\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {isInstalled && showJustInstalled ? (\n <button\n type=\"button\"\n disabled\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg text-status-success-text border border-status-success-border cursor-default\"\n data-testid={`plugin-installed-${pkg}`}\n aria-live=\"polite\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </button>\n ) : isInstalled ? (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleRemove(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-remove-${pkg}`}\n >\n {isRemoving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Remove\n </button>\n ) : (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleInstall(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n aria-busy={isInstalling}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"size-3.5\" />\n Install\n </>\n )}\n </button>\n )}\n </div>\n {err && <p className=\"text-xs text-status-error-text\">{err}</p>}\n </Card>\n );\n })}\n </div>\n {!agentId && <p className=\"text-xs text-text-muted\">Select an agent to manage plugins.</p>}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"PluginHarnessPicker.js","names":[],"sources":["../../../src/components/ai/PluginHarnessPicker.tsx"],"sourcesContent":["/**\n * <PluginHarnessPicker />\n *\n * Grid of installable VibeControls agent plugins with per-card\n * Install / Installed / Remove state. Driven by the existing\n * `usePluginAvailability` hook (same code path as `<PluginGate>`), so\n * the Plugins tab on the Agent details page and this picker stay in\n * sync automatically.\n *\n * The default use-case is the AI workbench: once the base\n * `@vibecontrols/vibe-plugin-ai` plugin is installed, this picker lists\n * every AI provider harness (claude, codex, copilot, gemini, …) so the\n * user can add / remove harnesses without leaving the AI page.\n *\n * The component is feature-neutral — pass any `packages` array (e.g.\n * session providers, tunnel providers) to reuse the same UI.\n */\n\nimport { useCallback, useEffect, useRef, useState, type ReactNode } from 'react';\nimport { Check, CheckCircle2, Download, Loader2, Puzzle, Trash2 } from 'lucide-react';\nimport { useRemoveAgentPluginMutation } from '@/generated/wspace-operations';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { Card } from '../shared/Card';\n\nexport interface PluginHarnessPickerProps {\n /** Package names to offer. Metadata is resolved via `getPluginMeta`. */\n packages: readonly string[];\n /** Active agent id. When null, the picker renders in a disabled state. */\n agentId: string | null;\n /** Optional section heading (rendered above the grid). */\n title?: string;\n /** Optional section description. */\n description?: string;\n /** Fired after a successful install or remove so parents can refetch. */\n onChange?: () => void;\n /**\n * Optional per-card action slot, rendered alongside the Remove button when\n * a package is installed. Lets a feature (e.g. the Security tab) attach a\n * contextual action — like \"Run scan\" — without coupling this generic\n * picker to that feature. Receives the installed package name.\n */\n renderInstalledAction?: (pkg: string) => ReactNode;\n}\n\nexport function PluginHarnessPicker({\n packages,\n agentId,\n title,\n description,\n onChange,\n renderInstalledAction,\n}: PluginHarnessPickerProps) {\n // Plugins are agent-wide (BOFF-2612); no profile threading needed.\n const { installed, installing, install, refetch, loading, error } = usePluginAvailability(\n packages,\n agentId\n );\n const [removePlugin] = useRemoveAgentPluginMutation();\n const [removingPkg, setRemovingPkg] = useState<string | null>(null);\n const [removeError, setRemoveError] = useState<Record<string, string>>({});\n // Per-package \"just installed\" pulse — flips on for ~1.5s after a\n // successful install so the button shows a clear success affordance\n // (checkmark + \"Installed\") before the card transitions to its\n // installed/Remove state. Without this the button silently flips from\n // spinner to Remove and users miss the success signal.\n const [justInstalled, setJustInstalled] = useState<Record<string, boolean>>({});\n const justInstalledTimers = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n\n useEffect(() => {\n const timers = justInstalledTimers.current;\n return () => {\n for (const t of timers.values()) clearTimeout(t);\n timers.clear();\n };\n }, []);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) {\n setJustInstalled((prev) => ({ ...prev, [pkg]: true }));\n const existing = justInstalledTimers.current.get(pkg);\n if (existing) clearTimeout(existing);\n const timer = setTimeout(() => {\n setJustInstalled((prev) => {\n if (!prev[pkg]) return prev;\n const next = { ...prev };\n delete next[pkg];\n return next;\n });\n justInstalledTimers.current.delete(pkg);\n }, 1500);\n justInstalledTimers.current.set(pkg, timer);\n onChange?.();\n }\n },\n [install, onChange]\n );\n\n const handleRemove = useCallback(\n async (pkg: string) => {\n if (!agentId) return;\n setRemovingPkg(pkg);\n setRemoveError((e) => ({ ...e, [pkg]: '' }));\n try {\n const { data } = await removePlugin({\n variables: { agentId, packageName: pkg },\n });\n if (!data?.removeAgentPlugin.success) {\n setRemoveError((e) => ({\n ...e,\n [pkg]: data?.removeAgentPlugin.error || 'Remove failed',\n }));\n return;\n }\n await refetch();\n onChange?.();\n } catch (e) {\n setRemoveError((err) => ({\n ...err,\n [pkg]: e instanceof Error ? e.message : String(e),\n }));\n } finally {\n setRemovingPkg(null);\n }\n },\n [agentId, removePlugin, refetch, onChange]\n );\n\n if (packages.length === 0) return null;\n\n return (\n <section className=\"flex flex-col gap-3\" data-testid=\"plugin-harness-picker\">\n {(title || description) && (\n <div>\n {title && <h3 className=\"text-base font-semibold text-text-primary\">{title}</h3>}\n {description && <p className=\"text-sm text-text-secondary mt-1\">{description}</p>}\n </div>\n )}\n {error && (\n <div\n role=\"alert\"\n className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n {error}\n </div>\n )}\n <div className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3\">\n {packages.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalled = !!installed[pkg];\n const isInstalling = installing === pkg;\n const isRemoving = removingPkg === pkg;\n const isBusy = isInstalling || isRemoving || loading;\n const showJustInstalled = !!justInstalled[pkg];\n const err = removeError[pkg];\n return (\n <Card key={pkg} padding=\"md\" className=\"flex flex-col gap-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"flex-shrink-0 size-9 rounded-md bg-bg-sunken flex items-center justify-center\">\n <Puzzle className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-start gap-2 flex-wrap\">\n {/*\n Plugin display name must wrap freely up to a\n reasonable hard cap so the column never blows up\n the modal layout. break-words allows wrapping on\n arbitrary characters when a name has no spaces;\n max-w bound corresponds to ~100 char-cells (a\n generous upper bound on real-world display names).\n */}\n <h4 className=\"font-medium text-text-primary break-words max-w-[100ch]\">\n {meta.displayName}\n </h4>\n {isInstalled && (\n <span className=\"inline-flex items-center gap-1 px-2 py-0.5 text-xs rounded bg-status-success-bg text-status-success-text shrink-0\">\n <Check className=\"size-3\" /> installed\n </span>\n )}\n </div>\n <p className=\"text-sm text-text-secondary mt-0.5\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder mt-1 break-all\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n </div>\n <div className=\"flex items-center gap-2\">\n {isInstalled && showJustInstalled ? (\n <button\n type=\"button\"\n disabled\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-status-success-bg text-status-success-text border border-status-success-border cursor-default\"\n data-testid={`plugin-installed-${pkg}`}\n aria-live=\"polite\"\n >\n <CheckCircle2 className=\"size-3.5\" />\n Installed\n </button>\n ) : isInstalled ? (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleRemove(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md border border-border-default text-text-secondary hover:border-status-error-border hover:text-status-error-text transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-remove-${pkg}`}\n >\n {isRemoving ? (\n <Loader2 className=\"size-3.5 animate-spin\" />\n ) : (\n <Trash2 className=\"size-3.5\" />\n )}\n Remove\n </button>\n ) : null}\n {isInstalled && !showJustInstalled && renderInstalledAction?.(pkg)}\n {!isInstalled && (\n <button\n type=\"button\"\n disabled={isBusy || !agentId}\n onClick={() => handleInstall(pkg)}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n aria-busy={isInstalling}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"size-3.5\" />\n Install\n </>\n )}\n </button>\n )}\n </div>\n {err && <p className=\"text-xs text-status-error-text\">{err}</p>}\n </Card>\n );\n })}\n </div>\n {!agentId && <p className=\"text-xs text-text-muted\">Select an agent to manage plugins.</p>}\n </section>\n );\n}\n"],"mappings":"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"}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
import { SECURITY_STAGE_BY_PACKAGE as e } from "../../constants/pluginCatalog.js";
|
|
2
|
+
import { SecurityAgentError as t } from "./agentClient.js";
|
|
3
|
+
import { useStartSecurityScanMutation as n } from "./hooks.js";
|
|
4
|
+
import { useCallback as r, useState as i } from "react";
|
|
5
|
+
import { AlertCircle as a, Loader2 as o, Play as s } from "lucide-react";
|
|
6
|
+
import { Fragment as c, jsx as l, jsxs as u } from "react/jsx-runtime";
|
|
7
|
+
//#region src/components/security/SecurityRunButton.tsx
|
|
8
|
+
function d({ pkg: d, vibeId: f, workspaceId: p, repoUrl: m, repoLocalPath: h, commit: g, agentId: _, profile: v }) {
|
|
9
|
+
let y = e[d], { mutateAsync: b, isPending: x } = n({
|
|
10
|
+
agentId: _,
|
|
11
|
+
profile: v
|
|
12
|
+
}), [S, C] = i(null), w = r(async () => {
|
|
13
|
+
if (!(!y || !_)) {
|
|
14
|
+
C(null);
|
|
15
|
+
try {
|
|
16
|
+
await b({
|
|
17
|
+
vibeId: f,
|
|
18
|
+
workspaceId: p,
|
|
19
|
+
repoUrl: m,
|
|
20
|
+
repoLocalPath: h,
|
|
21
|
+
commit: g,
|
|
22
|
+
stage: y
|
|
23
|
+
});
|
|
24
|
+
} catch (e) {
|
|
25
|
+
C(e instanceof t || e instanceof Error ? e.message : "Failed to start scan");
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}, [
|
|
29
|
+
y,
|
|
30
|
+
_,
|
|
31
|
+
b,
|
|
32
|
+
f,
|
|
33
|
+
p,
|
|
34
|
+
m,
|
|
35
|
+
h,
|
|
36
|
+
g
|
|
37
|
+
]);
|
|
38
|
+
return y ? /* @__PURE__ */ u("div", {
|
|
39
|
+
className: "flex flex-col gap-1",
|
|
40
|
+
children: [/* @__PURE__ */ l("button", {
|
|
41
|
+
type: "button",
|
|
42
|
+
disabled: x || !_,
|
|
43
|
+
onClick: () => void w(),
|
|
44
|
+
className: "inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed",
|
|
45
|
+
"data-testid": `security-run-${d}`,
|
|
46
|
+
"aria-busy": x,
|
|
47
|
+
children: x ? /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(o, { className: "size-3.5 animate-spin" }), "Running…"] }) : /* @__PURE__ */ u(c, { children: [/* @__PURE__ */ l(s, { className: "size-3.5" }), "Run scan"] })
|
|
48
|
+
}), S && /* @__PURE__ */ u("p", {
|
|
49
|
+
className: "inline-flex items-start gap-1 text-xs text-status-error-text",
|
|
50
|
+
children: [/* @__PURE__ */ l(a, {
|
|
51
|
+
className: "size-3.5 mt-px shrink-0",
|
|
52
|
+
"aria-hidden": "true"
|
|
53
|
+
}), /* @__PURE__ */ l("span", { children: S })]
|
|
54
|
+
})]
|
|
55
|
+
}) : null;
|
|
56
|
+
}
|
|
57
|
+
//#endregion
|
|
58
|
+
export { d as SecurityRunButton };
|
|
59
|
+
|
|
60
|
+
//# sourceMappingURL=SecurityRunButton.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"SecurityRunButton.js","names":[],"sources":["../../../src/components/security/SecurityRunButton.tsx"],"sourcesContent":["/**\n * Per-stage \"Run scan\" control for an installed security provider plugin.\n *\n * Mounted as the `renderInstalledAction` slot of `<PluginHarnessPicker>` on\n * the Security tab: each installed provider card gets a Run button that\n * kicks off a scan for that provider's lifecycle stage against the current\n * vibe. The scan runs on the agent (over the svc GraphQL proxy); on success\n * the start mutation invalidates the `['security', agentId, profile, …]`\n * query family — which includes `useSecurityRecentScansQuery` — so the new\n * run surfaces in `<SecurityScanList>` with its conclusion + severity counts.\n *\n * Errors are surfaced inline (a small chip under the button), matching how\n * `<PluginHarnessPicker>` and `<SecurityScanList>` render their failures —\n * never a raw error toast.\n */\nimport { useCallback, useState, type ReactElement } from 'react';\nimport { AlertCircle, Loader2, Play } from 'lucide-react';\n\nimport { SECURITY_STAGE_BY_PACKAGE } from '@/constants/pluginCatalog';\nimport { useStartSecurityScanMutation, type UseSecurityAgentContext } from './hooks';\nimport { SecurityAgentError, type AgentStage } from './agentClient';\n\nexport interface SecurityRunButtonProps extends UseSecurityAgentContext {\n /** The installed security provider package this button runs. */\n pkg: string;\n /** Current vibe id. */\n vibeId: string;\n /** Workspace the vibe belongs to (required by the agent scan contract). */\n workspaceId: string;\n /** Git remote → recorded as `repoUrl` on the run. */\n repoUrl: string;\n /** Local checkout path the provider tooling runs against. */\n repoLocalPath: string;\n /** Best-effort commit ref (the vibe's git branch). */\n commit: string;\n}\n\nexport function SecurityRunButton({\n pkg,\n vibeId,\n workspaceId,\n repoUrl,\n repoLocalPath,\n commit,\n agentId,\n profile,\n}: SecurityRunButtonProps): ReactElement | null {\n const stage = SECURITY_STAGE_BY_PACKAGE[pkg] as AgentStage | undefined;\n const { mutateAsync, isPending } = useStartSecurityScanMutation({ agentId, profile });\n const [error, setError] = useState<string | null>(null);\n\n const handleRun = useCallback(async () => {\n if (!stage || !agentId) return;\n setError(null);\n try {\n await mutateAsync({\n vibeId,\n workspaceId,\n repoUrl,\n repoLocalPath,\n commit,\n stage,\n });\n // On success the mutation invalidates the security query family, which\n // refetches the recent-scans list — no manual refetch needed here.\n } catch (err) {\n const message =\n err instanceof SecurityAgentError || err instanceof Error\n ? err.message\n : 'Failed to start scan';\n setError(message);\n }\n }, [stage, agentId, mutateAsync, vibeId, workspaceId, repoUrl, repoLocalPath, commit]);\n\n // Provider package isn't mapped to a known stage — can't run it.\n if (!stage) return null;\n\n const disabled = isPending || !agentId;\n\n return (\n <div className=\"flex flex-col gap-1\">\n <button\n type=\"button\"\n disabled={disabled}\n onClick={() => void handleRun()}\n className=\"inline-flex items-center gap-1.5 px-3 py-1.5 text-xs rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition disabled:opacity-60 disabled:cursor-not-allowed\"\n data-testid={`security-run-${pkg}`}\n aria-busy={isPending}\n >\n {isPending ? (\n <>\n <Loader2 className=\"size-3.5 animate-spin\" />\n Running…\n </>\n ) : (\n <>\n <Play className=\"size-3.5\" />\n Run scan\n </>\n )}\n </button>\n {error && (\n <p className=\"inline-flex items-start gap-1 text-xs text-status-error-text\">\n <AlertCircle className=\"size-3.5 mt-px shrink-0\" aria-hidden=\"true\" />\n <span>{error}</span>\n </p>\n )}\n </div>\n );\n}\n"],"mappings":";;;;;;;AAqCA,SAAgB,EAAkB,EAChC,QACA,WACA,gBACA,YACA,kBACA,WACA,YACA,cAC8C;CAC9C,IAAM,IAAQ,EAA0B,IAClC,EAAE,gBAAa,iBAAc,EAA6B;EAAE;EAAS;EAAS,CAAC,EAC/E,CAAC,GAAO,KAAY,EAAwB,KAAK,EAEjD,IAAY,EAAY,YAAY;AACpC,SAAC,KAAS,CAAC,IACf;KAAS,KAAK;AACd,OAAI;AACF,UAAM,EAAY;KAChB;KACA;KACA;KACA;KACA;KACA;KACD,CAAC;YAGK,GAAK;AAKZ,MAHE,aAAe,KAAsB,aAAe,QAChD,EAAI,UACJ,uBACW;;;IAElB;EAAC;EAAO;EAAS;EAAa;EAAQ;EAAa;EAAS;EAAe;EAAO,CAAC;AAOtF,QAJK,IAKH,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,UAAD;GACE,MAAK;GACK,UANC,KAAa,CAAC;GAOzB,eAAe,KAAK,GAAW;GAC/B,WAAU;GACV,eAAa,gBAAgB;GAC7B,aAAW;aAEV,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,yBAA0B,CAAA,EAAA,WAE5C,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAM,WAAU,YAAa,CAAA,EAAA,WAE5B,EAAA,CAAA;GAEE,CAAA,EACR,KACC,kBAAC,KAAD;GAAG,WAAU;aAAb,CACE,kBAAC,GAAD;IAAa,WAAU;IAA0B,eAAY;IAAS,CAAA,EACtE,kBAAC,QAAD,EAAA,UAAO,GAAa,CAAA,CAClB;KAEF;MAhCW"}
|
|
@@ -1,50 +1,66 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
3
|
-
import {
|
|
4
|
-
import {
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
1
|
+
import { useGetVibeQuery as e } from "../../generated/wspace-operations.js";
|
|
2
|
+
import { SECURITY_META_PACKAGE as t, SECURITY_PROVIDER_PACKAGES as n } from "../../constants/pluginCatalog.js";
|
|
3
|
+
import { PluginGate as r } from "../shared/PluginGate.js";
|
|
4
|
+
import { PluginHarnessPicker as i } from "../ai/PluginHarnessPicker.js";
|
|
5
|
+
import { SecurityScanList as a } from "./SecurityScanList.js";
|
|
6
|
+
import { SecurityRunButton as o } from "./SecurityRunButton.js";
|
|
7
|
+
import { Shield as s } from "lucide-react";
|
|
8
|
+
import { jsx as c, jsxs as l } from "react/jsx-runtime";
|
|
7
9
|
//#region src/components/security/SecurityTabPanel.tsx
|
|
8
|
-
function
|
|
9
|
-
|
|
10
|
+
function u({ vibeId: u, agentId: d, profile: f = "default" }) {
|
|
11
|
+
let { data: p } = e({
|
|
12
|
+
variables: { id: u },
|
|
13
|
+
skip: !u
|
|
14
|
+
}), m = p?.vibe, h = m?.workspaceId ?? "", g = m?.gitRemote ?? "", _ = m?.path ?? "", v = m?.gitBranch ?? "";
|
|
15
|
+
return /* @__PURE__ */ l("div", {
|
|
10
16
|
"data-testid": "security-tab-panel",
|
|
11
17
|
className: "space-y-4",
|
|
12
|
-
children: [/* @__PURE__ */
|
|
18
|
+
children: [/* @__PURE__ */ l("header", {
|
|
13
19
|
className: "flex items-start gap-3",
|
|
14
|
-
children: [/* @__PURE__ */
|
|
20
|
+
children: [/* @__PURE__ */ c("div", {
|
|
15
21
|
className: "size-9 rounded-md bg-bg-sunken flex items-center justify-center shrink-0",
|
|
16
|
-
children: /* @__PURE__ */
|
|
17
|
-
}), /* @__PURE__ */
|
|
22
|
+
children: /* @__PURE__ */ c(s, { className: "size-4 text-text-secondary" })
|
|
23
|
+
}), /* @__PURE__ */ l("div", {
|
|
18
24
|
className: "min-w-0",
|
|
19
|
-
children: [/* @__PURE__ */
|
|
25
|
+
children: [/* @__PURE__ */ c("h2", {
|
|
20
26
|
className: "text-base font-semibold text-text-primary",
|
|
21
27
|
children: "Security"
|
|
22
|
-
}), /* @__PURE__ */
|
|
28
|
+
}), /* @__PURE__ */ c("p", {
|
|
23
29
|
className: "text-sm text-text-secondary mt-0.5",
|
|
24
30
|
children: "Install + run security scans across the 14-stage lifecycle. Plugins run on the agent; findings live in its local SQLite cache."
|
|
25
31
|
})]
|
|
26
32
|
})]
|
|
27
|
-
}), /* @__PURE__ */
|
|
28
|
-
agentId:
|
|
29
|
-
required: [
|
|
33
|
+
}), /* @__PURE__ */ l(r, {
|
|
34
|
+
agentId: d,
|
|
35
|
+
required: [t],
|
|
30
36
|
mode: "all",
|
|
31
37
|
eyebrow: "Security plugin required",
|
|
32
38
|
title: "Install the Security Orchestrator",
|
|
33
39
|
description: "@vibecontrols/vibe-plugin-security routes scans across per-stage providers. Required before installing individual stage providers.",
|
|
34
|
-
children: [/* @__PURE__ */
|
|
35
|
-
packages:
|
|
36
|
-
agentId:
|
|
40
|
+
children: [/* @__PURE__ */ c(i, {
|
|
41
|
+
packages: n,
|
|
42
|
+
agentId: d,
|
|
37
43
|
title: "Security Providers",
|
|
38
|
-
description: "One plugin per stage in the security lifecycle. Install the providers you want to use; uninstalled stages stay skipped."
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
44
|
+
description: "One plugin per stage in the security lifecycle. Install the providers you want to use; uninstalled stages stay skipped. Run a stage's scan from its card once installed.",
|
|
45
|
+
renderInstalledAction: (e) => /* @__PURE__ */ c(o, {
|
|
46
|
+
pkg: e,
|
|
47
|
+
vibeId: u,
|
|
48
|
+
workspaceId: h,
|
|
49
|
+
repoUrl: g,
|
|
50
|
+
repoLocalPath: _,
|
|
51
|
+
commit: v,
|
|
52
|
+
agentId: d,
|
|
53
|
+
profile: f
|
|
54
|
+
})
|
|
55
|
+
}), /* @__PURE__ */ c(a, {
|
|
56
|
+
vibeId: u,
|
|
57
|
+
agentId: d,
|
|
58
|
+
profile: f
|
|
43
59
|
})]
|
|
44
60
|
})]
|
|
45
61
|
});
|
|
46
62
|
}
|
|
47
63
|
//#endregion
|
|
48
|
-
export {
|
|
64
|
+
export { u as SecurityTabPanel };
|
|
49
65
|
|
|
50
66
|
//# sourceMappingURL=SecurityTabPanel.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"SecurityTabPanel.js","names":[],"sources":["../../../src/components/security/SecurityTabPanel.tsx"],"sourcesContent":["/**\n * Security tab on Vibe detail.\n *\n * Wave 1: surface the security plugins so the user can find +\n * install them from the UI (same pattern as the AI page).\n *\n * Wave 2 (this commit): once the meta plugin is installed, mount\n * `SecurityScanList` which reads recent scan runs straight from the\n * agent over the tunnel (`/api/profiles/<profile>/security/*`). No\n * backend GraphQL involvement — all per-vibe scan history lives in\n * the agent's local SQLite cache.\n */\n\nimport { Shield } from 'lucide-react';\nimport { PluginGate } from '../shared/PluginGate';\nimport { PluginHarnessPicker } from '../ai/PluginHarnessPicker';\nimport { SECURITY_META_PACKAGE, SECURITY_PROVIDER_PACKAGES } from '@/constants/pluginCatalog';\nimport { SecurityScanList } from './SecurityScanList';\n\ninterface PanelProps {\n vibeId: string;\n agentId: string | null;\n /**\n * Canonical agent profile name. Threaded through from VibeDetailsPage's\n * `useActiveAgentProfile` so the tab targets the same profile the rest\n * of the page is talking to.\n */\n profile?: string;\n}\n\nexport function SecurityTabPanel({\n vibeId,\n agentId,\n profile = 'default',\n}: PanelProps): React.ReactElement {\n return (\n <div data-testid=\"security-tab-panel\" className=\"space-y-4\">\n <header className=\"flex items-start gap-3\">\n <div className=\"size-9 rounded-md bg-bg-sunken flex items-center justify-center shrink-0\">\n <Shield className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">Security</h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Install + run security scans across the 14-stage lifecycle. Plugins run on the agent;\n findings live in its local SQLite cache.\n </p>\n </div>\n </header>\n\n <PluginGate\n agentId={agentId}\n required={[SECURITY_META_PACKAGE]}\n mode=\"all\"\n eyebrow=\"Security plugin required\"\n title=\"Install the Security Orchestrator\"\n description=\"@vibecontrols/vibe-plugin-security routes scans across per-stage providers. Required before installing individual stage providers.\"\n >\n <PluginHarnessPicker\n packages={SECURITY_PROVIDER_PACKAGES}\n agentId={agentId}\n title=\"Security Providers\"\n description=\"One plugin per stage in the security lifecycle. Install the providers you want to use; uninstalled stages stay skipped.\"\n />\n <SecurityScanList vibeId={vibeId} agentId={agentId} profile={profile} />\n </PluginGate>\n </div>\n );\n}\n"],"mappings":"
|
|
1
|
+
{"version":3,"file":"SecurityTabPanel.js","names":[],"sources":["../../../src/components/security/SecurityTabPanel.tsx"],"sourcesContent":["/**\n * Security tab on Vibe detail.\n *\n * Wave 1: surface the security plugins so the user can find +\n * install them from the UI (same pattern as the AI page).\n *\n * Wave 2 (this commit): once the meta plugin is installed, mount\n * `SecurityScanList` which reads recent scan runs straight from the\n * agent over the tunnel (`/api/profiles/<profile>/security/*`). No\n * backend GraphQL involvement — all per-vibe scan history lives in\n * the agent's local SQLite cache.\n */\n\nimport { Shield } from 'lucide-react';\nimport { useGetVibeQuery } from '@/generated/wspace-operations';\nimport { PluginGate } from '../shared/PluginGate';\nimport { PluginHarnessPicker } from '../ai/PluginHarnessPicker';\nimport { SECURITY_META_PACKAGE, SECURITY_PROVIDER_PACKAGES } from '@/constants/pluginCatalog';\nimport { SecurityScanList } from './SecurityScanList';\nimport { SecurityRunButton } from './SecurityRunButton';\n\ninterface PanelProps {\n vibeId: string;\n agentId: string | null;\n /**\n * Canonical agent profile name. Threaded through from VibeDetailsPage's\n * `useActiveAgentProfile` so the tab targets the same profile the rest\n * of the page is talking to.\n */\n profile?: string;\n}\n\nexport function SecurityTabPanel({\n vibeId,\n agentId,\n profile = 'default',\n}: PanelProps): React.ReactElement {\n // Repo metadata the agent scan contract needs. Shares the Apollo cache\n // entry the details page already primed via its own `useGetVibeQuery`, so\n // this is a cache read in practice — no extra network round-trip.\n const { data: vibeData } = useGetVibeQuery({\n variables: { id: vibeId },\n skip: !vibeId,\n });\n const vibe = vibeData?.vibe;\n const workspaceId = vibe?.workspaceId ?? '';\n const repoUrl = vibe?.gitRemote ?? '';\n const repoLocalPath = vibe?.path ?? '';\n const commit = vibe?.gitBranch ?? '';\n\n return (\n <div data-testid=\"security-tab-panel\" className=\"space-y-4\">\n <header className=\"flex items-start gap-3\">\n <div className=\"size-9 rounded-md bg-bg-sunken flex items-center justify-center shrink-0\">\n <Shield className=\"size-4 text-text-secondary\" />\n </div>\n <div className=\"min-w-0\">\n <h2 className=\"text-base font-semibold text-text-primary\">Security</h2>\n <p className=\"text-sm text-text-secondary mt-0.5\">\n Install + run security scans across the 14-stage lifecycle. Plugins run on the agent;\n findings live in its local SQLite cache.\n </p>\n </div>\n </header>\n\n <PluginGate\n agentId={agentId}\n required={[SECURITY_META_PACKAGE]}\n mode=\"all\"\n eyebrow=\"Security plugin required\"\n title=\"Install the Security Orchestrator\"\n description=\"@vibecontrols/vibe-plugin-security routes scans across per-stage providers. Required before installing individual stage providers.\"\n >\n <PluginHarnessPicker\n packages={SECURITY_PROVIDER_PACKAGES}\n agentId={agentId}\n title=\"Security Providers\"\n description=\"One plugin per stage in the security lifecycle. Install the providers you want to use; uninstalled stages stay skipped. Run a stage's scan from its card once installed.\"\n renderInstalledAction={(pkg) => (\n <SecurityRunButton\n pkg={pkg}\n vibeId={vibeId}\n workspaceId={workspaceId}\n repoUrl={repoUrl}\n repoLocalPath={repoLocalPath}\n commit={commit}\n agentId={agentId}\n profile={profile}\n />\n )}\n />\n <SecurityScanList vibeId={vibeId} agentId={agentId} profile={profile} />\n </PluginGate>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAgCA,SAAgB,EAAiB,EAC/B,WACA,YACA,aAAU,aACuB;CAIjC,IAAM,EAAE,MAAM,MAAa,EAAgB;EACzC,WAAW,EAAE,IAAI,GAAQ;EACzB,MAAM,CAAC;EACR,CAAC,EACI,IAAO,GAAU,MACjB,IAAc,GAAM,eAAe,IACnC,IAAU,GAAM,aAAa,IAC7B,IAAgB,GAAM,QAAQ,IAC9B,IAAS,GAAM,aAAa;AAElC,QACE,kBAAC,OAAD;EAAK,eAAY;EAAqB,WAAU;YAAhD,CACE,kBAAC,UAAD;GAAQ,WAAU;aAAlB,CACE,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,GAAD,EAAQ,WAAU,8BAA+B,CAAA;IAC7C,CAAA,EACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eAA4C;KAAa,CAAA,EACvE,kBAAC,KAAD;KAAG,WAAU;eAAqC;KAG9C,CAAA,CACA;MACC;MAET,kBAAC,GAAD;GACW;GACT,UAAU,CAAC,EAAsB;GACjC,MAAK;GACL,SAAQ;GACR,OAAM;GACN,aAAY;aANd,CAQE,kBAAC,GAAD;IACE,UAAU;IACD;IACT,OAAM;IACN,aAAY;IACZ,wBAAwB,MACtB,kBAAC,GAAD;KACO;KACG;KACK;KACJ;KACM;KACP;KACC;KACA;KACT,CAAA;IAEJ,CAAA,EACF,kBAAC,GAAD;IAA0B;IAAiB;IAAkB;IAAW,CAAA,CAC7D;KACT"}
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { SecurityAgentError as e, securityAgent as t } from "./agentClient.js";
|
|
2
2
|
import { useApolloClient as n } from "@apollo/client/react";
|
|
3
|
-
import {
|
|
3
|
+
import { useMutation as r, useQuery as i, useQueryClient as a } from "@tanstack/react-query";
|
|
4
4
|
//#region src/components/security/hooks.ts
|
|
5
|
-
function
|
|
5
|
+
function o(e, ...t) {
|
|
6
6
|
return [
|
|
7
7
|
"security",
|
|
8
8
|
e.agentId ?? "_",
|
|
@@ -10,23 +10,40 @@ function i(e, ...t) {
|
|
|
10
10
|
...t
|
|
11
11
|
];
|
|
12
12
|
}
|
|
13
|
-
function
|
|
13
|
+
function s(r, a, s = 20) {
|
|
14
14
|
let c = n();
|
|
15
|
-
return
|
|
16
|
-
queryKey:
|
|
17
|
-
enabled: !!
|
|
15
|
+
return i({
|
|
16
|
+
queryKey: o(r, "recent-scans", a, s),
|
|
17
|
+
enabled: !!r.agentId && !!a,
|
|
18
18
|
queryFn: async () => {
|
|
19
|
-
if (!
|
|
19
|
+
if (!r.agentId) throw new e("INVALID", 0, "no agent opts");
|
|
20
20
|
let n = {
|
|
21
|
-
agentId:
|
|
22
|
-
profile:
|
|
21
|
+
agentId: r.agentId,
|
|
22
|
+
profile: r.profile,
|
|
23
|
+
apolloClient: c
|
|
24
|
+
};
|
|
25
|
+
return t.listRecentScanRuns(n, a, s);
|
|
26
|
+
}
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
function c(i) {
|
|
30
|
+
let s = a(), c = n();
|
|
31
|
+
return r({
|
|
32
|
+
mutationFn: async (n) => {
|
|
33
|
+
if (!i.agentId) throw new e("INVALID", 0, "no agent opts");
|
|
34
|
+
let r = {
|
|
35
|
+
agentId: i.agentId,
|
|
36
|
+
profile: i.profile,
|
|
23
37
|
apolloClient: c
|
|
24
38
|
};
|
|
25
|
-
return t.
|
|
39
|
+
return t.startScan(r, n);
|
|
40
|
+
},
|
|
41
|
+
onSuccess: () => {
|
|
42
|
+
s.invalidateQueries({ queryKey: o(i) });
|
|
26
43
|
}
|
|
27
44
|
});
|
|
28
45
|
}
|
|
29
46
|
//#endregion
|
|
30
|
-
export {
|
|
47
|
+
export { s as useSecurityRecentScansQuery, c as useStartSecurityScanMutation };
|
|
31
48
|
|
|
32
49
|
//# sourceMappingURL=hooks.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hooks.js","names":[],"sources":["../../../src/components/security/hooks.ts"],"sourcesContent":["/**\n * React Query hooks over the security agent GraphQL proxy.\n *\n * F4 Wave C: rewired off the direct browser→agent tunnel onto the svc\n * GraphQL proxy (`agentSecurityScanProxy` / `agentSecurityScanMutate`).\n * Cache keys still include the agent identity (agentId + profile) so\n * two simultaneously-active vibes against different agents stay\n * isolated. Mirrors the layout of `src/hooks/useGitops.ts`.\n */\nimport {\n useMutation,\n useQuery,\n useQueryClient,\n type UseMutationResult,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { useApolloClient } from '@apollo/client/react';\n\nimport {\n securityAgent,\n SecurityAgentError,\n type FindingsFilter,\n type FindingsPage,\n type SecurityAgentOpts,\n type SecurityProvidersByStage,\n type SecurityScanRun,\n type StartScanInput,\n type StartScanResult,\n} from './agentClient';\n\nexport interface UseSecurityAgentContext {\n /** Required: canonical profile name. */\n profile: string;\n /** Required: agent id — scopes cache + passes through to svc proxy. */\n agentId: string | null;\n}\n\nfunction key(ctx: UseSecurityAgentContext, ...rest: unknown[]): unknown[] {\n return ['security', ctx.agentId ?? '_', ctx.profile, ...rest];\n}\n\nexport function useSecurityProvidersQuery(\n ctx: UseSecurityAgentContext\n): UseQueryResult<SecurityProvidersByStage, SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'providers'),\n enabled: !!ctx.agentId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listProviders(opts);\n },\n });\n}\n\nexport function useSecurityFindingsQuery(\n ctx: UseSecurityAgentContext,\n filter: FindingsFilter\n): UseQueryResult<FindingsPage, SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'findings', filter.vibeId, filter.severity, filter.status, filter.limit),\n enabled: !!ctx.agentId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listFindings(opts, filter);\n },\n });\n}\n\nexport function useSecurityRecentScansQuery(\n ctx: UseSecurityAgentContext,\n vibeId: string,\n limit = 20\n): UseQueryResult<SecurityScanRun[], SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'recent-scans', vibeId, limit),\n enabled: !!ctx.agentId && !!vibeId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listRecentScanRuns(opts, vibeId, limit);\n },\n });\n}\n\nexport function useStartSecurityScanMutation(\n ctx: UseSecurityAgentContext\n): UseMutationResult<StartScanResult, SecurityAgentError, StartScanInput> {\n const qc = useQueryClient();\n const apolloClient = useApolloClient();\n return useMutation({\n mutationFn: async (input: StartScanInput) => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.startScan(opts, input);\n },\n onSuccess: () => {\n void qc.invalidateQueries({ queryKey: key(ctx) });\n },\n });\n}\n"],"mappings":";;;;AAqCA,SAAS,EAAI,GAA8B,GAAG,GAA4B;AACxE,QAAO;EAAC;EAAY,EAAI,WAAW;EAAK,EAAI;EAAS,GAAG;EAAK;;AAkC/D,SAAgB,EACd,GACA,GACA,IAAQ,IAC+C;CACvD,IAAM,IAAe,GAAiB;AACtC,QAAO,EAAS;EACd,UAAU,EAAI,GAAK,gBAAgB,GAAQ,EAAM;EACjD,SAAS,CAAC,CAAC,EAAI,WAAW,CAAC,CAAC;EAC5B,SAAS,YAAY;AACnB,OAAI,CAAC,EAAI,QAAS,OAAM,IAAI,EAAmB,WAAW,GAAG,gBAAgB;GAC7E,IAAM,IAA0B;IAAE,SAAS,EAAI;IAAS,SAAS,EAAI;IAAS;IAAc;AAC5F,UAAO,EAAc,mBAAmB,GAAM,GAAQ,EAAM;;EAE/D,CAAC"}
|
|
1
|
+
{"version":3,"file":"hooks.js","names":[],"sources":["../../../src/components/security/hooks.ts"],"sourcesContent":["/**\n * React Query hooks over the security agent GraphQL proxy.\n *\n * F4 Wave C: rewired off the direct browser→agent tunnel onto the svc\n * GraphQL proxy (`agentSecurityScanProxy` / `agentSecurityScanMutate`).\n * Cache keys still include the agent identity (agentId + profile) so\n * two simultaneously-active vibes against different agents stay\n * isolated. Mirrors the layout of `src/hooks/useGitops.ts`.\n */\nimport {\n useMutation,\n useQuery,\n useQueryClient,\n type UseMutationResult,\n type UseQueryResult,\n} from '@tanstack/react-query';\nimport { useApolloClient } from '@apollo/client/react';\n\nimport {\n securityAgent,\n SecurityAgentError,\n type FindingsFilter,\n type FindingsPage,\n type SecurityAgentOpts,\n type SecurityProvidersByStage,\n type SecurityScanRun,\n type StartScanInput,\n type StartScanResult,\n} from './agentClient';\n\nexport interface UseSecurityAgentContext {\n /** Required: canonical profile name. */\n profile: string;\n /** Required: agent id — scopes cache + passes through to svc proxy. */\n agentId: string | null;\n}\n\nfunction key(ctx: UseSecurityAgentContext, ...rest: unknown[]): unknown[] {\n return ['security', ctx.agentId ?? '_', ctx.profile, ...rest];\n}\n\nexport function useSecurityProvidersQuery(\n ctx: UseSecurityAgentContext\n): UseQueryResult<SecurityProvidersByStage, SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'providers'),\n enabled: !!ctx.agentId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listProviders(opts);\n },\n });\n}\n\nexport function useSecurityFindingsQuery(\n ctx: UseSecurityAgentContext,\n filter: FindingsFilter\n): UseQueryResult<FindingsPage, SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'findings', filter.vibeId, filter.severity, filter.status, filter.limit),\n enabled: !!ctx.agentId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listFindings(opts, filter);\n },\n });\n}\n\nexport function useSecurityRecentScansQuery(\n ctx: UseSecurityAgentContext,\n vibeId: string,\n limit = 20\n): UseQueryResult<SecurityScanRun[], SecurityAgentError> {\n const apolloClient = useApolloClient();\n return useQuery({\n queryKey: key(ctx, 'recent-scans', vibeId, limit),\n enabled: !!ctx.agentId && !!vibeId,\n queryFn: async () => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.listRecentScanRuns(opts, vibeId, limit);\n },\n });\n}\n\nexport function useStartSecurityScanMutation(\n ctx: UseSecurityAgentContext\n): UseMutationResult<StartScanResult, SecurityAgentError, StartScanInput> {\n const qc = useQueryClient();\n const apolloClient = useApolloClient();\n return useMutation({\n mutationFn: async (input: StartScanInput) => {\n if (!ctx.agentId) throw new SecurityAgentError('INVALID', 0, 'no agent opts');\n const opts: SecurityAgentOpts = { agentId: ctx.agentId, profile: ctx.profile, apolloClient };\n return securityAgent.startScan(opts, input);\n },\n onSuccess: () => {\n void qc.invalidateQueries({ queryKey: key(ctx) });\n },\n });\n}\n"],"mappings":";;;;AAqCA,SAAS,EAAI,GAA8B,GAAG,GAA4B;AACxE,QAAO;EAAC;EAAY,EAAI,WAAW;EAAK,EAAI;EAAS,GAAG;EAAK;;AAkC/D,SAAgB,EACd,GACA,GACA,IAAQ,IAC+C;CACvD,IAAM,IAAe,GAAiB;AACtC,QAAO,EAAS;EACd,UAAU,EAAI,GAAK,gBAAgB,GAAQ,EAAM;EACjD,SAAS,CAAC,CAAC,EAAI,WAAW,CAAC,CAAC;EAC5B,SAAS,YAAY;AACnB,OAAI,CAAC,EAAI,QAAS,OAAM,IAAI,EAAmB,WAAW,GAAG,gBAAgB;GAC7E,IAAM,IAA0B;IAAE,SAAS,EAAI;IAAS,SAAS,EAAI;IAAS;IAAc;AAC5F,UAAO,EAAc,mBAAmB,GAAM,GAAQ,EAAM;;EAE/D,CAAC;;AAGJ,SAAgB,EACd,GACwE;CACxE,IAAM,IAAK,GAAgB,EACrB,IAAe,GAAiB;AACtC,QAAO,EAAY;EACjB,YAAY,OAAO,MAA0B;AAC3C,OAAI,CAAC,EAAI,QAAS,OAAM,IAAI,EAAmB,WAAW,GAAG,gBAAgB;GAC7E,IAAM,IAA0B;IAAE,SAAS,EAAI;IAAS,SAAS,EAAI;IAAS;IAAc;AAC5F,UAAO,EAAc,UAAU,GAAM,EAAM;;EAE7C,iBAAiB;AACV,KAAG,kBAAkB,EAAE,UAAU,EAAI,EAAI,EAAE,CAAC;;EAEpD,CAAC"}
|
|
@@ -30,6 +30,21 @@ var n = "@vibecontrols/vibe-plugin-session-manager", r = [
|
|
|
30
30
|
"@vibecontrols/vibe-plugin-security-incident",
|
|
31
31
|
"@vibecontrols/vibe-plugin-security-archive"
|
|
32
32
|
], d = {
|
|
33
|
+
"@vibecontrols/vibe-plugin-security-onboard": "repo.onboard",
|
|
34
|
+
"@vibecontrols/vibe-plugin-security-developer-local": "developer.local",
|
|
35
|
+
"@vibecontrols/vibe-plugin-security-secrets-pr": "pull_request.fast",
|
|
36
|
+
"@vibecontrols/vibe-plugin-security-sast-deep": "pull_request.deep",
|
|
37
|
+
"@vibecontrols/vibe-plugin-security-scorecard": "main.merge",
|
|
38
|
+
"@vibecontrols/vibe-plugin-security-sbom-build": "build",
|
|
39
|
+
"@vibecontrols/vibe-plugin-security-package-publish": "package.publish",
|
|
40
|
+
"@vibecontrols/vibe-plugin-security-dast-preview": "deploy.preview",
|
|
41
|
+
"@vibecontrols/vibe-plugin-security-deploy-alpha": "deploy.alpha",
|
|
42
|
+
"@vibecontrols/vibe-plugin-security-release-gate": "promote.prod",
|
|
43
|
+
"@vibecontrols/vibe-plugin-security-runtime": "runtime.continuous",
|
|
44
|
+
"@vibecontrols/vibe-plugin-security-rescan": "scheduled.rescan",
|
|
45
|
+
"@vibecontrols/vibe-plugin-security-incident": "incident.response",
|
|
46
|
+
"@vibecontrols/vibe-plugin-security-archive": "archive.offboard"
|
|
47
|
+
}, f = {
|
|
33
48
|
[e]: {
|
|
34
49
|
packageName: e,
|
|
35
50
|
displayName: "AI Orchestrator",
|
|
@@ -301,8 +316,8 @@ var n = "@vibecontrols/vibe-plugin-session-manager", r = [
|
|
|
301
316
|
category: "security-provider"
|
|
302
317
|
}
|
|
303
318
|
};
|
|
304
|
-
function
|
|
305
|
-
return
|
|
319
|
+
function p(e) {
|
|
320
|
+
return f[e] ?? {
|
|
306
321
|
packageName: e,
|
|
307
322
|
displayName: e.replace("@vibecontrols/vibe-plugin-", ""),
|
|
308
323
|
description: "VibeControls agent plugin.",
|
|
@@ -310,6 +325,6 @@ function f(e) {
|
|
|
310
325
|
};
|
|
311
326
|
}
|
|
312
327
|
//#endregion
|
|
313
|
-
export { e as AI_CORE_PACKAGE, t as AI_PROVIDER_PACKAGES, o as GIT_PACKAGE, s as GRAPHQL_PLAYGROUND_PACKAGE, i as PLAN_META_PACKAGE, a as PLAN_PLANNOTATOR_PACKAGE,
|
|
328
|
+
export { e as AI_CORE_PACKAGE, t as AI_PROVIDER_PACKAGES, o as GIT_PACKAGE, s as GRAPHQL_PLAYGROUND_PACKAGE, i as PLAN_META_PACKAGE, a as PLAN_PLANNOTATOR_PACKAGE, f as PLUGIN_CATALOG, l as SECURITY_META_PACKAGE, u as SECURITY_PROVIDER_PACKAGES, d as SECURITY_STAGE_BY_PACKAGE, n as SESSION_MANAGER_PACKAGE, r as SESSION_PROVIDER_PACKAGES, p as getPluginMeta };
|
|
314
329
|
|
|
315
330
|
//# sourceMappingURL=pluginCatalog.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pluginCatalog.js","names":[],"sources":["../../src/constants/pluginCatalog.ts"],"sourcesContent":["/**\n * Plugin catalog — UI-facing metadata for vibecontrols-agent plugins.\n *\n * Source of truth for install state is the agent's profile-prefixed\n * `/api/profiles/<profile>/plugins/available` endpoint (proxied via\n * GraphQL `agentPlugins`). This module only mirrors the display\n * metadata the UI needs when prompting a user to install.\n */\n\nexport interface PluginMeta {\n packageName: string;\n displayName: string;\n description: string;\n category:\n | 'ai-core'\n | 'ai-provider'\n | 'plan-core'\n | 'plan-provider'\n | 'session-core'\n | 'session-provider'\n | 'tunnel'\n | 'vcs'\n | 'api-playground'\n | 'gitops-core'\n | 'gitops-provider'\n | 'security-core'\n | 'security-provider'\n | 'tool';\n}\n\n// ── AI ─────────────────────────────────────────────────────────────────────\n\nexport const AI_CORE_PACKAGE = '@vibecontrols/vibe-plugin-ai';\n\n/**\n * Supported AI provider harnesses. Eight orchestrator-backed providers we\n * ship + validate end-to-end (claude, codex, gemini, opencode + cursor,\n * openrouter, minimax, ollama added 2026.507). The wider set in\n * `PLUGIN_CATALOG` below is retained for `getPluginMeta` lookups so legacy\n * installs still resolve display metadata, but the picker + gate only\n * offer the supported eight. See ~/products/dev/platform/context/ai-plugins/\n * PROVIDERS.md for the full status matrix.\n */\nexport const AI_PROVIDER_PACKAGES = [\n '@vibecontrols/vibe-plugin-ai-claude',\n '@vibecontrols/vibe-plugin-ai-codex',\n '@vibecontrols/vibe-plugin-ai-gemini',\n '@vibecontrols/vibe-plugin-ai-opencode',\n '@vibecontrols/vibe-plugin-ai-cursor',\n '@vibecontrols/vibe-plugin-ai-openrouter',\n '@vibecontrols/vibe-plugin-ai-minimax',\n '@vibecontrols/vibe-plugin-ai-ollama',\n] as const;\n\n/**\n * Full set of supported AI packages including the orchestrator. Useful for\n * server-side allow-listing or detection scans that need to recognise an\n * installed plugin even if the UI doesn't currently offer it.\n */\nexport const SUPPORTED_AI_PACKAGES = [AI_CORE_PACKAGE, ...AI_PROVIDER_PACKAGES] as const;\n\n// ── Sessions ───────────────────────────────────────────────────────────────\n\nexport const SESSION_MANAGER_PACKAGE = '@vibecontrols/vibe-plugin-session-manager';\n\nexport const SESSION_PROVIDER_PACKAGES = [\n '@vibecontrols/vibe-plugin-session-tmux',\n '@vibecontrols/vibe-plugin-session-wezterm',\n '@vibecontrols/vibe-plugin-session-zellij',\n] as const;\n\n// ── Plan ──────────────────────────────────────────────────────────────────\n\nexport const PLAN_META_PACKAGE = '@vibecontrols/vibe-plugin-plan';\nexport const PLAN_PLANNOTATOR_PACKAGE = '@vibecontrols/vibe-plugin-plan-plannotator';\n\nexport const PLAN_PROVIDER_PACKAGES = [PLAN_PLANNOTATOR_PACKAGE] as const;\n\n// ── VCS & API Playground ───────────────────────────────────────────────────\n\nexport const GIT_PACKAGE = '@vibecontrols/vibe-plugin-tool-git';\nexport const GRAPHQL_PLAYGROUND_PACKAGE = '@vibecontrols/vibe-plugin-tool-graphiql';\n\n// ── GitOps ─────────────────────────────────────────────────────────────────\n\nexport const GITOPS_META_PACKAGE = '@vibecontrols/vibe-plugin-gitops';\n\nexport const GITOPS_PROVIDER_PACKAGES = [\n '@vibecontrols/vibe-plugin-gitops-github',\n '@vibecontrols/vibe-plugin-gitops-gitlab',\n '@vibecontrols/vibe-plugin-gitops-bitbucket',\n '@vibecontrols/vibe-plugin-gitops-azdevops',\n] as const;\n\n// ── Security ───────────────────────────────────────────────────────────────\n\nexport const SECURITY_META_PACKAGE = '@vibecontrols/vibe-plugin-security';\n\n/**\n * Per-stage security provider plugins. Each entry maps a stage from\n * the 14-stage security lifecycle (see vibecontrols-specs/security/)\n * to its provider package. Three are published today (secrets-pr,\n * sbom-build, release-gate); the other 11 are placeholders that will\n * be created in upcoming waves of the security plugin rollout.\n */\nexport const SECURITY_PROVIDER_PACKAGES = [\n '@vibecontrols/vibe-plugin-security-onboard',\n '@vibecontrols/vibe-plugin-security-developer-local',\n '@vibecontrols/vibe-plugin-security-secrets-pr',\n '@vibecontrols/vibe-plugin-security-sast-deep',\n '@vibecontrols/vibe-plugin-security-scorecard',\n '@vibecontrols/vibe-plugin-security-sbom-build',\n '@vibecontrols/vibe-plugin-security-package-publish',\n '@vibecontrols/vibe-plugin-security-dast-preview',\n '@vibecontrols/vibe-plugin-security-deploy-alpha',\n '@vibecontrols/vibe-plugin-security-release-gate',\n '@vibecontrols/vibe-plugin-security-runtime',\n '@vibecontrols/vibe-plugin-security-rescan',\n '@vibecontrols/vibe-plugin-security-incident',\n '@vibecontrols/vibe-plugin-security-archive',\n] as const;\n\n/**\n * Maps each security provider package to the lifecycle stage it\n * registers against. The stage strings match the SecurityStage enum\n * in @vibecontrols/vibe-plugin-security/types.\n */\nexport const SECURITY_STAGE_BY_PACKAGE: Record<string, string> = {\n '@vibecontrols/vibe-plugin-security-onboard': 'repo.onboard',\n '@vibecontrols/vibe-plugin-security-developer-local': 'developer.local',\n '@vibecontrols/vibe-plugin-security-secrets-pr': 'pull_request.fast',\n '@vibecontrols/vibe-plugin-security-sast-deep': 'pull_request.deep',\n '@vibecontrols/vibe-plugin-security-scorecard': 'main.merge',\n '@vibecontrols/vibe-plugin-security-sbom-build': 'build',\n '@vibecontrols/vibe-plugin-security-package-publish': 'package.publish',\n '@vibecontrols/vibe-plugin-security-dast-preview': 'deploy.preview',\n '@vibecontrols/vibe-plugin-security-deploy-alpha': 'deploy.alpha',\n '@vibecontrols/vibe-plugin-security-release-gate': 'promote.prod',\n '@vibecontrols/vibe-plugin-security-runtime': 'runtime.continuous',\n '@vibecontrols/vibe-plugin-security-rescan': 'scheduled.rescan',\n '@vibecontrols/vibe-plugin-security-incident': 'incident.response',\n '@vibecontrols/vibe-plugin-security-archive': 'archive.offboard',\n};\n\n// ── Metadata map ───────────────────────────────────────────────────────────\n\nexport const PLUGIN_CATALOG: Record<string, PluginMeta> = {\n [AI_CORE_PACKAGE]: {\n packageName: AI_CORE_PACKAGE,\n displayName: 'AI Orchestrator',\n description: 'Routes prompts and sessions across installed AI provider plugins.',\n category: 'ai-core',\n },\n '@vibecontrols/vibe-plugin-ai-claude': {\n packageName: '@vibecontrols/vibe-plugin-ai-claude',\n displayName: 'Claude Code',\n description: 'Anthropic Claude Code agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-codex': {\n packageName: '@vibecontrols/vibe-plugin-ai-codex',\n displayName: 'OpenAI Codex',\n description: 'OpenAI Codex CLI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-copilot': {\n packageName: '@vibecontrols/vibe-plugin-ai-copilot',\n displayName: 'GitHub Copilot',\n description: 'GitHub Copilot agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-gemini': {\n packageName: '@vibecontrols/vibe-plugin-ai-gemini',\n displayName: 'Google Gemini',\n description: 'Google Gemini / Vertex AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-opencode': {\n packageName: '@vibecontrols/vibe-plugin-ai-opencode',\n displayName: 'OpenCode',\n description: 'OpenCode agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-cursor': {\n packageName: '@vibecontrols/vibe-plugin-ai-cursor',\n displayName: 'Cursor',\n description: 'Cursor agent provider (CLI + SDK).',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-openrouter': {\n packageName: '@vibecontrols/vibe-plugin-ai-openrouter',\n displayName: 'OpenRouter',\n description: 'OpenRouter aggregator (300+ models, SDK only).',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-minimax': {\n packageName: '@vibecontrols/vibe-plugin-ai-minimax',\n displayName: 'Minimax',\n description: 'Minimax provider (CLI + Anthropic-compatible SDK).',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-ollama': {\n packageName: '@vibecontrols/vibe-plugin-ai-ollama',\n displayName: 'Ollama',\n description: 'Ollama Cloud + self-hosted (CLI + SDK).',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-aider': {\n packageName: '@vibecontrols/vibe-plugin-ai-aider',\n displayName: 'Aider',\n description: 'Aider AI pair-programmer provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-crush': {\n packageName: '@vibecontrols/vibe-plugin-ai-crush',\n displayName: 'Crush',\n description: 'Crush AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-goose': {\n packageName: '@vibecontrols/vibe-plugin-ai-goose',\n displayName: 'Goose',\n description: 'Block Goose AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-pi': {\n packageName: '@vibecontrols/vibe-plugin-ai-pi',\n displayName: 'Pi',\n description: 'Pi AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-plandex': {\n packageName: '@vibecontrols/vibe-plugin-ai-plandex',\n displayName: 'Plandex',\n description: 'Plandex AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-amp': {\n packageName: '@vibecontrols/vibe-plugin-ai-amp',\n displayName: 'Amp',\n description: 'Sourcegraph Amp AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-openai-compat': {\n packageName: '@vibecontrols/vibe-plugin-ai-openai-compat',\n displayName: 'OpenAI-compatible',\n description: 'Bring-your-own OpenAI-compatible endpoint provider.',\n category: 'ai-provider',\n },\n [SESSION_MANAGER_PACKAGE]: {\n packageName: SESSION_MANAGER_PACKAGE,\n displayName: 'Session Manager',\n description: 'Unified session manager that routes across tmux / WezTerm / Zellij.',\n category: 'session-core',\n },\n '@vibecontrols/vibe-plugin-session-tmux': {\n packageName: '@vibecontrols/vibe-plugin-session-tmux',\n displayName: 'tmux',\n description: 'tmux terminal multiplexer provider (lightweight, universal).',\n category: 'session-provider',\n },\n '@vibecontrols/vibe-plugin-session-wezterm': {\n packageName: '@vibecontrols/vibe-plugin-session-wezterm',\n displayName: 'WezTerm',\n description: 'WezTerm multiplexer provider with rich rendering.',\n category: 'session-provider',\n },\n '@vibecontrols/vibe-plugin-session-zellij': {\n packageName: '@vibecontrols/vibe-plugin-session-zellij',\n displayName: 'Zellij',\n description: 'Zellij multiplexer provider with workspace layouts.',\n category: 'session-provider',\n },\n [GIT_PACKAGE]: {\n packageName: GIT_PACKAGE,\n displayName: 'Visual Git (Ungit)',\n description: 'Ungit visual git client. Required for the Git UI sub-tab.',\n category: 'vcs',\n },\n [GRAPHQL_PLAYGROUND_PACKAGE]: {\n packageName: GRAPHQL_PLAYGROUND_PACKAGE,\n displayName: 'GraphQL Playground',\n description: 'GraphiQL-powered GraphQL playground.',\n category: 'api-playground',\n },\n [PLAN_META_PACKAGE]: {\n packageName: PLAN_META_PACKAGE,\n displayName: 'Plan Orchestrator',\n description: 'Routes plan sessions to a registered provider (Plannotator etc.).',\n category: 'plan-core',\n },\n [PLAN_PLANNOTATOR_PACKAGE]: {\n packageName: PLAN_PLANNOTATOR_PACKAGE,\n displayName: 'Plannotator',\n description: 'Wraps the upstream plannotator CLI for plan review inside vibecontrols.',\n category: 'plan-provider',\n },\n [GITOPS_META_PACKAGE]: {\n packageName: GITOPS_META_PACKAGE,\n displayName: 'GitOps Orchestrator',\n description:\n 'Routes repo / PR / CI / security queries to a registered provider (GitHub, GitLab, Bitbucket, Azure DevOps).',\n category: 'gitops-core',\n },\n '@vibecontrols/vibe-plugin-gitops-github': {\n packageName: '@vibecontrols/vibe-plugin-gitops-github',\n displayName: 'GitHub',\n description: 'GitHub provider for GitOps (REST v3 + GraphQL v4).',\n category: 'gitops-provider',\n },\n '@vibecontrols/vibe-plugin-gitops-gitlab': {\n packageName: '@vibecontrols/vibe-plugin-gitops-gitlab',\n displayName: 'GitLab',\n description: 'GitLab provider for GitOps (REST v4, supports self-hosted).',\n category: 'gitops-provider',\n },\n '@vibecontrols/vibe-plugin-gitops-bitbucket': {\n packageName: '@vibecontrols/vibe-plugin-gitops-bitbucket',\n displayName: 'Bitbucket Cloud',\n description: 'Bitbucket Cloud provider for GitOps (REST v2).',\n category: 'gitops-provider',\n },\n '@vibecontrols/vibe-plugin-gitops-azdevops': {\n packageName: '@vibecontrols/vibe-plugin-gitops-azdevops',\n displayName: 'Azure DevOps',\n description: 'Azure DevOps Services provider for GitOps (REST 7.1).',\n category: 'gitops-provider',\n },\n [SECURITY_META_PACKAGE]: {\n packageName: SECURITY_META_PACKAGE,\n displayName: 'Security Orchestrator',\n description: 'Routes security scans across installed per-stage security provider plugins.',\n category: 'security-core',\n },\n '@vibecontrols/vibe-plugin-security-onboard': {\n packageName: '@vibecontrols/vibe-plugin-security-onboard',\n displayName: 'Repo Onboard',\n description:\n 'Detects repo profile (frontend/backend/cli/mcp/...) and sets default security policy.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-developer-local': {\n packageName: '@vibecontrols/vibe-plugin-security-developer-local',\n displayName: 'Developer Local',\n description: 'Pre-commit Gitleaks + Semgrep --quick on the developer machine.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-secrets-pr': {\n packageName: '@vibecontrols/vibe-plugin-security-secrets-pr',\n displayName: 'Gitleaks (PR Secrets)',\n description: 'Gitleaks-backed secret scanner for the pull_request.fast lifecycle stage.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-sast-deep': {\n packageName: '@vibecontrols/vibe-plugin-security-sast-deep',\n displayName: 'Semgrep + OSV (Deep PR)',\n description: 'Full Semgrep + osv-scanner sweep for the pull_request.deep lifecycle stage.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-scorecard': {\n packageName: '@vibecontrols/vibe-plugin-security-scorecard',\n displayName: 'OpenSSF Scorecard',\n description:\n 'OpenSSF Scorecard checks at main.merge — branch protection, signed commits, dep update tools.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-sbom-build': {\n packageName: '@vibecontrols/vibe-plugin-security-sbom-build',\n displayName: 'Syft + Grype (SBOM Build)',\n description: 'Generates CycloneDX SBOM with Syft, scans it with Grype at the build stage.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-package-publish': {\n packageName: '@vibecontrols/vibe-plugin-security-package-publish',\n displayName: 'Cosign + SLSA (Publish)',\n description: 'Cosign signing + SLSA provenance generator for the package.publish stage.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-dast-preview': {\n packageName: '@vibecontrols/vibe-plugin-security-dast-preview',\n displayName: 'OWASP ZAP (DAST Preview)',\n description: 'OWASP ZAP baseline scan against preview deploy URL.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-deploy-alpha': {\n packageName: '@vibecontrols/vibe-plugin-security-deploy-alpha',\n displayName: 'Alpha Deploy Smoke',\n description: 'TLS + security-header smoke checks against the alpha environment.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-release-gate': {\n packageName: '@vibecontrols/vibe-plugin-security-release-gate',\n displayName: 'OPA Release Gate',\n description:\n 'Agent-local policy decision for the promote.prod stage — blocks releases that violate the gate.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-runtime': {\n packageName: '@vibecontrols/vibe-plugin-security-runtime',\n displayName: 'Runtime (Trivy + kube-bench)',\n description: 'Continuous runtime checks — image drift, K8s misconfig.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-rescan': {\n packageName: '@vibecontrols/vibe-plugin-security-rescan',\n displayName: 'Scheduled Rescan',\n description: 'Nightly Grype rescan with EPSS enrichment against the latest SBOM.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-incident': {\n packageName: '@vibecontrols/vibe-plugin-security-incident',\n displayName: 'Incident Response',\n description: 'Targeted CVE + secret blast-radius scan for active incidents.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-archive': {\n packageName: '@vibecontrols/vibe-plugin-security-archive',\n displayName: 'Archive / Offboard',\n description:\n 'Writes a retention tombstone and cleans up local security cache when a vibe is archived.',\n category: 'security-provider',\n },\n};\n\nexport function getPluginMeta(packageName: string): PluginMeta {\n return (\n PLUGIN_CATALOG[packageName] ?? {\n packageName,\n displayName: packageName.replace('@vibecontrols/vibe-plugin-', ''),\n description: 'VibeControls agent plugin.',\n category: 'tool',\n }\n );\n}\n"],"mappings":";AAgCA,IAAa,IAAkB,gCAWlB,IAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAOoC,CAAkB,GAAG,EAAqB;AAI/E,IAAa,IAA0B,6CAE1B,IAA4B;CACvC;CACA;CACA;CACD,EAIY,IAAoB,kCACpB,IAA2B,8CAM3B,IAAc,sCACd,IAA6B,2CAI7B,IAAsB,oCAWtB,IAAwB,sCASxB,IAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EA0BY,IAA6C;EACvD,IAAkB;EACjB,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,uCAAuC;EACrC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sCAAsC;EACpC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,wCAAwC;EACtC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,uCAAuC;EACrC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,yCAAyC;EACvC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,uCAAuC;EACrC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,2CAA2C;EACzC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,wCAAwC;EACtC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,uCAAuC;EACrC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sCAAsC;EACpC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sCAAsC;EACpC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sCAAsC;EACpC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,mCAAmC;EACjC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,wCAAwC;EACtC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,oCAAoC;EAClC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAA0B;EACzB,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,0CAA0C;EACxC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,6CAA6C;EAC3C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,4CAA4C;EAC1C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAAc;EACb,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAA6B;EAC5B,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAAoB;EACnB,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAA2B;EAC1B,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAAsB;EACrB,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACD,2CAA2C;EACzC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,2CAA2C;EACzC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,6CAA6C;EAC3C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAAwB;EACvB,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACD,sDAAsD;EACpD,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,iDAAiD;EAC/C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,gDAAgD;EAC9C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,gDAAgD;EAC9C,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACD,iDAAiD;EAC/C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sDAAsD;EACpD,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,mDAAmD;EACjD,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,mDAAmD;EACjD,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,mDAAmD;EACjD,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,6CAA6C;EAC3C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,+CAA+C;EAC7C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACF;AAED,SAAgB,EAAc,GAAiC;AAC7D,QACE,EAAe,MAAgB;EAC7B;EACA,aAAa,EAAY,QAAQ,8BAA8B,GAAG;EAClE,aAAa;EACb,UAAU;EACX"}
|
|
1
|
+
{"version":3,"file":"pluginCatalog.js","names":[],"sources":["../../src/constants/pluginCatalog.ts"],"sourcesContent":["/**\n * Plugin catalog — UI-facing metadata for vibecontrols-agent plugins.\n *\n * Source of truth for install state is the agent's profile-prefixed\n * `/api/profiles/<profile>/plugins/available` endpoint (proxied via\n * GraphQL `agentPlugins`). This module only mirrors the display\n * metadata the UI needs when prompting a user to install.\n */\n\nexport interface PluginMeta {\n packageName: string;\n displayName: string;\n description: string;\n category:\n | 'ai-core'\n | 'ai-provider'\n | 'plan-core'\n | 'plan-provider'\n | 'session-core'\n | 'session-provider'\n | 'tunnel'\n | 'vcs'\n | 'api-playground'\n | 'gitops-core'\n | 'gitops-provider'\n | 'security-core'\n | 'security-provider'\n | 'tool';\n}\n\n// ── AI ─────────────────────────────────────────────────────────────────────\n\nexport const AI_CORE_PACKAGE = '@vibecontrols/vibe-plugin-ai';\n\n/**\n * Supported AI provider harnesses. Eight orchestrator-backed providers we\n * ship + validate end-to-end (claude, codex, gemini, opencode + cursor,\n * openrouter, minimax, ollama added 2026.507). The wider set in\n * `PLUGIN_CATALOG` below is retained for `getPluginMeta` lookups so legacy\n * installs still resolve display metadata, but the picker + gate only\n * offer the supported eight. See ~/products/dev/platform/context/ai-plugins/\n * PROVIDERS.md for the full status matrix.\n */\nexport const AI_PROVIDER_PACKAGES = [\n '@vibecontrols/vibe-plugin-ai-claude',\n '@vibecontrols/vibe-plugin-ai-codex',\n '@vibecontrols/vibe-plugin-ai-gemini',\n '@vibecontrols/vibe-plugin-ai-opencode',\n '@vibecontrols/vibe-plugin-ai-cursor',\n '@vibecontrols/vibe-plugin-ai-openrouter',\n '@vibecontrols/vibe-plugin-ai-minimax',\n '@vibecontrols/vibe-plugin-ai-ollama',\n] as const;\n\n/**\n * Full set of supported AI packages including the orchestrator. Useful for\n * server-side allow-listing or detection scans that need to recognise an\n * installed plugin even if the UI doesn't currently offer it.\n */\nexport const SUPPORTED_AI_PACKAGES = [AI_CORE_PACKAGE, ...AI_PROVIDER_PACKAGES] as const;\n\n// ── Sessions ───────────────────────────────────────────────────────────────\n\nexport const SESSION_MANAGER_PACKAGE = '@vibecontrols/vibe-plugin-session-manager';\n\nexport const SESSION_PROVIDER_PACKAGES = [\n '@vibecontrols/vibe-plugin-session-tmux',\n '@vibecontrols/vibe-plugin-session-wezterm',\n '@vibecontrols/vibe-plugin-session-zellij',\n] as const;\n\n// ── Plan ──────────────────────────────────────────────────────────────────\n\nexport const PLAN_META_PACKAGE = '@vibecontrols/vibe-plugin-plan';\nexport const PLAN_PLANNOTATOR_PACKAGE = '@vibecontrols/vibe-plugin-plan-plannotator';\n\nexport const PLAN_PROVIDER_PACKAGES = [PLAN_PLANNOTATOR_PACKAGE] as const;\n\n// ── VCS & API Playground ───────────────────────────────────────────────────\n\nexport const GIT_PACKAGE = '@vibecontrols/vibe-plugin-tool-git';\nexport const GRAPHQL_PLAYGROUND_PACKAGE = '@vibecontrols/vibe-plugin-tool-graphiql';\n\n// ── GitOps ─────────────────────────────────────────────────────────────────\n\nexport const GITOPS_META_PACKAGE = '@vibecontrols/vibe-plugin-gitops';\n\nexport const GITOPS_PROVIDER_PACKAGES = [\n '@vibecontrols/vibe-plugin-gitops-github',\n '@vibecontrols/vibe-plugin-gitops-gitlab',\n '@vibecontrols/vibe-plugin-gitops-bitbucket',\n '@vibecontrols/vibe-plugin-gitops-azdevops',\n] as const;\n\n// ── Security ───────────────────────────────────────────────────────────────\n\nexport const SECURITY_META_PACKAGE = '@vibecontrols/vibe-plugin-security';\n\n/**\n * Per-stage security provider plugins. Each entry maps a stage from\n * the 14-stage security lifecycle (see vibecontrols-specs/security/)\n * to its provider package. Three are published today (secrets-pr,\n * sbom-build, release-gate); the other 11 are placeholders that will\n * be created in upcoming waves of the security plugin rollout.\n */\nexport const SECURITY_PROVIDER_PACKAGES = [\n '@vibecontrols/vibe-plugin-security-onboard',\n '@vibecontrols/vibe-plugin-security-developer-local',\n '@vibecontrols/vibe-plugin-security-secrets-pr',\n '@vibecontrols/vibe-plugin-security-sast-deep',\n '@vibecontrols/vibe-plugin-security-scorecard',\n '@vibecontrols/vibe-plugin-security-sbom-build',\n '@vibecontrols/vibe-plugin-security-package-publish',\n '@vibecontrols/vibe-plugin-security-dast-preview',\n '@vibecontrols/vibe-plugin-security-deploy-alpha',\n '@vibecontrols/vibe-plugin-security-release-gate',\n '@vibecontrols/vibe-plugin-security-runtime',\n '@vibecontrols/vibe-plugin-security-rescan',\n '@vibecontrols/vibe-plugin-security-incident',\n '@vibecontrols/vibe-plugin-security-archive',\n] as const;\n\n/**\n * Maps each security provider package to the lifecycle stage it\n * registers against. The stage strings match the SecurityStage enum\n * in @vibecontrols/vibe-plugin-security/types.\n */\nexport const SECURITY_STAGE_BY_PACKAGE: Record<string, string> = {\n '@vibecontrols/vibe-plugin-security-onboard': 'repo.onboard',\n '@vibecontrols/vibe-plugin-security-developer-local': 'developer.local',\n '@vibecontrols/vibe-plugin-security-secrets-pr': 'pull_request.fast',\n '@vibecontrols/vibe-plugin-security-sast-deep': 'pull_request.deep',\n '@vibecontrols/vibe-plugin-security-scorecard': 'main.merge',\n '@vibecontrols/vibe-plugin-security-sbom-build': 'build',\n '@vibecontrols/vibe-plugin-security-package-publish': 'package.publish',\n '@vibecontrols/vibe-plugin-security-dast-preview': 'deploy.preview',\n '@vibecontrols/vibe-plugin-security-deploy-alpha': 'deploy.alpha',\n '@vibecontrols/vibe-plugin-security-release-gate': 'promote.prod',\n '@vibecontrols/vibe-plugin-security-runtime': 'runtime.continuous',\n '@vibecontrols/vibe-plugin-security-rescan': 'scheduled.rescan',\n '@vibecontrols/vibe-plugin-security-incident': 'incident.response',\n '@vibecontrols/vibe-plugin-security-archive': 'archive.offboard',\n};\n\n// ── Metadata map ───────────────────────────────────────────────────────────\n\nexport const PLUGIN_CATALOG: Record<string, PluginMeta> = {\n [AI_CORE_PACKAGE]: {\n packageName: AI_CORE_PACKAGE,\n displayName: 'AI Orchestrator',\n description: 'Routes prompts and sessions across installed AI provider plugins.',\n category: 'ai-core',\n },\n '@vibecontrols/vibe-plugin-ai-claude': {\n packageName: '@vibecontrols/vibe-plugin-ai-claude',\n displayName: 'Claude Code',\n description: 'Anthropic Claude Code agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-codex': {\n packageName: '@vibecontrols/vibe-plugin-ai-codex',\n displayName: 'OpenAI Codex',\n description: 'OpenAI Codex CLI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-copilot': {\n packageName: '@vibecontrols/vibe-plugin-ai-copilot',\n displayName: 'GitHub Copilot',\n description: 'GitHub Copilot agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-gemini': {\n packageName: '@vibecontrols/vibe-plugin-ai-gemini',\n displayName: 'Google Gemini',\n description: 'Google Gemini / Vertex AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-opencode': {\n packageName: '@vibecontrols/vibe-plugin-ai-opencode',\n displayName: 'OpenCode',\n description: 'OpenCode agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-cursor': {\n packageName: '@vibecontrols/vibe-plugin-ai-cursor',\n displayName: 'Cursor',\n description: 'Cursor agent provider (CLI + SDK).',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-openrouter': {\n packageName: '@vibecontrols/vibe-plugin-ai-openrouter',\n displayName: 'OpenRouter',\n description: 'OpenRouter aggregator (300+ models, SDK only).',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-minimax': {\n packageName: '@vibecontrols/vibe-plugin-ai-minimax',\n displayName: 'Minimax',\n description: 'Minimax provider (CLI + Anthropic-compatible SDK).',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-ollama': {\n packageName: '@vibecontrols/vibe-plugin-ai-ollama',\n displayName: 'Ollama',\n description: 'Ollama Cloud + self-hosted (CLI + SDK).',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-aider': {\n packageName: '@vibecontrols/vibe-plugin-ai-aider',\n displayName: 'Aider',\n description: 'Aider AI pair-programmer provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-crush': {\n packageName: '@vibecontrols/vibe-plugin-ai-crush',\n displayName: 'Crush',\n description: 'Crush AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-goose': {\n packageName: '@vibecontrols/vibe-plugin-ai-goose',\n displayName: 'Goose',\n description: 'Block Goose AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-pi': {\n packageName: '@vibecontrols/vibe-plugin-ai-pi',\n displayName: 'Pi',\n description: 'Pi AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-plandex': {\n packageName: '@vibecontrols/vibe-plugin-ai-plandex',\n displayName: 'Plandex',\n description: 'Plandex AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-amp': {\n packageName: '@vibecontrols/vibe-plugin-ai-amp',\n displayName: 'Amp',\n description: 'Sourcegraph Amp AI agent provider.',\n category: 'ai-provider',\n },\n '@vibecontrols/vibe-plugin-ai-openai-compat': {\n packageName: '@vibecontrols/vibe-plugin-ai-openai-compat',\n displayName: 'OpenAI-compatible',\n description: 'Bring-your-own OpenAI-compatible endpoint provider.',\n category: 'ai-provider',\n },\n [SESSION_MANAGER_PACKAGE]: {\n packageName: SESSION_MANAGER_PACKAGE,\n displayName: 'Session Manager',\n description: 'Unified session manager that routes across tmux / WezTerm / Zellij.',\n category: 'session-core',\n },\n '@vibecontrols/vibe-plugin-session-tmux': {\n packageName: '@vibecontrols/vibe-plugin-session-tmux',\n displayName: 'tmux',\n description: 'tmux terminal multiplexer provider (lightweight, universal).',\n category: 'session-provider',\n },\n '@vibecontrols/vibe-plugin-session-wezterm': {\n packageName: '@vibecontrols/vibe-plugin-session-wezterm',\n displayName: 'WezTerm',\n description: 'WezTerm multiplexer provider with rich rendering.',\n category: 'session-provider',\n },\n '@vibecontrols/vibe-plugin-session-zellij': {\n packageName: '@vibecontrols/vibe-plugin-session-zellij',\n displayName: 'Zellij',\n description: 'Zellij multiplexer provider with workspace layouts.',\n category: 'session-provider',\n },\n [GIT_PACKAGE]: {\n packageName: GIT_PACKAGE,\n displayName: 'Visual Git (Ungit)',\n description: 'Ungit visual git client. Required for the Git UI sub-tab.',\n category: 'vcs',\n },\n [GRAPHQL_PLAYGROUND_PACKAGE]: {\n packageName: GRAPHQL_PLAYGROUND_PACKAGE,\n displayName: 'GraphQL Playground',\n description: 'GraphiQL-powered GraphQL playground.',\n category: 'api-playground',\n },\n [PLAN_META_PACKAGE]: {\n packageName: PLAN_META_PACKAGE,\n displayName: 'Plan Orchestrator',\n description: 'Routes plan sessions to a registered provider (Plannotator etc.).',\n category: 'plan-core',\n },\n [PLAN_PLANNOTATOR_PACKAGE]: {\n packageName: PLAN_PLANNOTATOR_PACKAGE,\n displayName: 'Plannotator',\n description: 'Wraps the upstream plannotator CLI for plan review inside vibecontrols.',\n category: 'plan-provider',\n },\n [GITOPS_META_PACKAGE]: {\n packageName: GITOPS_META_PACKAGE,\n displayName: 'GitOps Orchestrator',\n description:\n 'Routes repo / PR / CI / security queries to a registered provider (GitHub, GitLab, Bitbucket, Azure DevOps).',\n category: 'gitops-core',\n },\n '@vibecontrols/vibe-plugin-gitops-github': {\n packageName: '@vibecontrols/vibe-plugin-gitops-github',\n displayName: 'GitHub',\n description: 'GitHub provider for GitOps (REST v3 + GraphQL v4).',\n category: 'gitops-provider',\n },\n '@vibecontrols/vibe-plugin-gitops-gitlab': {\n packageName: '@vibecontrols/vibe-plugin-gitops-gitlab',\n displayName: 'GitLab',\n description: 'GitLab provider for GitOps (REST v4, supports self-hosted).',\n category: 'gitops-provider',\n },\n '@vibecontrols/vibe-plugin-gitops-bitbucket': {\n packageName: '@vibecontrols/vibe-plugin-gitops-bitbucket',\n displayName: 'Bitbucket Cloud',\n description: 'Bitbucket Cloud provider for GitOps (REST v2).',\n category: 'gitops-provider',\n },\n '@vibecontrols/vibe-plugin-gitops-azdevops': {\n packageName: '@vibecontrols/vibe-plugin-gitops-azdevops',\n displayName: 'Azure DevOps',\n description: 'Azure DevOps Services provider for GitOps (REST 7.1).',\n category: 'gitops-provider',\n },\n [SECURITY_META_PACKAGE]: {\n packageName: SECURITY_META_PACKAGE,\n displayName: 'Security Orchestrator',\n description: 'Routes security scans across installed per-stage security provider plugins.',\n category: 'security-core',\n },\n '@vibecontrols/vibe-plugin-security-onboard': {\n packageName: '@vibecontrols/vibe-plugin-security-onboard',\n displayName: 'Repo Onboard',\n description:\n 'Detects repo profile (frontend/backend/cli/mcp/...) and sets default security policy.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-developer-local': {\n packageName: '@vibecontrols/vibe-plugin-security-developer-local',\n displayName: 'Developer Local',\n description: 'Pre-commit Gitleaks + Semgrep --quick on the developer machine.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-secrets-pr': {\n packageName: '@vibecontrols/vibe-plugin-security-secrets-pr',\n displayName: 'Gitleaks (PR Secrets)',\n description: 'Gitleaks-backed secret scanner for the pull_request.fast lifecycle stage.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-sast-deep': {\n packageName: '@vibecontrols/vibe-plugin-security-sast-deep',\n displayName: 'Semgrep + OSV (Deep PR)',\n description: 'Full Semgrep + osv-scanner sweep for the pull_request.deep lifecycle stage.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-scorecard': {\n packageName: '@vibecontrols/vibe-plugin-security-scorecard',\n displayName: 'OpenSSF Scorecard',\n description:\n 'OpenSSF Scorecard checks at main.merge — branch protection, signed commits, dep update tools.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-sbom-build': {\n packageName: '@vibecontrols/vibe-plugin-security-sbom-build',\n displayName: 'Syft + Grype (SBOM Build)',\n description: 'Generates CycloneDX SBOM with Syft, scans it with Grype at the build stage.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-package-publish': {\n packageName: '@vibecontrols/vibe-plugin-security-package-publish',\n displayName: 'Cosign + SLSA (Publish)',\n description: 'Cosign signing + SLSA provenance generator for the package.publish stage.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-dast-preview': {\n packageName: '@vibecontrols/vibe-plugin-security-dast-preview',\n displayName: 'OWASP ZAP (DAST Preview)',\n description: 'OWASP ZAP baseline scan against preview deploy URL.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-deploy-alpha': {\n packageName: '@vibecontrols/vibe-plugin-security-deploy-alpha',\n displayName: 'Alpha Deploy Smoke',\n description: 'TLS + security-header smoke checks against the alpha environment.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-release-gate': {\n packageName: '@vibecontrols/vibe-plugin-security-release-gate',\n displayName: 'OPA Release Gate',\n description:\n 'Agent-local policy decision for the promote.prod stage — blocks releases that violate the gate.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-runtime': {\n packageName: '@vibecontrols/vibe-plugin-security-runtime',\n displayName: 'Runtime (Trivy + kube-bench)',\n description: 'Continuous runtime checks — image drift, K8s misconfig.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-rescan': {\n packageName: '@vibecontrols/vibe-plugin-security-rescan',\n displayName: 'Scheduled Rescan',\n description: 'Nightly Grype rescan with EPSS enrichment against the latest SBOM.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-incident': {\n packageName: '@vibecontrols/vibe-plugin-security-incident',\n displayName: 'Incident Response',\n description: 'Targeted CVE + secret blast-radius scan for active incidents.',\n category: 'security-provider',\n },\n '@vibecontrols/vibe-plugin-security-archive': {\n packageName: '@vibecontrols/vibe-plugin-security-archive',\n displayName: 'Archive / Offboard',\n description:\n 'Writes a retention tombstone and cleans up local security cache when a vibe is archived.',\n category: 'security-provider',\n },\n};\n\nexport function getPluginMeta(packageName: string): PluginMeta {\n return (\n PLUGIN_CATALOG[packageName] ?? {\n packageName,\n displayName: packageName.replace('@vibecontrols/vibe-plugin-', ''),\n description: 'VibeControls agent plugin.',\n category: 'tool',\n }\n );\n}\n"],"mappings":";AAgCA,IAAa,IAAkB,gCAWlB,IAAuB;CAClC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD;AAOoC,CAAkB,GAAG,EAAqB;AAI/E,IAAa,IAA0B,6CAE1B,IAA4B;CACvC;CACA;CACA;CACD,EAIY,IAAoB,kCACpB,IAA2B,8CAM3B,IAAc,sCACd,IAA6B,2CAI7B,IAAsB,oCAWtB,IAAwB,sCASxB,IAA6B;CACxC;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACA;CACD,EAOY,IAAoD;CAC/D,8CAA8C;CAC9C,sDAAsD;CACtD,iDAAiD;CACjD,gDAAgD;CAChD,gDAAgD;CAChD,iDAAiD;CACjD,sDAAsD;CACtD,mDAAmD;CACnD,mDAAmD;CACnD,mDAAmD;CACnD,8CAA8C;CAC9C,6CAA6C;CAC7C,+CAA+C;CAC/C,8CAA8C;CAC/C,EAIY,IAA6C;EACvD,IAAkB;EACjB,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,uCAAuC;EACrC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sCAAsC;EACpC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,wCAAwC;EACtC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,uCAAuC;EACrC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,yCAAyC;EACvC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,uCAAuC;EACrC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,2CAA2C;EACzC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,wCAAwC;EACtC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,uCAAuC;EACrC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sCAAsC;EACpC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sCAAsC;EACpC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sCAAsC;EACpC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,mCAAmC;EACjC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,wCAAwC;EACtC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,oCAAoC;EAClC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAA0B;EACzB,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,0CAA0C;EACxC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,6CAA6C;EAC3C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,4CAA4C;EAC1C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAAc;EACb,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAA6B;EAC5B,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAAoB;EACnB,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAA2B;EAC1B,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAAsB;EACrB,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACD,2CAA2C;EACzC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,2CAA2C;EACzC,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,6CAA6C;EAC3C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;EACA,IAAwB;EACvB,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACD,sDAAsD;EACpD,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,iDAAiD;EAC/C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,gDAAgD;EAC9C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,gDAAgD;EAC9C,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACD,iDAAiD;EAC/C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,sDAAsD;EACpD,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,mDAAmD;EACjD,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,mDAAmD;EACjD,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,mDAAmD;EACjD,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,6CAA6C;EAC3C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,+CAA+C;EAC7C,aAAa;EACb,aAAa;EACb,aAAa;EACb,UAAU;EACX;CACD,8CAA8C;EAC5C,aAAa;EACb,aAAa;EACb,aACE;EACF,UAAU;EACX;CACF;AAED,SAAgB,EAAc,GAAiC;AAC7D,QACE,EAAe,MAAgB;EAC7B;EACA,aAAa,EAAY,QAAQ,8BAA8B,GAAG;EAClE,aAAa;EACb,UAAU;EACX"}
|