refactor: remove daemon connections context, use host runtime directly
This commit is contained in:
@@ -13,7 +13,6 @@ import { View, ActivityIndicator, Text } from "react-native";
|
|||||||
import { UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
import { UnistylesRuntime, useUnistyles } from "react-native-unistyles";
|
||||||
import { darkTheme } from "@/styles/theme";
|
import { darkTheme } from "@/styles/theme";
|
||||||
import { DaemonRegistryProvider, useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
import { DaemonRegistryProvider, useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { DaemonConnectionsProvider, useDaemonConnections } from "@/contexts/daemon-connections-context";
|
|
||||||
import { MultiDaemonSessionHost } from "@/components/multi-daemon-session-host";
|
import { MultiDaemonSessionHost } from "@/components/multi-daemon-session-host";
|
||||||
import { QueryClientProvider } from "@tanstack/react-query";
|
import { QueryClientProvider } from "@tanstack/react-query";
|
||||||
import { useState, useEffect, type ReactNode, useMemo, useRef } from "react";
|
import { useState, useEffect, type ReactNode, useMemo, useRef } from "react";
|
||||||
@@ -449,37 +448,35 @@ export default function RootLayout() {
|
|||||||
<BottomSheetModalProvider>
|
<BottomSheetModalProvider>
|
||||||
<QueryProvider>
|
<QueryProvider>
|
||||||
<DaemonRegistryProvider>
|
<DaemonRegistryProvider>
|
||||||
<DaemonConnectionsProvider>
|
<PushNotificationRouter />
|
||||||
<PushNotificationRouter />
|
<MultiDaemonSessionHost />
|
||||||
<MultiDaemonSessionHost />
|
<ProvidersWrapper>
|
||||||
<ProvidersWrapper>
|
<SidebarAnimationProvider>
|
||||||
<SidebarAnimationProvider>
|
<HorizontalScrollProvider>
|
||||||
<HorizontalScrollProvider>
|
<ToastProvider>
|
||||||
<ToastProvider>
|
<AppWithSidebar>
|
||||||
<AppWithSidebar>
|
<Stack
|
||||||
<Stack
|
screenOptions={{
|
||||||
screenOptions={{
|
headerShown: false,
|
||||||
headerShown: false,
|
animation: "none",
|
||||||
animation: "none",
|
}}
|
||||||
}}
|
>
|
||||||
>
|
<Stack.Screen name="index" />
|
||||||
<Stack.Screen name="index" />
|
<Stack.Screen
|
||||||
<Stack.Screen
|
name="h/[serverId]/agent/[agentId]"
|
||||||
name="h/[serverId]/agent/[agentId]"
|
options={{ gestureEnabled: false }}
|
||||||
options={{ gestureEnabled: false }}
|
/>
|
||||||
/>
|
<Stack.Screen name="h/[serverId]/index" />
|
||||||
<Stack.Screen name="h/[serverId]/index" />
|
<Stack.Screen name="h/[serverId]/agent/index" />
|
||||||
<Stack.Screen name="h/[serverId]/agent/index" />
|
<Stack.Screen name="h/[serverId]/agents" />
|
||||||
<Stack.Screen name="h/[serverId]/agents" />
|
<Stack.Screen name="h/[serverId]/settings" />
|
||||||
<Stack.Screen name="h/[serverId]/settings" />
|
<Stack.Screen name="pair-scan" />
|
||||||
<Stack.Screen name="pair-scan" />
|
</Stack>
|
||||||
</Stack>
|
</AppWithSidebar>
|
||||||
</AppWithSidebar>
|
</ToastProvider>
|
||||||
</ToastProvider>
|
</HorizontalScrollProvider>
|
||||||
</HorizontalScrollProvider>
|
</SidebarAnimationProvider>
|
||||||
</SidebarAnimationProvider>
|
</ProvidersWrapper>
|
||||||
</ProvidersWrapper>
|
|
||||||
</DaemonConnectionsProvider>
|
|
||||||
</DaemonRegistryProvider>
|
</DaemonRegistryProvider>
|
||||||
</QueryProvider>
|
</QueryProvider>
|
||||||
</BottomSheetModalProvider>
|
</BottomSheetModalProvider>
|
||||||
|
|||||||
@@ -49,7 +49,7 @@ import type {
|
|||||||
ExplorerEntry,
|
ExplorerEntry,
|
||||||
ExplorerFile,
|
ExplorerFile,
|
||||||
} from "@/stores/session-store";
|
} from "@/stores/session-store";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { useDownloadStore } from "@/stores/download-store";
|
import { useDownloadStore } from "@/stores/download-store";
|
||||||
import {
|
import {
|
||||||
@@ -95,8 +95,11 @@ export function FileExplorerPane({ serverId, agentId }: FileExplorerPaneProps) {
|
|||||||
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
UnistylesRuntime.breakpoint === "xs" || UnistylesRuntime.breakpoint === "sm";
|
||||||
const showDesktopWebScrollbar = Platform.OS === "web" && !isMobile;
|
const showDesktopWebScrollbar = Platform.OS === "web" && !isMobile;
|
||||||
|
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { daemons } = useDaemonRegistry();
|
||||||
const daemonProfile = connectionStates.get(serverId)?.daemon;
|
const daemonProfile = useMemo(
|
||||||
|
() => daemons.find((daemon) => daemon.serverId === serverId),
|
||||||
|
[daemons, serverId]
|
||||||
|
);
|
||||||
const agentExists = useSessionStore((state) =>
|
const agentExists = useSessionStore((state) =>
|
||||||
agentId && state.sessions[serverId]
|
agentId && state.sessions[serverId]
|
||||||
? state.sessions[serverId]?.agents.has(agentId)
|
? state.sessions[serverId]?.agents.has(agentId)
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import { useCallback, useMemo, useState, useEffect, useRef } from "react";
|
import {
|
||||||
|
useCallback,
|
||||||
|
useMemo,
|
||||||
|
useState,
|
||||||
|
useEffect,
|
||||||
|
useRef,
|
||||||
|
useSyncExternalStore,
|
||||||
|
} from "react";
|
||||||
import { View, Pressable, Text, Platform } from "react-native";
|
import { View, Pressable, Text, Platform } from "react-native";
|
||||||
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
import { useSafeAreaInsets } from "react-native-safe-area-context";
|
||||||
import Animated, {
|
import Animated, {
|
||||||
@@ -20,7 +27,7 @@ import { useTauriDragHandlers, useTrafficLightPadding } from "@/utils/tauri-wind
|
|||||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||||
import { Combobox } from "@/components/ui/combobox";
|
import { Combobox } from "@/components/ui/combobox";
|
||||||
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { getHostRuntimeStore } from "@/runtime/host-runtime";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { formatConnectionStatus } from "@/utils/daemons";
|
import { formatConnectionStatus } from "@/utils/daemons";
|
||||||
import { HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT_MOBILE } from "@/constants/layout";
|
import { HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT_MOBILE } from "@/constants/layout";
|
||||||
@@ -57,7 +64,12 @@ export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
|||||||
const closeToAgent = usePanelStore((state) => state.closeToAgent);
|
const closeToAgent = usePanelStore((state) => state.closeToAgent);
|
||||||
const pathname = usePathname();
|
const pathname = usePathname();
|
||||||
const { daemons } = useDaemonRegistry();
|
const { daemons } = useDaemonRegistry();
|
||||||
const { connectionStates } = useDaemonConnections();
|
const runtime = getHostRuntimeStore();
|
||||||
|
const runtimeVersion = useSyncExternalStore(
|
||||||
|
(onStoreChange) => runtime.subscribeAll(onStoreChange),
|
||||||
|
() => runtime.getVersion(),
|
||||||
|
() => runtime.getVersion()
|
||||||
|
);
|
||||||
const activeServerIdFromPath = useMemo(
|
const activeServerIdFromPath = useMemo(
|
||||||
() => parseServerIdFromPathname(pathname),
|
() => parseServerIdFromPathname(pathname),
|
||||||
[pathname]
|
[pathname]
|
||||||
@@ -70,7 +82,7 @@ export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
|||||||
return trimmed && trimmed.length > 0 ? trimmed : activeServerId;
|
return trimmed && trimmed.length > 0 ? trimmed : activeServerId;
|
||||||
}, [activeServerId, daemons]);
|
}, [activeServerId, daemons]);
|
||||||
const activeHostStatus = activeServerId
|
const activeHostStatus = activeServerId
|
||||||
? (connectionStates.get(activeServerId)?.status ?? "idle")
|
? runtime.getSnapshot(activeServerId)?.connectionStatus ?? "connecting"
|
||||||
: "idle";
|
: "idle";
|
||||||
const activeHostStatusColor =
|
const activeHostStatusColor =
|
||||||
activeHostStatus === "online"
|
activeHostStatus === "online"
|
||||||
@@ -84,10 +96,10 @@ export function LeftSidebar({ selectedAgentId }: LeftSidebarProps) {
|
|||||||
id: daemon.serverId,
|
id: daemon.serverId,
|
||||||
label: daemon.label?.trim() || daemon.serverId,
|
label: daemon.label?.trim() || daemon.serverId,
|
||||||
description: formatConnectionStatus(
|
description: formatConnectionStatus(
|
||||||
connectionStates.get(daemon.serverId)?.status ?? "idle"
|
runtime.getSnapshot(daemon.serverId)?.connectionStatus ?? "connecting"
|
||||||
),
|
),
|
||||||
})),
|
})),
|
||||||
[connectionStates, daemons]
|
[daemons, runtime, runtimeVersion]
|
||||||
);
|
);
|
||||||
const hostTriggerRef = useRef<View>(null);
|
const hostTriggerRef = useRef<View>(null);
|
||||||
const [isHostPickerOpen, setIsHostPickerOpen] = useState(false);
|
const [isHostPickerOpen, setIsHostPickerOpen] = useState(false);
|
||||||
|
|||||||
@@ -3,11 +3,11 @@ import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
|||||||
import { MicOff, Square } from "lucide-react-native";
|
import { MicOff, Square } from "lucide-react-native";
|
||||||
import { VolumeMeter } from "./volume-meter";
|
import { VolumeMeter } from "./volume-meter";
|
||||||
import { useVoice } from "@/contexts/voice-context";
|
import { useVoice } from "@/contexts/voice-context";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
|
|
||||||
export function VoicePanel() {
|
export function VoicePanel() {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { daemons } = useDaemonRegistry();
|
||||||
const {
|
const {
|
||||||
volume,
|
volume,
|
||||||
isMuted,
|
isMuted,
|
||||||
@@ -18,8 +18,9 @@ export function VoicePanel() {
|
|||||||
activeServerId,
|
activeServerId,
|
||||||
} = useVoice();
|
} = useVoice();
|
||||||
|
|
||||||
const activeHost = activeServerId ? connectionStates.get(activeServerId) ?? null : null;
|
const hostLabel = activeServerId
|
||||||
const hostLabel = activeHost?.daemon.label ?? null;
|
? daemons.find((daemon) => daemon.serverId === activeServerId)?.label ?? null
|
||||||
|
: null;
|
||||||
const hostSuffix = hostLabel ? ` (${hostLabel})` : "";
|
const hostSuffix = hostLabel ? ` (${hostLabel})` : "";
|
||||||
|
|
||||||
return (
|
return (
|
||||||
|
|||||||
@@ -1,97 +0,0 @@
|
|||||||
import { createContext, useContext, useMemo, useSyncExternalStore } from "react";
|
|
||||||
import type { ReactNode } from "react";
|
|
||||||
import {
|
|
||||||
getHostRuntimeStore,
|
|
||||||
type HostRuntimeAgentDirectoryStatus,
|
|
||||||
type HostRuntimeConnectionStatus,
|
|
||||||
} from "@/runtime/host-runtime";
|
|
||||||
import { useDaemonRegistry, type HostProfile } from "./daemon-registry-context";
|
|
||||||
|
|
||||||
export type ActiveConnection =
|
|
||||||
| { type: "direct"; endpoint: string; display: string }
|
|
||||||
| { type: "relay"; endpoint: string; display: "relay" };
|
|
||||||
|
|
||||||
export type ConnectionStatus = HostRuntimeConnectionStatus;
|
|
||||||
|
|
||||||
export type DaemonConnectionRecord = {
|
|
||||||
daemon: HostProfile;
|
|
||||||
status: HostRuntimeConnectionStatus;
|
|
||||||
activeConnection: ActiveConnection | null;
|
|
||||||
lastError: string | null;
|
|
||||||
lastOnlineAt: string | null;
|
|
||||||
agentDirectoryStatus: HostRuntimeAgentDirectoryStatus;
|
|
||||||
agentDirectoryError: string | null;
|
|
||||||
hasEverLoadedAgentDirectory: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
interface DaemonConnectionsContextValue {
|
|
||||||
connectionStates: Map<string, DaemonConnectionRecord>;
|
|
||||||
isLoading: boolean;
|
|
||||||
}
|
|
||||||
|
|
||||||
const DaemonConnectionsContext = createContext<DaemonConnectionsContextValue | null>(null);
|
|
||||||
|
|
||||||
function buildConnectionStates(input: {
|
|
||||||
daemons: HostProfile[];
|
|
||||||
runtime: ReturnType<typeof getHostRuntimeStore>;
|
|
||||||
}): Map<string, DaemonConnectionRecord> {
|
|
||||||
const { daemons, runtime } = input;
|
|
||||||
const next = new Map<string, DaemonConnectionRecord>();
|
|
||||||
|
|
||||||
for (const daemon of daemons) {
|
|
||||||
const snapshot = runtime.getSnapshot(daemon.serverId);
|
|
||||||
next.set(daemon.serverId, {
|
|
||||||
daemon,
|
|
||||||
status: snapshot?.connectionStatus ?? "connecting",
|
|
||||||
activeConnection: snapshot?.activeConnection ?? null,
|
|
||||||
lastError: snapshot?.lastError ?? null,
|
|
||||||
lastOnlineAt: snapshot?.lastOnlineAt ?? null,
|
|
||||||
agentDirectoryStatus: snapshot?.agentDirectoryStatus ?? "initial_loading",
|
|
||||||
agentDirectoryError: snapshot?.agentDirectoryError ?? null,
|
|
||||||
hasEverLoadedAgentDirectory:
|
|
||||||
snapshot?.hasEverLoadedAgentDirectory ?? false,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
return next;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function useDaemonConnections(): DaemonConnectionsContextValue {
|
|
||||||
const ctx = useContext(DaemonConnectionsContext);
|
|
||||||
if (!ctx) {
|
|
||||||
throw new Error(
|
|
||||||
"useDaemonConnections must be used within DaemonConnectionsProvider"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return ctx;
|
|
||||||
}
|
|
||||||
|
|
||||||
export function DaemonConnectionsProvider({ children }: { children: ReactNode }) {
|
|
||||||
const { daemons, isLoading: registryLoading } = useDaemonRegistry();
|
|
||||||
const runtime = getHostRuntimeStore();
|
|
||||||
|
|
||||||
const runtimeVersion = useSyncExternalStore(
|
|
||||||
(onStoreChange) => runtime.subscribeAll(onStoreChange),
|
|
||||||
() => runtime.getVersion(),
|
|
||||||
() => runtime.getVersion()
|
|
||||||
);
|
|
||||||
|
|
||||||
const connectionStates = useMemo(
|
|
||||||
() => buildConnectionStates({ daemons, runtime }),
|
|
||||||
[daemons, runtime, runtimeVersion]
|
|
||||||
);
|
|
||||||
|
|
||||||
const value = useMemo<DaemonConnectionsContextValue>(
|
|
||||||
() => ({
|
|
||||||
connectionStates,
|
|
||||||
isLoading: registryLoading,
|
|
||||||
}),
|
|
||||||
[connectionStates, registryLoading]
|
|
||||||
);
|
|
||||||
|
|
||||||
return (
|
|
||||||
<DaemonConnectionsContext.Provider value={value}>
|
|
||||||
{children}
|
|
||||||
</DaemonConnectionsContext.Provider>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useMemo, useCallback } from "react";
|
import { useMemo, useCallback, useSyncExternalStore } from "react";
|
||||||
import { useShallow } from "zustand/shallow";
|
import { useShallow } from "zustand/shallow";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import type { AgentDirectoryEntry } from "@/types/agent-directory";
|
import type { AgentDirectoryEntry } from "@/types/agent-directory";
|
||||||
import type { Agent } from "@/stores/session-store";
|
import type { Agent } from "@/stores/session-store";
|
||||||
@@ -20,8 +20,13 @@ export interface AggregatedAgentsResult {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useAggregatedAgents(): AggregatedAgentsResult {
|
export function useAggregatedAgents(): AggregatedAgentsResult {
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { daemons } = useDaemonRegistry();
|
||||||
const runtime = getHostRuntimeStore();
|
const runtime = getHostRuntimeStore();
|
||||||
|
const runtimeVersion = useSyncExternalStore(
|
||||||
|
(onStoreChange) => runtime.subscribeAll(onStoreChange),
|
||||||
|
() => runtime.getVersion(),
|
||||||
|
() => runtime.getVersion()
|
||||||
|
);
|
||||||
|
|
||||||
const sessionAgents = useSessionStore(
|
const sessionAgents = useSessionStore(
|
||||||
useShallow((state) => {
|
useShallow((state) => {
|
||||||
@@ -39,13 +44,16 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
|
|||||||
|
|
||||||
const result = useMemo(() => {
|
const result = useMemo(() => {
|
||||||
const allAgents: AggregatedAgent[] = [];
|
const allAgents: AggregatedAgent[] = [];
|
||||||
|
const serverLabelById = new Map(
|
||||||
|
daemons.map((daemon) => [daemon.serverId, daemon.label] as const)
|
||||||
|
);
|
||||||
|
|
||||||
// Derive agent directory from all sessions
|
// Derive agent directory from all sessions
|
||||||
for (const [serverId, agents] of Object.entries(sessionAgents)) {
|
for (const [serverId, agents] of Object.entries(sessionAgents)) {
|
||||||
if (!agents || agents.size === 0) {
|
if (!agents || agents.size === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId;
|
const serverLabel = serverLabelById.get(serverId) ?? serverId;
|
||||||
for (const agent of agents.values()) {
|
for (const agent of agents.values()) {
|
||||||
const nextAgent: AggregatedAgent = {
|
const nextAgent: AggregatedAgent = {
|
||||||
id: agent.id,
|
id: agent.id,
|
||||||
@@ -85,11 +93,12 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
|
|||||||
const hasAnyData = allAgents.length > 0;
|
const hasAnyData = allAgents.length > 0;
|
||||||
|
|
||||||
// Align list loading with the runtime directory-sync machine.
|
// Align list loading with the runtime directory-sync machine.
|
||||||
const isLoading = Array.from(connectionStates.values()).some(
|
const isLoading = daemons.some((daemon) => {
|
||||||
(connection) =>
|
const status =
|
||||||
connection.agentDirectoryStatus === "initial_loading" ||
|
runtime.getSnapshot(daemon.serverId)?.agentDirectoryStatus ??
|
||||||
connection.agentDirectoryStatus === "revalidating"
|
"initial_loading";
|
||||||
);
|
return status === "initial_loading" || status === "revalidating";
|
||||||
|
});
|
||||||
const isInitialLoad = isLoading && !hasAnyData;
|
const isInitialLoad = isLoading && !hasAnyData;
|
||||||
const isRevalidating = isLoading && hasAnyData;
|
const isRevalidating = isLoading && hasAnyData;
|
||||||
|
|
||||||
@@ -99,7 +108,7 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
|
|||||||
isInitialLoad,
|
isInitialLoad,
|
||||||
isRevalidating,
|
isRevalidating,
|
||||||
};
|
};
|
||||||
}, [sessionAgents, connectionStates]);
|
}, [daemons, runtime, runtimeVersion, sessionAgents]);
|
||||||
|
|
||||||
return {
|
return {
|
||||||
...result,
|
...result,
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { useSessionStore, type Agent } from "@/stores/session-store";
|
import { useSessionStore, type Agent } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
getHostRuntimeStore,
|
getHostRuntimeStore,
|
||||||
@@ -37,7 +37,7 @@ function toAggregatedAgent(params: {
|
|||||||
export function useAllAgentsList(options?: {
|
export function useAllAgentsList(options?: {
|
||||||
serverId?: string | null;
|
serverId?: string | null;
|
||||||
}): AggregatedAgentsResult {
|
}): AggregatedAgentsResult {
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { daemons } = useDaemonRegistry();
|
||||||
const runtime = getHostRuntimeStore();
|
const runtime = getHostRuntimeStore();
|
||||||
|
|
||||||
const serverId = useMemo(() => {
|
const serverId = useMemo(() => {
|
||||||
@@ -64,7 +64,8 @@ export function useAllAgentsList(options?: {
|
|||||||
if (!serverId || !liveAgents) {
|
if (!serverId || !liveAgents) {
|
||||||
return [];
|
return [];
|
||||||
}
|
}
|
||||||
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId;
|
const serverLabel =
|
||||||
|
daemons.find((daemon) => daemon.serverId === serverId)?.label ?? serverId;
|
||||||
const list: AggregatedAgent[] = [];
|
const list: AggregatedAgent[] = [];
|
||||||
|
|
||||||
for (const agent of liveAgents.values()) {
|
for (const agent of liveAgents.values()) {
|
||||||
@@ -95,7 +96,7 @@ export function useAllAgentsList(options?: {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return list;
|
return list;
|
||||||
}, [connectionStates, liveAgents, serverId]);
|
}, [daemons, liveAgents, serverId]);
|
||||||
|
|
||||||
const isDirectoryLoading = Boolean(serverId && isHostRuntimeDirectoryLoading(snapshot));
|
const isDirectoryLoading = Boolean(serverId && isHostRuntimeDirectoryLoading(snapshot));
|
||||||
const isInitialLoad = isDirectoryLoading && agents.length === 0;
|
const isInitialLoad = isDirectoryLoading && agents.length === 0;
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useCallback, useMemo } from "react";
|
import { useCallback, useMemo } from "react";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { useSessionStore, type Agent } from "@/stores/session-store";
|
import { useSessionStore, type Agent } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
getHostRuntimeStore,
|
getHostRuntimeStore,
|
||||||
@@ -149,7 +149,7 @@ export function useSidebarAgentsList(options?: {
|
|||||||
serverId?: string | null;
|
serverId?: string | null;
|
||||||
selectedProjectFilterKeys?: string[];
|
selectedProjectFilterKeys?: string[];
|
||||||
}): SidebarAgentsListResult {
|
}): SidebarAgentsListResult {
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { daemons } = useDaemonRegistry();
|
||||||
const runtime = getHostRuntimeStore();
|
const runtime = getHostRuntimeStore();
|
||||||
const serverId = useMemo(() => {
|
const serverId = useMemo(() => {
|
||||||
const value = options?.serverId;
|
const value = options?.serverId;
|
||||||
@@ -184,7 +184,8 @@ export function useSidebarAgentsList(options?: {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId;
|
const serverLabel =
|
||||||
|
daemons.find((daemon) => daemon.serverId === serverId)?.label ?? serverId;
|
||||||
const seenAgentIds = new Set<string>();
|
const seenAgentIds = new Set<string>();
|
||||||
const byProject = new Map<string, SidebarProjectFilterOption>();
|
const byProject = new Map<string, SidebarProjectFilterOption>();
|
||||||
const mergedEntries: SidebarAgentListEntry[] = [];
|
const mergedEntries: SidebarAgentListEntry[] = [];
|
||||||
@@ -261,7 +262,7 @@ export function useSidebarAgentsList(options?: {
|
|||||||
hasAnyData: ordered.entries.length > 0,
|
hasAnyData: ordered.entries.length > 0,
|
||||||
hasMoreEntries: ordered.hasMore,
|
hasMoreEntries: ordered.hasMore,
|
||||||
};
|
};
|
||||||
}, [connectionStates, liveAgents, selectedProjectFilterKeys, serverId]);
|
}, [daemons, liveAgents, selectedProjectFilterKeys, serverId]);
|
||||||
|
|
||||||
const refreshAll = useCallback(() => {
|
const refreshAll = useCallback(() => {
|
||||||
if (!serverId || snapshot?.connectionStatus !== "online") {
|
if (!serverId || snapshot?.connectionStatus !== "online") {
|
||||||
|
|||||||
@@ -5,7 +5,6 @@ import {
|
|||||||
type FetchAgentsOptions,
|
type FetchAgentsOptions,
|
||||||
} from "@server/client/daemon-client";
|
} from "@server/client/daemon-client";
|
||||||
import type { HostConnection, HostProfile } from "@/contexts/daemon-registry-context";
|
import type { HostConnection, HostProfile } from "@/contexts/daemon-registry-context";
|
||||||
import type { ActiveConnection } from "@/contexts/daemon-connections-context";
|
|
||||||
import {
|
import {
|
||||||
buildDaemonWebSocketUrl,
|
buildDaemonWebSocketUrl,
|
||||||
buildRelayWebSocketUrl,
|
buildRelayWebSocketUrl,
|
||||||
@@ -28,6 +27,10 @@ export type HostRuntimeConnectionStatus =
|
|||||||
| "offline"
|
| "offline"
|
||||||
| "error";
|
| "error";
|
||||||
|
|
||||||
|
export type ActiveConnection =
|
||||||
|
| { type: "direct"; endpoint: string; display: string }
|
||||||
|
| { type: "relay"; endpoint: string; display: "relay" };
|
||||||
|
|
||||||
export type HostRuntimeAgentDirectoryStatus =
|
export type HostRuntimeAgentDirectoryStatus =
|
||||||
| "idle"
|
| "idle"
|
||||||
| "initial_loading"
|
| "initial_loading"
|
||||||
|
|||||||
@@ -40,7 +40,7 @@ import {
|
|||||||
ExplorerSidebarAnimationProvider,
|
ExplorerSidebarAnimationProvider,
|
||||||
} 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 { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
useHostRuntimeSession,
|
useHostRuntimeSession,
|
||||||
@@ -112,7 +112,7 @@ export function AgentReadyScreen({
|
|||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const resolvedAgentId = agentId?.trim() || undefined;
|
const resolvedAgentId = agentId?.trim() || undefined;
|
||||||
const resolvedServerId = serverId?.trim() || undefined;
|
const resolvedServerId = serverId?.trim() || undefined;
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { daemons } = useDaemonRegistry();
|
||||||
const runtimeServerId = resolvedServerId ?? "";
|
const runtimeServerId = resolvedServerId ?? "";
|
||||||
const {
|
const {
|
||||||
snapshot: runtimeSnapshot,
|
snapshot: runtimeSnapshot,
|
||||||
@@ -121,12 +121,12 @@ export function AgentReadyScreen({
|
|||||||
} = useHostRuntimeSession(runtimeServerId);
|
} = useHostRuntimeSession(runtimeServerId);
|
||||||
|
|
||||||
const connectionServerId = resolvedServerId ?? null;
|
const connectionServerId = resolvedServerId ?? null;
|
||||||
const connection = connectionServerId
|
const daemon = connectionServerId
|
||||||
? connectionStates.get(connectionServerId)
|
? daemons.find((entry) => entry.serverId === connectionServerId) ?? null
|
||||||
: null;
|
: null;
|
||||||
const serverLabel =
|
const serverLabel =
|
||||||
connection?.daemon.label ?? connectionServerId ?? "Selected host";
|
daemon?.label ?? connectionServerId ?? "Selected host";
|
||||||
const isUnknownDaemon = Boolean(connectionServerId && !connection);
|
const isUnknownDaemon = Boolean(connectionServerId && !daemon);
|
||||||
const connectionStatus: HostRuntimeConnectionStatus =
|
const connectionStatus: HostRuntimeConnectionStatus =
|
||||||
runtimeSnapshot?.connectionStatus ??
|
runtimeSnapshot?.connectionStatus ??
|
||||||
(isUnknownDaemon ? "offline" : "connecting");
|
(isUnknownDaemon ? "offline" : "connecting");
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { useCallback, useEffect, useMemo, useReducer, useRef, useState } from 'react'
|
import { useCallback, useEffect, useMemo, useReducer, useRef, useState, useSyncExternalStore } from 'react'
|
||||||
import { createNameId } from 'mnemonic-id'
|
import { createNameId } from 'mnemonic-id'
|
||||||
import type { ImageAttachment } from '@/components/message-input'
|
import type { ImageAttachment } from '@/components/message-input'
|
||||||
import { View, Text, Pressable, ScrollView, Keyboard, Platform } from 'react-native'
|
import { View, Text, Pressable, ScrollView, Keyboard, Platform } from 'react-native'
|
||||||
@@ -26,7 +26,6 @@ import {
|
|||||||
checkoutStatusQueryKey,
|
checkoutStatusQueryKey,
|
||||||
} from '@/hooks/use-checkout-status-query'
|
} from '@/hooks/use-checkout-status-query'
|
||||||
import { useAllAgentsList } from '@/hooks/use-all-agents-list'
|
import { useAllAgentsList } from '@/hooks/use-all-agents-list'
|
||||||
import { useDaemonConnections } from '@/contexts/daemon-connections-context'
|
|
||||||
import { useDaemonRegistry } from '@/contexts/daemon-registry-context'
|
import { useDaemonRegistry } from '@/contexts/daemon-registry-context'
|
||||||
import { buildBranchComboOptions, normalizeBranchOptionName } from '@/utils/branch-suggestions'
|
import { buildBranchComboOptions, normalizeBranchOptionName } from '@/utils/branch-suggestions'
|
||||||
import { shortenPath } from '@/utils/shorten-path'
|
import { shortenPath } from '@/utils/shorten-path'
|
||||||
@@ -35,7 +34,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 { getHostRuntimeStore, 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'
|
||||||
@@ -139,8 +138,13 @@ function DraftAgentScreenContent({
|
|||||||
const { theme } = useUnistyles()
|
const { theme } = useUnistyles()
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const { connectionStates } = useDaemonConnections()
|
|
||||||
const { daemons } = useDaemonRegistry()
|
const { daemons } = useDaemonRegistry()
|
||||||
|
const runtime = getHostRuntimeStore()
|
||||||
|
const runtimeVersion = useSyncExternalStore(
|
||||||
|
(onStoreChange) => runtime.subscribeAll(onStoreChange),
|
||||||
|
() => runtime.getVersion(),
|
||||||
|
() => runtime.getVersion()
|
||||||
|
)
|
||||||
const params = useLocalSearchParams<DraftAgentParams>()
|
const params = useLocalSearchParams<DraftAgentParams>()
|
||||||
|
|
||||||
const { height: keyboardHeight } = useReanimatedKeyboardAnimation()
|
const { height: keyboardHeight } = useReanimatedKeyboardAnimation()
|
||||||
@@ -174,11 +178,11 @@ function DraftAgentScreenContent({
|
|||||||
if (daemons.length === 0) return []
|
if (daemons.length === 0) return []
|
||||||
const out: string[] = []
|
const out: string[] = []
|
||||||
for (const daemon of daemons) {
|
for (const daemon of daemons) {
|
||||||
const status = connectionStates.get(daemon.serverId)?.status ?? 'idle'
|
const status = runtime.getSnapshot(daemon.serverId)?.connectionStatus ?? 'connecting'
|
||||||
if (status === 'online') out.push(daemon.serverId)
|
if (status === 'online') out.push(daemon.serverId)
|
||||||
}
|
}
|
||||||
return out
|
return out
|
||||||
}, [connectionStates, daemons])
|
}, [daemons, runtime, runtimeVersion])
|
||||||
|
|
||||||
const initialValues = useMemo((): CreateAgentInitialValues => {
|
const initialValues = useMemo((): CreateAgentInitialValues => {
|
||||||
const values: CreateAgentInitialValues = {}
|
const values: CreateAgentInitialValues = {}
|
||||||
@@ -228,7 +232,6 @@ function DraftAgentScreenContent({
|
|||||||
isCreateFlow: true,
|
isCreateFlow: true,
|
||||||
onlineServerIds,
|
onlineServerIds,
|
||||||
})
|
})
|
||||||
const hostEntry = selectedServerId ? connectionStates.get(selectedServerId) : undefined
|
|
||||||
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
const isMobile = UnistylesRuntime.breakpoint === 'xs' || UnistylesRuntime.breakpoint === 'sm'
|
||||||
const mobileView = usePanelStore((state) => state.mobileView)
|
const mobileView = usePanelStore((state) => state.mobileView)
|
||||||
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen)
|
const desktopFileExplorerOpen = usePanelStore((state) => state.desktop.fileExplorerOpen)
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import { useRouter } from "expo-router";
|
|||||||
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
import { StyleSheet, useUnistyles } from "react-native-unistyles";
|
||||||
import { BackHeader } from "@/components/headers/back-header";
|
import { BackHeader } from "@/components/headers/back-header";
|
||||||
import { useSessionDirectory } from "@/hooks/use-session-directory";
|
import { useSessionDirectory } from "@/hooks/use-session-directory";
|
||||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
import { useDaemonRegistry } from "@/contexts/daemon-registry-context";
|
||||||
import type { Agent } from "@/contexts/session-context";
|
import type { Agent } from "@/contexts/session-context";
|
||||||
import {
|
import {
|
||||||
buildHostAgentDetailRoute,
|
buildHostAgentDetailRoute,
|
||||||
@@ -21,7 +21,7 @@ type AgentMatch = {
|
|||||||
export function LegacyAgentIdScreen({ agentId }: { agentId: string }) {
|
export function LegacyAgentIdScreen({ agentId }: { agentId: string }) {
|
||||||
const router = useRouter();
|
const router = useRouter();
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { daemons } = useDaemonRegistry();
|
||||||
const sessionDirectory = useSessionDirectory();
|
const sessionDirectory = useSessionDirectory();
|
||||||
const resolvedAgentId = typeof agentId === "string" ? agentId.trim() : undefined;
|
const resolvedAgentId = typeof agentId === "string" ? agentId.trim() : undefined;
|
||||||
|
|
||||||
@@ -39,12 +39,13 @@ export function LegacyAgentIdScreen({ agentId }: { agentId: string }) {
|
|||||||
if (!agent) {
|
if (!agent) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId;
|
const serverLabel =
|
||||||
|
daemons.find((daemon) => daemon.serverId === serverId)?.label ?? serverId;
|
||||||
results.push({ serverId, serverLabel, agent });
|
results.push({ serverId, serverLabel, agent });
|
||||||
});
|
});
|
||||||
|
|
||||||
return results;
|
return results;
|
||||||
}, [resolvedAgentId, sessionDirectory, connectionStates]);
|
}, [daemons, resolvedAgentId, sessionDirectory]);
|
||||||
|
|
||||||
const hasSessions = sessionDirectory.size > 0;
|
const hasSessions = sessionDirectory.size > 0;
|
||||||
const isRedirecting = Boolean(resolvedAgentId && matches.length === 1);
|
const isRedirecting = Boolean(resolvedAgentId && matches.length === 1);
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import type { ConnectionStatus } from "@/contexts/daemon-connections-context";
|
import type { HostRuntimeConnectionStatus } from "@/runtime/host-runtime";
|
||||||
import { assertUnreachable } from "./exhaustive";
|
import { assertUnreachable } from "./exhaustive";
|
||||||
|
|
||||||
export function formatConnectionStatus(status: ConnectionStatus): string {
|
export function formatConnectionStatus(status: HostRuntimeConnectionStatus): string {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "online":
|
case "online":
|
||||||
return "Online";
|
return "Online";
|
||||||
@@ -20,7 +20,9 @@ export function formatConnectionStatus(status: ConnectionStatus): string {
|
|||||||
|
|
||||||
export type ConnectionStatusTone = "success" | "warning" | "error" | "muted";
|
export type ConnectionStatusTone = "success" | "warning" | "error" | "muted";
|
||||||
|
|
||||||
export function getConnectionStatusTone(status: ConnectionStatus): ConnectionStatusTone {
|
export function getConnectionStatusTone(
|
||||||
|
status: HostRuntimeConnectionStatus
|
||||||
|
): ConnectionStatusTone {
|
||||||
switch (status) {
|
switch (status) {
|
||||||
case "online":
|
case "online":
|
||||||
return "success";
|
return "success";
|
||||||
|
|||||||
Reference in New Issue
Block a user