import { useStore } from '@nanostores/react' import type { ReactNode } from 'react' import { useCallback, useMemo } from 'react' import type { CommandCenterSection } from '@/app/command-center' import { GatewayMenuPanel } from '@/app/shell/gateway-menu-panel' import { Activity, AlertCircle, ChevronDown, Clock, Command, Hash, Loader2, Sparkles, Zap, ZapFilled } from '@/lib/icons' import { formatModelStatusLabel } from '@/lib/model-status-label' import type { RuntimeReadinessResult } from '@/lib/runtime-readiness' import { contextBarLabel, LiveDuration, usageContextLabel } from '@/lib/statusbar' import { cn } from '@/lib/utils' import { setSessionYolo } from '@/lib/yolo-session' import { $desktopActionTasks } from '@/store/activity' import { $previewServerRestartStatus } from '@/store/preview' import { $activeSessionId, $busy, $currentFastMode, $currentModel, $currentProvider, $currentReasoningEffort, $currentUsage, $sessionStartedAt, $turnStartedAt, $workingSessionIds, $yoloActive, setModelPickerOpen, setYoloActive } from '@/store/session' import { $subagentsBySession, activeSubagentCount } from '@/store/subagents' import { $desktopVersion, $updateApply, $updateStatus, setUpdateOverlayOpen } from '@/store/updates' import type { StatusResponse } from '@/types/hermes' import { CRON_ROUTE } from '../../routes' import type { StatusbarItem } from '../statusbar-controls' interface StatusbarItemsOptions { agentsOpen: boolean commandCenterOpen: boolean extraLeftItems: readonly StatusbarItem[] extraRightItems: readonly StatusbarItem[] gatewayLogLines: readonly string[] gatewayState: string inferenceStatus: RuntimeReadinessResult | null modelMenuContent?: ReactNode openAgents: () => void openCommandCenterSection: (section: CommandCenterSection) => void freshDraftReady: boolean requestGateway: (method: string, params?: Record) => Promise statusSnapshot: StatusResponse | null toggleCommandCenter: () => void } export function useStatusbarItems({ agentsOpen, commandCenterOpen, extraLeftItems, extraRightItems, gatewayLogLines, gatewayState, inferenceStatus, modelMenuContent, openAgents, openCommandCenterSection, freshDraftReady, requestGateway, statusSnapshot, toggleCommandCenter }: StatusbarItemsOptions) { const activeSessionId = useStore($activeSessionId) const yoloActive = useStore($yoloActive) const busy = useStore($busy) const currentFastMode = useStore($currentFastMode) const currentModel = useStore($currentModel) const currentProvider = useStore($currentProvider) const currentReasoningEffort = useStore($currentReasoningEffort) const currentUsage = useStore($currentUsage) const desktopActionTasks = useStore($desktopActionTasks) const previewServerRestartStatus = useStore($previewServerRestartStatus) const sessionStartedAt = useStore($sessionStartedAt) const turnStartedAt = useStore($turnStartedAt) const workingSessionIds = useStore($workingSessionIds) const subagentsBySession = useStore($subagentsBySession) const updateStatus = useStore($updateStatus) const updateApply = useStore($updateApply) const desktopVersion = useStore($desktopVersion) const contextUsage = useMemo(() => usageContextLabel(currentUsage), [currentUsage]) const contextBar = useMemo(() => contextBarLabel(currentUsage), [currentUsage]) // Per-session approval bypass (same scope as the TUI's Shift+Tab). On a // new-chat draft (no runtime session yet) we arm locally; the session-create // path applies it once the backend session exists. const toggleYolo = useCallback(async () => { const next = !$yoloActive.get() const sid = $activeSessionId.get() setYoloActive(next) if (!sid) { return } try { await setSessionYolo(requestGateway, sid, next) } catch { setYoloActive(!next) } }, [requestGateway]) const showYoloToggle = gatewayState === 'open' && (!!activeSessionId || freshDraftReady) const gatewayMenuContent = useMemo( () => ( openCommandCenterSection('system')} statusSnapshot={statusSnapshot} /> ), [gatewayLogLines, gatewayState, inferenceStatus, openCommandCenterSection, statusSnapshot] ) const { bgFailed, bgRunning, subagentsRunning } = useMemo(() => { const actions = Object.values(desktopActionTasks) const running = actions.filter(t => t.status.running).length const failed = actions.filter(t => !t.status.running && (t.status.exit_code ?? 0) !== 0).length const previewRunning = previewServerRestartStatus === 'running' ? 1 : 0 const previewFailed = previewServerRestartStatus === 'error' ? 1 : 0 const subagentsRunning = Object.values(subagentsBySession).reduce( (sum, items) => sum + activeSubagentCount(items), 0 ) return { bgFailed: failed + previewFailed, bgRunning: workingSessionIds.length + running + previewRunning, subagentsRunning } }, [desktopActionTasks, previewServerRestartStatus, subagentsBySession, workingSessionIds]) const gatewayOpen = gatewayState === 'open' const gatewayConnecting = gatewayState === 'connecting' const inferenceReady = gatewayOpen && inferenceStatus?.ready === true const gatewayDegraded = gatewayOpen || gatewayConnecting const gatewayDetail = gatewayOpen ? inferenceStatus?.ready ? 'ready' : inferenceStatus ? 'needs setup' : 'checking' : gatewayConnecting ? 'connecting' : 'offline' const gatewayClassName = inferenceReady ? undefined : gatewayDegraded ? 'text-amber-600 hover:text-amber-600' : 'text-destructive hover:text-destructive' const versionItem = useMemo(() => { const appVersion = desktopVersion?.appVersion const sha = updateStatus?.currentSha?.slice(0, 7) ?? null const behind = updateStatus?.behind ?? 0 const applying = updateApply.applying || updateApply.stage === 'restart' const base = appVersion ? `v${appVersion}` : (sha ?? 'unknown') const behindHint = !applying && behind > 0 ? ` (+${behind})` : '' const label = applying ? updateApply.stage === 'restart' ? `${base} · restart` : `${base} · update` : `${base}${behindHint}` const tooltip = [ applying ? updateApply.message || 'Update in progress' : null, !applying && behind > 0 && `${behind} commit${behind === 1 ? '' : 's'} behind ${updateStatus?.branch ?? '…'}`, appVersion && `Hermes Desktop v${appVersion}`, sha && `commit ${sha}`, updateStatus?.branch && `branch ${updateStatus.branch}` ] .filter(Boolean) .join(' · ') return { className: !applying && behind > 0 ? 'text-primary hover:text-primary' : undefined, detail: appVersion && sha && !applying ? sha : undefined, hidden: !appVersion && !sha, icon: applying ? : , id: 'version', label, onSelect: () => setUpdateOverlayOpen(true), title: tooltip || undefined, variant: 'action' } }, [ desktopVersion?.appVersion, updateApply.applying, updateApply.message, updateApply.stage, updateStatus?.behind, updateStatus?.branch, updateStatus?.currentSha ]) const coreLeftStatusbarItems = useMemo( () => [ { className: `w-7 justify-center px-0${commandCenterOpen ? ' bg-accent/55 text-foreground' : ''}`, icon: , id: 'command-center', onSelect: toggleCommandCenter, title: commandCenterOpen ? 'Close Command Center' : 'Open Command Center', variant: 'action' }, { className: gatewayClassName, detail: gatewayDetail, icon: inferenceReady ? : , id: 'gateway-health', label: 'Gateway', menuClassName: 'w-72', menuContent: gatewayMenuContent, title: inferenceStatus?.reason || 'Hermes inference gateway status', variant: 'menu' }, { className: cn( agentsOpen && 'bg-accent/55 text-foreground', bgFailed > 0 && 'text-destructive hover:text-destructive' ), detail: subagentsRunning > 0 ? `${subagentsRunning} subagent${subagentsRunning === 1 ? '' : 's'}` : bgFailed > 0 ? `${bgFailed} failed` : bgRunning > 0 ? `${bgRunning} running` : undefined, icon: bgFailed > 0 ? ( ) : bgRunning > 0 || subagentsRunning > 0 ? ( ) : ( ), id: 'agents', label: 'Agents', onSelect: openAgents, title: agentsOpen ? 'Close agents' : 'Open agents', variant: 'action' }, { icon: , id: 'cron', label: 'Cron', title: 'Open cron jobs', to: CRON_ROUTE, variant: 'action' } ], [ agentsOpen, bgFailed, bgRunning, commandCenterOpen, gatewayMenuContent, gatewayClassName, gatewayDetail, inferenceReady, inferenceStatus?.reason, openAgents, subagentsRunning, toggleCommandCenter ] ) const coreRightStatusbarItems = useMemo( () => [ { detail: , hidden: !busy || !turnStartedAt, icon: , id: 'running-timer', label: 'Running', title: 'Current turn elapsed', variant: 'text' }, { detail: contextBar || undefined, hidden: !contextUsage, id: 'context-usage', label: contextUsage, title: 'Context usage', variant: 'text' }, { detail: , hidden: !sessionStartedAt, id: 'session-timer', label: 'Session', title: 'Runtime session elapsed', variant: 'text' }, { className: cn('px-1', yoloActive && 'bg-(--chrome-action-hover)'), hidden: !showYoloToggle, icon: yoloActive ? ( ) : ( ), id: 'yolo', onSelect: () => void toggleYolo(), title: yoloActive ? 'YOLO on — auto-approving dangerous commands. Click to turn off.' : 'YOLO off — click to auto-approve dangerous commands.', variant: 'action' }, { id: 'model-summary', label: ( {formatModelStatusLabel(currentModel, { fastMode: currentFastMode, reasoningEffort: currentReasoningEffort })} ), ...(modelMenuContent ? { menuAlign: 'end' as const, menuClassName: 'w-64', menuContent: modelMenuContent, title: currentProvider ? `Model · ${currentProvider}: ${currentModel || 'none'}` : 'Switch model', variant: 'menu' as const } : { onSelect: () => setModelPickerOpen(true), title: currentProvider ? `${currentProvider} · ${currentModel || 'no model'}` : 'Open model picker', variant: 'action' as const }) }, versionItem ], [ busy, contextBar, contextUsage, currentFastMode, currentModel, currentProvider, currentReasoningEffort, modelMenuContent, sessionStartedAt, showYoloToggle, toggleYolo, turnStartedAt, versionItem, yoloActive ] ) const leftStatusbarItems = useMemo( () => [...coreLeftStatusbarItems, ...extraLeftItems], [coreLeftStatusbarItems, extraLeftItems] ) const statusbarItems = useMemo( () => [...extraRightItems, ...coreRightStatusbarItems], [coreRightStatusbarItems, extraRightItems] ) return { leftStatusbarItems, statusbarItems } }