@burdenoff/microfe-vibecontrols 2026.515.1 → 2026.515.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":"AgentManagerTabBar.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerTabBar.tsx"],"sourcesContent":["/**\n * Browser-style tab bar for the Agent Manager.\n *\n * Horizontal scrolling tabs with a \"+\" button to create new sessions.\n * Supports drag-to-reorder via simple index swap.\n */\n\nimport { useRef, useState, useCallback, useEffect, type FC, type DragEvent } from 'react';\nimport {\n Plus,\n Download,\n ChevronLeft,\n ChevronRight,\n Search,\n HelpCircle,\n Maximize2,\n Minimize2,\n} from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { renameSession, createSession, type AgentRef } from '@/services/aiApi';\nimport { AgentManagerTab } from './AgentManagerTab';\nimport { RenameSessionDialog } from './dialogs/RenameSessionDialog';\nimport { ExportSessionDialog } from './dialogs/ExportSessionDialog';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerTabBarProps {\n agent: AgentRef;\n onSearch?: () => void;\n onShowShortcuts?: () => void;\n}\n\nexport const AgentManagerTabBar: FC<AgentManagerTabBarProps> = ({\n agent,\n onSearch,\n onShowShortcuts,\n}) => {\n const tabs = useAgentManagerStore((s) => s.tabs);\n const activeTabSessionId = useAgentManagerStore((s) => s.activeTabSessionId);\n const sessionStates = useAgentManagerStore((s) => s.sessionStates);\n const setActiveTab = useAgentManagerStore((s) => s.setActiveTab);\n const closeTab = useAgentManagerStore((s) => s.closeTab);\n const reorderTabs = useAgentManagerStore((s) => s.reorderTabs);\n const openSession = useAgentManagerStore((s) => s.openSession);\n const renameTab = useAgentManagerStore((s) => s.renameTab);\n const isFullscreen = useAgentManagerStore((s) => s.isFullscreen);\n const toggleFullscreen = useAgentManagerStore((s) => s.toggleFullscreen);\n const scrollRef = useRef<HTMLDivElement>(null);\n const tr = useTr();\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [renamingSessionId, setRenamingSessionId] = useState<string | null>(null);\n const [exportOpen, setExportOpen] = useState(false);\n const [showLeftArrow, setShowLeftArrow] = useState(false);\n const [showRightArrow, setShowRightArrow] = useState(false);\n\n const checkScroll = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n setShowLeftArrow(el.scrollLeft > 0);\n setShowRightArrow(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);\n }, []);\n\n const scrollTabs = useCallback(\n (direction: 'left' | 'right') => {\n const el = scrollRef.current;\n if (!el) return;\n el.scrollBy({ left: direction === 'left' ? -200 : 200, behavior: 'smooth' });\n // Update arrow visibility after scroll animation\n setTimeout(checkScroll, 300);\n },\n [checkScroll]\n );\n\n useEffect(() => {\n checkScroll();\n }, [tabs.length, checkScroll]);\n\n // Re-check scroll whenever the container is resized\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => checkScroll());\n ro.observe(el);\n return () => ro.disconnect();\n }, [checkScroll]);\n\n const bumpSessionsRefresh = useAgentManagerStore((s) => s.bumpSessionsRefresh);\n const handleNewSession = useCallback(async () => {\n try {\n const session = await createSession(agent, {\n name: `Session ${tabs.length + 1}`,\n agentType: 'claude',\n config: { model: 'default', mode: 'cli', tags: [] },\n });\n openSession(session.id, session.name);\n bumpSessionsRefresh();\n } catch (err) {\n console.error('[AgentManagerTabBar] createSession failed:', err);\n }\n }, [agent, tabs.length, openSession, bumpSessionsRefresh]);\n\n const handleDragStart = (e: DragEvent, index: number) => {\n setDragIndex(index);\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(index));\n };\n\n const handleDragOver = (e: DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n };\n\n const handleDrop = (e: DragEvent, dropIndex: number) => {\n e.preventDefault();\n if (dragIndex === null || dragIndex === dropIndex) return;\n\n const reordered = [...tabs];\n const [moved] = reordered.splice(dragIndex, 1);\n reordered.splice(dropIndex, 0, moved);\n\n // Update order values\n const updated = reordered.map((tab, i) => ({ ...tab, order: i }));\n reorderTabs(updated);\n setDragIndex(null);\n };\n\n const handleDragEnd = () => {\n setDragIndex(null);\n };\n\n const handleRename = useCallback(\n async (sessionId: string, newName: string) => {\n renameTab(sessionId, newName);\n try {\n await renameSession(agent, sessionId, newName);\n } catch {\n // Best-effort API sync -- local rename already applied\n }\n },\n [agent, renameTab]\n );\n\n const renamingTab = renamingSessionId\n ? tabs.find((t) => t.sessionId === renamingSessionId)\n : null;\n\n return (\n <div className=\"flex items-stretch bg-bg-primary border-b border-border-default w-full\">\n {showLeftArrow && (\n <button\n type=\"button\"\n onClick={() => scrollTabs('left')}\n className=\"flex-shrink-0 px-1 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-r border-border-default\"\n aria-label=\"Scroll tabs left\"\n >\n <ChevronLeft className=\"w-3.5 h-3.5\" />\n </button>\n )}\n {/* Clip wrapper hides the browser scrollbar while keeping scroll functional */}\n <div className=\"flex-1 min-w-0 overflow-hidden\">\n <div\n ref={scrollRef}\n onScroll={checkScroll}\n className=\"flex items-stretch overflow-x-auto h-full\"\n style={{ scrollbarWidth: 'none' }}\n >\n {tabs.map((tab, index) => {\n const sessionState = sessionStates[tab.sessionId];\n const isProcessing =\n sessionState?.status === 'streaming' || sessionState?.status === 'processing';\n\n return (\n <div\n key={tab.sessionId}\n draggable\n onDragStart={(e) => handleDragStart(e, index)}\n onDragOver={handleDragOver}\n onDrop={(e) => handleDrop(e, index)}\n onDragEnd={handleDragEnd}\n className={`flex-shrink-0${dragIndex === index ? ' opacity-50' : ''}`}\n >\n <AgentManagerTab\n sessionId={tab.sessionId}\n name={tab.name}\n isActive={tab.sessionId === activeTabSessionId}\n isPinned={tab.pinned}\n isProcessing={isProcessing}\n onActivate={() => setActiveTab(tab.sessionId)}\n onClose={() => closeTab(tab.sessionId)}\n onRename={setRenamingSessionId}\n />\n </div>\n );\n })}\n </div>\n </div>\n {showRightArrow && (\n <button\n type=\"button\"\n onClick={() => scrollTabs('right')}\n className=\"flex-shrink-0 px-1 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n aria-label=\"Scroll tabs right\"\n >\n <ChevronRight className=\"w-3.5 h-3.5\" />\n </button>\n )}\n\n {/* Export session button */}\n {activeTabSessionId && (\n <button\n onClick={() => setExportOpen(true)}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.exportSession', 'Export session')}\n aria-label={tr('vibecontrols.agentManager.tabBar.exportSession', 'Export session')}\n >\n <Download className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* New session button */}\n <button\n onClick={handleNewSession}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr(\n 'vibecontrols.agentManager.tabBar.newSession',\n 'New session (Ctrl/Cmd+Shift+N)'\n )}\n aria-label={tr('vibecontrols.agentManager.tabBar.createNewSession', 'Create new session')}\n >\n <Plus className=\"w-4 h-4\" />\n </button>\n\n {/* Search sessions */}\n {onSearch && (\n <button\n onClick={onSearch}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr(\n 'vibecontrols.agentManager.tabBar.searchSessions',\n 'Search sessions (Ctrl/Cmd+Shift+K)'\n )}\n aria-label={tr('vibecontrols.agentManager.tabBar.searchSessions', 'Search sessions')}\n >\n <Search className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Keyboard shortcuts */}\n {onShowShortcuts && (\n <button\n onClick={onShowShortcuts}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.keyboardShortcuts', 'Keyboard shortcuts')}\n aria-label={tr(\n 'vibecontrols.agentManager.tabBar.keyboardShortcuts',\n 'Keyboard shortcuts'\n )}\n >\n <HelpCircle className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Fullscreen toggle */}\n <button\n onClick={toggleFullscreen}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={\n isFullscreen\n ? tr('vibecontrols.agentManager.tabBar.exitFullscreen', 'Exit fullscreen')\n : tr('vibecontrols.agentManager.tabBar.enterFullscreen', 'Enter fullscreen')\n }\n aria-label={\n isFullscreen\n ? tr('vibecontrols.agentManager.tabBar.exitFullscreen', 'Exit fullscreen')\n : tr('vibecontrols.agentManager.tabBar.enterFullscreen', 'Enter fullscreen')\n }\n >\n {isFullscreen ? <Minimize2 className=\"w-4 h-4\" /> : <Maximize2 className=\"w-4 h-4\" />}\n </button>\n\n {/* Export session dialog */}\n <ExportSessionDialog\n isOpen={exportOpen}\n onClose={() => setExportOpen(false)}\n sessionId={activeTabSessionId ?? ''}\n agent={agent}\n />\n\n {/* Rename session dialog */}\n <RenameSessionDialog\n isOpen={renamingSessionId !== null}\n sessionId={renamingSessionId ?? ''}\n currentName={renamingTab?.name ?? ''}\n onClose={() => setRenamingSessionId(null)}\n onRename={(sessionId, newName) => void handleRename(sessionId, newName)}\n />\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AgentManagerTabBar.js","names":[],"sources":["../../../src/components/agent-manager/AgentManagerTabBar.tsx"],"sourcesContent":["/**\n * Browser-style tab bar for the Agent Manager.\n *\n * Horizontal scrolling tabs with a \"+\" button to create new sessions.\n * Supports drag-to-reorder via simple index swap.\n */\n\nimport { useRef, useState, useCallback, useEffect, type FC, type DragEvent } from 'react';\nimport {\n Plus,\n Download,\n ChevronLeft,\n ChevronRight,\n Search,\n HelpCircle,\n Maximize2,\n Minimize2,\n} from 'lucide-react';\nimport { useAgentManagerStore } from '@/store/agentManagerStore';\nimport { renameSession, createSession, type AgentRef } from '@/services/aiApi';\nimport { AgentManagerTab } from './AgentManagerTab';\nimport { RenameSessionDialog } from './dialogs/RenameSessionDialog';\nimport { ExportSessionDialog } from './dialogs/ExportSessionDialog';\nimport { useTr } from '../../shared/hooks/useTr';\n\ninterface AgentManagerTabBarProps {\n agent: AgentRef;\n onSearch?: () => void;\n onShowShortcuts?: () => void;\n}\n\nexport const AgentManagerTabBar: FC<AgentManagerTabBarProps> = ({\n agent,\n onSearch,\n onShowShortcuts,\n}) => {\n const tabs = useAgentManagerStore((s) => s.tabs);\n const activeTabSessionId = useAgentManagerStore((s) => s.activeTabSessionId);\n const sessionStates = useAgentManagerStore((s) => s.sessionStates);\n const setActiveTab = useAgentManagerStore((s) => s.setActiveTab);\n const closeTab = useAgentManagerStore((s) => s.closeTab);\n const reorderTabs = useAgentManagerStore((s) => s.reorderTabs);\n const openSession = useAgentManagerStore((s) => s.openSession);\n const renameTab = useAgentManagerStore((s) => s.renameTab);\n const isFullscreen = useAgentManagerStore((s) => s.isFullscreen);\n const toggleFullscreen = useAgentManagerStore((s) => s.toggleFullscreen);\n const scrollRef = useRef<HTMLDivElement>(null);\n const tr = useTr();\n const [dragIndex, setDragIndex] = useState<number | null>(null);\n const [renamingSessionId, setRenamingSessionId] = useState<string | null>(null);\n const [exportOpen, setExportOpen] = useState(false);\n const [showLeftArrow, setShowLeftArrow] = useState(false);\n const [showRightArrow, setShowRightArrow] = useState(false);\n\n const checkScroll = useCallback(() => {\n const el = scrollRef.current;\n if (!el) return;\n setShowLeftArrow(el.scrollLeft > 0);\n setShowRightArrow(el.scrollLeft + el.clientWidth < el.scrollWidth - 1);\n }, []);\n\n const scrollTabs = useCallback(\n (direction: 'left' | 'right') => {\n const el = scrollRef.current;\n if (!el) return;\n el.scrollBy({ left: direction === 'left' ? -200 : 200, behavior: 'smooth' });\n // Update arrow visibility after scroll animation\n setTimeout(checkScroll, 300);\n },\n [checkScroll]\n );\n\n useEffect(() => {\n checkScroll();\n }, [tabs.length, checkScroll]);\n\n // Re-check scroll whenever the container is resized\n useEffect(() => {\n const el = scrollRef.current;\n if (!el) return;\n const ro = new ResizeObserver(() => checkScroll());\n ro.observe(el);\n return () => ro.disconnect();\n }, [checkScroll]);\n\n const bumpSessionsRefresh = useAgentManagerStore((s) => s.bumpSessionsRefresh);\n const handleNewSession = useCallback(async () => {\n try {\n const session = await createSession(agent, {\n name: `Session ${tabs.length + 1}`,\n agentType: 'claude',\n config: { model: 'default', mode: 'cli', tags: [] },\n });\n openSession(session.id, session.name);\n bumpSessionsRefresh();\n } catch (err) {\n console.error('[AgentManagerTabBar] createSession failed:', err);\n }\n }, [agent, tabs.length, openSession, bumpSessionsRefresh]);\n\n const handleDragStart = (e: DragEvent, index: number) => {\n setDragIndex(index);\n e.dataTransfer.effectAllowed = 'move';\n e.dataTransfer.setData('text/plain', String(index));\n };\n\n const handleDragOver = (e: DragEvent) => {\n e.preventDefault();\n e.dataTransfer.dropEffect = 'move';\n };\n\n const handleDrop = (e: DragEvent, dropIndex: number) => {\n e.preventDefault();\n if (dragIndex === null || dragIndex === dropIndex) return;\n\n const reordered = [...tabs];\n const [moved] = reordered.splice(dragIndex, 1);\n reordered.splice(dropIndex, 0, moved);\n\n // Update order values\n const updated = reordered.map((tab, i) => ({ ...tab, order: i }));\n reorderTabs(updated);\n setDragIndex(null);\n };\n\n const handleDragEnd = () => {\n setDragIndex(null);\n };\n\n const handleRename = useCallback(\n async (sessionId: string, newName: string) => {\n renameTab(sessionId, newName);\n try {\n await renameSession(agent, sessionId, newName);\n } catch {\n // Best-effort API sync -- local rename already applied\n }\n },\n [agent, renameTab]\n );\n\n const renamingTab = renamingSessionId\n ? tabs.find((t) => t.sessionId === renamingSessionId)\n : null;\n\n return (\n <div className=\"flex items-stretch bg-bg-primary border-b border-border-default w-full\">\n {showLeftArrow && (\n <button\n type=\"button\"\n onClick={() => scrollTabs('left')}\n className=\"flex-shrink-0 px-1 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-r border-border-default\"\n aria-label=\"Scroll tabs left\"\n >\n <ChevronLeft className=\"w-3.5 h-3.5\" />\n </button>\n )}\n {/* Clip wrapper hides the browser scrollbar while keeping scroll functional */}\n <div className=\"flex-1 min-w-0 overflow-hidden\">\n <div\n ref={scrollRef}\n onScroll={checkScroll}\n className=\"flex items-stretch overflow-x-auto h-full\"\n style={{ scrollbarWidth: 'none' }}\n >\n {tabs.map((tab, index) => {\n const sessionState = sessionStates[tab.sessionId];\n const isProcessing =\n sessionState?.status === 'streaming' || sessionState?.status === 'processing';\n\n return (\n <div\n key={tab.sessionId}\n draggable\n onDragStart={(e) => handleDragStart(e, index)}\n onDragOver={handleDragOver}\n onDrop={(e) => handleDrop(e, index)}\n onDragEnd={handleDragEnd}\n className={`flex-shrink-0${dragIndex === index ? ' opacity-50' : ''}`}\n >\n <AgentManagerTab\n sessionId={tab.sessionId}\n name={tab.name}\n isActive={tab.sessionId === activeTabSessionId}\n isPinned={tab.pinned}\n isProcessing={isProcessing}\n onActivate={() => setActiveTab(tab.sessionId)}\n onClose={() => closeTab(tab.sessionId)}\n onRename={setRenamingSessionId}\n />\n </div>\n );\n })}\n </div>\n </div>\n {showRightArrow && (\n <button\n type=\"button\"\n onClick={() => scrollTabs('right')}\n className=\"flex-shrink-0 px-1 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n aria-label=\"Scroll tabs right\"\n >\n <ChevronRight className=\"w-3.5 h-3.5\" />\n </button>\n )}\n\n {/* Export session button */}\n {activeTabSessionId && (\n <button\n onClick={() => setExportOpen(true)}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.exportSession', 'Export session')}\n aria-label={tr('vibecontrols.agentManager.tabBar.exportSession', 'Export session')}\n >\n <Download className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* New session button */}\n <button\n onClick={handleNewSession}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.newSession', 'New session (Ctrl/Cmd+Shift+N)')}\n aria-label={tr('vibecontrols.agentManager.tabBar.createNewSession', 'Create new session')}\n >\n <Plus className=\"w-4 h-4\" />\n </button>\n\n {/* Search sessions */}\n {onSearch && (\n <button\n onClick={onSearch}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr(\n 'vibecontrols.agentManager.tabBar.searchSessions',\n 'Search sessions (Ctrl/Cmd+Shift+K)'\n )}\n aria-label={tr('vibecontrols.agentManager.tabBar.searchSessions', 'Search sessions')}\n >\n <Search className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Keyboard shortcuts */}\n {onShowShortcuts && (\n <button\n onClick={onShowShortcuts}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={tr('vibecontrols.agentManager.tabBar.keyboardShortcuts', 'Keyboard shortcuts')}\n aria-label={tr(\n 'vibecontrols.agentManager.tabBar.keyboardShortcuts',\n 'Keyboard shortcuts'\n )}\n >\n <HelpCircle className=\"w-4 h-4\" />\n </button>\n )}\n\n {/* Fullscreen toggle */}\n <button\n onClick={toggleFullscreen}\n className=\"flex-shrink-0 px-2 py-1.5 text-text-secondary hover:text-text-primary hover:bg-bg-elevated transition-colors border-l border-border-default\"\n title={\n isFullscreen\n ? tr('vibecontrols.agentManager.tabBar.exitFullscreen', 'Exit fullscreen')\n : tr('vibecontrols.agentManager.tabBar.enterFullscreen', 'Enter fullscreen')\n }\n aria-label={\n isFullscreen\n ? tr('vibecontrols.agentManager.tabBar.exitFullscreen', 'Exit fullscreen')\n : tr('vibecontrols.agentManager.tabBar.enterFullscreen', 'Enter fullscreen')\n }\n >\n {isFullscreen ? <Minimize2 className=\"w-4 h-4\" /> : <Maximize2 className=\"w-4 h-4\" />}\n </button>\n\n {/* Export session dialog */}\n <ExportSessionDialog\n isOpen={exportOpen}\n onClose={() => setExportOpen(false)}\n sessionId={activeTabSessionId ?? ''}\n agent={agent}\n />\n\n {/* Rename session dialog */}\n <RenameSessionDialog\n isOpen={renamingSessionId !== null}\n sessionId={renamingSessionId ?? ''}\n currentName={renamingTab?.name ?? ''}\n onClose={() => setRenamingSessionId(null)}\n onRename={(sessionId, newName) => void handleRename(sessionId, newName)}\n />\n </div>\n );\n};\n"],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"AIProviderCredentialsCard.js","names":[],"sources":["../../../src/components/ai/AIProviderCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport { ArrowLeft, ChevronRight, Eye, EyeOff, KeyRound, Loader2, Save, Trash2 } from 'lucide-react';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\n/**\n * One provider definition — what env-var-style keys the agent stores under\n * its config bag and what to call the provider in the UI. Kept as a flat\n * static list rather than discovered from the plugin manifest because the\n * config keys are part of the upstream SDK contract (ANTHROPIC_API_KEY,\n * OPENAI_API_KEY, etc.) and don't change with each plugin release.\n */\nexport interface AIProviderCredentialDef {\n /** Stable provider id — usually matches the plugin's pluginName. */\n providerId: string;\n /** Display name shown in the card heading. */\n label: string;\n /** Short helper text under the heading. */\n description: string;\n /**\n * One row per key. The `kind` distinguishes a primary credential (for\n * which the SDK refuses to start without ONE of the listed keys) from\n * an optional URL/host override.\n */\n fields: Array<{\n /** Agent-config key name, also rendered as the input label. */\n key: string;\n /**\n * Whether the field is a secret (rendered as password with eye toggle)\n * or a plain string (URL / host overrides — visible by default).\n */\n secret?: boolean;\n }>;\n}\n\n/**\n * Catalog of every AI provider whose plugin reads credentials from the\n * agent's config bag (POST /api/config). CLI-only providers (Aider, Amp,\n * Copilot, Crush, Goose, Plandex, Pi) authenticate via their own harness\n * binary (~/.claude/, ~/.config/opencode/ etc.) — Bridle handles those\n * and they don't appear here.\n */\nexport const AI_PROVIDER_CREDENTIALS: AIProviderCredentialDef[] = [\n {\n providerId: 'claude',\n label: 'Anthropic Claude',\n description:\n 'Claude SDK sessions require either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN on the agent. Save one of them to make SDK sessions work.',\n fields: [\n { key: 'ANTHROPIC_API_KEY', secret: true },\n { key: 'ANTHROPIC_AUTH_TOKEN', secret: true },\n ],\n },\n {\n providerId: 'codex',\n label: 'OpenAI Codex',\n description:\n 'Codex SDK / CLI sessions read OPENAI_API_KEY from the agent. Set it here to enable Codex-backed sessions.',\n fields: [{ key: 'OPENAI_API_KEY', secret: true }],\n },\n {\n providerId: 'gemini',\n label: 'Google Gemini',\n description:\n 'Gemini SDK reads GEMINI_API_KEY (preferred) or GOOGLE_API_KEY. Either one works — both shown so an existing GOOGLE_API_KEY in your environment keeps working.',\n fields: [\n { key: 'GEMINI_API_KEY', secret: true },\n { key: 'GOOGLE_API_KEY', secret: true },\n ],\n },\n {\n providerId: 'openai-compat',\n label: 'OpenAI-Compatible',\n description:\n 'Generic OpenAI-compatible provider (Together, Groq, Mistral, etc.). Set the API key and the base URL the SDK should hit.',\n fields: [{ key: 'OPENAI_COMPAT_API_KEY', secret: true }, { key: 'OPENAI_COMPAT_BASE_URL' }],\n },\n {\n providerId: 'cursor',\n label: 'Cursor',\n description:\n 'Cursor agent (CLI + SDK) reads CURSOR_API_KEY from the agent. Set it here to enable both modes.',\n fields: [{ key: 'CURSOR_API_KEY', secret: true }],\n },\n {\n providerId: 'openrouter',\n label: 'OpenRouter',\n description: 'OpenRouter aggregator (300+ models, SDK only). Set OPENROUTER_API_KEY to enable.',\n fields: [{ key: 'OPENROUTER_API_KEY', secret: true }],\n },\n {\n providerId: 'minimax',\n label: 'Minimax',\n description:\n 'Minimax provider (CLI + Anthropic-compatible SDK). Set MINIMAX_API_KEY to enable.',\n fields: [{ key: 'MINIMAX_API_KEY', secret: true }],\n },\n {\n providerId: 'ollama',\n label: 'Ollama',\n description:\n 'Ollama Cloud + self-hosted (CLI + SDK). OLLAMA_API_KEY for Cloud; OLLAMA_HOST optional override (defaults: https://ollama.com when key present, http://localhost:11434 otherwise).',\n fields: [{ key: 'OLLAMA_API_KEY', secret: true }, { key: 'OLLAMA_HOST' }],\n },\n];\n\ninterface AIProviderCredentialsCardProps {\n agent: AgentRef;\n /**\n * Optional filter — when present, only render providers whose providerId\n * appears in the list. Lets a future caller scope the card to \"only the\n * providers I have plugins installed for\" without changing the catalog.\n */\n providerIds?: string[];\n /**\n * When provided, the staged picker preselects this provider so the\n * caller (e.g. the SDK-auth-missing modal) can drop the user straight\n * onto the right form without an extra click.\n */\n initialProviderId?: string;\n}\n\n/**\n * Staged credentials manager: shows a provider picker first, then on\n * select renders one card with that provider's config keys plus a back\n * button. Keeps the rendered DOM small (and the surrounding modal\n * viewport-friendly) regardless of how many providers are in the catalog.\n *\n * Replaces the older Claude-only ClaudeCredentialsCard.\n */\nexport function AIProviderCredentialsCard({\n agent,\n providerIds,\n initialProviderId,\n}: AIProviderCredentialsCardProps) {\n const tr = useTr();\n const providers = providerIds\n ? AI_PROVIDER_CREDENTIALS.filter((p) => providerIds.includes(p.providerId))\n : AI_PROVIDER_CREDENTIALS;\n\n // Preselect when the catalog narrows to exactly one provider so the\n // single-provider case (e.g. claude-only filter) skips the picker.\n const [selectedId, setSelectedId] = useState<string | null>(() => {\n if (initialProviderId && providers.some((p) => p.providerId === initialProviderId)) {\n return initialProviderId;\n }\n return providers.length === 1 ? providers[0]?.providerId ?? null : null;\n });\n\n const selected = providers.find((p) => p.providerId === selectedId) ?? null;\n\n if (!selected) {\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"h-4 w-4\" />\n </div>\n <div className=\"space-y-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('ai.providerCredentials.pickTitle', 'Choose a provider')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ai.providerCredentials.pickDescription',\n 'Pick the provider whose credentials you want to manage. Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {providers.map((p) => (\n <button\n key={p.providerId}\n type=\"button\"\n onClick={() => setSelectedId(p.providerId)}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-left hover:border-border-strong hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary truncate\">{p.label}</div>\n <div className=\"text-xs text-text-secondary truncate\">\n {p.fields.map((f) => f.key).join(', ')}\n </div>\n </div>\n <ChevronRight className=\"h-4 w-4 flex-shrink-0 text-text-secondary\" />\n </button>\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n {providers.length > 1 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedId(null)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 text-xs text-text-secondary hover:text-text-primary hover:border-border-strong\"\n aria-label={tr('ai.providerCredentials.back', 'Back to providers')}\n >\n <ArrowLeft className=\"h-3.5 w-3.5\" />\n {tr('ai.providerCredentials.back', 'Back')}\n </button>\n ) : null}\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary truncate\">{selected.label}</h3>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'ai.providerCredentials.scopedDescription',\n 'Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <ProviderCard agent={agent} provider={selected} />\n </div>\n );\n}\n\nfunction ProviderCard({ agent, provider }: { agent: AgentRef; provider: AIProviderCredentialDef }) {\n const tr = useTr();\n const [values, setValues] = useState<Record<string, string>>({});\n const [visible, setVisible] = useState<Record<string, boolean>>({});\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n\n const loadValues = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n provider.fields.map(async (f) => [f.key, (await getAgentConfigValue(agent, f.key)) ?? ''])\n );\n setValues(Object.fromEntries(entries));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.loadFailed', 'Failed to load provider credentials')\n );\n } finally {\n setLoading(false);\n }\n }, [agent, provider.fields, tr]);\n\n useEffect(() => {\n void loadValues();\n }, [loadValues]);\n\n const persistValue = useCallback(\n async (key: string, value: string) => {\n setSaving(key);\n setError(null);\n setSuccess(null);\n try {\n if (value.trim()) {\n await setAgentConfigValue(agent, key, value.trim());\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setSuccess(\n value.trim()\n ? tr('ai.providerCredentials.saved', `${key} saved`)\n : tr('ai.providerCredentials.cleared', `${key} cleared`)\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.saveFailed', 'Failed to save credential')\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <div className=\"rounded-xl border border-border-default bg-bg-surface p-4 space-y-3\">\n <div className=\"space-y-1\">\n <h4 className=\"text-sm font-semibold text-text-primary\">{provider.label}</h4>\n <p className=\"text-xs text-text-secondary\">{provider.description}</p>\n </div>\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {success ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-sm text-status-success-text\">\n {success}\n </div>\n ) : null}\n\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr('ai.providerCredentials.loading', 'Loading…')}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {provider.fields.map((f) => (\n <CredentialField\n key={f.key}\n label={f.key}\n secret={f.secret ?? false}\n value={values[f.key] ?? ''}\n onChange={(v) => setValues((cur) => ({ ...cur, [f.key]: v }))}\n saving={saving === f.key}\n visible={visible[f.key] ?? false}\n onToggleVisible={() => setVisible((cur) => ({ ...cur, [f.key]: !cur[f.key] }))}\n onSave={() => void persistValue(f.key, values[f.key] ?? '')}\n onClear={() => void persistValue(f.key, '')}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n secret: boolean;\n value: string;\n onChange: (value: string) => void;\n saving: boolean;\n visible: boolean;\n onToggleVisible: () => void;\n onSave: () => void;\n onClear: () => void;\n}\n\nfunction CredentialField({\n label,\n secret,\n value,\n onChange,\n saving,\n visible,\n onToggleVisible,\n onSave,\n onClear,\n}: CredentialFieldProps) {\n const showAsPassword = secret && !visible;\n return (\n <div className=\"space-y-2 rounded-lg border border-border-default bg-bg-primary p-3\">\n <label className=\"block text-xs font-medium text-text-secondary\">{label}</label>\n <div className=\"flex gap-2\">\n <div className=\"relative flex-1\">\n <input\n type={showAsPassword ? 'password' : 'text'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary\"\n />\n {secret ? (\n <button\n type=\"button\"\n onClick={onToggleVisible}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 text-text-secondary hover:text-text-primary\"\n >\n {visible ? <EyeOff className=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n </button>\n ) : null}\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n Save\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n disabled={saving || !value}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Clear\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
1
+ {"version":3,"file":"AIProviderCredentialsCard.js","names":[],"sources":["../../../src/components/ai/AIProviderCredentialsCard.tsx"],"sourcesContent":["import { useCallback, useEffect, useState } from 'react';\nimport {\n ArrowLeft,\n ChevronRight,\n Eye,\n EyeOff,\n KeyRound,\n Loader2,\n Save,\n Trash2,\n} from 'lucide-react';\n\nimport { useTr } from '../../shared/hooks/useTr';\nimport {\n deleteAgentConfigValue,\n getAgentConfigValue,\n setAgentConfigValue,\n type AgentRef,\n} from '@/services/aiApi';\n\n/**\n * One provider definition — what env-var-style keys the agent stores under\n * its config bag and what to call the provider in the UI. Kept as a flat\n * static list rather than discovered from the plugin manifest because the\n * config keys are part of the upstream SDK contract (ANTHROPIC_API_KEY,\n * OPENAI_API_KEY, etc.) and don't change with each plugin release.\n */\nexport interface AIProviderCredentialDef {\n /** Stable provider id — usually matches the plugin's pluginName. */\n providerId: string;\n /** Display name shown in the card heading. */\n label: string;\n /** Short helper text under the heading. */\n description: string;\n /**\n * One row per key. The `kind` distinguishes a primary credential (for\n * which the SDK refuses to start without ONE of the listed keys) from\n * an optional URL/host override.\n */\n fields: Array<{\n /** Agent-config key name, also rendered as the input label. */\n key: string;\n /**\n * Whether the field is a secret (rendered as password with eye toggle)\n * or a plain string (URL / host overrides — visible by default).\n */\n secret?: boolean;\n }>;\n}\n\n/**\n * Catalog of every AI provider whose plugin reads credentials from the\n * agent's config bag (POST /api/config). CLI-only providers (Aider, Amp,\n * Copilot, Crush, Goose, Plandex, Pi) authenticate via their own harness\n * binary (~/.claude/, ~/.config/opencode/ etc.) — Bridle handles those\n * and they don't appear here.\n */\nexport const AI_PROVIDER_CREDENTIALS: AIProviderCredentialDef[] = [\n {\n providerId: 'claude',\n label: 'Anthropic Claude',\n description:\n 'Claude SDK sessions require either ANTHROPIC_API_KEY or ANTHROPIC_AUTH_TOKEN on the agent. Save one of them to make SDK sessions work.',\n fields: [\n { key: 'ANTHROPIC_API_KEY', secret: true },\n { key: 'ANTHROPIC_AUTH_TOKEN', secret: true },\n ],\n },\n {\n providerId: 'codex',\n label: 'OpenAI Codex',\n description:\n 'Codex SDK / CLI sessions read OPENAI_API_KEY from the agent. Set it here to enable Codex-backed sessions.',\n fields: [{ key: 'OPENAI_API_KEY', secret: true }],\n },\n {\n providerId: 'gemini',\n label: 'Google Gemini',\n description:\n 'Gemini SDK reads GEMINI_API_KEY (preferred) or GOOGLE_API_KEY. Either one works — both shown so an existing GOOGLE_API_KEY in your environment keeps working.',\n fields: [\n { key: 'GEMINI_API_KEY', secret: true },\n { key: 'GOOGLE_API_KEY', secret: true },\n ],\n },\n {\n providerId: 'openai-compat',\n label: 'OpenAI-Compatible',\n description:\n 'Generic OpenAI-compatible provider (Together, Groq, Mistral, etc.). Set the API key and the base URL the SDK should hit.',\n fields: [{ key: 'OPENAI_COMPAT_API_KEY', secret: true }, { key: 'OPENAI_COMPAT_BASE_URL' }],\n },\n {\n providerId: 'cursor',\n label: 'Cursor',\n description:\n 'Cursor agent (CLI + SDK) reads CURSOR_API_KEY from the agent. Set it here to enable both modes.',\n fields: [{ key: 'CURSOR_API_KEY', secret: true }],\n },\n {\n providerId: 'openrouter',\n label: 'OpenRouter',\n description: 'OpenRouter aggregator (300+ models, SDK only). Set OPENROUTER_API_KEY to enable.',\n fields: [{ key: 'OPENROUTER_API_KEY', secret: true }],\n },\n {\n providerId: 'minimax',\n label: 'Minimax',\n description:\n 'Minimax provider (CLI + Anthropic-compatible SDK). Set MINIMAX_API_KEY to enable.',\n fields: [{ key: 'MINIMAX_API_KEY', secret: true }],\n },\n {\n providerId: 'ollama',\n label: 'Ollama',\n description:\n 'Ollama Cloud + self-hosted (CLI + SDK). OLLAMA_API_KEY for Cloud; OLLAMA_HOST optional override (defaults: https://ollama.com when key present, http://localhost:11434 otherwise).',\n fields: [{ key: 'OLLAMA_API_KEY', secret: true }, { key: 'OLLAMA_HOST' }],\n },\n];\n\ninterface AIProviderCredentialsCardProps {\n agent: AgentRef;\n /**\n * Optional filter — when present, only render providers whose providerId\n * appears in the list. Lets a future caller scope the card to \"only the\n * providers I have plugins installed for\" without changing the catalog.\n */\n providerIds?: string[];\n /**\n * When provided, the staged picker preselects this provider so the\n * caller (e.g. the SDK-auth-missing modal) can drop the user straight\n * onto the right form without an extra click.\n */\n initialProviderId?: string;\n}\n\n/**\n * Staged credentials manager: shows a provider picker first, then on\n * select renders one card with that provider's config keys plus a back\n * button. Keeps the rendered DOM small (and the surrounding modal\n * viewport-friendly) regardless of how many providers are in the catalog.\n *\n * Replaces the older Claude-only ClaudeCredentialsCard.\n */\nexport function AIProviderCredentialsCard({\n agent,\n providerIds,\n initialProviderId,\n}: AIProviderCredentialsCardProps) {\n const tr = useTr();\n const providers = providerIds\n ? AI_PROVIDER_CREDENTIALS.filter((p) => providerIds.includes(p.providerId))\n : AI_PROVIDER_CREDENTIALS;\n\n // Preselect when the catalog narrows to exactly one provider so the\n // single-provider case (e.g. claude-only filter) skips the picker.\n const [selectedId, setSelectedId] = useState<string | null>(() => {\n if (initialProviderId && providers.some((p) => p.providerId === initialProviderId)) {\n return initialProviderId;\n }\n return providers.length === 1 ? (providers[0]?.providerId ?? null) : null;\n });\n\n const selected = providers.find((p) => p.providerId === selectedId) ?? null;\n\n if (!selected) {\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-start gap-3\">\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"h-4 w-4\" />\n </div>\n <div className=\"space-y-1\">\n <h3 className=\"text-sm font-semibold text-text-primary\">\n {tr('ai.providerCredentials.pickTitle', 'Choose a provider')}\n </h3>\n <p className=\"text-sm text-text-secondary\">\n {tr(\n 'ai.providerCredentials.pickDescription',\n 'Pick the provider whose credentials you want to manage. Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <div className=\"grid grid-cols-1 sm:grid-cols-2 gap-2\">\n {providers.map((p) => (\n <button\n key={p.providerId}\n type=\"button\"\n onClick={() => setSelectedId(p.providerId)}\n className=\"flex items-center justify-between gap-2 rounded-lg border border-border-default bg-bg-surface px-3 py-2 text-left hover:border-border-strong hover:bg-bg-sunken transition-colors\"\n >\n <div className=\"min-w-0\">\n <div className=\"text-sm font-medium text-text-primary truncate\">{p.label}</div>\n <div className=\"text-xs text-text-secondary truncate\">\n {p.fields.map((f) => f.key).join(', ')}\n </div>\n </div>\n <ChevronRight className=\"h-4 w-4 flex-shrink-0 text-text-secondary\" />\n </button>\n ))}\n </div>\n </div>\n );\n }\n\n return (\n <div className=\"space-y-3\">\n <div className=\"flex items-center gap-2\">\n {providers.length > 1 ? (\n <button\n type=\"button\"\n onClick={() => setSelectedId(null)}\n className=\"inline-flex items-center gap-1 rounded-md border border-border-default px-2 py-1 text-xs text-text-secondary hover:text-text-primary hover:border-border-strong\"\n aria-label={tr('ai.providerCredentials.back', 'Back to providers')}\n >\n <ArrowLeft className=\"h-3.5 w-3.5\" />\n {tr('ai.providerCredentials.back', 'Back')}\n </button>\n ) : null}\n <div className=\"rounded-lg bg-bg-sunken p-2 text-text-secondary\">\n <KeyRound className=\"h-4 w-4\" />\n </div>\n <div className=\"min-w-0\">\n <h3 className=\"text-sm font-semibold text-text-primary truncate\">{selected.label}</h3>\n <p className=\"text-xs text-text-secondary\">\n {tr(\n 'ai.providerCredentials.scopedDescription',\n 'Keys are stored on the agent and read at session start.'\n )}\n </p>\n </div>\n </div>\n <ProviderCard agent={agent} provider={selected} />\n </div>\n );\n}\n\nfunction ProviderCard({ agent, provider }: { agent: AgentRef; provider: AIProviderCredentialDef }) {\n const tr = useTr();\n const [values, setValues] = useState<Record<string, string>>({});\n const [visible, setVisible] = useState<Record<string, boolean>>({});\n const [loading, setLoading] = useState(true);\n const [saving, setSaving] = useState<string | null>(null);\n const [error, setError] = useState<string | null>(null);\n const [success, setSuccess] = useState<string | null>(null);\n\n const loadValues = useCallback(async () => {\n setLoading(true);\n setError(null);\n try {\n const entries = await Promise.all(\n provider.fields.map(async (f) => [f.key, (await getAgentConfigValue(agent, f.key)) ?? ''])\n );\n setValues(Object.fromEntries(entries));\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.loadFailed', 'Failed to load provider credentials')\n );\n } finally {\n setLoading(false);\n }\n }, [agent, provider.fields, tr]);\n\n useEffect(() => {\n void loadValues();\n }, [loadValues]);\n\n const persistValue = useCallback(\n async (key: string, value: string) => {\n setSaving(key);\n setError(null);\n setSuccess(null);\n try {\n if (value.trim()) {\n await setAgentConfigValue(agent, key, value.trim());\n } else {\n await deleteAgentConfigValue(agent, key);\n }\n setSuccess(\n value.trim()\n ? tr('ai.providerCredentials.saved', `${key} saved`)\n : tr('ai.providerCredentials.cleared', `${key} cleared`)\n );\n } catch (err) {\n setError(\n err instanceof Error\n ? err.message\n : tr('ai.providerCredentials.saveFailed', 'Failed to save credential')\n );\n } finally {\n setSaving(null);\n }\n },\n [agent, tr]\n );\n\n return (\n <div className=\"rounded-xl border border-border-default bg-bg-surface p-4 space-y-3\">\n <div className=\"space-y-1\">\n <h4 className=\"text-sm font-semibold text-text-primary\">{provider.label}</h4>\n <p className=\"text-xs text-text-secondary\">{provider.description}</p>\n </div>\n\n {error ? (\n <div className=\"rounded-lg border border-status-error-border bg-status-error-bg px-3 py-2 text-sm text-status-error-text\">\n {error}\n </div>\n ) : null}\n {success ? (\n <div className=\"rounded-lg border border-status-success-border bg-status-success-bg px-3 py-2 text-sm text-status-success-text\">\n {success}\n </div>\n ) : null}\n\n {loading ? (\n <div className=\"flex items-center gap-2 text-sm text-text-secondary\">\n <Loader2 className=\"h-4 w-4 animate-spin\" />\n {tr('ai.providerCredentials.loading', 'Loading…')}\n </div>\n ) : (\n <div className=\"space-y-3\">\n {provider.fields.map((f) => (\n <CredentialField\n key={f.key}\n label={f.key}\n secret={f.secret ?? false}\n value={values[f.key] ?? ''}\n onChange={(v) => setValues((cur) => ({ ...cur, [f.key]: v }))}\n saving={saving === f.key}\n visible={visible[f.key] ?? false}\n onToggleVisible={() => setVisible((cur) => ({ ...cur, [f.key]: !cur[f.key] }))}\n onSave={() => void persistValue(f.key, values[f.key] ?? '')}\n onClear={() => void persistValue(f.key, '')}\n />\n ))}\n </div>\n )}\n </div>\n );\n}\n\ninterface CredentialFieldProps {\n label: string;\n secret: boolean;\n value: string;\n onChange: (value: string) => void;\n saving: boolean;\n visible: boolean;\n onToggleVisible: () => void;\n onSave: () => void;\n onClear: () => void;\n}\n\nfunction CredentialField({\n label,\n secret,\n value,\n onChange,\n saving,\n visible,\n onToggleVisible,\n onSave,\n onClear,\n}: CredentialFieldProps) {\n const showAsPassword = secret && !visible;\n return (\n <div className=\"space-y-2 rounded-lg border border-border-default bg-bg-primary p-3\">\n <label className=\"block text-xs font-medium text-text-secondary\">{label}</label>\n <div className=\"flex gap-2\">\n <div className=\"relative flex-1\">\n <input\n type={showAsPassword ? 'password' : 'text'}\n value={value}\n onChange={(event) => onChange(event.target.value)}\n autoComplete=\"off\"\n spellCheck={false}\n className=\"w-full rounded-md border border-border-default bg-bg-surface px-3 py-2 pr-10 text-sm text-text-primary\"\n />\n {secret ? (\n <button\n type=\"button\"\n onClick={onToggleVisible}\n className=\"absolute right-2 top-1/2 -translate-y-1/2 text-text-secondary hover:text-text-primary\"\n >\n {visible ? <EyeOff className=\"h-4 w-4\" /> : <Eye className=\"h-4 w-4\" />}\n </button>\n ) : null}\n </div>\n <button\n type=\"button\"\n onClick={onSave}\n disabled={saving}\n className=\"inline-flex items-center gap-1.5 rounded-md bg-action-primary-bg px-3 py-2 text-sm font-medium text-action-primary-text hover:bg-action-primary-bg-hover disabled:opacity-50\"\n >\n {saving ? <Loader2 className=\"h-4 w-4 animate-spin\" /> : <Save className=\"h-4 w-4\" />}\n Save\n </button>\n <button\n type=\"button\"\n onClick={onClear}\n disabled={saving || !value}\n className=\"inline-flex items-center gap-1.5 rounded-md border border-border-default px-3 py-2 text-sm font-medium text-text-secondary hover:text-text-primary hover:border-border-strong disabled:opacity-50\"\n >\n <Trash2 className=\"h-4 w-4\" />\n Clear\n </button>\n </div>\n </div>\n );\n}\n"],"mappings":"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"}
@@ -200,7 +200,10 @@ var j = () => {
200
200
  G,
201
201
  Y
202
202
  ]), Q = g(async (e) => {
203
- if (M && P[M]) return;
203
+ if (M) {
204
+ let e = P[M]?.status;
205
+ if (e === "provisioning" || e === "connecting" || e === "streaming") return;
206
+ }
204
207
  let n = t.getState().mode, r = M, a = r ? t.getState().conversations.find((e) => e.id === r) : void 0;
205
208
  if (r ? a && a.mode !== n && (a.messages.length > 0 || a.backendSessionId) ? r = R(void 0, n) : a && a.mode !== n && I(r, n) : r = R(void 0, n), !r) return;
206
209
  let c = S(), u = {
@@ -1 +1 @@
1
- {"version":3,"file":"AssistantChatArea.js","names":[],"sources":["../../../src/components/assistant/AssistantChatArea.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, type FC } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AssistantChatInput } from './AssistantChatInput';\nimport { AssistantMessageList } from './AssistantMessageList';\nimport { AssistantModeSelector } from './AssistantModeSelector';\nimport { AssistantWelcome } from './AssistantWelcome';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport {\n createAssistantSandbox,\n createAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantArtifacts,\n getAssistantMessages,\n getAssistantSession,\n sendAssistantPromptAsync,\n waitForAssistantServiceReady,\n waitForSandboxReady,\n type AssistantRawMessage,\n type AssistantSandboxAuthContext,\n} from '@/services/assistantApi';\nimport { gatherPageContext } from '@/utils/pageContext';\nimport { useTr } from '../../shared/hooks/useTr';\nimport type {\n AssistantArtifact,\n AssistantConversationStatus,\n AssistantMode,\n Message,\n} from '@/types/assistant';\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId'\n ? 'burdenoff-active-context-workspace'\n : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as {\n workspaceId?: string;\n organizationId?: string;\n };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId');\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\nfunction isRecoverableAssistantError(error: unknown): boolean {\n const message =\n error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n 'session mode mismatch',\n ].some((fragment) => message.includes(fragment));\n}\n\nfunction sanitizeAssistantResponse(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nconst VIBECONTROLS_HELP_TOPICS: Record<string, string> = {\n action:\n 'An Action is a reusable operation VibeControls can run against a target, vibe, session, or automation flow. Create one from VibeControls > Actions, choose the action type, configure the inputs and permissions, then save and run it from the relevant page.',\n component:\n 'A Component represents a software unit such as a service, frontend, library, API, database, or infrastructure asset. Add one from VibeControls > Components, fill in the metadata, lifecycle, owner, language/framework, and tags, then attach it to catalogs or vibes as needed.',\n vibe: 'A Vibe is a workspace project/repository context managed by VibeControls. Create one from VibeControls > Vibes, select the type and path, optionally attach an agent, then use its detail tabs for sessions, docs, catalogs, GitOps, and related resources.',\n session:\n 'A Session is a managed terminal/workflow runtime connected to an agent. Open VibeControls > Sessions, create or start a session, then use terminal tabs to interact with it, share it, restart it, or bookmark a specific session tab from the URL.',\n target:\n 'A Target is a machine or runtime endpoint that an agent can manage. Register or inspect targets from VibeControls > Targets & Agents, then use them for sessions, actions, tunnels, and vibe operations.',\n};\n\nfunction resolveVibeControlsHelpPrompt(prompt: string): string | null {\n const normalized = prompt.toLowerCase();\n if (!normalized.includes('vibecontrols') && !normalized.includes('vibe controls')) {\n return null;\n }\n\n const topic = Object.keys(VIBECONTROLS_HELP_TOPICS).find((key) =>\n new RegExp(`\\\\b${key}s?\\\\b`).test(normalized)\n );\n\n if (!topic) return null;\n return VIBECONTROLS_HELP_TOPICS[topic] ?? null;\n}\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n return message.info?.time?.created ?? 0;\n}\n\nfunction getAssistantText(parts: AssistantRawMessage['parts']): string {\n return (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction getToolProgress(parts: AssistantRawMessage['parts']): string[] {\n return (parts ?? [])\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const tool = part.tool ?? 'tool';\n if (status === 'completed') return `Completed: ${tool}`;\n if (status === 'failed') return `Failed: ${tool}`;\n return `Running: ${tool}`;\n });\n}\n\nfunction buildAssistantProgress(\n messages: AssistantRawMessage[],\n sinceMs: number\n): {\n content: string;\n done: boolean;\n tokens?: { input: number; output: number };\n} {\n const relevant = messages\n .filter(\n (message) => message.info?.role === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs\n )\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n if (relevant.length === 0) {\n return { content: 'Thinking...', done: false };\n }\n\n const latest = relevant[relevant.length - 1]!;\n const text = getAssistantText(latest.parts);\n const toolProgress = getToolProgress(latest.parts);\n const content = [text, ...toolProgress].filter(Boolean).join('\\n\\n') || 'Thinking...';\n\n return {\n content,\n done: Boolean(latest.info?.time?.completed) && (Boolean(text) || toolProgress.length > 0),\n tokens: latest.info?.tokens,\n };\n}\n\nfunction getConversationStatusMessage(status: AssistantConversationStatus): string {\n switch (status) {\n case 'provisioning':\n return 'Starting assistant environment...';\n case 'connecting':\n return 'Connecting to assistant...';\n case 'streaming':\n return 'Thinking...';\n default:\n return '';\n }\n}\n\nfunction isWorkspaceListPrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return (\n normalized.includes('workspace') &&\n (normalized.includes('access') ||\n normalized.includes('authorized') ||\n normalized.includes('list'))\n );\n}\n\nfunction isApiCapabilitiesPrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return (\n normalized.includes('what api calls') ||\n normalized.includes('what can you do') ||\n normalized.includes('which api calls')\n );\n}\n\nfunction isProfileNamePrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return normalized.includes('what is my name') || normalized.includes('my profile name');\n}\n\nfunction extractTargetNamePrompt(prompt: string): string | null {\n const match = prompt.match(/create\\s+a\\s+target\\s+named\\s+(.+)/i);\n return match?.[1]?.trim() ?? null;\n}\n\nfunction getLocalProfileName(): { name?: string; email?: string } {\n try {\n const profileId = sessionStorage.getItem('bf-active-profile');\n if (!profileId) return {};\n const raw = localStorage.getItem(`bf-p-${profileId}-user`);\n if (!raw) return {};\n const user = JSON.parse(raw) as {\n username?: string;\n name?: string;\n firstName?: string;\n email?: string;\n };\n return {\n name: user.name || user.username || user.firstName,\n email: user.email,\n };\n } catch {\n return {};\n }\n}\n\nfunction isGeneralCapabilitiesPrompt(prompt: string): boolean {\n return prompt.toLowerCase().trim() === 'what can you do';\n}\n\nexport const AssistantChatArea: FC = () => {\n const tr = useTr();\n const apollo = useGatewayClient('workspace');\n const { getAccessToken, getWorkspaceToken, userId } = useAuthToken();\n\n const {\n conversations,\n activeConversationId,\n mode,\n processingByConversationId,\n setMode,\n setConversationMode,\n setActiveConversation,\n createConversation,\n addMessage,\n updateMessage,\n patchMessage,\n setConversationProcessing,\n clearConversationProcessing,\n setBackendSessionId,\n clearBackendSessionId,\n setSandbox,\n clearSandbox,\n } = useAssistantStore();\n\n const activeConversation = conversations.find(\n (conversation) => conversation.id === activeConversationId\n );\n const messages = activeConversation?.messages ?? [];\n const activeProcessing = activeConversationId\n ? processingByConversationId[activeConversationId]\n : undefined;\n const activeStreamingMessageId = activeProcessing?.messageId ?? null;\n const activeIsProcessing =\n activeProcessing?.status === 'provisioning' ||\n activeProcessing?.status === 'connecting' ||\n activeProcessing?.status === 'streaming';\n\n const updateConversationStage = useCallback(\n (\n conversationId: string,\n assistantMessageId: string,\n status: AssistantConversationStatus,\n content?: string\n ) => {\n setConversationProcessing(conversationId, status, assistantMessageId, null);\n if (content) {\n updateMessage(conversationId, assistantMessageId, content);\n } else {\n updateMessage(conversationId, assistantMessageId, getConversationStatusMessage(status));\n }\n },\n [setConversationProcessing, updateMessage]\n );\n\n const resolveApiCallsPromptLocally = useCallback(\n async (prompt: string): Promise<string | null> => {\n if (isWorkspaceListPrompt(prompt)) {\n const workspaceId = getWorkspaceId();\n const organizationId = getOrganizationId();\n\n if (!workspaceId) {\n return 'No authorized workspace is attached to this assistant session yet.';\n }\n\n const publicUrl = organizationId\n ? `https://api.${organizationId}.burdenoff.com/workspaces/graphql`\n : '';\n\n return `This assistant sandbox is authorized for 1 workspace:\\n\\n1. Current active workspace — ${workspaceId}${publicUrl ? `\\n Public URL: ${publicUrl}` : ''}`;\n }\n\n if (isProfileNamePrompt(prompt)) {\n const profile = getLocalProfileName();\n if (!profile.name && !profile.email) {\n return 'I could not determine your profile name from the local session.';\n }\n\n const nameLine = profile.name ? `Your profile name is ${profile.name}.` : '';\n const emailLine = profile.email ? `Your profile email is ${profile.email}.` : '';\n return [nameLine, emailLine].filter(Boolean).join('\\n');\n }\n\n const requestedTargetName = extractTargetNamePrompt(prompt);\n if (requestedTargetName) {\n return [\n `I can create the target \"${requestedTargetName}\", but I still need the target connection details before I can execute the mutation.`,\n '',\n 'Please provide at least:',\n '- target type (SSH, RDP, VNC, or DIRECT)',\n '- host or IP address',\n '- port if it is not the default for that protocol',\n '- any credentials or auth method the target requires',\n '',\n `For example: create a target named ${requestedTargetName} using SSH at 10.0.0.15:22 with username ubuntu.`,\n ].join('\\n');\n }\n\n if (isApiCapabilitiesPrompt(prompt)) {\n return [\n 'I can query or mutate both Burdenoff GraphQL gateways on your behalf:',\n '',\n '- Global gateway: organizations, workspaces, apps, billing, notifications, store, support, and dev portal operations',\n '- Workspace gateway: workspace-scoped modules like vibecontrols, files, tags, conversations, calendar, sandbox, and related resources',\n '',\n 'Ask me for a specific action like listing your workspaces, fetching a resource, or creating/updating something through the APIs.',\n ].join('\\n');\n }\n\n return null;\n },\n []\n );\n\n const resolveLocalPromptByMode = useCallback(\n async (currentMode: AssistantMode, prompt: string): Promise<string | null> => {\n if (currentMode === 'api-calls') {\n return resolveApiCallsPromptLocally(prompt);\n }\n\n if (currentMode === 'general' && isGeneralCapabilitiesPrompt(prompt)) {\n return [\n 'I can help explain VibeControls concepts, navigation, sessions, targets, tunnels, and platform workflows.',\n '',\n 'I can also guide you through the UI, describe features, and help you understand how different parts of the product fit together.',\n ].join('\\n');\n }\n\n if (currentMode === 'vibe-plugins' && isGeneralCapabilitiesPrompt(prompt)) {\n return [\n 'I can help design, generate, explain, and package Vibe Plugins.',\n '',\n 'I can create plugin scaffolds, explain manifests and lifecycle hooks, and provide downloadable plugin artifacts for this session.',\n ].join('\\n');\n }\n\n const helpResponse = resolveVibeControlsHelpPrompt(prompt);\n if (helpResponse) return helpResponse;\n\n return null;\n },\n [resolveApiCallsPromptLocally]\n );\n\n const ensureAssistantRuntime = useCallback(\n async (\n conversationId: string,\n assistantMessageId: string,\n currentMode: AssistantMode,\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let currentSandboxId =\n useAssistantStore.getState().sandboxesByMode[currentMode]?.sandboxId ?? null;\n\n if (!currentSandboxId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Looking for existing environment...'\n );\n const existingId = await findExistingSandbox(apollo, workspaceId, currentMode, authContext);\n\n if (existingId) {\n currentSandboxId = existingId;\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Reusing assistant environment...'\n );\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n } else {\n if (currentMode === 'api-calls' && !getAccessToken()) {\n throw new Error(\n 'API Calls mode requires an authenticated session. Please sign in again.'\n );\n }\n\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Starting assistant environment...'\n );\n setSandbox(currentMode, null, 'provisioning');\n currentSandboxId = await createAssistantSandbox(\n apollo,\n currentMode,\n workspaceId,\n authContext\n );\n\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n }\n }\n\n let sessionId =\n useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n ?.backendSessionId ?? null;\n\n if (sessionId) {\n try {\n const session = await getAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n sessionId,\n authContext\n );\n if (session.mode !== currentMode) {\n throw new Error(\n `Session mode mismatch: expected ${currentMode}, received ${session.mode}`\n );\n }\n } catch {\n clearBackendSessionId(conversationId);\n sessionId = null;\n }\n }\n\n if (!sessionId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'connecting',\n 'Preparing session...'\n );\n const result = await createAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n currentMode,\n authContext\n );\n sessionId = result.sessionId;\n setBackendSessionId(conversationId, sessionId);\n }\n\n return {\n sandboxId: currentSandboxId,\n sessionId,\n };\n },\n [\n apollo,\n clearBackendSessionId,\n getAccessToken,\n setBackendSessionId,\n setSandbox,\n updateConversationStage,\n ]\n );\n\n const handleSend = useCallback(\n async (content: string) => {\n if (activeConversationId && processingByConversationId[activeConversationId]) return;\n\n const selectedMode = useAssistantStore.getState().mode;\n let conversationId = activeConversationId;\n const currentConversation = conversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n : undefined;\n\n if (!conversationId) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (\n currentConversation &&\n currentConversation.mode !== selectedMode &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (currentConversation && currentConversation.mode !== selectedMode) {\n setConversationMode(conversationId, selectedMode);\n }\n\n if (!conversationId) return;\n\n const workspaceId = getWorkspaceId();\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n const userMessage: Message = {\n id: generateId(),\n role: 'user',\n content,\n timestamp: new Date().toISOString(),\n };\n addMessage(conversationId, userMessage);\n\n const assistantMessageId = generateId();\n addMessage(conversationId, {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: new Date().toISOString(),\n });\n\n const applyAssistantResponse = (\n response: string,\n artifacts?: AssistantArtifact[],\n sessionId?: string,\n sandboxId?: string\n ) => {\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(response),\n metadata:\n artifacts?.length || sessionId || sandboxId\n ? {\n artifacts,\n mode: selectedMode,\n sessionId,\n sandboxId,\n }\n : undefined,\n });\n };\n\n const sendWithRealBackend = async (attempt: 1 | 2 = 1): Promise<void> => {\n try {\n const { sandboxId, sessionId } = await ensureAssistantRuntime(\n conversationId,\n assistantMessageId,\n selectedMode,\n workspaceId,\n authContext\n );\n\n updateConversationStage(conversationId, assistantMessageId, 'streaming', 'Thinking...');\n const promptStartedAt = Date.now();\n await sendAssistantPromptAsync(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n content,\n selectedMode,\n gatherPageContext(),\n authContext\n );\n\n let result = buildAssistantProgress([], promptStartedAt);\n const timeoutAt = Date.now() + 180_000;\n let lastTtlExtensionAt = Date.now();\n\n while (Date.now() < timeoutAt) {\n const sessionMessages = await getAssistantMessages(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext,\n 50\n );\n\n result = buildAssistantProgress(sessionMessages, promptStartedAt);\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(result.content),\n });\n\n if (result.done) break;\n\n if (Date.now() - lastTtlExtensionAt > 30_000) {\n await extendAssistantSandboxTTL(\n apollo,\n sandboxId,\n workspaceId,\n 600,\n authContext\n ).catch(() => undefined);\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, 1500));\n }\n\n if (!result.done) {\n throw new Error('Assistant response timed out while streaming progress');\n }\n\n const artifacts =\n selectedMode === 'vibe-plugins'\n ? await getAssistantArtifacts(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext\n ).catch(() => [])\n : undefined;\n\n applyAssistantResponse(result.content, artifacts, sessionId, sandboxId);\n\n if (result.tokens) {\n patchMessage(conversationId, assistantMessageId, { tokens: result.tokens });\n }\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n } catch (error) {\n if (attempt === 1 && isRecoverableAssistantError(error)) {\n clearBackendSessionId(conversationId);\n clearSandbox(selectedMode);\n return sendWithRealBackend(2);\n }\n\n throw error;\n }\n };\n\n try {\n if (!workspaceId) {\n throw new Error('Assistant backend is unavailable without an active workspace context.');\n }\n\n {\n const localResponse = await resolveLocalPromptByMode(selectedMode, content);\n if (localResponse) {\n applyAssistantResponse(localResponse);\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n return;\n }\n }\n\n await sendWithRealBackend();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Something went wrong';\n\n {\n const localResponse = await resolveLocalPromptByMode(selectedMode, content).catch(\n () => null\n );\n if (localResponse) {\n applyAssistantResponse(localResponse);\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n return;\n }\n }\n\n setConversationProcessing(conversationId, 'error', assistantMessageId, errorMessage);\n updateMessage(\n conversationId,\n assistantMessageId,\n `Sorry, an error occurred: ${errorMessage}. Please try again.`\n );\n }\n },\n [\n activeConversationId,\n addMessage,\n apollo,\n clearBackendSessionId,\n clearSandbox,\n conversations,\n createConversation,\n ensureAssistantRuntime,\n getAccessToken,\n getWorkspaceToken,\n patchMessage,\n processingByConversationId,\n setBackendSessionId,\n setConversationMode,\n setConversationProcessing,\n resolveLocalPromptByMode,\n updateConversationStage,\n updateMessage,\n userId,\n ]\n );\n\n const handleSuggestionClick = useCallback(\n (suggestion: string) => {\n void handleSend(suggestion);\n },\n [handleSend]\n );\n\n // Auto-send any prompt that was queued by `useOpenAssistantHelp` (from\n // the shared GetHelpButton). We keep the latest handleSend in a ref so\n // the subscription effect below doesn't need to tear down on every\n // render — the store subscription lives for the component lifetime and\n // always calls the freshest handler.\n const handleSendRef = useRef(handleSend);\n useEffect(() => {\n handleSendRef.current = handleSend;\n }, [handleSend]);\n\n useEffect(() => {\n // Flush any prompt that was queued before this component mounted.\n const initial = useAssistantStore.getState().consumePendingPrompt();\n if (initial) {\n void handleSendRef.current(initial);\n }\n\n // Watch for new prompts queued while we're already mounted.\n const unsubscribe = useAssistantStore.subscribe((state, prev) => {\n if (state.pendingPrompt && state.pendingPrompt !== prev.pendingPrompt) {\n const queued = useAssistantStore.getState().consumePendingPrompt();\n if (queued) {\n void handleSendRef.current(queued);\n }\n }\n });\n return unsubscribe;\n }, []);\n\n const handleModeChange = useCallback(\n (newMode: AssistantMode) => {\n const currentConversation = activeConversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === activeConversationId)\n : undefined;\n\n if (\n currentConversation &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n if (currentConversation.mode !== newMode) {\n const reusableDraft = useAssistantStore\n .getState()\n .conversations.find(\n (conversation) =>\n conversation.mode === newMode &&\n conversation.messages.length === 0 &&\n !conversation.backendSessionId &&\n !useAssistantStore.getState().processingByConversationId[conversation.id]\n );\n\n if (reusableDraft) {\n setActiveConversation(reusableDraft.id);\n } else {\n createConversation(undefined, newMode);\n }\n }\n } else if (activeConversationId) {\n setConversationMode(activeConversationId, newMode);\n }\n\n setMode(newMode);\n },\n [activeConversationId, createConversation, setActiveConversation, setConversationMode, setMode]\n );\n\n return (\n <div className=\"flex flex-col flex-1 overflow-hidden pt-3\">\n {messages.length === 0 ? (\n <AssistantWelcome mode={mode} onSuggestionClick={handleSuggestionClick} />\n ) : (\n <AssistantMessageList messages={messages} streamingMessageId={activeStreamingMessageId} />\n )}\n\n <div>\n <div className=\"px-3 pb-1\">\n <AssistantModeSelector mode={mode} onModeChange={handleModeChange} />\n </div>\n <AssistantChatInput\n onSend={(value) => void handleSend(value)}\n disabled={activeIsProcessing}\n placeholder={tr(\n 'vibecontrols.assistant.chatArea.placeholder',\n `Ask about ${mode.replace(/-/g, ' ')}...`\n )}\n />\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
1
+ {"version":3,"file":"AssistantChatArea.js","names":[],"sources":["../../../src/components/assistant/AssistantChatArea.tsx"],"sourcesContent":["import { useCallback, useEffect, useRef, type FC } from 'react';\nimport { useGatewayClient } from '@burdenoff/fe-libs/shared/graphql';\nimport { useAuthToken } from '@burdenoff/fe-libs/shared/providers/shell';\nimport { AssistantChatInput } from './AssistantChatInput';\nimport { AssistantMessageList } from './AssistantMessageList';\nimport { AssistantModeSelector } from './AssistantModeSelector';\nimport { AssistantWelcome } from './AssistantWelcome';\nimport { useAssistantStore } from '@/store/assistantStore';\nimport {\n createAssistantSandbox,\n createAssistantSession,\n extendAssistantSandboxTTL,\n findExistingSandbox,\n getAssistantArtifacts,\n getAssistantMessages,\n getAssistantSession,\n sendAssistantPromptAsync,\n waitForAssistantServiceReady,\n waitForSandboxReady,\n type AssistantRawMessage,\n type AssistantSandboxAuthContext,\n} from '@/services/assistantApi';\nimport { gatherPageContext } from '@/utils/pageContext';\nimport { useTr } from '../../shared/hooks/useTr';\nimport type {\n AssistantArtifact,\n AssistantConversationStatus,\n AssistantMode,\n Message,\n} from '@/types/assistant';\n\nfunction generateId(): string {\n return `${Date.now()}-${Math.random().toString(36).slice(2, 9)}`;\n}\n\nfunction getProfileContextValue(key: 'workspaceId' | 'organizationId'): string {\n try {\n const activeContextKey =\n key === 'workspaceId'\n ? 'burdenoff-active-context-workspace'\n : 'burdenoff-active-context-organization';\n const activeContextValue = localStorage.getItem(activeContextKey);\n if (activeContextValue) return activeContextValue;\n\n const activeProfileId = sessionStorage.getItem('bf-active-profile');\n if (!activeProfileId) return '';\n const raw = localStorage.getItem(`bf-p-${activeProfileId}-context`);\n if (!raw) return '';\n const context = JSON.parse(raw) as {\n workspaceId?: string;\n organizationId?: string;\n };\n return context[key] ?? '';\n } catch {\n return '';\n }\n}\n\nfunction getWorkspaceId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('workspace') ?? getProfileContextValue('workspaceId');\n}\n\nfunction getOrganizationId(): string {\n const params = new URLSearchParams(window.location.search);\n return params.get('org') ?? getProfileContextValue('organizationId');\n}\n\nfunction isRecoverableAssistantError(error: unknown): boolean {\n const message =\n error instanceof Error ? error.message.toLowerCase() : String(error).toLowerCase();\n\n if (\n message.includes('rate limit exceeded') ||\n message.includes('unauthorized') ||\n message.includes('k8s api error 401')\n ) {\n return false;\n }\n\n return [\n 'sandbox not found',\n 'sandbox is not running',\n 'sandbox service not available yet',\n 'sandbox failed',\n 'sandbox startup timed out',\n 'assistant service did not become healthy',\n 'proxy error: 404',\n 'proxy error: 502',\n 'proxy error: 503',\n 'session mode mismatch',\n ].some((fragment) => message.includes(fragment));\n}\n\nfunction sanitizeAssistantResponse(response: string): string {\n return response\n .replace(/(Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/(X-Workspace-Authorization\\s*:\\s*Bearer\\s+)[^\\s\\n]+/gi, '$1[REDACTED]')\n .replace(/\\beyJ[A-Za-z0-9_-]+\\.[A-Za-z0-9._-]+\\.[A-Za-z0-9._-]+\\b/g, '[REDACTED_JWT]')\n .replace(/\\bsk-ant-[A-Za-z0-9-]+\\b/g, '[REDACTED_API_KEY]');\n}\n\nconst VIBECONTROLS_HELP_TOPICS: Record<string, string> = {\n action:\n 'An Action is a reusable operation VibeControls can run against a target, vibe, session, or automation flow. Create one from VibeControls > Actions, choose the action type, configure the inputs and permissions, then save and run it from the relevant page.',\n component:\n 'A Component represents a software unit such as a service, frontend, library, API, database, or infrastructure asset. Add one from VibeControls > Components, fill in the metadata, lifecycle, owner, language/framework, and tags, then attach it to catalogs or vibes as needed.',\n vibe: 'A Vibe is a workspace project/repository context managed by VibeControls. Create one from VibeControls > Vibes, select the type and path, optionally attach an agent, then use its detail tabs for sessions, docs, catalogs, GitOps, and related resources.',\n session:\n 'A Session is a managed terminal/workflow runtime connected to an agent. Open VibeControls > Sessions, create or start a session, then use terminal tabs to interact with it, share it, restart it, or bookmark a specific session tab from the URL.',\n target:\n 'A Target is a machine or runtime endpoint that an agent can manage. Register or inspect targets from VibeControls > Targets & Agents, then use them for sessions, actions, tunnels, and vibe operations.',\n};\n\nfunction resolveVibeControlsHelpPrompt(prompt: string): string | null {\n const normalized = prompt.toLowerCase();\n if (!normalized.includes('vibecontrols') && !normalized.includes('vibe controls')) {\n return null;\n }\n\n const topic = Object.keys(VIBECONTROLS_HELP_TOPICS).find((key) =>\n new RegExp(`\\\\b${key}s?\\\\b`).test(normalized)\n );\n\n if (!topic) return null;\n return VIBECONTROLS_HELP_TOPICS[topic] ?? null;\n}\n\nfunction getRawMessageCreatedAt(message: AssistantRawMessage): number {\n return message.info?.time?.created ?? 0;\n}\n\nfunction getAssistantText(parts: AssistantRawMessage['parts']): string {\n return (parts ?? [])\n .filter((part) => part.type === 'text' && typeof part.text === 'string')\n .map((part) => part.text?.trim() ?? '')\n .filter(Boolean)\n .join('\\n');\n}\n\nfunction getToolProgress(parts: AssistantRawMessage['parts']): string[] {\n return (parts ?? [])\n .filter((part) => part.type === 'tool' && part.tool)\n .map((part) => {\n const status = part.state?.status ?? 'running';\n const tool = part.tool ?? 'tool';\n if (status === 'completed') return `Completed: ${tool}`;\n if (status === 'failed') return `Failed: ${tool}`;\n return `Running: ${tool}`;\n });\n}\n\nfunction buildAssistantProgress(\n messages: AssistantRawMessage[],\n sinceMs: number\n): {\n content: string;\n done: boolean;\n tokens?: { input: number; output: number };\n} {\n const relevant = messages\n .filter(\n (message) => message.info?.role === 'assistant' && getRawMessageCreatedAt(message) >= sinceMs\n )\n .sort((left, right) => getRawMessageCreatedAt(left) - getRawMessageCreatedAt(right));\n\n if (relevant.length === 0) {\n return { content: 'Thinking...', done: false };\n }\n\n const latest = relevant[relevant.length - 1]!;\n const text = getAssistantText(latest.parts);\n const toolProgress = getToolProgress(latest.parts);\n const content = [text, ...toolProgress].filter(Boolean).join('\\n\\n') || 'Thinking...';\n\n return {\n content,\n done: Boolean(latest.info?.time?.completed) && (Boolean(text) || toolProgress.length > 0),\n tokens: latest.info?.tokens,\n };\n}\n\nfunction getConversationStatusMessage(status: AssistantConversationStatus): string {\n switch (status) {\n case 'provisioning':\n return 'Starting assistant environment...';\n case 'connecting':\n return 'Connecting to assistant...';\n case 'streaming':\n return 'Thinking...';\n default:\n return '';\n }\n}\n\nfunction isWorkspaceListPrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return (\n normalized.includes('workspace') &&\n (normalized.includes('access') ||\n normalized.includes('authorized') ||\n normalized.includes('list'))\n );\n}\n\nfunction isApiCapabilitiesPrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return (\n normalized.includes('what api calls') ||\n normalized.includes('what can you do') ||\n normalized.includes('which api calls')\n );\n}\n\nfunction isProfileNamePrompt(prompt: string): boolean {\n const normalized = prompt.toLowerCase();\n return normalized.includes('what is my name') || normalized.includes('my profile name');\n}\n\nfunction extractTargetNamePrompt(prompt: string): string | null {\n const match = prompt.match(/create\\s+a\\s+target\\s+named\\s+(.+)/i);\n return match?.[1]?.trim() ?? null;\n}\n\nfunction getLocalProfileName(): { name?: string; email?: string } {\n try {\n const profileId = sessionStorage.getItem('bf-active-profile');\n if (!profileId) return {};\n const raw = localStorage.getItem(`bf-p-${profileId}-user`);\n if (!raw) return {};\n const user = JSON.parse(raw) as {\n username?: string;\n name?: string;\n firstName?: string;\n email?: string;\n };\n return {\n name: user.name || user.username || user.firstName,\n email: user.email,\n };\n } catch {\n return {};\n }\n}\n\nfunction isGeneralCapabilitiesPrompt(prompt: string): boolean {\n return prompt.toLowerCase().trim() === 'what can you do';\n}\n\nexport const AssistantChatArea: FC = () => {\n const tr = useTr();\n const apollo = useGatewayClient('workspace');\n const { getAccessToken, getWorkspaceToken, userId } = useAuthToken();\n\n const {\n conversations,\n activeConversationId,\n mode,\n processingByConversationId,\n setMode,\n setConversationMode,\n setActiveConversation,\n createConversation,\n addMessage,\n updateMessage,\n patchMessage,\n setConversationProcessing,\n clearConversationProcessing,\n setBackendSessionId,\n clearBackendSessionId,\n setSandbox,\n clearSandbox,\n } = useAssistantStore();\n\n const activeConversation = conversations.find(\n (conversation) => conversation.id === activeConversationId\n );\n const messages = activeConversation?.messages ?? [];\n const activeProcessing = activeConversationId\n ? processingByConversationId[activeConversationId]\n : undefined;\n const activeStreamingMessageId = activeProcessing?.messageId ?? null;\n const activeIsProcessing =\n activeProcessing?.status === 'provisioning' ||\n activeProcessing?.status === 'connecting' ||\n activeProcessing?.status === 'streaming';\n\n const updateConversationStage = useCallback(\n (\n conversationId: string,\n assistantMessageId: string,\n status: AssistantConversationStatus,\n content?: string\n ) => {\n setConversationProcessing(conversationId, status, assistantMessageId, null);\n if (content) {\n updateMessage(conversationId, assistantMessageId, content);\n } else {\n updateMessage(conversationId, assistantMessageId, getConversationStatusMessage(status));\n }\n },\n [setConversationProcessing, updateMessage]\n );\n\n const resolveApiCallsPromptLocally = useCallback(\n async (prompt: string): Promise<string | null> => {\n if (isWorkspaceListPrompt(prompt)) {\n const workspaceId = getWorkspaceId();\n const organizationId = getOrganizationId();\n\n if (!workspaceId) {\n return 'No authorized workspace is attached to this assistant session yet.';\n }\n\n const publicUrl = organizationId\n ? `https://api.${organizationId}.burdenoff.com/workspaces/graphql`\n : '';\n\n return `This assistant sandbox is authorized for 1 workspace:\\n\\n1. Current active workspace — ${workspaceId}${publicUrl ? `\\n Public URL: ${publicUrl}` : ''}`;\n }\n\n if (isProfileNamePrompt(prompt)) {\n const profile = getLocalProfileName();\n if (!profile.name && !profile.email) {\n return 'I could not determine your profile name from the local session.';\n }\n\n const nameLine = profile.name ? `Your profile name is ${profile.name}.` : '';\n const emailLine = profile.email ? `Your profile email is ${profile.email}.` : '';\n return [nameLine, emailLine].filter(Boolean).join('\\n');\n }\n\n const requestedTargetName = extractTargetNamePrompt(prompt);\n if (requestedTargetName) {\n return [\n `I can create the target \"${requestedTargetName}\", but I still need the target connection details before I can execute the mutation.`,\n '',\n 'Please provide at least:',\n '- target type (SSH, RDP, VNC, or DIRECT)',\n '- host or IP address',\n '- port if it is not the default for that protocol',\n '- any credentials or auth method the target requires',\n '',\n `For example: create a target named ${requestedTargetName} using SSH at 10.0.0.15:22 with username ubuntu.`,\n ].join('\\n');\n }\n\n if (isApiCapabilitiesPrompt(prompt)) {\n return [\n 'I can query or mutate both Burdenoff GraphQL gateways on your behalf:',\n '',\n '- Global gateway: organizations, workspaces, apps, billing, notifications, store, support, and dev portal operations',\n '- Workspace gateway: workspace-scoped modules like vibecontrols, files, tags, conversations, calendar, sandbox, and related resources',\n '',\n 'Ask me for a specific action like listing your workspaces, fetching a resource, or creating/updating something through the APIs.',\n ].join('\\n');\n }\n\n return null;\n },\n []\n );\n\n const resolveLocalPromptByMode = useCallback(\n async (currentMode: AssistantMode, prompt: string): Promise<string | null> => {\n if (currentMode === 'api-calls') {\n return resolveApiCallsPromptLocally(prompt);\n }\n\n if (currentMode === 'general' && isGeneralCapabilitiesPrompt(prompt)) {\n return [\n 'I can help explain VibeControls concepts, navigation, sessions, targets, tunnels, and platform workflows.',\n '',\n 'I can also guide you through the UI, describe features, and help you understand how different parts of the product fit together.',\n ].join('\\n');\n }\n\n if (currentMode === 'vibe-plugins' && isGeneralCapabilitiesPrompt(prompt)) {\n return [\n 'I can help design, generate, explain, and package Vibe Plugins.',\n '',\n 'I can create plugin scaffolds, explain manifests and lifecycle hooks, and provide downloadable plugin artifacts for this session.',\n ].join('\\n');\n }\n\n const helpResponse = resolveVibeControlsHelpPrompt(prompt);\n if (helpResponse) return helpResponse;\n\n return null;\n },\n [resolveApiCallsPromptLocally]\n );\n\n const ensureAssistantRuntime = useCallback(\n async (\n conversationId: string,\n assistantMessageId: string,\n currentMode: AssistantMode,\n workspaceId: string,\n authContext: AssistantSandboxAuthContext\n ): Promise<{ sandboxId: string; sessionId: string }> => {\n let currentSandboxId =\n useAssistantStore.getState().sandboxesByMode[currentMode]?.sandboxId ?? null;\n\n if (!currentSandboxId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Looking for existing environment...'\n );\n const existingId = await findExistingSandbox(apollo, workspaceId, currentMode, authContext);\n\n if (existingId) {\n currentSandboxId = existingId;\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Reusing assistant environment...'\n );\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n } else {\n if (currentMode === 'api-calls' && !getAccessToken()) {\n throw new Error(\n 'API Calls mode requires an authenticated session. Please sign in again.'\n );\n }\n\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'provisioning',\n 'Starting assistant environment...'\n );\n setSandbox(currentMode, null, 'provisioning');\n currentSandboxId = await createAssistantSandbox(\n apollo,\n currentMode,\n workspaceId,\n authContext\n );\n\n setSandbox(currentMode, currentSandboxId, 'provisioning');\n await waitForSandboxReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 300_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'provisioning', message)\n );\n await waitForAssistantServiceReady(\n apollo,\n currentSandboxId,\n workspaceId,\n authContext,\n 90_000,\n (message) =>\n updateConversationStage(conversationId, assistantMessageId, 'connecting', message)\n );\n setSandbox(currentMode, currentSandboxId, 'running');\n }\n }\n\n let sessionId =\n useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n ?.backendSessionId ?? null;\n\n if (sessionId) {\n try {\n const session = await getAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n sessionId,\n authContext\n );\n if (session.mode !== currentMode) {\n throw new Error(\n `Session mode mismatch: expected ${currentMode}, received ${session.mode}`\n );\n }\n } catch {\n clearBackendSessionId(conversationId);\n sessionId = null;\n }\n }\n\n if (!sessionId) {\n updateConversationStage(\n conversationId,\n assistantMessageId,\n 'connecting',\n 'Preparing session...'\n );\n const result = await createAssistantSession(\n apollo,\n currentSandboxId,\n workspaceId,\n currentMode,\n authContext\n );\n sessionId = result.sessionId;\n setBackendSessionId(conversationId, sessionId);\n }\n\n return {\n sandboxId: currentSandboxId,\n sessionId,\n };\n },\n [\n apollo,\n clearBackendSessionId,\n getAccessToken,\n setBackendSessionId,\n setSandbox,\n updateConversationStage,\n ]\n );\n\n const handleSend = useCallback(\n async (content: string) => {\n // Only block re-entry while a turn is genuinely in-flight. Earlier code\n // checked for *any* entry in the map, which stuck around as `complete`\n // / `error` after the first turn — making every subsequent message\n // silently disappear (the input cleared but handleSend bailed before\n // adding the user message to the conversation).\n if (activeConversationId) {\n const inflightStatus = processingByConversationId[activeConversationId]?.status;\n if (\n inflightStatus === 'provisioning' ||\n inflightStatus === 'connecting' ||\n inflightStatus === 'streaming'\n ) {\n return;\n }\n }\n\n const selectedMode = useAssistantStore.getState().mode;\n let conversationId = activeConversationId;\n const currentConversation = conversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === conversationId)\n : undefined;\n\n if (!conversationId) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (\n currentConversation &&\n currentConversation.mode !== selectedMode &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n conversationId = createConversation(undefined, selectedMode);\n } else if (currentConversation && currentConversation.mode !== selectedMode) {\n setConversationMode(conversationId, selectedMode);\n }\n\n if (!conversationId) return;\n\n const workspaceId = getWorkspaceId();\n const authContext: AssistantSandboxAuthContext = {\n accessToken: getAccessToken(),\n workspaceToken: getWorkspaceToken(),\n userId,\n organizationId: getOrganizationId(),\n };\n\n const userMessage: Message = {\n id: generateId(),\n role: 'user',\n content,\n timestamp: new Date().toISOString(),\n };\n addMessage(conversationId, userMessage);\n\n const assistantMessageId = generateId();\n addMessage(conversationId, {\n id: assistantMessageId,\n role: 'assistant',\n content: '',\n timestamp: new Date().toISOString(),\n });\n\n const applyAssistantResponse = (\n response: string,\n artifacts?: AssistantArtifact[],\n sessionId?: string,\n sandboxId?: string\n ) => {\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(response),\n metadata:\n artifacts?.length || sessionId || sandboxId\n ? {\n artifacts,\n mode: selectedMode,\n sessionId,\n sandboxId,\n }\n : undefined,\n });\n };\n\n const sendWithRealBackend = async (attempt: 1 | 2 = 1): Promise<void> => {\n try {\n const { sandboxId, sessionId } = await ensureAssistantRuntime(\n conversationId,\n assistantMessageId,\n selectedMode,\n workspaceId,\n authContext\n );\n\n updateConversationStage(conversationId, assistantMessageId, 'streaming', 'Thinking...');\n const promptStartedAt = Date.now();\n await sendAssistantPromptAsync(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n content,\n selectedMode,\n gatherPageContext(),\n authContext\n );\n\n let result = buildAssistantProgress([], promptStartedAt);\n const timeoutAt = Date.now() + 180_000;\n let lastTtlExtensionAt = Date.now();\n\n while (Date.now() < timeoutAt) {\n const sessionMessages = await getAssistantMessages(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext,\n 50\n );\n\n result = buildAssistantProgress(sessionMessages, promptStartedAt);\n patchMessage(conversationId, assistantMessageId, {\n content: sanitizeAssistantResponse(result.content),\n });\n\n if (result.done) break;\n\n if (Date.now() - lastTtlExtensionAt > 30_000) {\n await extendAssistantSandboxTTL(\n apollo,\n sandboxId,\n workspaceId,\n 600,\n authContext\n ).catch(() => undefined);\n lastTtlExtensionAt = Date.now();\n }\n\n await new Promise((resolve) => setTimeout(resolve, 1500));\n }\n\n if (!result.done) {\n throw new Error('Assistant response timed out while streaming progress');\n }\n\n const artifacts =\n selectedMode === 'vibe-plugins'\n ? await getAssistantArtifacts(\n apollo,\n sandboxId,\n workspaceId,\n sessionId,\n authContext\n ).catch(() => [])\n : undefined;\n\n applyAssistantResponse(result.content, artifacts, sessionId, sandboxId);\n\n if (result.tokens) {\n patchMessage(conversationId, assistantMessageId, { tokens: result.tokens });\n }\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n } catch (error) {\n if (attempt === 1 && isRecoverableAssistantError(error)) {\n clearBackendSessionId(conversationId);\n clearSandbox(selectedMode);\n return sendWithRealBackend(2);\n }\n\n throw error;\n }\n };\n\n try {\n if (!workspaceId) {\n throw new Error('Assistant backend is unavailable without an active workspace context.');\n }\n\n {\n const localResponse = await resolveLocalPromptByMode(selectedMode, content);\n if (localResponse) {\n applyAssistantResponse(localResponse);\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n return;\n }\n }\n\n await sendWithRealBackend();\n } catch (error) {\n const errorMessage = error instanceof Error ? error.message : 'Something went wrong';\n\n {\n const localResponse = await resolveLocalPromptByMode(selectedMode, content).catch(\n () => null\n );\n if (localResponse) {\n applyAssistantResponse(localResponse);\n setConversationProcessing(conversationId, 'complete', assistantMessageId, null);\n return;\n }\n }\n\n setConversationProcessing(conversationId, 'error', assistantMessageId, errorMessage);\n updateMessage(\n conversationId,\n assistantMessageId,\n `Sorry, an error occurred: ${errorMessage}. Please try again.`\n );\n }\n },\n [\n activeConversationId,\n addMessage,\n apollo,\n clearBackendSessionId,\n clearSandbox,\n conversations,\n createConversation,\n ensureAssistantRuntime,\n getAccessToken,\n getWorkspaceToken,\n patchMessage,\n processingByConversationId,\n setBackendSessionId,\n setConversationMode,\n setConversationProcessing,\n resolveLocalPromptByMode,\n updateConversationStage,\n updateMessage,\n userId,\n ]\n );\n\n const handleSuggestionClick = useCallback(\n (suggestion: string) => {\n void handleSend(suggestion);\n },\n [handleSend]\n );\n\n // Auto-send any prompt that was queued by `useOpenAssistantHelp` (from\n // the shared GetHelpButton). We keep the latest handleSend in a ref so\n // the subscription effect below doesn't need to tear down on every\n // render — the store subscription lives for the component lifetime and\n // always calls the freshest handler.\n const handleSendRef = useRef(handleSend);\n useEffect(() => {\n handleSendRef.current = handleSend;\n }, [handleSend]);\n\n useEffect(() => {\n // Flush any prompt that was queued before this component mounted.\n const initial = useAssistantStore.getState().consumePendingPrompt();\n if (initial) {\n void handleSendRef.current(initial);\n }\n\n // Watch for new prompts queued while we're already mounted.\n const unsubscribe = useAssistantStore.subscribe((state, prev) => {\n if (state.pendingPrompt && state.pendingPrompt !== prev.pendingPrompt) {\n const queued = useAssistantStore.getState().consumePendingPrompt();\n if (queued) {\n void handleSendRef.current(queued);\n }\n }\n });\n return unsubscribe;\n }, []);\n\n const handleModeChange = useCallback(\n (newMode: AssistantMode) => {\n const currentConversation = activeConversationId\n ? useAssistantStore\n .getState()\n .conversations.find((conversation) => conversation.id === activeConversationId)\n : undefined;\n\n if (\n currentConversation &&\n (currentConversation.messages.length > 0 || currentConversation.backendSessionId)\n ) {\n if (currentConversation.mode !== newMode) {\n const reusableDraft = useAssistantStore\n .getState()\n .conversations.find(\n (conversation) =>\n conversation.mode === newMode &&\n conversation.messages.length === 0 &&\n !conversation.backendSessionId &&\n !useAssistantStore.getState().processingByConversationId[conversation.id]\n );\n\n if (reusableDraft) {\n setActiveConversation(reusableDraft.id);\n } else {\n createConversation(undefined, newMode);\n }\n }\n } else if (activeConversationId) {\n setConversationMode(activeConversationId, newMode);\n }\n\n setMode(newMode);\n },\n [activeConversationId, createConversation, setActiveConversation, setConversationMode, setMode]\n );\n\n return (\n <div className=\"flex flex-col flex-1 overflow-hidden pt-3\">\n {messages.length === 0 ? (\n <AssistantWelcome mode={mode} onSuggestionClick={handleSuggestionClick} />\n ) : (\n <AssistantMessageList messages={messages} streamingMessageId={activeStreamingMessageId} />\n )}\n\n <div>\n <div className=\"px-3 pb-1\">\n <AssistantModeSelector mode={mode} onModeChange={handleModeChange} />\n </div>\n <AssistantChatInput\n onSend={(value) => void handleSend(value)}\n disabled={activeIsProcessing}\n placeholder={tr(\n 'vibecontrols.assistant.chatArea.placeholder',\n `Ask about ${mode.replace(/-/g, ' ')}...`\n )}\n />\n </div>\n </div>\n );\n};\n"],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@burdenoff/microfe-vibecontrols",
3
- "version": "2026.515.1",
3
+ "version": "2026.515.2",
4
4
  "description": "VibeControls microfrontend for Burdenoff products",
5
5
  "type": "module",
6
6
  "files": [