mirror of
https://github.com/Ark0N/Codeman.git
synced 2026-10-05 06:59:42 +02:00
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:
+2
-1
@@ -45,6 +45,7 @@ export function App(): React.ReactElement {
|
|||||||
terminalOutput,
|
terminalOutput,
|
||||||
innerLoopState,
|
innerLoopState,
|
||||||
innerTodos,
|
innerTodos,
|
||||||
|
respawnStatus,
|
||||||
} = useSessionManager();
|
} = useSessionManager();
|
||||||
|
|
||||||
// Calculate terminal height based on stdout dimensions
|
// 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>
|
</Box>
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,16 @@ import React from 'react';
|
|||||||
import { Box, Text } from 'ink';
|
import { Box, Text } from 'ink';
|
||||||
import type { ScreenSession } from '../../types.js';
|
import type { ScreenSession } from '../../types.js';
|
||||||
|
|
||||||
|
interface RespawnStatus {
|
||||||
|
enabled: boolean;
|
||||||
|
state: string;
|
||||||
|
cycleCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface StatusBarProps {
|
interface StatusBarProps {
|
||||||
session: ScreenSession | null;
|
session: ScreenSession | null;
|
||||||
inputMode?: boolean;
|
inputMode?: boolean;
|
||||||
|
respawnStatus?: RespawnStatus | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -38,7 +45,7 @@ function formatDuration(ms: number): string {
|
|||||||
/**
|
/**
|
||||||
* StatusBar component showing session information
|
* 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) {
|
if (!session) {
|
||||||
return (
|
return (
|
||||||
<Box
|
<Box
|
||||||
@@ -88,6 +95,15 @@ export function StatusBar({ session, inputMode = false }: StatusBarProps): React
|
|||||||
<Text color="yellow" bold>INPUT MODE</Text>
|
<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>
|
</Box>
|
||||||
|
|
||||||
{/* Right side: Keyboard hints */}
|
{/* Right side: Keyboard hints */}
|
||||||
|
|||||||
@@ -18,6 +18,12 @@ const SCREENS_FILE = join(homedir(), '.claudeman', 'screens.json');
|
|||||||
const INNER_STATE_FILE = join(homedir(), '.claudeman', 'state-inner.json');
|
const INNER_STATE_FILE = join(homedir(), '.claudeman', 'state-inner.json');
|
||||||
const OUTPUT_POLL_INTERVAL = 500; // Poll terminal output every 500ms
|
const OUTPUT_POLL_INTERVAL = 500; // Poll terminal output every 500ms
|
||||||
|
|
||||||
|
interface RespawnStatus {
|
||||||
|
enabled: boolean;
|
||||||
|
state: string;
|
||||||
|
cycleCount: number;
|
||||||
|
}
|
||||||
|
|
||||||
interface SessionManagerState {
|
interface SessionManagerState {
|
||||||
sessions: ScreenSession[];
|
sessions: ScreenSession[];
|
||||||
activeSessionId: string | null;
|
activeSessionId: string | null;
|
||||||
@@ -25,6 +31,7 @@ interface SessionManagerState {
|
|||||||
terminalOutput: string;
|
terminalOutput: string;
|
||||||
innerLoopState: InnerLoopState | null;
|
innerLoopState: InnerLoopState | null;
|
||||||
innerTodos: InnerTodoItem[];
|
innerTodos: InnerTodoItem[];
|
||||||
|
respawnStatus: RespawnStatus | null;
|
||||||
refreshSessions: () => void;
|
refreshSessions: () => void;
|
||||||
selectSession: (sessionId: string) => void;
|
selectSession: (sessionId: string) => void;
|
||||||
createSession: () => Promise<string | null>;
|
createSession: () => Promise<string | null>;
|
||||||
@@ -93,6 +100,7 @@ export function useSessionManager(): SessionManagerState {
|
|||||||
const [terminalOutput, setTerminalOutput] = useState<string>('');
|
const [terminalOutput, setTerminalOutput] = useState<string>('');
|
||||||
const [innerLoopState, setInnerLoopState] = useState<InnerLoopState | null>(null);
|
const [innerLoopState, setInnerLoopState] = useState<InnerLoopState | null>(null);
|
||||||
const [innerTodos, setInnerTodos] = useState<InnerTodoItem[]>([]);
|
const [innerTodos, setInnerTodos] = useState<InnerTodoItem[]>([]);
|
||||||
|
const [respawnStatus, setRespawnStatus] = useState<RespawnStatus | null>(null);
|
||||||
const outputBufferRef = useRef<string>('');
|
const outputBufferRef = useRef<string>('');
|
||||||
|
|
||||||
// Load sessions on mount and watch for changes
|
// Load sessions on mount and watch for changes
|
||||||
@@ -153,6 +161,48 @@ export function useSessionManager(): SessionManagerState {
|
|||||||
};
|
};
|
||||||
}, [activeSessionId]);
|
}, [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
|
// Poll terminal output for active session
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (!activeSessionId || !activeSession) return;
|
if (!activeSessionId || !activeSession) return;
|
||||||
@@ -335,6 +385,7 @@ export function useSessionManager(): SessionManagerState {
|
|||||||
terminalOutput,
|
terminalOutput,
|
||||||
innerLoopState,
|
innerLoopState,
|
||||||
innerTodos,
|
innerTodos,
|
||||||
|
respawnStatus,
|
||||||
refreshSessions,
|
refreshSessions,
|
||||||
selectSession,
|
selectSession,
|
||||||
createSession,
|
createSession,
|
||||||
|
|||||||
Reference in New Issue
Block a user