diff --git a/packages/app/src/contexts/session-context.tsx b/packages/app/src/contexts/session-context.tsx index edc1c8a97..7b4d69e62 100644 --- a/packages/app/src/contexts/session-context.tsx +++ b/packages/app/src/contexts/session-context.tsx @@ -750,142 +750,25 @@ export function SessionProvider({ } hasRequestedInitialSnapshotRef.current = true; - const MAX_RETRIES = 3; - const RETRY_DELAY_MS = 2000; - - let cancelled = false; - - const hydrateAgents = async () => { - for (let attempt = 0; attempt <= MAX_RETRIES; attempt++) { - try { - if (!agentUpdatesSubscriptionIdRef.current) { - agentUpdatesSubscriptionIdRef.current = client.subscribeAgentUpdates({ - subscriptionId: `app:${serverId}`, - filter: { labels: { ui: "true" } }, - }); - } - - const agentsList = await client.fetchAgents({ - filter: { labels: { ui: "true" } }, - }); - if (cancelled) { - return; - } - - setInitializingAgents(serverId, new Map()); - - const agents = new Map(); - const pendingPermissions = new Map(); - const agentLastActivity = new Map(); - - for (const agentSnapshot of agentsList) { - const agent = normalizeAgentSnapshot(agentSnapshot, serverId); - agents.set(agent.id, agent); - agentLastActivity.set(agent.id, agent.lastActivityAt); - for (const request of agent.pendingPermissions) { - const key = derivePendingPermissionKey(agent.id, request); - pendingPermissions.set(key, { key, agentId: agent.id, request }); - } - } - - setAgents(serverId, agents); - - for (const [agentId, timestamp] of agentLastActivity.entries()) { - setAgentLastActivity(agentId, timestamp); - } - - setPendingPermissions(serverId, pendingPermissions); - setAgentStreamTail(serverId, (prev) => { - if (prev.size === 0) { - return prev; - } - - const validAgentIds = new Set(agentsList.map((snapshot) => snapshot.id)); - let changed = false; - const next = new Map(prev); - - for (const agentId of prev.keys()) { - if (!validAgentIds.has(agentId)) { - next.delete(agentId); - changed = true; - } - } - - return changed ? next : prev; - }); - setAgentStreamHead(serverId, (prev) => { - if (prev.size === 0) { - return prev; - } - - const validAgentIds = new Set(agentsList.map((snapshot) => snapshot.id)); - let changed = false; - const next = new Map(prev); - - for (const agentId of prev.keys()) { - if (!validAgentIds.has(agentId)) { - next.delete(agentId); - changed = true; - } - } - - return changed ? next : prev; - }); - setInitializingAgents(serverId, (prev) => { - if (prev.size === 0) { - return prev; - } - - const validAgentIds = new Set(agentsList.map((snapshot) => snapshot.id)); - let changed = false; - const next = new Map(prev); - - for (const agentId of prev.keys()) { - if (!validAgentIds.has(agentId)) { - next.delete(agentId); - changed = true; - } - } - - return changed ? next : prev; - }); - - setHasHydratedAgents(serverId, true); - updateConnectionStatus(serverId, { - status: "online", - lastOnlineAt: new Date().toISOString(), - agentListReady: true, - }); - return; - } catch (err) { - if (attempt < MAX_RETRIES) { - console.warn( - `[Session] fetchAgents failed, retrying in ${RETRY_DELAY_MS}ms`, - { serverId, attempt: attempt + 1, maxRetries: MAX_RETRIES, err } - ); - await new Promise((resolve) => setTimeout(resolve, RETRY_DELAY_MS)); - continue; - } - - console.error(`[Session] fetchAgents failed after ${MAX_RETRIES} retries`, { - serverId, - err, - }); - setHasHydratedAgents(serverId, true); - updateConnectionStatus(serverId, { - status: "online", - lastOnlineAt: new Date().toISOString(), - agentListReady: true, - }); - return; - } + try { + if (!agentUpdatesSubscriptionIdRef.current) { + agentUpdatesSubscriptionIdRef.current = client.subscribeAgentUpdates({ + subscriptionId: `app:${serverId}`, + filter: { labels: { ui: "true" } }, + }); } - }; + } catch (err) { + console.error("[Session] subscribeAgentUpdates failed", { serverId, err }); + } - void hydrateAgents(); - return () => { - cancelled = true; - }; + // Session bootstrap is now fully event-driven for agent lists. + setInitializingAgents(serverId, new Map()); + setHasHydratedAgents(serverId, true); + updateConnectionStatus(serverId, { + status: "online", + lastOnlineAt: new Date().toISOString(), + agentListReady: true, + }); }, [connectionSnapshot.isConnected, client, serverId, setHasHydratedAgents, updateConnectionStatus]); // Daemon message handlers - directly update Zustand store @@ -1903,54 +1786,6 @@ export function SessionProvider({ [directoryListingMutation, updateExplorerState] ); - const refreshSession = useCallback(() => { - console.log(`[Session] Manual refresh requested for ${serverId}`); - if (!client) { - console.warn("[Session] refreshSession skipped: daemon unavailable"); - return; - } - void (async () => { - try { - const agentsList = await client.fetchAgents({ - filter: { labels: { ui: "true" } }, - }); - - setInitializingAgents(serverId, new Map()); - - const agents = new Map(); - const pendingPermissions = new Map(); - const agentLastActivity = new Map(); - - for (const agentSnapshot of agentsList) { - const agent = normalizeAgentSnapshot(agentSnapshot, serverId); - agents.set(agent.id, agent); - agentLastActivity.set(agent.id, agent.lastActivityAt); - for (const request of agent.pendingPermissions) { - const key = derivePendingPermissionKey(agent.id, request); - pendingPermissions.set(key, { key, agentId: agent.id, request }); - } - } - - setAgents(serverId, agents); - for (const [agentId, timestamp] of agentLastActivity.entries()) { - setAgentLastActivity(agentId, timestamp); - } - setPendingPermissions(serverId, pendingPermissions); - } catch (error: any) { - console.error("[Session] Failed to refresh agents:", error); - } - })(); - }, [ - client, - derivePendingPermissionKey, - normalizeAgentSnapshot, - serverId, - setAgentLastActivity, - setAgents, - setInitializingAgents, - setPendingPermissions, - ]); - // Cleanup on unmount useEffect(() => { return () => { diff --git a/packages/app/src/hooks/use-sidebar-agents-grouped.ts b/packages/app/src/hooks/use-sidebar-agents-grouped.ts index 6a613cb78..2522b173c 100644 --- a/packages/app/src/hooks/use-sidebar-agents-grouped.ts +++ b/packages/app/src/hooks/use-sidebar-agents-grouped.ts @@ -5,10 +5,6 @@ import { useDaemonConnections } from "@/contexts/daemon-connections-context"; import { useSessionStore, type Agent } from "@/stores/session-store"; import type { AggregatedAgent } from "@/hooks/use-aggregated-agents"; import { normalizeAgentSnapshot } from "@/utils/agent-snapshots"; -import { - deriveProjectKey, - deriveProjectName, -} from "@/utils/agent-grouping"; import { useSectionOrderStore, sortProjectsByStoredOrder, @@ -206,18 +202,23 @@ export function useSidebarAgentsGrouped(options?: { if (live.archivedAt || live.labels.ui !== "true") { continue; } + if (!live.projectPlacement) { + // Ignore fetchAgents-hydrated snapshots for sidebar placement. + // Sidebar should derive placement from grouped RPC or project-enriched agent_update. + continue; + } const agentKey = `${serverId}:${live.id}`; if (seenAgentKeys.has(agentKey)) { continue; } const livePlacement = live.projectPlacement; - const projectKey = livePlacement?.projectKey ?? deriveProjectKey(live.cwd); + const projectKey = livePlacement.projectKey; const existing: MutableSidebarGroup = groupsByKey.get(projectKey) ?? { projectKey, - projectName: livePlacement?.projectName ?? deriveProjectName(projectKey), + projectName: livePlacement.projectName, agents: [], }; existing.agents.push( @@ -227,9 +228,7 @@ export function useSidebarAgentsGrouped(options?: { serverLabel, }) ); - if (livePlacement) { - checkoutLookup.set(agentKey, livePlacement.checkout); - } + checkoutLookup.set(agentKey, livePlacement.checkout); groupsByKey.set(projectKey, existing); } }