diff --git a/packages/app/e2e/browser/sessions-search.spec.ts b/packages/app/e2e/browser/sessions-search.spec.ts index 7f54617304..6cf8024e35 100644 --- a/packages/app/e2e/browser/sessions-search.spec.ts +++ b/packages/app/e2e/browser/sessions-search.spec.ts @@ -60,6 +60,15 @@ async function verifyChronologicalFiltering(page: Page): Promise { }); } +async function verifyHistorySurvivesReload(page: Page): Promise { + await test.step("reloads History without visiting a workspace to reconnect", async () => { + await expectVisibleTitles(page, [TITLES.unbilled, TITLES.billing, TITLES.terminal]); + await page.reload(); + await expectVisibleTitles(page, [TITLES.unbilled, TITLES.billing, TITLES.terminal]); + await expect(page.getByTestId("sessions-host-errors")).toBeHidden(); + }); +} + async function verifyRecencyBeforeMatchStrength(page: Page): Promise { await test.step("keeps newer partial matches before older stronger matches", async () => { await search(page, `${NONCE} bill`); @@ -151,6 +160,8 @@ test.describe("History search", () => { await verifyChronologicalFiltering(page); + await verifyHistorySurvivesReload(page); + await verifyRecencyBeforeMatchStrength(page); await verifyExactAndTypoHighlights(page); diff --git a/packages/app/src/hooks/use-agent-history.ts b/packages/app/src/hooks/use-agent-history.ts index 96b0395e76..efd17c74b3 100644 --- a/packages/app/src/hooks/use-agent-history.ts +++ b/packages/app/src/hooks/use-agent-history.ts @@ -4,10 +4,14 @@ import type { FetchAgentHistoryPageInfo, } from "@getpaseo/client/internal/daemon-client"; import { useInfiniteQuery } from "@tanstack/react-query"; -import { useCallback, useMemo, useSyncExternalStore } from "react"; +import { useCallback, useMemo } from "react"; import { useTranslation } from "react-i18next"; import type { AggregatedAgent } from "@/hooks/use-aggregated-agents"; -import { getHostRuntimeStore, isHostRuntimeConnected, useHosts } from "@/runtime/host-runtime"; +import { + getHostRuntimeStore, + useHostRuntimeConnectionStatuses, + useHosts, +} from "@/runtime/host-runtime"; import { useSessionStore } from "@/stores/session-store"; import { buildAgentDirectoryState } from "@/utils/agent-directory-sync"; import { agentHistoryQueryKey, allAgentHistoryQueryKey } from "./agent-history-query-key"; @@ -222,32 +226,29 @@ export function useAgentHistory(options: { const { t } = useTranslation(); const daemons = useHosts(); const runtime = getHostRuntimeStore(); - const runtimeVersion = useSyncExternalStore( - (onStoreChange) => runtime.subscribeAll(onStoreChange), - () => runtime.getVersion(), - () => runtime.getVersion(), - ); const serverId = useMemo(() => { const value = options.serverId; return typeof value === "string" && value.trim().length > 0 ? value.trim() : null; }, [options.serverId]); const enabled = options.enabled ?? true; + const serverIds = useMemo( + () => (serverId ? [serverId] : daemons.map((daemon) => daemon.serverId)), + [daemons, serverId], + ); + const connectionStatuses = useHostRuntimeConnectionStatuses(serverIds); // A host the user asked about splits two ways: one this fetch can reach, and // one whose sessions will be missing from the answer. Both come out of here, // because dropping the unreachable ones silently is what lets the list — and // worse, "No sessions match" — overstate what was actually searched. const { targetHosts, unreachableHosts } = useMemo(() => { - void runtimeVersion; const serverLabelById = new Map(daemons.map((daemon) => [daemon.serverId, daemon.label])); - const serverIds = serverId ? [serverId] : daemons.map((daemon) => daemon.serverId); const hosts: AgentHistoryHost[] = []; const unreachable: AgentHistoryHostError[] = []; for (const targetServerId of serverIds) { - const snapshot = runtime.getSnapshot(targetServerId); const client = runtime.getClient(targetServerId); const serverName = serverLabelById.get(targetServerId) ?? targetServerId; - if (!client || !isHostRuntimeConnected(snapshot)) { + if (!client || connectionStatuses.get(targetServerId) !== "online") { unreachable.push({ serverId: targetServerId, serverName }); continue; } @@ -255,7 +256,7 @@ export function useAgentHistory(options: { } return { targetHosts: hosts, unreachableHosts: unreachable }; - }, [daemons, runtime, runtimeVersion, serverId]); + }, [daemons, runtime, connectionStatuses, serverIds]); const targetServerIds = useMemo(() => targetHosts.map((host) => host.serverId), [targetHosts]); // One gate, checked before the field is offered: a fleet where any host // predates search has no search, rather than a list that silently omits that