diff --git a/src/tui/App.tsx b/src/tui/App.tsx
index 48b7a0e3..16983451 100644
--- a/src/tui/App.tsx
+++ b/src/tui/App.tsx
@@ -14,6 +14,7 @@ import { TabBar } from './components/TabBar.js';
import { TerminalView } from './components/TerminalView.js';
import { StatusBar } from './components/StatusBar.js';
import { HelpOverlay } from './components/HelpOverlay.js';
+import { RalphPanel } from './components/RalphPanel.js';
import { useSessionManager } from './hooks/useSessionManager.js';
import type { ScreenSession } from '../types.js';
@@ -42,6 +43,8 @@ export function App(): React.ReactElement {
prevSession,
sendInput,
terminalOutput,
+ innerLoopState,
+ innerTodos,
} = useSessionManager();
// Calculate terminal height based on stdout dimensions
@@ -224,7 +227,13 @@ export function App(): React.ReactElement {
);
}
- // Render main view with tabs, terminal, and status bar
+ // Check if Ralph panel should be visible (enabled and has data)
+ const showRalphPanel = innerLoopState?.enabled && (innerLoopState.active || innerTodos.length > 0);
+
+ // Adjust terminal height if Ralph panel is shown
+ const adjustedTerminalHeight = showRalphPanel ? terminalHeight - 6 : terminalHeight;
+
+ // Render main view with tabs, terminal, ralph panel, and status bar
return (
+ {showRalphPanel && (
+
+ )}
+
);
diff --git a/src/tui/hooks/useSessionManager.ts b/src/tui/hooks/useSessionManager.ts
index b5b104fe..12600048 100644
--- a/src/tui/hooks/useSessionManager.ts
+++ b/src/tui/hooks/useSessionManager.ts
@@ -12,9 +12,10 @@ import { existsSync, readFileSync, writeFileSync, watchFile, unwatchFile, unlink
import { homedir } from 'os';
import { join } from 'path';
import { execSync } from 'child_process';
-import type { ScreenSession } from '../../types.js';
+import type { ScreenSession, InnerLoopState, InnerTodoItem, InnerSessionState } from '../../types.js';
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 SessionManagerState {
@@ -22,6 +23,8 @@ interface SessionManagerState {
activeSessionId: string | null;
activeSession: ScreenSession | null;
terminalOutput: string;
+ innerLoopState: InnerLoopState | null;
+ innerTodos: InnerTodoItem[];
refreshSessions: () => void;
selectSession: (sessionId: string) => void;
createSession: () => Promise;
@@ -65,6 +68,22 @@ function loadSessions(): ScreenSession[] {
}
}
+/**
+ * Load inner state for a session
+ */
+function loadInnerState(sessionId: string): InnerSessionState | null {
+ try {
+ if (!existsSync(INNER_STATE_FILE)) {
+ return null;
+ }
+ const data = readFileSync(INNER_STATE_FILE, 'utf-8');
+ const allStates = JSON.parse(data) as Record;
+ return allStates[sessionId] || null;
+ } catch {
+ return null;
+ }
+}
+
/**
* Hook for managing sessions in the TUI
*/
@@ -72,6 +91,8 @@ export function useSessionManager(): SessionManagerState {
const [sessions, setSessions] = useState([]);
const [activeSessionId, setActiveSessionId] = useState(null);
const [terminalOutput, setTerminalOutput] = useState('');
+ const [innerLoopState, setInnerLoopState] = useState(null);
+ const [innerTodos, setInnerTodos] = useState([]);
const outputBufferRef = useRef('');
// Load sessions on mount and watch for changes
@@ -102,6 +123,36 @@ export function useSessionManager(): SessionManagerState {
// Get active session
const activeSession = sessions.find((s) => s.sessionId === activeSessionId) || null;
+ // Poll inner state for active session
+ useEffect(() => {
+ if (!activeSessionId) {
+ setInnerLoopState(null);
+ setInnerTodos([]);
+ return;
+ }
+
+ const pollInnerState = () => {
+ const state = loadInnerState(activeSessionId);
+ if (state) {
+ setInnerLoopState(state.loop);
+ setInnerTodos(state.todos);
+ } else {
+ setInnerLoopState(null);
+ setInnerTodos([]);
+ }
+ };
+
+ // Initial poll
+ pollInnerState();
+
+ // Set up polling interval (same as output polling)
+ const intervalId = setInterval(pollInnerState, OUTPUT_POLL_INTERVAL);
+
+ return () => {
+ clearInterval(intervalId);
+ };
+ }, [activeSessionId]);
+
// Poll terminal output for active session
useEffect(() => {
if (!activeSessionId || !activeSession) return;
@@ -282,6 +333,8 @@ export function useSessionManager(): SessionManagerState {
activeSessionId,
activeSession,
terminalOutput,
+ innerLoopState,
+ innerTodos,
refreshSessions,
selectSession,
createSession,