diff --git a/packages/app/src/app/agent/[id].tsx b/packages/app/src/app/agent/[id].tsx index 99bb33538..a9829cf06 100644 --- a/packages/app/src/app/agent/[id].tsx +++ b/packages/app/src/app/agent/[id].tsx @@ -1,4 +1,4 @@ -import { useEffect, useMemo, useState } from "react"; +import { useEffect, useMemo } from "react"; import { View, Text, ActivityIndicator } from "react-native"; import { useLocalSearchParams } from "expo-router"; import { useSafeAreaInsets } from "react-native-safe-area-context"; @@ -18,7 +18,6 @@ export default function AgentScreen() { const { id } = useLocalSearchParams<{ id: string }>(); const { agents, agentStreamState, pendingPermissions, respondToPermission, initializeAgent } = useSession(); const { registerFooterControls, unregisterFooterControls } = useFooterControls(); - const [isContentReady, setIsContentReady] = useState(false); // Keyboard animation const { height: keyboardHeight } = useReanimatedKeyboardAnimation(); @@ -38,6 +37,9 @@ export default function AgentScreen() { Array.from(pendingPermissions.entries()).filter(([_, perm]) => perm.agentId === id) ); + // Agent is initializing if we don't have stream state yet + const isInitializing = id ? !agentStreamState.has(id) : false; + useEffect(() => { if (!id) { return; @@ -50,39 +52,8 @@ export default function AgentScreen() { return ; }, [id]); - // Defer heavy rendering to next frame for faster initial paint useEffect(() => { - let isMounted = true; - let frameId: number | null = null; - let timeoutId: ReturnType | null = null; - - const markReady = () => { - if (isMounted) { - setIsContentReady(true); - } - }; - - if (typeof requestAnimationFrame === "function") { - frameId = requestAnimationFrame(markReady); - } else { - timeoutId = setTimeout(markReady, 0); - } - - return () => { - isMounted = false; - - if (frameId !== null && typeof cancelAnimationFrame === "function") { - cancelAnimationFrame(frameId); - } - - if (timeoutId !== null) { - clearTimeout(timeoutId); - } - }; - }, []); - - useEffect(() => { - if (!agentControls || !agent || !isContentReady) { + if (!agentControls || !agent || isInitializing) { unregisterFooterControls(); return; } @@ -92,7 +63,7 @@ export default function AgentScreen() { return () => { unregisterFooterControls(); }; - }, [agentControls, agent, isContentReady, registerFooterControls, unregisterFooterControls]); + }, [agentControls, agent, isInitializing, registerFooterControls, unregisterFooterControls]); if (!agent) { return ( @@ -112,7 +83,12 @@ export default function AgentScreen() { {/* Content Area with Keyboard Animation */} - {isContentReady ? ( + {isInitializing ? ( + + + Loading agent... + + ) : ( <> - ) : ( - - - )} @@ -154,6 +126,11 @@ const styles = StyleSheet.create((theme) => ({ flex: 1, alignItems: "center", justifyContent: "center", + gap: 16, + }, + loadingText: { + fontSize: theme.fontSize.base, + color: theme.colors.mutedForeground, }, errorContainer: { flex: 1, diff --git a/packages/app/src/hooks/use-websocket.ts b/packages/app/src/hooks/use-websocket.ts index 29a5157cb..7ce7f7281 100644 --- a/packages/app/src/hooks/use-websocket.ts +++ b/packages/app/src/hooks/use-websocket.ts @@ -1,4 +1,4 @@ -import { useEffect, useRef, useState, useCallback } from 'react'; +import { useEffect, useRef, useState, useCallback, useMemo } from 'react'; import type { WSInboundMessage, WSOutboundMessage, @@ -150,12 +150,15 @@ export function useWebSocket(url: string, conversationId?: string | null): UseWe [send] ); - return { - isConnected, - conversationId: currentConversationId, - send, - on, - sendPing, - sendUserMessage, - }; + return useMemo( + () => ({ + isConnected, + conversationId: currentConversationId, + send, + on, + sendPing, + sendUserMessage, + }), + [isConnected, currentConversationId, send, on, sendPing, sendUserMessage] + ); } diff --git a/packages/server/agents.json b/packages/server/agents.json index aea373ff6..45c0b05f6 100644 --- a/packages/server/agents.json +++ b/packages/server/agents.json @@ -53,5 +53,16 @@ }, "createdAt": "2025-10-25T12:44:52.183Z", "cwd": "/Users/moboudra/dev/faro/main" + }, + { + "id": "415c2eb6-6649-4f3f-aee5-acc3d6b2cfd4", + "title": "Agent 415c2eb6", + "sessionId": "019a1c5a-0929-76de-ad58-1b80fa056209", + "options": { + "type": "claude", + "sessionId": null + }, + "createdAt": "2025-10-25T17:10:52.912Z", + "cwd": "/Users/moboudra/dev/faro/main" } ] \ No newline at end of file diff --git a/packages/server/package.json b/packages/server/package.json index cb98ec714..6ece976c8 100644 --- a/packages/server/package.json +++ b/packages/server/package.json @@ -18,7 +18,7 @@ "dependencies": { "@agentclientprotocol/sdk": "^0.4.9", "@ai-sdk/openai": "^2.0.52", - "@boudra/claude-code-acp": "^0.8.2", + "@boudra/claude-code-acp": "^0.8.3", "@deepgram/sdk": "^3.4.0", "@modelcontextprotocol/sdk": "^1.20.1", "@openrouter/ai-sdk-provider": "^1.2.0", diff --git a/packages/server/src/server/session.ts b/packages/server/src/server/session.ts index cbaec27fe..bd78ddf81 100644 --- a/packages/server/src/server/session.ts +++ b/packages/server/src/server/session.ts @@ -923,12 +923,7 @@ export class Session { private async sendSessionState(): Promise { try { // Get live agents with session modes - const agents = this.agentManager?.listAgents() || []; - - // Subscribe to all existing agents so future updates stream through - for (const agent of agents) { - this.subscribeToAgent(agent.id); - } + const agents = this.agentManager.listAgents(); // Get live commands from terminal manager let commands: any[] = [];