@burdenoff/microfe-vibecontrols 2026.531.9 → 2026.531.10

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.
@@ -129,7 +129,7 @@ function m({ value: m, onChange: h, provider: g, agentId: _, profile: v }) {
129
129
  }
130
130
  x((e) => !e);
131
131
  },
132
- className: "flex items-center gap-1 px-2 py-1 text-xs font-medium rounded border border-border-default bg-bg-surface text-text-secondary hover:text-text-primary hover:border-border-strong transition-colors max-w-[160px]",
132
+ className: "flex items-center gap-1 px-2 py-1 text-xs font-medium rounded border border-border-default bg-bg-surface text-text-primary hover:bg-bg-elevated hover:border-border-strong transition-colors max-w-[160px]",
133
133
  title: m,
134
134
  children: [/* @__PURE__ */ c("span", {
135
135
  className: "truncate",
@@ -158,7 +158,7 @@ function m({ value: m, onChange: h, provider: g, agentId: _, profile: v }) {
158
158
  onClick: () => {
159
159
  h(e), x(!1);
160
160
  },
161
- className: `w-full text-left px-3 py-2 text-sm hover:bg-bg-elevated transition-colors ${e === m ? "text-action-primary-text bg-action-primary-bg/10" : "text-text-primary"}`,
161
+ className: `w-full text-left px-3 py-2 text-sm hover:bg-bg-elevated transition-colors ${e === m ? "text-text-primary bg-bg-sunken font-medium" : "text-text-primary"}`,
162
162
  children: e
163
163
  }, e))] }) }, e.sdk)),
164
164
  N.every((e) => e.models.length === 0) && /* @__PURE__ */ c("div", {
@@ -1 +1 @@
1
- {"version":3,"file":"ModelSelector.js","names":[],"sources":["../../../../src/components/agent-manager/controls/ModelSelector.tsx"],"sourcesContent":["/**\n * Model selector dropdown.\n *\n * Displays a compact button with the current model name that opens a\n * grouped dropdown of models filtered by SDK provider.\n */\n\nimport { useState, useRef, useEffect, useCallback } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport { fetchModels } from '@/services/aiApi';\nimport type { AgentSdkType } from '@/types/agentManager';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface ModelSelectorProps {\n value: string;\n onChange: (model: string) => void;\n provider?: AgentSdkType;\n agentId?: string;\n /** Canonical CLI profile name on the remote agent. */\n profile?: string;\n}\n\ninterface ModelGroup {\n label: string;\n sdk: AgentSdkType;\n models: string[];\n}\n\nconst MODEL_GROUPS: ModelGroup[] = [\n { label: 'Claude', sdk: 'claude', models: [] },\n { label: 'Codex', sdk: 'codex', models: [] },\n { label: 'Gemini', sdk: 'gemini', models: [] },\n { label: 'OpenCode', sdk: 'opencode', models: [] },\n { label: 'OpenAI-compatible', sdk: 'openai-compat', models: [] },\n { label: 'Goose', sdk: 'goose', models: [] },\n { label: 'Amp', sdk: 'amp', models: [] },\n { label: 'Copilot', sdk: 'copilot', models: [] },\n { label: 'Crush', sdk: 'crush', models: [] },\n { label: 'Pi', sdk: 'pi', models: [] },\n { label: 'Aider', sdk: 'aider', models: [] },\n { label: 'Plandex', sdk: 'plandex', models: [] },\n];\n\n/** Truncate model name for compact display */\nfunction displayName(model: string): string {\n // Remove common prefixes for compact display\n return model\n .replace(/^opencode\\//, '')\n .replace('claude-', '')\n .replace('-20250514', '')\n .replace('-20251001', '');\n}\n\nfunction modelGroupLabel(provider: AgentSdkType): string {\n return MODEL_GROUPS.find((group) => group.sdk === provider)?.label ?? provider;\n}\n\nfunction uniqueModels(models: string[], currentValue: string): string[] {\n return Array.from(new Set([currentValue, ...models].filter(Boolean)));\n}\n\nexport function ModelSelector({ value, onChange, provider, agentId, profile }: ModelSelectorProps) {\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [agentModels, setAgentModels] = useState<string[] | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const [pos, setPos] = useState({ bottom: 0, left: 0 });\n\n const filteredGroups =\n provider && agentModels\n ? [\n {\n label: modelGroupLabel(provider),\n sdk: provider,\n models: uniqueModels(agentModels, value),\n },\n ]\n : provider\n ? MODEL_GROUPS.filter((g) => g.sdk === provider)\n : MODEL_GROUPS;\n\n const handleClickOutside = useCallback((e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n }, []);\n\n useEffect(() => {\n if (open) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [open, handleClickOutside]);\n\n useEffect(() => {\n if (!open) setSearch('');\n }, [open]);\n\n useEffect(() => {\n if (!open || !agentId || !provider) return;\n\n let cancelled = false;\n setAgentModels(null);\n void fetchModels({ id: agentId, profile: profile ?? 'default' }, provider)\n .then((models) => {\n if (cancelled) return;\n const modelIds = models.map((model) => model.id).filter(Boolean);\n setAgentModels(modelIds);\n })\n .catch(() => {\n if (!cancelled) setAgentModels([]);\n });\n\n return () => {\n cancelled = true;\n };\n }, [agentId, open, profile, provider]);\n\n const searchLower = search.toLowerCase();\n const searchedGroups = filteredGroups.map((g) => ({\n ...g,\n models: search ? g.models.filter((m) => m.toLowerCase().includes(searchLower)) : g.models,\n }));\n\n const handleToggle = () => {\n if (!open && buttonRef.current) {\n const rect = buttonRef.current.getBoundingClientRect();\n setPos({ bottom: window.innerHeight - rect.top + 4, left: rect.left });\n }\n setOpen((prev) => !prev);\n };\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n ref={buttonRef}\n type=\"button\"\n onClick={handleToggle}\n className=\"flex items-center gap-1 px-2 py-1 text-xs font-medium rounded border border-border-default bg-bg-surface text-text-secondary hover:text-text-primary hover:border-border-strong transition-colors max-w-[160px]\"\n title={value}\n >\n <span className=\"truncate\">{displayName(value)}</span>\n <ChevronDown className=\"size-3 flex-shrink-0\" />\n </button>\n\n {open && (\n <div\n className=\"fixed w-64 max-h-72 overflow-y-auto rounded-lg border border-border-default bg-bg-surface shadow-lg z-[100]\"\n style={{ bottom: pos.bottom, left: pos.left }}\n >\n <input\n aria-label=\"Search...\"\n type=\"text\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search...\"\n className=\"w-full px-3 py-1.5 text-xs border-b border-border-default bg-transparent text-text-primary placeholder:text-text-tertiary focus:outline-none sticky top-0 bg-bg-surface\"\n />\n {searchedGroups.map((group) => (\n <div key={group.sdk}>\n {group.models.length > 0 && (\n <>\n <div className=\"px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-text-tertiary bg-bg-sunken\">\n {group.label}\n </div>\n {group.models.map((model) => (\n <button\n key={model}\n type=\"button\"\n onClick={() => {\n onChange(model);\n setOpen(false);\n }}\n className={`w-full text-left px-3 py-2 text-sm hover:bg-bg-elevated transition-colors ${\n model === value\n ? 'text-action-primary-text bg-action-primary-bg/10'\n : 'text-text-primary'\n }`}\n >\n {model}\n </button>\n ))}\n </>\n )}\n </div>\n ))}\n {searchedGroups.every((g) => g.models.length === 0) && (\n <div className=\"px-3 py-4 text-sm text-text-tertiary text-center\">\n {tr('vibecontrols.agentManager.controls.noModelsAvailable', 'No models available')}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"ModelSelector.js","names":[],"sources":["../../../../src/components/agent-manager/controls/ModelSelector.tsx"],"sourcesContent":["/**\n * Model selector dropdown.\n *\n * Displays a compact button with the current model name that opens a\n * grouped dropdown of models filtered by SDK provider.\n */\n\nimport { useState, useRef, useEffect, useCallback } from 'react';\nimport { ChevronDown } from 'lucide-react';\nimport { fetchModels } from '@/services/aiApi';\nimport type { AgentSdkType } from '@/types/agentManager';\nimport { useTr } from '../../../shared/hooks/useTr';\n\ninterface ModelSelectorProps {\n value: string;\n onChange: (model: string) => void;\n provider?: AgentSdkType;\n agentId?: string;\n /** Canonical CLI profile name on the remote agent. */\n profile?: string;\n}\n\ninterface ModelGroup {\n label: string;\n sdk: AgentSdkType;\n models: string[];\n}\n\nconst MODEL_GROUPS: ModelGroup[] = [\n { label: 'Claude', sdk: 'claude', models: [] },\n { label: 'Codex', sdk: 'codex', models: [] },\n { label: 'Gemini', sdk: 'gemini', models: [] },\n { label: 'OpenCode', sdk: 'opencode', models: [] },\n { label: 'OpenAI-compatible', sdk: 'openai-compat', models: [] },\n { label: 'Goose', sdk: 'goose', models: [] },\n { label: 'Amp', sdk: 'amp', models: [] },\n { label: 'Copilot', sdk: 'copilot', models: [] },\n { label: 'Crush', sdk: 'crush', models: [] },\n { label: 'Pi', sdk: 'pi', models: [] },\n { label: 'Aider', sdk: 'aider', models: [] },\n { label: 'Plandex', sdk: 'plandex', models: [] },\n];\n\n/** Truncate model name for compact display */\nfunction displayName(model: string): string {\n // Remove common prefixes for compact display\n return model\n .replace(/^opencode\\//, '')\n .replace('claude-', '')\n .replace('-20250514', '')\n .replace('-20251001', '');\n}\n\nfunction modelGroupLabel(provider: AgentSdkType): string {\n return MODEL_GROUPS.find((group) => group.sdk === provider)?.label ?? provider;\n}\n\nfunction uniqueModels(models: string[], currentValue: string): string[] {\n return Array.from(new Set([currentValue, ...models].filter(Boolean)));\n}\n\nexport function ModelSelector({ value, onChange, provider, agentId, profile }: ModelSelectorProps) {\n const tr = useTr();\n const [open, setOpen] = useState(false);\n const [search, setSearch] = useState('');\n const [agentModels, setAgentModels] = useState<string[] | null>(null);\n const containerRef = useRef<HTMLDivElement>(null);\n const buttonRef = useRef<HTMLButtonElement>(null);\n const [pos, setPos] = useState({ bottom: 0, left: 0 });\n\n const filteredGroups =\n provider && agentModels\n ? [\n {\n label: modelGroupLabel(provider),\n sdk: provider,\n models: uniqueModels(agentModels, value),\n },\n ]\n : provider\n ? MODEL_GROUPS.filter((g) => g.sdk === provider)\n : MODEL_GROUPS;\n\n const handleClickOutside = useCallback((e: MouseEvent) => {\n if (containerRef.current && !containerRef.current.contains(e.target as Node)) {\n setOpen(false);\n }\n }, []);\n\n useEffect(() => {\n if (open) {\n document.addEventListener('mousedown', handleClickOutside);\n return () => document.removeEventListener('mousedown', handleClickOutside);\n }\n }, [open, handleClickOutside]);\n\n useEffect(() => {\n if (!open) setSearch('');\n }, [open]);\n\n useEffect(() => {\n if (!open || !agentId || !provider) return;\n\n let cancelled = false;\n setAgentModels(null);\n void fetchModels({ id: agentId, profile: profile ?? 'default' }, provider)\n .then((models) => {\n if (cancelled) return;\n const modelIds = models.map((model) => model.id).filter(Boolean);\n setAgentModels(modelIds);\n })\n .catch(() => {\n if (!cancelled) setAgentModels([]);\n });\n\n return () => {\n cancelled = true;\n };\n }, [agentId, open, profile, provider]);\n\n const searchLower = search.toLowerCase();\n const searchedGroups = filteredGroups.map((g) => ({\n ...g,\n models: search ? g.models.filter((m) => m.toLowerCase().includes(searchLower)) : g.models,\n }));\n\n const handleToggle = () => {\n if (!open && buttonRef.current) {\n const rect = buttonRef.current.getBoundingClientRect();\n setPos({ bottom: window.innerHeight - rect.top + 4, left: rect.left });\n }\n setOpen((prev) => !prev);\n };\n\n return (\n <div ref={containerRef} className=\"relative\">\n <button\n ref={buttonRef}\n type=\"button\"\n onClick={handleToggle}\n className=\"flex items-center gap-1 px-2 py-1 text-xs font-medium rounded border border-border-default bg-bg-surface text-text-primary hover:bg-bg-elevated hover:border-border-strong transition-colors max-w-[160px]\"\n title={value}\n >\n <span className=\"truncate\">{displayName(value)}</span>\n <ChevronDown className=\"size-3 flex-shrink-0\" />\n </button>\n\n {open && (\n <div\n className=\"fixed w-64 max-h-72 overflow-y-auto rounded-lg border border-border-default bg-bg-surface shadow-lg z-[100]\"\n style={{ bottom: pos.bottom, left: pos.left }}\n >\n <input\n aria-label=\"Search...\"\n type=\"text\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search...\"\n className=\"w-full px-3 py-1.5 text-xs border-b border-border-default bg-transparent text-text-primary placeholder:text-text-tertiary focus:outline-none sticky top-0 bg-bg-surface\"\n />\n {searchedGroups.map((group) => (\n <div key={group.sdk}>\n {group.models.length > 0 && (\n <>\n <div className=\"px-3 py-1.5 text-[10px] font-semibold uppercase tracking-wider text-text-tertiary bg-bg-sunken\">\n {group.label}\n </div>\n {group.models.map((model) => (\n <button\n key={model}\n type=\"button\"\n onClick={() => {\n onChange(model);\n setOpen(false);\n }}\n className={`w-full text-left px-3 py-2 text-sm hover:bg-bg-elevated transition-colors ${\n model === value\n ? 'text-text-primary bg-bg-sunken font-medium'\n : 'text-text-primary'\n }`}\n >\n {model}\n </button>\n ))}\n </>\n )}\n </div>\n ))}\n {searchedGroups.every((g) => g.models.length === 0) && (\n <div className=\"px-3 py-4 text-sm text-text-tertiary text-center\">\n {tr('vibecontrols.agentManager.controls.noModelsAvailable', 'No models available')}\n </div>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"RemoteTaskPermissionsCard.js","names":[],"sources":["../../../src/components/agents/RemoteTaskPermissionsCard.tsx"],"sourcesContent":["/**\n * RemoteTaskPermissionsCard\n *\n * Toggles the per-agent \"allow remote shell tasks\" and \"allow remote\n * file-mutation tasks\" gates that the agent enforces in\n * `core/request-security.ts:denyNonLocalMutation`. The agent's task\n * plugin (POST /api/profiles/<n>/tasks/) refuses non-loopback callers by\n * default because remote shell execution + file writes are high-blast\n * radius; this card lets operators opt in per-agent without having to\n * SSH in and restart the daemon with the matching env var\n * (VIBECONTROLS_ALLOW_REMOTE_SHELL_TASK / _FILE_TASK).\n *\n * Storage: writes to the agent's encrypted config bag under\n * `security:vibecontrols_allow_remote_shell_task` and\n * `security:vibecontrols_allow_remote_file_task`. The same keys are\n * read by the agent's gate via `db.getConfig(...)`. Persisted across\n * agent restarts.\n */\nimport { useCallback, useEffect, useState } from 'react';\nimport { ShieldAlert, Loader2 } from 'lucide-react';\n\nimport { Card } from '@burdenoff/fe-libs/ui';\nimport { Switch } from '@burdenoff/fe-libs/ui';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\ninterface RemoteTaskPermissionsCardProps {\n agent: AgentRef;\n}\n\ninterface GateDef {\n key: string;\n title: string;\n description: string;\n warning?: string;\n}\n\nfunction buildGates(tr: ReturnType<typeof useTr>): GateDef[] {\n return [\n {\n key: 'security:vibecontrols_allow_remote_shell_task',\n title: tr(\n 'agentDetails.remoteShellTask.title',\n 'Allow remote shell + script tasks'\n ),\n description: tr(\n 'agentDetails.remoteShellTask.description',\n 'Enable VibeDeck buttons, scheduled jobs, and the UI to run shell commands and scripts on this agent over its tunnel. Required for any UI-triggered \"execute action\" that runs a command.'\n ),\n warning: tr(\n 'agentDetails.remoteShellTask.warning',\n 'Anyone with workspace access can execute commands on the host machine. Audit log records every invocation; reviewers can revert this flag at any time.'\n ),\n },\n {\n key: 'security:vibecontrols_allow_remote_file_task',\n title: tr(\n 'agentDetails.remoteFileTask.title',\n 'Allow remote file writes + deletes'\n ),\n description: tr(\n 'agentDetails.remoteFileTask.description',\n 'Enable UI-triggered file_operation write/delete tasks on this agent. Reads are always allowed; this gate covers mutations only.'\n ),\n warning: tr(\n 'agentDetails.remoteFileTask.warning',\n 'Writes and deletes are confined to the cwd / homedir / tmpdir allowlist via resolveSafePath + isSensitivePath, but a malicious caller could still corrupt files in those scopes.'\n ),\n },\n ];\n}\n\nfunction isTruthy(raw: string | null | undefined): boolean {\n if (!raw) return false;\n const v = raw.trim().toLowerCase();\n return v === '1' || v === 'true' || v === 'yes';\n}\n\nexport function RemoteTaskPermissionsCard({ agent }: RemoteTaskPermissionsCardProps) {\n const tr = useTr();\n const gates = buildGates(tr);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [values, setValues] = useState<Record<string, boolean>>({});\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n gates.map(async (g) => [g.key, isTruthy(await getAgentConfigValue(agent, g.key))] as const)\n );\n setValues(Object.fromEntries(entries));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('agentDetails.remoteTaskPermissions.loadFailed', 'Failed to load permissions')\n );\n } finally {\n setLoading(false);\n }\n // gates is stable across renders (derived from tr); intentionally not in deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agent, tr]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const toggle = useCallback(\n async (key: string, next: boolean) => {\n setSaving(key);\n setError(null);\n try {\n if (next) {\n await setAgentConfigValue(agent, key, '1');\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setValues((prev) => ({ ...prev, [key]: next }));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr(\n 'agentDetails.remoteTaskPermissions.saveFailed',\n 'Failed to save permission'\n )\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <ShieldAlert className=\"size-5\" />\n {tr(\n 'agentDetails.remoteTaskPermissions.title',\n 'Remote Task Permissions'\n )}\n </h3>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'agentDetails.remoteTaskPermissions.subtitle',\n 'These gates default to deny so an agent on a public tunnel can’t be coerced into shell execution. Toggle them on per-agent if you want VibeDeck buttons / scheduled jobs to run shell or file-mutation tasks here.'\n )}\n </p>\n </div>\n <div className=\"p-4 space-y-4\">\n {error ? (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n {tr('agentDetails.remoteTaskPermissions.loading', 'Loading…')}\n </div>\n ) : (\n gates.map((g) => {\n const current = values[g.key] ?? false;\n const busy = saving === g.key;\n return (\n <div\n key={g.key}\n className=\"flex items-start gap-3 rounded-lg border border-border-default bg-bg-primary p-3\"\n >\n <div className=\"flex-1 min-w-0 space-y-1\">\n <p className=\"text-sm font-medium text-text-primary\">{g.title}</p>\n <p className=\"text-xs text-text-secondary\">{g.description}</p>\n {g.warning ? (\n <p className=\"text-xs text-status-warning-text\">{g.warning}</p>\n ) : null}\n <p className=\"text-[11px] font-mono text-text-tertiary\">{g.key}</p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {busy ? (\n <Loader2 className=\"size-4 animate-spin text-text-secondary\" />\n ) : null}\n <Switch\n checked={current}\n onCheckedChange={(checked) => void toggle(g.key, checked)}\n disabled={busy}\n aria-label={g.title}\n />\n </div>\n </div>\n );\n })\n )}\n </div>\n </Card>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"RemoteTaskPermissionsCard.js","names":[],"sources":["../../../src/components/agents/RemoteTaskPermissionsCard.tsx"],"sourcesContent":["/**\n * RemoteTaskPermissionsCard\n *\n * Toggles the per-agent \"allow remote shell tasks\" and \"allow remote\n * file-mutation tasks\" gates that the agent enforces in\n * `core/request-security.ts:denyNonLocalMutation`. The agent's task\n * plugin (POST /api/profiles/<n>/tasks/) refuses non-loopback callers by\n * default because remote shell execution + file writes are high-blast\n * radius; this card lets operators opt in per-agent without having to\n * SSH in and restart the daemon with the matching env var\n * (VIBECONTROLS_ALLOW_REMOTE_SHELL_TASK / _FILE_TASK).\n *\n * Storage: writes to the agent's encrypted config bag under\n * `security:vibecontrols_allow_remote_shell_task` and\n * `security:vibecontrols_allow_remote_file_task`. The same keys are\n * read by the agent's gate via `db.getConfig(...)`. Persisted across\n * agent restarts.\n */\nimport { useCallback, useEffect, useState } from 'react';\nimport { ShieldAlert, Loader2 } from 'lucide-react';\n\nimport { Card } from '@burdenoff/fe-libs/ui';\nimport { Switch } from '@burdenoff/fe-libs/ui';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\ninterface RemoteTaskPermissionsCardProps {\n agent: AgentRef;\n}\n\ninterface GateDef {\n key: string;\n title: string;\n description: string;\n warning?: string;\n}\n\nfunction buildGates(tr: ReturnType<typeof useTr>): GateDef[] {\n return [\n {\n key: 'security:vibecontrols_allow_remote_shell_task',\n title: tr('agentDetails.remoteShellTask.title', 'Allow remote shell + script tasks'),\n description: tr(\n 'agentDetails.remoteShellTask.description',\n 'Enable VibeDeck buttons, scheduled jobs, and the UI to run shell commands and scripts on this agent over its tunnel. Required for any UI-triggered \"execute action\" that runs a command.'\n ),\n warning: tr(\n 'agentDetails.remoteShellTask.warning',\n 'Anyone with workspace access can execute commands on the host machine. Audit log records every invocation; reviewers can revert this flag at any time.'\n ),\n },\n {\n key: 'security:vibecontrols_allow_remote_file_task',\n title: tr('agentDetails.remoteFileTask.title', 'Allow remote file writes + deletes'),\n description: tr(\n 'agentDetails.remoteFileTask.description',\n 'Enable UI-triggered file_operation write/delete tasks on this agent. Reads are always allowed; this gate covers mutations only.'\n ),\n warning: tr(\n 'agentDetails.remoteFileTask.warning',\n 'Writes and deletes are confined to the cwd / homedir / tmpdir allowlist via resolveSafePath + isSensitivePath, but a malicious caller could still corrupt files in those scopes.'\n ),\n },\n ];\n}\n\nfunction isTruthy(raw: string | null | undefined): boolean {\n if (!raw) return false;\n const v = raw.trim().toLowerCase();\n return v === '1' || v === 'true' || v === 'yes';\n}\n\nexport function RemoteTaskPermissionsCard({ agent }: RemoteTaskPermissionsCardProps) {\n const tr = useTr();\n const gates = buildGates(tr);\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [values, setValues] = useState<Record<string, boolean>>({});\n const [error, setError] = useState<string | null>(null);\n\n const load = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n gates.map(async (g) => [g.key, isTruthy(await getAgentConfigValue(agent, g.key))] as const)\n );\n setValues(Object.fromEntries(entries));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('agentDetails.remoteTaskPermissions.loadFailed', 'Failed to load permissions')\n );\n } finally {\n setLoading(false);\n }\n // gates is stable across renders (derived from tr); intentionally not in deps.\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agent, tr]);\n\n useEffect(() => {\n void load();\n }, [load]);\n\n const toggle = useCallback(\n async (key: string, next: boolean) => {\n setSaving(key);\n setError(null);\n try {\n if (next) {\n await setAgentConfigValue(agent, key, '1');\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setValues((prev) => ({ ...prev, [key]: next }));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('agentDetails.remoteTaskPermissions.saveFailed', 'Failed to save permission')\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <Card>\n <div className=\"p-4 border-b border-border-default\">\n <h3 className=\"font-semibold text-text-primary flex items-center gap-2\">\n <ShieldAlert className=\"size-5\" />\n {tr('agentDetails.remoteTaskPermissions.title', 'Remote Task Permissions')}\n </h3>\n <p className=\"mt-1 text-xs text-text-secondary\">\n {tr(\n 'agentDetails.remoteTaskPermissions.subtitle',\n 'These gates default to deny so an agent on a public tunnel can’t be coerced into shell execution. Toggle them on per-agent if you want VibeDeck buttons / scheduled jobs to run shell or file-mutation tasks here.'\n )}\n </p>\n </div>\n <div className=\"p-4 space-y-4\">\n {error ? (\n <div className=\"rounded-md border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"size-4 animate-spin\" />\n {tr('agentDetails.remoteTaskPermissions.loading', 'Loading…')}\n </div>\n ) : (\n gates.map((g) => {\n const current = values[g.key] ?? false;\n const busy = saving === g.key;\n return (\n <div\n key={g.key}\n className=\"flex items-start gap-3 rounded-lg border border-border-default bg-bg-primary p-3\"\n >\n <div className=\"flex-1 min-w-0 space-y-1\">\n <p className=\"text-sm font-medium text-text-primary\">{g.title}</p>\n <p className=\"text-xs text-text-secondary\">{g.description}</p>\n {g.warning ? (\n <p className=\"text-xs text-status-warning-text\">{g.warning}</p>\n ) : null}\n <p className=\"text-[11px] font-mono text-text-tertiary\">{g.key}</p>\n </div>\n <div className=\"flex items-center gap-2 flex-shrink-0\">\n {busy ? <Loader2 className=\"size-4 animate-spin text-text-secondary\" /> : null}\n <Switch\n checked={current}\n onCheckedChange={(checked) => void toggle(g.key, checked)}\n disabled={busy}\n aria-label={g.title}\n />\n </div>\n </div>\n );\n })\n )}\n </div>\n </Card>\n );\n}\n"],"mappings":"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"}
@@ -128,7 +128,7 @@ function p(p, m) {
128
128
  if (t?.installAgentPlugin?.success) return C((t) => ({
129
129
  ...t,
130
130
  [e]: !0
131
- })), setTimeout(() => void M(), 3e3), !0;
131
+ })), setTimeout(() => void M(), 1200), !0;
132
132
  throw Error(t?.installAgentPlugin?.error || "Install failed");
133
133
  } catch (e) {
134
134
  if (r(e)) return i(), k(!0), !1;
@@ -1 +1 @@
1
- {"version":3,"file":"usePluginAvailability.js","names":[],"sources":["../../src/hooks/usePluginAvailability.ts"],"sourcesContent":["/**\n * usePluginAvailability — query + install helpers for vibecontrols-agent plugins.\n *\n * Wraps the existing backend-proxied GraphQL ops (AgentPlugins query +\n * InstallAgentPlugin mutation) and exposes a small state surface that\n * gating components can render against.\n *\n * Live updates: subscribes to `vibecontrolsAgentPluginStream` so the\n * installed map reflects out-of-band installs (CLI, other tabs,\n * agent-side changes) without per-tab polling. The initial AgentPlugins\n * query is still used for first paint so the gate can render before\n * the WebSocket completes its handshake.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport {\n AgentPluginsDocument,\n useInstallAgentPluginMutation,\n useVibecontrolsAgentPluginStreamSubscription,\n type AgentPluginsQuery,\n} from '@/generated/wspace-operations';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\n\n/**\n * Classifies transient \"the agent itself isn't ready\" errors so the UI can\n * render a dedicated waiting state instead of an install CTA. These errors\n * are not the user's to fix via an install click — they resolve on their\n * own (or require Agents-page intervention).\n */\nfunction classifyAgentStateError(message: string | null | undefined): boolean {\n if (!message) return false;\n const m = message.toLowerCase();\n return (\n m.includes('agent not yet configured') ||\n m.includes('awaiting-config') ||\n m.includes('initializing') ||\n m.includes('agent unreachable') ||\n m.includes('agent returned 503') ||\n m.includes('http 503') ||\n m.includes('plugin routes are unavailable') ||\n m.includes('econnrefused') ||\n m.includes('rate_limited') ||\n m.includes('rate limited') ||\n m.includes('operation timeout') ||\n m.includes('operation_timeout') ||\n // Bun's fetch surfaces these human-friendly strings when the agent\n // socket isn't accepting connections. Without this branch, the raw\n // text would leak into the install card as the error chip.\n m.includes('typo in the url') ||\n m.includes('unable to connect') ||\n m.includes('failed to open socket') ||\n m.includes('failedtoopensocket') ||\n m.includes('fetch failed') ||\n m.includes('enotfound') ||\n m.includes('getaddrinfo')\n // Note: \"operation was aborted\" is deliberately NOT classified as an\n // agent-state error. Apollo aborts previous in-flight queries when\n // the same lazy query is re-fired (common in React Strict Mode\n // double-mount). Those aborts are transient and not about agent\n // state; the follow-up fetch succeeds normally.\n );\n}\n\n/**\n * Translate raw error strings (especially Bun fetch's human-friendly but\n * jargon-y errors) into a short, user-safe message. Used right before we\n * stash anything into the surfaced `error` field — guarantees we never\n * render \"Was there a typo in the url or port?\" or similar into the DOM.\n */\nfunction sanitizeAgentStateError(raw: string): string {\n const m = raw.toLowerCase();\n if (\n m.includes('typo in the url') ||\n m.includes('unable to connect') ||\n m.includes('failed to open socket') ||\n m.includes('failedtoopensocket') ||\n m.includes('econnrefused') ||\n m.includes('enotfound') ||\n m.includes('getaddrinfo') ||\n m.includes('fetch failed') ||\n m.includes('agent unreachable')\n ) {\n return 'Agent unreachable. Make sure the vibecontrols-agent is running locally.';\n }\n if (m.includes('agent not yet configured') || m.includes('awaiting-config')) {\n return 'Agent is waiting for configuration. Finish setup in the Agents page.';\n }\n if (m.includes('initializing')) {\n return 'Agent is starting up. Try again in a moment.';\n }\n if (m.includes('http 503') || m.includes('agent returned 503')) {\n return 'Agent service unavailable. It may be restarting.';\n }\n return raw.length > 200 ? `${raw.slice(0, 200)}…` : raw;\n}\n\nexport interface UsePluginAvailabilityResult {\n loading: boolean;\n /**\n * True while the very first fetch is in flight (or before it has begun).\n * Consumers should render a loading spinner instead of the install CTA in\n * this state so users don't see a flash of \"plugin required / Install\"\n * followed by a flash of the real content.\n */\n initialLoading: boolean;\n installed: Record<string, boolean>;\n missing: string[];\n anyInstalled: boolean;\n allInstalled: boolean;\n installing: string | null;\n install: (packageName: string) => Promise<boolean>;\n error: string | null;\n /** True when `error` points to the agent itself not being ready. */\n errorIsAgentState: boolean;\n quotaExhausted: boolean;\n clearQuotaExhausted: () => void;\n refetch: () => Promise<void>;\n}\n\nexport function usePluginAvailability(\n packages: readonly string[],\n agentId: string | null\n): UsePluginAvailabilityResult {\n const apollo = useApolloClient();\n const [installPluginMutation] = useInstallAgentPluginMutation();\n\n // Read cached data synchronously so the first render already has the\n // installed map populated when another consumer has primed the cache.\n const cachedInstalledSet = useMemo(() => {\n if (!agentId) return null;\n try {\n const cached = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n const installedList = cached?.agentPlugins?.installed ?? null;\n if (!installedList) return null;\n return new Set(installedList.map((p: { packageName: string }) => p.packageName));\n } catch {\n return null;\n }\n }, [apollo, agentId]);\n\n const [loading, setLoading] = useState(false);\n const [hasFetchedOnce, setHasFetchedOnce] = useState(() => cachedInstalledSet !== null);\n const [installedMap, setInstalledMap] = useState<Record<string, boolean>>(() => {\n const next: Record<string, boolean> = {};\n if (cachedInstalledSet) {\n for (const pkg of packages) next[pkg] = cachedInstalledSet.has(pkg);\n }\n return next;\n });\n const [installing, setInstalling] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [quotaExhausted, setQuotaExhausted] = useState(false);\n const errorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Stable key so we re-run only when the *set* of requested packages changes.\n const packagesKey = useMemo(() => packages.slice().sort().join('|'), [packages]);\n\n // Re-seed local state whenever the agent changes so consumers never\n // render the previous agent's installed map during the refetch window.\n useEffect(() => {\n let cached: Set<string> | null = null;\n if (agentId) {\n try {\n const c = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n const list = c?.agentPlugins?.installed ?? null;\n if (list) cached = new Set(list.map((p: { packageName: string }) => p.packageName));\n } catch {\n cached = null;\n }\n }\n const next: Record<string, boolean> = {};\n if (cached) for (const pkg of packages) next[pkg] = cached.has(pkg);\n setInstalledMap(next);\n setHasFetchedOnce(cached !== null);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agentId, packagesKey, apollo]);\n\n const refetch = useCallback(async () => {\n if (!agentId) {\n setInstalledMap({});\n return;\n }\n setLoading(true);\n try {\n // Use the Apollo client directly with `network-only` so out-of-band\n // installs (CLI, other tabs, agent-side changes) are always picked\n // up. `useAgentPluginsLazyQuery`'s lazy execute() doesn't accept\n // per-call fetchPolicy in Apollo v4; `apollo.query()` does.\n const { data } = await apollo.query<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n fetchPolicy: 'network-only',\n });\n const next: Record<string, boolean> = {};\n const installedList = data?.agentPlugins?.installed ?? [];\n const pluginError = data?.agentPlugins?.error ?? null;\n const installedSet = new Set(\n installedList.map((p: { packageName: string }) => p.packageName)\n );\n for (const pkg of packages) next[pkg] = installedSet.has(pkg);\n if (pluginError) {\n if (!classifyAgentStateError(pluginError)) {\n setInstalledMap(next);\n }\n // Log the raw error for debugging, but only surface a sanitized\n // version to the UI. Raw Bun fetch errors (\"Was there a typo in\n // the url or port?\") would otherwise leak into the install card.\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] agentPlugins returned error', pluginError);\n setError(sanitizeAgentStateError(pluginError));\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!classifyAgentStateError(pluginError)) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n return;\n }\n setInstalledMap(next);\n // Clear any lingering error from a previously-aborted fetch so the\n // gate doesn't keep showing \"Agent not ready\" after a successful\n // subsequent request (e.g. React Strict Mode double-mount).\n setError(null);\n if (errorTimerRef.current) {\n clearTimeout(errorTimerRef.current);\n errorTimerRef.current = null;\n }\n } catch (err) {\n const msg =\n err instanceof Error ? err.message : 'Agent unreachable — unable to read plugin list';\n const isAgentStateError = classifyAgentStateError(msg);\n // Keep the last known installed state while the agent is restarting or\n // finalizing. Otherwise a transient 503 flashes an install CTA for an\n // already-installed plugin.\n if (!isAgentStateError) {\n setInstalledMap({});\n }\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] agentPlugins fetch failed', err);\n setError(sanitizeAgentStateError(msg));\n // State-related errors stay visible until resolved; transient\n // network/GraphQL errors auto-dismiss so the UI stays tidy.\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!isAgentStateError) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n } finally {\n setLoading(false);\n setHasFetchedOnce(true);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agentId, apollo, packagesKey]);\n\n useEffect(() => {\n void refetch();\n }, [refetch]);\n\n // Subscribe to the push stream. The svc emits the installed list\n // whenever it diverges from its previous snapshot, so we only update\n // local state on payloads — and we also write through to the Apollo\n // cache so other consumers reading `agentPlugins` directly see fresh\n // data without their own refetch.\n useVibecontrolsAgentPluginStreamSubscription({\n variables: { agentId: agentId ?? '' },\n skip: !agentId,\n onData: ({ data }) => {\n const installedList = data.data?.vibecontrolsAgentPluginStream;\n if (!installedList || !agentId) return;\n const installedSet = new Set(\n installedList.map((p: { packageName: string }) => p.packageName)\n );\n setInstalledMap((prev) => {\n const next: Record<string, boolean> = { ...prev };\n let changed = false;\n for (const pkg of packages) {\n const has = installedSet.has(pkg);\n if (next[pkg] !== has) {\n next[pkg] = has;\n changed = true;\n }\n }\n return changed ? next : prev;\n });\n setHasFetchedOnce(true);\n // Mirror into Apollo cache so consumers that read AgentPluginsQuery\n // directly (e.g. PluginHarnessPicker) see the same fresh list.\n try {\n const existing = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n apollo.writeQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n data: {\n agentPlugins: {\n __typename: 'AgentPluginListResult',\n installed: installedList,\n available: existing?.agentPlugins?.available ?? [],\n error: null,\n },\n },\n });\n } catch {\n // Cache may not have been primed yet — the next refetch will\n // populate `available` and we'll write again on the next event.\n }\n },\n });\n\n // Also refetch when the tab regains focus so installs made elsewhere flow in.\n useEffect(() => {\n const onFocus = () => void refetch();\n window.addEventListener('focus', onFocus);\n return () => window.removeEventListener('focus', onFocus);\n }, [refetch]);\n\n useEffect(() => {\n return () => {\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n };\n }, []);\n\n const install = useCallback(\n async (packageName: string): Promise<boolean> => {\n if (!agentId) return false;\n setInstalling(packageName);\n setError(null);\n try {\n const { data } = await installPluginMutation({\n variables: { agentId, packageName },\n });\n if (data?.installAgentPlugin?.success) {\n setInstalledMap((prev) => ({ ...prev, [packageName]: true }));\n // Don't immediately refetch: the mutation returns success before the agent\n // finishes installing the npm package. An immediate refetch races the agent\n // and overwrites this optimistic update with a stale \"not installed\" result,\n // putting the install card back up. The subscription stream delivers the\n // confirmed update when the agent is done; a delayed refetch is a fallback\n // for environments where the WebSocket push is slow.\n setTimeout(() => void refetch(), 3000);\n return true;\n }\n throw new Error(data?.installAgentPlugin?.error || 'Install failed');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setQuotaExhausted(true);\n return false;\n }\n const msg = err instanceof Error ? err.message : 'Failed to install plugin';\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] plugin install failed', err);\n setError(sanitizeAgentStateError(msg));\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!classifyAgentStateError(msg)) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n return false;\n } finally {\n setInstalling(null);\n }\n },\n [agentId, installPluginMutation, refetch]\n );\n\n const clearQuotaExhausted = useCallback(() => setQuotaExhausted(false), []);\n\n const missing = useMemo(\n () => packages.filter((pkg) => !installedMap[pkg]),\n [packages, installedMap]\n );\n const anyInstalled = useMemo(\n () => packages.some((pkg) => installedMap[pkg]),\n [packages, installedMap]\n );\n const allInstalled = missing.length === 0 && packages.length > 0;\n\n return {\n loading,\n initialLoading: !hasFetchedOnce,\n installed: installedMap,\n missing,\n anyInstalled,\n allInstalled,\n installing,\n install,\n error,\n errorIsAgentState: classifyAgentStateError(error),\n quotaExhausted,\n clearQuotaExhausted,\n refetch,\n };\n}\n"],"mappings":";;;;;AA8BA,SAAS,EAAwB,GAA6C;AAC5E,KAAI,CAAC,EAAS,QAAO;CACrB,IAAM,IAAI,EAAQ,aAAa;AAC/B,QACE,EAAE,SAAS,2BAA2B,IACtC,EAAE,SAAS,kBAAkB,IAC7B,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,oBAAoB,IAC/B,EAAE,SAAS,qBAAqB,IAChC,EAAE,SAAS,WAAW,IACtB,EAAE,SAAS,gCAAgC,IAC3C,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,oBAAoB,IAC/B,EAAE,SAAS,oBAAoB,IAI/B,EAAE,SAAS,kBAAkB,IAC7B,EAAE,SAAS,oBAAoB,IAC/B,EAAE,SAAS,wBAAwB,IACnC,EAAE,SAAS,qBAAqB,IAChC,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,YAAY,IACvB,EAAE,SAAS,cAAc;;AAe7B,SAAS,EAAwB,GAAqB;CACpD,IAAM,IAAI,EAAI,aAAa;AAuB3B,QArBE,EAAE,SAAS,kBAAkB,IAC7B,EAAE,SAAS,oBAAoB,IAC/B,EAAE,SAAS,wBAAwB,IACnC,EAAE,SAAS,qBAAqB,IAChC,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,YAAY,IACvB,EAAE,SAAS,cAAc,IACzB,EAAE,SAAS,eAAe,IAC1B,EAAE,SAAS,oBAAoB,GAExB,4EAEL,EAAE,SAAS,2BAA2B,IAAI,EAAE,SAAS,kBAAkB,GAClE,yEAEL,EAAE,SAAS,eAAe,GACrB,iDAEL,EAAE,SAAS,WAAW,IAAI,EAAE,SAAS,qBAAqB,GACrD,qDAEF,EAAI,SAAS,MAAM,GAAG,EAAI,MAAM,GAAG,IAAI,CAAC,KAAK;;AA0BtD,SAAgB,EACd,GACA,GAC6B;CAC7B,IAAM,IAAS,GAAiB,EAC1B,CAAC,KAAyB,GAA+B,EAIzD,IAAqB,QAAc;AACvC,MAAI,CAAC,EAAS,QAAO;AACrB,MAAI;GAKF,IAAM,IAJS,EAAO,UAA6B;IACjD,OAAO;IACP,WAAW,EAAE,YAAS;IACvB,CAAC,EAC4B,cAAc,aAAa;AAEzD,UADK,IACE,IAAI,IAAI,EAAc,KAAK,MAA+B,EAAE,YAAY,CAAC,GADrD;UAErB;AACN,UAAO;;IAER,CAAC,GAAQ,EAAQ,CAAC,EAEf,CAAC,GAAS,KAAc,EAAS,GAAM,EACvC,CAAC,GAAgB,KAAqB,QAAe,MAAuB,KAAK,EACjF,CAAC,GAAc,KAAmB,QAAwC;EAC9E,IAAM,IAAgC,EAAE;AACxC,MAAI,EACF,MAAK,IAAM,KAAO,EAAU,GAAK,KAAO,EAAmB,IAAI,EAAI;AAErE,SAAO;GACP,EACI,CAAC,GAAY,KAAiB,EAAwB,KAAK,EAC3D,CAAC,GAAO,KAAY,EAAwB,KAAK,EACjD,CAAC,GAAgB,KAAqB,EAAS,GAAM,EACrD,IAAgB,EAA6C,KAAK,EAGlE,IAAc,QAAc,EAAS,OAAO,CAAC,MAAM,CAAC,KAAK,IAAI,EAAE,CAAC,EAAS,CAAC;AAIhF,SAAgB;EACd,IAAI,IAA6B;AACjC,MAAI,EACF,KAAI;GAKF,IAAM,IAJI,EAAO,UAA6B;IAC5C,OAAO;IACP,WAAW,EAAE,YAAS;IACvB,CAAC,EACc,cAAc,aAAa;AAC3C,GAAI,MAAM,IAAS,IAAI,IAAI,EAAK,KAAK,MAA+B,EAAE,YAAY,CAAC;UAC7E;AACN,OAAS;;EAGb,IAAM,IAAgC,EAAE;AACxC,MAAI,EAAQ,MAAK,IAAM,KAAO,EAAU,GAAK,KAAO,EAAO,IAAI,EAAI;AAEnE,EADA,EAAgB,EAAK,EACrB,EAAkB,MAAW,KAAK;IAEjC;EAAC;EAAS;EAAa;EAAO,CAAC;CAElC,IAAM,IAAU,EAAY,YAAY;AACtC,MAAI,CAAC,GAAS;AACZ,KAAgB,EAAE,CAAC;AACnB;;AAEF,IAAW,GAAK;AAChB,MAAI;GAKF,IAAM,EAAE,YAAS,MAAM,EAAO,MAAyB;IACrD,OAAO;IACP,WAAW,EAAE,YAAS;IACtB,aAAa;IACd,CAAC,EACI,IAAgC,EAAE,EAClC,IAAgB,GAAM,cAAc,aAAa,EAAE,EACnD,IAAc,GAAM,cAAc,SAAS,MAC3C,IAAe,IAAI,IACvB,EAAc,KAAK,MAA+B,EAAE,YAAY,CACjE;AACD,QAAK,IAAM,KAAO,EAAU,GAAK,KAAO,EAAa,IAAI,EAAI;AAC7D,OAAI,GAAa;AAWf,IAVK,EAAwB,EAAY,IACvC,EAAgB,EAAK,EAMvB,QAAQ,MAAM,oCAAoC,EAAY,EAC9D,EAAS,EAAwB,EAAY,CAAC,EAC1C,EAAc,WAAS,aAAa,EAAc,QAAQ,EACzD,EAAwB,EAAY,KACvC,EAAc,UAAU,iBAAiB,EAAS,KAAK,EAAE,IAAK;AAEhE;;AAOF,GALA,EAAgB,EAAK,EAIrB,EAAS,KAAK,EACd,AAEE,EAAc,aADd,aAAa,EAAc,QAAQ,EACX;WAEnB,GAAK;GACZ,IAAM,IACJ,aAAe,QAAQ,EAAI,UAAU,kDACjC,IAAoB,EAAwB,EAAI;AAatD,GATK,KACH,EAAgB,EAAE,CAAC,EAGrB,QAAQ,MAAM,kCAAkC,EAAI,EACpD,EAAS,EAAwB,EAAI,CAAC,EAGlC,EAAc,WAAS,aAAa,EAAc,QAAQ,EACzD,MACH,EAAc,UAAU,iBAAiB,EAAS,KAAK,EAAE,IAAK;YAExD;AAER,GADA,EAAW,GAAM,EACjB,EAAkB,GAAK;;IAGxB;EAAC;EAAS;EAAQ;EAAY,CAAC;AAkElC,CAhEA,QAAgB;AACT,KAAS;IACb,CAAC,EAAQ,CAAC,EAOb,EAA6C;EAC3C,WAAW,EAAE,SAAS,KAAW,IAAI;EACrC,MAAM,CAAC;EACP,SAAS,EAAE,cAAW;GACpB,IAAM,IAAgB,EAAK,MAAM;AACjC,OAAI,CAAC,KAAiB,CAAC,EAAS;GAChC,IAAM,IAAe,IAAI,IACvB,EAAc,KAAK,MAA+B,EAAE,YAAY,CACjE;AAaD,GAZA,GAAiB,MAAS;IACxB,IAAM,IAAgC,EAAE,GAAG,GAAM,EAC7C,IAAU;AACd,SAAK,IAAM,KAAO,GAAU;KAC1B,IAAM,IAAM,EAAa,IAAI,EAAI;AACjC,KAAI,EAAK,OAAS,MAChB,EAAK,KAAO,GACZ,IAAU;;AAGd,WAAO,IAAU,IAAO;KACxB,EACF,EAAkB,GAAK;AAGvB,OAAI;IACF,IAAM,IAAW,EAAO,UAA6B;KACnD,OAAO;KACP,WAAW,EAAE,YAAS;KACvB,CAAC;AACF,MAAO,WAA8B;KACnC,OAAO;KACP,WAAW,EAAE,YAAS;KACtB,MAAM,EACJ,cAAc;MACZ,YAAY;MACZ,WAAW;MACX,WAAW,GAAU,cAAc,aAAa,EAAE;MAClD,OAAO;MACR,EACF;KACF,CAAC;WACI;;EAKX,CAAC,EAGF,QAAgB;EACd,IAAM,UAAgB,KAAK,GAAS;AAEpC,SADA,OAAO,iBAAiB,SAAS,EAAQ,QAC5B,OAAO,oBAAoB,SAAS,EAAQ;IACxD,CAAC,EAAQ,CAAC,EAEb,cACe;AACX,EAAI,EAAc,WAAS,aAAa,EAAc,QAAQ;IAE/D,EAAE,CAAC;CAEN,IAAM,IAAU,EACd,OAAO,MAA0C;AAC/C,MAAI,CAAC,EAAS,QAAO;AAErB,EADA,EAAc,EAAY,EAC1B,EAAS,KAAK;AACd,MAAI;GACF,IAAM,EAAE,YAAS,MAAM,EAAsB,EAC3C,WAAW;IAAE;IAAS;IAAa,EACpC,CAAC;AACF,OAAI,GAAM,oBAAoB,QAS5B,QARA,GAAiB,OAAU;IAAE,GAAG;KAAO,IAAc;IAAM,EAAE,EAO7D,iBAAiB,KAAK,GAAS,EAAE,IAAK,EAC/B;AAET,SAAU,MAAM,GAAM,oBAAoB,SAAS,iBAAiB;WAC7D,GAAK;AACZ,OAAI,EAAsB,EAAI,CAG5B,QAFA,GAAyB,EACzB,EAAkB,GAAK,EAChB;GAET,IAAM,IAAM,aAAe,QAAQ,EAAI,UAAU;AAQjD,UANA,QAAQ,MAAM,8BAA8B,EAAI,EAChD,EAAS,EAAwB,EAAI,CAAC,EAClC,EAAc,WAAS,aAAa,EAAc,QAAQ,EACzD,EAAwB,EAAI,KAC/B,EAAc,UAAU,iBAAiB,EAAS,KAAK,EAAE,IAAK,GAEzD;YACC;AACR,KAAc,KAAK;;IAGvB;EAAC;EAAS;EAAuB;EAAQ,CAC1C,EAEK,IAAsB,QAAkB,EAAkB,GAAM,EAAE,EAAE,CAAC,EAErE,IAAU,QACR,EAAS,QAAQ,MAAQ,CAAC,EAAa,GAAK,EAClD,CAAC,GAAU,EAAa,CACzB,EACK,IAAe,QACb,EAAS,MAAM,MAAQ,EAAa,GAAK,EAC/C,CAAC,GAAU,EAAa,CACzB,EACK,IAAe,EAAQ,WAAW,KAAK,EAAS,SAAS;AAE/D,QAAO;EACL;EACA,gBAAgB,CAAC;EACjB,WAAW;EACX;EACA;EACA;EACA;EACA;EACA;EACA,mBAAmB,EAAwB,EAAM;EACjD;EACA;EACA;EACD"}
1
+ {"version":3,"file":"usePluginAvailability.js","names":[],"sources":["../../src/hooks/usePluginAvailability.ts"],"sourcesContent":["/**\n * usePluginAvailability — query + install helpers for vibecontrols-agent plugins.\n *\n * Wraps the existing backend-proxied GraphQL ops (AgentPlugins query +\n * InstallAgentPlugin mutation) and exposes a small state surface that\n * gating components can render against.\n *\n * Live updates: subscribes to `vibecontrolsAgentPluginStream` so the\n * installed map reflects out-of-band installs (CLI, other tabs,\n * agent-side changes) without per-tab polling. The initial AgentPlugins\n * query is still used for first paint so the gate can render before\n * the WebSocket completes its handshake.\n */\n\nimport { useCallback, useEffect, useMemo, useRef, useState } from 'react';\nimport { useApolloClient } from '@apollo/client/react';\nimport {\n AgentPluginsDocument,\n useInstallAgentPluginMutation,\n useVibecontrolsAgentPluginStreamSubscription,\n type AgentPluginsQuery,\n} from '@/generated/wspace-operations';\nimport { isQuotaExhaustedError, markQuotaHandledLocally } from '@/utils/quotaUtils';\n\n/**\n * Classifies transient \"the agent itself isn't ready\" errors so the UI can\n * render a dedicated waiting state instead of an install CTA. These errors\n * are not the user's to fix via an install click — they resolve on their\n * own (or require Agents-page intervention).\n */\nfunction classifyAgentStateError(message: string | null | undefined): boolean {\n if (!message) return false;\n const m = message.toLowerCase();\n return (\n m.includes('agent not yet configured') ||\n m.includes('awaiting-config') ||\n m.includes('initializing') ||\n m.includes('agent unreachable') ||\n m.includes('agent returned 503') ||\n m.includes('http 503') ||\n m.includes('plugin routes are unavailable') ||\n m.includes('econnrefused') ||\n m.includes('rate_limited') ||\n m.includes('rate limited') ||\n m.includes('operation timeout') ||\n m.includes('operation_timeout') ||\n // Bun's fetch surfaces these human-friendly strings when the agent\n // socket isn't accepting connections. Without this branch, the raw\n // text would leak into the install card as the error chip.\n m.includes('typo in the url') ||\n m.includes('unable to connect') ||\n m.includes('failed to open socket') ||\n m.includes('failedtoopensocket') ||\n m.includes('fetch failed') ||\n m.includes('enotfound') ||\n m.includes('getaddrinfo')\n // Note: \"operation was aborted\" is deliberately NOT classified as an\n // agent-state error. Apollo aborts previous in-flight queries when\n // the same lazy query is re-fired (common in React Strict Mode\n // double-mount). Those aborts are transient and not about agent\n // state; the follow-up fetch succeeds normally.\n );\n}\n\n/**\n * Translate raw error strings (especially Bun fetch's human-friendly but\n * jargon-y errors) into a short, user-safe message. Used right before we\n * stash anything into the surfaced `error` field — guarantees we never\n * render \"Was there a typo in the url or port?\" or similar into the DOM.\n */\nfunction sanitizeAgentStateError(raw: string): string {\n const m = raw.toLowerCase();\n if (\n m.includes('typo in the url') ||\n m.includes('unable to connect') ||\n m.includes('failed to open socket') ||\n m.includes('failedtoopensocket') ||\n m.includes('econnrefused') ||\n m.includes('enotfound') ||\n m.includes('getaddrinfo') ||\n m.includes('fetch failed') ||\n m.includes('agent unreachable')\n ) {\n return 'Agent unreachable. Make sure the vibecontrols-agent is running locally.';\n }\n if (m.includes('agent not yet configured') || m.includes('awaiting-config')) {\n return 'Agent is waiting for configuration. Finish setup in the Agents page.';\n }\n if (m.includes('initializing')) {\n return 'Agent is starting up. Try again in a moment.';\n }\n if (m.includes('http 503') || m.includes('agent returned 503')) {\n return 'Agent service unavailable. It may be restarting.';\n }\n return raw.length > 200 ? `${raw.slice(0, 200)}…` : raw;\n}\n\nexport interface UsePluginAvailabilityResult {\n loading: boolean;\n /**\n * True while the very first fetch is in flight (or before it has begun).\n * Consumers should render a loading spinner instead of the install CTA in\n * this state so users don't see a flash of \"plugin required / Install\"\n * followed by a flash of the real content.\n */\n initialLoading: boolean;\n installed: Record<string, boolean>;\n missing: string[];\n anyInstalled: boolean;\n allInstalled: boolean;\n installing: string | null;\n install: (packageName: string) => Promise<boolean>;\n error: string | null;\n /** True when `error` points to the agent itself not being ready. */\n errorIsAgentState: boolean;\n quotaExhausted: boolean;\n clearQuotaExhausted: () => void;\n refetch: () => Promise<void>;\n}\n\nexport function usePluginAvailability(\n packages: readonly string[],\n agentId: string | null\n): UsePluginAvailabilityResult {\n const apollo = useApolloClient();\n const [installPluginMutation] = useInstallAgentPluginMutation();\n\n // Read cached data synchronously so the first render already has the\n // installed map populated when another consumer has primed the cache.\n const cachedInstalledSet = useMemo(() => {\n if (!agentId) return null;\n try {\n const cached = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n const installedList = cached?.agentPlugins?.installed ?? null;\n if (!installedList) return null;\n return new Set(installedList.map((p: { packageName: string }) => p.packageName));\n } catch {\n return null;\n }\n }, [apollo, agentId]);\n\n const [loading, setLoading] = useState(false);\n const [hasFetchedOnce, setHasFetchedOnce] = useState(() => cachedInstalledSet !== null);\n const [installedMap, setInstalledMap] = useState<Record<string, boolean>>(() => {\n const next: Record<string, boolean> = {};\n if (cachedInstalledSet) {\n for (const pkg of packages) next[pkg] = cachedInstalledSet.has(pkg);\n }\n return next;\n });\n const [installing, setInstalling] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [quotaExhausted, setQuotaExhausted] = useState(false);\n const errorTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n // Stable key so we re-run only when the *set* of requested packages changes.\n const packagesKey = useMemo(() => packages.slice().sort().join('|'), [packages]);\n\n // Re-seed local state whenever the agent changes so consumers never\n // render the previous agent's installed map during the refetch window.\n useEffect(() => {\n let cached: Set<string> | null = null;\n if (agentId) {\n try {\n const c = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n const list = c?.agentPlugins?.installed ?? null;\n if (list) cached = new Set(list.map((p: { packageName: string }) => p.packageName));\n } catch {\n cached = null;\n }\n }\n const next: Record<string, boolean> = {};\n if (cached) for (const pkg of packages) next[pkg] = cached.has(pkg);\n setInstalledMap(next);\n setHasFetchedOnce(cached !== null);\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agentId, packagesKey, apollo]);\n\n const refetch = useCallback(async () => {\n if (!agentId) {\n setInstalledMap({});\n return;\n }\n setLoading(true);\n try {\n // Use the Apollo client directly with `network-only` so out-of-band\n // installs (CLI, other tabs, agent-side changes) are always picked\n // up. `useAgentPluginsLazyQuery`'s lazy execute() doesn't accept\n // per-call fetchPolicy in Apollo v4; `apollo.query()` does.\n const { data } = await apollo.query<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n fetchPolicy: 'network-only',\n });\n const next: Record<string, boolean> = {};\n const installedList = data?.agentPlugins?.installed ?? [];\n const pluginError = data?.agentPlugins?.error ?? null;\n const installedSet = new Set(\n installedList.map((p: { packageName: string }) => p.packageName)\n );\n for (const pkg of packages) next[pkg] = installedSet.has(pkg);\n if (pluginError) {\n if (!classifyAgentStateError(pluginError)) {\n setInstalledMap(next);\n }\n // Log the raw error for debugging, but only surface a sanitized\n // version to the UI. Raw Bun fetch errors (\"Was there a typo in\n // the url or port?\") would otherwise leak into the install card.\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] agentPlugins returned error', pluginError);\n setError(sanitizeAgentStateError(pluginError));\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!classifyAgentStateError(pluginError)) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n return;\n }\n setInstalledMap(next);\n // Clear any lingering error from a previously-aborted fetch so the\n // gate doesn't keep showing \"Agent not ready\" after a successful\n // subsequent request (e.g. React Strict Mode double-mount).\n setError(null);\n if (errorTimerRef.current) {\n clearTimeout(errorTimerRef.current);\n errorTimerRef.current = null;\n }\n } catch (err) {\n const msg =\n err instanceof Error ? err.message : 'Agent unreachable — unable to read plugin list';\n const isAgentStateError = classifyAgentStateError(msg);\n // Keep the last known installed state while the agent is restarting or\n // finalizing. Otherwise a transient 503 flashes an install CTA for an\n // already-installed plugin.\n if (!isAgentStateError) {\n setInstalledMap({});\n }\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] agentPlugins fetch failed', err);\n setError(sanitizeAgentStateError(msg));\n // State-related errors stay visible until resolved; transient\n // network/GraphQL errors auto-dismiss so the UI stays tidy.\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!isAgentStateError) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n } finally {\n setLoading(false);\n setHasFetchedOnce(true);\n }\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [agentId, apollo, packagesKey]);\n\n useEffect(() => {\n void refetch();\n }, [refetch]);\n\n // Subscribe to the push stream. The svc emits the installed list\n // whenever it diverges from its previous snapshot, so we only update\n // local state on payloads — and we also write through to the Apollo\n // cache so other consumers reading `agentPlugins` directly see fresh\n // data without their own refetch.\n useVibecontrolsAgentPluginStreamSubscription({\n variables: { agentId: agentId ?? '' },\n skip: !agentId,\n onData: ({ data }) => {\n const installedList = data.data?.vibecontrolsAgentPluginStream;\n if (!installedList || !agentId) return;\n const installedSet = new Set(\n installedList.map((p: { packageName: string }) => p.packageName)\n );\n setInstalledMap((prev) => {\n const next: Record<string, boolean> = { ...prev };\n let changed = false;\n for (const pkg of packages) {\n const has = installedSet.has(pkg);\n if (next[pkg] !== has) {\n next[pkg] = has;\n changed = true;\n }\n }\n return changed ? next : prev;\n });\n setHasFetchedOnce(true);\n // Mirror into Apollo cache so consumers that read AgentPluginsQuery\n // directly (e.g. PluginHarnessPicker) see the same fresh list.\n try {\n const existing = apollo.readQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n });\n apollo.writeQuery<AgentPluginsQuery>({\n query: AgentPluginsDocument,\n variables: { agentId },\n data: {\n agentPlugins: {\n __typename: 'AgentPluginListResult',\n installed: installedList,\n available: existing?.agentPlugins?.available ?? [],\n error: null,\n },\n },\n });\n } catch {\n // Cache may not have been primed yet — the next refetch will\n // populate `available` and we'll write again on the next event.\n }\n },\n });\n\n // Also refetch when the tab regains focus so installs made elsewhere flow in.\n useEffect(() => {\n const onFocus = () => void refetch();\n window.addEventListener('focus', onFocus);\n return () => window.removeEventListener('focus', onFocus);\n }, [refetch]);\n\n useEffect(() => {\n return () => {\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n };\n }, []);\n\n const install = useCallback(\n async (packageName: string): Promise<boolean> => {\n if (!agentId) return false;\n setInstalling(packageName);\n setError(null);\n try {\n const { data } = await installPluginMutation({\n variables: { agentId, packageName },\n });\n if (data?.installAgentPlugin?.success) {\n setInstalledMap((prev) => ({ ...prev, [packageName]: true }));\n // Don't immediately refetch — the mutation returns success the\n // moment the agent's plugin manager accepts the install, but the\n // npm-level fetch + manifest publish takes a beat. An eager\n // refetch races that and overwrites our optimistic flip with a\n // stale \"not installed\" result, putting the install card right\n // back up.\n //\n // The subscription stream is the primary signal; this delayed\n // refetch is the fallback for environments where the SSE upgrade\n // is slow. 1200ms is enough for the agent to publish the new\n // entry on the plugin stream in the common case, short enough\n // that the user doesn't notice when AgentManagerPage's\n // onInstalled fires a window.location.reload() at 1500ms.\n setTimeout(() => void refetch(), 1200);\n return true;\n }\n throw new Error(data?.installAgentPlugin?.error || 'Install failed');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n markQuotaHandledLocally();\n setQuotaExhausted(true);\n return false;\n }\n const msg = err instanceof Error ? err.message : 'Failed to install plugin';\n // eslint-disable-next-line no-console -- intentional: keep technical detail for debugging\n console.error('[ai] plugin install failed', err);\n setError(sanitizeAgentStateError(msg));\n if (errorTimerRef.current) clearTimeout(errorTimerRef.current);\n if (!classifyAgentStateError(msg)) {\n errorTimerRef.current = setTimeout(() => setError(null), 5000);\n }\n return false;\n } finally {\n setInstalling(null);\n }\n },\n [agentId, installPluginMutation, refetch]\n );\n\n const clearQuotaExhausted = useCallback(() => setQuotaExhausted(false), []);\n\n const missing = useMemo(\n () => packages.filter((pkg) => !installedMap[pkg]),\n [packages, installedMap]\n );\n const anyInstalled = useMemo(\n () => packages.some((pkg) => installedMap[pkg]),\n [packages, installedMap]\n );\n const allInstalled = missing.length === 0 && packages.length > 0;\n\n return {\n loading,\n initialLoading: !hasFetchedOnce,\n installed: installedMap,\n missing,\n anyInstalled,\n allInstalled,\n installing,\n install,\n error,\n errorIsAgentState: classifyAgentStateError(error),\n quotaExhausted,\n clearQuotaExhausted,\n refetch,\n };\n}\n"],"mappings":"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"}