mirror of
https://github.com/getpaseo/paseo.git
synced 2026-08-15 04:42:45 +00:00
Remove agent directory syncing, make it pure derived state
Problem: Agent directory was still being synced from SessionProvider to Zustand store, violating the "SessionProvider is just a message handler" principle and creating unnecessary state duplication. Solution: Make agent directory pure derived state: - Removed agentDirectory from SessionStoreState - Removed setAgentDirectory/clearAgentDirectory actions - Changed getAgentDirectory to compute on-demand from session.agents - Removed buildAgentDirectoryEntries and syncing from SessionProvider - Updated useAggregatedAgents to derive from sessions directly How it works now: - Agent directory is computed on-demand from session.agents Map - lastActivityAt automatically updates when agents update via WebSocket - No syncing, no stale state, no overhead - Single source of truth: session.agents Benefits: - Eliminated redundant state (agentDirectory was duplicate of agents) - No sync overhead or complexity - Always fresh data (derived on read) - Simpler mental model (agents is the only source) 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -137,22 +137,6 @@ function normalizeAgentSnapshot(snapshot: AgentSnapshotPayload, serverId: string
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
function buildAgentDirectoryEntries(serverId: string, agents: Map<string, any>): AgentDirectoryEntry[] {
|
|
||||||
const entries: AgentDirectoryEntry[] = [];
|
|
||||||
for (const agent of agents.values()) {
|
|
||||||
entries.push({
|
|
||||||
id: agent.id,
|
|
||||||
serverId,
|
|
||||||
title: agent.title ?? null,
|
|
||||||
status: agent.status,
|
|
||||||
lastActivityAt: agent.lastActivityAt,
|
|
||||||
cwd: agent.cwd,
|
|
||||||
provider: agent.provider,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
return entries;
|
|
||||||
}
|
|
||||||
|
|
||||||
const createExplorerState = () => ({
|
const createExplorerState = () => ({
|
||||||
directories: new Map(),
|
directories: new Map(),
|
||||||
files: new Map(),
|
files: new Map(),
|
||||||
@@ -231,8 +215,6 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
// Zustand store actions
|
// Zustand store actions
|
||||||
const initializeSession = useSessionStore((state) => state.initializeSession);
|
const initializeSession = useSessionStore((state) => state.initializeSession);
|
||||||
const clearSession = useSessionStore((state) => state.clearSession);
|
const clearSession = useSessionStore((state) => state.clearSession);
|
||||||
const setAgentDirectory = useSessionStore((state) => state.setAgentDirectory);
|
|
||||||
const clearAgentDirectory = useSessionStore((state) => state.clearAgentDirectory);
|
|
||||||
const setIsPlayingAudio = useSessionStore((state) => state.setIsPlayingAudio);
|
const setIsPlayingAudio = useSessionStore((state) => state.setIsPlayingAudio);
|
||||||
const setMessages = useSessionStore((state) => state.setMessages);
|
const setMessages = useSessionStore((state) => state.setMessages);
|
||||||
const setCurrentAssistantMessage = useSessionStore((state) => state.setCurrentAssistantMessage);
|
const setCurrentAssistantMessage = useSessionStore((state) => state.setCurrentAssistantMessage);
|
||||||
@@ -1087,14 +1069,6 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
};
|
};
|
||||||
}, [ws, audioPlayer, serverId, setIsPlayingAudio, setMessages, setCurrentAssistantMessage, setAgentStreamState, setInitializingAgents, setAgents, setCommands, setPendingPermissions, setGitDiffs, setFileExplorer, setProviderModels, setHasHydratedAgents, updateConnectionStatus, getSession, saveDraftInput]);
|
}, [ws, audioPlayer, serverId, setIsPlayingAudio, setMessages, setCurrentAssistantMessage, setAgentStreamState, setInitializingAgents, setAgents, setCommands, setPendingPermissions, setGitDiffs, setFileExplorer, setProviderModels, setHasHydratedAgents, updateConnectionStatus, getSession, saveDraftInput]);
|
||||||
|
|
||||||
// Sync agent directory
|
|
||||||
useEffect(() => {
|
|
||||||
const session = getSession(serverId);
|
|
||||||
if (!session) return;
|
|
||||||
const lightweightAgentDirectory = buildAgentDirectoryEntries(serverId, session.agents);
|
|
||||||
setAgentDirectory(serverId, lightweightAgentDirectory);
|
|
||||||
}, [serverId, getSession, setAgentDirectory]);
|
|
||||||
|
|
||||||
// Auto-flush queued messages when agent transitions from running -> not running
|
// Auto-flush queued messages when agent transitions from running -> not running
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
const session = getSession(serverId);
|
const session = getSession(serverId);
|
||||||
@@ -1546,9 +1520,8 @@ export function SessionProvider({ children, serverUrl, serverId }: SessionProvid
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return () => {
|
return () => {
|
||||||
clearSession(serverId);
|
clearSession(serverId);
|
||||||
clearAgentDirectory(serverId);
|
|
||||||
};
|
};
|
||||||
}, [serverId, clearSession, clearAgentDirectory]);
|
}, [serverId, clearSession]);
|
||||||
|
|
||||||
const value = useMemo<SessionContextValue>(
|
const value = useMemo<SessionContextValue>(
|
||||||
() => ({
|
() => ({
|
||||||
|
|||||||
@@ -15,21 +15,27 @@ export interface AggregatedAgentsResult {
|
|||||||
|
|
||||||
export function useAggregatedAgents(): AggregatedAgentsResult {
|
export function useAggregatedAgents(): AggregatedAgentsResult {
|
||||||
const { connectionStates } = useDaemonConnections();
|
const { connectionStates } = useDaemonConnections();
|
||||||
const agentDirectory = useSessionStore((state) => state.agentDirectory);
|
const sessions = useSessionStore((state) => state.sessions);
|
||||||
|
|
||||||
return useMemo(() => {
|
return useMemo(() => {
|
||||||
const allAgents: AggregatedAgent[] = [];
|
const allAgents: AggregatedAgent[] = [];
|
||||||
|
|
||||||
for (const [serverId, agents] of Object.entries(agentDirectory)) {
|
// Derive agent directory from all sessions
|
||||||
if (!agents || agents.length === 0) {
|
for (const [serverId, session] of Object.entries(sessions)) {
|
||||||
|
if (!session?.agents || session.agents.size === 0) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId;
|
const serverLabel = connectionStates.get(serverId)?.daemon.label ?? serverId;
|
||||||
for (const agent of agents) {
|
for (const agent of session.agents.values()) {
|
||||||
allAgents.push({
|
allAgents.push({
|
||||||
...agent,
|
id: agent.id,
|
||||||
serverId,
|
serverId,
|
||||||
serverLabel,
|
serverLabel,
|
||||||
|
title: agent.title ?? null,
|
||||||
|
status: agent.status,
|
||||||
|
lastActivityAt: agent.lastActivityAt,
|
||||||
|
cwd: agent.cwd,
|
||||||
|
provider: agent.provider,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -61,5 +67,5 @@ export function useAggregatedAgents(): AggregatedAgentsResult {
|
|||||||
});
|
});
|
||||||
|
|
||||||
return { agents: allAgents, isLoading };
|
return { agents: allAgents, isLoading };
|
||||||
}, [agentDirectory, connectionStates]);
|
}, [sessions, connectionStates]);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -203,7 +203,6 @@ export interface SessionState {
|
|||||||
// Global store state
|
// Global store state
|
||||||
interface SessionStoreState {
|
interface SessionStoreState {
|
||||||
sessions: Record<string, SessionState>;
|
sessions: Record<string, SessionState>;
|
||||||
agentDirectory: Record<string, AgentDirectoryEntry[]>;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Action types
|
// Action types
|
||||||
@@ -257,9 +256,7 @@ interface SessionStoreActions {
|
|||||||
// Hydration
|
// Hydration
|
||||||
setHasHydratedAgents: (serverId: string, hydrated: boolean) => void;
|
setHasHydratedAgents: (serverId: string, hydrated: boolean) => void;
|
||||||
|
|
||||||
// Agent directory
|
// Agent directory (derived from agents)
|
||||||
setAgentDirectory: (serverId: string, agents: AgentDirectoryEntry[]) => void;
|
|
||||||
clearAgentDirectory: (serverId: string) => void;
|
|
||||||
getAgentDirectory: (serverId: string) => AgentDirectoryEntry[] | undefined;
|
getAgentDirectory: (serverId: string) => AgentDirectoryEntry[] | undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -288,29 +285,6 @@ function logSessionStoreUpdate(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
function areAgentDirectoriesEqual(left: AgentDirectoryEntry[] | undefined, right: AgentDirectoryEntry[]): boolean {
|
|
||||||
if (!left) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
if (left.length !== right.length) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return left.every((entry, index) => {
|
|
||||||
const other = right[index];
|
|
||||||
if (!other) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
entry.id === other.id &&
|
|
||||||
entry.status === other.status &&
|
|
||||||
entry.serverId === other.serverId &&
|
|
||||||
entry.lastActivityAt.getTime() === other.lastActivityAt.getTime() &&
|
|
||||||
entry.title === other.title &&
|
|
||||||
entry.cwd === other.cwd &&
|
|
||||||
entry.provider === other.provider
|
|
||||||
);
|
|
||||||
});
|
|
||||||
}
|
|
||||||
|
|
||||||
// Helper to create initial session state
|
// Helper to create initial session state
|
||||||
function createInitialSessionState(serverId: string, ws: UseWebSocketReturn, audioPlayer: ReturnType<typeof useAudioPlayer>): SessionState {
|
function createInitialSessionState(serverId: string, ws: UseWebSocketReturn, audioPlayer: ReturnType<typeof useAudioPlayer>): SessionState {
|
||||||
@@ -339,7 +313,6 @@ function createInitialSessionState(serverId: string, ws: UseWebSocketReturn, aud
|
|||||||
export const useSessionStore = create<SessionStore>()(
|
export const useSessionStore = create<SessionStore>()(
|
||||||
subscribeWithSelector((set, get) => ({
|
subscribeWithSelector((set, get) => ({
|
||||||
sessions: {},
|
sessions: {},
|
||||||
agentDirectory: {},
|
|
||||||
|
|
||||||
// Session management
|
// Session management
|
||||||
initializeSession: (serverId, ws, audioPlayer) => {
|
initializeSession: (serverId, ws, audioPlayer) => {
|
||||||
@@ -697,38 +670,26 @@ export const useSessionStore = create<SessionStore>()(
|
|||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
||||||
// Agent directory
|
// Agent directory - derived from agents (computed on-demand)
|
||||||
setAgentDirectory: (serverId, agents) => {
|
|
||||||
set((prev) => {
|
|
||||||
const existing = prev.agentDirectory[serverId];
|
|
||||||
if (existing && areAgentDirectoriesEqual(existing, agents)) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
logSessionStoreUpdate("setAgentDirectory", serverId, { agentCount: agents.length });
|
|
||||||
return {
|
|
||||||
...prev,
|
|
||||||
agentDirectory: {
|
|
||||||
...prev.agentDirectory,
|
|
||||||
[serverId]: agents,
|
|
||||||
},
|
|
||||||
};
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
clearAgentDirectory: (serverId) => {
|
|
||||||
set((prev) => {
|
|
||||||
if (!(serverId in prev.agentDirectory)) {
|
|
||||||
return prev;
|
|
||||||
}
|
|
||||||
logSessionStoreUpdate("clearAgentDirectory", serverId);
|
|
||||||
const nextDirectory = { ...prev.agentDirectory };
|
|
||||||
delete nextDirectory[serverId];
|
|
||||||
return { ...prev, agentDirectory: nextDirectory };
|
|
||||||
});
|
|
||||||
},
|
|
||||||
|
|
||||||
getAgentDirectory: (serverId) => {
|
getAgentDirectory: (serverId) => {
|
||||||
return get().agentDirectory[serverId];
|
const session = get().sessions[serverId];
|
||||||
|
if (!session) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
|
||||||
|
const entries: AgentDirectoryEntry[] = [];
|
||||||
|
for (const agent of session.agents.values()) {
|
||||||
|
entries.push({
|
||||||
|
id: agent.id,
|
||||||
|
serverId,
|
||||||
|
title: agent.title ?? null,
|
||||||
|
status: agent.status,
|
||||||
|
lastActivityAt: agent.lastActivityAt,
|
||||||
|
cwd: agent.cwd,
|
||||||
|
provider: agent.provider,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
return entries;
|
||||||
},
|
},
|
||||||
}))
|
}))
|
||||||
);
|
);
|
||||||
|
|||||||
Reference in New Issue
Block a user