@burdenoff/microfe-vibecontrols 2026.518.2 → 2026.519.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.
- package/dist/pages/AgentsPage.js +7 -4
- package/dist/pages/AgentsPage.js.map +1 -1
- package/dist/pages/CatalogDetailPage.js +333 -289
- package/dist/pages/CatalogDetailPage.js.map +1 -1
- package/dist/pages/ComponentDetailPage.js +243 -199
- package/dist/pages/ComponentDetailPage.js.map +1 -1
- package/dist/pages/TemplateDetailPage.js +104 -71
- package/dist/pages/TemplateDetailPage.js.map +1 -1
- package/package.json +2 -2
package/dist/pages/AgentsPage.js
CHANGED
|
@@ -133,7 +133,8 @@ function y() {
|
|
|
133
133
|
fetchPolicy: "cache-and-network",
|
|
134
134
|
nextFetchPolicy: "cache-first",
|
|
135
135
|
notifyOnNetworkStatusChange: !0,
|
|
136
|
-
skip: !b
|
|
136
|
+
skip: !b,
|
|
137
|
+
pollInterval: 3e4
|
|
137
138
|
}), { data: J } = ae({ skip: !b }), kt = q.data === void 0 && J === void 0, Y = l(() => {
|
|
138
139
|
let e = /* @__PURE__ */ new Map(), t = J?.vibecontrolsAgentHealthProbeStream ?? q.data?.vibecontrolsAgentHealthProbes ?? [];
|
|
139
140
|
for (let n of t) e.set(n.agentId, {
|
|
@@ -409,11 +410,13 @@ function y() {
|
|
|
409
410
|
actions: /* @__PURE__ */ h("div", {
|
|
410
411
|
className: "flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center",
|
|
411
412
|
children: [/* @__PURE__ */ m("button", {
|
|
412
|
-
onClick: () =>
|
|
413
|
-
|
|
413
|
+
onClick: () => {
|
|
414
|
+
K(), q.refetch();
|
|
415
|
+
},
|
|
416
|
+
disabled: G || q.loading,
|
|
414
417
|
className: "flex items-center justify-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50",
|
|
415
418
|
title: S("agentsPage.refreshTitle", "Refresh agents"),
|
|
416
|
-
children: /* @__PURE__ */ m(Ae, { className: `w-4 h-4 ${G ? "animate-spin" : ""}` })
|
|
419
|
+
children: /* @__PURE__ */ m(Ae, { className: `w-4 h-4 ${G || q.loading ? "animate-spin" : ""}` })
|
|
417
420
|
}), St ? /* @__PURE__ */ m(Le, {
|
|
418
421
|
permission: be.AGENT_CREATE,
|
|
419
422
|
deniedBehavior: "disable",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"AgentsPage.js","names":[],"sources":["../../src/pages/AgentsPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useCallback, useEffect } from 'react';\nimport { Plus, Bot, Grid, List, RefreshCw, Trash2, CheckSquare, XSquare } from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { AgentCard, AgentList, AgentForm } from '@/components/agents';\nimport type { Agent } from '@/components/agents';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n TagFilter,\n EmptyState,\n LoadingState,\n ErrorState,\n AgentSetupBanner,\n QuotaExhaustedDialog,\n GetHelpButton,\n} from '@/components/shared';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useListAgentsQuery,\n useVibecontrolsAgentHealthProbesQuery,\n useVibecontrolsAgentHealthProbeStreamSubscription,\n useCreateAgentMutation,\n useUpdateAgentMutation,\n useDeleteAgentMutation,\n useAgentHeartbeatMutation,\n useDeactivateAgentMutation,\n useCreateTargetMutation,\n useAddAgentFromTunnelMutation,\n useStartVibecontrolsAgentMutation,\n useStopVibecontrolsAgentMutation,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { deriveAgentStatus, type AgentHealthProbe } from '@/utils/agentStatus';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Agents management page with filtering, search, and CRUD operations\n */\nexport function AgentsPage() {\n const { onAgentSelect, workspaceId } = useVibeControls();\n const handleNavigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const { t } = useI18n();\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const statusOptions = [\n { value: 'all', label: tr('agentsPage.statusAll', 'All Statuses') },\n { value: 'ACTIVE', label: tr('agentsPage.statusActive', 'Active') },\n { value: 'STOPPED', label: tr('agentsPage.statusStopped', 'Stopped') },\n { value: 'OFFLINE', label: tr('agentsPage.statusOffline', 'Offline') },\n ];\n\n const platformOptions = [\n { value: 'all', label: tr('agentsPage.platformAll', 'All Platforms') },\n { value: 'darwin', label: tr('agentsPage.platformMac', 'macOS') },\n { value: 'linux', label: tr('agentsPage.platformLinux', 'Linux') },\n { value: 'windows', label: tr('agentsPage.platformWindows', 'Windows') },\n ];\n\n // Check permissions\n const canListAgents = hasPermission(VIBE_PERMISSIONS.AGENT_LIST);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [platformFilter, setPlatformFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [isCreating, setIsCreating] = useState(false);\n const [editTunnelAgent, setEditTunnelAgent] = useState<Agent | null>(null);\n const [isSavingTunnel, setIsSavingTunnel] = useState(false);\n // Per-agent \"connecting\" overlay shown after Edit Tunnel save while the\n // backend pushes OAuth creds → agent finalize() → /health/ready flips to\n // ok. State lives in the global store (vibeControlsStore) so the overlay\n // survives navigation to AgentDetailsPage and back — the documented\n // recovery path navigates into the details page so pushAgentGatewayAuth\n // auto-recovery can fire, and storing the set in local React state used\n // to discard it on unmount, causing a CONNECTING → DEGRADED → ACTIVE\n // flicker on return.\n const connectingAgents = useVibeControlsStore((s) => s.connectingAgents);\n const markAgentConnecting = useVibeControlsStore((s) => s.markAgentConnecting);\n const clearAgentConnecting = useVibeControlsStore((s) => s.clearAgentConnecting);\n const connectingAgentIds = useMemo(\n () => new Set(Object.keys(connectingAgents)),\n [connectingAgents]\n );\n\n // 30s safety net — if a probe never reports healthy, drop the overlay\n // so the user eventually sees the real DEGRADED state.\n const connectingTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n useEffect(() => {\n const timers = connectingTimersRef.current;\n for (const agentId of Object.keys(connectingAgents)) {\n if (timers.has(agentId)) continue;\n const timer = setTimeout(() => {\n clearAgentConnecting(agentId);\n timers.delete(agentId);\n }, 30_000);\n timers.set(agentId, timer);\n }\n // Drop timers for agents no longer marked connecting.\n for (const [agentId, timer] of timers.entries()) {\n if (!(agentId in connectingAgents)) {\n clearTimeout(timer);\n timers.delete(agentId);\n }\n }\n }, [connectingAgents, clearAgentConnecting]);\n\n useEffect(() => {\n const timers = connectingTimersRef.current;\n return () => {\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n };\n }, []);\n const [disconnectConfirmAgent, setDisconnectConfirmAgent] = useState<Agent | null>(null);\n const [isDisconnecting, setIsDisconnecting] = useState(false);\n const tunnelUrlRef = useRef<HTMLInputElement>(null);\n const tunnelApiKeyRef = useRef<HTMLInputElement>(null);\n\n // Bulk selection state\n const [selectedAgentIds, setSelectedAgentIds] = useState<Set<string>>(new Set());\n const [bulkLoading, setBulkLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const toastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n };\n }, []);\n\n const showSuccess = useCallback((msg: string) => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n setErrorMessage(null);\n setSuccessMessage(msg);\n toastTimerRef.current = setTimeout(() => setSuccessMessage(null), 3000);\n }, []);\n const showError = useCallback((msg: string) => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n setSuccessMessage(null);\n setErrorMessage(msg);\n toastTimerRef.current = setTimeout(() => setErrorMessage(null), 5000);\n }, []);\n\n const [createAgent] = useCreateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [addAgentFromTunnel] = useAddAgentFromTunnelMutation({ refetchQueries: ['ListAgents'] });\n const [agentHeartbeat] = useAgentHeartbeatMutation({ refetchQueries: ['ListAgents'] });\n const [deactivateAgent] = useDeactivateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [startAgentMutation] = useStartVibecontrolsAgentMutation({\n refetchQueries: ['ListAgents'],\n });\n const [stopAgentMutation] = useStopVibecontrolsAgentMutation({ refetchQueries: ['ListAgents'] });\n\n const {\n targets: cachedTargets,\n loading: initialDataLoading,\n refetch: refetchInitialData,\n } = useCachedInitialData();\n const targetsList = cachedTargets;\n const targetsLoading = initialDataLoading;\n const hasTargets = targetsList.length > 0;\n const [updateAgent] = useUpdateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [deleteAgent] = useDeleteAgentMutation({ refetchQueries: ['ListAgents'] });\n // refetchQueries + awaitRefetchQueries: the inline create flow used to fire\n // refetchInitialData() without awaiting it, so AgentForm closed and\n // re-rendered against stale `cachedTargets` — the new target only appeared\n // on full page reload. Awaiting the underlying queries here makes the\n // mutation resolve only after the initial-data + targets caches are fresh.\n const [createTargetMutation] = useCreateTargetMutation({\n refetchQueries: ['VibeControlsInitialData', 'ListTargets'],\n awaitRefetchQueries: true,\n });\n\n /**\n * Inline DIRECT target creation callback for the AgentForm. Returns\n * the newly created target so the form can auto-select it.\n */\n const handleInlineCreateTarget = useCallback(\n async (targetName: string) => {\n if (!workspaceId || !targetName.trim()) return null;\n try {\n const { data: result } = await createTargetMutation({\n variables: {\n workspaceId,\n input: {\n type: 'DIRECT',\n name: targetName.trim(),\n },\n },\n });\n const created = result?.createTarget;\n if (!created) return null;\n refetchInitialData();\n return {\n id: created.id,\n name: created.name,\n type: created.type ?? 'DIRECT',\n status: created.status ?? 'unknown',\n };\n } catch {\n return null;\n }\n },\n [createTargetMutation, refetchInitialData, workspaceId]\n );\n\n // Fetch agents from GraphQL API\n // Skip until a workspace is selected: the resolver requires\n // x-workspace-id and throws \"Workspace context required\" when fired\n // before the auth provider has resolved a workspace (URL with ?org but\n // no &workspace, post-context-switcher self-heal, etc.). Without this\n // skip, the page lands on \"Failed to load agents\" and never recovers\n // because Apollo's errorPolicy: 'all' returns the error and doesn't\n // auto-refetch when workspaceId becomes available.\n const { data, loading, error, refetch } = useListAgentsQuery({\n variables: {\n filter: {\n isActive: statusFilter === 'ACTIVE' ? true : statusFilter === 'OFFLINE' ? false : undefined,\n platform: platformFilter !== 'all' ? platformFilter : undefined,\n },\n pagination: { limit: 100 },\n },\n fetchPolicy: 'cache-first',\n skip: !workspaceId,\n });\n\n // Live deep-health probe — backend hits /health/ready on every agent and\n // returns per-agent { healthy, errorMessage }. Polled every 30s so stale\n // tunnels (cloudflared dead, agent crashed but `isActive` still true)\n // flip the badge to DEGRADED before users hit \"create session\" against\n // an unreachable agent.\n // Initial query primes the cache; the subscription below replaces the\n // prior 30s pollInterval so badges flip to/from DEGRADED in <1s.\n const probeQuery = useVibecontrolsAgentHealthProbesQuery({\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n notifyOnNetworkStatusChange: true,\n skip: !workspaceId,\n });\n const { data: probeStreamData } = useVibecontrolsAgentHealthProbeStreamSubscription({\n skip: !workspaceId,\n });\n // True when no probe data has been delivered for this mount cycle yet.\n // Used by deriveAgentStatus to show REGISTERING for agents we have not\n // yet probed (instead of optimistically ACTIVE).\n const probesLoading = probeQuery.data === undefined && probeStreamData === undefined;\n const probesById = useMemo(() => {\n const m = new Map<string, AgentHealthProbe>();\n // Subscription stream wins over the initial query — strictly newer.\n const probes =\n probeStreamData?.vibecontrolsAgentHealthProbeStream ??\n probeQuery.data?.vibecontrolsAgentHealthProbes ??\n [];\n for (const p of probes) {\n m.set(p.agentId, {\n agentId: p.agentId,\n healthy: p.healthy,\n errorMessage: p.errorMessage,\n });\n }\n return m;\n }, [probeQuery.data, probeStreamData]);\n\n // Map GraphQL response to Agent type\n const agents: Agent[] = useMemo(() => {\n const rawAgents = data?.vibecontrolsAgents ?? [];\n return rawAgents.map((agent: NonNullable<typeof rawAgents>[number]) => ({\n id: agent.id,\n workspaceId: agent.workspaceId,\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture,\n version: agent.version,\n isActive: agent.isActive,\n status: connectingAgentIds.has(agent.id)\n ? ('CONNECTING' as const)\n : deriveAgentStatus(\n {\n id: agent.id,\n isActive: agent.isActive,\n metadata: agent.metadata as Record<string, unknown> | null,\n },\n probesById,\n { probesLoading }\n ),\n // Prefer the live health probe error. When the live probe didn't run\n // (or returned ok) but the agent's most-recent gateway-auth push\n // failed, surface that instead — otherwise the only signal of a\n // failed onboarding is a yellow Degraded badge with no hover text.\n // `pending` is hidden: it just means the async finalize is still\n // running and the badge already shows the right transient state.\n healthError:\n probesById.get(agent.id)?.errorMessage ??\n (agent.lastGatewayAuthPushStatus === 'failed'\n ? (agent.lastGatewayAuthPushError ?? 'Gateway-auth push failed')\n : null),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n agentApiKey: agent.agentApiKey ?? null,\n configuration: (agent.configuration as Record<string, unknown>) || {},\n capabilities: Array.isArray(agent.capabilities)\n ? (agent.capabilities as string[])\n : typeof agent.capabilities === 'object' && agent.capabilities !== null\n ? (() => {\n const obj = agent.capabilities as Record<string, unknown>;\n const keys = Object.keys(obj);\n // When capabilities was serialized from an array, keys are numeric (\"0\",\"1\",\"2\")\n // In that case, extract values (the actual capability names) instead of keys\n const hasNumericKeys = keys.length > 0 && keys.every((k) => /^\\d+$/.test(k));\n return hasNumericKeys ? (Object.values(obj) as string[]) : (keys as string[]);\n })()\n : [],\n metadata: (agent.metadata as Record<string, unknown>) || {},\n createdAt: agent.createdAt,\n updatedAt: agent.updatedAt,\n tagAssignments: (agent.tagAssignments ?? []).map(\n (a: NonNullable<typeof agent.tagAssignments>[number]) => ({\n id: a.id,\n tagId: a.tagId,\n tag: a.tagInfo\n ? {\n id: a.tagInfo.id,\n key: a.tagInfo.key ?? null,\n label: a.tagInfo.label ?? null,\n color: a.tagInfo.color ?? null,\n }\n : null,\n })\n ),\n }));\n }, [data?.vibecontrolsAgents, probesById, probesLoading, connectingAgentIds]);\n\n // Clear CONNECTING overlay only when ALL of these hold:\n // 1. The live probe reports `healthy: true` (deep-health probe, which\n // already gates on session.ok + tunnel.ok + ttyd.ok server-side —\n // see probeAgentHealth in wspace-vibecontrols-svc).\n // 2. The grace-window timestamp stored at markAgentConnecting time has\n // elapsed. This defends against a STALE `healthy:true` probe\n // delivered from the Apollo cache during the in-flight refetch\n // that follows updateAgent — without the grace window, that stale\n // cache hit cleared CONNECTING immediately and the next (fresh,\n // transient) probe landing during cloudflared restart flipped the\n // badge to DEGRADED, producing the CONNECTING → DEGRADED → ACTIVE\n // flicker reported by users.\n //\n // Re-runs on `probesById` updates AND on a one-shot timer per pending\n // agent so the clear fires automatically once the grace window elapses\n // even if no new probe arrives.\n useEffect(() => {\n const pending = Object.keys(connectingAgents);\n if (pending.length === 0) return;\n const now = Date.now();\n let earliestPending = Infinity;\n for (const agentId of pending) {\n const minClearAt = connectingAgents[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && now >= minClearAt) {\n clearAgentConnecting(agentId);\n } else if (minClearAt < earliestPending) {\n earliestPending = minClearAt;\n }\n }\n if (earliestPending === Infinity) return;\n const wait = Math.max(0, earliestPending - now) + 50;\n const tick = setTimeout(() => {\n // No-op state poke — the dependency on `connectingAgents` causes\n // this effect to re-run when state changes, but a healthy probe\n // arriving BEFORE the grace window elapses needs a re-evaluation\n // when the window finally closes. Re-marking with graceMs=0 is a\n // no-op when the entry is already past its window; if cleared in\n // the meantime, the early `pending.length === 0` short-circuit\n // makes this safe.\n const fresh = useVibeControlsStore.getState().connectingAgents;\n for (const agentId of Object.keys(fresh)) {\n const minClearAt = fresh[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && Date.now() >= minClearAt) {\n clearAgentConnecting(agentId);\n }\n }\n }, wait);\n return () => clearTimeout(tick);\n }, [probesById, connectingAgents, clearAgentConnecting]);\n\n // Filter agents based on search and tag (client-side filtering).\n // Status / platform filters are applied server-side via the GraphQL filter\n // input above; these client-side filters compose on top of that result set.\n const filteredAgents = useMemo(() => {\n return agents.filter((agent) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n agent.name.toLowerCase().includes(query) ||\n agent.hostname?.toLowerCase().includes(query) ||\n agent.id.toLowerCase().includes(query);\n if (!matchesSearch) return false;\n }\n\n // Tag filter — match against structured tagAssignments (UUIDs).\n // Never falls back to the legacy free-text agent.tags array because\n // those values are not the same as tagFilterIds and would silently\n // produce empty results (the failure mode that got the previous\n // TagFilter implementation removed in commit 4e101ca).\n if (tagFilter !== 'all') {\n const hasTag = (agent.tagAssignments ?? []).some(\n (a: { tagId: string }) => a.tagId === tagFilter\n );\n if (!hasTag) return false;\n }\n\n return true;\n });\n }, [agents, searchQuery, tagFilter]);\n\n useEffect(() => {\n return () => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n };\n }, []);\n\n const handleAgentSelect = (agent: Agent) => {\n // Navigate to agent details page\n handleNavigate(`/agents/${agent.id}`);\n // Also call the shell callback if provided\n onAgentSelect?.({\n id: agent.id,\n name: agent.name,\n type: agent.platform || 'unknown',\n status: agent.status || (agent.isActive ? 'ACTIVE' : 'OFFLINE'),\n });\n };\n\n const handleCreateAgent = () => {\n setShowCreateForm(true);\n };\n\n const handleCreateAgentSubmit = async (\n formData: Record<string, unknown>\n ): Promise<{ id: string } | void> => {\n try {\n setIsCreating(true);\n\n const tunnelUrl = (formData.tunnelUrl as string | undefined)?.trim();\n const agentApiKey = (formData.agentApiKey as string | undefined)?.trim();\n\n // Zero-config path: operator pasted the agent's tunnel URL + API key\n // shown in its startup banner. Backend probes the agent, registers it,\n // mints an OAuth app, and pushes credentials — the agent promotes\n // itself to `ready` inside that final POST /api/agent/gateway-auth.\n if (tunnelUrl && agentApiKey) {\n const result = await addAgentFromTunnel({\n variables: {\n input: {\n tunnelUrl,\n apiKey: agentApiKey,\n ...(formData.name ? { name: formData.name as string } : {}),\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n },\n },\n });\n if (result.error) throw result.error;\n const payload = result.data?.addVibecontrolsAgentFromTunnel;\n if (!payload?.success) {\n throw new Error(\n payload?.error || 'Failed to onboard agent — probe or OAuth setup failed.'\n );\n }\n if (payload.agent?.id) return { id: payload.agent.id };\n throw new Error('Agent onboarding returned no agent record.');\n }\n\n // Legacy manual path — operator typed hostname/platform/etc directly.\n const result = await createAgent({\n variables: {\n input: {\n name: formData.name as string,\n hostname: formData.hostname as string,\n platform: formData.platform as string,\n architecture: formData.architecture as string,\n version: formData.version as string,\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n ...(formData.tunnelUrl ? { tunnelUrl: formData.tunnelUrl as string } : {}),\n ...(formData.tunnelStatus ? { tunnelStatus: formData.tunnelStatus as string } : {}),\n },\n },\n });\n\n const createdAgent = result.data?.createVibecontrolsAgent;\n if (createdAgent?.id) return { id: createdAgent.id };\n\n if (result.error) {\n throw result.error;\n }\n\n throw new Error('Agent creation failed — no data returned from the server.');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowQuotaExhausted(true);\n setShowCreateForm(false);\n return;\n }\n\n console.error('Failed to create agent:', err);\n throw err;\n } finally {\n setIsCreating(false);\n }\n };\n\n const handleStartAgent = async (agent: Agent) => {\n try {\n const result = await startAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.startVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to start agent');\n }\n\n await agentHeartbeat({ variables: { id: agent.id } });\n showSuccess(`Agent \"${agent.name}\" started`);\n } catch (err) {\n console.error('Failed to start agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to start agent');\n }\n };\n\n // NOTE: Stop and Disconnect both call deactivateVibecontrolsAgent and produce the same\n // server-side list state (isActive: false). The distinction is that Disconnect additionally\n // clears tunnel fields (tunnelUrl, tunnelStatus). When a dedicated stopVibecontrolsAgent\n // mutation succeeds, we still deactivate in workspace state so the list reflects the agent as offline.\n const handleStopAgent = async (agent: Agent) => {\n try {\n const result = await stopAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.stopVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to stop agent');\n }\n\n await deactivateAgent({ variables: { id: agent.id } });\n showSuccess(`Agent \"${agent.name}\" stopped`);\n } catch (err) {\n console.error('Failed to stop agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to stop agent');\n }\n };\n\n const handleDisconnectAgent = (agent: Agent) => {\n setDisconnectConfirmAgent(agent);\n };\n\n const handleConfirmDisconnect = async () => {\n if (!disconnectConfirmAgent) return;\n const agent = disconnectConfirmAgent;\n setIsDisconnecting(true);\n // Deactivate + clear tunnel info so the agent appears fully disconnected.\n // Two sequential mutations with refetchQueries suppressed — a single refetch()\n // is called after both complete so the list doesn't flash with stale tunnel fields.\n // A single atomic disconnectVibecontrolsAgent backend mutation that handles\n // both deactivation and tunnel field clearing is tracked in vibecontrols-svc\n // backlog; until then the two-step approach below is the supported flow.\n try {\n await deactivateAgent({ variables: { id: agent.id }, refetchQueries: [] });\n } catch (err) {\n console.error('Failed to deactivate agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to disconnect agent');\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Early-return path: refetch here so the list reflects the deactivated state,\n // then return so the bottom refetch below does not also fire.\n await refetch();\n return;\n }\n try {\n await updateAgent({\n variables: {\n id: agent.id,\n input: { tunnelUrl: null, tunnelStatus: null, agentApiKey: null },\n },\n refetchQueries: [],\n });\n showSuccess(`Agent \"${agent.name}\" disconnected`);\n } catch (err) {\n console.error('Failed to clear tunnel fields:', err);\n showError(\n `Agent marked offline, but failed to clear connection fields — clear them manually in Settings.`\n );\n }\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Both updateAgent success and updateAgent failure reach here — single refetch covers both.\n await refetch();\n };\n\n const handleConfigureAgent = (agent: Agent) => {\n // Navigate to agent details page (settings tab)\n handleNavigate(`/agents/${agent.id}`);\n };\n\n const handleEditTunnelUrl = (agent: Agent) => {\n setEditTunnelAgent(agent);\n };\n\n const handleSaveTunnelSettings = async () => {\n if (!editTunnelAgent) return;\n const agentId = editTunnelAgent.id;\n const newUrl = tunnelUrlRef.current?.value?.trim() || '';\n const newApiKey = tunnelApiKeyRef.current?.value?.trim() || '';\n try {\n setIsSavingTunnel(true);\n await updateAgent({\n variables: {\n id: agentId,\n input: {\n tunnelUrl: newUrl || null,\n tunnelStatus: newUrl ? 'ACTIVE' : null,\n agentApiKey: newApiKey || null,\n },\n },\n // Force a fresh probe immediately so the badge resolves to the\n // real state (instead of relying on the 30s subscription cadence).\n refetchQueries: ['ListAgents', 'VibecontrolsAgentHealthProbes'],\n });\n // Begin the \"Connecting…\" overlay BEFORE clearing the dialog so the\n // operator sees an unbroken progress signal: dialog spinner →\n // CONNECTING badge with spinner → ACTIVE / DEGRADED.\n if (newUrl) markAgentConnecting(agentId);\n setEditTunnelAgent(null);\n } catch (err) {\n console.error('Failed to update tunnel settings:', err);\n } finally {\n setIsSavingTunnel(false);\n }\n };\n\n // ── Bulk Selection ─────────────────────────────────────────────────\n const toggleAgentSelection = useCallback((agentId: string) => {\n setSelectedAgentIds((prev) => {\n const next = new Set(prev);\n if (next.has(agentId)) {\n next.delete(agentId);\n } else {\n next.add(agentId);\n }\n return next;\n });\n }, []);\n\n const selectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set(filteredAgents.map((a) => a.id)));\n }, [filteredAgents]);\n\n const deselectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set());\n }, []);\n\n const isAllSelected =\n filteredAgents.length > 0 && selectedAgentIds.size === filteredAgents.length;\n const hasSelection = selectedAgentIds.size > 0;\n\n // ── Bulk Delete ──────────────────────────────────────────────────\n const handleBulkDelete = useCallback(async () => {\n if (selectedAgentIds.size === 0) return;\n if (\n !window.confirm(\n tr(\n 'agentsPage.bulkDeleteConfirm',\n 'Are you sure you want to remove {{count}} agent(s)? This cannot be undone.',\n { count: selectedAgentIds.size }\n )\n )\n )\n return;\n\n setBulkLoading(true);\n try {\n const results = await Promise.allSettled(\n Array.from(selectedAgentIds).map((id) => deleteAgent({ variables: { id } }))\n );\n const succeeded = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n if (failed > 0) {\n showError(\n `Removed ${succeeded}, failed to remove ${failed} agent${failed !== 1 ? 's' : ''}`\n );\n } else {\n showSuccess(`Removed ${succeeded} agent${succeeded !== 1 ? 's' : ''}`);\n }\n setSelectedAgentIds(new Set());\n } catch (err) {\n showError(err instanceof Error ? err.message : 'Failed to remove agents');\n } finally {\n setBulkLoading(false);\n }\n }, [selectedAgentIds, deleteAgent, showError, showSuccess]);\n\n // Stats\n const stats = {\n total: agents.length,\n active: agents.filter((a) => a.isActive).length,\n offline: agents.filter((a) => !a.isActive).length,\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('agentsPage.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAgents) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('agentsPage.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'agentsPage.accessDeniedDescription',\n \"You don't have permission to view agents. Contact your workspace administrator for access.\"\n )}\n />\n </MotionPage>\n );\n }\n\n if (loading && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('agentsPage.loadingAgents', 'Loading agents...')} />\n </MotionPage>\n );\n }\n\n if (error && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <ErrorState\n title={tr('agentsPage.failedLoad', 'Failed to load agents')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n {/* Toasts */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n {errorMessage && (\n <div className=\"fixed top-4 right-4 z-50 bg-status-error-bg text-status-error-text px-4 py-3 rounded-md shadow-lg text-sm\">\n {errorMessage}\n </div>\n )}\n\n <PageHeader\n title={tr('agentsPage.title', 'Agents')}\n description={tr('agentsPage.descriptionOnline', '{{active}} of {{total}} agents online', {\n active: stats.active,\n total: stats.total,\n })}\n actions={\n <div className=\"flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center\">\n <button\n onClick={() => refetch()}\n disabled={loading}\n className=\"flex items-center justify-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('agentsPage.refreshTitle', 'Refresh agents')}\n >\n <RefreshCw className={`w-4 h-4 ${loading ? 'animate-spin' : ''}`} />\n </button>\n {hasTargets ? (\n <PermissionButton\n permission={VIBE_PERMISSIONS.AGENT_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tr(\n 'agentsPage.deniedCreateTooltip',\n \"You don't have permission to create agents\"\n )}\n >\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n </PermissionButton>\n ) : (\n /* No targets yet — still open Add Agent; the form now\n supports inline target creation. */\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-agents-setup-banner\" />\n )}\n\n {/* Compact reminder when agents exist but shown once */}\n {agents.length > 0 && (\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-agents-compact-banner\" />\n )}\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('agentsPage.searchPlaceholder', 'Search agents...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n onClick={() => {\n setViewMode('grid');\n setSelectedAgentIds(new Set());\n }}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('agentsPage.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"w-4 h-4 text-text-secondary\" />\n </button>\n <button\n onClick={() => {\n setViewMode('list');\n setSelectedAgentIds(new Set());\n }}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('agentsPage.listViewTitle', 'List view')}\n >\n <List className=\"w-4 h-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('agentsPage.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <FilterDropdown\n label={tr('agentsPage.filterPlatform', 'Platform')}\n value={platformFilter}\n options={platformOptions}\n onChange={setPlatformFilter}\n />\n <TagFilter\n label={tr('agentsPage.filterTag', 'Tag')}\n value={tagFilter}\n onChange={setTagFilter}\n />\n </div>\n </div>\n\n {/* Select All & Bulk Actions */}\n {filteredAgents.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap\">\n <div className=\"ml-auto flex items-center gap-2\">\n <button\n onClick={isAllSelected ? deselectAllAgents : selectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <CheckSquare className=\"w-3.5 h-3.5\" />\n {isAllSelected\n ? tr('agentsPage.deselectAll', 'Deselect All')\n : tr('agentsPage.selectAll', 'Select All')}\n </button>\n </div>\n </div>\n )}\n\n {/* Bulk Action Bar */}\n {hasSelection && (\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-lg\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('agentsPage.selectedCount', '{{count}} agents selected', {\n count: selectedAgentIds.size,\n })}\n </span>\n <div className=\"flex items-center gap-1.5 sm:ml-auto\">\n <button\n onClick={handleBulkDelete}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-status-error-text bg-status-error-bg border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50\"\n >\n <Trash2 className=\"w-3 h-3\" /> {tr('agentsPage.remove', 'Remove')}\n </button>\n <button\n onClick={deselectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <XSquare className=\"w-3 h-3\" /> {tr('agentsPage.deselect', 'Deselect')}\n </button>\n </div>\n </div>\n )}\n\n {/* Results Count */}\n {searchQuery || statusFilter !== 'all' || platformFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('agentsPage.showingResults', 'Showing {{filtered}} of {{total}} agents', {\n filtered: filteredAgents.length,\n total: agents.length,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n {tr('agentsPage.refreshing', '(refreshing...)')}\n </span>\n )}\n </p>\n ) : null}\n\n {/* Agents Display */}\n {filteredAgents.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n {viewMode === 'grid' ? (\n <MotionList\n key=\"grid-view\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredAgents.map((agent) => (\n <MotionItem key={agent.id}>\n <AgentCard\n agent={agent}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onConfigure={handleConfigureAgent}\n onEditTunnelUrl={handleEditTunnelUrl}\n selected={selectedAgentIds.has(agent.id)}\n onToggleSelect={() => toggleAgentSelection(agent.id)}\n healthErrorMessage={agent.healthError ?? null}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list-view\" stagger={0.04}>\n <MotionItem variant=\"fadeRight\">\n {/* List view intentionally omits bulk-select props — checkboxes are\n only available in grid view. The bulk action toolbar is hidden\n when selectedAgentIds is empty, so this is safe. */}\n <AgentList\n agents={filteredAgents}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onSettings={handleConfigureAgent}\n />\n </MotionItem>\n </MotionList>\n )}\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<Bot className=\"w-8 h-8 text-text-secondary\" />}\n title={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyNoAgentsFound', 'No agents found')\n : tr('agentsPage.emptyNoAgentsConfigured', 'No agents configured')\n }\n description={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'agentsPage.emptyNoAgentsDescription',\n 'Install the CLI agent on your machine, then add it here. You can create a target inline while adding the agent.'\n )\n }\n action={\n !searchQuery && statusFilter === 'all' && platformFilter === 'all' ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {/* Always open Add Agent — the form handles the \"no\n targets yet\" case via inline DIRECT target creation. */}\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n <button\n onClick={() => handleNavigate('/getting-started')}\n className=\"flex items-center gap-2 px-4 py-2 border border-border-default text-text-primary rounded-md hover:bg-bg-sunken transition-colors text-sm\"\n >\n {tr('agentsPage.setupGuide', 'Setup guide')}\n </button>\n <GetHelpButton prompt=\"What is an Agent in VibeControls and how do I add one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n\n {/* Create Agent Modal */}\n {showCreateForm && (\n <AgentForm\n onSubmit={handleCreateAgentSubmit}\n onCancel={() => setShowCreateForm(false)}\n isLoading={isCreating}\n onNavigate={(path) => {\n setShowCreateForm(false);\n handleNavigate(path);\n }}\n targets={targetsList.map(\n (t: { id: string; name: string; type?: string; status: string }) => ({\n id: t.id,\n name: t.name,\n type: t.type || 'DIRECT',\n status: t.status,\n })\n )}\n targetsLoading={targetsLoading}\n onCreateTarget={handleInlineCreateTarget}\n />\n )}\n\n {/* Disconnect Confirmation Modal */}\n <AnimatePresence>\n {disconnectConfirmAgent && (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4 sm:p-6\"\n onClick={() => !isDisconnecting && setDisconnectConfirmAgent(null)}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-4 sm:p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-1\">Disconnect Agent</h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {deriveAgentStatus(disconnectConfirmAgent) === 'OFFLINE' ? (\n <>\n Clear the stored tunnel connection for{' '}\n <strong>{disconnectConfirmAgent.name}</strong>? This will remove the tunnel URL\n and API key from this offline agent.\n </>\n ) : (\n <>\n Mark <strong>{disconnectConfirmAgent.name}</strong> as offline and clear its\n tunnel connection? The agent process will not be stopped automatically — you may\n need to stop it manually on the host machine.\n </>\n )}\n </p>\n <div className=\"flex justify-end gap-2\">\n <button\n onClick={() => setDisconnectConfirmAgent(null)}\n disabled={isDisconnecting}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n onClick={handleConfirmDisconnect}\n disabled={isDisconnecting}\n className=\"px-4 py-2 text-sm bg-action-danger-bg text-action-danger-text rounded-md hover:bg-action-danger-bg-hover transition-colors disabled:opacity-50\"\n >\n {isDisconnecting ? 'Disconnecting...' : 'Disconnect'}\n </button>\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Edit Tunnel Modal */}\n <AnimatePresence>\n {editTunnelAgent && (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4 sm:p-6\"\n onClick={() => setEditTunnelAgent(null)}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-md p-4 sm:p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-1\">\n {tr('agentsPage.editTunnelTitle', 'Edit Tunnel')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n 'agentsPage.editTunnelDescription',\n 'Configure the cloudflared tunnel URL and API key for {{name}}.',\n {\n name: editTunnelAgent.name,\n }\n )}\n </p>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('agentsPage.tunnelUrlLabel', 'Tunnel URL')}\n </label>\n <input\n ref={tunnelUrlRef}\n type=\"url\"\n defaultValue={editTunnelAgent.tunnelUrl || ''}\n placeholder={tr(\n 'agentsPage.tunnelUrlPlaceholder',\n 'https://example.trycloudflare.com'\n )}\n className=\"w-full px-3 py-2 border border-border-default rounded-md bg-bg-sunken text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-border-strong\"\n autoFocus\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'agentsPage.tunnelUrlHint',\n 'Leave empty to clear. Agents with auto-tunnel report this automatically on startup.'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('agentsPage.agentApiKeyLabel', 'Agent API Key')}\n </label>\n <input\n ref={tunnelApiKeyRef}\n type=\"text\"\n defaultValue={editTunnelAgent.agentApiKey || ''}\n placeholder={tr('agentsPage.agentApiKeyPlaceholder', 'sk-agent-...')}\n className=\"w-full px-3 py-2 border border-border-default rounded-md bg-bg-sunken text-text-primary font-mono text-sm placeholder:text-text-placeholder focus:outline-none focus:border-border-strong\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'agentsPage.agentApiKeyHint',\n 'Authenticates requests to the agent. Typically changes on each agent restart.'\n )}\n </p>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-5\">\n <button\n onClick={() => setEditTunnelAgent(null)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('agentsPage.cancel', 'Cancel')}\n </button>\n <button\n onClick={handleSaveTunnelSettings}\n disabled={isSavingTunnel}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isSavingTunnel\n ? tr('agentsPage.saving', 'Saving...')\n : tr('agentsPage.save', 'Save')}\n </button>\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Quota Exhausted Dialog */}\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n handleNavigate('/billing');\n }}\n title=\"Agent Quota Reached\"\n message=\"You've used all available agent slots on your current plan. Upgrade your plan to register more agents.\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"AgentsPage.js","names":[],"sources":["../../src/pages/AgentsPage.tsx"],"sourcesContent":["import { useState, useMemo, useRef, useCallback, useEffect } from 'react';\nimport { Plus, Bot, Grid, List, RefreshCw, Trash2, CheckSquare, XSquare } from 'lucide-react';\nimport { MotionPage, MotionList, MotionItem } from '@burdenoff/fe-libs/shared/motion';\nimport { AnimatePresence, motion } from 'framer-motion';\nimport { AgentCard, AgentList, AgentForm } from '@/components/agents';\nimport type { Agent } from '@/components/agents';\nimport {\n PageHeader,\n SearchInput,\n FilterDropdown,\n TagFilter,\n EmptyState,\n LoadingState,\n ErrorState,\n AgentSetupBanner,\n QuotaExhaustedDialog,\n GetHelpButton,\n} from '@/components/shared';\nimport { useVibeControls, useVibeNavigate } from '@/providers/VibeControlsProvider';\nimport {\n useListAgentsQuery,\n useVibecontrolsAgentHealthProbesQuery,\n useVibecontrolsAgentHealthProbeStreamSubscription,\n useCreateAgentMutation,\n useUpdateAgentMutation,\n useDeleteAgentMutation,\n useAgentHeartbeatMutation,\n useDeactivateAgentMutation,\n useCreateTargetMutation,\n useAddAgentFromTunnelMutation,\n useStartVibecontrolsAgentMutation,\n useStopVibecontrolsAgentMutation,\n} from '@/generated/wspace-operations';\nimport { PermissionButton, AccessDenied } from '@burdenoff/fe-libs/shared/components';\nimport { usePermissions } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { useI18n } from '@burdenoff/fe-libs/shared/providers/shell/I18nProvider';\nimport { VIBE_PERMISSIONS } from '@/constants/permissions';\nimport { deriveAgentStatus, type AgentHealthProbe } from '@/utils/agentStatus';\nimport { isQuotaExhaustedError } from '@/utils/quotaUtils';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { useVibeControlsStore } from '@/store/vibeControlsStore';\n\ntype ViewMode = 'grid' | 'list';\n\n/**\n * Agents management page with filtering, search, and CRUD operations\n */\nexport function AgentsPage() {\n const { onAgentSelect, workspaceId } = useVibeControls();\n const handleNavigate = useVibeNavigate();\n const { hasPermission, isLoading: permissionsLoading, permissions } = usePermissions();\n const { t } = useI18n();\n\n const tr = useCallback(\n (key: string, fallback: string, params?: Record<string, string | number>): string => {\n const translated = t(key, params);\n const template = translated === key ? fallback : translated;\n\n if (!params) return template;\n\n return Object.entries(params).reduce(\n (acc, [paramKey, paramValue]) => acc.replace(`{{${paramKey}}}`, String(paramValue)),\n template\n );\n },\n [t]\n );\n\n const statusOptions = [\n { value: 'all', label: tr('agentsPage.statusAll', 'All Statuses') },\n { value: 'ACTIVE', label: tr('agentsPage.statusActive', 'Active') },\n { value: 'STOPPED', label: tr('agentsPage.statusStopped', 'Stopped') },\n { value: 'OFFLINE', label: tr('agentsPage.statusOffline', 'Offline') },\n ];\n\n const platformOptions = [\n { value: 'all', label: tr('agentsPage.platformAll', 'All Platforms') },\n { value: 'darwin', label: tr('agentsPage.platformMac', 'macOS') },\n { value: 'linux', label: tr('agentsPage.platformLinux', 'Linux') },\n { value: 'windows', label: tr('agentsPage.platformWindows', 'Windows') },\n ];\n\n // Check permissions\n const canListAgents = hasPermission(VIBE_PERMISSIONS.AGENT_LIST);\n\n const [searchQuery, setSearchQuery] = useState('');\n const [statusFilter, setStatusFilter] = useState('all');\n const [platformFilter, setPlatformFilter] = useState('all');\n const [tagFilter, setTagFilter] = useState('all');\n const [viewMode, setViewMode] = useState<ViewMode>('grid');\n const [showCreateForm, setShowCreateForm] = useState(false);\n const [isCreating, setIsCreating] = useState(false);\n const [editTunnelAgent, setEditTunnelAgent] = useState<Agent | null>(null);\n const [isSavingTunnel, setIsSavingTunnel] = useState(false);\n // Per-agent \"connecting\" overlay shown after Edit Tunnel save while the\n // backend pushes OAuth creds → agent finalize() → /health/ready flips to\n // ok. State lives in the global store (vibeControlsStore) so the overlay\n // survives navigation to AgentDetailsPage and back — the documented\n // recovery path navigates into the details page so pushAgentGatewayAuth\n // auto-recovery can fire, and storing the set in local React state used\n // to discard it on unmount, causing a CONNECTING → DEGRADED → ACTIVE\n // flicker on return.\n const connectingAgents = useVibeControlsStore((s) => s.connectingAgents);\n const markAgentConnecting = useVibeControlsStore((s) => s.markAgentConnecting);\n const clearAgentConnecting = useVibeControlsStore((s) => s.clearAgentConnecting);\n const connectingAgentIds = useMemo(\n () => new Set(Object.keys(connectingAgents)),\n [connectingAgents]\n );\n\n // 30s safety net — if a probe never reports healthy, drop the overlay\n // so the user eventually sees the real DEGRADED state.\n const connectingTimersRef = useRef<Map<string, ReturnType<typeof setTimeout>>>(new Map());\n useEffect(() => {\n const timers = connectingTimersRef.current;\n for (const agentId of Object.keys(connectingAgents)) {\n if (timers.has(agentId)) continue;\n const timer = setTimeout(() => {\n clearAgentConnecting(agentId);\n timers.delete(agentId);\n }, 30_000);\n timers.set(agentId, timer);\n }\n // Drop timers for agents no longer marked connecting.\n for (const [agentId, timer] of timers.entries()) {\n if (!(agentId in connectingAgents)) {\n clearTimeout(timer);\n timers.delete(agentId);\n }\n }\n }, [connectingAgents, clearAgentConnecting]);\n\n useEffect(() => {\n const timers = connectingTimersRef.current;\n return () => {\n for (const timer of timers.values()) clearTimeout(timer);\n timers.clear();\n };\n }, []);\n const [disconnectConfirmAgent, setDisconnectConfirmAgent] = useState<Agent | null>(null);\n const [isDisconnecting, setIsDisconnecting] = useState(false);\n const tunnelUrlRef = useRef<HTMLInputElement>(null);\n const tunnelApiKeyRef = useRef<HTMLInputElement>(null);\n\n // Bulk selection state\n const [selectedAgentIds, setSelectedAgentIds] = useState<Set<string>>(new Set());\n const [bulkLoading, setBulkLoading] = useState(false);\n const [successMessage, setSuccessMessage] = useState<string | null>(null);\n const [errorMessage, setErrorMessage] = useState<string | null>(null);\n const [showQuotaExhausted, setShowQuotaExhausted] = useState(false);\n const toastTimerRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n return () => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n };\n }, []);\n\n const showSuccess = useCallback((msg: string) => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n setErrorMessage(null);\n setSuccessMessage(msg);\n toastTimerRef.current = setTimeout(() => setSuccessMessage(null), 3000);\n }, []);\n const showError = useCallback((msg: string) => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n setSuccessMessage(null);\n setErrorMessage(msg);\n toastTimerRef.current = setTimeout(() => setErrorMessage(null), 5000);\n }, []);\n\n const [createAgent] = useCreateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [addAgentFromTunnel] = useAddAgentFromTunnelMutation({ refetchQueries: ['ListAgents'] });\n const [agentHeartbeat] = useAgentHeartbeatMutation({ refetchQueries: ['ListAgents'] });\n const [deactivateAgent] = useDeactivateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [startAgentMutation] = useStartVibecontrolsAgentMutation({\n refetchQueries: ['ListAgents'],\n });\n const [stopAgentMutation] = useStopVibecontrolsAgentMutation({ refetchQueries: ['ListAgents'] });\n\n const {\n targets: cachedTargets,\n loading: initialDataLoading,\n refetch: refetchInitialData,\n } = useCachedInitialData();\n const targetsList = cachedTargets;\n const targetsLoading = initialDataLoading;\n const hasTargets = targetsList.length > 0;\n const [updateAgent] = useUpdateAgentMutation({ refetchQueries: ['ListAgents'] });\n const [deleteAgent] = useDeleteAgentMutation({ refetchQueries: ['ListAgents'] });\n // refetchQueries + awaitRefetchQueries: the inline create flow used to fire\n // refetchInitialData() without awaiting it, so AgentForm closed and\n // re-rendered against stale `cachedTargets` — the new target only appeared\n // on full page reload. Awaiting the underlying queries here makes the\n // mutation resolve only after the initial-data + targets caches are fresh.\n const [createTargetMutation] = useCreateTargetMutation({\n refetchQueries: ['VibeControlsInitialData', 'ListTargets'],\n awaitRefetchQueries: true,\n });\n\n /**\n * Inline DIRECT target creation callback for the AgentForm. Returns\n * the newly created target so the form can auto-select it.\n */\n const handleInlineCreateTarget = useCallback(\n async (targetName: string) => {\n if (!workspaceId || !targetName.trim()) return null;\n try {\n const { data: result } = await createTargetMutation({\n variables: {\n workspaceId,\n input: {\n type: 'DIRECT',\n name: targetName.trim(),\n },\n },\n });\n const created = result?.createTarget;\n if (!created) return null;\n refetchInitialData();\n return {\n id: created.id,\n name: created.name,\n type: created.type ?? 'DIRECT',\n status: created.status ?? 'unknown',\n };\n } catch {\n return null;\n }\n },\n [createTargetMutation, refetchInitialData, workspaceId]\n );\n\n // Fetch agents from GraphQL API\n // Skip until a workspace is selected: the resolver requires\n // x-workspace-id and throws \"Workspace context required\" when fired\n // before the auth provider has resolved a workspace (URL with ?org but\n // no &workspace, post-context-switcher self-heal, etc.). Without this\n // skip, the page lands on \"Failed to load agents\" and never recovers\n // because Apollo's errorPolicy: 'all' returns the error and doesn't\n // auto-refetch when workspaceId becomes available.\n const { data, loading, error, refetch } = useListAgentsQuery({\n variables: {\n filter: {\n isActive: statusFilter === 'ACTIVE' ? true : statusFilter === 'OFFLINE' ? false : undefined,\n platform: platformFilter !== 'all' ? platformFilter : undefined,\n },\n pagination: { limit: 100 },\n },\n fetchPolicy: 'cache-first',\n skip: !workspaceId,\n });\n\n // Live deep-health probe — backend hits /health/ready on every agent and\n // returns per-agent { healthy, errorMessage }. Polled every 30s so stale\n // tunnels (cloudflared dead, agent crashed but `isActive` still true)\n // flip the badge to DEGRADED before users hit \"create session\" against\n // an unreachable agent.\n // Initial query primes the cache; the subscription below replaces the\n // prior 30s pollInterval so badges flip to/from DEGRADED in <1s.\n const probeQuery = useVibecontrolsAgentHealthProbesQuery({\n fetchPolicy: 'cache-and-network',\n nextFetchPolicy: 'cache-first',\n notifyOnNetworkStatusChange: true,\n skip: !workspaceId,\n // Fallback polling: the stream subscription is the primary push channel\n // (sub-second updates) but it doesn't always reach the browser in local\n // dev — graphql-ws upgrade can be blocked by an SSE-only proxy, the\n // svc may have just restarted and dropped subscribers, etc. Without a\n // fallback the badge sticks at whatever the *initial* probe captured\n // (often DEGRADED while the agent's session-tmux plugin was still\n // loading) and only flips on a full page reload. 30s matches the\n // server-side periodic probe interval so we don't probe more often\n // than the backend re-evaluates.\n pollInterval: 30_000,\n });\n const { data: probeStreamData } = useVibecontrolsAgentHealthProbeStreamSubscription({\n skip: !workspaceId,\n });\n // True when no probe data has been delivered for this mount cycle yet.\n // Used by deriveAgentStatus to show REGISTERING for agents we have not\n // yet probed (instead of optimistically ACTIVE).\n const probesLoading = probeQuery.data === undefined && probeStreamData === undefined;\n const probesById = useMemo(() => {\n const m = new Map<string, AgentHealthProbe>();\n // Subscription stream wins over the initial query — strictly newer.\n const probes =\n probeStreamData?.vibecontrolsAgentHealthProbeStream ??\n probeQuery.data?.vibecontrolsAgentHealthProbes ??\n [];\n for (const p of probes) {\n m.set(p.agentId, {\n agentId: p.agentId,\n healthy: p.healthy,\n errorMessage: p.errorMessage,\n });\n }\n return m;\n }, [probeQuery.data, probeStreamData]);\n\n // Map GraphQL response to Agent type\n const agents: Agent[] = useMemo(() => {\n const rawAgents = data?.vibecontrolsAgents ?? [];\n return rawAgents.map((agent: NonNullable<typeof rawAgents>[number]) => ({\n id: agent.id,\n workspaceId: agent.workspaceId,\n name: agent.name,\n hostname: agent.hostname,\n platform: agent.platform,\n architecture: agent.architecture,\n version: agent.version,\n isActive: agent.isActive,\n status: connectingAgentIds.has(agent.id)\n ? ('CONNECTING' as const)\n : deriveAgentStatus(\n {\n id: agent.id,\n isActive: agent.isActive,\n metadata: agent.metadata as Record<string, unknown> | null,\n },\n probesById,\n { probesLoading }\n ),\n // Prefer the live health probe error. When the live probe didn't run\n // (or returned ok) but the agent's most-recent gateway-auth push\n // failed, surface that instead — otherwise the only signal of a\n // failed onboarding is a yellow Degraded badge with no hover text.\n // `pending` is hidden: it just means the async finalize is still\n // running and the badge already shows the right transient state.\n healthError:\n probesById.get(agent.id)?.errorMessage ??\n (agent.lastGatewayAuthPushStatus === 'failed'\n ? (agent.lastGatewayAuthPushError ?? 'Gateway-auth push failed')\n : null),\n lastHeartbeat: agent.lastHeartbeat || null,\n tunnelUrl: agent.tunnelUrl ?? null,\n tunnelStatus: agent.tunnelStatus ?? null,\n agentApiKey: agent.agentApiKey ?? null,\n configuration: (agent.configuration as Record<string, unknown>) || {},\n capabilities: Array.isArray(agent.capabilities)\n ? (agent.capabilities as string[])\n : typeof agent.capabilities === 'object' && agent.capabilities !== null\n ? (() => {\n const obj = agent.capabilities as Record<string, unknown>;\n const keys = Object.keys(obj);\n // When capabilities was serialized from an array, keys are numeric (\"0\",\"1\",\"2\")\n // In that case, extract values (the actual capability names) instead of keys\n const hasNumericKeys = keys.length > 0 && keys.every((k) => /^\\d+$/.test(k));\n return hasNumericKeys ? (Object.values(obj) as string[]) : (keys as string[]);\n })()\n : [],\n metadata: (agent.metadata as Record<string, unknown>) || {},\n createdAt: agent.createdAt,\n updatedAt: agent.updatedAt,\n tagAssignments: (agent.tagAssignments ?? []).map(\n (a: NonNullable<typeof agent.tagAssignments>[number]) => ({\n id: a.id,\n tagId: a.tagId,\n tag: a.tagInfo\n ? {\n id: a.tagInfo.id,\n key: a.tagInfo.key ?? null,\n label: a.tagInfo.label ?? null,\n color: a.tagInfo.color ?? null,\n }\n : null,\n })\n ),\n }));\n }, [data?.vibecontrolsAgents, probesById, probesLoading, connectingAgentIds]);\n\n // Clear CONNECTING overlay only when ALL of these hold:\n // 1. The live probe reports `healthy: true` (deep-health probe, which\n // already gates on session.ok + tunnel.ok + ttyd.ok server-side —\n // see probeAgentHealth in wspace-vibecontrols-svc).\n // 2. The grace-window timestamp stored at markAgentConnecting time has\n // elapsed. This defends against a STALE `healthy:true` probe\n // delivered from the Apollo cache during the in-flight refetch\n // that follows updateAgent — without the grace window, that stale\n // cache hit cleared CONNECTING immediately and the next (fresh,\n // transient) probe landing during cloudflared restart flipped the\n // badge to DEGRADED, producing the CONNECTING → DEGRADED → ACTIVE\n // flicker reported by users.\n //\n // Re-runs on `probesById` updates AND on a one-shot timer per pending\n // agent so the clear fires automatically once the grace window elapses\n // even if no new probe arrives.\n useEffect(() => {\n const pending = Object.keys(connectingAgents);\n if (pending.length === 0) return;\n const now = Date.now();\n let earliestPending = Infinity;\n for (const agentId of pending) {\n const minClearAt = connectingAgents[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && now >= minClearAt) {\n clearAgentConnecting(agentId);\n } else if (minClearAt < earliestPending) {\n earliestPending = minClearAt;\n }\n }\n if (earliestPending === Infinity) return;\n const wait = Math.max(0, earliestPending - now) + 50;\n const tick = setTimeout(() => {\n // No-op state poke — the dependency on `connectingAgents` causes\n // this effect to re-run when state changes, but a healthy probe\n // arriving BEFORE the grace window elapses needs a re-evaluation\n // when the window finally closes. Re-marking with graceMs=0 is a\n // no-op when the entry is already past its window; if cleared in\n // the meantime, the early `pending.length === 0` short-circuit\n // makes this safe.\n const fresh = useVibeControlsStore.getState().connectingAgents;\n for (const agentId of Object.keys(fresh)) {\n const minClearAt = fresh[agentId];\n if (minClearAt === undefined) continue;\n const probe = probesById.get(agentId);\n if (probe?.healthy === true && Date.now() >= minClearAt) {\n clearAgentConnecting(agentId);\n }\n }\n }, wait);\n return () => clearTimeout(tick);\n }, [probesById, connectingAgents, clearAgentConnecting]);\n\n // Filter agents based on search and tag (client-side filtering).\n // Status / platform filters are applied server-side via the GraphQL filter\n // input above; these client-side filters compose on top of that result set.\n const filteredAgents = useMemo(() => {\n return agents.filter((agent) => {\n // Search filter\n if (searchQuery) {\n const query = searchQuery.toLowerCase();\n const matchesSearch =\n agent.name.toLowerCase().includes(query) ||\n agent.hostname?.toLowerCase().includes(query) ||\n agent.id.toLowerCase().includes(query);\n if (!matchesSearch) return false;\n }\n\n // Tag filter — match against structured tagAssignments (UUIDs).\n // Never falls back to the legacy free-text agent.tags array because\n // those values are not the same as tagFilterIds and would silently\n // produce empty results (the failure mode that got the previous\n // TagFilter implementation removed in commit 4e101ca).\n if (tagFilter !== 'all') {\n const hasTag = (agent.tagAssignments ?? []).some(\n (a: { tagId: string }) => a.tagId === tagFilter\n );\n if (!hasTag) return false;\n }\n\n return true;\n });\n }, [agents, searchQuery, tagFilter]);\n\n useEffect(() => {\n return () => {\n if (toastTimerRef.current) clearTimeout(toastTimerRef.current);\n };\n }, []);\n\n const handleAgentSelect = (agent: Agent) => {\n // Navigate to agent details page\n handleNavigate(`/agents/${agent.id}`);\n // Also call the shell callback if provided\n onAgentSelect?.({\n id: agent.id,\n name: agent.name,\n type: agent.platform || 'unknown',\n status: agent.status || (agent.isActive ? 'ACTIVE' : 'OFFLINE'),\n });\n };\n\n const handleCreateAgent = () => {\n setShowCreateForm(true);\n };\n\n const handleCreateAgentSubmit = async (\n formData: Record<string, unknown>\n ): Promise<{ id: string } | void> => {\n try {\n setIsCreating(true);\n\n const tunnelUrl = (formData.tunnelUrl as string | undefined)?.trim();\n const agentApiKey = (formData.agentApiKey as string | undefined)?.trim();\n\n // Zero-config path: operator pasted the agent's tunnel URL + API key\n // shown in its startup banner. Backend probes the agent, registers it,\n // mints an OAuth app, and pushes credentials — the agent promotes\n // itself to `ready` inside that final POST /api/agent/gateway-auth.\n if (tunnelUrl && agentApiKey) {\n const result = await addAgentFromTunnel({\n variables: {\n input: {\n tunnelUrl,\n apiKey: agentApiKey,\n ...(formData.name ? { name: formData.name as string } : {}),\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n },\n },\n });\n if (result.error) throw result.error;\n const payload = result.data?.addVibecontrolsAgentFromTunnel;\n if (!payload?.success) {\n throw new Error(\n payload?.error || 'Failed to onboard agent — probe or OAuth setup failed.'\n );\n }\n if (payload.agent?.id) return { id: payload.agent.id };\n throw new Error('Agent onboarding returned no agent record.');\n }\n\n // Legacy manual path — operator typed hostname/platform/etc directly.\n const result = await createAgent({\n variables: {\n input: {\n name: formData.name as string,\n hostname: formData.hostname as string,\n platform: formData.platform as string,\n architecture: formData.architecture as string,\n version: formData.version as string,\n ...(formData.targetId ? { targetId: formData.targetId as string } : {}),\n ...(formData.tunnelUrl ? { tunnelUrl: formData.tunnelUrl as string } : {}),\n ...(formData.tunnelStatus ? { tunnelStatus: formData.tunnelStatus as string } : {}),\n },\n },\n });\n\n const createdAgent = result.data?.createVibecontrolsAgent;\n if (createdAgent?.id) return { id: createdAgent.id };\n\n if (result.error) {\n throw result.error;\n }\n\n throw new Error('Agent creation failed — no data returned from the server.');\n } catch (err) {\n if (isQuotaExhaustedError(err)) {\n setShowQuotaExhausted(true);\n setShowCreateForm(false);\n return;\n }\n\n console.error('Failed to create agent:', err);\n throw err;\n } finally {\n setIsCreating(false);\n }\n };\n\n const handleStartAgent = async (agent: Agent) => {\n try {\n const result = await startAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.startVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to start agent');\n }\n\n await agentHeartbeat({ variables: { id: agent.id } });\n showSuccess(`Agent \"${agent.name}\" started`);\n } catch (err) {\n console.error('Failed to start agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to start agent');\n }\n };\n\n // NOTE: Stop and Disconnect both call deactivateVibecontrolsAgent and produce the same\n // server-side list state (isActive: false). The distinction is that Disconnect additionally\n // clears tunnel fields (tunnelUrl, tunnelStatus). When a dedicated stopVibecontrolsAgent\n // mutation succeeds, we still deactivate in workspace state so the list reflects the agent as offline.\n const handleStopAgent = async (agent: Agent) => {\n try {\n const result = await stopAgentMutation({ variables: { agentId: agent.id } });\n const payload = result.data?.stopVibecontrolsAgent;\n\n if (!payload?.success) {\n throw new Error(payload?.error || 'Failed to stop agent');\n }\n\n await deactivateAgent({ variables: { id: agent.id } });\n showSuccess(`Agent \"${agent.name}\" stopped`);\n } catch (err) {\n console.error('Failed to stop agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to stop agent');\n }\n };\n\n const handleDisconnectAgent = (agent: Agent) => {\n setDisconnectConfirmAgent(agent);\n };\n\n const handleConfirmDisconnect = async () => {\n if (!disconnectConfirmAgent) return;\n const agent = disconnectConfirmAgent;\n setIsDisconnecting(true);\n // Deactivate + clear tunnel info so the agent appears fully disconnected.\n // Two sequential mutations with refetchQueries suppressed — a single refetch()\n // is called after both complete so the list doesn't flash with stale tunnel fields.\n // A single atomic disconnectVibecontrolsAgent backend mutation that handles\n // both deactivation and tunnel field clearing is tracked in vibecontrols-svc\n // backlog; until then the two-step approach below is the supported flow.\n try {\n await deactivateAgent({ variables: { id: agent.id }, refetchQueries: [] });\n } catch (err) {\n console.error('Failed to deactivate agent:', err);\n showError(err instanceof Error ? err.message : 'Failed to disconnect agent');\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Early-return path: refetch here so the list reflects the deactivated state,\n // then return so the bottom refetch below does not also fire.\n await refetch();\n return;\n }\n try {\n await updateAgent({\n variables: {\n id: agent.id,\n input: { tunnelUrl: null, tunnelStatus: null, agentApiKey: null },\n },\n refetchQueries: [],\n });\n showSuccess(`Agent \"${agent.name}\" disconnected`);\n } catch (err) {\n console.error('Failed to clear tunnel fields:', err);\n showError(\n `Agent marked offline, but failed to clear connection fields — clear them manually in Settings.`\n );\n }\n setIsDisconnecting(false);\n setDisconnectConfirmAgent(null);\n // Both updateAgent success and updateAgent failure reach here — single refetch covers both.\n await refetch();\n };\n\n const handleConfigureAgent = (agent: Agent) => {\n // Navigate to agent details page (settings tab)\n handleNavigate(`/agents/${agent.id}`);\n };\n\n const handleEditTunnelUrl = (agent: Agent) => {\n setEditTunnelAgent(agent);\n };\n\n const handleSaveTunnelSettings = async () => {\n if (!editTunnelAgent) return;\n const agentId = editTunnelAgent.id;\n const newUrl = tunnelUrlRef.current?.value?.trim() || '';\n const newApiKey = tunnelApiKeyRef.current?.value?.trim() || '';\n try {\n setIsSavingTunnel(true);\n await updateAgent({\n variables: {\n id: agentId,\n input: {\n tunnelUrl: newUrl || null,\n tunnelStatus: newUrl ? 'ACTIVE' : null,\n agentApiKey: newApiKey || null,\n },\n },\n // Force a fresh probe immediately so the badge resolves to the\n // real state (instead of relying on the 30s subscription cadence).\n refetchQueries: ['ListAgents', 'VibecontrolsAgentHealthProbes'],\n });\n // Begin the \"Connecting…\" overlay BEFORE clearing the dialog so the\n // operator sees an unbroken progress signal: dialog spinner →\n // CONNECTING badge with spinner → ACTIVE / DEGRADED.\n if (newUrl) markAgentConnecting(agentId);\n setEditTunnelAgent(null);\n } catch (err) {\n console.error('Failed to update tunnel settings:', err);\n } finally {\n setIsSavingTunnel(false);\n }\n };\n\n // ── Bulk Selection ─────────────────────────────────────────────────\n const toggleAgentSelection = useCallback((agentId: string) => {\n setSelectedAgentIds((prev) => {\n const next = new Set(prev);\n if (next.has(agentId)) {\n next.delete(agentId);\n } else {\n next.add(agentId);\n }\n return next;\n });\n }, []);\n\n const selectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set(filteredAgents.map((a) => a.id)));\n }, [filteredAgents]);\n\n const deselectAllAgents = useCallback(() => {\n setSelectedAgentIds(new Set());\n }, []);\n\n const isAllSelected =\n filteredAgents.length > 0 && selectedAgentIds.size === filteredAgents.length;\n const hasSelection = selectedAgentIds.size > 0;\n\n // ── Bulk Delete ──────────────────────────────────────────────────\n const handleBulkDelete = useCallback(async () => {\n if (selectedAgentIds.size === 0) return;\n if (\n !window.confirm(\n tr(\n 'agentsPage.bulkDeleteConfirm',\n 'Are you sure you want to remove {{count}} agent(s)? This cannot be undone.',\n { count: selectedAgentIds.size }\n )\n )\n )\n return;\n\n setBulkLoading(true);\n try {\n const results = await Promise.allSettled(\n Array.from(selectedAgentIds).map((id) => deleteAgent({ variables: { id } }))\n );\n const succeeded = results.filter((r) => r.status === 'fulfilled').length;\n const failed = results.filter((r) => r.status === 'rejected').length;\n if (failed > 0) {\n showError(\n `Removed ${succeeded}, failed to remove ${failed} agent${failed !== 1 ? 's' : ''}`\n );\n } else {\n showSuccess(`Removed ${succeeded} agent${succeeded !== 1 ? 's' : ''}`);\n }\n setSelectedAgentIds(new Set());\n } catch (err) {\n showError(err instanceof Error ? err.message : 'Failed to remove agents');\n } finally {\n setBulkLoading(false);\n }\n }, [selectedAgentIds, deleteAgent, showError, showSuccess]);\n\n // Stats\n const stats = {\n total: agents.length,\n active: agents.filter((a) => a.isActive).length,\n offline: agents.filter((a) => !a.isActive).length,\n };\n\n // Guard states rendered inside MotionPage to prevent unmount/remount animation replay\n if (permissionsLoading) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('agentsPage.loadingPermissions', 'Loading permissions...')} />\n </MotionPage>\n );\n }\n\n if (permissions && permissions.permissionStrings?.length > 0 && !canListAgents) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <AccessDenied\n title={tr('agentsPage.accessDeniedTitle', 'Access Denied')}\n description={tr(\n 'agentsPage.accessDeniedDescription',\n \"You don't have permission to view agents. Contact your workspace administrator for access.\"\n )}\n />\n </MotionPage>\n );\n }\n\n if (loading && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <LoadingState message={tr('agentsPage.loadingAgents', 'Loading agents...')} />\n </MotionPage>\n );\n }\n\n if (error && agents.length === 0) {\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n <ErrorState\n title={tr('agentsPage.failedLoad', 'Failed to load agents')}\n message={error.message}\n onRetry={() => refetch()}\n />\n </MotionPage>\n );\n }\n\n return (\n <MotionPage className=\"space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen\">\n {/* Toasts */}\n {successMessage && (\n <div className=\"fixed top-4 left-4 right-4 sm:left-auto z-50 bg-status-success-bg text-status-success-text px-4 py-3 rounded-md shadow-lg text-sm\">\n {successMessage}\n </div>\n )}\n {errorMessage && (\n <div className=\"fixed top-4 right-4 z-50 bg-status-error-bg text-status-error-text px-4 py-3 rounded-md shadow-lg text-sm\">\n {errorMessage}\n </div>\n )}\n\n <PageHeader\n title={tr('agentsPage.title', 'Agents')}\n description={tr('agentsPage.descriptionOnline', '{{active}} of {{total}} agents online', {\n active: stats.active,\n total: stats.total,\n })}\n actions={\n <div className=\"flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center\">\n <button\n onClick={() => {\n // Refresh both the agent list AND the live deep-health\n // probe so the badge reflects current state, not a stale\n // initial probe captured while the agent was still\n // booting. Without re-probing here the user clicks the\n // refresh icon and the row's name/tunnel reload but the\n // DEGRADED badge stays stuck until the next 30s tick.\n refetch();\n probeQuery.refetch();\n }}\n disabled={loading || probeQuery.loading}\n className=\"flex items-center justify-center gap-2 px-3 py-2 text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n title={tr('agentsPage.refreshTitle', 'Refresh agents')}\n >\n <RefreshCw\n className={`w-4 h-4 ${loading || probeQuery.loading ? 'animate-spin' : ''}`}\n />\n </button>\n {hasTargets ? (\n <PermissionButton\n permission={VIBE_PERMISSIONS.AGENT_CREATE}\n deniedBehavior=\"disable\"\n deniedTooltip={tr(\n 'agentsPage.deniedCreateTooltip',\n \"You don't have permission to create agents\"\n )}\n >\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50 disabled:cursor-not-allowed\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n </PermissionButton>\n ) : (\n /* No targets yet — still open Add Agent; the form now\n supports inline target creation. */\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n )}\n </div>\n }\n />\n\n {/* Agent CLI Setup Banner — shown when no agents exist */}\n {agents.length === 0 && !loading && (\n <AgentSetupBanner variant=\"full\" dismissKey=\"vibecontrols-agents-setup-banner\" />\n )}\n\n {/* Compact reminder when agents exist but shown once */}\n {agents.length > 0 && (\n <AgentSetupBanner variant=\"compact\" dismissKey=\"vibecontrols-agents-compact-banner\" />\n )}\n\n {/* Filters Bar */}\n <div className=\"space-y-3\">\n {/* Row 1: Search + View Toggle */}\n <div className=\"flex items-center gap-2\">\n <SearchInput\n value={searchQuery}\n onChange={setSearchQuery}\n placeholder={tr('agentsPage.searchPlaceholder', 'Search agents...')}\n className=\"flex-1 sm:w-64 sm:flex-none\"\n />\n {/* View Toggle */}\n <div className=\"flex items-center gap-1 bg-bg-sunken rounded-md p-1 flex-shrink-0\">\n <button\n onClick={() => {\n setViewMode('grid');\n setSelectedAgentIds(new Set());\n }}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'grid' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('agentsPage.gridViewTitle', 'Grid view')}\n >\n <Grid className=\"w-4 h-4 text-text-secondary\" />\n </button>\n <button\n onClick={() => {\n setViewMode('list');\n setSelectedAgentIds(new Set());\n }}\n className={`p-1.5 sm:p-2 rounded ${viewMode === 'list' ? 'bg-bg-surface shadow-sm' : 'hover:bg-bg-surface/50'}`}\n title={tr('agentsPage.listViewTitle', 'List view')}\n >\n <List className=\"w-4 h-4 text-text-secondary\" />\n </button>\n </div>\n </div>\n\n {/* Row 2: Filters - wrap on all screens */}\n <div className=\"flex items-center gap-2 sm:gap-3 flex-wrap\">\n <FilterDropdown\n label={tr('agentsPage.filterStatus', 'Status')}\n value={statusFilter}\n options={statusOptions}\n onChange={setStatusFilter}\n />\n <FilterDropdown\n label={tr('agentsPage.filterPlatform', 'Platform')}\n value={platformFilter}\n options={platformOptions}\n onChange={setPlatformFilter}\n />\n <TagFilter\n label={tr('agentsPage.filterTag', 'Tag')}\n value={tagFilter}\n onChange={setTagFilter}\n />\n </div>\n </div>\n\n {/* Select All & Bulk Actions */}\n {filteredAgents.length > 0 && (\n <div className=\"flex items-center gap-2 flex-wrap\">\n <div className=\"ml-auto flex items-center gap-2\">\n <button\n onClick={isAllSelected ? deselectAllAgents : selectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <CheckSquare className=\"w-3.5 h-3.5\" />\n {isAllSelected\n ? tr('agentsPage.deselectAll', 'Deselect All')\n : tr('agentsPage.selectAll', 'Select All')}\n </button>\n </div>\n </div>\n )}\n\n {/* Bulk Action Bar */}\n {hasSelection && (\n <div className=\"flex flex-col sm:flex-row items-start sm:items-center gap-3 px-4 py-2 bg-action-primary-bg/10 border border-action-primary-bg/20 rounded-lg\">\n <span className=\"text-sm font-medium text-text-primary\">\n {tr('agentsPage.selectedCount', '{{count}} agents selected', {\n count: selectedAgentIds.size,\n })}\n </span>\n <div className=\"flex items-center gap-1.5 sm:ml-auto\">\n <button\n onClick={handleBulkDelete}\n disabled={bulkLoading}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs font-medium text-status-error-text bg-status-error-bg border border-status-error-text/20 rounded-md hover:bg-status-error-bg/80 transition-colors disabled:opacity-50\"\n >\n <Trash2 className=\"w-3 h-3\" /> {tr('agentsPage.remove', 'Remove')}\n </button>\n <button\n onClick={deselectAllAgents}\n className=\"flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors\"\n >\n <XSquare className=\"w-3 h-3\" /> {tr('agentsPage.deselect', 'Deselect')}\n </button>\n </div>\n </div>\n )}\n\n {/* Results Count */}\n {searchQuery || statusFilter !== 'all' || platformFilter !== 'all' ? (\n <p className=\"text-sm text-text-secondary\">\n {tr('agentsPage.showingResults', 'Showing {{filtered}} of {{total}} agents', {\n filtered: filteredAgents.length,\n total: agents.length,\n })}\n {loading && (\n <span className=\"ml-2 text-text-muted\">\n {tr('agentsPage.refreshing', '(refreshing...)')}\n </span>\n )}\n </p>\n ) : null}\n\n {/* Agents Display */}\n {filteredAgents.length > 0 ? (\n <AnimatePresence mode=\"wait\">\n {viewMode === 'grid' ? (\n <MotionList\n key=\"grid-view\"\n stagger={0.04}\n className=\"grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4\"\n >\n {filteredAgents.map((agent) => (\n <MotionItem key={agent.id}>\n <AgentCard\n agent={agent}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onConfigure={handleConfigureAgent}\n onEditTunnelUrl={handleEditTunnelUrl}\n selected={selectedAgentIds.has(agent.id)}\n onToggleSelect={() => toggleAgentSelection(agent.id)}\n healthErrorMessage={agent.healthError ?? null}\n />\n </MotionItem>\n ))}\n </MotionList>\n ) : (\n <MotionList key=\"list-view\" stagger={0.04}>\n <MotionItem variant=\"fadeRight\">\n {/* List view intentionally omits bulk-select props — checkboxes are\n only available in grid view. The bulk action toolbar is hidden\n when selectedAgentIds is empty, so this is safe. */}\n <AgentList\n agents={filteredAgents}\n onSelect={handleAgentSelect}\n onStart={handleStartAgent}\n onStop={handleStopAgent}\n onDisconnect={handleDisconnectAgent}\n onSettings={handleConfigureAgent}\n />\n </MotionItem>\n </MotionList>\n )}\n </AnimatePresence>\n ) : (\n <EmptyState\n icon={<Bot className=\"w-8 h-8 text-text-secondary\" />}\n title={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyNoAgentsFound', 'No agents found')\n : tr('agentsPage.emptyNoAgentsConfigured', 'No agents configured')\n }\n description={\n searchQuery || statusFilter !== 'all' || platformFilter !== 'all'\n ? tr('agentsPage.emptyAdjustSearch', 'Try adjusting your search or filters')\n : tr(\n 'agentsPage.emptyNoAgentsDescription',\n 'Install the CLI agent on your machine, then add it here. You can create a target inline while adding the agent.'\n )\n }\n action={\n !searchQuery && statusFilter === 'all' && platformFilter === 'all' ? (\n <div className=\"flex flex-wrap items-center gap-3\">\n {/* Always open Add Agent — the form handles the \"no\n targets yet\" case via inline DIRECT target creation. */}\n <button\n onClick={handleCreateAgent}\n className=\"flex items-center gap-2 px-4 py-2 bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors\"\n >\n <Plus className=\"w-4 h-4\" />\n {tr('agentsPage.addAgent', 'Add Agent')}\n </button>\n <button\n onClick={() => handleNavigate('/getting-started')}\n className=\"flex items-center gap-2 px-4 py-2 border border-border-default text-text-primary rounded-md hover:bg-bg-sunken transition-colors text-sm\"\n >\n {tr('agentsPage.setupGuide', 'Setup guide')}\n </button>\n <GetHelpButton prompt=\"What is an Agent in VibeControls and how do I add one?\" />\n </div>\n ) : undefined\n }\n />\n )}\n\n {/* Create Agent Modal */}\n {showCreateForm && (\n <AgentForm\n onSubmit={handleCreateAgentSubmit}\n onCancel={() => setShowCreateForm(false)}\n isLoading={isCreating}\n onNavigate={(path) => {\n setShowCreateForm(false);\n handleNavigate(path);\n }}\n targets={targetsList.map(\n (t: { id: string; name: string; type?: string; status: string }) => ({\n id: t.id,\n name: t.name,\n type: t.type || 'DIRECT',\n status: t.status,\n })\n )}\n targetsLoading={targetsLoading}\n onCreateTarget={handleInlineCreateTarget}\n />\n )}\n\n {/* Disconnect Confirmation Modal */}\n <AnimatePresence>\n {disconnectConfirmAgent && (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4 sm:p-6\"\n onClick={() => !isDisconnecting && setDisconnectConfirmAgent(null)}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-sm p-4 sm:p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-1\">Disconnect Agent</h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {deriveAgentStatus(disconnectConfirmAgent) === 'OFFLINE' ? (\n <>\n Clear the stored tunnel connection for{' '}\n <strong>{disconnectConfirmAgent.name}</strong>? This will remove the tunnel URL\n and API key from this offline agent.\n </>\n ) : (\n <>\n Mark <strong>{disconnectConfirmAgent.name}</strong> as offline and clear its\n tunnel connection? The agent process will not be stopped automatically — you may\n need to stop it manually on the host machine.\n </>\n )}\n </p>\n <div className=\"flex justify-end gap-2\">\n <button\n onClick={() => setDisconnectConfirmAgent(null)}\n disabled={isDisconnecting}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50\"\n >\n Cancel\n </button>\n <button\n onClick={handleConfirmDisconnect}\n disabled={isDisconnecting}\n className=\"px-4 py-2 text-sm bg-action-danger-bg text-action-danger-text rounded-md hover:bg-action-danger-bg-hover transition-colors disabled:opacity-50\"\n >\n {isDisconnecting ? 'Disconnecting...' : 'Disconnect'}\n </button>\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Edit Tunnel Modal */}\n <AnimatePresence>\n {editTunnelAgent && (\n <div\n className=\"fixed inset-0 bg-bg-overlay flex items-center justify-center z-50 p-4 sm:p-6\"\n onClick={() => setEditTunnelAgent(null)}\n >\n <motion.div\n initial={{ opacity: 0, scale: 0.95 }}\n animate={{ opacity: 1, scale: 1 }}\n exit={{ opacity: 0, scale: 0.95 }}\n transition={{ duration: 0.15 }}\n className=\"bg-bg-surface border border-border-default rounded-lg shadow-xl w-full max-w-md p-4 sm:p-6\"\n onClick={(e) => e.stopPropagation()}\n >\n <h3 className=\"text-lg font-semibold text-text-primary mb-1\">\n {tr('agentsPage.editTunnelTitle', 'Edit Tunnel')}\n </h3>\n <p className=\"text-sm text-text-secondary mb-4\">\n {tr(\n 'agentsPage.editTunnelDescription',\n 'Configure the cloudflared tunnel URL and API key for {{name}}.',\n {\n name: editTunnelAgent.name,\n }\n )}\n </p>\n <div className=\"space-y-4\">\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('agentsPage.tunnelUrlLabel', 'Tunnel URL')}\n </label>\n <input\n ref={tunnelUrlRef}\n type=\"url\"\n defaultValue={editTunnelAgent.tunnelUrl || ''}\n placeholder={tr(\n 'agentsPage.tunnelUrlPlaceholder',\n 'https://example.trycloudflare.com'\n )}\n className=\"w-full px-3 py-2 border border-border-default rounded-md bg-bg-sunken text-text-primary placeholder:text-text-placeholder focus:outline-none focus:border-border-strong\"\n autoFocus\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'agentsPage.tunnelUrlHint',\n 'Leave empty to clear. Agents with auto-tunnel report this automatically on startup.'\n )}\n </p>\n </div>\n <div>\n <label className=\"block text-sm font-medium text-text-primary mb-1\">\n {tr('agentsPage.agentApiKeyLabel', 'Agent API Key')}\n </label>\n <input\n ref={tunnelApiKeyRef}\n type=\"text\"\n defaultValue={editTunnelAgent.agentApiKey || ''}\n placeholder={tr('agentsPage.agentApiKeyPlaceholder', 'sk-agent-...')}\n className=\"w-full px-3 py-2 border border-border-default rounded-md bg-bg-sunken text-text-primary font-mono text-sm placeholder:text-text-placeholder focus:outline-none focus:border-border-strong\"\n />\n <p className=\"text-xs text-text-muted mt-1\">\n {tr(\n 'agentsPage.agentApiKeyHint',\n 'Authenticates requests to the agent. Typically changes on each agent restart.'\n )}\n </p>\n </div>\n </div>\n <div className=\"flex justify-end gap-2 mt-5\">\n <button\n onClick={() => setEditTunnelAgent(null)}\n className=\"px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors\"\n >\n {tr('agentsPage.cancel', 'Cancel')}\n </button>\n <button\n onClick={handleSaveTunnelSettings}\n disabled={isSavingTunnel}\n className=\"px-4 py-2 text-sm bg-action-primary-bg text-action-primary-text rounded-md hover:bg-action-primary-bg-hover transition-colors disabled:opacity-50\"\n >\n {isSavingTunnel\n ? tr('agentsPage.saving', 'Saving...')\n : tr('agentsPage.save', 'Save')}\n </button>\n </div>\n </motion.div>\n </div>\n )}\n </AnimatePresence>\n\n {/* Quota Exhausted Dialog */}\n <QuotaExhaustedDialog\n isOpen={showQuotaExhausted}\n onClose={() => setShowQuotaExhausted(false)}\n onConfirm={() => {\n setShowQuotaExhausted(false);\n handleNavigate('/billing');\n }}\n title=\"Agent Quota Reached\"\n message=\"You've used all available agent slots on your current plan. Upgrade your plan to register more agents.\"\n />\n </MotionPage>\n );\n}\n"],"mappings":"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"}
|