mirror of
https://github.com/getpaseo/paseo.git
synced 2026-07-29 12:01:31 +00:00
Fix useDaemonSession to return combined state + APIs
Problem: After migrating to pure Zustand, useDaemonSession returned only SessionState from the store, but components need both state AND imperative APIs (like sendAgentMessage, createAgent, etc.). Solution: Create DaemonSession type that combines: - SessionState (from Zustand store) - Imperative APIs (from SessionContext) - Wrapped store actions (getDraftInput, setFocusedAgentId, etc.) Changes: - Added DaemonSession type to use-daemon-session.ts - useDaemonSession now merges sessionState + context + store actions - Wraps store actions to bind serverId automatically - Updated prop types in components to use DaemonSession - Fixed use-session-directory.ts to use SessionState This restores component compatibility while keeping pure Zustand architecture. Still TODO: Fix remaining components that use SessionContext directly 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -171,7 +171,7 @@ export default function AgentScreen() {
|
||||
}
|
||||
|
||||
type AgentScreenContentProps = {
|
||||
session: SessionContextValue;
|
||||
session: import("@/hooks/use-daemon-session").DaemonSession;
|
||||
agentId?: string;
|
||||
routeServerId: string;
|
||||
onBack: () => void;
|
||||
|
||||
@@ -88,7 +88,7 @@ export default function FileExplorerScreen() {
|
||||
}
|
||||
|
||||
type FileExplorerContentProps = {
|
||||
session: SessionContextValue;
|
||||
session: import("@/hooks/use-daemon-session").DaemonSession;
|
||||
agentId?: string;
|
||||
pathParamRaw?: string | string[];
|
||||
fileParamRaw?: string | string[];
|
||||
|
||||
@@ -95,7 +95,7 @@ function GitDiffContent({
|
||||
agentId,
|
||||
serverLabel,
|
||||
}: {
|
||||
session: SessionContextValue;
|
||||
session: import("@/hooks/use-daemon-session").DaemonSession;
|
||||
agentId?: string;
|
||||
serverLabel: string;
|
||||
}) {
|
||||
|
||||
@@ -1095,7 +1095,6 @@ const styles = StyleSheet.create(((theme: any) => ({
|
||||
container: {
|
||||
flexDirection: "column",
|
||||
position: "relative",
|
||||
flex: 1,
|
||||
},
|
||||
borderSeparator: {
|
||||
height: theme.borderWidth[1],
|
||||
|
||||
@@ -252,7 +252,7 @@ function AgentFlowModal({
|
||||
}, [serverId, daemonEntries]);
|
||||
const [selectedServerId, setSelectedServerId] = useState<string | null>(initialServerId);
|
||||
const selectSessionSlice = useCallback(
|
||||
(session: SessionContextValue | null): CreateAgentSessionSlice | null => {
|
||||
(session: import("@/hooks/use-daemon-session").DaemonSession | null): CreateAgentSessionSlice | null => {
|
||||
if (!session) {
|
||||
return null;
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { useCallback, useRef } from "react";
|
||||
import { useCallback, useRef, useContext } from "react";
|
||||
import { Alert } from "react-native";
|
||||
import type { SessionContextValue } from "@/contexts/session-context";
|
||||
import { SessionContext } from "@/contexts/session-context";
|
||||
import { useDaemonConnections } from "@/contexts/daemon-connections-context";
|
||||
import { useSessionStore } from "@/stores/session-store";
|
||||
import { useSessionStore, type SessionState } from "@/stores/session-store";
|
||||
|
||||
export class DaemonSessionUnavailableError extends Error {
|
||||
serverId: string;
|
||||
@@ -19,14 +19,57 @@ type UseDaemonSessionOptions = {
|
||||
allowUnavailable?: boolean;
|
||||
};
|
||||
|
||||
// Combined type: SessionState (from store) + imperative APIs (from context)
|
||||
export type DaemonSession = SessionState & {
|
||||
// Imperative APIs from context
|
||||
setVoiceDetectionFlags: (isDetecting: boolean, isSpeaking: boolean) => void;
|
||||
requestGitDiff: (agentId: string) => void;
|
||||
requestDirectoryListing: (agentId: string, path: string, options?: { recordHistory?: boolean }) => void;
|
||||
requestFilePreview: (agentId: string, path: string) => void;
|
||||
navigateExplorerBack: (agentId: string) => string | null;
|
||||
requestProviderModels: (provider: any, options?: { cwd?: string }) => void;
|
||||
restartServer: (reason?: string) => void;
|
||||
initializeAgent: (params: { agentId: string; requestId?: string }) => void;
|
||||
refreshAgent: (params: { agentId: string; requestId?: string }) => void;
|
||||
cancelAgentRun: (agentId: string) => void;
|
||||
sendAgentMessage: (
|
||||
agentId: string,
|
||||
message: string,
|
||||
images?: Array<{ uri: string; mimeType?: string }>
|
||||
) => Promise<void>;
|
||||
sendAgentAudio: (
|
||||
agentId: string,
|
||||
audioBlob: Blob,
|
||||
requestId?: string,
|
||||
options?: { mode?: "transcribe_only" | "auto_run" }
|
||||
) => Promise<void>;
|
||||
deleteAgent: (agentId: string) => void;
|
||||
createAgent: (options: {
|
||||
config: any;
|
||||
initialPrompt: string;
|
||||
git?: any;
|
||||
worktreeName?: string;
|
||||
requestId?: string;
|
||||
}) => void;
|
||||
resumeAgent: (options: { handle: any; overrides?: any; requestId?: string }) => void;
|
||||
setAgentMode: (agentId: string, modeId: string) => void;
|
||||
respondToPermission: (agentId: string, requestId: string, response: any) => void;
|
||||
// State getters/setters that components might use directly
|
||||
getDraftInput: (agentId: string) => any;
|
||||
saveDraftInput: (agentId: string, draft: any) => void;
|
||||
setFocusedAgentId: (agentId: string | null) => void;
|
||||
setMessages: (messages: any[] | ((prev: any[]) => any[])) => void;
|
||||
setQueuedMessages: (value: any) => void;
|
||||
};
|
||||
|
||||
export function useDaemonSession(
|
||||
serverId?: string | null,
|
||||
options?: UseDaemonSessionOptions & { allowUnavailable?: false }
|
||||
): SessionContextValue | null;
|
||||
): DaemonSession | null;
|
||||
export function useDaemonSession(
|
||||
serverId: string | null | undefined,
|
||||
options: UseDaemonSessionOptions & { allowUnavailable: true }
|
||||
): SessionContextValue | null;
|
||||
): DaemonSession | null;
|
||||
export function useDaemonSession(serverId?: string | null, options?: UseDaemonSessionOptions) {
|
||||
const selectSession = useCallback(
|
||||
(state: ReturnType<typeof useSessionStore.getState>) => {
|
||||
@@ -37,21 +80,42 @@ export function useDaemonSession(serverId?: string | null, options?: UseDaemonSe
|
||||
},
|
||||
[serverId]
|
||||
);
|
||||
const session = useSessionStore(selectSession);
|
||||
const sessionState = useSessionStore(selectSession);
|
||||
const context = useContext(SessionContext);
|
||||
const { connectionStates } = useDaemonConnections();
|
||||
const alertedDaemonsRef = useRef<Set<string>>(new Set());
|
||||
const loggedDaemonsRef = useRef<Set<string>>(new Set());
|
||||
const { suppressUnavailableAlert = false, allowUnavailable = false } = options ?? {};
|
||||
|
||||
// Get store actions
|
||||
const getDraftInput = useSessionStore((state) => state.getDraftInput);
|
||||
const saveDraftInput = useSessionStore((state) => state.saveDraftInput);
|
||||
const setFocusedAgentId = useSessionStore((state) => state.setFocusedAgentId);
|
||||
const setMessages = useSessionStore((state) => state.setMessages);
|
||||
const setQueuedMessages = useSessionStore((state) => state.setQueuedMessages);
|
||||
|
||||
if (!serverId) {
|
||||
return null;
|
||||
}
|
||||
|
||||
try {
|
||||
if (!session) {
|
||||
if (!sessionState || !context) {
|
||||
throw new DaemonSessionUnavailableError(serverId);
|
||||
}
|
||||
return session;
|
||||
|
||||
// Combine session state with imperative APIs from context and store actions
|
||||
const combined: DaemonSession = {
|
||||
...sessionState,
|
||||
...context,
|
||||
// Wrap store actions to bind serverId
|
||||
getDraftInput: (agentId: string) => getDraftInput(serverId, agentId),
|
||||
saveDraftInput: (agentId: string, draft: any) => saveDraftInput(serverId, agentId, draft),
|
||||
setFocusedAgentId: (agentId: string | null) => setFocusedAgentId(serverId, agentId),
|
||||
setMessages: (messages: any) => setMessages(serverId, messages),
|
||||
setQueuedMessages: (value: any) => setQueuedMessages(serverId, value),
|
||||
};
|
||||
|
||||
return combined;
|
||||
} catch (error) {
|
||||
if (error instanceof DaemonSessionUnavailableError) {
|
||||
const connection = connectionStates.get(serverId);
|
||||
|
||||
@@ -1,19 +1,19 @@
|
||||
import { useCallback, useMemo } from "react";
|
||||
import { useStoreWithEqualityFn } from "zustand/traditional";
|
||||
import { useSessionStore, type SessionData } from "@/stores/session-store";
|
||||
import { useSessionStore, type SessionState } from "@/stores/session-store";
|
||||
|
||||
export function useSessionDirectory(): Map<string, SessionData> {
|
||||
export function useSessionDirectory(): Map<string, SessionState> {
|
||||
const sessions = useSessionStore((state) => state.sessions);
|
||||
|
||||
return useMemo(() => {
|
||||
return new Map<string, SessionData>(Object.entries(sessions));
|
||||
return new Map<string, SessionState>(Object.entries(sessions));
|
||||
}, [sessions]);
|
||||
}
|
||||
|
||||
type SessionSelector<T> = (session: SessionData | null) => T;
|
||||
type SessionSelector<T> = (session: SessionState | null) => T;
|
||||
type EqualityFn<T> = ((left: T, right: T) => boolean) | undefined;
|
||||
|
||||
export function useSessionForServer(serverId: string | null): SessionData | null;
|
||||
export function useSessionForServer(serverId: string | null): SessionState | null;
|
||||
export function useSessionForServer<T>(
|
||||
serverId: string | null,
|
||||
selector: SessionSelector<T>,
|
||||
@@ -23,7 +23,7 @@ export function useSessionForServer<T>(
|
||||
serverId: string | null,
|
||||
selector?: SessionSelector<T>,
|
||||
equalityFn?: EqualityFn<T>
|
||||
): SessionData | null | T {
|
||||
): SessionState | null | T {
|
||||
const baseSelector = useCallback(
|
||||
(state: ReturnType<typeof useSessionStore.getState>) =>
|
||||
(serverId ? state.sessions[serverId] ?? null : null),
|
||||
|
||||
Reference in New Issue
Block a user