fix: stop sidebar from relying on fetchAgents hydration
This commit is contained in:
@@ -750,142 +750,25 @@ export function SessionProvider({
|
|||||||
}
|
}
|
||||||
hasRequestedInitialSnapshotRef.current = true;
|
hasRequestedInitialSnapshotRef.current = true;
|
||||||
|
|
||||||
const MAX_RETRIES = 3;
|
try {
|
||||||
const RETRY_DELAY_MS = 2000;
|
if (!agentUpdatesSubscriptionIdRef.current) {
|
||||||
|
agentUpdatesSubscriptionIdRef.current = client.subscribeAgentUpdates({
|
||||||
let cancelled = false;
|
subscriptionId: `app:${serverId}`,
|
||||||
|
filter: { labels: { ui: "true" } },
|
||||||
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;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
};
|
} catch (err) {
|
||||||
|
console.error("[Session] subscribeAgentUpdates failed", { serverId, err });
|
||||||
|
}
|
||||||
|
|
||||||
void hydrateAgents();
|
// Session bootstrap is now fully event-driven for agent lists.
|
||||||
return () => {
|
setInitializingAgents(serverId, new Map());
|
||||||
cancelled = true;
|
setHasHydratedAgents(serverId, true);
|
||||||
};
|
updateConnectionStatus(serverId, {
|
||||||
|
status: "online",
|
||||||
|
lastOnlineAt: new Date().toISOString(),
|
||||||
|
agentListReady: true,
|
||||||
|
});
|
||||||
}, [connectionSnapshot.isConnected, client, serverId, setHasHydratedAgents, updateConnectionStatus]);
|
}, [connectionSnapshot.isConnected, client, serverId, setHasHydratedAgents, updateConnectionStatus]);
|
||||||
|
|
||||||
// Daemon message handlers - directly update Zustand store
|
// Daemon message handlers - directly update Zustand store
|
||||||
@@ -1903,54 +1786,6 @@ export function SessionProvider({
|
|||||||
[directoryListingMutation, updateExplorerState]
|
[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
|
// Cleanup on unmount
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
|
|||||||
@@ -5,10 +5,6 @@ import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
|||||||
import { useSessionStore, type Agent } from "@/stores/session-store";
|
import { useSessionStore, type Agent } from "@/stores/session-store";
|
||||||
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 {
|
|
||||||
deriveProjectKey,
|
|
||||||
deriveProjectName,
|
|
||||||
} from "@/utils/agent-grouping";
|
|
||||||
import {
|
import {
|
||||||
useSectionOrderStore,
|
useSectionOrderStore,
|
||||||
sortProjectsByStoredOrder,
|
sortProjectsByStoredOrder,
|
||||||
@@ -206,18 +202,23 @@ export function useSidebarAgentsGrouped(options?: {
|
|||||||
if (live.archivedAt || live.labels.ui !== "true") {
|
if (live.archivedAt || live.labels.ui !== "true") {
|
||||||
continue;
|
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}`;
|
const agentKey = `${serverId}:${live.id}`;
|
||||||
if (seenAgentKeys.has(agentKey)) {
|
if (seenAgentKeys.has(agentKey)) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
|
|
||||||
const livePlacement = live.projectPlacement;
|
const livePlacement = live.projectPlacement;
|
||||||
const projectKey = livePlacement?.projectKey ?? deriveProjectKey(live.cwd);
|
const projectKey = livePlacement.projectKey;
|
||||||
const existing: MutableSidebarGroup =
|
const existing: MutableSidebarGroup =
|
||||||
groupsByKey.get(projectKey) ??
|
groupsByKey.get(projectKey) ??
|
||||||
{
|
{
|
||||||
projectKey,
|
projectKey,
|
||||||
projectName: livePlacement?.projectName ?? deriveProjectName(projectKey),
|
projectName: livePlacement.projectName,
|
||||||
agents: [],
|
agents: [],
|
||||||
};
|
};
|
||||||
existing.agents.push(
|
existing.agents.push(
|
||||||
@@ -227,9 +228,7 @@ export function useSidebarAgentsGrouped(options?: {
|
|||||||
serverLabel,
|
serverLabel,
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
if (livePlacement) {
|
checkoutLookup.set(agentKey, livePlacement.checkout);
|
||||||
checkoutLookup.set(agentKey, livePlacement.checkout);
|
|
||||||
}
|
|
||||||
groupsByKey.set(projectKey, existing);
|
groupsByKey.set(projectKey, existing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user