@burdenoff/microfe-vibecontrols 2026.522.1 → 2026.522.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- {"version":3,"file":"AgentManagerChatPane.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerChatPane.tsx"],"sourcesContent":["/**\n * Main chat interface for a single Agent Manager session.\n *\n * Renders message list + input area, manages the send/receive flow,\n * fetches existing conversation logs on mount, and provides\n * auto-scroll, cancel, and in-session search support.\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo, type FC } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Bookmark, PackagePlus, Search, X } from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { useAgentManagerStreaming } from '@/hooks/useAgentManagerStreaming';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { fetchSessionLogs } from '@/services/aiApi';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { useToggleAgentBookmark } from '@/hooks/useToggleAgentBookmark';\nimport { AgentManagerMessageList } from './AgentManagerMessageList';\nimport { AgentManagerInputArea } from './AgentManagerInputArea';\nimport { AgentManagerBookmarksDrawer } from './AgentManagerBookmarksDrawer';\nimport { PluginInstallModal } from './PluginInstallModal';\nimport { AIProviderCredentialsCard } from '@/components/ai/AIProviderCredentialsCard';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerChatPaneProps {\n sessionId: string;\n agentId: string;\n}\n\nexport const AgentManagerChatPane: FC<AgentManagerChatPaneProps> = ({ sessionId, agentId }) => {\n const [loadingLogs, setLoadingLogs] = useState(false);\n const [searchOpen, setSearchOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n // Provider-install modal triggered from the header. Lets users add a\n // claude/codex/gemini/opencode plugin without leaving the conversation.\n const [installModalOpen, setInstallModalOpen] = useState(false);\n const [bookmarksDrawerOpen, setBookmarksDrawerOpen] = useState(false);\n const [flashMessageId, setFlashMessageId] = useState<string | null>(null);\n const [searchParams, setSearchParams] = useSearchParams();\n const pendingScrollMessageId = searchParams.get('messageId');\n const consumedScrollTargetRef = useRef<string | null>(null);\n const tr = useTr();\n\n const { agents } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((a) => a.id === agentId) ?? null,\n [agents, agentId]\n );\n\n const sessionState = useAgentManagerStore((s) => s.sessionStates[sessionId]);\n const addMessage = useAgentManagerStore((s) => s.addMessage);\n\n const messages = useMemo(\n () => sessionState?.messages ?? [],\n [sessionState?.messages]\n );\n\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const { sendMessage, cancelStream, isStreaming } = useAgentManagerStreaming({\n agent: agentRef,\n sessionId,\n });\n\n const { bookmarkedIds, toggle: toggleBookmark } = useToggleAgentBookmark(sessionId);\n\n const jumpToMessage = useCallback((messageId: string) => {\n const el = document.querySelector(`[data-message-id=\"${messageId}\"]`);\n if (!el) return;\n el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n setFlashMessageId(messageId);\n window.setTimeout(() => {\n setFlashMessageId((current) => (current === messageId ? null : current));\n }, 1500);\n }, []);\n\n const handleToggleBookmark = useCallback(\n (messageId: string) => {\n void toggleBookmark({ agentId, sessionId, messageId });\n },\n [toggleBookmark, agentId, sessionId]\n );\n\n const toggleSearch = useCallback(() => {\n setSearchOpen((prev) => {\n if (prev) setSearchQuery('');\n return !prev;\n });\n }, []);\n\n // Fetch existing conversation logs on mount\n useEffect(() => {\n let cancelled = false;\n\n const loadExistingLogs = async () => {\n // Only load if session has no messages yet\n if (messages.length > 0) return;\n\n setLoadingLogs(true);\n try {\n const logs = await fetchSessionLogs(agentRef, sessionId, {\n types: ['input', 'output'],\n });\n\n if (cancelled || logs.length === 0) return;\n\n const seenLogKeys = new Set<string>();\n\n // Convert session logs to AgentManagerMessage format. The core AI\n // route owns logging, but older provider builds also wrote the same\n // input/output records; collapse same-second duplicates on restore.\n for (const log of logs) {\n const content = log.message || log.content || '';\n const timestamp = log.timestamp || log.createdAt;\n const timestampSecond = timestamp ? Math.floor(new Date(timestamp).getTime() / 1000) : 0;\n const logKey = [log.type, content, log.model ?? '', timestampSecond].join('\\u0000');\n if (seenLogKeys.has(logKey)) continue;\n seenLogKeys.add(logKey);\n\n const msg: AgentManagerMessage = {\n id: log.id,\n role: log.type === 'input' ? 'user' : 'assistant',\n content,\n timestamp,\n tokens: log.tokenCount ? { input: 0, output: log.tokenCount } : undefined,\n model: log.model,\n };\n addMessage(sessionId, msg);\n }\n } catch {\n // Silent fail -- session may not have logs yet\n } finally {\n if (!cancelled) setLoadingLogs(false);\n }\n };\n\n void loadExistingLogs();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only run on mount\n }, [agentId, sessionId]);\n\n // Auto-scroll to a message linked via ?messageId=... (from drawer / cross-session page).\n // Waits for logs to finish loading and the target to be in the DOM before scrolling.\n useEffect(() => {\n if (!pendingScrollMessageId || loadingLogs) return;\n if (consumedScrollTargetRef.current === pendingScrollMessageId) return;\n\n const exists = messages.some((m) => m.id === pendingScrollMessageId);\n if (!exists) return;\n\n consumedScrollTargetRef.current = pendingScrollMessageId;\n jumpToMessage(pendingScrollMessageId);\n\n // Strip the query param so a manual refresh doesn't re-trigger the flash.\n const next = new URLSearchParams(searchParams);\n next.delete('messageId');\n setSearchParams(next, { replace: true });\n }, [pendingScrollMessageId, loadingLogs, messages, jumpToMessage, searchParams, setSearchParams]);\n\n return (\n <div className=\"flex flex-col h-full min-h-0\" data-testid=\"agent-manager-chat-pane\">\n {/* In-session search bar.\n Layout: [search controls (flex-1)] ... [Manage AI providers].\n The search input expands inside the LEFT cluster only, so the\n Manage button stays anchored on the right and never shifts. */}\n <div className=\"flex items-center gap-1 px-3 py-1 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={toggleSearch}\n className={`p-1 rounded transition-colors shrink-0 ${\n searchOpen\n ? 'bg-action-primary-bg/10 text-action-primary-text'\n : 'text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated'\n }`}\n title={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session (Ctrl+F)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session'\n )}\n >\n <Search className=\"w-3.5 h-3.5\" />\n </button>\n {searchOpen && (\n <div className=\"flex items-center gap-1.5 flex-1 min-w-0\">\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={tr(\n 'vibecontrols.agentManager.chatPane.searchPlaceholder',\n 'Search in conversation...'\n )}\n autoFocus\n className=\"flex-1 min-w-0 bg-transparent text-xs text-text-primary placeholder:text-text-tertiary focus:outline-none\"\n />\n {searchQuery && (\n <button\n type=\"button\"\n onClick={() => setSearchQuery('')}\n className=\"p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"w-3 h-3 text-text-tertiary\" />\n </button>\n )}\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => setBookmarksDrawerOpen(true)}\n className=\"shrink-0 p-1 rounded transition-colors text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.bookmarksTooltip',\n 'View bookmarked messages'\n )}\n aria-label={tr('vibecontrols.agentManager.chatPane.bookmarksLabel', 'Bookmarks')}\n data-testid=\"agent-manager-bookmarks-trigger\"\n >\n <Bookmark className=\"w-3.5 h-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setInstallModalOpen(true)}\n className=\"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border-default bg-bg-elevated px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken hover:border-action-primary-bg/40\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.manageProvidersTooltip',\n 'Install or remove AI providers (Claude, Codex, Gemini, OpenCode)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.manageProviders',\n 'Manage AI providers'\n )}\n data-testid=\"agent-manager-manage-providers\"\n >\n <PackagePlus className=\"w-3.5 h-3.5\" />\n <span className=\"hidden sm:inline\">\n {tr('vibecontrols.agentManager.chatPane.manageProviders', 'Manage AI providers')}\n </span>\n </button>\n </div>\n\n {/* Message list */}\n {loadingLogs ? (\n <div className=\"flex-1 flex items-center justify-center\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.agentManager.chatPane.loadingConversation',\n 'Loading conversation...'\n )}\n </p>\n </div>\n ) : (\n <AgentManagerMessageList\n messages={messages}\n isStreaming={isStreaming}\n onCancelStream={cancelStream}\n searchQuery={searchQuery}\n bookmarkedIds={bookmarkedIds}\n onToggleBookmark={handleToggleBookmark}\n flashMessageId={flashMessageId}\n />\n )}\n\n {/* Rich input area with controls */}\n <AgentManagerInputArea\n agentId={agentId}\n sessionId={sessionId}\n onSend={(text) => void sendMessage(text)}\n onCancel={cancelStream}\n agentTunnelUrl={activeAgent?.tunnelUrl ?? undefined}\n agentApiKey={activeAgent?.agentApiKey ?? undefined}\n />\n\n {/* Per-session bookmarks drawer */}\n <AgentManagerBookmarksDrawer\n open={bookmarksDrawerOpen}\n onClose={() => setBookmarksDrawerOpen(false)}\n sessionId={sessionId}\n messages={messages}\n onJump={jumpToMessage}\n />\n\n {/* Header-triggered provider install/remove. Sits below the chat\n tree so it can render full-screen without disturbing layout. */}\n <PluginInstallModal\n open={installModalOpen}\n agentId={agentId}\n onClose={() => setInstallModalOpen(false)}\n />\n\n {/* SDK auth prompt: opens when the streaming hook detects a missing\n ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, so the user can save the\n credential inline instead of being told to go look elsewhere.\n The session's currently-selected SDK is forwarded so the picker\n can preselect the right provider. */}\n <SdkAuthPromptModal\n agentId={agentId}\n agentRef={agentRef}\n initialProviderId={sessionState?.sdk ?? undefined}\n />\n </div>\n );\n};\n\ninterface SdkAuthPromptModalProps {\n agentId: string;\n agentRef: { id: string; profile: string };\n initialProviderId?: string;\n}\n\nconst SdkAuthPromptModal: FC<SdkAuthPromptModalProps> = ({\n agentId,\n agentRef,\n initialProviderId,\n}) => {\n const sdkAuthPrompt = useAgentManagerStore((s) => s.sdkAuthPrompt);\n const setSdkAuthPrompt = useAgentManagerStore((s) => s.setSdkAuthPrompt);\n const tr = useTr();\n\n if (!sdkAuthPrompt || sdkAuthPrompt.agentId !== agentId) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <div\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={() => setSdkAuthPrompt(null)}\n aria-hidden\n />\n {/* Constrain height to the viewport and let the inner body scroll so the\n modal never escapes the screen no matter how many providers/fields\n render inside AIProviderCredentialsCard. The header stays sticky so\n the close button is always reachable. */}\n <div className=\"relative w-full max-w-xl max-h-[85vh] flex flex-col rounded-lg border border-border-default bg-bg-surface shadow-xl overflow-hidden\">\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <h3 className=\"text-base font-semibold text-text-primary truncate pr-2\">\n {tr('vibecontrols.ai.sdkAuth.title', 'Configure AI provider credentials')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('vibecontrols.common.close', 'Close')}\n >\n <X className=\"w-5 h-5 text-text-secondary\" />\n </button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.ai.sdkAuth.body',\n 'The agent does not have a credential configured for this provider. Pick a provider and save one to continue the conversation.'\n )}\n </p>\n <AIProviderCredentialsCard agent={agentRef} initialProviderId={initialProviderId} />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":";;;;;;;;;;;;;;;;;AA8BA,IAAa,KAAuD,EAAE,cAAW,iBAAc;CAC7F,IAAM,CAAC,GAAa,KAAkB,EAAS,GAAM,EAC/C,CAAC,GAAY,MAAiB,EAAS,GAAM,EAC7C,CAAC,GAAa,KAAkB,EAAS,GAAG,EAG5C,CAAC,GAAkB,KAAuB,EAAS,GAAM,EACzD,CAAC,GAAqB,KAA0B,EAAS,GAAM,EAC/D,CAAC,GAAgB,KAAqB,EAAwB,KAAK,EACnE,CAAC,GAAc,KAAmB,GAAiB,EACnD,IAAyB,EAAa,IAAI,YAAY,EACtD,IAA0B,EAAsB,KAAK,EACrD,IAAK,GAAO,EAEZ,EAAE,cAAW,GAAsB,EACnC,IAAc,QACZ,EAAO,MAAM,MAAM,EAAE,OAAO,EAAQ,IAAI,MAC9C,CAAC,GAAQ,EAAQ,CAClB,EAEK,IAAe,GAAsB,MAAM,EAAE,cAAc,GAAW,EACtE,IAAa,GAAsB,MAAM,EAAE,WAAW,EAEtD,IAAW,QACT,GAAc,YAAY,EAAE,EAClC,CAAC,GAAc,SAAS,CACzB,EAEK,EAAE,QAAQ,MAAkB,EAAsB,GAAa,MAAM,KAAK,EAC1E,IAAW,SACR;EAAE,IAAI;EAAS,SAAS;EAAe,GAC9C,CAAC,GAAS,EAAc,CACzB,EAEK,EAAE,gBAAa,iBAAc,mBAAgB,EAAyB;EAC1E,OAAO;EACP;EACD,CAAC,EAEI,EAAE,kBAAe,QAAQ,MAAmB,EAAuB,EAAU,EAE7E,IAAgB,GAAa,MAAsB;EACvD,IAAM,IAAK,SAAS,cAAc,qBAAqB,EAAU,IAAI;AAChE,QACL,EAAG,eAAe;GAAE,UAAU;GAAU,OAAO;GAAU,CAAC,EAC1D,EAAkB,EAAU,EAC5B,OAAO,iBAAiB;AACtB,MAAmB,MAAa,MAAY,IAAY,OAAO,EAAS;KACvE,KAAK;IACP,EAAE,CAAC,EAEA,KAAuB,GAC1B,MAAsB;AAChB,IAAe;GAAE;GAAS;GAAW;GAAW,CAAC;IAExD;EAAC;EAAgB;EAAS;EAAU,CACrC,EAEK,KAAe,QAAkB;AACrC,MAAe,OACT,KAAM,EAAe,GAAG,EACrB,CAAC,GACR;IACD,EAAE,CAAC;AA0EN,QAvEA,QAAgB;EACd,IAAI,IAAY;AA8ChB,UA5CyB,YAAY;AAE/B,WAAS,SAAS,IAEtB;MAAe,GAAK;AACpB,QAAI;KACF,IAAM,IAAO,MAAM,EAAiB,GAAU,GAAW,EACvD,OAAO,CAAC,SAAS,SAAS,EAC3B,CAAC;AAEF,SAAI,KAAa,EAAK,WAAW,EAAG;KAEpC,IAAM,oBAAc,IAAI,KAAa;AAKrC,UAAK,IAAM,KAAO,GAAM;MACtB,IAAM,IAAU,EAAI,WAAW,EAAI,WAAW,IACxC,IAAY,EAAI,aAAa,EAAI,WACjC,IAAkB,IAAY,KAAK,MAAM,IAAI,KAAK,EAAU,CAAC,SAAS,GAAG,IAAK,GAAG,GACjF,IAAS;OAAC,EAAI;OAAM;OAAS,EAAI,SAAS;OAAI;OAAgB,CAAC,KAAK,KAAS;AAC/E,QAAY,IAAI,EAAO,KAC3B,EAAY,IAAI,EAAO,EAUvB,EAAW,GARsB;OAC/B,IAAI,EAAI;OACR,MAAM,EAAI,SAAS,UAAU,SAAS;OACtC;OACA;OACA,QAAQ,EAAI,aAAa;QAAE,OAAO;QAAG,QAAQ,EAAI;QAAY,GAAG,KAAA;OAChE,OAAO,EAAI;OACZ,CACyB;;YAEtB,WAEE;AACR,KAAK,KAAW,EAAe,GAAM;;;MAIlB,QAEV;AACX,OAAY;;IAGb,CAAC,GAAS,EAAU,CAAC,EAIxB,QAAgB;AAKd,MAJI,CAAC,KAA0B,KAC3B,EAAwB,YAAY,KAGpC,CADW,EAAS,MAAM,MAAM,EAAE,OAAO,EAAuB,CACvD;AAGb,EADA,EAAwB,UAAU,GAClC,EAAc,EAAuB;EAGrC,IAAM,IAAO,IAAI,gBAAgB,EAAa;AAE9C,EADA,EAAK,OAAO,YAAY,EACxB,EAAgB,GAAM,EAAE,SAAS,IAAM,CAAC;IACvC;EAAC;EAAwB;EAAa;EAAU;EAAe;EAAc;EAAgB,CAAC,EAG/F,kBAAC,OAAD;EAAK,WAAU;EAA+B,eAAY;YAA1D;GAKE,kBAAC,OAAD;IAAK,WAAU;cAAf;KACE,kBAAC,OAAD;MAAK,WAAU;gBAAf,CACE,kBAAC,UAAD;OACE,MAAK;OACL,SAAS;OACT,WAAW,0CACT,IACI,qDACA;OAEN,OAAO,EACL,sDACA,6BACD;OACD,cAAY,EACV,sDACA,oBACD;iBAED,kBAAC,GAAD,EAAQ,WAAU,eAAgB,CAAA;OAC3B,CAAA,EACR,KACC,kBAAC,OAAD;OAAK,WAAU;iBAAf,CACE,kBAAC,SAAD;QACE,MAAK;QACL,OAAO;QACP,WAAW,MAAM,EAAe,EAAE,OAAO,MAAM;QAC/C,aAAa,EACX,wDACA,4BACD;QACD,WAAA;QACA,WAAU;QACV,CAAA,EACD,KACC,kBAAC,UAAD;QACE,MAAK;QACL,eAAe,EAAe,GAAG;QACjC,WAAU;kBAEV,kBAAC,GAAD,EAAG,WAAU,8BAA+B,CAAA;QACrC,CAAA,CAEP;SAEJ;;KACN,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAuB,GAAK;MAC3C,WAAU;MACV,OAAO,EACL,uDACA,2BACD;MACD,cAAY,EAAG,qDAAqD,YAAY;MAChF,eAAY;gBAEZ,kBAAC,GAAD,EAAU,WAAU,eAAgB,CAAA;MAC7B,CAAA;KACT,kBAAC,UAAD;MACE,MAAK;MACL,eAAe,EAAoB,GAAK;MACxC,WAAU;MACV,OAAO,EACL,6DACA,mEACD;MACD,cAAY,EACV,sDACA,sBACD;MACD,eAAY;gBAZd,CAcE,kBAAC,GAAD,EAAa,WAAU,eAAgB,CAAA,EACvC,kBAAC,QAAD;OAAM,WAAU;iBACb,EAAG,sDAAsD,sBAAsB;OAC3E,CAAA,CACA;;KACL;;GAGL,IACC,kBAAC,OAAD;IAAK,WAAU;cACb,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,0DACA,0BACD;KACC,CAAA;IACA,CAAA,GAEN,kBAAC,GAAD;IACY;IACG;IACb,gBAAgB;IACH;IACE;IACf,kBAAkB;IACF;IAChB,CAAA;GAIJ,kBAAC,GAAD;IACW;IACE;IACX,SAAS,MAAS,KAAK,EAAY,EAAK;IACxC,UAAU;IACV,gBAAgB,GAAa,aAAa,KAAA;IAC1C,aAAa,GAAa,eAAe,KAAA;IACzC,CAAA;GAGF,kBAAC,GAAD;IACE,MAAM;IACN,eAAe,EAAuB,GAAM;IACjC;IACD;IACV,QAAQ;IACR,CAAA;GAIF,kBAAC,IAAD;IACE,MAAM;IACG;IACT,eAAe,EAAoB,GAAM;IACzC,CAAA;GAOF,kBAAC,GAAD;IACW;IACC;IACV,mBAAmB,GAAc,OAAO,KAAA;IACxC,CAAA;GACE;;GAUJ,KAAmD,EACvD,YACA,aACA,2BACI;CACJ,IAAM,IAAgB,GAAsB,MAAM,EAAE,cAAc,EAC5D,IAAmB,GAAsB,MAAM,EAAE,iBAAiB,EAClE,IAAK,GAAO;AAIlB,QAFI,CAAC,KAAiB,EAAc,YAAY,IAAgB,OAG9D,kBAAC,OAAD;EAAK,WAAU;YAAf,CACE,kBAAC,OAAD;GACE,WAAU;GACV,eAAe,EAAiB,KAAK;GACrC,eAAA;GACA,CAAA,EAKF,kBAAC,OAAD;GAAK,WAAU;aAAf,CACE,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,MAAD;KAAI,WAAU;eACX,EAAG,iCAAiC,oCAAoC;KACtE,CAAA,EACL,kBAAC,UAAD;KACE,MAAK;KACL,eAAe,EAAiB,KAAK;KACrC,WAAU;KACV,cAAY,EAAG,6BAA6B,QAAQ;eAEpD,kBAAC,GAAD,EAAG,WAAU,+BAAgC,CAAA;KACtC,CAAA,CACL;OACN,kBAAC,OAAD;IAAK,WAAU;cAAf,CACE,kBAAC,KAAD;KAAG,WAAU;eACV,EACC,gCACA,gIACD;KACC,CAAA,EACJ,kBAAC,GAAD;KAA2B,OAAO;KAA6B;KAAqB,CAAA,CAChF;MACF;KACF"}
1
+ {"version":3,"file":"AgentManagerChatPane.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerChatPane.tsx"],"sourcesContent":["/**\n * Main chat interface for a single Agent Manager session.\n *\n * Renders message list + input area, manages the send/receive flow,\n * fetches existing conversation logs on mount, and provides\n * auto-scroll, cancel, and in-session search support.\n */\n\nimport { useEffect, useRef, useState, useCallback, useMemo, type FC } from 'react';\nimport { useSearchParams } from 'react-router-dom';\nimport { Bookmark, PackagePlus, Search, X } from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { useAgentManagerStreaming } from '@/hooks/useAgentManagerStreaming';\nimport { useCachedInitialData } from '@/hooks/useCachedInitialData';\nimport { fetchSessionLogs } from '@/services/aiApi';\nimport { useActiveAgentProfile } from '@/hooks/useActiveAgentProfile';\nimport { useToggleAgentBookmark } from '@/hooks/useToggleAgentBookmark';\nimport { AgentManagerMessageList } from './AgentManagerMessageList';\nimport { AgentManagerInputArea } from './AgentManagerInputArea';\nimport { AgentManagerBookmarksDrawer } from './AgentManagerBookmarksDrawer';\nimport { PluginInstallModal } from './PluginInstallModal';\nimport { AIProviderCredentialsCard } from '@/components/ai/AIProviderCredentialsCard';\nimport type { AgentManagerMessage } from '@/types/agentManager';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerChatPaneProps {\n sessionId: string;\n agentId: string;\n}\n\nexport const AgentManagerChatPane: FC<AgentManagerChatPaneProps> = ({ sessionId, agentId }) => {\n const [loadingLogs, setLoadingLogs] = useState(false);\n const [searchOpen, setSearchOpen] = useState(false);\n const [searchQuery, setSearchQuery] = useState('');\n // Provider-install modal triggered from the header. Lets users add a\n // claude/codex/gemini/opencode plugin without leaving the conversation.\n const [installModalOpen, setInstallModalOpen] = useState(false);\n const [bookmarksDrawerOpen, setBookmarksDrawerOpen] = useState(false);\n const [flashMessageId, setFlashMessageId] = useState<string | null>(null);\n const [searchParams, setSearchParams] = useSearchParams();\n const pendingScrollMessageId = searchParams.get('messageId');\n const consumedScrollTargetRef = useRef<string | null>(null);\n const tr = useTr();\n\n const { agents } = useCachedInitialData();\n const activeAgent = useMemo(\n () => agents.find((a) => a.id === agentId) ?? null,\n [agents, agentId]\n );\n\n const sessionState = useAgentManagerStore((s) => s.sessionStates[sessionId]);\n const addMessage = useAgentManagerStore((s) => s.addMessage);\n\n const messages = useMemo(() => sessionState?.messages ?? [], [sessionState?.messages]);\n\n const { active: activeProfile } = useActiveAgentProfile(activeAgent?.id ?? null);\n const agentRef = useMemo(\n () => ({ id: agentId, profile: activeProfile }),\n [agentId, activeProfile]\n );\n\n const { sendMessage, cancelStream, isStreaming } = useAgentManagerStreaming({\n agent: agentRef,\n sessionId,\n });\n\n const { bookmarkedIds, toggle: toggleBookmark } = useToggleAgentBookmark(sessionId);\n\n const jumpToMessage = useCallback((messageId: string) => {\n const el = document.querySelector(`[data-message-id=\"${messageId}\"]`);\n if (!el) return;\n el.scrollIntoView({ behavior: 'smooth', block: 'center' });\n setFlashMessageId(messageId);\n window.setTimeout(() => {\n setFlashMessageId((current) => (current === messageId ? null : current));\n }, 1500);\n }, []);\n\n const handleToggleBookmark = useCallback(\n (messageId: string) => {\n void toggleBookmark({ agentId, sessionId, messageId });\n },\n [toggleBookmark, agentId, sessionId]\n );\n\n const toggleSearch = useCallback(() => {\n setSearchOpen((prev) => {\n if (prev) setSearchQuery('');\n return !prev;\n });\n }, []);\n\n // Fetch existing conversation logs on mount\n useEffect(() => {\n let cancelled = false;\n\n const loadExistingLogs = async () => {\n // Only load if session has no messages yet\n if (messages.length > 0) return;\n\n setLoadingLogs(true);\n try {\n const logs = await fetchSessionLogs(agentRef, sessionId, {\n types: ['input', 'output'],\n });\n\n if (cancelled || logs.length === 0) return;\n\n const seenLogKeys = new Set<string>();\n\n // Convert session logs to AgentManagerMessage format. The core AI\n // route owns logging, but older provider builds also wrote the same\n // input/output records; collapse same-second duplicates on restore.\n for (const log of logs) {\n const content = log.message || log.content || '';\n const timestamp = log.timestamp || log.createdAt;\n const timestampSecond = timestamp ? Math.floor(new Date(timestamp).getTime() / 1000) : 0;\n const logKey = [log.type, content, log.model ?? '', timestampSecond].join('\\u0000');\n if (seenLogKeys.has(logKey)) continue;\n seenLogKeys.add(logKey);\n\n const msg: AgentManagerMessage = {\n id: log.id,\n role: log.type === 'input' ? 'user' : 'assistant',\n content,\n timestamp,\n tokens: log.tokenCount ? { input: 0, output: log.tokenCount } : undefined,\n model: log.model,\n };\n addMessage(sessionId, msg);\n }\n } catch {\n // Silent fail -- session may not have logs yet\n } finally {\n if (!cancelled) setLoadingLogs(false);\n }\n };\n\n void loadExistingLogs();\n\n return () => {\n cancelled = true;\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps -- only run on mount\n }, [agentId, sessionId]);\n\n // Auto-scroll to a message linked via ?messageId=... (from drawer / cross-session page).\n // Waits for logs to finish loading and the target to be in the DOM before scrolling.\n useEffect(() => {\n if (!pendingScrollMessageId || loadingLogs) return;\n if (consumedScrollTargetRef.current === pendingScrollMessageId) return;\n\n const exists = messages.some((m) => m.id === pendingScrollMessageId);\n if (!exists) return;\n\n consumedScrollTargetRef.current = pendingScrollMessageId;\n jumpToMessage(pendingScrollMessageId);\n\n // Strip the query param so a manual refresh doesn't re-trigger the flash.\n const next = new URLSearchParams(searchParams);\n next.delete('messageId');\n setSearchParams(next, { replace: true });\n }, [pendingScrollMessageId, loadingLogs, messages, jumpToMessage, searchParams, setSearchParams]);\n\n return (\n <div className=\"flex flex-col h-full min-h-0\" data-testid=\"agent-manager-chat-pane\">\n {/* In-session search bar.\n Layout: [search controls (flex-1)] ... [Manage AI providers].\n The search input expands inside the LEFT cluster only, so the\n Manage button stays anchored on the right and never shifts. */}\n <div className=\"flex items-center gap-1 px-3 py-1 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <div className=\"flex items-center gap-1.5 min-w-0 flex-1\">\n <button\n type=\"button\"\n onClick={toggleSearch}\n className={`p-1 rounded transition-colors shrink-0 ${\n searchOpen\n ? 'bg-action-primary-bg/10 text-action-primary-text'\n : 'text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated'\n }`}\n title={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session (Ctrl+F)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.searchInSession',\n 'Search in session'\n )}\n >\n <Search className=\"w-3.5 h-3.5\" />\n </button>\n {searchOpen && (\n <div className=\"flex items-center gap-1.5 flex-1 min-w-0\">\n <input\n type=\"text\"\n value={searchQuery}\n onChange={(e) => setSearchQuery(e.target.value)}\n placeholder={tr(\n 'vibecontrols.agentManager.chatPane.searchPlaceholder',\n 'Search in conversation...'\n )}\n autoFocus\n className=\"flex-1 min-w-0 bg-transparent text-xs text-text-primary placeholder:text-text-tertiary focus:outline-none\"\n />\n {searchQuery && (\n <button\n type=\"button\"\n onClick={() => setSearchQuery('')}\n className=\"p-0.5 rounded hover:bg-bg-elevated transition-colors\"\n >\n <X className=\"w-3 h-3 text-text-tertiary\" />\n </button>\n )}\n </div>\n )}\n </div>\n <button\n type=\"button\"\n onClick={() => setBookmarksDrawerOpen(true)}\n className=\"shrink-0 p-1 rounded transition-colors text-text-tertiary hover:text-text-secondary hover:bg-bg-elevated\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.bookmarksTooltip',\n 'View bookmarked messages'\n )}\n aria-label={tr('vibecontrols.agentManager.chatPane.bookmarksLabel', 'Bookmarks')}\n data-testid=\"agent-manager-bookmarks-trigger\"\n >\n <Bookmark className=\"w-3.5 h-3.5\" />\n </button>\n <button\n type=\"button\"\n onClick={() => setInstallModalOpen(true)}\n className=\"shrink-0 inline-flex items-center gap-1.5 rounded-md border border-border-default bg-bg-elevated px-2 py-1 text-xs font-medium text-text-primary transition-colors hover:bg-bg-sunken hover:border-action-primary-bg/40\"\n title={tr(\n 'vibecontrols.agentManager.chatPane.manageProvidersTooltip',\n 'Install or remove AI providers (Claude, Codex, Gemini, OpenCode)'\n )}\n aria-label={tr(\n 'vibecontrols.agentManager.chatPane.manageProviders',\n 'Manage AI providers'\n )}\n data-testid=\"agent-manager-manage-providers\"\n >\n <PackagePlus className=\"w-3.5 h-3.5\" />\n <span className=\"hidden sm:inline\">\n {tr('vibecontrols.agentManager.chatPane.manageProviders', 'Manage AI providers')}\n </span>\n </button>\n </div>\n\n {/* Message list */}\n {loadingLogs ? (\n <div className=\"flex-1 flex items-center justify-center\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.agentManager.chatPane.loadingConversation',\n 'Loading conversation...'\n )}\n </p>\n </div>\n ) : (\n <AgentManagerMessageList\n messages={messages}\n isStreaming={isStreaming}\n onCancelStream={cancelStream}\n searchQuery={searchQuery}\n bookmarkedIds={bookmarkedIds}\n onToggleBookmark={handleToggleBookmark}\n flashMessageId={flashMessageId}\n />\n )}\n\n {/* Rich input area with controls */}\n <AgentManagerInputArea\n agentId={agentId}\n sessionId={sessionId}\n onSend={(text) => void sendMessage(text)}\n onCancel={cancelStream}\n agentTunnelUrl={activeAgent?.tunnelUrl ?? undefined}\n agentApiKey={activeAgent?.agentApiKey ?? undefined}\n />\n\n {/* Per-session bookmarks drawer */}\n <AgentManagerBookmarksDrawer\n open={bookmarksDrawerOpen}\n onClose={() => setBookmarksDrawerOpen(false)}\n sessionId={sessionId}\n messages={messages}\n onJump={jumpToMessage}\n />\n\n {/* Header-triggered provider install/remove. Sits below the chat\n tree so it can render full-screen without disturbing layout. */}\n <PluginInstallModal\n open={installModalOpen}\n agentId={agentId}\n onClose={() => setInstallModalOpen(false)}\n />\n\n {/* SDK auth prompt: opens when the streaming hook detects a missing\n ANTHROPIC_API_KEY / ANTHROPIC_AUTH_TOKEN, so the user can save the\n credential inline instead of being told to go look elsewhere.\n The session's currently-selected SDK is forwarded so the picker\n can preselect the right provider. */}\n <SdkAuthPromptModal\n agentId={agentId}\n agentRef={agentRef}\n initialProviderId={sessionState?.sdk ?? undefined}\n />\n </div>\n );\n};\n\ninterface SdkAuthPromptModalProps {\n agentId: string;\n agentRef: { id: string; profile: string };\n initialProviderId?: string;\n}\n\nconst SdkAuthPromptModal: FC<SdkAuthPromptModalProps> = ({\n agentId,\n agentRef,\n initialProviderId,\n}) => {\n const sdkAuthPrompt = useAgentManagerStore((s) => s.sdkAuthPrompt);\n const setSdkAuthPrompt = useAgentManagerStore((s) => s.setSdkAuthPrompt);\n const tr = useTr();\n\n if (!sdkAuthPrompt || sdkAuthPrompt.agentId !== agentId) return null;\n\n return (\n <div className=\"fixed inset-0 z-50 flex items-center justify-center p-4\">\n <div\n className=\"absolute inset-0 bg-bg-overlay\"\n onClick={() => setSdkAuthPrompt(null)}\n aria-hidden\n />\n {/* Constrain height to the viewport and let the inner body scroll so the\n modal never escapes the screen no matter how many providers/fields\n render inside AIProviderCredentialsCard. The header stays sticky so\n the close button is always reachable. */}\n <div className=\"relative w-full max-w-xl max-h-[85vh] flex flex-col rounded-lg border border-border-default bg-bg-surface shadow-xl overflow-hidden\">\n <div className=\"flex items-center justify-between px-4 py-3 border-b border-border-default bg-bg-surface flex-shrink-0\">\n <h3 className=\"text-base font-semibold text-text-primary truncate pr-2\">\n {tr('vibecontrols.ai.sdkAuth.title', 'Configure AI provider credentials')}\n </h3>\n <button\n type=\"button\"\n onClick={() => setSdkAuthPrompt(null)}\n className=\"p-1 rounded hover:bg-bg-sunken transition-colors flex-shrink-0\"\n aria-label={tr('vibecontrols.common.close', 'Close')}\n >\n <X className=\"w-5 h-5 text-text-secondary\" />\n </button>\n </div>\n <div className=\"flex-1 min-h-0 overflow-y-auto px-4 py-3 space-y-3\">\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'vibecontrols.ai.sdkAuth.body',\n 'The agent does not have a credential configured for this provider. Pick a provider and save one to continue the conversation.'\n )}\n </p>\n <AIProviderCredentialsCard agent={agentRef} initialProviderId={initialProviderId} />\n </div>\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1,8 +1,8 @@
1
+ import "./AnalyticsBreakdownChart.js";
2
+ import "./AnalyticsTimeSeriesChart.js";
1
3
  import "./types.js";
2
4
  import "./AnalyticsFilterBar.js";
3
5
  import "./AnalyticsKpiGrid.js";
4
- import "./AnalyticsTimeSeriesChart.js";
5
- import "./AnalyticsBreakdownChart.js";
6
6
  import "./AnalyticsBreakdownTable.js";
7
7
  import "./AnalyticsEntityTabs.js";
8
8
  import "./SandboxAnalyticsSection.js";
@@ -1,9 +1,9 @@
1
1
  import { useTr as e } from "../../shared/hooks/useTr.js";
2
- import { formatDuration as t } from "../../utils/relativeTime.js";
3
- import { Tooltip as n } from "../../node_modules/recharts/es6/component/Tooltip.js";
4
- import { ResponsiveContainer as r } from "../../node_modules/recharts/es6/component/ResponsiveContainer.js";
5
- import { Area as i } from "../../node_modules/recharts/es6/cartesian/Area.js";
6
- import { AreaChart as a } from "../../node_modules/recharts/es6/chart/AreaChart.js";
2
+ import { Tooltip as t } from "../../node_modules/recharts/es6/component/Tooltip.js";
3
+ import { ResponsiveContainer as n } from "../../node_modules/recharts/es6/component/ResponsiveContainer.js";
4
+ import { Area as r } from "../../node_modules/recharts/es6/cartesian/Area.js";
5
+ import { AreaChart as i } from "../../node_modules/recharts/es6/chart/AreaChart.js";
6
+ import { formatDuration as a } from "../../utils/relativeTime.js";
7
7
  import { useMemo as o } from "react";
8
8
  import { Timer as s } from "lucide-react";
9
9
  import { jsx as c, jsxs as l } from "react/jsx-runtime";
@@ -35,7 +35,7 @@ var u = "var(--color-text-link)", d = ({ averageDuration: d }) => {
35
35
  className: "mb-3",
36
36
  children: [/* @__PURE__ */ c("p", {
37
37
  className: "text-3xl font-bold text-text-primary",
38
- children: t(d)
38
+ children: a(d)
39
39
  }), /* @__PURE__ */ c("p", {
40
40
  className: "text-xs text-text-secondary mt-0.5",
41
41
  children: f("vibecontrols.gitops.ciTab.perPipelineRun", "per pipeline run")
@@ -43,10 +43,10 @@ var u = "var(--color-text-link)", d = ({ averageDuration: d }) => {
43
43
  }),
44
44
  /* @__PURE__ */ c("div", {
45
45
  className: "h-16",
46
- children: /* @__PURE__ */ c(r, {
46
+ children: /* @__PURE__ */ c(n, {
47
47
  width: "100%",
48
48
  height: "100%",
49
- children: /* @__PURE__ */ l(a, {
49
+ children: /* @__PURE__ */ l(i, {
50
50
  data: p,
51
51
  margin: {
52
52
  top: 0,
@@ -71,17 +71,17 @@ var u = "var(--color-text-link)", d = ({ averageDuration: d }) => {
71
71
  stopOpacity: 0
72
72
  })]
73
73
  }) }),
74
- /* @__PURE__ */ c(n, {
74
+ /* @__PURE__ */ c(t, {
75
75
  contentStyle: {
76
76
  background: "var(--color-bg-surface)",
77
77
  border: "1px solid var(--color-border-default)",
78
78
  borderRadius: "6px",
79
79
  fontSize: "11px"
80
80
  },
81
- formatter: (e) => [t(Math.round(e)), "Duration"],
81
+ formatter: (e) => [a(Math.round(e)), "Duration"],
82
82
  labelFormatter: () => ""
83
83
  }),
84
- /* @__PURE__ */ c(i, {
84
+ /* @__PURE__ */ c(r, {
85
85
  type: "monotone",
86
86
  dataKey: "v",
87
87
  stroke: u,
@@ -83,6 +83,6 @@ function a({ header: a, variant: o = "rows", count: s = 6 }) {
83
83
  });
84
84
  }
85
85
  //#endregion
86
- export { a as PageSkeleton };
86
+ export { a as PageSkeleton, n as SkeletonLine };
87
87
 
88
88
  //# sourceMappingURL=PageSkeleton.js.map
@@ -1,4 +1,4 @@
1
- import { useAddAgentFromTunnelMutation as e, useAgentHeartbeatMutation as t, useCreateAgentMutation as n, useCreateTargetMutation as r, useDeactivateAgentMutation as i, useDeleteAgentMutation as ee, useListAgentsQuery as te, useStartVibecontrolsAgentMutation as ne, useStopVibecontrolsAgentMutation as re, useUpdateAgentMutation as ie, useVibecontrolsAgentHealthProbeStreamSubscription as ae, useVibecontrolsAgentHealthProbesQuery as oe } from "../generated/wspace-operations.js";
1
+ import { useAddAgentFromTunnelMutation as e, useAgentHeartbeatMutation as t, useCreateAgentMutation as n, useCreateTargetMutation as r, useDeactivateAgentMutation as i, useDeleteAgentMutation as ee, useListAgentsQuery as te, useStartVibecontrolsAgentMutation as ne, useStopVibecontrolsAgentMutation as re, useUpdateAgentMutation as ie, useVibecontrolsAgentHealthProbeStreamSubscription as ae, useVibecontrolsAgentHealthProbesLazyQuery as oe } from "../generated/wspace-operations.js";
2
2
  import { useVibeControls as se, useVibeNavigate as ce } from "../providers/VibeControlsProvider.js";
3
3
  import { useVibeControlsStore as a } from "../store/vibeControlsStore.js";
4
4
  import le from "../hooks/useCachedInitialData.js";
@@ -122,7 +122,7 @@ function y() {
122
122
  At,
123
123
  wt,
124
124
  b
125
- ]), { data: Mt, loading: G, error: K, refetch: q } = te({
125
+ ]), { data: Mt, loading: G, error: Nt, refetch: K } = te({
126
126
  variables: {
127
127
  filter: {
128
128
  isActive: w === "ACTIVE" ? !0 : w === "OFFLINE" ? !1 : void 0,
@@ -132,20 +132,18 @@ function y() {
132
132
  },
133
133
  fetchPolicy: "cache-first",
134
134
  skip: !b
135
- }), J = oe({
136
- fetchPolicy: "cache-and-network",
137
- nextFetchPolicy: "cache-first",
138
- notifyOnNetworkStatusChange: !0,
139
- skip: !b
140
- }), { data: Y } = ae({ skip: !b }), Nt = J.data === void 0 && Y === void 0, X = c(() => {
141
- let e = /* @__PURE__ */ new Map(), t = Y?.vibecontrolsAgentHealthProbeStream ?? J.data?.vibecontrolsAgentHealthProbes ?? [];
135
+ }), [Pt, q] = oe({
136
+ fetchPolicy: "network-only",
137
+ notifyOnNetworkStatusChange: !0
138
+ }), { data: J } = ae({ skip: !b }), Ft = q.data === void 0 && J === void 0, Y = c(() => {
139
+ let e = /* @__PURE__ */ new Map(), t = J?.vibecontrolsAgentHealthProbeStream ?? q.data?.vibecontrolsAgentHealthProbes ?? [];
142
140
  for (let n of t) e.set(n.agentId, {
143
141
  agentId: n.agentId,
144
142
  healthy: n.healthy,
145
143
  errorMessage: n.errorMessage
146
144
  });
147
145
  return e;
148
- }, [J.data, Y]), Z = c(() => (Mt?.vibecontrolsAgents ?? []).map((e) => ({
146
+ }, [q.data, J]), X = c(() => (Mt?.vibecontrolsAgents ?? []).map((e) => ({
149
147
  id: e.id,
150
148
  workspaceId: e.workspaceId,
151
149
  name: e.name,
@@ -158,8 +156,8 @@ function y() {
158
156
  id: e.id,
159
157
  isActive: e.isActive,
160
158
  metadata: e.metadata
161
- }, X, { probesLoading: Nt }),
162
- healthError: X.get(e.id)?.errorMessage ?? (e.lastGatewayAuthPushStatus === "failed" ? e.lastGatewayAuthPushError ?? "Gateway-auth push failed" : null),
159
+ }, Y, { probesLoading: Ft }),
160
+ healthError: Y.get(e.id)?.errorMessage ?? (e.lastGatewayAuthPushStatus === "failed" ? e.lastGatewayAuthPushError ?? "Gateway-auth push failed" : null),
163
161
  lastHeartbeat: e.lastHeartbeat || null,
164
162
  tunnelUrl: e.tunnelUrl ?? null,
165
163
  tunnelStatus: e.tunnelStatus ?? null,
@@ -184,8 +182,8 @@ function y() {
184
182
  }))
185
183
  })), [
186
184
  Mt?.vibecontrolsAgents,
187
- X,
188
- Nt,
185
+ Y,
186
+ Ft,
189
187
  at
190
188
  ]);
191
189
  s(() => {
@@ -194,46 +192,46 @@ function y() {
194
192
  let t = Date.now(), n = Infinity;
195
193
  for (let r of e) {
196
194
  let e = j[r];
197
- e !== void 0 && (X.get(r)?.healthy === !0 && t >= e ? M(r) : e < n && (n = e));
195
+ e !== void 0 && (Y.get(r)?.healthy === !0 && t >= e ? M(r) : e < n && (n = e));
198
196
  }
199
197
  if (n === Infinity) return;
200
198
  let r = Math.max(0, n - t) + 50, i = setTimeout(() => {
201
199
  let e = a.getState().connectingAgents;
202
200
  for (let t of Object.keys(e)) {
203
201
  let n = e[t];
204
- n !== void 0 && X.get(t)?.healthy === !0 && Date.now() >= n && M(t);
202
+ n !== void 0 && Y.get(t)?.healthy === !0 && Date.now() >= n && M(t);
205
203
  }
206
204
  }, r);
207
205
  return () => clearTimeout(i);
208
206
  }, [
209
- X,
207
+ Y,
210
208
  j,
211
209
  M
212
210
  ]);
213
- let Q = c(() => Z.filter((e) => {
211
+ let Z = c(() => X.filter((e) => {
214
212
  if (C) {
215
213
  let t = C.toLowerCase();
216
214
  if (!(e.name.toLowerCase().includes(t) || e.hostname?.toLowerCase().includes(t) || e.id.toLowerCase().includes(t))) return !1;
217
215
  }
218
216
  return !(E !== "all" && !(e.tagAssignments ?? []).some((e) => e.tagId === E));
219
217
  }), [
220
- Z,
218
+ X,
221
219
  C,
222
220
  E
223
221
  ]);
224
222
  s(() => () => {
225
223
  H.current && clearTimeout(H.current);
226
224
  }, []);
227
- let Pt = (e) => {
225
+ let It = (e) => {
228
226
  x(`/agents/${e.id}`), y?.({
229
227
  id: e.id,
230
228
  name: e.name,
231
229
  type: e.platform || "unknown",
232
230
  status: e.status || (e.isActive ? "ACTIVE" : "OFFLINE")
233
231
  });
234
- }, $ = () => {
232
+ }, Q = () => {
235
233
  O(!0);
236
- }, Ft = async (e) => {
234
+ }, Lt = async (e) => {
237
235
  try {
238
236
  tt(!0);
239
237
  let t = e.tunnelUrl?.trim(), n = e.agentApiKey?.trim();
@@ -271,7 +269,7 @@ function y() {
271
269
  } finally {
272
270
  tt(!1);
273
271
  }
274
- }, It = async (e) => {
272
+ }, Rt = async (e) => {
275
273
  try {
276
274
  let t = (await bt({ variables: { agentId: e.id } })).data?.startVibecontrolsAgent;
277
275
  if (!t?.success) throw Error(t?.error || "Failed to start agent");
@@ -279,7 +277,7 @@ function y() {
279
277
  } catch (e) {
280
278
  console.error("Failed to start agent:", e), W(e instanceof Error ? e.message : "Failed to start agent");
281
279
  }
282
- }, Lt = async (e) => {
280
+ }, zt = async (e) => {
283
281
  try {
284
282
  let t = (await xt({ variables: { agentId: e.id } })).data?.stopVibecontrolsAgent;
285
283
  if (!t?.success) throw Error(t?.error || "Failed to stop agent");
@@ -287,9 +285,9 @@ function y() {
287
285
  } catch (e) {
288
286
  console.error("Failed to stop agent:", e), W(e instanceof Error ? e.message : "Failed to stop agent");
289
287
  }
290
- }, Rt = (e) => {
288
+ }, $ = (e) => {
291
289
  P(e);
292
- }, zt = async () => {
290
+ }, Bt = async () => {
293
291
  if (!N) return;
294
292
  let e = N;
295
293
  I(!0);
@@ -299,7 +297,7 @@ function y() {
299
297
  refetchQueries: []
300
298
  });
301
299
  } catch (e) {
302
- console.error("Failed to deactivate agent:", e), W(e instanceof Error ? e.message : "Failed to disconnect agent"), I(!1), P(null), await q();
300
+ console.error("Failed to deactivate agent:", e), W(e instanceof Error ? e.message : "Failed to disconnect agent"), I(!1), P(null), await K();
303
301
  return;
304
302
  }
305
303
  try {
@@ -317,12 +315,12 @@ function y() {
317
315
  } catch (e) {
318
316
  console.error("Failed to clear tunnel fields:", e), W("Agent marked offline, but failed to clear connection fields — clear them manually in Settings.");
319
317
  }
320
- I(!1), P(null), await q();
321
- }, Bt = (e) => {
322
- x(`/agents/${e.id}`);
318
+ I(!1), P(null), await K();
323
319
  }, Vt = (e) => {
320
+ x(`/agents/${e.id}`);
321
+ }, Ht = (e) => {
324
322
  A(e);
325
- }, Ht = async () => {
323
+ }, Ut = async () => {
326
324
  if (!k) return;
327
325
  let e = k.id, t = st.current?.value?.trim() || "", n = ct.current?.value?.trim() || "";
328
326
  try {
@@ -342,16 +340,16 @@ function y() {
342
340
  } finally {
343
341
  rt(!1);
344
342
  }
345
- }, Ut = o((e) => {
343
+ }, Wt = o((e) => {
346
344
  R((t) => {
347
345
  let n = new Set(t);
348
346
  return n.has(e) ? n.delete(e) : n.add(e), n;
349
347
  });
350
- }, []), Wt = o(() => {
351
- R(new Set(Q.map((e) => e.id)));
352
- }, [Q]), Gt = o(() => {
348
+ }, []), Gt = o(() => {
349
+ R(new Set(Z.map((e) => e.id)));
350
+ }, [Z]), Kt = o(() => {
353
351
  R(/* @__PURE__ */ new Set());
354
- }, []), Kt = Q.length > 0 && L.size === Q.length, qt = L.size > 0, Jt = o(async () => {
352
+ }, []), qt = Z.length > 0 && L.size === Z.length, Jt = L.size > 0, Yt = o(async () => {
355
353
  if (L.size !== 0 && window.confirm(S("agentsPage.bulkDeleteConfirm", "Are you sure you want to remove {{count}} agent(s)? This cannot be undone.", { count: L.size }))) {
356
354
  ut(!0);
357
355
  try {
@@ -368,10 +366,10 @@ function y() {
368
366
  kt,
369
367
  W,
370
368
  U
371
- ]), Yt = {
372
- total: Z.length,
373
- active: Z.filter((e) => e.isActive).length,
374
- offline: Z.filter((e) => !e.isActive).length
369
+ ]), Xt = {
370
+ total: X.length,
371
+ active: X.filter((e) => e.isActive).length,
372
+ offline: X.filter((e) => !e.isActive).length
375
373
  };
376
374
  return He ? /* @__PURE__ */ m(v, {
377
375
  className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
@@ -382,12 +380,12 @@ function y() {
382
380
  title: S("agentsPage.accessDeniedTitle", "Access Denied"),
383
381
  description: S("agentsPage.accessDeniedDescription", "You don't have permission to view agents. Contact your workspace administrator for access.")
384
382
  })
385
- }) : K && Z.length === 0 ? /* @__PURE__ */ m(v, {
383
+ }) : Nt && X.length === 0 ? /* @__PURE__ */ m(v, {
386
384
  className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
387
385
  children: /* @__PURE__ */ m(fe, {
388
386
  title: S("agentsPage.failedLoad", "Failed to load agents"),
389
- message: K.message,
390
- onRetry: () => q()
387
+ message: Nt.message,
388
+ onRetry: () => K()
391
389
  })
392
390
  }) : /* @__PURE__ */ h(v, {
393
391
  className: "space-y-6 p-4 sm:p-6 bg-bg-surface min-h-screen",
@@ -403,40 +401,40 @@ function y() {
403
401
  /* @__PURE__ */ m(ue, {
404
402
  title: S("agentsPage.title", "Agents"),
405
403
  description: S("agentsPage.descriptionOnline", "{{active}} of {{total}} agents online", {
406
- active: Yt.active,
407
- total: Yt.total
404
+ active: Xt.active,
405
+ total: Xt.total
408
406
  }),
409
407
  actions: /* @__PURE__ */ h("div", {
410
408
  className: "flex w-full flex-col items-end gap-2 sm:w-auto sm:flex-row sm:items-center",
411
409
  children: [/* @__PURE__ */ m("button", {
412
410
  onClick: () => {
413
- q(), J.refetch();
411
+ K(), Pt();
414
412
  },
415
- disabled: G || J.loading,
413
+ disabled: G || q.loading,
416
414
  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",
417
415
  title: S("agentsPage.refreshTitle", "Refresh agents"),
418
- children: /* @__PURE__ */ m(Pe, { className: `w-4 h-4 ${G || J.loading ? "animate-spin" : ""}` })
416
+ children: /* @__PURE__ */ m(Pe, { className: `w-4 h-4 ${G || q.loading ? "animate-spin" : ""}` })
419
417
  }), Dt ? /* @__PURE__ */ m(Be, {
420
418
  permission: Ce.AGENT_CREATE,
421
419
  deniedBehavior: "disable",
422
420
  deniedTooltip: S("agentsPage.deniedCreateTooltip", "You don't have permission to create agents"),
423
421
  children: /* @__PURE__ */ h("button", {
424
- onClick: $,
422
+ onClick: Q,
425
423
  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",
426
424
  children: [/* @__PURE__ */ m(d, { className: "w-4 h-4" }), S("agentsPage.addAgent", "Add Agent")]
427
425
  })
428
426
  }) : /* @__PURE__ */ h("button", {
429
- onClick: $,
427
+ onClick: Q,
430
428
  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",
431
429
  children: [/* @__PURE__ */ m(d, { className: "w-4 h-4" }), S("agentsPage.addAgent", "Add Agent")]
432
430
  })]
433
431
  })
434
432
  }),
435
- Z.length === 0 && !G && /* @__PURE__ */ m(be, {
433
+ X.length === 0 && !G && /* @__PURE__ */ m(be, {
436
434
  variant: "full",
437
435
  dismissKey: "vibecontrols-agents-setup-banner"
438
436
  }),
439
- Z.length > 0 && /* @__PURE__ */ m(be, {
437
+ X.length > 0 && /* @__PURE__ */ m(be, {
440
438
  variant: "compact",
441
439
  dismissKey: "vibecontrols-agents-compact-banner"
442
440
  }),
@@ -490,18 +488,18 @@ function y() {
490
488
  ]
491
489
  })]
492
490
  }),
493
- Q.length > 0 && /* @__PURE__ */ m("div", {
491
+ Z.length > 0 && /* @__PURE__ */ m("div", {
494
492
  className: "flex items-center gap-2 flex-wrap",
495
493
  children: /* @__PURE__ */ m("div", {
496
494
  className: "ml-auto flex items-center gap-2",
497
495
  children: /* @__PURE__ */ h("button", {
498
- onClick: Kt ? Gt : Wt,
496
+ onClick: qt ? Kt : Gt,
499
497
  className: "flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors",
500
- children: [/* @__PURE__ */ m(je, { className: "w-3.5 h-3.5" }), Kt ? S("agentsPage.deselectAll", "Deselect All") : S("agentsPage.selectAll", "Select All")]
498
+ children: [/* @__PURE__ */ m(je, { className: "w-3.5 h-3.5" }), qt ? S("agentsPage.deselectAll", "Deselect All") : S("agentsPage.selectAll", "Select All")]
501
499
  })
502
500
  })
503
501
  }),
504
- qt && /* @__PURE__ */ h("div", {
502
+ Jt && /* @__PURE__ */ h("div", {
505
503
  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",
506
504
  children: [/* @__PURE__ */ m("span", {
507
505
  className: "text-sm font-medium text-text-primary",
@@ -509,7 +507,7 @@ function y() {
509
507
  }), /* @__PURE__ */ h("div", {
510
508
  className: "flex items-center gap-1.5 sm:ml-auto",
511
509
  children: [/* @__PURE__ */ h("button", {
512
- onClick: Jt,
510
+ onClick: Yt,
513
511
  disabled: lt,
514
512
  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",
515
513
  children: [
@@ -518,7 +516,7 @@ function y() {
518
516
  S("agentsPage.remove", "Remove")
519
517
  ]
520
518
  }), /* @__PURE__ */ h("button", {
521
- onClick: Gt,
519
+ onClick: Kt,
522
520
  className: "flex items-center gap-1.5 px-3 py-1.5 text-xs text-text-secondary hover:text-text-primary transition-colors",
523
521
  children: [
524
522
  /* @__PURE__ */ m(Ie, { className: "w-3 h-3" }),
@@ -531,31 +529,31 @@ function y() {
531
529
  C || w !== "all" || T !== "all" ? /* @__PURE__ */ h("p", {
532
530
  className: "text-sm text-text-secondary",
533
531
  children: [S("agentsPage.showingResults", "Showing {{filtered}} of {{total}} agents", {
534
- filtered: Q.length,
535
- total: Z.length
532
+ filtered: Z.length,
533
+ total: X.length
536
534
  }), G && /* @__PURE__ */ m("span", {
537
535
  className: "ml-2 text-text-muted",
538
536
  children: S("agentsPage.refreshing", "(refreshing...)")
539
537
  })]
540
538
  }) : null,
541
- G && Z.length === 0 ? /* @__PURE__ */ m(we, {
539
+ G && X.length === 0 ? /* @__PURE__ */ m(we, {
542
540
  variant: "rows",
543
541
  count: 6
544
- }) : Q.length > 0 ? /* @__PURE__ */ m(f, {
542
+ }) : Z.length > 0 ? /* @__PURE__ */ m(f, {
545
543
  mode: "wait",
546
544
  children: D === "grid" ? /* @__PURE__ */ m(_, {
547
545
  stagger: .04,
548
546
  className: "grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-4",
549
- children: Q.map((e) => /* @__PURE__ */ m(g, { children: /* @__PURE__ */ m(Te, {
547
+ children: Z.map((e) => /* @__PURE__ */ m(g, { children: /* @__PURE__ */ m(Te, {
550
548
  agent: e,
551
- onSelect: Pt,
552
- onStart: It,
553
- onStop: Lt,
554
- onDisconnect: Rt,
555
- onConfigure: Bt,
556
- onEditTunnelUrl: Vt,
549
+ onSelect: It,
550
+ onStart: Rt,
551
+ onStop: zt,
552
+ onDisconnect: $,
553
+ onConfigure: Vt,
554
+ onEditTunnelUrl: Ht,
557
555
  selected: L.has(e.id),
558
- onToggleSelect: () => Ut(e.id),
556
+ onToggleSelect: () => Wt(e.id),
559
557
  healthErrorMessage: e.healthError ?? null
560
558
  }) }, e.id))
561
559
  }, "grid-view") : /* @__PURE__ */ m(_, {
@@ -563,12 +561,12 @@ function y() {
563
561
  children: /* @__PURE__ */ m(g, {
564
562
  variant: "fadeRight",
565
563
  children: /* @__PURE__ */ m(Ee, {
566
- agents: Q,
567
- onSelect: Pt,
568
- onStart: It,
569
- onStop: Lt,
570
- onDisconnect: Rt,
571
- onSettings: Bt
564
+ agents: Z,
565
+ onSelect: It,
566
+ onStart: Rt,
567
+ onStop: zt,
568
+ onDisconnect: $,
569
+ onSettings: Vt
572
570
  })
573
571
  })
574
572
  }, "list-view")
@@ -580,7 +578,7 @@ function y() {
580
578
  className: "flex flex-wrap items-center gap-3",
581
579
  children: [
582
580
  /* @__PURE__ */ h("button", {
583
- onClick: $,
581
+ onClick: Q,
584
582
  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",
585
583
  children: [/* @__PURE__ */ m(d, { className: "w-4 h-4" }), S("agentsPage.addAgent", "Add Agent")]
586
584
  }),
@@ -594,7 +592,7 @@ function y() {
594
592
  }) : void 0
595
593
  }),
596
594
  $e && /* @__PURE__ */ m(De, {
597
- onSubmit: Ft,
595
+ onSubmit: Lt,
598
596
  onCancel: () => O(!1),
599
597
  isLoading: et,
600
598
  onNavigate: (e) => {
@@ -661,7 +659,7 @@ function y() {
661
659
  className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors disabled:opacity-50",
662
660
  children: "Cancel"
663
661
  }), /* @__PURE__ */ m("button", {
664
- onClick: zt,
662
+ onClick: Bt,
665
663
  disabled: F,
666
664
  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",
667
665
  children: F ? "Disconnecting..." : "Disconnect"
@@ -742,7 +740,7 @@ function y() {
742
740
  className: "px-4 py-2 text-sm text-text-secondary hover:bg-bg-sunken rounded-md transition-colors",
743
741
  children: S("agentsPage.cancel", "Cancel")
744
742
  }), /* @__PURE__ */ m("button", {
745
- onClick: Ht,
743
+ onClick: Ut,
746
744
  disabled: nt,
747
745
  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",
748
746
  children: nt ? S("agentsPage.saving", "Saving...") : S("agentsPage.save", "Save")