From bd3bfced96d8a459a7d7dcfae45e1ddba09fdfa9 Mon Sep 17 00:00:00 2001 From: arkon Date: Wed, 21 Jan 2026 06:38:38 +0100 Subject: [PATCH] feat(tui): add respawn status polling and display - Poll session API every 2 seconds for respawn status - Display respawn state and cycle count in StatusBar - Show respawn status in magenta to match web UI - Format state (underscores to spaces) for readability Co-Authored-By: Claude Opus 4.5 --- src/tui/App.tsx | 3 +- src/tui/components/StatusBar.tsx | 18 ++++++++++- src/tui/hooks/useSessionManager.ts | 51 ++++++++++++++++++++++++++++++ 3 files changed, 70 insertions(+), 2 deletions(-) diff --git a/src/tui/App.tsx b/src/tui/App.tsx index 16983451..3dea5f0f 100644 --- a/src/tui/App.tsx +++ b/src/tui/App.tsx @@ -45,6 +45,7 @@ export function App(): React.ReactElement { terminalOutput, innerLoopState, innerTodos, + respawnStatus, } = useSessionManager(); // Calculate terminal height based on stdout dimensions @@ -255,7 +256,7 @@ export function App(): React.ReactElement { /> )} - + ); } diff --git a/src/tui/components/StatusBar.tsx b/src/tui/components/StatusBar.tsx index fc6b187f..164c9146 100644 --- a/src/tui/components/StatusBar.tsx +++ b/src/tui/components/StatusBar.tsx @@ -13,9 +13,16 @@ import React from 'react'; import { Box, Text } from 'ink'; import type { ScreenSession } from '../../types.js'; +interface RespawnStatus { + enabled: boolean; + state: string; + cycleCount: number; +} + interface StatusBarProps { session: ScreenSession | null; inputMode?: boolean; + respawnStatus?: RespawnStatus | null; } /** @@ -38,7 +45,7 @@ function formatDuration(ms: number): string { /** * StatusBar component showing session information */ -export function StatusBar({ session, inputMode = false }: StatusBarProps): React.ReactElement { +export function StatusBar({ session, inputMode = false, respawnStatus }: StatusBarProps): React.ReactElement { if (!session) { return ( INPUT MODE )} + {respawnStatus?.enabled && ( + <> + | + + respawn: {respawnStatus.state.replace(/_/g, ' ')} + {respawnStatus.cycleCount > 0 && ` (${respawnStatus.cycleCount})`} + + + )} {/* Right side: Keyboard hints */} diff --git a/src/tui/hooks/useSessionManager.ts b/src/tui/hooks/useSessionManager.ts index 12600048..bb760f82 100644 --- a/src/tui/hooks/useSessionManager.ts +++ b/src/tui/hooks/useSessionManager.ts @@ -18,6 +18,12 @@ const SCREENS_FILE = join(homedir(), '.claudeman', 'screens.json'); const INNER_STATE_FILE = join(homedir(), '.claudeman', 'state-inner.json'); const OUTPUT_POLL_INTERVAL = 500; // Poll terminal output every 500ms +interface RespawnStatus { + enabled: boolean; + state: string; + cycleCount: number; +} + interface SessionManagerState { sessions: ScreenSession[]; activeSessionId: string | null; @@ -25,6 +31,7 @@ interface SessionManagerState { terminalOutput: string; innerLoopState: InnerLoopState | null; innerTodos: InnerTodoItem[]; + respawnStatus: RespawnStatus | null; refreshSessions: () => void; selectSession: (sessionId: string) => void; createSession: () => Promise; @@ -93,6 +100,7 @@ export function useSessionManager(): SessionManagerState { const [terminalOutput, setTerminalOutput] = useState(''); const [innerLoopState, setInnerLoopState] = useState(null); const [innerTodos, setInnerTodos] = useState([]); + const [respawnStatus, setRespawnStatus] = useState(null); const outputBufferRef = useRef(''); // Load sessions on mount and watch for changes @@ -153,6 +161,48 @@ export function useSessionManager(): SessionManagerState { }; }, [activeSessionId]); + // Poll respawn status via API (when server is running) + useEffect(() => { + if (!activeSessionId) { + setRespawnStatus(null); + return; + } + + const pollRespawnStatus = async () => { + try { + const response = await fetch(`http://localhost:3000/api/sessions/${activeSessionId}`); + if (response.ok) { + const data = await response.json() as { + success?: boolean; + session?: { + respawnEnabled?: boolean; + respawnState?: string; + respawnCycleCount?: number; + }; + }; + if (data.success && data.session) { + setRespawnStatus({ + enabled: data.session.respawnEnabled || false, + state: data.session.respawnState || 'stopped', + cycleCount: data.session.respawnCycleCount || 0, + }); + } + } + } catch { + // Server not running, that's ok + setRespawnStatus(null); + } + }; + + // Poll less frequently than output (every 2 seconds) + pollRespawnStatus(); + const intervalId = setInterval(pollRespawnStatus, 2000); + + return () => { + clearInterval(intervalId); + }; + }, [activeSessionId]); + // Poll terminal output for active session useEffect(() => { if (!activeSessionId || !activeSession) return; @@ -335,6 +385,7 @@ export function useSessionManager(): SessionManagerState { terminalOutput, innerLoopState, innerTodos, + respawnStatus, refreshSessions, selectSession, createSession,