refactor(store): split gatewayStore into specialized domain stores
Major restructuring: - Split monolithic gatewayStore into 5 focused stores: - connectionStore: WebSocket connection and gateway lifecycle - configStore: quickConfig, workspaceInfo, MCP services - agentStore: clones, usage stats, agent management - handStore: hands, approvals, triggers, hand runs - workflowStore: workflows, workflow runs, execution - Update all components to use new stores with selector pattern - Remove
This commit is contained in:
228
desktop/src/store/sessionStore.ts
Normal file
228
desktop/src/store/sessionStore.ts
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* sessionStore.ts - Session Management Store
|
||||
*
|
||||
* Extracted from gatewayStore.ts for Store Refactoring.
|
||||
* Manages Gateway sessions and session messages.
|
||||
*/
|
||||
import { create } from 'zustand';
|
||||
import type { GatewayClient } from '../lib/gateway-client';
|
||||
|
||||
// === Types ===
|
||||
|
||||
export interface Session {
|
||||
id: string;
|
||||
agentId: string;
|
||||
createdAt: string;
|
||||
updatedAt?: string;
|
||||
messageCount?: number;
|
||||
status?: 'active' | 'archived' | 'expired';
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export interface SessionMessage {
|
||||
id: string;
|
||||
role: 'user' | 'assistant' | 'system';
|
||||
content: string;
|
||||
createdAt: string;
|
||||
tokens?: { input?: number; output?: number };
|
||||
}
|
||||
|
||||
// === Raw API Response Types ===
|
||||
|
||||
interface RawSession {
|
||||
id?: string;
|
||||
sessionId?: string;
|
||||
session_id?: string;
|
||||
agentId?: string;
|
||||
agent_id?: string;
|
||||
model?: string;
|
||||
status?: string;
|
||||
createdAt?: string;
|
||||
created_at?: string;
|
||||
updatedAt?: string;
|
||||
updated_at?: string;
|
||||
messageCount?: number;
|
||||
message_count?: number;
|
||||
metadata?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
interface RawSessionMessage {
|
||||
id?: string;
|
||||
messageId?: string;
|
||||
message_id?: string;
|
||||
role?: string;
|
||||
content?: string;
|
||||
createdAt?: string;
|
||||
created_at?: string;
|
||||
metadata?: Record<string, unknown>;
|
||||
tokens?: { input?: number; output?: number };
|
||||
}
|
||||
|
||||
// === Client Interface ===
|
||||
|
||||
interface SessionClient {
|
||||
listSessions(opts?: { limit?: number; offset?: number }): Promise<{ sessions?: RawSession[] } | null>;
|
||||
getSession(sessionId: string): Promise<Record<string, unknown> | null>;
|
||||
createSession(params: { agent_id: string; metadata?: Record<string, unknown> }): Promise<Record<string, unknown> | null>;
|
||||
deleteSession(sessionId: string): Promise<void>;
|
||||
getSessionMessages(sessionId: string, opts?: { limit?: number; offset?: number }): Promise<{ messages?: RawSessionMessage[] } | null>;
|
||||
}
|
||||
|
||||
// === Store Interface ===
|
||||
|
||||
export interface SessionStateSlice {
|
||||
sessions: Session[];
|
||||
sessionMessages: Record<string, SessionMessage[]>;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
}
|
||||
|
||||
export interface SessionActionsSlice {
|
||||
loadSessions: (opts?: { limit?: number; offset?: number }) => Promise<void>;
|
||||
getSession: (sessionId: string) => Promise<Session | undefined>;
|
||||
createSession: (agentId: string, metadata?: Record<string, unknown>) => Promise<Session | undefined>;
|
||||
deleteSession: (sessionId: string) => Promise<void>;
|
||||
loadSessionMessages: (sessionId: string, opts?: { limit?: number; offset?: number }) => Promise<SessionMessage[]>;
|
||||
}
|
||||
|
||||
export type SessionStore = SessionStateSlice & SessionActionsSlice & { client: SessionClient | null };
|
||||
|
||||
// === Store Implementation ===
|
||||
|
||||
export const useSessionStore = create<SessionStore>((set, get) => ({
|
||||
// Initial state
|
||||
sessions: [],
|
||||
sessionMessages: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
client: null,
|
||||
|
||||
loadSessions: async (opts?: { limit?: number; offset?: number }) => {
|
||||
const client = get().client;
|
||||
if (!client) return;
|
||||
|
||||
try {
|
||||
const result = await client.listSessions(opts);
|
||||
const sessions: Session[] = (result?.sessions || [])
|
||||
.filter((s: RawSession) => s.id || s.session_id)
|
||||
.map((s: RawSession) => ({
|
||||
id: s.id || s.session_id || '',
|
||||
agentId: s.agent_id || s.agentId || '',
|
||||
createdAt: s.created_at || s.createdAt || new Date().toISOString(),
|
||||
updatedAt: s.updated_at || s.updatedAt,
|
||||
messageCount: s.message_count || s.messageCount,
|
||||
status: s.status as Session['status'],
|
||||
metadata: s.metadata,
|
||||
}));
|
||||
set({ sessions });
|
||||
} catch {
|
||||
/* ignore if sessions API not available */
|
||||
}
|
||||
},
|
||||
|
||||
getSession: async (sessionId: string) => {
|
||||
const client = get().client;
|
||||
if (!client) return undefined;
|
||||
|
||||
try {
|
||||
const result = await client.getSession(sessionId);
|
||||
if (!result) return undefined;
|
||||
const session: Session = {
|
||||
id: result.id as string,
|
||||
agentId: result.agent_id as string,
|
||||
createdAt: result.created_at as string,
|
||||
updatedAt: result.updated_at as string | undefined,
|
||||
messageCount: result.message_count as number | undefined,
|
||||
status: result.status as Session['status'],
|
||||
metadata: result.metadata as Record<string, unknown> | undefined,
|
||||
};
|
||||
set(state => ({
|
||||
sessions: state.sessions.some(s => s.id === sessionId)
|
||||
? state.sessions.map(s => s.id === sessionId ? session : s)
|
||||
: [...state.sessions, session],
|
||||
}));
|
||||
return session;
|
||||
} catch {
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
createSession: async (agentId: string, metadata?: Record<string, unknown>) => {
|
||||
const client = get().client;
|
||||
if (!client) return undefined;
|
||||
|
||||
try {
|
||||
const result = await client.createSession({ agent_id: agentId, metadata });
|
||||
if (!result) return undefined;
|
||||
const session: Session = {
|
||||
id: result.id as string,
|
||||
agentId: result.agent_id as string,
|
||||
createdAt: result.created_at as string,
|
||||
status: 'active',
|
||||
metadata,
|
||||
};
|
||||
set(state => ({ sessions: [...state.sessions, session] }));
|
||||
return session;
|
||||
} catch (err: unknown) {
|
||||
set({ error: err instanceof Error ? err.message : String(err) });
|
||||
return undefined;
|
||||
}
|
||||
},
|
||||
|
||||
deleteSession: async (sessionId: string) => {
|
||||
const client = get().client;
|
||||
if (!client) return;
|
||||
|
||||
try {
|
||||
await client.deleteSession(sessionId);
|
||||
set(state => ({
|
||||
sessions: state.sessions.filter(s => s.id !== sessionId),
|
||||
sessionMessages: Object.fromEntries(
|
||||
Object.entries(state.sessionMessages).filter(([id]) => id !== sessionId)
|
||||
),
|
||||
}));
|
||||
} catch (err: unknown) {
|
||||
set({ error: err instanceof Error ? err.message : String(err) });
|
||||
throw err;
|
||||
}
|
||||
},
|
||||
|
||||
loadSessionMessages: async (sessionId: string, opts?: { limit?: number; offset?: number }) => {
|
||||
const client = get().client;
|
||||
if (!client) return [];
|
||||
|
||||
try {
|
||||
const result = await client.getSessionMessages(sessionId, opts);
|
||||
const messages: SessionMessage[] = (result?.messages || []).map((m: RawSessionMessage) => ({
|
||||
id: m.id || m.message_id || '',
|
||||
role: (m.role || 'user') as 'user' | 'assistant' | 'system',
|
||||
content: m.content || '',
|
||||
createdAt: m.created_at || m.createdAt || new Date().toISOString(),
|
||||
tokens: m.tokens,
|
||||
}));
|
||||
set(state => ({
|
||||
sessionMessages: { ...state.sessionMessages, [sessionId]: messages },
|
||||
}));
|
||||
return messages;
|
||||
} catch {
|
||||
return [];
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// === Client Injection ===
|
||||
|
||||
function createSessionClientFromGateway(client: GatewayClient): SessionClient {
|
||||
return {
|
||||
listSessions: (opts) => client.listSessions(opts),
|
||||
getSession: (sessionId) => client.getSession(sessionId),
|
||||
createSession: (params) => client.createSession(params),
|
||||
deleteSession: async (sessionId) => { await client.deleteSession(sessionId); },
|
||||
getSessionMessages: (sessionId, opts) => client.getSessionMessages(sessionId, opts),
|
||||
};
|
||||
}
|
||||
|
||||
export function setSessionStoreClient(client: unknown): void {
|
||||
const sessionClient = createSessionClientFromGateway(client as GatewayClient);
|
||||
useSessionStore.setState({ client: sessionClient });
|
||||
}
|
||||
Reference in New Issue
Block a user