Unify app connection state around host runtime
This commit is contained in:
@@ -18,6 +18,10 @@ import { shortenPath } from "@/utils/shorten-path";
|
|||||||
import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info";
|
import { deriveBranchLabel, deriveProjectPath } from "@/utils/agent-display-info";
|
||||||
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
import { type AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
|
import {
|
||||||
|
getHostRuntimeStore,
|
||||||
|
isHostRuntimeConnected,
|
||||||
|
} from "@/runtime/host-runtime";
|
||||||
import { AgentStatusDot } from "@/components/agent-status-dot";
|
import { AgentStatusDot } from "@/components/agent-status-dot";
|
||||||
import {
|
import {
|
||||||
CHECKOUT_STATUS_STALE_TIME,
|
CHECKOUT_STATUS_STALE_TIME,
|
||||||
@@ -154,9 +158,9 @@ export function AgentList({
|
|||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const session = useSessionStore.getState().sessions[agent.serverId];
|
const runtime = getHostRuntimeStore();
|
||||||
const client = session?.client ?? null;
|
const client = runtime.getClient(agent.serverId);
|
||||||
const isConnected = session?.connection.isConnected ?? false;
|
const isConnected = isHostRuntimeConnected(runtime.getSnapshot(agent.serverId));
|
||||||
if (!client || !isConnected) {
|
if (!client || !isConnected) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ function ManagedDaemonSession({ daemon }: { daemon: HostProfile }) {
|
|||||||
);
|
);
|
||||||
}, [daemon.serverId, hasConnectionRecord, snapshot, updateConnectionStatus]);
|
}, [daemon.serverId, hasConnectionRecord, snapshot, updateConnectionStatus]);
|
||||||
|
|
||||||
if (!snapshot?.activeConnection || !client) {
|
if (!client) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -28,7 +28,10 @@ import {
|
|||||||
DraggableList,
|
DraggableList,
|
||||||
type DraggableRenderItemInfo,
|
type DraggableRenderItemInfo,
|
||||||
} from "./draggable-list";
|
} from "./draggable-list";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import {
|
||||||
|
getHostRuntimeStore,
|
||||||
|
isHostRuntimeConnected,
|
||||||
|
} from "@/runtime/host-runtime";
|
||||||
import {
|
import {
|
||||||
buildAgentNavigationKey,
|
buildAgentNavigationKey,
|
||||||
startNavigationTiming,
|
startNavigationTiming,
|
||||||
@@ -490,7 +493,6 @@ export function SidebarAgentList({
|
|||||||
}, [projectOptions, selectedProjectKeys]);
|
}, [projectOptions, selectedProjectKeys]);
|
||||||
const showProjectFilters = projectOptions.length > 0;
|
const showProjectFilters = projectOptions.length > 0;
|
||||||
|
|
||||||
const sessions = useSessionStore((state) => state.sessions);
|
|
||||||
const projectIconRequests = useMemo(() => {
|
const projectIconRequests = useMemo(() => {
|
||||||
const unique = new Map<string, { serverId: string; cwd: string }>();
|
const unique = new Map<string, { serverId: string; cwd: string }>();
|
||||||
for (const option of projectOptions) {
|
for (const option of projectOptions) {
|
||||||
@@ -517,8 +519,7 @@ export function SidebarAgentList({
|
|||||||
queries: projectIconRequests.map((request) => ({
|
queries: projectIconRequests.map((request) => ({
|
||||||
queryKey: projectIconQueryKey(request.serverId, request.cwd),
|
queryKey: projectIconQueryKey(request.serverId, request.cwd),
|
||||||
queryFn: async () => {
|
queryFn: async () => {
|
||||||
const client =
|
const client = getHostRuntimeStore().getClient(request.serverId);
|
||||||
useSessionStore.getState().sessions[request.serverId]?.client ?? null;
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -526,8 +527,10 @@ export function SidebarAgentList({
|
|||||||
return result.icon;
|
return result.icon;
|
||||||
},
|
},
|
||||||
enabled: Boolean(
|
enabled: Boolean(
|
||||||
sessions[request.serverId]?.client &&
|
getHostRuntimeStore().getClient(request.serverId) &&
|
||||||
sessions[request.serverId]?.connection.isConnected &&
|
isHostRuntimeConnected(
|
||||||
|
getHostRuntimeStore().getSnapshot(request.serverId)
|
||||||
|
) &&
|
||||||
request.cwd
|
request.cwd
|
||||||
),
|
),
|
||||||
staleTime: Infinity,
|
staleTime: Infinity,
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ import Svg, {
|
|||||||
} from "react-native-svg";
|
} from "react-native-svg";
|
||||||
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import type { ListTerminalsResponse } from "@server/shared/messages";
|
import type { ListTerminalsResponse } from "@server/shared/messages";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import {
|
import {
|
||||||
hasPendingTerminalModifiers,
|
hasPendingTerminalModifiers,
|
||||||
normalizeTerminalTransportKey,
|
normalizeTerminalTransportKey,
|
||||||
@@ -135,10 +135,7 @@ export function TerminalPane({ serverId, cwd }: TerminalPaneProps) {
|
|||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null);
|
const { client, isConnected } = useHostRuntimeSession(serverId);
|
||||||
const isConnected = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
|
||||||
);
|
|
||||||
|
|
||||||
const scopeKey = useMemo(() => terminalScopeKey({ serverId, cwd }), [serverId, cwd]);
|
const scopeKey = useMemo(() => terminalScopeKey({ serverId, cwd }), [serverId, cwd]);
|
||||||
const terminalsQueryKey = useMemo(() => ["terminals", serverId, cwd] as const, [cwd, serverId]);
|
const terminalsQueryKey = useMemo(() => ["terminals", serverId, cwd] as const, [cwd, serverId]);
|
||||||
|
|||||||
@@ -18,7 +18,7 @@ export type ConnectionStatus = ConnectionState["status"];
|
|||||||
export type ConnectionStateUpdate =
|
export type ConnectionStateUpdate =
|
||||||
| { status: "idle" }
|
| { status: "idle" }
|
||||||
| { status: "connecting"; activeConnection?: ActiveConnection | null; lastOnlineAt?: string | null }
|
| { status: "connecting"; activeConnection?: ActiveConnection | null; lastOnlineAt?: string | null }
|
||||||
| { status: "online"; activeConnection?: ActiveConnection | null; lastOnlineAt: string; agentListReady?: boolean }
|
| { status: "online"; activeConnection?: ActiveConnection | null; lastOnlineAt: string }
|
||||||
| { status: "offline"; activeConnection?: ActiveConnection | null; lastError?: string | null; lastOnlineAt?: string | null }
|
| { status: "offline"; activeConnection?: ActiveConnection | null; lastError?: string | null; lastOnlineAt?: string | null }
|
||||||
| { status: "error"; activeConnection?: ActiveConnection | null; lastError: string; lastOnlineAt?: string | null };
|
| { status: "error"; activeConnection?: ActiveConnection | null; lastError: string; lastOnlineAt?: string | null };
|
||||||
|
|
||||||
@@ -30,6 +30,7 @@ interface DaemonConnectionsContextValue {
|
|||||||
connectionStates: Map<string, DaemonConnectionRecord>;
|
connectionStates: Map<string, DaemonConnectionRecord>;
|
||||||
isLoading: boolean;
|
isLoading: boolean;
|
||||||
updateConnectionStatus: (serverId: string, update: ConnectionStateUpdate) => void;
|
updateConnectionStatus: (serverId: string, update: ConnectionStateUpdate) => void;
|
||||||
|
markAgentListReady: (serverId: string, ready: boolean) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const DaemonConnectionsContext = createContext<DaemonConnectionsContextValue | null>(null);
|
const DaemonConnectionsContext = createContext<DaemonConnectionsContextValue | null>(null);
|
||||||
@@ -69,7 +70,8 @@ function resolveNextConnectionState(
|
|||||||
hasEverReceivedAgentList: existing.hasEverReceivedAgentList ?? false,
|
hasEverReceivedAgentList: existing.hasEverReceivedAgentList ?? false,
|
||||||
};
|
};
|
||||||
case "online":
|
case "online":
|
||||||
const currentAgentListReady = update.agentListReady ?? (existing.status === "online" ? existing.agentListReady : false);
|
const currentAgentListReady =
|
||||||
|
existing.status === "online" ? existing.agentListReady : false;
|
||||||
|
|
||||||
return {
|
return {
|
||||||
status: "online",
|
status: "online",
|
||||||
@@ -173,10 +175,37 @@ export function DaemonConnectionsProvider({ children }: { children: ReactNode })
|
|||||||
[]
|
[]
|
||||||
);
|
);
|
||||||
|
|
||||||
|
const markAgentListReady = useCallback((serverId: string, ready: boolean) => {
|
||||||
|
setConnectionStates((prev) => {
|
||||||
|
const existing = prev.get(serverId);
|
||||||
|
if (!existing) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
if (existing.status !== "online") {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
if (
|
||||||
|
existing.agentListReady === ready &&
|
||||||
|
(ready ? existing.hasEverReceivedAgentList : true)
|
||||||
|
) {
|
||||||
|
return prev;
|
||||||
|
}
|
||||||
|
const next = new Map(prev);
|
||||||
|
next.set(serverId, {
|
||||||
|
...existing,
|
||||||
|
agentListReady: ready,
|
||||||
|
hasEverReceivedAgentList:
|
||||||
|
ready || existing.hasEverReceivedAgentList,
|
||||||
|
});
|
||||||
|
return next;
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
const value: DaemonConnectionsContextValue = {
|
const value: DaemonConnectionsContextValue = {
|
||||||
connectionStates,
|
connectionStates,
|
||||||
isLoading: registryLoading,
|
isLoading: registryLoading,
|
||||||
updateConnectionStatus,
|
updateConnectionStatus,
|
||||||
|
markAgentListReady,
|
||||||
};
|
};
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -33,7 +33,6 @@ import {
|
|||||||
useSessionStore,
|
useSessionStore,
|
||||||
type Agent,
|
type Agent,
|
||||||
type SessionState,
|
type SessionState,
|
||||||
type DaemonConnectionSnapshot,
|
|
||||||
} from "@/stores/session-store";
|
} from "@/stores/session-store";
|
||||||
import { useDraftStore } from "@/stores/draft-store";
|
import { useDraftStore } from "@/stores/draft-store";
|
||||||
import type { AgentDirectoryEntry } from "@/types/agent-directory";
|
import type { AgentDirectoryEntry } from "@/types/agent-directory";
|
||||||
@@ -74,6 +73,12 @@ const derivePendingPermissionKey = (
|
|||||||
|
|
||||||
const HISTORY_STALE_AFTER_MS = 60_000;
|
const HISTORY_STALE_AFTER_MS = 60_000;
|
||||||
|
|
||||||
|
type SessionConnectionSnapshot = {
|
||||||
|
isConnected: boolean;
|
||||||
|
isConnecting: boolean;
|
||||||
|
lastError: string | null;
|
||||||
|
};
|
||||||
|
|
||||||
const findLatestAssistantMessageText = (items: StreamItem[]): string | null => {
|
const findLatestAssistantMessageText = (items: StreamItem[]): string | null => {
|
||||||
for (let i = items.length - 1; i >= 0; i -= 1) {
|
for (let i = items.length - 1; i >= 0; i -= 1) {
|
||||||
const item = items[i];
|
const item = items[i];
|
||||||
@@ -87,7 +92,7 @@ const findLatestAssistantMessageText = (items: StreamItem[]): string | null => {
|
|||||||
const mapConnectionState = (
|
const mapConnectionState = (
|
||||||
state: ConnectionState,
|
state: ConnectionState,
|
||||||
lastError: string | null
|
lastError: string | null
|
||||||
): DaemonConnectionSnapshot => ({
|
): SessionConnectionSnapshot => ({
|
||||||
isConnected: state.status === "connected",
|
isConnected: state.status === "connected",
|
||||||
isConnecting: state.status === "connecting",
|
isConnecting: state.status === "connecting",
|
||||||
lastError: state.status === "disconnected" ? state.reason ?? lastError : null,
|
lastError: state.status === "disconnected" ? state.reason ?? lastError : null,
|
||||||
@@ -264,10 +269,10 @@ function SessionProviderInternal({
|
|||||||
}: SessionProviderClientProps) {
|
}: SessionProviderClientProps) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const [connectionSnapshot, setConnectionSnapshot] =
|
const [connectionSnapshot, setConnectionSnapshot] =
|
||||||
useState<DaemonConnectionSnapshot>(() =>
|
useState<SessionConnectionSnapshot>(() =>
|
||||||
mapConnectionState(client.getConnectionState(), client.lastError)
|
mapConnectionState(client.getConnectionState(), client.lastError)
|
||||||
);
|
);
|
||||||
const { updateConnectionStatus } = useDaemonConnections();
|
const { markAgentListReady } = useDaemonConnections();
|
||||||
|
|
||||||
// Zustand store actions
|
// Zustand store actions
|
||||||
const initializeSession = useSessionStore((state) => state.initializeSession);
|
const initializeSession = useSessionStore((state) => state.initializeSession);
|
||||||
@@ -312,9 +317,6 @@ function SessionProviderInternal({
|
|||||||
const clearDraftInput = useDraftStore((state) => state.clearDraftInput);
|
const clearDraftInput = useDraftStore((state) => state.clearDraftInput);
|
||||||
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
|
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
|
||||||
const updateSessionClient = useSessionStore((state) => state.updateSessionClient);
|
const updateSessionClient = useSessionStore((state) => state.updateSessionClient);
|
||||||
const updateSessionConnection = useSessionStore(
|
|
||||||
(state) => state.updateSessionConnection
|
|
||||||
);
|
|
||||||
const updateSessionServerInfo = useSessionStore((state) => state.updateSessionServerInfo);
|
const updateSessionServerInfo = useSessionStore((state) => state.updateSessionServerInfo);
|
||||||
|
|
||||||
// Track focused agent for heartbeat
|
// Track focused agent for heartbeat
|
||||||
@@ -470,10 +472,6 @@ function SessionProviderInternal({
|
|||||||
wasConnectedRef.current = connectionSnapshot.isConnected;
|
wasConnectedRef.current = connectionSnapshot.isConnected;
|
||||||
}, [serverId, connectionSnapshot.isConnected, bumpHistorySyncGeneration]);
|
}, [serverId, connectionSnapshot.isConnected, bumpHistorySyncGeneration]);
|
||||||
|
|
||||||
useEffect(() => {
|
|
||||||
updateSessionConnection(serverId, connectionSnapshot);
|
|
||||||
}, [serverId, connectionSnapshot, updateSessionConnection]);
|
|
||||||
|
|
||||||
// Initialize session in store
|
// Initialize session in store
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
initializeSession(serverId, client, audioPlayer);
|
initializeSession(serverId, client, audioPlayer);
|
||||||
@@ -855,11 +853,7 @@ function SessionProviderInternal({
|
|||||||
setPendingPermissions(serverId, nextPendingPermissions);
|
setPendingPermissions(serverId, nextPendingPermissions);
|
||||||
setInitializingAgents(serverId, new Map());
|
setInitializingAgents(serverId, new Map());
|
||||||
setHasHydratedAgents(serverId, true);
|
setHasHydratedAgents(serverId, true);
|
||||||
updateConnectionStatus(serverId, {
|
markAgentListReady(serverId, true);
|
||||||
status: "online",
|
|
||||||
lastOnlineAt: new Date().toISOString(),
|
|
||||||
agentListReady: true,
|
|
||||||
});
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (cancelled) {
|
if (cancelled) {
|
||||||
return;
|
return;
|
||||||
@@ -885,7 +879,7 @@ function SessionProviderInternal({
|
|||||||
setHasHydratedAgents,
|
setHasHydratedAgents,
|
||||||
setInitializingAgents,
|
setInitializingAgents,
|
||||||
setPendingPermissions,
|
setPendingPermissions,
|
||||||
updateConnectionStatus,
|
markAgentListReady,
|
||||||
]);
|
]);
|
||||||
|
|
||||||
// Daemon message handlers - directly update Zustand store
|
// Daemon message handlers - directly update Zustand store
|
||||||
@@ -1420,7 +1414,6 @@ function SessionProviderInternal({
|
|||||||
setPendingPermissions,
|
setPendingPermissions,
|
||||||
setFileExplorer,
|
setFileExplorer,
|
||||||
setHasHydratedAgents,
|
setHasHydratedAgents,
|
||||||
updateConnectionStatus,
|
|
||||||
clearDraftInput,
|
clearDraftInput,
|
||||||
notifyAgentAttention,
|
notifyAgentAttention,
|
||||||
applyAgentUpdatePayload,
|
applyAgentUpdatePayload,
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { createContext, useContext, useState, ReactNode, useCallback, useEffect,
|
|||||||
import { useSpeechmaticsAudio } from "@/hooks/use-speechmatics-audio";
|
import { useSpeechmaticsAudio } from "@/hooks/use-speechmatics-audio";
|
||||||
import type { SessionState } from "@/stores/session-store";
|
import type { SessionState } from "@/stores/session-store";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import { resolveVoiceUnavailableMessage } from "@/utils/server-info-capabilities";
|
import { resolveVoiceUnavailableMessage } from "@/utils/server-info-capabilities";
|
||||||
import { activateKeepAwakeAsync, deactivateKeepAwake } from "expo-keep-awake";
|
import { activateKeepAwakeAsync, deactivateKeepAwake } from "expo-keep-awake";
|
||||||
import { REALTIME_VOICE_VAD_CONFIG } from "@/voice/realtime-voice-config";
|
import { REALTIME_VOICE_VAD_CONFIG } from "@/voice/realtime-voice-config";
|
||||||
@@ -45,6 +46,9 @@ export function VoiceProvider({ children }: VoiceProviderProps) {
|
|||||||
const getSession = useSessionStore((state) => state.getSession);
|
const getSession = useSessionStore((state) => state.getSession);
|
||||||
const [activeServerId, setActiveServerId] = useState<string | null>(null);
|
const [activeServerId, setActiveServerId] = useState<string | null>(null);
|
||||||
const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
|
const [activeAgentId, setActiveAgentId] = useState<string | null>(null);
|
||||||
|
const { client: runtimeClient, isConnected: activeRuntimeConnected } = useHostRuntimeSession(
|
||||||
|
activeServerId ?? ""
|
||||||
|
);
|
||||||
const activeSession = useSessionStore(
|
const activeSession = useSessionStore(
|
||||||
useCallback(
|
useCallback(
|
||||||
(state: ReturnType<typeof useSessionStore.getState>) => {
|
(state: ReturnType<typeof useSessionStore.getState>) => {
|
||||||
@@ -274,8 +278,8 @@ export function VoiceProvider({ children }: VoiceProviderProps) {
|
|||||||
}, [isVoiceMode, realtimeAudio.isDetecting, realtimeAudio.isSpeaking]);
|
}, [isVoiceMode, realtimeAudio.isDetecting, realtimeAudio.isSpeaking]);
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const connected = activeSession?.connection.isConnected ?? false;
|
const connected = activeRuntimeConnected;
|
||||||
const client = activeSession?.client ?? null;
|
const client = runtimeClient;
|
||||||
if (!connected) {
|
if (!connected) {
|
||||||
voiceTransportReadyRef.current = false;
|
voiceTransportReadyRef.current = false;
|
||||||
}
|
}
|
||||||
@@ -313,7 +317,7 @@ export function VoiceProvider({ children }: VoiceProviderProps) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
wasVoiceSocketConnectedRef.current = connected;
|
wasVoiceSocketConnectedRef.current = connected;
|
||||||
}, [activeAgentId, activeServerId, activeSession?.client, activeSession?.connection.isConnected, isVoiceMode]);
|
}, [activeAgentId, activeRuntimeConnected, activeServerId, isVoiceMode, runtimeClient]);
|
||||||
|
|
||||||
const isPlayingAudio = activeSession?.isPlayingAudio ?? false;
|
const isPlayingAudio = activeSession?.isPlayingAudio ?? false;
|
||||||
|
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import { useAgentCommandsQuery, type DraftCommandConfig } from './use-agent-comm
|
|||||||
import { orderAutocompleteOptions } from '@/components/ui/autocomplete-utils'
|
import { orderAutocompleteOptions } from '@/components/ui/autocomplete-utils'
|
||||||
import { useAutocomplete } from './use-autocomplete'
|
import { useAutocomplete } from './use-autocomplete'
|
||||||
import { useSessionStore } from '@/stores/session-store'
|
import { useSessionStore } from '@/stores/session-store'
|
||||||
|
import { useHostRuntimeSession } from '@/runtime/host-runtime'
|
||||||
import {
|
import {
|
||||||
applyFileMentionReplacement,
|
applyFileMentionReplacement,
|
||||||
findActiveFileMention,
|
findActiveFileMention,
|
||||||
@@ -137,10 +138,7 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut
|
|||||||
return queryDraftConfig?.cwd ?? ''
|
return queryDraftConfig?.cwd ?? ''
|
||||||
}, [agentCwd, isRealAgent, queryDraftConfig])
|
}, [agentCwd, isRealAgent, queryDraftConfig])
|
||||||
|
|
||||||
const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null)
|
const { client, isConnected } = useHostRuntimeSession(serverId)
|
||||||
const isConnected = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
|
||||||
)
|
|
||||||
|
|
||||||
const mode: 'command' | 'file' | null = showFileAutocomplete
|
const mode: 'command' | 'file' | null = showFileAutocomplete
|
||||||
? 'file'
|
? 'file'
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
|
import type { AgentProvider } from "@server/server/agent/agent-sdk-types";
|
||||||
|
|
||||||
const COMMANDS_STALE_TIME = 60_000; // Commands rarely change, cache for 1 minute
|
const COMMANDS_STALE_TIME = 60_000; // Commands rarely change, cache for 1 minute
|
||||||
@@ -48,12 +48,7 @@ export function useAgentCommandsQuery({
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
draftConfig,
|
draftConfig,
|
||||||
}: UseAgentCommandsQueryOptions) {
|
}: UseAgentCommandsQueryOptions) {
|
||||||
const client = useSessionStore(
|
const { client, isConnected } = useHostRuntimeSession(serverId);
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
|
||||||
);
|
|
||||||
const isConnected = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: commandsQueryKey(serverId, agentId, draftConfig),
|
queryKey: commandsQueryKey(serverId, agentId, draftConfig),
|
||||||
|
|||||||
@@ -9,8 +9,8 @@ import type {
|
|||||||
AgentModelDefinition,
|
AgentModelDefinition,
|
||||||
AgentProvider,
|
AgentProvider,
|
||||||
} from "@server/server/agent/agent-sdk-types";
|
} from "@server/server/agent/agent-sdk-types";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
|
||||||
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import { useFormPreferences, type FormPreferences } from "./use-form-preferences";
|
import { useFormPreferences, type FormPreferences } from "./use-form-preferences";
|
||||||
|
|
||||||
// Explicit overrides from URL params or "New Agent" button
|
// Explicit overrides from URL params or "New Agent" button
|
||||||
@@ -364,11 +364,7 @@ export function useAgentFormState(
|
|||||||
}, [isVisible]);
|
}, [isVisible]);
|
||||||
|
|
||||||
// Session state for provider model listing
|
// Session state for provider model listing
|
||||||
const sessionState = useSessionStore((state) =>
|
const { client, isConnected } = useHostRuntimeSession(formState.serverId ?? "");
|
||||||
formState.serverId ? state.sessions[formState.serverId] : undefined
|
|
||||||
);
|
|
||||||
const client = sessionState?.client ?? null;
|
|
||||||
const isConnected = sessionState?.connection?.isConnected ?? false;
|
|
||||||
|
|
||||||
const availableProvidersQuery = useQuery({
|
const availableProvidersQuery = useQuery({
|
||||||
queryKey: ["availableProviders", formState.serverId],
|
queryKey: ["availableProviders", formState.serverId],
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import type { FetchAgentTimelineOptions } from "@server/client/daemon-client";
|
import type {
|
||||||
|
DaemonClient,
|
||||||
|
FetchAgentTimelineOptions,
|
||||||
|
} from "@server/client/daemon-client";
|
||||||
import {
|
import {
|
||||||
attachInitTimeout,
|
attachInitTimeout,
|
||||||
createInitDeferred,
|
createInitDeferred,
|
||||||
@@ -37,8 +40,13 @@ function buildInitialTimelineRequest(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useAgentInitialization(serverId: string) {
|
export function useAgentInitialization({
|
||||||
const client = useSessionStore((state) => state.sessions[serverId]?.client ?? null);
|
serverId,
|
||||||
|
client,
|
||||||
|
}: {
|
||||||
|
serverId: string;
|
||||||
|
client: DaemonClient | null;
|
||||||
|
}) {
|
||||||
const setInitializingAgents = useSessionStore((state) => state.setInitializingAgents);
|
const setInitializingAgents = useSessionStore((state) => state.setInitializingAgents);
|
||||||
const setAgentInitializing = useCallback(
|
const setAgentInitializing = useCallback(
|
||||||
(agentId: string, initializing: boolean) => {
|
(agentId: string, initializing: boolean) => {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCallback, useMemo } from "react";
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||||
import { useSessionStore, type Agent } from "@/stores/session-store";
|
import { useSessionStore, type Agent } from "@/stores/session-store";
|
||||||
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import type { AggregatedAgent, AggregatedAgentsResult } from "@/hooks/use-aggregated-agents";
|
import type { AggregatedAgent, AggregatedAgentsResult } from "@/hooks/use-aggregated-agents";
|
||||||
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||||
|
|
||||||
@@ -45,8 +46,7 @@ export function useAllAgentsList(options?: {
|
|||||||
const session = useSessionStore((state) =>
|
const session = useSessionStore((state) =>
|
||||||
serverId ? state.sessions[serverId] : undefined
|
serverId ? state.sessions[serverId] : undefined
|
||||||
);
|
);
|
||||||
const client = session?.client ?? null;
|
const { client, isConnected } = useHostRuntimeSession(serverId ?? "");
|
||||||
const isConnected = session?.connection.isConnected ?? false;
|
|
||||||
const liveAgents = session?.agents ?? null;
|
const liveAgents = session?.agents ?? null;
|
||||||
const canFetch = Boolean(serverId && client && isConnected);
|
const canFetch = Boolean(serverId && client && isConnected);
|
||||||
|
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useCallback, useEffect, useId, useMemo } from "react";
|
import { useCallback, useEffect, useId, useMemo } from "react";
|
||||||
import { UnistylesRuntime } from "react-native-unistyles";
|
import { UnistylesRuntime } from "react-native-unistyles";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import type { SubscribeCheckoutDiffResponse } from "@server/shared/messages";
|
import type { SubscribeCheckoutDiffResponse } from "@server/shared/messages";
|
||||||
import { orderCheckoutDiffFiles } from "./checkout-diff-order";
|
import { orderCheckoutDiffFiles } from "./checkout-diff-order";
|
||||||
|
|
||||||
@@ -56,12 +56,7 @@ export function useCheckoutDiffQuery({
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
}: UseCheckoutDiffQueryOptions) {
|
}: UseCheckoutDiffQueryOptions) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const client = useSessionStore(
|
const { client, isConnected } = useHostRuntimeSession(serverId);
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
|
||||||
);
|
|
||||||
const isConnected = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
|
||||||
);
|
|
||||||
const isMobile =
|
const isMobile =
|
||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
const mobileView = usePanelStore((state) => state.mobileView);
|
const mobileView = usePanelStore((state) => state.mobileView);
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import type { CheckoutPrStatusResponse } from "@server/shared/messages";
|
import type { CheckoutPrStatusResponse } from "@server/shared/messages";
|
||||||
|
|
||||||
const CHECKOUT_PR_STATUS_STALE_TIME = 20_000;
|
const CHECKOUT_PR_STATUS_STALE_TIME = 20_000;
|
||||||
@@ -21,12 +21,7 @@ export function useCheckoutPrStatusQuery({
|
|||||||
cwd,
|
cwd,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
}: UseCheckoutPrStatusQueryOptions) {
|
}: UseCheckoutPrStatusQueryOptions) {
|
||||||
const client = useSessionStore(
|
const { client, isConnected } = useHostRuntimeSession(serverId);
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
|
||||||
);
|
|
||||||
const isConnected = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: checkoutPrStatusQueryKey(serverId, cwd),
|
queryKey: checkoutPrStatusQueryKey(serverId, cwd),
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useEffect, useMemo, useRef } from "react";
|
import { useEffect, useMemo, useRef } from "react";
|
||||||
import { UnistylesRuntime } from "react-native-unistyles";
|
import { UnistylesRuntime } from "react-native-unistyles";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import type { CheckoutStatusResponse } from "@server/shared/messages";
|
import type { CheckoutStatusResponse } from "@server/shared/messages";
|
||||||
import {
|
import {
|
||||||
checkoutStatusRevalidationKey,
|
checkoutStatusRevalidationKey,
|
||||||
@@ -30,12 +30,7 @@ function fetchCheckoutStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useCheckoutStatusQuery({ serverId, cwd }: UseCheckoutStatusQueryOptions) {
|
export function useCheckoutStatusQuery({ serverId, cwd }: UseCheckoutStatusQueryOptions) {
|
||||||
const client = useSessionStore(
|
const { client, isConnected } = useHostRuntimeSession(serverId);
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
|
||||||
);
|
|
||||||
const isConnected = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
|
||||||
);
|
|
||||||
const isMobile =
|
const isMobile =
|
||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
const mobileView = usePanelStore((state) => state.mobileView);
|
const mobileView = usePanelStore((state) => state.mobileView);
|
||||||
@@ -92,9 +87,7 @@ export function useCheckoutStatusQuery({ serverId, cwd }: UseCheckoutStatusQuery
|
|||||||
* only the visible agents.
|
* only the visible agents.
|
||||||
*/
|
*/
|
||||||
export function useCheckoutStatusCacheOnly({ serverId, cwd }: UseCheckoutStatusQueryOptions) {
|
export function useCheckoutStatusCacheOnly({ serverId, cwd }: UseCheckoutStatusQueryOptions) {
|
||||||
const client = useSessionStore(
|
const { client } = useHostRuntimeSession(serverId);
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
|
||||||
);
|
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: checkoutStatusQueryKey(serverId, cwd),
|
queryKey: checkoutStatusQueryKey(serverId, cwd),
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import type { ProjectIcon } from "@server/shared/messages";
|
import type { ProjectIcon } from "@server/shared/messages";
|
||||||
|
|
||||||
export function projectIconQueryKey(serverId: string, cwd: string) {
|
export function projectIconQueryKey(serverId: string, cwd: string) {
|
||||||
@@ -12,12 +12,7 @@ interface UseProjectIconQueryOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useProjectIconQuery({ serverId, cwd }: UseProjectIconQueryOptions) {
|
export function useProjectIconQuery({ serverId, cwd }: UseProjectIconQueryOptions) {
|
||||||
const client = useSessionStore(
|
const { client, isConnected } = useHostRuntimeSession(serverId);
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
|
||||||
);
|
|
||||||
const isConnected = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
|
||||||
);
|
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: projectIconQueryKey(serverId, cwd),
|
queryKey: projectIconQueryKey(serverId, cwd),
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import { useCallback, useMemo } from "react";
|
|||||||
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
import { useQuery, useQueryClient } from "@tanstack/react-query";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||||
import { useSessionStore, type Agent } from "@/stores/session-store";
|
import { useSessionStore, type Agent } from "@/stores/session-store";
|
||||||
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import type { AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
import type { AggregatedAgent } from "@/hooks/use-aggregated-agents";
|
||||||
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
import { normalizeAgentSnapshot } from "@/utils/agent-snapshots";
|
||||||
import {
|
import {
|
||||||
@@ -170,9 +171,8 @@ export function useSidebarAgentsGrouped(options?: {
|
|||||||
const session = useSessionStore((state) =>
|
const session = useSessionStore((state) =>
|
||||||
serverId ? state.sessions[serverId] : undefined
|
serverId ? state.sessions[serverId] : undefined
|
||||||
);
|
);
|
||||||
const client = session?.client ?? null;
|
const { client, isConnected } = useHostRuntimeSession(serverId ?? "");
|
||||||
const liveAgents = session?.agents ?? null;
|
const liveAgents = session?.agents ?? null;
|
||||||
const isConnected = session?.connection.isConnected ?? false;
|
|
||||||
const canFetch = Boolean(serverId && client && isConnected);
|
const canFetch = Boolean(serverId && client && isConnected);
|
||||||
|
|
||||||
const agentsQuery = useQuery({
|
const agentsQuery = useQuery({
|
||||||
|
|||||||
@@ -32,6 +32,12 @@ export type HostRuntimeSnapshot = {
|
|||||||
clientGeneration: number;
|
clientGeneration: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export function isHostRuntimeConnected(
|
||||||
|
snapshot: HostRuntimeSnapshot | null
|
||||||
|
): boolean {
|
||||||
|
return snapshot?.connectionStatus === "online";
|
||||||
|
}
|
||||||
|
|
||||||
export type HostRuntimeControllerDeps = {
|
export type HostRuntimeControllerDeps = {
|
||||||
createClient: (input: {
|
createClient: (input: {
|
||||||
host: HostProfile;
|
host: HostProfile;
|
||||||
@@ -564,6 +570,7 @@ export function useHostRuntimeSnapshot(
|
|||||||
export function useHostRuntimeSession(serverId: string): {
|
export function useHostRuntimeSession(serverId: string): {
|
||||||
snapshot: HostRuntimeSnapshot | null;
|
snapshot: HostRuntimeSnapshot | null;
|
||||||
client: DaemonClient | null;
|
client: DaemonClient | null;
|
||||||
|
isConnected: boolean;
|
||||||
} {
|
} {
|
||||||
const store = getHostRuntimeStore();
|
const store = getHostRuntimeStore();
|
||||||
const snapshot = useHostRuntimeSnapshot(serverId);
|
const snapshot = useHostRuntimeSnapshot(serverId);
|
||||||
@@ -571,6 +578,7 @@ export function useHostRuntimeSession(serverId: string): {
|
|||||||
() => ({
|
() => ({
|
||||||
snapshot,
|
snapshot,
|
||||||
client: store.getClient(serverId),
|
client: store.getClient(serverId),
|
||||||
|
isConnected: isHostRuntimeConnected(snapshot),
|
||||||
}),
|
}),
|
||||||
[serverId, snapshot, store]
|
[serverId, snapshot, store]
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -41,8 +41,11 @@ import {
|
|||||||
} from "@/contexts/explorer-sidebar-animation-context";
|
} from "@/contexts/explorer-sidebar-animation-context";
|
||||||
import { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||||
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
|
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
|
import {
|
||||||
|
useHostRuntimeSession,
|
||||||
|
type HostRuntimeConnectionStatus,
|
||||||
|
} from "@/runtime/host-runtime";
|
||||||
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
import { useCreateFlowStore } from "@/stores/create-flow-store";
|
||||||
import type { Agent } from "@/contexts/session-context";
|
import type { Agent } from "@/contexts/session-context";
|
||||||
import type { StreamItem } from "@/types/stream";
|
import type { StreamItem } from "@/types/stream";
|
||||||
@@ -75,7 +78,7 @@ import {
|
|||||||
} from "@/utils/agent-snapshots";
|
} from "@/utils/agent-snapshots";
|
||||||
import { mergePendingCreateImages } from "@/utils/pending-create-images";
|
import { mergePendingCreateImages } from "@/utils/pending-create-images";
|
||||||
import { shouldClearAgentAttentionOnView } from "@/utils/agent-attention";
|
import { shouldClearAgentAttentionOnView } from "@/utils/agent-attention";
|
||||||
import type { FetchAgentsEntry } from "@server/client/daemon-client";
|
import type { DaemonClient, FetchAgentsEntry } from "@server/client/daemon-client";
|
||||||
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
|
import { useExplorerOpenGesture } from "@/hooks/use-explorer-open-gesture";
|
||||||
import {
|
import {
|
||||||
DropdownMenu,
|
DropdownMenu,
|
||||||
@@ -109,11 +112,12 @@ export function AgentReadyScreen({
|
|||||||
const resolvedAgentId = agentId?.trim() || undefined;
|
const resolvedAgentId = agentId?.trim() || undefined;
|
||||||
const resolvedServerId = serverId?.trim() || undefined;
|
const resolvedServerId = serverId?.trim() || undefined;
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { connectionStates } = useDaemonConnections();
|
||||||
|
const runtimeServerId = resolvedServerId ?? "";
|
||||||
// Check if session exists for this serverId
|
const {
|
||||||
const hasSession = useSessionStore((state) =>
|
snapshot: runtimeSnapshot,
|
||||||
resolvedServerId ? !!state.sessions[resolvedServerId] : false
|
client: runtimeClient,
|
||||||
);
|
isConnected: runtimeIsConnected,
|
||||||
|
} = useHostRuntimeSession(runtimeServerId);
|
||||||
|
|
||||||
const connectionServerId = resolvedServerId ?? null;
|
const connectionServerId = resolvedServerId ?? null;
|
||||||
const connection = connectionServerId
|
const connection = connectionServerId
|
||||||
@@ -122,9 +126,11 @@ export function AgentReadyScreen({
|
|||||||
const serverLabel =
|
const serverLabel =
|
||||||
connection?.daemon.label ?? connectionServerId ?? "Selected host";
|
connection?.daemon.label ?? connectionServerId ?? "Selected host";
|
||||||
const isUnknownDaemon = Boolean(connectionServerId && !connection);
|
const isUnknownDaemon = Boolean(connectionServerId && !connection);
|
||||||
const connectionStatus =
|
const connectionStatus: HostRuntimeConnectionStatus =
|
||||||
connection?.status ?? (isUnknownDaemon ? "offline" : "idle");
|
runtimeSnapshot?.connectionStatus ??
|
||||||
const lastConnectionError = connection?.lastError ?? null;
|
(isUnknownDaemon ? "offline" : "idle");
|
||||||
|
const lastConnectionError = runtimeSnapshot?.lastError ?? null;
|
||||||
|
const isRuntimeSessionAvailable = Boolean(resolvedServerId && runtimeClient);
|
||||||
|
|
||||||
const handleBackToHome = useCallback(() => {
|
const handleBackToHome = useCallback(() => {
|
||||||
const targetServerId = resolvedServerId;
|
const targetServerId = resolvedServerId;
|
||||||
@@ -154,7 +160,9 @@ export function AgentReadyScreen({
|
|||||||
}, [resolvedAgentId, resolvedServerId, router]);
|
}, [resolvedAgentId, resolvedServerId, router]);
|
||||||
|
|
||||||
const focusServerId = resolvedServerId;
|
const focusServerId = resolvedServerId;
|
||||||
const navigationStatus = hasSession ? "ready" : "session_unavailable";
|
const navigationStatus = isRuntimeSessionAvailable
|
||||||
|
? "ready"
|
||||||
|
: "session_unavailable";
|
||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
@@ -172,7 +180,7 @@ export function AgentReadyScreen({
|
|||||||
}, [focusServerId, navigationStatus, resolvedAgentId])
|
}, [focusServerId, navigationStatus, resolvedAgentId])
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!hasSession || !resolvedServerId) {
|
if (!resolvedServerId || !runtimeClient) {
|
||||||
return (
|
return (
|
||||||
<AgentSessionUnavailableState
|
<AgentSessionUnavailableState
|
||||||
onBack={handleBackToHome}
|
onBack={handleBackToHome}
|
||||||
@@ -189,6 +197,8 @@ export function AgentReadyScreen({
|
|||||||
<AgentScreenContent
|
<AgentScreenContent
|
||||||
serverId={resolvedServerId}
|
serverId={resolvedServerId}
|
||||||
agentId={resolvedAgentId}
|
agentId={resolvedAgentId}
|
||||||
|
client={runtimeClient}
|
||||||
|
isConnected={runtimeIsConnected}
|
||||||
connectionStatus={connectionStatus}
|
connectionStatus={connectionStatus}
|
||||||
/>
|
/>
|
||||||
</ExplorerSidebarAnimationProvider>
|
</ExplorerSidebarAnimationProvider>
|
||||||
@@ -198,7 +208,9 @@ export function AgentReadyScreen({
|
|||||||
type AgentScreenContentProps = {
|
type AgentScreenContentProps = {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
agentId?: string;
|
agentId?: string;
|
||||||
connectionStatus: ConnectionStatus;
|
client: DaemonClient;
|
||||||
|
isConnected: boolean;
|
||||||
|
connectionStatus: HostRuntimeConnectionStatus;
|
||||||
};
|
};
|
||||||
|
|
||||||
function toErrorMessage(error: unknown): string {
|
function toErrorMessage(error: unknown): string {
|
||||||
@@ -215,6 +227,8 @@ function isNotFoundErrorMessage(message: string): boolean {
|
|||||||
function AgentScreenContent({
|
function AgentScreenContent({
|
||||||
serverId,
|
serverId,
|
||||||
agentId,
|
agentId,
|
||||||
|
client,
|
||||||
|
isConnected,
|
||||||
connectionStatus,
|
connectionStatus,
|
||||||
}: AgentScreenContentProps) {
|
}: AgentScreenContentProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
@@ -465,16 +479,16 @@ function AgentScreenContent({
|
|||||||
return filtered;
|
return filtered;
|
||||||
}, [allPendingPermissions, resolvedAgentId]);
|
}, [allPendingPermissions, resolvedAgentId]);
|
||||||
|
|
||||||
const client = useSessionStore(
|
const hasSession = useSessionStore(
|
||||||
(state) => state.sessions[serverId]?.client ?? null
|
(state) => Boolean(state.sessions[serverId])
|
||||||
);
|
|
||||||
const isConnected = useSessionStore(
|
|
||||||
(state) => state.sessions[serverId]?.connection.isConnected ?? false
|
|
||||||
);
|
);
|
||||||
const focusedAgentId = useSessionStore(
|
const focusedAgentId = useSessionStore(
|
||||||
(state) => state.sessions[serverId]?.focusedAgentId ?? null
|
(state) => state.sessions[serverId]?.focusedAgentId ?? null
|
||||||
);
|
);
|
||||||
const { ensureAgentIsInitialized, refreshAgent } = useAgentInitialization(serverId);
|
const { ensureAgentIsInitialized, refreshAgent } = useAgentInitialization({
|
||||||
|
serverId,
|
||||||
|
client: hasSession ? client : null,
|
||||||
|
});
|
||||||
const [missingAgentState, setMissingAgentState] = useState<AgentScreenMissingState>({
|
const [missingAgentState, setMissingAgentState] = useState<AgentScreenMissingState>({
|
||||||
kind: "idle",
|
kind: "idle",
|
||||||
});
|
});
|
||||||
@@ -529,7 +543,7 @@ function AgentScreenContent({
|
|||||||
|
|
||||||
useFocusEffect(
|
useFocusEffect(
|
||||||
useCallback(() => {
|
useCallback(() => {
|
||||||
if (!resolvedAgentId || !isConnected) {
|
if (!resolvedAgentId || !isConnected || !hasSession) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
ensureAgentIsInitialized(resolvedAgentId).catch((error) => {
|
ensureAgentIsInitialized(resolvedAgentId).catch((error) => {
|
||||||
@@ -538,7 +552,7 @@ function AgentScreenContent({
|
|||||||
error,
|
error,
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}, [ensureAgentIsInitialized, isConnected, resolvedAgentId])
|
}, [ensureAgentIsInitialized, hasSession, isConnected, resolvedAgentId])
|
||||||
);
|
);
|
||||||
|
|
||||||
const isGitCheckout = activeExplorerCheckout?.isGit ?? false;
|
const isGitCheckout = activeExplorerCheckout?.isGit ?? false;
|
||||||
@@ -745,7 +759,7 @@ function AgentScreenContent({
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isConnected) {
|
if (!isConnected || !hasSession) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
// On native clients, daemon stream forwarding is focused-agent only, so switching
|
// On native clients, daemon stream forwarding is focused-agent only, so switching
|
||||||
@@ -764,6 +778,7 @@ function AgentScreenContent({
|
|||||||
}, [
|
}, [
|
||||||
resolvedAgentId,
|
resolvedAgentId,
|
||||||
ensureAgentIsInitialized,
|
ensureAgentIsInitialized,
|
||||||
|
hasSession,
|
||||||
isConnected,
|
isConnected,
|
||||||
needsAuthoritativeSync,
|
needsAuthoritativeSync,
|
||||||
]);
|
]);
|
||||||
@@ -784,7 +799,7 @@ function AgentScreenContent({
|
|||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!isConnected) {
|
if (!isConnected || !hasSession) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (missingAgentState.kind === "resolving" || missingAgentState.kind === "not_found") {
|
if (missingAgentState.kind === "resolving" || missingAgentState.kind === "not_found") {
|
||||||
@@ -855,6 +870,7 @@ function AgentScreenContent({
|
|||||||
agent,
|
agent,
|
||||||
client,
|
client,
|
||||||
ensureAgentIsInitialized,
|
ensureAgentIsInitialized,
|
||||||
|
hasSession,
|
||||||
isConnected,
|
isConnected,
|
||||||
missingAgentState.kind,
|
missingAgentState.kind,
|
||||||
resolvedAgentId,
|
resolvedAgentId,
|
||||||
@@ -898,13 +914,13 @@ function AgentScreenContent({
|
|||||||
]);
|
]);
|
||||||
|
|
||||||
const handleRefreshAgent = useCallback(() => {
|
const handleRefreshAgent = useCallback(() => {
|
||||||
if (!resolvedAgentId) {
|
if (!resolvedAgentId || !hasSession) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void refreshAgent(resolvedAgentId).catch((error) => {
|
void refreshAgent(resolvedAgentId).catch((error) => {
|
||||||
console.warn("[AgentScreen] refreshAgent failed", { agentId: resolvedAgentId, error });
|
console.warn("[AgentScreen] refreshAgent failed", { agentId: resolvedAgentId, error });
|
||||||
});
|
});
|
||||||
}, [resolvedAgentId, refreshAgent]);
|
}, [hasSession, refreshAgent, resolvedAgentId]);
|
||||||
|
|
||||||
const handleCopyMeta = useCallback(
|
const handleCopyMeta = useCallback(
|
||||||
async (label: string, value: string | null | undefined) => {
|
async (label: string, value: string | null | undefined) => {
|
||||||
@@ -1262,7 +1278,7 @@ function AgentSessionUnavailableState({
|
|||||||
}: {
|
}: {
|
||||||
onBack: () => void;
|
onBack: () => void;
|
||||||
serverLabel: string;
|
serverLabel: string;
|
||||||
connectionStatus: ConnectionStatus;
|
connectionStatus: HostRuntimeConnectionStatus;
|
||||||
lastError: string | null;
|
lastError: string | null;
|
||||||
isUnknownDaemon?: boolean;
|
isUnknownDaemon?: boolean;
|
||||||
}) {
|
}) {
|
||||||
@@ -1285,19 +1301,24 @@ function AgentSessionUnavailableState({
|
|||||||
}
|
}
|
||||||
|
|
||||||
const isConnecting = connectionStatus === "connecting";
|
const isConnecting = connectionStatus === "connecting";
|
||||||
|
const isPreparingSession = connectionStatus === "online";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.container}>
|
<View style={styles.container}>
|
||||||
<BackHeader title="Agent" onBack={onBack} />
|
<BackHeader title="Agent" onBack={onBack} />
|
||||||
<View style={styles.centerState}>
|
<View style={styles.centerState}>
|
||||||
{isConnecting ? (
|
{isConnecting || isPreparingSession ? (
|
||||||
<>
|
<>
|
||||||
<ActivityIndicator size="large" />
|
<ActivityIndicator size="large" />
|
||||||
<Text style={styles.loadingText}>
|
<Text style={styles.loadingText}>
|
||||||
Connecting to {serverLabel}...
|
{isPreparingSession
|
||||||
|
? `Preparing ${serverLabel} session...`
|
||||||
|
: `Connecting to ${serverLabel}...`}
|
||||||
</Text>
|
</Text>
|
||||||
<Text style={styles.statusText}>
|
<Text style={styles.statusText}>
|
||||||
We will show this agent once the host is online.
|
{isPreparingSession
|
||||||
|
? "We will show this agent in a moment."
|
||||||
|
: "We will show this agent once the host is online."}
|
||||||
</Text>
|
</Text>
|
||||||
</>
|
</>
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ import { buildWorkingDirectorySuggestions } from '@/utils/working-directory-sugg
|
|||||||
import { useExplorerOpenGesture } from '@/hooks/use-explorer-open-gesture'
|
import { useExplorerOpenGesture } from '@/hooks/use-explorer-open-gesture'
|
||||||
import { useSessionStore } from '@/stores/session-store'
|
import { useSessionStore } from '@/stores/session-store'
|
||||||
import { useCreateFlowStore } from '@/stores/create-flow-store'
|
import { useCreateFlowStore } from '@/stores/create-flow-store'
|
||||||
|
import { useHostRuntimeSession } from '@/runtime/host-runtime'
|
||||||
import { ExplorerSidebarAnimationProvider } from '@/contexts/explorer-sidebar-animation-context'
|
import { ExplorerSidebarAnimationProvider } from '@/contexts/explorer-sidebar-animation-context'
|
||||||
import { usePanelStore, type ExplorerCheckoutContext } from '@/stores/panel-store'
|
import { usePanelStore, type ExplorerCheckoutContext } from '@/stores/panel-store'
|
||||||
import { MAX_CONTENT_WIDTH } from '@/constants/layout'
|
import { MAX_CONTENT_WIDTH } from '@/constants/layout'
|
||||||
@@ -368,18 +369,16 @@ function DraftAgentScreenContent({
|
|||||||
return collectAgentWorkingDirectorySuggestions([...liveSources, ...fetchedSources])
|
return collectAgentWorkingDirectorySuggestions([...liveSources, ...fetchedSources])
|
||||||
}, [allAgents, sessionAgents])
|
}, [allAgents, sessionAgents])
|
||||||
|
|
||||||
const sessionClient = useSessionStore((state) =>
|
const { client: runtimeClient, isConnected: isHostOnline } = useHostRuntimeSession(
|
||||||
selectedServerId ? (state.sessions[selectedServerId]?.client ?? null) : null
|
selectedServerId ?? ''
|
||||||
)
|
|
||||||
const isConnected = useSessionStore((state) =>
|
|
||||||
selectedServerId ? (state.sessions[selectedServerId]?.connection.isConnected ?? false) : false
|
|
||||||
)
|
)
|
||||||
|
const sessionClient = runtimeClient
|
||||||
const trimmedWorkingDir = workingDir.trim()
|
const trimmedWorkingDir = workingDir.trim()
|
||||||
const shouldInspectRepo = trimmedWorkingDir.length > 0
|
const shouldInspectRepo = trimmedWorkingDir.length > 0
|
||||||
const daemonAvailabilityError =
|
const daemonAvailabilityError =
|
||||||
!selectedServerId || hostEntry?.status !== 'online' ? 'Host is offline' : null
|
!selectedServerId || !isHostOnline ? 'Host is offline' : null
|
||||||
const repoAvailabilityError =
|
const repoAvailabilityError =
|
||||||
shouldInspectRepo && (!hostEntry || hostEntry.status !== 'online' || !isConnected)
|
shouldInspectRepo && !isHostOnline
|
||||||
? (daemonAvailabilityError ??
|
? (daemonAvailabilityError ??
|
||||||
'Repository details will load automatically once the selected host is back online.')
|
'Repository details will load automatically once the selected host is back online.')
|
||||||
: null
|
: null
|
||||||
@@ -398,7 +397,7 @@ function DraftAgentScreenContent({
|
|||||||
Boolean(trimmedWorkingDir) &&
|
Boolean(trimmedWorkingDir) &&
|
||||||
!repoAvailabilityError &&
|
!repoAvailabilityError &&
|
||||||
Boolean(sessionClient) &&
|
Boolean(sessionClient) &&
|
||||||
isConnected,
|
isHostOnline,
|
||||||
retry: false,
|
retry: false,
|
||||||
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
staleTime: CHECKOUT_STATUS_STALE_TIME,
|
||||||
refetchOnMount: 'always',
|
refetchOnMount: 'always',
|
||||||
@@ -461,7 +460,7 @@ function DraftAgentScreenContent({
|
|||||||
Boolean(worktreeListRoot) &&
|
Boolean(worktreeListRoot) &&
|
||||||
!repoAvailabilityError &&
|
!repoAvailabilityError &&
|
||||||
Boolean(sessionClient) &&
|
Boolean(sessionClient) &&
|
||||||
isConnected &&
|
isHostOnline &&
|
||||||
!isNonGitDirectory,
|
!isNonGitDirectory,
|
||||||
retry: false,
|
retry: false,
|
||||||
staleTime: 0,
|
staleTime: 0,
|
||||||
@@ -526,7 +525,7 @@ function DraftAgentScreenContent({
|
|||||||
Boolean(trimmedWorkingDir) &&
|
Boolean(trimmedWorkingDir) &&
|
||||||
!repoAvailabilityError &&
|
!repoAvailabilityError &&
|
||||||
Boolean(sessionClient) &&
|
Boolean(sessionClient) &&
|
||||||
isConnected,
|
isHostOnline,
|
||||||
retry: false,
|
retry: false,
|
||||||
staleTime: 15_000,
|
staleTime: 15_000,
|
||||||
})
|
})
|
||||||
@@ -559,7 +558,7 @@ function DraftAgentScreenContent({
|
|||||||
Boolean(selectedServerId) &&
|
Boolean(selectedServerId) &&
|
||||||
!daemonAvailabilityError &&
|
!daemonAvailabilityError &&
|
||||||
Boolean(sessionClient) &&
|
Boolean(sessionClient) &&
|
||||||
isConnected,
|
isHostOnline,
|
||||||
retry: false,
|
retry: false,
|
||||||
staleTime: 15_000,
|
staleTime: 15_000,
|
||||||
})
|
})
|
||||||
@@ -625,7 +624,7 @@ function DraftAgentScreenContent({
|
|||||||
Boolean(baseBranch) &&
|
Boolean(baseBranch) &&
|
||||||
Boolean(trimmedWorkingDir) &&
|
Boolean(trimmedWorkingDir) &&
|
||||||
Boolean(sessionClient) &&
|
Boolean(sessionClient) &&
|
||||||
isConnected,
|
isHostOnline,
|
||||||
retry: false,
|
retry: false,
|
||||||
staleTime: 30_000,
|
staleTime: 30_000,
|
||||||
})
|
})
|
||||||
@@ -810,9 +809,7 @@ function DraftAgentScreenContent({
|
|||||||
})
|
})
|
||||||
}, [baseBranch, branchSearchQuery, branchSuggestionsQuery.data, checkout, worktreeOptions])
|
}, [baseBranch, branchSearchQuery, branchSuggestionsQuery.data, checkout, worktreeOptions])
|
||||||
|
|
||||||
const createAgentClient = useSessionStore((state) =>
|
const createAgentClient = sessionClient
|
||||||
selectedServerId ? (state.sessions[selectedServerId]?.client ?? null) : null
|
|
||||||
)
|
|
||||||
const draftCommandConfig = useMemo<DraftCommandConfig | undefined>(() => {
|
const draftCommandConfig = useMemo<DraftCommandConfig | undefined>(() => {
|
||||||
const cwd = (
|
const cwd = (
|
||||||
isAttachWorktree && selectedWorktreePath ? selectedWorktreePath : workingDir
|
isAttachWorktree && selectedWorktreePath ? selectedWorktreePath : workingDir
|
||||||
|
|||||||
@@ -22,6 +22,7 @@ import { measureConnectionLatency } from "@/utils/test-daemon-connection";
|
|||||||
import { confirmDialog } from "@/utils/confirm-dialog";
|
import { confirmDialog } from "@/utils/confirm-dialog";
|
||||||
import { MenuHeader } from "@/components/headers/menu-header";
|
import { MenuHeader } from "@/components/headers/menu-header";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
|
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
||||||
import { AddHostMethodModal } from "@/components/add-host-method-modal";
|
import { AddHostMethodModal } from "@/components/add-host-method-modal";
|
||||||
import { AddHostModal } from "@/components/add-host-modal";
|
import { AddHostModal } from "@/components/add-host-modal";
|
||||||
import { PairLinkModal } from "@/components/pair-link-modal";
|
import { PairLinkModal } from "@/components/pair-link-modal";
|
||||||
@@ -1035,10 +1036,11 @@ function HostDetailModal({
|
|||||||
);
|
);
|
||||||
|
|
||||||
// Restart logic (moved from DaemonCard)
|
// Restart logic (moved from DaemonCard)
|
||||||
const daemonClient = useSessionStore((state) => host ? (state.sessions[host.serverId]?.client ?? null) : null);
|
const { client: runtimeClient, isConnected } = useHostRuntimeSession(
|
||||||
const daemonConnection = useSessionStore((state) => host ? (state.sessions[host.serverId]?.connection ?? null) : null);
|
host?.serverId ?? ""
|
||||||
|
);
|
||||||
|
const daemonClient = runtimeClient;
|
||||||
const daemonVersion = useSessionStore((state) => host ? (state.sessions[host.serverId]?.serverInfo?.version ?? null) : null);
|
const daemonVersion = useSessionStore((state) => host ? (state.sessions[host.serverId]?.serverInfo?.version ?? null) : null);
|
||||||
const isConnected = daemonConnection?.isConnected ?? false;
|
|
||||||
const isConnectedRef = useRef(isConnected);
|
const isConnectedRef = useRef(isConnected);
|
||||||
const [isRestarting, setIsRestarting] = useState(false);
|
const [isRestarting, setIsRestarting] = useState(false);
|
||||||
|
|
||||||
|
|||||||
@@ -147,12 +147,6 @@ export interface AgentFileExplorerState {
|
|||||||
selectedEntryPath: string | null;
|
selectedEntryPath: string | null;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface DaemonConnectionSnapshot {
|
|
||||||
isConnected: boolean;
|
|
||||||
isConnecting: boolean;
|
|
||||||
lastError: string | null;
|
|
||||||
}
|
|
||||||
|
|
||||||
export type DaemonServerInfo = {
|
export type DaemonServerInfo = {
|
||||||
serverId: string;
|
serverId: string;
|
||||||
hostname: string | null;
|
hostname: string | null;
|
||||||
@@ -173,9 +167,6 @@ export interface SessionState {
|
|||||||
// Daemon client (immutable reference)
|
// Daemon client (immutable reference)
|
||||||
client: DaemonClient | null;
|
client: DaemonClient | null;
|
||||||
|
|
||||||
// Connection snapshot (mutable)
|
|
||||||
connection: DaemonConnectionSnapshot;
|
|
||||||
|
|
||||||
// Server metadata (from server_info handshake)
|
// Server metadata (from server_info handshake)
|
||||||
serverInfo: DaemonServerInfo | null;
|
serverInfo: DaemonServerInfo | null;
|
||||||
|
|
||||||
@@ -233,7 +224,6 @@ interface SessionStoreActions {
|
|||||||
clearSession: (serverId: string) => void;
|
clearSession: (serverId: string) => void;
|
||||||
getSession: (serverId: string) => SessionState | undefined;
|
getSession: (serverId: string) => SessionState | undefined;
|
||||||
updateSessionClient: (serverId: string, client: DaemonClient) => void;
|
updateSessionClient: (serverId: string, client: DaemonClient) => void;
|
||||||
updateSessionConnection: (serverId: string, connection: DaemonConnectionSnapshot) => void;
|
|
||||||
updateSessionServerInfo: (serverId: string, info: DaemonServerInfo) => void;
|
updateSessionServerInfo: (serverId: string, info: DaemonServerInfo) => void;
|
||||||
|
|
||||||
// Audio state
|
// Audio state
|
||||||
@@ -309,25 +299,11 @@ function logSessionStoreUpdate(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
function createDefaultConnectionSnapshot(client?: DaemonClient | null): DaemonConnectionSnapshot {
|
|
||||||
if (!client) {
|
|
||||||
return { isConnected: false, isConnecting: false, lastError: null };
|
|
||||||
}
|
|
||||||
const state = client.getConnectionState();
|
|
||||||
return {
|
|
||||||
isConnected: state.status === "connected",
|
|
||||||
isConnecting: state.status === "connecting",
|
|
||||||
lastError: state.status === "disconnected" ? state.reason ?? client.lastError ?? null : null,
|
|
||||||
};
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to create initial session state
|
// Helper to create initial session state
|
||||||
function createInitialSessionState(serverId: string, client: DaemonClient, audioPlayer: ReturnType<typeof useAudioPlayer>): SessionState {
|
function createInitialSessionState(serverId: string, client: DaemonClient, audioPlayer: ReturnType<typeof useAudioPlayer>): SessionState {
|
||||||
return {
|
return {
|
||||||
serverId,
|
serverId,
|
||||||
client,
|
client,
|
||||||
connection: createDefaultConnectionSnapshot(client),
|
|
||||||
serverInfo: null,
|
serverInfo: null,
|
||||||
audioPlayer,
|
audioPlayer,
|
||||||
hasHydratedAgents: false,
|
hasHydratedAgents: false,
|
||||||
@@ -420,33 +396,6 @@ export const useSessionStore = create<SessionStore>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
updateSessionConnection: (serverId, connection) => {
|
|
||||||
set((prev) => {
|
|
||||||
const session = prev.sessions[serverId];
|
|
||||||
if (!session) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
if (
|
|
||||||
session.connection.isConnected === connection.isConnected &&
|
|
||||||
session.connection.isConnecting === connection.isConnecting &&
|
|
||||||
session.connection.lastError === connection.lastError
|
|
||||||
) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
logSessionStoreUpdate("updateSessionConnection", serverId, connection);
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
sessions: {
|
|
||||||
...prev.sessions,
|
|
||||||
[serverId]: {
|
|
||||||
...session,
|
|
||||||
connection,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
updateSessionServerInfo: (serverId, info) => {
|
updateSessionServerInfo: (serverId, info) => {
|
||||||
set((prev) => {
|
set((prev) => {
|
||||||
const session = prev.sessions[serverId];
|
const session = prev.sessions[serverId];
|
||||||
|
|||||||
Reference in New Issue
Block a user