@burdenoff/microfe-vibecontrols 2026.526.1 → 2026.526.2

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.
@@ -1 +1 @@
1
- {"version":3,"file":"PluginGate.js","names":[],"sources":["../../../src/components/shared/PluginGate.tsx"],"sourcesContent":["/**\n * PluginGate — gates a UI surface on one or more vibecontrols-agent plugins\n * being installed. If missing, renders a card listing each required plugin\n * with an Install button. Once satisfied, renders children.\n *\n * Supports two modes:\n * - Single-group (backwards compat): `required` + `mode` + `title/description`.\n * - Multi-group: `requirements={[{packages, mode, ...}, ...]}` — one fetch\n * for all packages across all groups, unified loading state, stacked\n * install cards for any unsatisfied group. Prevents the \"flicker cascade\"\n * you get when nesting PluginGates (outer resolves, inner starts its own\n * initial-loading spinner, etc.).\n */\n\nimport { useCallback, useMemo, type ReactNode } from 'react';\nimport { Puzzle, Download, Loader2, AlertCircle, Server, RefreshCw, Clock } from 'lucide-react';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { QuotaExhaustedDialog } from './QuotaExhaustedDialog';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\n\nexport interface PluginRequirement {\n /** Package names that satisfy this requirement. */\n packages: readonly string[];\n /** 'all' (default) — every package must be installed. 'any' — at least one. */\n mode?: 'all' | 'any';\n /** Optional per-group eyebrow, title, description (fall back to defaults). */\n eyebrow?: string;\n title?: string;\n description?: string;\n}\n\nexport interface PluginGateProps {\n /** Active agent id. When null, gate shows an \"activate an agent\" prompt. */\n agentId: string | null;\n\n // ── Single-group API (backwards compat) ────────────────────────────────\n /** Package names required for the wrapped surface to render. */\n required?: readonly string[];\n /** 'all' (default) — every package must be installed. 'any' — at least one. */\n mode?: 'all' | 'any';\n /** Card heading. */\n title?: string;\n /** Card subheading / instruction line. */\n description?: string;\n /** Optional eyebrow label above the title (e.g. \"Plugin required\"). */\n eyebrow?: string;\n\n // ── Multi-group API (preferred when gating multiple requirements) ──────\n /** Multiple requirement groups — fetched + evaluated in one pass. */\n requirements?: readonly PluginRequirement[];\n\n /** Pass to render a compact version (smaller vertical padding). */\n compact?: boolean;\n /** Optional callback fired after install succeeds and gate resolves. */\n onInstalled?: () => void;\n children: ReactNode;\n}\n\ninterface ResolvedRequirement {\n packages: readonly string[];\n mode: 'all' | 'any';\n eyebrow?: string;\n title?: string;\n description?: string;\n}\n\nfunction resolveRequirements(props: PluginGateProps): ResolvedRequirement[] {\n if (props.requirements && props.requirements.length > 0) {\n return props.requirements.map((r) => ({\n packages: r.packages,\n mode: r.mode ?? 'all',\n eyebrow: r.eyebrow,\n title: r.title,\n description: r.description,\n }));\n }\n if (props.required && props.required.length > 0) {\n return [\n {\n packages: props.required,\n mode: props.mode ?? 'all',\n eyebrow: props.eyebrow,\n title: props.title,\n description: props.description,\n },\n ];\n }\n return [];\n}\n\nexport function PluginGate(props: PluginGateProps) {\n const { agentId, compact = false, onInstalled, children } = props;\n const { active: activeProfile } = useActiveAgentProfile(agentId);\n const resolved = useMemo(() => resolveRequirements(props), [props]);\n\n const allPackages = useMemo(() => {\n const set = new Set<string>();\n for (const req of resolved) {\n for (const pkg of req.packages) set.add(pkg);\n }\n return [...set];\n }, [resolved]);\n\n const {\n loading,\n initialLoading,\n installed,\n installing,\n install,\n error,\n errorIsAgentState,\n quotaExhausted,\n clearQuotaExhausted,\n refetch,\n } = usePluginAvailability(allPackages, agentId, activeProfile);\n\n const navigate = useVibeNavigate();\n\n // Per-requirement satisfaction.\n const requirementSatisfied = useMemo(\n () =>\n resolved.map((req) => {\n if (req.mode === 'any') return req.packages.some((p) => !!installed[p]);\n return req.packages.every((p) => !!installed[p]);\n }),\n [resolved, installed]\n );\n\n const allSatisfied = resolved.length > 0 && requirementSatisfied.every(Boolean);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) onInstalled?.();\n },\n [install, onInstalled]\n );\n\n // No agent available — universal precondition.\n if (!agentId) {\n return (\n <div\n className={`rounded-lg border border-border-default bg-bg-surface ${compact ? 'p-4' : 'p-6'}`}\n >\n <div className=\"flex items-start gap-3\">\n <Server className=\"h-5 w-5 text-text-secondary shrink-0 mt-0.5\" />\n <div className=\"flex-1 space-y-2\">\n <p className=\"text-sm font-semibold text-text-primary\">No active agent</p>\n <p className=\"text-sm text-text-secondary\">\n This surface needs an active vibecontrols agent with a tunnel URL and API key.\n </p>\n <button\n onClick={() => navigate('/agents')}\n className=\"px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n Go to Agents\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n if (allSatisfied) {\n return <>{children}</>;\n }\n\n // First-fetch loading state — one spinner for ALL requirements. Prevents\n // the cascade you get with nested PluginGates (outer resolves -> inner\n // starts a fresh initialLoading cycle). After the first fetch settles,\n // subsequent refetches (focus, install, retry) fall through to the\n // install card so context isn't replaced by a spinner.\n if (initialLoading) {\n return (\n <div\n className={`rounded-lg border border-border-default bg-bg-surface ${compact ? 'p-4' : 'p-6'}`}\n data-testid=\"plugin-gate-initial-loading\"\n >\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n Checking plugin availability on the agent…\n </div>\n </div>\n );\n }\n\n // Agent is not ready (initializing / awaiting-config / unreachable).\n // The install button would just fail with the same message — instead,\n // render a dedicated waiting state with a retry button and an escape\n // hatch to the Agents page.\n if (errorIsAgentState) {\n return (\n <div\n className={`rounded-lg border border-border-default bg-bg-surface ${compact ? 'p-4' : 'p-6'}`}\n >\n <div className=\"flex items-start gap-3\">\n <Clock className=\"h-5 w-5 text-status-warning-text shrink-0 mt-0.5\" />\n <div className=\"flex-1 min-w-0 space-y-2\">\n <p className=\"text-sm font-semibold text-text-primary\">Agent is not ready yet</p>\n <p className=\"text-sm text-text-secondary\">\n Your agent is still starting up or waiting for gateway credentials. Plugin install is\n only available once the agent reaches the ready state.\n </p>\n <p className=\"text-xs font-mono text-text-placeholder break-all\">{error}</p>\n <div className=\"flex flex-wrap items-center gap-2 pt-1\">\n <button\n onClick={() => void refetch()}\n disabled={loading}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n data-testid=\"plugin-gate-retry\"\n >\n <RefreshCw className={`h-3.5 w-3.5 ${loading ? 'animate-spin' : ''}`} />\n Retry\n </button>\n <button\n onClick={() => navigate('/agents')}\n className=\"rounded-md border border-border-default px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:border-border-strong transition-colors\"\n >\n Manage agents\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n // Stack one install card per unsatisfied requirement. The user sees every\n // outstanding dependency at once instead of resolving them one-by-one with\n // a re-render between each.\n return (\n <div className=\"space-y-3\">\n {resolved.map((req, idx) => {\n if (requirementSatisfied[idx]) return null;\n const packagesToOffer =\n req.mode === 'any'\n ? req.packages.filter((p) => !installed[p])\n : req.packages.filter((p) => !installed[p]);\n const defaultTitle =\n req.mode === 'any'\n ? 'Install a plugin to continue'\n : req.packages.length === 1\n ? 'Plugin required'\n : 'Plugins required';\n const defaultDescription =\n req.mode === 'any'\n ? 'Pick any one of the plugins below. Once installed, this surface will render automatically.'\n : req.packages.length === 1\n ? 'This surface needs the following agent plugin installed.'\n : 'This surface needs the following agent plugins installed.';\n\n return (\n <div\n key={idx}\n className={`rounded-lg border border-border-default bg-bg-surface ${compact ? 'p-4' : 'p-6'}`}\n >\n <div className=\"flex items-start gap-3\">\n <Puzzle className=\"h-5 w-5 text-action-primary-bg shrink-0 mt-0.5\" />\n <div className=\"flex-1 min-w-0\">\n {req.eyebrow && (\n <p className=\"text-xs uppercase tracking-wide text-text-secondary mb-1\">\n {req.eyebrow}\n </p>\n )}\n <p className=\"text-sm font-semibold text-text-primary\">\n {req.title ?? defaultTitle}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {req.description ?? defaultDescription}\n </p>\n\n {error && (\n <div\n role=\"alert\"\n className=\"mt-3 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n <AlertCircle className=\"h-3.5 w-3.5 mt-0.5 shrink-0\" />\n <span>{error}</span>\n </div>\n )}\n\n <ul className=\"mt-4 space-y-2\">\n {packagesToOffer.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalling = installing === pkg;\n const isBusy = installing !== null;\n return (\n <li\n key={pkg}\n className=\"flex items-center justify-between gap-3 rounded-md border border-border-default bg-bg-sunken px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {meta.displayName}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder truncate\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n <button\n onClick={() => void handleInstall(pkg)}\n disabled={isBusy || loading}\n className=\"inline-flex items-center gap-1.5 whitespace-nowrap rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"h-3.5 w-3.5\" />\n Install\n </>\n )}\n </button>\n </li>\n );\n })}\n </ul>\n\n {loading && packagesToOffer.length === 0 && (\n <div className=\"mt-4 flex items-center gap-2 text-xs text-text-secondary\">\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n Checking plugins…\n </div>\n )}\n </div>\n </div>\n </div>\n );\n })}\n\n <QuotaExhaustedDialog\n isOpen={quotaExhausted}\n onClose={clearQuotaExhausted}\n onConfirm={() => {\n clearQuotaExhausted();\n navigate('/billing');\n }}\n title=\"Plugin quota reached\"\n message=\"You've used all plugin install quota on your current plan. Upgrade to install more plugins.\"\n />\n </div>\n );\n}\n"],"mappings":";;;;;;;;;AAoEA,SAAS,EAAoB,GAA+C;AAqB1E,QApBI,EAAM,gBAAgB,EAAM,aAAa,SAAS,IAC7C,EAAM,aAAa,KAAK,OAAO;EACpC,UAAU,EAAE;EACZ,MAAM,EAAE,QAAQ;EAChB,SAAS,EAAE;EACX,OAAO,EAAE;EACT,aAAa,EAAE;EAChB,EAAE,GAED,EAAM,YAAY,EAAM,SAAS,SAAS,IACrC,CACL;EACE,UAAU,EAAM;EAChB,MAAM,EAAM,QAAQ;EACpB,SAAS,EAAM;EACf,OAAO,EAAM;EACb,aAAa,EAAM;EACpB,CACF,GAEI,EAAE;;AAGX,SAAgB,EAAW,GAAwB;CACjD,IAAM,EAAE,YAAS,aAAU,IAAO,gBAAa,gBAAa,GACtD,EAAE,QAAQ,MAAkB,EAAsB,EAAQ,EAC1D,IAAW,QAAc,EAAoB,EAAM,EAAE,CAAC,EAAM,CAAC,EAU7D,EACJ,YACA,mBACA,cACA,eACA,YACA,UACA,sBACA,mBACA,wBACA,eACE,EAnBgB,QAAc;EAChC,IAAM,oBAAM,IAAI,KAAa;AAC7B,OAAK,IAAM,KAAO,EAChB,MAAK,IAAM,KAAO,EAAI,SAAU,GAAI,IAAI,EAAI;AAE9C,SAAO,CAAC,GAAG,EAAI;IACd,CAAC,EAAS,CAAC,EAayB,GAAS,EAAc,EAExD,IAAW,GAAiB,EAG5B,IAAuB,QAEzB,EAAS,KAAK,MACR,EAAI,SAAS,QAAc,EAAI,SAAS,MAAM,MAAM,CAAC,CAAC,EAAU,GAAG,GAChE,EAAI,SAAS,OAAO,MAAM,CAAC,CAAC,EAAU,GAAG,CAChD,EACJ,CAAC,GAAU,EAAU,CACtB,EAEK,IAAe,EAAS,SAAS,KAAK,EAAqB,MAAM,QAAQ,EAEzE,IAAgB,EACpB,OAAO,MAAgB;AAErB,EADW,MAAM,EAAQ,EAAI,IACrB,KAAe;IAEzB,CAAC,GAAS,EAAY,CACvB;AA8FD,QA3FK,IAwBD,IACK,kBAAA,GAAA,EAAG,aAAY,CAAA,GAQpB,IAEA,kBAAC,OAAD;EACE,WAAW,yDAAyD,IAAU,QAAQ;EACtF,eAAY;YAEZ,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAS,WAAU,wBAAyB,CAAA,EAAA,6CAExC;;EACF,CAAA,GAQN,IAEA,kBAAC,OAAD;EACE,WAAW,yDAAyD,IAAU,QAAQ;YAEtF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAO,WAAU,oDAAqD,CAAA,EACtE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,KAAD;MAAG,WAAU;gBAA0C;MAA0B,CAAA;KACjF,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAGvC,CAAA;KACJ,kBAAC,KAAD;MAAG,WAAU;gBAAqD;MAAU,CAAA;KAC5E,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,eAAe,KAAK,GAAS;OAC7B,UAAU;OACV,WAAU;OACV,eAAY;iBAJd,CAME,kBAAC,GAAD,EAAW,WAAW,eAAe,IAAU,iBAAiB,MAAQ,CAAA,EAAA,QAEjE;UACT,kBAAC,UAAD;OACE,eAAe,EAAS,UAAU;OAClC,WAAU;iBACX;OAEQ,CAAA,CACL;;KACF;MACF;;EACF,CAAA,GAQR,kBAAC,OAAD;EAAK,WAAU;YAAf,CACG,EAAS,KAAK,GAAK,MAAQ;AAC1B,OAAI,EAAqB,GAAM,QAAO;GACtC,IAAM,KACJ,EAAI,MACA,EAAI,SAAS,QAAQ,MAAM,CAAC,EAAU,GAAG,GAEzC,IACJ,EAAI,SAAS,QACT,iCACA,EAAI,SAAS,WAAW,IACtB,oBACA,oBACF,IACJ,EAAI,SAAS,QACT,+FACA,EAAI,SAAS,WAAW,IACtB,6DACA;AAER,UACE,kBAAC,OAAD;IAEE,WAAW,yDAAyD,IAAU,QAAQ;cAEtF,kBAAC,OAAD;KAAK,WAAU;eAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,kDAAmD,CAAA,EACrE,kBAAC,OAAD;MAAK,WAAU;gBAAf;OACG,EAAI,WACH,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAI;QACH,CAAA;OAEN,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAI,SAAS;QACZ,CAAA;OACJ,kBAAC,KAAD;QAAG,WAAU;kBACV,EAAI,eAAe;QAClB,CAAA;OAEH,KACC,kBAAC,OAAD;QACE,MAAK;QACL,WAAU;kBAFZ,CAIE,kBAAC,GAAD,EAAa,WAAU,+BAAgC,CAAA,EACvD,kBAAC,QAAD,EAAA,UAAO,GAAa,CAAA,CAChB;;OAGR,kBAAC,MAAD;QAAI,WAAU;kBACX,EAAgB,KAAK,MAAQ;SAC5B,IAAM,IAAO,EAAc,EAAI,EACzB,IAAe,MAAe,GAC9B,IAAS,MAAe;AAC9B,gBACE,kBAAC,MAAD;UAEE,WAAU;oBAFZ,CAIE,kBAAC,OAAD;WAAK,WAAU;qBAAf;YACE,kBAAC,KAAD;aAAG,WAAU;uBACV,EAAK;aACJ,CAAA;YACJ,kBAAC,KAAD;aAAG,WAAU;uBAAwC,EAAK;aAAgB,CAAA;YAC1E,kBAAC,KAAD;aACE,WAAU;aACV,OAAO;uBAEN;aACC,CAAA;YACA;cACN,kBAAC,UAAD;WACE,eAAe,KAAK,EAAc,EAAI;WACtC,UAAU,KAAU;WACpB,WAAU;WACV,eAAa,kBAAkB;qBAE9B,IACC,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAS,WAAU,4BAA6B,CAAA,EAAA,cAE/C,EAAA,CAAA,GAEH,kBAAA,GAAA,EAAA,UAAA,CACE,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA,EAAA,UAEnC,EAAA,CAAA;WAEE,CAAA,CACN;YAjCE,EAiCF;UAEP;QACC,CAAA;OAEJ,KAAW,EAAgB,WAAW,KACrC,kBAAC,OAAD;QAAK,WAAU;kBAAf,CACE,kBAAC,GAAD,EAAS,WAAU,4BAA6B,CAAA,EAAA,oBAE5C;;OAEJ;QACF;;IACF,EAjFC,EAiFD;IAER,EAEF,kBAAC,GAAD;GACE,QAAQ;GACR,SAAS;GACT,iBAAiB;AAEf,IADA,GAAqB,EACrB,EAAS,WAAW;;GAEtB,OAAM;GACN,SAAQ;GACR,CAAA,CACE;MA/MJ,kBAAC,OAAD;EACE,WAAW,yDAAyD,IAAU,QAAQ;YAEtF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,GAAD,EAAQ,WAAU,+CAAgD,CAAA,EAClE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,KAAD;MAAG,WAAU;gBAA0C;MAAmB,CAAA;KAC1E,kBAAC,KAAD;MAAG,WAAU;gBAA8B;MAEvC,CAAA;KACJ,kBAAC,UAAD;MACE,eAAe,EAAS,UAAU;MAClC,WAAU;gBACX;MAEQ,CAAA;KACL;MACF;;EACF,CAAA"}
1
+ {"version":3,"file":"PluginGate.js","names":[],"sources":["../../../src/components/shared/PluginGate.tsx"],"sourcesContent":["/**\n * PluginGate — gates a UI surface on one or more vibecontrols-agent plugins\n * being installed. If missing, renders a card listing each required plugin\n * with an Install button. Once satisfied, renders children.\n *\n * Supports two modes:\n * - Single-group (backwards compat): `required` + `mode` + `title/description`.\n * - Multi-group: `requirements={[{packages, mode, ...}, ...]}` — one fetch\n * for all packages across all groups, unified loading state, stacked\n * install cards for any unsatisfied group. Prevents the \"flicker cascade\"\n * you get when nesting PluginGates (outer resolves, inner starts its own\n * initial-loading spinner, etc.).\n */\n\nimport { useCallback, useMemo, type ReactNode } from 'react';\nimport { Puzzle, Download, Loader2, AlertCircle, Server, RefreshCw, Clock } from 'lucide-react';\nimport { usePluginAvailability } from '@/hooks/usePluginAvailability';\nimport { getPluginMeta } from '@/constants/pluginCatalog';\nimport { QuotaExhaustedDialog } from './QuotaExhaustedDialog';\nimport { useVibeNavigate } from '@/providers/VibeControlsProvider';\n\nexport interface PluginRequirement {\n /** Package names that satisfy this requirement. */\n packages: readonly string[];\n /** 'all' (default) — every package must be installed. 'any' — at least one. */\n mode?: 'all' | 'any';\n /** Optional per-group eyebrow, title, description (fall back to defaults). */\n eyebrow?: string;\n title?: string;\n description?: string;\n}\n\nexport interface PluginGateProps {\n /** Active agent id. When null, gate shows an \"activate an agent\" prompt. */\n agentId: string | null;\n\n // ── Single-group API (backwards compat) ────────────────────────────────\n /** Package names required for the wrapped surface to render. */\n required?: readonly string[];\n /** 'all' (default) — every package must be installed. 'any' — at least one. */\n mode?: 'all' | 'any';\n /** Card heading. */\n title?: string;\n /** Card subheading / instruction line. */\n description?: string;\n /** Optional eyebrow label above the title (e.g. \"Plugin required\"). */\n eyebrow?: string;\n\n // ── Multi-group API (preferred when gating multiple requirements) ──────\n /** Multiple requirement groups — fetched + evaluated in one pass. */\n requirements?: readonly PluginRequirement[];\n\n /** Pass to render a compact version (smaller vertical padding). */\n compact?: boolean;\n /** Optional callback fired after install succeeds and gate resolves. */\n onInstalled?: () => void;\n children: ReactNode;\n}\n\ninterface ResolvedRequirement {\n packages: readonly string[];\n mode: 'all' | 'any';\n eyebrow?: string;\n title?: string;\n description?: string;\n}\n\nfunction resolveRequirements(props: PluginGateProps): ResolvedRequirement[] {\n if (props.requirements && props.requirements.length > 0) {\n return props.requirements.map((r) => ({\n packages: r.packages,\n mode: r.mode ?? 'all',\n eyebrow: r.eyebrow,\n title: r.title,\n description: r.description,\n }));\n }\n if (props.required && props.required.length > 0) {\n return [\n {\n packages: props.required,\n mode: props.mode ?? 'all',\n eyebrow: props.eyebrow,\n title: props.title,\n description: props.description,\n },\n ];\n }\n return [];\n}\n\nexport function PluginGate(props: PluginGateProps) {\n const { agentId, compact = false, onInstalled, children } = props;\n // Plugins are agent-wide (BOFF-2612). `activeProfile` no longer\n // participates in plugin availability — the install state is the\n // same for every profile on the agent.\n const resolved = useMemo(() => resolveRequirements(props), [props]);\n\n const allPackages = useMemo(() => {\n const set = new Set<string>();\n for (const req of resolved) {\n for (const pkg of req.packages) set.add(pkg);\n }\n return [...set];\n }, [resolved]);\n\n const {\n loading,\n initialLoading,\n installed,\n installing,\n install,\n error,\n errorIsAgentState,\n quotaExhausted,\n clearQuotaExhausted,\n refetch,\n } = usePluginAvailability(allPackages, agentId);\n\n const navigate = useVibeNavigate();\n\n // Per-requirement satisfaction.\n const requirementSatisfied = useMemo(\n () =>\n resolved.map((req) => {\n if (req.mode === 'any') return req.packages.some((p) => !!installed[p]);\n return req.packages.every((p) => !!installed[p]);\n }),\n [resolved, installed]\n );\n\n const allSatisfied = resolved.length > 0 && requirementSatisfied.every(Boolean);\n\n const handleInstall = useCallback(\n async (pkg: string) => {\n const ok = await install(pkg);\n if (ok) onInstalled?.();\n },\n [install, onInstalled]\n );\n\n // No agent available — universal precondition.\n if (!agentId) {\n return (\n <div\n className={`rounded-lg border border-border-default bg-bg-surface ${compact ? 'p-4' : 'p-6'}`}\n >\n <div className=\"flex items-start gap-3\">\n <Server className=\"h-5 w-5 text-text-secondary shrink-0 mt-0.5\" />\n <div className=\"flex-1 space-y-2\">\n <p className=\"text-sm font-semibold text-text-primary\">No active agent</p>\n <p className=\"text-sm text-text-secondary\">\n This surface needs an active vibecontrols agent with a tunnel URL and API key.\n </p>\n <button\n onClick={() => navigate('/agents')}\n className=\"px-3 py-1.5 text-xs font-medium rounded-md bg-action-primary-bg text-action-primary-text hover:bg-action-primary-bg-hover transition-colors\"\n >\n Go to Agents\n </button>\n </div>\n </div>\n </div>\n );\n }\n\n if (allSatisfied) {\n return <>{children}</>;\n }\n\n // First-fetch loading state — one spinner for ALL requirements. Prevents\n // the cascade you get with nested PluginGates (outer resolves -> inner\n // starts a fresh initialLoading cycle). After the first fetch settles,\n // subsequent refetches (focus, install, retry) fall through to the\n // install card so context isn't replaced by a spinner.\n if (initialLoading) {\n return (\n <div\n className={`rounded-lg border border-border-default bg-bg-surface ${compact ? 'p-4' : 'p-6'}`}\n data-testid=\"plugin-gate-initial-loading\"\n >\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n Checking plugin availability on the agent…\n </div>\n </div>\n );\n }\n\n // Agent is not ready (initializing / awaiting-config / unreachable).\n // The install button would just fail with the same message — instead,\n // render a dedicated waiting state with a retry button and an escape\n // hatch to the Agents page.\n if (errorIsAgentState) {\n return (\n <div\n className={`rounded-lg border border-border-default bg-bg-surface ${compact ? 'p-4' : 'p-6'}`}\n >\n <div className=\"flex items-start gap-3\">\n <Clock className=\"h-5 w-5 text-status-warning-text shrink-0 mt-0.5\" />\n <div className=\"flex-1 min-w-0 space-y-2\">\n <p className=\"text-sm font-semibold text-text-primary\">Agent is not ready yet</p>\n <p className=\"text-sm text-text-secondary\">\n Your agent is still starting up or waiting for gateway credentials. Plugin install is\n only available once the agent reaches the ready state.\n </p>\n <p className=\"text-xs font-mono text-text-placeholder break-all\">{error}</p>\n <div className=\"flex flex-wrap items-center gap-2 pt-1\">\n <button\n onClick={() => void refetch()}\n disabled={loading}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n data-testid=\"plugin-gate-retry\"\n >\n <RefreshCw className={`h-3.5 w-3.5 ${loading ? 'animate-spin' : ''}`} />\n Retry\n </button>\n <button\n onClick={() => navigate('/agents')}\n className=\"rounded-md border border-border-default px-3 py-1.5 text-xs font-medium text-text-secondary hover:text-text-primary hover:border-border-strong transition-colors\"\n >\n Manage agents\n </button>\n </div>\n </div>\n </div>\n </div>\n );\n }\n\n // Stack one install card per unsatisfied requirement. The user sees every\n // outstanding dependency at once instead of resolving them one-by-one with\n // a re-render between each.\n return (\n <div className=\"space-y-3\">\n {resolved.map((req, idx) => {\n if (requirementSatisfied[idx]) return null;\n const packagesToOffer =\n req.mode === 'any'\n ? req.packages.filter((p) => !installed[p])\n : req.packages.filter((p) => !installed[p]);\n const defaultTitle =\n req.mode === 'any'\n ? 'Install a plugin to continue'\n : req.packages.length === 1\n ? 'Plugin required'\n : 'Plugins required';\n const defaultDescription =\n req.mode === 'any'\n ? 'Pick any one of the plugins below. Once installed, this surface will render automatically.'\n : req.packages.length === 1\n ? 'This surface needs the following agent plugin installed.'\n : 'This surface needs the following agent plugins installed.';\n\n return (\n <div\n key={idx}\n className={`rounded-lg border border-border-default bg-bg-surface ${compact ? 'p-4' : 'p-6'}`}\n >\n <div className=\"flex items-start gap-3\">\n <Puzzle className=\"h-5 w-5 text-action-primary-bg shrink-0 mt-0.5\" />\n <div className=\"flex-1 min-w-0\">\n {req.eyebrow && (\n <p className=\"text-xs uppercase tracking-wide text-text-secondary mb-1\">\n {req.eyebrow}\n </p>\n )}\n <p className=\"text-sm font-semibold text-text-primary\">\n {req.title ?? defaultTitle}\n </p>\n <p className=\"mt-1 text-sm text-text-secondary\">\n {req.description ?? defaultDescription}\n </p>\n\n {error && (\n <div\n role=\"alert\"\n className=\"mt-3 flex items-start gap-2 rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-xs text-status-error-text\"\n >\n <AlertCircle className=\"h-3.5 w-3.5 mt-0.5 shrink-0\" />\n <span>{error}</span>\n </div>\n )}\n\n <ul className=\"mt-4 space-y-2\">\n {packagesToOffer.map((pkg) => {\n const meta = getPluginMeta(pkg);\n const isInstalling = installing === pkg;\n const isBusy = installing !== null;\n return (\n <li\n key={pkg}\n className=\"flex items-center justify-between gap-3 rounded-md border border-border-default bg-bg-sunken px-3 py-2\"\n >\n <div className=\"min-w-0\">\n <p className=\"text-sm font-medium text-text-primary truncate\">\n {meta.displayName}\n </p>\n <p className=\"text-xs text-text-secondary truncate\">{meta.description}</p>\n <p\n className=\"text-[10px] font-mono text-text-placeholder truncate\"\n title={pkg}\n >\n {pkg}\n </p>\n </div>\n <button\n onClick={() => void handleInstall(pkg)}\n disabled={isBusy || loading}\n className=\"inline-flex items-center gap-1.5 whitespace-nowrap rounded-md bg-action-primary-bg px-3 py-1.5 text-xs font-medium text-action-primary-text hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n data-testid={`plugin-install-${pkg}`}\n >\n {isInstalling ? (\n <>\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n Installing…\n </>\n ) : (\n <>\n <Download className=\"h-3.5 w-3.5\" />\n Install\n </>\n )}\n </button>\n </li>\n );\n })}\n </ul>\n\n {loading && packagesToOffer.length === 0 && (\n <div className=\"mt-4 flex items-center gap-2 text-xs text-text-secondary\">\n <Loader2 className=\"h-3.5 w-3.5 animate-spin\" />\n Checking plugins…\n </div>\n )}\n </div>\n </div>\n </div>\n );\n })}\n\n <QuotaExhaustedDialog\n isOpen={quotaExhausted}\n onClose={clearQuotaExhausted}\n onConfirm={() => {\n clearQuotaExhausted();\n navigate('/billing');\n }}\n title=\"Plugin quota reached\"\n message=\"You've used all plugin install quota on your current plan. Upgrade to install more plugins.\"\n />\n </div>\n );\n}\n"],"mappings":"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"}
@@ -3917,8 +3917,8 @@ function Ls(e) {
3917
3917
  return t.useSuspenseQuery(z, r);
3918
3918
  }
3919
3919
  var B = e`
3920
- query AgentPlugins($agentId: ID!, $profile: String) {
3921
- agentPlugins(agentId: $agentId, profile: $profile) {
3920
+ query AgentPlugins($agentId: ID!) {
3921
+ agentPlugins(agentId: $agentId) {
3922
3922
  installed {
3923
3923
  packageName
3924
3924
  pluginName
@@ -7880,8 +7880,8 @@ function Bm(e) {
7880
7880
  return t.useSubscription(zm, r);
7881
7881
  }
7882
7882
  var Vm = e`
7883
- subscription VibecontrolsAgentPluginStream($agentId: ID!, $profile: String) {
7884
- vibecontrolsAgentPluginStream(agentId: $agentId, profile: $profile) {
7883
+ subscription VibecontrolsAgentPluginStream($agentId: ID!) {
7884
+ vibecontrolsAgentPluginStream(agentId: $agentId) {
7885
7885
  packageName
7886
7886
  pluginName
7887
7887
  version