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[] = [];