fix(app): replace greedy host runtime subscriptions with targeted hooks
useHostRuntimeSession returned the full HostRuntimeSnapshot, causing every consumer to rerender on any internal state change (probe cycles, client generation bumps). Replace with targeted hooks that return primitives/stable references so useSyncExternalStore skips rerenders when the subscribed value hasn't actually changed. New hooks: useHostRuntimeClient, useHostRuntimeConnectionStatus, useHostRuntimeLastError, useHostRuntimeAgentDirectoryStatus, useHostRuntimeIsDirectoryLoading. Existing useHostRuntimeIsConnected already followed this pattern. useHostRuntimeSnapshot kept only for settings-screen where probe data is legitimately needed.
This commit is contained in:
@@ -23,7 +23,7 @@ import {
|
|||||||
getHostRuntimeStore,
|
getHostRuntimeStore,
|
||||||
useHosts,
|
useHosts,
|
||||||
useHostMutations,
|
useHostMutations,
|
||||||
useHostRuntimeSession,
|
useHostRuntimeClient,
|
||||||
} from "@/runtime/host-runtime";
|
} from "@/runtime/host-runtime";
|
||||||
import { SessionProvider } from "@/contexts/session-context";
|
import { SessionProvider } from "@/contexts/session-context";
|
||||||
import type { HostProfile } from "@/types/host-connection";
|
import type { HostProfile } from "@/types/host-connection";
|
||||||
@@ -176,7 +176,7 @@ function PushNotificationRouter() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function ManagedDaemonSession({ daemon }: { daemon: HostProfile }) {
|
function ManagedDaemonSession({ daemon }: { daemon: HostProfile }) {
|
||||||
const { client } = useHostRuntimeSession(daemon.serverId);
|
const client = useHostRuntimeClient(daemon.serverId);
|
||||||
|
|
||||||
if (!client) {
|
if (!client) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { useEffect, useRef } from "react";
|
import { useEffect, useRef } from "react";
|
||||||
import { useLocalSearchParams, useRouter } from "expo-router";
|
import { useLocalSearchParams, useRouter } from "expo-router";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||||
import {
|
import {
|
||||||
buildHostRootRoute,
|
buildHostRootRoute,
|
||||||
buildHostWorkspaceAgentRoute,
|
buildHostWorkspaceAgentRoute,
|
||||||
@@ -16,7 +16,8 @@ export default function HostAgentReadyRoute() {
|
|||||||
const redirectedRef = useRef(false);
|
const redirectedRef = useRef(false);
|
||||||
const serverId = typeof params.serverId === "string" ? params.serverId : "";
|
const serverId = typeof params.serverId === "string" ? params.serverId : "";
|
||||||
const agentId = typeof params.agentId === "string" ? params.agentId : "";
|
const agentId = typeof params.agentId === "string" ? params.agentId : "";
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
const agentCwd = useSessionStore((state) => {
|
const agentCwd = useSessionStore((state) => {
|
||||||
if (!serverId || !agentId) {
|
if (!serverId || !agentId) {
|
||||||
return null;
|
return null;
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import { Tooltip, TooltipContent, TooltipTrigger } from '@/components/ui/tooltip
|
|||||||
import { Shortcut } from '@/components/ui/shortcut'
|
import { Shortcut } from '@/components/ui/shortcut'
|
||||||
import { Autocomplete } from '@/components/ui/autocomplete'
|
import { Autocomplete } from '@/components/ui/autocomplete'
|
||||||
import { useAgentAutocomplete } from '@/hooks/use-agent-autocomplete'
|
import { useAgentAutocomplete } from '@/hooks/use-agent-autocomplete'
|
||||||
import { useHostRuntimeSession } from '@/runtime/host-runtime'
|
import { useHostRuntimeAgentDirectoryStatus, useHostRuntimeClient, useHostRuntimeIsConnected } from '@/runtime/host-runtime'
|
||||||
import {
|
import {
|
||||||
deleteAttachments,
|
deleteAttachments,
|
||||||
persistAttachmentFromBlob,
|
persistAttachmentFromBlob,
|
||||||
@@ -100,14 +100,16 @@ export function AgentInputArea({
|
|||||||
const insets = useSafeAreaInsets()
|
const insets = useSafeAreaInsets()
|
||||||
const isScreenFocused = useIsFocused()
|
const isScreenFocused = useIsFocused()
|
||||||
|
|
||||||
const { client, isConnected, snapshot } = useHostRuntimeSession(serverId)
|
const client = useHostRuntimeClient(serverId)
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId)
|
||||||
|
const agentDirectoryStatus = useHostRuntimeAgentDirectoryStatus(serverId)
|
||||||
const toast = useToast()
|
const toast = useToast()
|
||||||
const voice = useVoiceOptional()
|
const voice = useVoiceOptional()
|
||||||
const isDictationReady =
|
const isDictationReady =
|
||||||
isConnected &&
|
isConnected &&
|
||||||
(snapshot?.agentDirectoryStatus === 'ready' ||
|
(agentDirectoryStatus === 'ready' ||
|
||||||
snapshot?.agentDirectoryStatus === 'revalidating' ||
|
agentDirectoryStatus === 'revalidating' ||
|
||||||
snapshot?.agentDirectoryStatus === 'error_after_ready')
|
agentDirectoryStatus === 'error_after_ready')
|
||||||
|
|
||||||
const agent = useSessionStore((state) => state.sessions[serverId]?.agents?.get(agentId))
|
const agent = useSessionStore((state) => state.sessions[serverId]?.agents?.get(agentId))
|
||||||
|
|
||||||
|
|||||||
@@ -15,7 +15,7 @@ import { usePathname } from "expo-router";
|
|||||||
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
import { useKeyboardShortcutsStore } from "@/stores/keyboard-shortcuts-store";
|
||||||
import { shortenPath } from "@/utils/shorten-path";
|
import { shortenPath } from "@/utils/shorten-path";
|
||||||
import { useSessionStore } from "@/stores/session-store";
|
import { useSessionStore } from "@/stores/session-store";
|
||||||
import { useHosts, useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHosts, useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||||
import { useOpenProject } from "@/hooks/use-open-project";
|
import { useOpenProject } from "@/hooks/use-open-project";
|
||||||
import { parseServerIdFromPathname } from "@/utils/host-routes";
|
import { parseServerIdFromPathname } from "@/utils/host-routes";
|
||||||
import { buildWorkingDirectorySuggestions } from "@/utils/working-directory-suggestions";
|
import { buildWorkingDirectorySuggestions } from "@/utils/working-directory-suggestions";
|
||||||
@@ -34,7 +34,8 @@ export function ProjectPickerModal() {
|
|||||||
return daemons[0]?.serverId ?? null;
|
return daemons[0]?.serverId ?? null;
|
||||||
}, [pathname, daemons]);
|
}, [pathname, daemons]);
|
||||||
|
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId ?? "");
|
const client = useHostRuntimeClient(serverId ?? "");
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId ?? "");
|
||||||
const workspaces = useSessionStore((state) =>
|
const workspaces = useSessionStore((state) =>
|
||||||
serverId ? state.sessions[serverId]?.workspaces : undefined
|
serverId ? state.sessions[serverId]?.workspaces : undefined
|
||||||
);
|
);
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ import 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 { encodeTerminalKeyInput } from "@server/shared/terminal-key-input";
|
import { encodeTerminalKeyInput } from "@server/shared/terminal-key-input";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||||
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
||||||
import {
|
import {
|
||||||
hasPendingTerminalModifiers,
|
hasPendingTerminalModifiers,
|
||||||
@@ -150,7 +150,8 @@ export function TerminalPane({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
|
|
||||||
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]);
|
||||||
|
|||||||
@@ -5,7 +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 { useHostRuntimeClient, useHostRuntimeIsConnected } from '@/runtime/host-runtime'
|
||||||
import {
|
import {
|
||||||
applyFileMentionReplacement,
|
applyFileMentionReplacement,
|
||||||
findActiveFileMention,
|
findActiveFileMention,
|
||||||
@@ -138,7 +138,8 @@ export function useAgentAutocomplete(input: UseAgentAutocompleteInput): AgentAut
|
|||||||
return agentCwd.trim()
|
return agentCwd.trim()
|
||||||
}, [agentCwd, isDraftContext, queryDraftConfig])
|
}, [agentCwd, isDraftContext, queryDraftConfig])
|
||||||
|
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId)
|
const client = useHostRuntimeClient(serverId)
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId)
|
||||||
|
|
||||||
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 { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } 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,7 +48,8 @@ export function useAgentCommandsQuery({
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
draftConfig,
|
draftConfig,
|
||||||
}: UseAgentCommandsQueryOptions) {
|
}: UseAgentCommandsQueryOptions) {
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: commandsQueryKey(serverId, agentId, draftConfig),
|
queryKey: commandsQueryKey(serverId, agentId, draftConfig),
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import type {
|
|||||||
AgentProvider,
|
AgentProvider,
|
||||||
} from "@server/server/agent/agent-sdk-types";
|
} from "@server/server/agent/agent-sdk-types";
|
||||||
import { useHosts } from "@/runtime/host-runtime";
|
import { useHosts } from "@/runtime/host-runtime";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } 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
|
||||||
@@ -367,7 +367,8 @@ export function useAgentFormState(
|
|||||||
}, [isVisible]);
|
}, [isVisible]);
|
||||||
|
|
||||||
// Session state for provider model listing
|
// Session state for provider model listing
|
||||||
const { client, isConnected } = useHostRuntimeSession(formState.serverId ?? "");
|
const client = useHostRuntimeClient(formState.serverId ?? "");
|
||||||
|
const isConnected = useHostRuntimeIsConnected(formState.serverId ?? "");
|
||||||
|
|
||||||
const availableProvidersQuery = useQuery({
|
const availableProvidersQuery = useQuery({
|
||||||
queryKey: ["availableProviders", formState.serverId],
|
queryKey: ["availableProviders", formState.serverId],
|
||||||
|
|||||||
@@ -3,8 +3,8 @@ import { useHosts } from "@/runtime/host-runtime";
|
|||||||
import { useSessionStore, type Agent } from "@/stores/session-store";
|
import { useSessionStore, type Agent } from "@/stores/session-store";
|
||||||
import {
|
import {
|
||||||
getHostRuntimeStore,
|
getHostRuntimeStore,
|
||||||
isHostRuntimeDirectoryLoading,
|
useHostRuntimeConnectionStatus,
|
||||||
useHostRuntimeSession,
|
useHostRuntimeIsDirectoryLoading,
|
||||||
} from "@/runtime/host-runtime";
|
} from "@/runtime/host-runtime";
|
||||||
import type {
|
import type {
|
||||||
AggregatedAgent,
|
AggregatedAgent,
|
||||||
@@ -88,14 +88,14 @@ export function useAllAgentsList(options?: {
|
|||||||
const liveAgents = useSessionStore((state) =>
|
const liveAgents = useSessionStore((state) =>
|
||||||
serverId ? state.sessions[serverId]?.agents ?? null : null
|
serverId ? state.sessions[serverId]?.agents ?? null : null
|
||||||
);
|
);
|
||||||
const { snapshot } = useHostRuntimeSession(serverId ?? "");
|
const connectionStatus = useHostRuntimeConnectionStatus(serverId ?? "");
|
||||||
|
|
||||||
const refreshAll = useCallback(() => {
|
const refreshAll = useCallback(() => {
|
||||||
if (!serverId || snapshot?.connectionStatus !== "online") {
|
if (!serverId || connectionStatus !== "online") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
void runtime.refreshAgentDirectory({ serverId }).catch(() => undefined);
|
void runtime.refreshAgentDirectory({ serverId }).catch(() => undefined);
|
||||||
}, [runtime, serverId, snapshot?.connectionStatus]);
|
}, [runtime, serverId, connectionStatus]);
|
||||||
|
|
||||||
const agents = useMemo(() => {
|
const agents = useMemo(() => {
|
||||||
if (!serverId || !liveAgents) {
|
if (!serverId || !liveAgents) {
|
||||||
@@ -111,7 +111,7 @@ export function useAllAgentsList(options?: {
|
|||||||
});
|
});
|
||||||
}, [daemons, includeArchived, liveAgents, serverId]);
|
}, [daemons, includeArchived, liveAgents, serverId]);
|
||||||
|
|
||||||
const isDirectoryLoading = Boolean(serverId && isHostRuntimeDirectoryLoading(snapshot));
|
const isDirectoryLoading = useHostRuntimeIsDirectoryLoading(serverId ?? "");
|
||||||
const isInitialLoad = isDirectoryLoading && agents.length === 0;
|
const isInitialLoad = isDirectoryLoading && agents.length === 0;
|
||||||
const isRevalidating = isDirectoryLoading && agents.length > 0;
|
const isRevalidating = isDirectoryLoading && agents.length > 0;
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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 { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } 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,7 +56,8 @@ export function useCheckoutDiffQuery({
|
|||||||
enabled = true,
|
enabled = true,
|
||||||
}: UseCheckoutDiffQueryOptions) {
|
}: UseCheckoutDiffQueryOptions) {
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
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 { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } 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,7 +21,8 @@ export function useCheckoutPrStatusQuery({
|
|||||||
cwd,
|
cwd,
|
||||||
enabled = true,
|
enabled = true,
|
||||||
}: UseCheckoutPrStatusQueryOptions) {
|
}: UseCheckoutPrStatusQueryOptions) {
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: checkoutPrStatusQueryKey(serverId, cwd),
|
queryKey: checkoutPrStatusQueryKey(serverId, cwd),
|
||||||
|
|||||||
@@ -2,7 +2,7 @@ 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 { usePanelStore } from "@/stores/panel-store";
|
import { usePanelStore } from "@/stores/panel-store";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||||
import type { CheckoutStatusResponse } from "@server/shared/messages";
|
import type { CheckoutStatusResponse } from "@server/shared/messages";
|
||||||
import {
|
import {
|
||||||
checkoutStatusRevalidationKey,
|
checkoutStatusRevalidationKey,
|
||||||
@@ -30,7 +30,8 @@ function fetchCheckoutStatus(
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useCheckoutStatusQuery({ serverId, cwd }: UseCheckoutStatusQueryOptions) {
|
export function useCheckoutStatusQuery({ serverId, cwd }: UseCheckoutStatusQueryOptions) {
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
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);
|
||||||
@@ -87,7 +88,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 } = useHostRuntimeSession(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
|
|
||||||
return useQuery({
|
return useQuery({
|
||||||
queryKey: checkoutStatusQueryKey(serverId, cwd),
|
queryKey: checkoutStatusQueryKey(serverId, cwd),
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { router } from "expo-router";
|
import { router } from "expo-router";
|
||||||
import { useCallback } from "react";
|
import { useCallback } from "react";
|
||||||
import { useToast } from "@/contexts/toast-context";
|
import { useToast } from "@/contexts/toast-context";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient } from "@/runtime/host-runtime";
|
||||||
import {
|
import {
|
||||||
normalizeWorkspaceDescriptor,
|
normalizeWorkspaceDescriptor,
|
||||||
useSessionStore,
|
useSessionStore,
|
||||||
@@ -13,7 +13,7 @@ export function useOpenProject(
|
|||||||
): (path: string) => Promise<boolean> {
|
): (path: string) => Promise<boolean> {
|
||||||
const normalizedServerId = serverId?.trim() ?? "";
|
const normalizedServerId = serverId?.trim() ?? "";
|
||||||
const toast = useToast();
|
const toast = useToast();
|
||||||
const { client } = useHostRuntimeSession(normalizedServerId);
|
const client = useHostRuntimeClient(normalizedServerId);
|
||||||
const mergeWorkspaces = useSessionStore((state) => state.mergeWorkspaces);
|
const mergeWorkspaces = useSessionStore((state) => state.mergeWorkspaces);
|
||||||
const setHasHydratedWorkspaces = useSessionStore(
|
const setHasHydratedWorkspaces = useSessionStore(
|
||||||
(state) => state.setHasHydratedWorkspaces
|
(state) => state.setHasHydratedWorkspaces
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { useQuery } from "@tanstack/react-query";
|
import { useQuery } from "@tanstack/react-query";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } 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,7 +12,8 @@ interface UseProjectIconQueryOptions {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export function useProjectIconQuery({ serverId, cwd }: UseProjectIconQueryOptions) {
|
export function useProjectIconQuery({ serverId, cwd }: UseProjectIconQueryOptions) {
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId);
|
const client = useHostRuntimeClient(serverId);
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId);
|
||||||
|
|
||||||
const query = useQuery({
|
const query = useQuery({
|
||||||
queryKey: projectIconQueryKey(serverId, cwd),
|
queryKey: projectIconQueryKey(serverId, cwd),
|
||||||
|
|||||||
@@ -23,7 +23,10 @@ import { useKeyboardShiftStyle } from "@/hooks/use-keyboard-shift-style";
|
|||||||
import { usePaneContext } from "@/panels/pane-context";
|
import { usePaneContext } from "@/panels/pane-context";
|
||||||
import type { PanelDescriptor, PanelRegistration } from "@/panels/panel-registry";
|
import type { PanelDescriptor, PanelRegistration } from "@/panels/panel-registry";
|
||||||
import {
|
import {
|
||||||
useHostRuntimeSession,
|
useHostRuntimeClient,
|
||||||
|
useHostRuntimeConnectionStatus,
|
||||||
|
useHostRuntimeIsConnected,
|
||||||
|
useHostRuntimeLastError,
|
||||||
useHosts,
|
useHosts,
|
||||||
type HostRuntimeConnectionStatus,
|
type HostRuntimeConnectionStatus,
|
||||||
} from "@/runtime/host-runtime";
|
} from "@/runtime/host-runtime";
|
||||||
@@ -154,11 +157,10 @@ function AgentPanelContent({
|
|||||||
const resolvedServerId = serverId.trim() || undefined;
|
const resolvedServerId = serverId.trim() || undefined;
|
||||||
const daemons = useHosts();
|
const daemons = useHosts();
|
||||||
const runtimeServerId = resolvedServerId ?? "";
|
const runtimeServerId = resolvedServerId ?? "";
|
||||||
const {
|
const runtimeClient = useHostRuntimeClient(runtimeServerId);
|
||||||
snapshot: runtimeSnapshot,
|
const runtimeIsConnected = useHostRuntimeIsConnected(runtimeServerId);
|
||||||
client: runtimeClient,
|
const runtimeConnectionStatus = useHostRuntimeConnectionStatus(runtimeServerId);
|
||||||
isConnected: runtimeIsConnected,
|
const runtimeLastError = useHostRuntimeLastError(runtimeServerId);
|
||||||
} = useHostRuntimeSession(runtimeServerId);
|
|
||||||
|
|
||||||
const connectionServerId = resolvedServerId ?? null;
|
const connectionServerId = resolvedServerId ?? null;
|
||||||
const daemon = connectionServerId
|
const daemon = connectionServerId
|
||||||
@@ -168,9 +170,10 @@ function AgentPanelContent({
|
|||||||
daemon?.label ?? connectionServerId ?? "Selected host";
|
daemon?.label ?? connectionServerId ?? "Selected host";
|
||||||
const isUnknownDaemon = Boolean(connectionServerId && !daemon);
|
const isUnknownDaemon = Boolean(connectionServerId && !daemon);
|
||||||
const connectionStatus: HostRuntimeConnectionStatus =
|
const connectionStatus: HostRuntimeConnectionStatus =
|
||||||
runtimeSnapshot?.connectionStatus ??
|
isUnknownDaemon && runtimeConnectionStatus === "connecting"
|
||||||
(isUnknownDaemon ? "offline" : "connecting");
|
? "offline"
|
||||||
const lastConnectionError = runtimeSnapshot?.lastError ?? null;
|
: runtimeConnectionStatus;
|
||||||
|
const lastConnectionError = runtimeLastError;
|
||||||
|
|
||||||
if (!resolvedServerId || !runtimeClient) {
|
if (!resolvedServerId || !runtimeClient) {
|
||||||
return (
|
return (
|
||||||
@@ -208,7 +211,7 @@ function AgentPanelBody({
|
|||||||
serverId: string;
|
serverId: string;
|
||||||
agentId?: string;
|
agentId?: string;
|
||||||
isPaneFocused: boolean;
|
isPaneFocused: boolean;
|
||||||
client: NonNullable<ReturnType<typeof useHostRuntimeSession>["client"]>;
|
client: NonNullable<ReturnType<typeof useHostRuntimeClient>>;
|
||||||
isConnected: boolean;
|
isConnected: boolean;
|
||||||
connectionStatus: HostRuntimeConnectionStatus;
|
connectionStatus: HostRuntimeConnectionStatus;
|
||||||
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
onOpenWorkspaceFile?: (input: { filePath: string }) => void;
|
||||||
|
|||||||
@@ -1755,17 +1755,13 @@ export function useHostRuntimeSnapshot(
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useHostRuntimeSession(serverId: string): {
|
export function useHostRuntimeClient(serverId: string): DaemonClient | null {
|
||||||
snapshot: HostRuntimeSnapshot | null;
|
const store = getHostRuntimeStore();
|
||||||
client: DaemonClient | null;
|
return useSyncExternalStore(
|
||||||
isConnected: boolean;
|
(onStoreChange) => store.subscribe(serverId, onStoreChange),
|
||||||
} {
|
() => store.getSnapshot(serverId)?.client ?? null,
|
||||||
const snapshot = useHostRuntimeSnapshot(serverId);
|
() => store.getSnapshot(serverId)?.client ?? null
|
||||||
return {
|
);
|
||||||
snapshot,
|
|
||||||
client: snapshot?.client ?? null,
|
|
||||||
isConnected: isHostRuntimeConnected(snapshot),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export function useHostRuntimeIsConnected(serverId: string): boolean {
|
export function useHostRuntimeIsConnected(serverId: string): boolean {
|
||||||
@@ -1777,6 +1773,46 @@ export function useHostRuntimeIsConnected(serverId: string): boolean {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function useHostRuntimeConnectionStatus(
|
||||||
|
serverId: string
|
||||||
|
): HostRuntimeConnectionStatus {
|
||||||
|
const store = getHostRuntimeStore();
|
||||||
|
return useSyncExternalStore(
|
||||||
|
(onStoreChange) => store.subscribe(serverId, onStoreChange),
|
||||||
|
() => store.getSnapshot(serverId)?.connectionStatus ?? "connecting",
|
||||||
|
() => store.getSnapshot(serverId)?.connectionStatus ?? "connecting"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHostRuntimeLastError(serverId: string): string | null {
|
||||||
|
const store = getHostRuntimeStore();
|
||||||
|
return useSyncExternalStore(
|
||||||
|
(onStoreChange) => store.subscribe(serverId, onStoreChange),
|
||||||
|
() => store.getSnapshot(serverId)?.lastError ?? null,
|
||||||
|
() => store.getSnapshot(serverId)?.lastError ?? null
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHostRuntimeAgentDirectoryStatus(
|
||||||
|
serverId: string
|
||||||
|
): HostRuntimeAgentDirectoryStatus {
|
||||||
|
const store = getHostRuntimeStore();
|
||||||
|
return useSyncExternalStore(
|
||||||
|
(onStoreChange) => store.subscribe(serverId, onStoreChange),
|
||||||
|
() => store.getSnapshot(serverId)?.agentDirectoryStatus ?? "idle",
|
||||||
|
() => store.getSnapshot(serverId)?.agentDirectoryStatus ?? "idle"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useHostRuntimeIsDirectoryLoading(serverId: string): boolean {
|
||||||
|
const store = getHostRuntimeStore();
|
||||||
|
return useSyncExternalStore(
|
||||||
|
(onStoreChange) => store.subscribe(serverId, onStoreChange),
|
||||||
|
() => isHostRuntimeDirectoryLoading(store.getSnapshot(serverId)),
|
||||||
|
() => isHostRuntimeDirectoryLoading(store.getSnapshot(serverId))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
export function useHosts(): HostProfile[] {
|
export function useHosts(): HostProfile[] {
|
||||||
const store = getHostRuntimeStore();
|
const store = getHostRuntimeStore();
|
||||||
return useSyncExternalStore(
|
return useSyncExternalStore(
|
||||||
|
|||||||
@@ -33,7 +33,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 { generateDraftId } from '@/stores/draft-keys'
|
import { generateDraftId } from '@/stores/draft-keys'
|
||||||
import { getHostRuntimeStore, useHostRuntimeSession } from '@/runtime/host-runtime'
|
import { getHostRuntimeStore, useHostRuntimeClient, useHostRuntimeIsConnected } 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'
|
||||||
@@ -317,9 +317,8 @@ function DraftAgentScreenContent({
|
|||||||
return collectAgentWorkingDirectorySuggestions([...liveSources, ...fetchedSources])
|
return collectAgentWorkingDirectorySuggestions([...liveSources, ...fetchedSources])
|
||||||
}, [allAgents, sessionAgents])
|
}, [allAgents, sessionAgents])
|
||||||
|
|
||||||
const { client: runtimeClient, isConnected: isHostOnline } = useHostRuntimeSession(
|
const runtimeClient = useHostRuntimeClient(selectedServerId ?? '')
|
||||||
selectedServerId ?? ''
|
const isHostOnline = useHostRuntimeIsConnected(selectedServerId ?? '')
|
||||||
)
|
|
||||||
const sessionClient = runtimeClient
|
const sessionClient = runtimeClient
|
||||||
const trimmedWorkingDir = workingDir.trim()
|
const trimmedWorkingDir = workingDir.trim()
|
||||||
const shouldInspectRepo = trimmedWorkingDir.length > 0
|
const shouldInspectRepo = trimmedWorkingDir.length > 0
|
||||||
|
|||||||
@@ -24,7 +24,9 @@ import { useSessionStore } from "@/stores/session-store";
|
|||||||
import {
|
import {
|
||||||
getHostRuntimeStore,
|
getHostRuntimeStore,
|
||||||
isHostRuntimeConnected,
|
isHostRuntimeConnected,
|
||||||
useHostRuntimeSession,
|
useHostRuntimeClient,
|
||||||
|
useHostRuntimeIsConnected,
|
||||||
|
useHostRuntimeSnapshot,
|
||||||
} from "@/runtime/host-runtime";
|
} 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";
|
||||||
@@ -992,9 +994,9 @@ function HostDetailModal({
|
|||||||
const connections = host?.connections ?? [];
|
const connections = host?.connections ?? [];
|
||||||
|
|
||||||
// Restart logic (moved from DaemonCard)
|
// Restart logic (moved from DaemonCard)
|
||||||
const { snapshot: runtimeSnapshot, client: runtimeClient, isConnected } = useHostRuntimeSession(
|
const runtimeSnapshot = useHostRuntimeSnapshot(host?.serverId ?? "");
|
||||||
host?.serverId ?? ""
|
const runtimeClient = useHostRuntimeClient(host?.serverId ?? "");
|
||||||
);
|
const isConnected = useHostRuntimeIsConnected(host?.serverId ?? "");
|
||||||
const runtime = getHostRuntimeStore();
|
const runtime = getHostRuntimeStore();
|
||||||
const daemonClient = runtimeClient;
|
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);
|
||||||
@@ -1389,7 +1391,7 @@ function DaemonCard({
|
|||||||
onOpenSettings,
|
onOpenSettings,
|
||||||
}: DaemonCardProps) {
|
}: DaemonCardProps) {
|
||||||
const { theme } = useUnistyles();
|
const { theme } = useUnistyles();
|
||||||
const { snapshot } = useHostRuntimeSession(daemon.serverId);
|
const snapshot = useHostRuntimeSnapshot(daemon.serverId);
|
||||||
const connectionStatus = snapshot?.connectionStatus ?? "connecting";
|
const connectionStatus = snapshot?.connectionStatus ?? "connecting";
|
||||||
const activeConnection = snapshot?.activeConnection ?? null;
|
const activeConnection = snapshot?.activeConnection ?? null;
|
||||||
const lastError = snapshot?.lastError ?? null;
|
const lastError = snapshot?.lastError ?? null;
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ import { AgentStreamView } from '@/components/agent-stream-view'
|
|||||||
import type { ImageAttachment } from '@/components/message-input'
|
import type { ImageAttachment } from '@/components/message-input'
|
||||||
import { useAgentFormState } from '@/hooks/use-agent-form-state'
|
import { useAgentFormState } from '@/hooks/use-agent-form-state'
|
||||||
import { useDraftAgentCreateFlow } from '@/hooks/use-draft-agent-create-flow'
|
import { useDraftAgentCreateFlow } from '@/hooks/use-draft-agent-create-flow'
|
||||||
import { useHostRuntimeSession } from '@/runtime/host-runtime'
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from '@/runtime/host-runtime'
|
||||||
import type { Agent } from '@/stores/session-store'
|
import type { Agent } from '@/stores/session-store'
|
||||||
import { encodeImages } from '@/utils/encode-images'
|
import { encodeImages } from '@/utils/encode-images'
|
||||||
import type { AgentCapabilityFlags, AgentSessionConfig } from '@server/server/agent/agent-sdk-types'
|
import type { AgentCapabilityFlags, AgentSessionConfig } from '@server/server/agent/agent-sdk-types'
|
||||||
@@ -40,7 +40,8 @@ export function WorkspaceDraftAgentTab({
|
|||||||
onCreated,
|
onCreated,
|
||||||
onOpenWorkspaceFile,
|
onOpenWorkspaceFile,
|
||||||
}: WorkspaceDraftAgentTabProps) {
|
}: WorkspaceDraftAgentTabProps) {
|
||||||
const { client, isConnected } = useHostRuntimeSession(serverId)
|
const client = useHostRuntimeClient(serverId)
|
||||||
|
const isConnected = useHostRuntimeIsConnected(serverId)
|
||||||
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null)
|
const addImagesRef = useRef<((images: ImageAttachment[]) => void) | null>(null)
|
||||||
|
|
||||||
const {
|
const {
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ import {
|
|||||||
normalizeWorkspaceTabTarget,
|
normalizeWorkspaceTabTarget,
|
||||||
workspaceTabTargetsEqual,
|
workspaceTabTargetsEqual,
|
||||||
} from "@/utils/workspace-tab-identity";
|
} from "@/utils/workspace-tab-identity";
|
||||||
import { useHostRuntimeSession } from "@/runtime/host-runtime";
|
import { useHostRuntimeClient, useHostRuntimeIsConnected } from "@/runtime/host-runtime";
|
||||||
import { useWorkspaceTerminalSessionRetention } from "@/terminal/hooks/use-workspace-terminal-session-retention";
|
import { useWorkspaceTerminalSessionRetention } from "@/terminal/hooks/use-workspace-terminal-session-retention";
|
||||||
import {
|
import {
|
||||||
checkoutStatusQueryKey,
|
checkoutStatusQueryKey,
|
||||||
@@ -579,7 +579,8 @@ function WorkspaceScreenContent({
|
|||||||
});
|
});
|
||||||
|
|
||||||
const queryClient = useQueryClient();
|
const queryClient = useQueryClient();
|
||||||
const { client, isConnected } = useHostRuntimeSession(normalizedServerId);
|
const client = useHostRuntimeClient(normalizedServerId);
|
||||||
|
const isConnected = useHostRuntimeIsConnected(normalizedServerId);
|
||||||
|
|
||||||
const sessionAgents = useSessionStore(
|
const sessionAgents = useSessionStore(
|
||||||
(state) => state.sessions[normalizedServerId]?.agents
|
(state) => state.sessions[normalizedServerId]?.agents
|
||||||
|
|||||||
Reference in New Issue
Block a user