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 <noreply@anthropic.com>
This commit is contained in:
arkon
2026-01-21 06:38:38 +01:00
co-authored by Claude Opus 4.5
parent 9c995d204c
commit bd3bfced96
3 changed files with 70 additions and 2 deletions
+2 -1
View File
@@ -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 {
/>
)}
<StatusBar session={activeSession} />
<StatusBar session={activeSession} respawnStatus={respawnStatus} />
</Box>
);
}
+17 -1
View File
@@ -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 (
<Box
@@ -88,6 +95,15 @@ export function StatusBar({ session, inputMode = false }: StatusBarProps): React
<Text color="yellow" bold>INPUT MODE</Text>
</>
)}
{respawnStatus?.enabled && (
<>
<Text> | </Text>
<Text color="magenta">
respawn: {respawnStatus.state.replace(/_/g, ' ')}
{respawnStatus.cycleCount > 0 && ` (${respawnStatus.cycleCount})`}
</Text>
</>
)}
</Box>
{/* Right side: Keyboard hints */}
+51
View File
@@ -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<string | null>;
@@ -93,6 +100,7 @@ export function useSessionManager(): SessionManagerState {
const [terminalOutput, setTerminalOutput] = useState<string>('');
const [innerLoopState, setInnerLoopState] = useState<InnerLoopState | null>(null);
const [innerTodos, setInnerTodos] = useState<InnerTodoItem[]>([]);
const [respawnStatus, setRespawnStatus] = useState<RespawnStatus | null>(null);
const outputBufferRef = useRef<string>('');
// 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,