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:
@@ -6,6 +6,7 @@
|
||||
*/
|
||||
import { create } from 'zustand';
|
||||
import type { GatewayModelChoice } from '../lib/gateway-config';
|
||||
import { setStoredGatewayUrl, setStoredGatewayToken } from '../lib/gateway-client';
|
||||
import type { GatewayClient } from '../lib/gateway-client';
|
||||
|
||||
// === Types ===
|
||||
@@ -233,6 +234,13 @@ export const useConfigStore = create<ConfigStateSlice & ConfigActionsSlice>((set
|
||||
|
||||
try {
|
||||
const nextConfig = { ...get().quickConfig, ...updates };
|
||||
// Persist gateway URL/token to localStorage for reconnection
|
||||
if (nextConfig.gatewayUrl) {
|
||||
setStoredGatewayUrl(nextConfig.gatewayUrl);
|
||||
}
|
||||
if (Object.prototype.hasOwnProperty.call(updates, 'gatewayToken')) {
|
||||
setStoredGatewayToken(nextConfig.gatewayToken || '');
|
||||
}
|
||||
const result = await client.saveQuickConfig(nextConfig);
|
||||
set({ quickConfig: result?.quickConfig || nextConfig });
|
||||
} catch (err: unknown) {
|
||||
@@ -278,12 +286,12 @@ export const useConfigStore = create<ConfigStateSlice & ConfigActionsSlice>((set
|
||||
channels.push({
|
||||
id: 'feishu',
|
||||
type: 'feishu',
|
||||
label: 'Feishu',
|
||||
label: '飞书 (Feishu)',
|
||||
status: feishu?.configured ? 'active' : 'inactive',
|
||||
accounts: feishu?.accounts || 0,
|
||||
});
|
||||
} catch {
|
||||
channels.push({ id: 'feishu', type: 'feishu', label: 'Feishu', status: 'inactive' });
|
||||
channels.push({ id: 'feishu', type: 'feishu', label: '飞书 (Feishu)', status: 'inactive' });
|
||||
}
|
||||
|
||||
set({ channels });
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -6,8 +6,7 @@
|
||||
*
|
||||
* The coordinator:
|
||||
* 1. Injects the shared client into all stores
|
||||
* 2. Provides a composite hook that combines all store slices
|
||||
* 3. Re-exports all individual stores for direct access
|
||||
* 2. Re-exports all individual stores for direct access
|
||||
*/
|
||||
|
||||
// === Re-export Individual Stores ===
|
||||
@@ -26,6 +25,12 @@ export type { WorkflowStore, WorkflowStateSlice, WorkflowActionsSlice, Workflow,
|
||||
export { useConfigStore, setConfigStoreClient } from './configStore';
|
||||
export type { ConfigStore, ConfigStateSlice, ConfigActionsSlice, QuickConfig, WorkspaceInfo, ChannelInfo, ScheduledTask, SkillInfo } from './configStore';
|
||||
|
||||
export { useSecurityStore, setSecurityStoreClient } from './securityStore';
|
||||
export type { SecurityStore, SecurityStateSlice, SecurityActionsSlice, SecurityLayer, SecurityStatus, AuditLogEntry } from './securityStore';
|
||||
|
||||
export { useSessionStore, setSessionStoreClient } from './sessionStore';
|
||||
export type { SessionStore, SessionStateSlice, SessionActionsSlice, Session, SessionMessage } from './sessionStore';
|
||||
|
||||
// === New Stores ===
|
||||
export { useMemoryGraphStore } from './memoryGraphStore';
|
||||
export type { MemoryGraphStore, GraphNode, GraphEdge, GraphFilter, GraphLayout } from './memoryGraphStore';
|
||||
@@ -49,14 +54,15 @@ export type {
|
||||
SessionOptions,
|
||||
} from '../components/BrowserHand/templates/types';
|
||||
|
||||
// === Composite Store Hook ===
|
||||
// === Store Initialization ===
|
||||
|
||||
import { useMemo } from 'react';
|
||||
import { useConnectionStore, getClient } from './connectionStore';
|
||||
import { useAgentStore, setAgentStoreClient } from './agentStore';
|
||||
import { useHandStore, setHandStoreClient } from './handStore';
|
||||
import { useWorkflowStore, setWorkflowStoreClient } from './workflowStore';
|
||||
import { useConfigStore, setConfigStoreClient } from './configStore';
|
||||
import { getClient } from './connectionStore';
|
||||
import { setAgentStoreClient } from './agentStore';
|
||||
import { setHandStoreClient } from './handStore';
|
||||
import { setWorkflowStoreClient } from './workflowStore';
|
||||
import { setConfigStoreClient } from './configStore';
|
||||
import { setSecurityStoreClient } from './securityStore';
|
||||
import { setSessionStoreClient } from './sessionStore';
|
||||
|
||||
/**
|
||||
* Initialize all stores with the shared client.
|
||||
@@ -70,207 +76,8 @@ export function initializeStores(): void {
|
||||
setHandStoreClient(client);
|
||||
setWorkflowStoreClient(client);
|
||||
setConfigStoreClient(client);
|
||||
}
|
||||
|
||||
/**
|
||||
* Hook that provides a composite view of all stores.
|
||||
* Use this for components that need access to multiple store slices.
|
||||
*
|
||||
* For components that only need specific slices, import the individual
|
||||
* store hooks directly (e.g., useConnectionStore, useAgentStore).
|
||||
*/
|
||||
export function useCompositeStore() {
|
||||
// Subscribe to all stores
|
||||
const connectionState = useConnectionStore((s) => s.connectionState);
|
||||
const gatewayVersion = useConnectionStore((s) => s.gatewayVersion);
|
||||
const connectionError = useConnectionStore((s) => s.error);
|
||||
const logs = useConnectionStore((s) => s.logs);
|
||||
const localGateway = useConnectionStore((s) => s.localGateway);
|
||||
const localGatewayBusy = useConnectionStore((s) => s.localGatewayBusy);
|
||||
const isLoading = useConnectionStore((s) => s.isLoading);
|
||||
const client = useConnectionStore((s) => s.client);
|
||||
|
||||
const clones = useAgentStore((s) => s.clones);
|
||||
const usageStats = useAgentStore((s) => s.usageStats);
|
||||
const pluginStatus = useAgentStore((s) => s.pluginStatus);
|
||||
|
||||
const hands = useHandStore((s) => s.hands);
|
||||
const handRuns = useHandStore((s) => s.handRuns);
|
||||
const triggers = useHandStore((s) => s.triggers);
|
||||
const approvals = useHandStore((s) => s.approvals);
|
||||
|
||||
const workflows = useWorkflowStore((s) => s.workflows);
|
||||
const workflowRuns = useWorkflowStore((s) => s.workflowRuns);
|
||||
|
||||
const quickConfig = useConfigStore((s) => s.quickConfig);
|
||||
const workspaceInfo = useConfigStore((s) => s.workspaceInfo);
|
||||
const channels = useConfigStore((s) => s.channels);
|
||||
const scheduledTasks = useConfigStore((s) => s.scheduledTasks);
|
||||
const skillsCatalog = useConfigStore((s) => s.skillsCatalog);
|
||||
const models = useConfigStore((s) => s.models);
|
||||
const modelsLoading = useConfigStore((s) => s.modelsLoading);
|
||||
const modelsError = useConfigStore((s) => s.modelsError);
|
||||
|
||||
// Get all actions
|
||||
const connect = useConnectionStore((s) => s.connect);
|
||||
const disconnect = useConnectionStore((s) => s.disconnect);
|
||||
const clearLogs = useConnectionStore((s) => s.clearLogs);
|
||||
const refreshLocalGateway = useConnectionStore((s) => s.refreshLocalGateway);
|
||||
const startLocalGateway = useConnectionStore((s) => s.startLocalGateway);
|
||||
const stopLocalGateway = useConnectionStore((s) => s.stopLocalGateway);
|
||||
const restartLocalGateway = useConnectionStore((s) => s.restartLocalGateway);
|
||||
|
||||
const loadClones = useAgentStore((s) => s.loadClones);
|
||||
const createClone = useAgentStore((s) => s.createClone);
|
||||
const updateClone = useAgentStore((s) => s.updateClone);
|
||||
const deleteClone = useAgentStore((s) => s.deleteClone);
|
||||
const loadUsageStats = useAgentStore((s) => s.loadUsageStats);
|
||||
const loadPluginStatus = useAgentStore((s) => s.loadPluginStatus);
|
||||
|
||||
const loadHands = useHandStore((s) => s.loadHands);
|
||||
const getHandDetails = useHandStore((s) => s.getHandDetails);
|
||||
const triggerHand = useHandStore((s) => s.triggerHand);
|
||||
const loadHandRuns = useHandStore((s) => s.loadHandRuns);
|
||||
const loadTriggers = useHandStore((s) => s.loadTriggers);
|
||||
const createTrigger = useHandStore((s) => s.createTrigger);
|
||||
const deleteTrigger = useHandStore((s) => s.deleteTrigger);
|
||||
const loadApprovals = useHandStore((s) => s.loadApprovals);
|
||||
const respondToApproval = useHandStore((s) => s.respondToApproval);
|
||||
|
||||
const loadWorkflows = useWorkflowStore((s) => s.loadWorkflows);
|
||||
const getWorkflow = useWorkflowStore((s) => s.getWorkflow);
|
||||
const createWorkflow = useWorkflowStore((s) => s.createWorkflow);
|
||||
const updateWorkflow = useWorkflowStore((s) => s.updateWorkflow);
|
||||
const deleteWorkflow = useWorkflowStore((s) => s.deleteWorkflow);
|
||||
const triggerWorkflow = useWorkflowStore((s) => s.triggerWorkflow);
|
||||
const loadWorkflowRuns = useWorkflowStore((s) => s.loadWorkflowRuns);
|
||||
|
||||
const loadQuickConfig = useConfigStore((s) => s.loadQuickConfig);
|
||||
const saveQuickConfig = useConfigStore((s) => s.saveQuickConfig);
|
||||
const loadWorkspaceInfo = useConfigStore((s) => s.loadWorkspaceInfo);
|
||||
const loadChannels = useConfigStore((s) => s.loadChannels);
|
||||
const getChannel = useConfigStore((s) => s.getChannel);
|
||||
const createChannel = useConfigStore((s) => s.createChannel);
|
||||
const updateChannel = useConfigStore((s) => s.updateChannel);
|
||||
const deleteChannel = useConfigStore((s) => s.deleteChannel);
|
||||
const loadScheduledTasks = useConfigStore((s) => s.loadScheduledTasks);
|
||||
const createScheduledTask = useConfigStore((s) => s.createScheduledTask);
|
||||
const loadSkillsCatalog = useConfigStore((s) => s.loadSkillsCatalog);
|
||||
const getSkill = useConfigStore((s) => s.getSkill);
|
||||
const createSkill = useConfigStore((s) => s.createSkill);
|
||||
const updateSkill = useConfigStore((s) => s.updateSkill);
|
||||
const deleteSkill = useConfigStore((s) => s.deleteSkill);
|
||||
const loadModels = useConfigStore((s) => s.loadModels);
|
||||
|
||||
// Memoize the composite store to prevent unnecessary re-renders
|
||||
return useMemo(() => ({
|
||||
// Connection state
|
||||
connectionState,
|
||||
gatewayVersion,
|
||||
error: connectionError,
|
||||
logs,
|
||||
localGateway,
|
||||
localGatewayBusy,
|
||||
isLoading,
|
||||
client,
|
||||
|
||||
// Agent state
|
||||
clones,
|
||||
usageStats,
|
||||
pluginStatus,
|
||||
|
||||
// Hand state
|
||||
hands,
|
||||
handRuns,
|
||||
triggers,
|
||||
approvals,
|
||||
|
||||
// Workflow state
|
||||
workflows,
|
||||
workflowRuns,
|
||||
|
||||
// Config state
|
||||
quickConfig,
|
||||
workspaceInfo,
|
||||
channels,
|
||||
scheduledTasks,
|
||||
skillsCatalog,
|
||||
models,
|
||||
modelsLoading,
|
||||
modelsError,
|
||||
|
||||
// Connection actions
|
||||
connect,
|
||||
disconnect,
|
||||
clearLogs,
|
||||
refreshLocalGateway,
|
||||
startLocalGateway,
|
||||
stopLocalGateway,
|
||||
restartLocalGateway,
|
||||
|
||||
// Agent actions
|
||||
loadClones,
|
||||
createClone,
|
||||
updateClone,
|
||||
deleteClone,
|
||||
loadUsageStats,
|
||||
loadPluginStatus,
|
||||
|
||||
// Hand actions
|
||||
loadHands,
|
||||
getHandDetails,
|
||||
triggerHand,
|
||||
loadHandRuns,
|
||||
loadTriggers,
|
||||
createTrigger,
|
||||
deleteTrigger,
|
||||
loadApprovals,
|
||||
respondToApproval,
|
||||
|
||||
// Workflow actions
|
||||
loadWorkflows,
|
||||
getWorkflow,
|
||||
createWorkflow,
|
||||
updateWorkflow,
|
||||
deleteWorkflow,
|
||||
triggerWorkflow,
|
||||
loadWorkflowRuns,
|
||||
|
||||
// Config actions
|
||||
loadQuickConfig,
|
||||
saveQuickConfig,
|
||||
loadWorkspaceInfo,
|
||||
loadChannels,
|
||||
getChannel,
|
||||
createChannel,
|
||||
updateChannel,
|
||||
deleteChannel,
|
||||
loadScheduledTasks,
|
||||
createScheduledTask,
|
||||
loadSkillsCatalog,
|
||||
getSkill,
|
||||
createSkill,
|
||||
updateSkill,
|
||||
deleteSkill,
|
||||
loadModels,
|
||||
|
||||
// Legacy sendMessage (delegates to client)
|
||||
sendMessage: async (message: string, sessionKey?: string) => {
|
||||
return client.chat(message, { sessionKey });
|
||||
},
|
||||
}), [
|
||||
connectionState, gatewayVersion, connectionError, logs, localGateway, localGatewayBusy, isLoading, client,
|
||||
clones, usageStats, pluginStatus,
|
||||
hands, handRuns, triggers, approvals,
|
||||
workflows, workflowRuns,
|
||||
quickConfig, workspaceInfo, channels, scheduledTasks, skillsCatalog, models, modelsLoading, modelsError,
|
||||
connect, disconnect, clearLogs, refreshLocalGateway, startLocalGateway, stopLocalGateway, restartLocalGateway,
|
||||
loadClones, createClone, updateClone, deleteClone, loadUsageStats, loadPluginStatus,
|
||||
loadHands, getHandDetails, triggerHand, loadHandRuns, loadTriggers, createTrigger, deleteTrigger, loadApprovals, respondToApproval,
|
||||
loadWorkflows, getWorkflow, createWorkflow, updateWorkflow, deleteWorkflow, triggerWorkflow, loadWorkflowRuns,
|
||||
loadQuickConfig, saveQuickConfig, loadWorkspaceInfo, loadChannels, getChannel, createChannel, updateChannel, deleteChannel,
|
||||
loadScheduledTasks, createScheduledTask, loadSkillsCatalog, getSkill, createSkill, updateSkill, deleteSkill, loadModels,
|
||||
]);
|
||||
setSecurityStoreClient(client);
|
||||
setSessionStoreClient(client);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
141
desktop/src/store/securityStore.ts
Normal file
141
desktop/src/store/securityStore.ts
Normal file
@@ -0,0 +1,141 @@
|
||||
/**
|
||||
* securityStore.ts - Security Status and Audit Log Management
|
||||
*
|
||||
* Extracted from gatewayStore.ts for Store Refactoring.
|
||||
* Manages OpenFang security layers, security status, and audit logs.
|
||||
*/
|
||||
import { create } from 'zustand';
|
||||
import type { GatewayClient } from '../lib/gateway-client';
|
||||
|
||||
// === Types ===
|
||||
|
||||
export interface SecurityLayer {
|
||||
name: string;
|
||||
enabled: boolean;
|
||||
description?: string;
|
||||
}
|
||||
|
||||
export interface SecurityStatus {
|
||||
layers: SecurityLayer[];
|
||||
enabledCount: number;
|
||||
totalCount: number;
|
||||
securityLevel: 'critical' | 'high' | 'medium' | 'low';
|
||||
}
|
||||
|
||||
export interface AuditLogEntry {
|
||||
id: string;
|
||||
timestamp: string;
|
||||
action: string;
|
||||
actor?: string;
|
||||
result?: 'success' | 'failure';
|
||||
details?: Record<string, unknown>;
|
||||
// Merkle hash chain fields (OpenFang)
|
||||
hash?: string;
|
||||
previousHash?: string;
|
||||
}
|
||||
|
||||
// === Helpers ===
|
||||
|
||||
function calculateSecurityLevel(enabledCount: number, totalCount: number): 'critical' | 'high' | 'medium' | 'low' {
|
||||
if (totalCount === 0) return 'low';
|
||||
const ratio = enabledCount / totalCount;
|
||||
if (ratio >= 0.875) return 'critical'; // 14-16 layers
|
||||
if (ratio >= 0.625) return 'high'; // 10-13 layers
|
||||
if (ratio >= 0.375) return 'medium'; // 6-9 layers
|
||||
return 'low'; // 0-5 layers
|
||||
}
|
||||
|
||||
// === Client Interface ===
|
||||
|
||||
interface SecurityClient {
|
||||
getSecurityStatus(): Promise<{ layers?: SecurityLayer[] } | null>;
|
||||
getAuditLogs(opts?: { limit?: number; offset?: number }): Promise<{ logs?: AuditLogEntry[] } | null>;
|
||||
}
|
||||
|
||||
// === Store Interface ===
|
||||
|
||||
export interface SecurityStateSlice {
|
||||
securityStatus: SecurityStatus | null;
|
||||
securityStatusLoading: boolean;
|
||||
securityStatusError: string | null;
|
||||
auditLogs: AuditLogEntry[];
|
||||
auditLogsLoading: boolean;
|
||||
}
|
||||
|
||||
export interface SecurityActionsSlice {
|
||||
loadSecurityStatus: () => Promise<void>;
|
||||
loadAuditLogs: (opts?: { limit?: number; offset?: number }) => Promise<void>;
|
||||
}
|
||||
|
||||
export type SecurityStore = SecurityStateSlice & SecurityActionsSlice & { client: SecurityClient | null };
|
||||
|
||||
// === Store Implementation ===
|
||||
|
||||
export const useSecurityStore = create<SecurityStore>((set, get) => ({
|
||||
// Initial state
|
||||
securityStatus: null,
|
||||
securityStatusLoading: false,
|
||||
securityStatusError: null,
|
||||
auditLogs: [],
|
||||
auditLogsLoading: false,
|
||||
client: null,
|
||||
|
||||
loadSecurityStatus: async () => {
|
||||
const client = get().client;
|
||||
if (!client) return;
|
||||
|
||||
set({ securityStatusLoading: true, securityStatusError: null });
|
||||
try {
|
||||
const result = await client.getSecurityStatus();
|
||||
if (result?.layers) {
|
||||
const layers = result.layers as SecurityLayer[];
|
||||
const enabledCount = layers.filter(l => l.enabled).length;
|
||||
const totalCount = layers.length;
|
||||
const securityLevel = calculateSecurityLevel(enabledCount, totalCount);
|
||||
set({
|
||||
securityStatus: { layers, enabledCount, totalCount, securityLevel },
|
||||
securityStatusLoading: false,
|
||||
securityStatusError: null,
|
||||
});
|
||||
} else {
|
||||
set({
|
||||
securityStatusLoading: false,
|
||||
securityStatusError: 'API returned no data',
|
||||
});
|
||||
}
|
||||
} catch (err: unknown) {
|
||||
set({
|
||||
securityStatusLoading: false,
|
||||
securityStatusError: (err instanceof Error ? err.message : String(err)) || 'Security API not available',
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
loadAuditLogs: async (opts?: { limit?: number; offset?: number }) => {
|
||||
const client = get().client;
|
||||
if (!client) return;
|
||||
|
||||
set({ auditLogsLoading: true });
|
||||
try {
|
||||
const result = await client.getAuditLogs(opts);
|
||||
set({ auditLogs: (result?.logs || []) as AuditLogEntry[], auditLogsLoading: false });
|
||||
} catch {
|
||||
set({ auditLogsLoading: false });
|
||||
/* ignore if audit API not available */
|
||||
}
|
||||
},
|
||||
}));
|
||||
|
||||
// === Client Injection ===
|
||||
|
||||
function createSecurityClientFromGateway(client: GatewayClient): SecurityClient {
|
||||
return {
|
||||
getSecurityStatus: () => client.getSecurityStatus() as Promise<{ layers?: SecurityLayer[] } | null>,
|
||||
getAuditLogs: (opts) => client.getAuditLogs(opts) as Promise<{ logs?: AuditLogEntry[] } | null>,
|
||||
};
|
||||
}
|
||||
|
||||
export function setSecurityStoreClient(client: unknown): void {
|
||||
const securityClient = createSecurityClientFromGateway(client as GatewayClient);
|
||||
useSecurityStore.setState({ client: securityClient });
|
||||
}
|
||||
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 });
|
||||
}
|
||||
@@ -24,8 +24,6 @@ import type {
|
||||
ReviewFeedback,
|
||||
TaskDeliverable,
|
||||
} from '../types/team';
|
||||
import { parseJsonOrDefault } from '../lib/json-utils';
|
||||
|
||||
// === Store State ===
|
||||
|
||||
interface TeamStoreState {
|
||||
|
||||
@@ -1,7 +1,23 @@
|
||||
import { create } from 'zustand';
|
||||
import { Workflow, WorkflowRun } from './gatewayStore';
|
||||
import type { GatewayClient } from '../lib/gateway-client';
|
||||
|
||||
// === Core Types (previously imported from gatewayStore) ===
|
||||
|
||||
export interface Workflow {
|
||||
id: string;
|
||||
name: string;
|
||||
steps: number;
|
||||
description?: string;
|
||||
createdAt?: string;
|
||||
}
|
||||
|
||||
export interface WorkflowRun {
|
||||
runId: string;
|
||||
status: string;
|
||||
step?: string;
|
||||
result?: unknown;
|
||||
}
|
||||
|
||||
// === Types ===
|
||||
|
||||
interface RawWorkflowRun {
|
||||
@@ -256,8 +272,7 @@ export const useWorkflowStore = create<WorkflowStateSlice & WorkflowActionsSlice
|
||||
},
|
||||
}));
|
||||
|
||||
// Re-export types from gatewayStore for convenience
|
||||
export type { Workflow, WorkflowRun };
|
||||
// Types are now defined locally in this file (no longer imported from gatewayStore)
|
||||
|
||||
// === Client Injection ===
|
||||
|
||||
|
||||
Reference in New Issue
Block a user