Fix loading state bugs and server crash
Backend fixes: - Fix server crash when logging large session_state messages (186 agents) - Use safe logging that extracts only message/stack properties - Prevents TypeError in util.inspect on circular refs or deep objects Frontend loading state fixes: - Add reactive WebSocket update to prevent stale session.ws references - Add timeout/retry wrapper for session_state loading (3 retries × 15s) - Add hasEverReceivedSessionState flag to track cached data across reconnects - Implement data-aware loading (isInitialLoad vs isRevalidating) - Show reconnecting badge instead of hiding agents on reconnect Debug improvements: - Add detailed connection state logging in useAggregatedAgents - Add connection status logging in AgentInputArea - Track connecting reasons for easier debugging Fixes: - Agent screen no longer shows false "Offline" toast on reconnect - Home screen shows loading immediately (no empty state flash) - Home screen keeps agents visible during reconnect with badge - Stuck loading has timeout protection with graceful degradation - Server no longer crashes when sending session_state with many agents 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
import { View, ActivityIndicator } from "react-native";
|
import { View, ActivityIndicator, Text } from "react-native";
|
||||||
import { useState, useCallback, useMemo, useRef, useEffect } from "react";
|
import { useState, useCallback, useMemo, useRef, useEffect } from "react";
|
||||||
import { useFocusEffect } from "@react-navigation/native";
|
import { useFocusEffect } from "@react-navigation/native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
@@ -16,7 +16,11 @@ import { endNavigationTiming, HOME_NAVIGATION_KEY } from "@/utils/navigation-tim
|
|||||||
export default function HomeScreen() {
|
export default function HomeScreen() {
|
||||||
const insets = useSafeAreaInsets();
|
const insets = useSafeAreaInsets();
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const { agents: aggregatedAgents, isLoading } = useAggregatedAgents();
|
const {
|
||||||
|
agents: aggregatedAgents,
|
||||||
|
isInitialLoad,
|
||||||
|
isRevalidating
|
||||||
|
} = useAggregatedAgents();
|
||||||
const [showCreateModal, setShowCreateModal] = useState(false);
|
const [showCreateModal, setShowCreateModal] = useState(false);
|
||||||
const [showImportModal, setShowImportModal] = useState(false);
|
const [showImportModal, setShowImportModal] = useState(false);
|
||||||
const [createModalMounted, setCreateModalMounted] = useState(false);
|
const [createModalMounted, setCreateModalMounted] = useState(false);
|
||||||
@@ -114,12 +118,21 @@ export default function HomeScreen() {
|
|||||||
|
|
||||||
{/* Content Area with Keyboard Animation */}
|
{/* Content Area with Keyboard Animation */}
|
||||||
<ReanimatedAnimated.View style={[styles.content, animatedKeyboardStyle]}>
|
<ReanimatedAnimated.View style={[styles.content, animatedKeyboardStyle]}>
|
||||||
{isLoading ? (
|
{isInitialLoad ? (
|
||||||
<View style={styles.loadingContainer}>
|
<View style={styles.loadingContainer}>
|
||||||
<ActivityIndicator size="large" color={theme.colors.mutedForeground} />
|
<ActivityIndicator size="large" color={theme.colors.mutedForeground} />
|
||||||
</View>
|
</View>
|
||||||
) : hasAgents ? (
|
) : hasAgents ? (
|
||||||
<AgentList agents={aggregatedAgents} />
|
<View style={{ flex: 1 }}>
|
||||||
|
<AgentList agents={aggregatedAgents} />
|
||||||
|
|
||||||
|
{isRevalidating && (
|
||||||
|
<View style={styles.revalidatingBadge}>
|
||||||
|
<ActivityIndicator size="small" color={theme.colors.mutedForeground} />
|
||||||
|
<Text style={styles.revalidatingText}>Reconnecting...</Text>
|
||||||
|
</View>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
) : (
|
) : (
|
||||||
<EmptyState
|
<EmptyState
|
||||||
onCreateAgent={handleCreateAgent}
|
onCreateAgent={handleCreateAgent}
|
||||||
@@ -159,4 +172,21 @@ const styles = StyleSheet.create((theme) => ({
|
|||||||
alignItems: "center",
|
alignItems: "center",
|
||||||
justifyContent: "center",
|
justifyContent: "center",
|
||||||
},
|
},
|
||||||
|
revalidatingBadge: {
|
||||||
|
position: "absolute",
|
||||||
|
top: 8,
|
||||||
|
right: 8,
|
||||||
|
flexDirection: "row",
|
||||||
|
alignItems: "center",
|
||||||
|
gap: 8,
|
||||||
|
paddingHorizontal: 12,
|
||||||
|
paddingVertical: 6,
|
||||||
|
backgroundColor: theme.colors.muted,
|
||||||
|
borderRadius: 16,
|
||||||
|
opacity: 0.9,
|
||||||
|
},
|
||||||
|
revalidatingText: {
|
||||||
|
fontSize: 12,
|
||||||
|
color: theme.colors.mutedForeground,
|
||||||
|
},
|
||||||
}));
|
}));
|
||||||
|
|||||||
@@ -272,6 +272,13 @@ export function AgentInputArea({ agentId, serverId }: AgentInputAreaProps) {
|
|||||||
}, [discardFailedDictation]);
|
}, [discardFailedDictation]);
|
||||||
|
|
||||||
const dictationToast = useMemo<DictationToastConfig | null>(() => {
|
const dictationToast = useMemo<DictationToastConfig | null>(() => {
|
||||||
|
console.log('[AgentInputArea] Connection status:', {
|
||||||
|
isConnected: connectionStatus.isConnected,
|
||||||
|
wsIsConnected: ws.isConnected,
|
||||||
|
wsIsConnecting: ws.isConnecting,
|
||||||
|
hasSession: !!session,
|
||||||
|
});
|
||||||
|
|
||||||
if (!connectionStatus.isConnected) {
|
if (!connectionStatus.isConnected) {
|
||||||
return {
|
return {
|
||||||
variant: "warning",
|
variant: "warning",
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import type { ReactNode } from "react";
|
|||||||
import { useDaemonRegistry, type DaemonProfile } from "./daemon-registry-context";
|
import { useDaemonRegistry, type DaemonProfile } from "./daemon-registry-context";
|
||||||
|
|
||||||
export type ConnectionState =
|
export type ConnectionState =
|
||||||
| { status: "idle"; lastError: null; lastOnlineAt: string | null; sessionReady: false }
|
| { status: "idle"; lastError: null; lastOnlineAt: string | null; sessionReady: false; hasEverReceivedSessionState: false }
|
||||||
| { status: "connecting"; lastError: null; lastOnlineAt: string | null; sessionReady: false }
|
| { status: "connecting"; lastError: null; lastOnlineAt: string | null; sessionReady: false; hasEverReceivedSessionState: boolean }
|
||||||
| { status: "online"; lastError: null; lastOnlineAt: string; sessionReady: boolean }
|
| { status: "online"; lastError: null; lastOnlineAt: string; sessionReady: boolean; hasEverReceivedSessionState: boolean }
|
||||||
| { status: "offline"; lastError: string | null; lastOnlineAt: string | null; sessionReady: false }
|
| { status: "offline"; lastError: string | null; lastOnlineAt: string | null; sessionReady: false; hasEverReceivedSessionState: boolean }
|
||||||
| { status: "error"; lastError: string; lastOnlineAt: string | null; sessionReady: false };
|
| { status: "error"; lastError: string; lastOnlineAt: string | null; sessionReady: false; hasEverReceivedSessionState: boolean };
|
||||||
|
|
||||||
export type ConnectionStatus = ConnectionState["status"];
|
export type ConnectionStatus = ConnectionState["status"];
|
||||||
|
|
||||||
@@ -36,6 +36,7 @@ function createDefaultConnectionState(): ConnectionState {
|
|||||||
lastError: null,
|
lastError: null,
|
||||||
lastOnlineAt: null,
|
lastOnlineAt: null,
|
||||||
sessionReady: false,
|
sessionReady: false,
|
||||||
|
hasEverReceivedSessionState: false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -45,20 +46,31 @@ function resolveNextConnectionState(
|
|||||||
): ConnectionState {
|
): ConnectionState {
|
||||||
switch (update.status) {
|
switch (update.status) {
|
||||||
case "idle":
|
case "idle":
|
||||||
return { status: "idle", lastError: null, lastOnlineAt: existing.lastOnlineAt, sessionReady: false };
|
return {
|
||||||
|
status: "idle",
|
||||||
|
lastError: null,
|
||||||
|
lastOnlineAt: existing.lastOnlineAt,
|
||||||
|
sessionReady: false,
|
||||||
|
hasEverReceivedSessionState: false,
|
||||||
|
};
|
||||||
case "connecting":
|
case "connecting":
|
||||||
return {
|
return {
|
||||||
status: "connecting",
|
status: "connecting",
|
||||||
lastError: null,
|
lastError: null,
|
||||||
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
|
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
|
||||||
sessionReady: false,
|
sessionReady: false,
|
||||||
|
hasEverReceivedSessionState: existing.hasEverReceivedSessionState ?? false,
|
||||||
};
|
};
|
||||||
case "online":
|
case "online":
|
||||||
|
const currentSessionReady = update.sessionReady ?? (existing.status === "online" ? existing.sessionReady : false);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "online",
|
status: "online",
|
||||||
lastError: null,
|
lastError: null,
|
||||||
lastOnlineAt: update.lastOnlineAt,
|
lastOnlineAt: update.lastOnlineAt,
|
||||||
sessionReady: update.sessionReady ?? (existing.status === "online" ? existing.sessionReady : false),
|
sessionReady: currentSessionReady,
|
||||||
|
hasEverReceivedSessionState:
|
||||||
|
currentSessionReady || existing.hasEverReceivedSessionState || false,
|
||||||
};
|
};
|
||||||
case "offline":
|
case "offline":
|
||||||
return {
|
return {
|
||||||
@@ -66,6 +78,7 @@ function resolveNextConnectionState(
|
|||||||
lastError: update.lastError ?? null,
|
lastError: update.lastError ?? null,
|
||||||
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
|
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
|
||||||
sessionReady: false,
|
sessionReady: false,
|
||||||
|
hasEverReceivedSessionState: existing.hasEverReceivedSessionState ?? false,
|
||||||
};
|
};
|
||||||
case "error":
|
case "error":
|
||||||
return {
|
return {
|
||||||
@@ -73,6 +86,7 @@ function resolveNextConnectionState(
|
|||||||
lastError: update.lastError,
|
lastError: update.lastError,
|
||||||
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
|
lastOnlineAt: update.lastOnlineAt ?? existing.lastOnlineAt,
|
||||||
sessionReady: false,
|
sessionReady: false,
|
||||||
|
hasEverReceivedSessionState: existing.hasEverReceivedSessionState ?? false,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -246,6 +246,7 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
const providerModelRequestIdsRef = useRef<Map<any, string>>(new Map());
|
const providerModelRequestIdsRef = useRef<Map<any, string>>(new Map());
|
||||||
const hasHydratedSnapshotRef = useRef(false);
|
const hasHydratedSnapshotRef = useRef(false);
|
||||||
const hasRequestedInitialSnapshotRef = useRef(false);
|
const hasRequestedInitialSnapshotRef = useRef(false);
|
||||||
|
const sessionStateTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);
|
||||||
|
|
||||||
// Buffer for streaming audio chunks
|
// Buffer for streaming audio chunks
|
||||||
interface AudioChunk {
|
interface AudioChunk {
|
||||||
@@ -261,6 +262,11 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
initializeSession(serverId, ws, audioPlayer);
|
initializeSession(serverId, ws, audioPlayer);
|
||||||
}, [serverId, ws, audioPlayer, initializeSession]);
|
}, [serverId, ws, audioPlayer, initializeSession]);
|
||||||
|
|
||||||
|
const updateSessionWebSocket = useSessionStore((state) => state.updateSessionWebSocket);
|
||||||
|
useEffect(() => {
|
||||||
|
updateSessionWebSocket(serverId, ws);
|
||||||
|
}, [serverId, ws, updateSessionWebSocket]);
|
||||||
|
|
||||||
// Connection status tracking
|
// Connection status tracking
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (ws.isConnected) {
|
if (ws.isConnected) {
|
||||||
@@ -496,14 +502,62 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
hasRequestedInitialSnapshotRef.current = true;
|
hasRequestedInitialSnapshotRef.current = true;
|
||||||
ws.send({
|
|
||||||
type: "session",
|
const MAX_RETRIES = 3;
|
||||||
message: {
|
const TIMEOUT_MS = 15000;
|
||||||
type: "load_conversation_request",
|
const RETRY_DELAY_MS = 2000;
|
||||||
conversationId: ws.conversationId ?? "",
|
|
||||||
},
|
let retryCount = 0;
|
||||||
});
|
|
||||||
}, [wsIsConnected, ws]);
|
const requestSessionState = () => {
|
||||||
|
console.log(`[Session] Requesting session_state (attempt ${retryCount + 1}/${MAX_RETRIES + 1})`, { serverId });
|
||||||
|
|
||||||
|
ws.send({
|
||||||
|
type: "session",
|
||||||
|
message: {
|
||||||
|
type: "load_conversation_request",
|
||||||
|
conversationId: ws.conversationId ?? "",
|
||||||
|
},
|
||||||
|
});
|
||||||
|
|
||||||
|
if (sessionStateTimeoutRef.current) {
|
||||||
|
clearTimeout(sessionStateTimeoutRef.current);
|
||||||
|
}
|
||||||
|
|
||||||
|
sessionStateTimeoutRef.current = setTimeout(() => {
|
||||||
|
if (retryCount < MAX_RETRIES) {
|
||||||
|
retryCount++;
|
||||||
|
console.warn(`[Session] session_state timeout, retrying in ${RETRY_DELAY_MS}ms`, {
|
||||||
|
serverId,
|
||||||
|
attempt: retryCount,
|
||||||
|
maxRetries: MAX_RETRIES,
|
||||||
|
});
|
||||||
|
|
||||||
|
setTimeout(() => {
|
||||||
|
requestSessionState();
|
||||||
|
}, RETRY_DELAY_MS);
|
||||||
|
} else {
|
||||||
|
console.error(`[Session] session_state failed after ${MAX_RETRIES} retries`, { serverId });
|
||||||
|
|
||||||
|
setHasHydratedAgents(serverId, true);
|
||||||
|
updateConnectionStatus(serverId, {
|
||||||
|
status: "online",
|
||||||
|
lastOnlineAt: new Date().toISOString(),
|
||||||
|
sessionReady: true
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}, TIMEOUT_MS);
|
||||||
|
};
|
||||||
|
|
||||||
|
requestSessionState();
|
||||||
|
|
||||||
|
return () => {
|
||||||
|
if (sessionStateTimeoutRef.current) {
|
||||||
|
clearTimeout(sessionStateTimeoutRef.current);
|
||||||
|
sessionStateTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
}, [wsIsConnected, ws, serverId, setHasHydratedAgents, updateConnectionStatus]);
|
||||||
|
|
||||||
// WebSocket message handlers - directly update Zustand store
|
// WebSocket message handlers - directly update Zustand store
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
@@ -511,6 +565,12 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
|
|
||||||
const unsubSessionState = ws.on("session_state", (message) => {
|
const unsubSessionState = ws.on("session_state", (message) => {
|
||||||
if (message.type !== "session_state") return;
|
if (message.type !== "session_state") return;
|
||||||
|
|
||||||
|
if (sessionStateTimeoutRef.current) {
|
||||||
|
clearTimeout(sessionStateTimeoutRef.current);
|
||||||
|
sessionStateTimeoutRef.current = null;
|
||||||
|
}
|
||||||
|
|
||||||
const { agents: agentsList, commands: commandsList } = message.payload;
|
const { agents: agentsList, commands: commandsList } = message.payload;
|
||||||
|
|
||||||
console.log("[Session] ✅ Received session_state:", agentsList.length, "agents,", commandsList.length, "commands");
|
console.log("[Session] ✅ Received session_state:", agentsList.length, "agents,", commandsList.length, "commands");
|
||||||
|
|||||||
@@ -11,6 +11,8 @@ export interface AggregatedAgent extends AgentDirectoryEntry {
|
|||||||
export interface AggregatedAgentsResult {
|
export interface AggregatedAgentsResult {
|
||||||
agents: AggregatedAgent[];
|
agents: AggregatedAgent[];
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
|
isInitialLoad: boolean;
|
||||||
|
isRevalidating: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAggregatedAgents(): AggregatedAgentsResult {
|
export function useAggregatedAgents(): AggregatedAgentsResult {
|
||||||
@@ -55,17 +57,71 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
|
|||||||
return rightTime - leftTime;
|
return rightTime - leftTime;
|
||||||
});
|
});
|
||||||
|
|
||||||
const isLoading = Array.from(connectionStates.values()).some(c => {
|
// Check if we have any cached data
|
||||||
// Only these states count as "loading":
|
const hasAnyData = allAgents.length > 0;
|
||||||
// 1. Actively connecting
|
|
||||||
// 2. Online but session not yet received
|
// Check if any connection is currently loading
|
||||||
if (c.status === 'connecting') return true;
|
const connectingReasons: string[] = [];
|
||||||
if (c.status === 'online' && !c.sessionReady) return true;
|
const isConnecting = Array.from(connectionStates.entries()).some(([id, c]) => {
|
||||||
|
const shortId = id.substring(0, 20);
|
||||||
|
|
||||||
|
// First-time connection (never received session state)
|
||||||
|
if (c.status === 'connecting' && !c.hasEverReceivedSessionState) {
|
||||||
|
connectingReasons.push(`${shortId}: first-time connecting`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (c.status === 'online' && !c.hasEverReceivedSessionState) {
|
||||||
|
connectingReasons.push(`${shortId}: online but no session_state yet`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Reconnecting (have received session state before)
|
||||||
|
if (c.status === 'connecting' && c.hasEverReceivedSessionState) {
|
||||||
|
connectingReasons.push(`${shortId}: reconnecting`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (c.status === 'online' && !c.sessionReady && c.hasEverReceivedSessionState) {
|
||||||
|
connectingReasons.push(`${shortId}: online but sessionReady=false (waiting for session_state)`);
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
// Offline/error = not loading, just unavailable (normal state)
|
|
||||||
return false;
|
return false;
|
||||||
});
|
});
|
||||||
|
|
||||||
return { agents: allAgents, isLoading };
|
// isInitialLoad: Loading for the first time (no cached data)
|
||||||
|
const isInitialLoad = isConnecting && !hasAnyData;
|
||||||
|
|
||||||
|
// isRevalidating: Loading but we have cached data (reconnecting)
|
||||||
|
const isRevalidating = isConnecting && hasAnyData;
|
||||||
|
|
||||||
|
// isLoading: Generic loading flag (either initial or revalidating)
|
||||||
|
const isLoading = isConnecting;
|
||||||
|
|
||||||
|
const connectionStatesArray = Array.from(connectionStates.entries()).map(([id, state]) => ({
|
||||||
|
id: id.substring(0, 20) + (id.length > 20 ? '...' : ''),
|
||||||
|
status: state.status,
|
||||||
|
sessionReady: state.sessionReady,
|
||||||
|
hasEverReceivedSessionState: state.hasEverReceivedSessionState,
|
||||||
|
}));
|
||||||
|
|
||||||
|
console.log('[useAggregatedAgents] States:', {
|
||||||
|
hasAnyData,
|
||||||
|
isConnecting,
|
||||||
|
isInitialLoad,
|
||||||
|
isRevalidating,
|
||||||
|
totalConnectionStates: connectionStates.size,
|
||||||
|
connectingReasons: connectingReasons.length > 0 ? connectingReasons : 'none',
|
||||||
|
});
|
||||||
|
|
||||||
|
console.log('[useAggregatedAgents] Connection States Detail:',
|
||||||
|
JSON.stringify(connectionStatesArray, null, 2)
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
agents: allAgents,
|
||||||
|
isLoading,
|
||||||
|
isInitialLoad,
|
||||||
|
isRevalidating,
|
||||||
|
};
|
||||||
}, [sessions, connectionStates]);
|
}, [sessions, connectionStates]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -247,6 +247,7 @@ interface SessionStoreActions {
|
|||||||
initializeSession: (serverId: string, ws: UseWebSocketReturn, audioPlayer: ReturnType<typeof useAudioPlayer>) => void;
|
initializeSession: (serverId: string, ws: UseWebSocketReturn, audioPlayer: ReturnType<typeof useAudioPlayer>) => void;
|
||||||
clearSession: (serverId: string) => void;
|
clearSession: (serverId: string) => void;
|
||||||
getSession: (serverId: string) => SessionState | undefined;
|
getSession: (serverId: string) => SessionState | undefined;
|
||||||
|
updateSessionWebSocket: (serverId: string, ws: UseWebSocketReturn) => void;
|
||||||
|
|
||||||
// Audio state
|
// Audio state
|
||||||
setIsPlayingAudio: (serverId: string, playing: boolean) => void;
|
setIsPlayingAudio: (serverId: string, playing: boolean) => void;
|
||||||
@@ -383,6 +384,37 @@ export const useSessionStore = create<SessionStore>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
|
updateSessionWebSocket: (serverId, ws) => {
|
||||||
|
set((prev) => {
|
||||||
|
const session = prev.sessions[serverId];
|
||||||
|
|
||||||
|
if (!session) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (session.ws === ws) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
|
||||||
|
logSessionStoreUpdate("updateSessionWebSocket", serverId, {
|
||||||
|
wasNull: session.ws === null,
|
||||||
|
isNowConnected: ws.isConnected,
|
||||||
|
isNowConnecting: ws.isConnecting,
|
||||||
|
});
|
||||||
|
|
||||||
|
return {
|
||||||
|
...prev,
|
||||||
|
sessions: {
|
||||||
|
...prev.sessions,
|
||||||
|
[serverId]: {
|
||||||
|
...session,
|
||||||
|
ws,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
};
|
||||||
|
});
|
||||||
|
},
|
||||||
|
|
||||||
getSession: (serverId) => {
|
getSession: (serverId) => {
|
||||||
return get().sessions[serverId];
|
return get().sessions[serverId];
|
||||||
},
|
},
|
||||||
|
|||||||
@@ -135,7 +135,10 @@ export class VoiceAssistantWebSocketServer {
|
|||||||
|
|
||||||
// Set up error handler
|
// Set up error handler
|
||||||
ws.on("error", async (error) => {
|
ws.on("error", async (error) => {
|
||||||
console.error(`[WS] Client error:`, error);
|
// Safe error logging
|
||||||
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
const errorStack = error instanceof Error ? error.stack : undefined;
|
||||||
|
console.error(`[WS] Client error:`, { message: errorMessage, stack: errorStack });
|
||||||
const session = this.sessions.get(ws);
|
const session = this.sessions.get(ws);
|
||||||
if (!session) return;
|
if (!session) return;
|
||||||
|
|
||||||
@@ -162,7 +165,14 @@ export class VoiceAssistantWebSocketServer {
|
|||||||
// Validate with Zod
|
// Validate with Zod
|
||||||
const message = WSInboundMessageSchema.parse(parsed);
|
const message = WSInboundMessageSchema.parse(parsed);
|
||||||
|
|
||||||
console.log(`[WS] Received message type: ${message.type}`, message);
|
// Safe logging that handles large objects without crashing
|
||||||
|
const messageSummary = {
|
||||||
|
type: message.type,
|
||||||
|
...(message.type === "session" && message.message ? {
|
||||||
|
sessionMessageType: message.message.type,
|
||||||
|
} : {}),
|
||||||
|
};
|
||||||
|
console.log(`[WS] Received message:`, messageSummary);
|
||||||
|
|
||||||
// Handle WebSocket-level messages
|
// Handle WebSocket-level messages
|
||||||
switch (message.type) {
|
switch (message.type) {
|
||||||
@@ -198,8 +208,13 @@ export class VoiceAssistantWebSocketServer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error("[WS] Failed to parse/handle message:", error);
|
// Safe error logging - avoid crashing on large/circular objects
|
||||||
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
const errorMessage = error instanceof Error ? error.message : "Unknown error";
|
||||||
|
const errorStack = error instanceof Error ? error.stack : undefined;
|
||||||
|
console.error("[WS] Failed to parse/handle message:", {
|
||||||
|
message: errorMessage,
|
||||||
|
stack: errorStack,
|
||||||
|
});
|
||||||
// Send error to client
|
// Send error to client
|
||||||
this.sendToClient(
|
this.sendToClient(
|
||||||
ws,
|
ws,
|
||||||
|
|||||||
Reference in New Issue
Block a user