/** * gatewayStore.ts - Backward-Compatible Facade * * This file was the original monolithic store (1800+ lines). * It is now a thin facade that re-exports types and provides * a composite useGatewayStore hook from the domain-specific stores: * * connectionStore.ts - Connection, local gateway management * agentStore.ts - Clones, usage stats, plugins * handStore.ts - Hands, triggers, approvals * workflowStore.ts - Workflows, workflow runs * configStore.ts - Config, channels, skills, models, workspace * securityStore.ts - Security status, audit logs * sessionStore.ts - Sessions, session messages * * Components should gradually migrate to import from the specific stores. * This facade exists only for backward compatibility. */ import { useConnectionStore } from './connectionStore'; import { useAgentStore } from './agentStore'; import { useHandStore } from './handStore'; import { useWorkflowStore } from './workflowStore'; import { useConfigStore } from './configStore'; import { useSecurityStore } from './securityStore'; import { useSessionStore } from './sessionStore'; import { useChatStore } from './chatStore'; import type { GatewayClient, ConnectionState } from '../lib/gateway-client'; import type { GatewayModelChoice } from '../lib/gateway-config'; import type { LocalGatewayStatus } from '../lib/tauri-gateway'; import type { Hand, HandRun, Trigger, Approval, ApprovalStatus } from './handStore'; import type { Workflow, WorkflowRun } from './workflowStore'; import type { Clone, PluginStatus, UsageStats } from './agentStore'; import type { QuickConfig, ChannelInfo, ScheduledTask, SkillInfo, WorkspaceInfo } from './configStore'; import type { SecurityStatus, AuditLogEntry } from './securityStore'; import type { Session, SessionMessage } from './sessionStore'; import type { GatewayLog } from './connectionStore'; // === Re-export Types from Domain Stores === // These re-exports maintain backward compatibility for all 34+ consumer files. export type { Hand, HandRun, HandRequirement, Trigger, Approval, ApprovalStatus } from './handStore'; export type { Workflow, WorkflowRun } from './workflowStore'; export type { Clone, UsageStats, PluginStatus } from './agentStore'; export type { QuickConfig, WorkspaceInfo, ChannelInfo, ScheduledTask, SkillInfo } from './configStore'; export type { SecurityLayer, SecurityStatus, AuditLogEntry } from './securityStore'; export type { Session, SessionMessage } from './sessionStore'; export type { GatewayLog } from './connectionStore'; // === Composite useGatewayStore Hook === // Provides a single store interface that delegates to all domain stores. // Components should gradually migrate to import from the specific stores. /** * Composite gateway store hook. * * Reads state from all domain stores and delegates actions. * This is a React hook (not a Zustand store) — it subscribes to * all underlying stores and returns a unified interface. * * @deprecated Components should migrate to use domain-specific stores directly: * useConnectionStore, useAgentStore, useHandStore, useWorkflowStore, * useConfigStore, useSecurityStore, useSessionStore */ export function useGatewayStore(): GatewayFacade; export function useGatewayStore(selector: (state: GatewayFacade) => T): T; export function useGatewayStore(selector?: (state: GatewayFacade) => T): T | GatewayFacade { // Subscribe to all stores (React will re-render when any changes) const conn = useConnectionStore(); const agent = useAgentStore(); const hand = useHandStore(); const workflow = useWorkflowStore(); const config = useConfigStore(); const security = useSecurityStore(); const session = useSessionStore(); const facade: GatewayFacade = { // === Connection State === connectionState: conn.connectionState, gatewayVersion: conn.gatewayVersion, error: conn.error || agent.error || hand.error || workflow.error || config.error || session.error || security.securityStatusError, logs: conn.logs, localGateway: conn.localGateway, localGatewayBusy: conn.localGatewayBusy, isLoading: conn.isLoading || agent.isLoading || hand.isLoading || workflow.isLoading, client: conn.client, // === Agent State === clones: agent.clones, usageStats: agent.usageStats, pluginStatus: agent.pluginStatus, // === Hand State === hands: hand.hands, handRuns: hand.handRuns, triggers: hand.triggers, approvals: hand.approvals, // === Workflow State === workflows: workflow.workflows, workflowRuns: workflow.workflowRuns as Record, // === Config State === quickConfig: config.quickConfig, workspaceInfo: config.workspaceInfo, channels: config.channels, scheduledTasks: config.scheduledTasks, skillsCatalog: config.skillsCatalog, models: config.models, modelsLoading: config.modelsLoading, modelsError: config.modelsError, // === Security State === securityStatus: security.securityStatus, securityStatusLoading: security.securityStatusLoading, securityStatusError: security.securityStatusError, auditLogs: security.auditLogs, // === Session State === sessions: session.sessions, sessionMessages: session.sessionMessages, // === Connection Actions === connect: async (url?: string, token?: string) => { await conn.connect(url, token); // Post-connect: load all data from domain stores await Promise.allSettled([ config.loadQuickConfig(), config.loadWorkspaceInfo(), agent.loadClones().then(() => { // Sync agents to chat store after loading (use getState for latest) useChatStore.getState().syncAgents(useAgentStore.getState().clones); }), agent.loadUsageStats(), agent.loadPluginStatus(), config.loadScheduledTasks(), config.loadSkillsCatalog(), hand.loadHands(), workflow.loadWorkflows(), hand.loadTriggers(), security.loadSecurityStatus(), config.loadModels(), ]); await config.loadChannels(); }, disconnect: conn.disconnect, clearLogs: conn.clearLogs, refreshLocalGateway: conn.refreshLocalGateway, startLocalGateway: conn.startLocalGateway, stopLocalGateway: conn.stopLocalGateway, restartLocalGateway: conn.restartLocalGateway, // === Agent Actions === loadClones: agent.loadClones, createClone: agent.createClone as GatewayFacade['createClone'], updateClone: agent.updateClone as GatewayFacade['updateClone'], deleteClone: agent.deleteClone, loadUsageStats: agent.loadUsageStats, loadPluginStatus: agent.loadPluginStatus, // === Hand Actions === loadHands: hand.loadHands, getHandDetails: hand.getHandDetails, triggerHand: hand.triggerHand, loadHandRuns: hand.loadHandRuns, approveHand: hand.approveHand, cancelHand: hand.cancelHand, loadTriggers: hand.loadTriggers, getTrigger: hand.getTrigger, createTrigger: hand.createTrigger as GatewayFacade['createTrigger'], updateTrigger: hand.updateTrigger, deleteTrigger: hand.deleteTrigger, loadApprovals: hand.loadApprovals, respondToApproval: hand.respondToApproval, // === Workflow Actions === loadWorkflows: workflow.loadWorkflows, createWorkflow: workflow.createWorkflow as GatewayFacade['createWorkflow'], updateWorkflow: workflow.updateWorkflow as GatewayFacade['updateWorkflow'], deleteWorkflow: workflow.deleteWorkflow, executeWorkflow: workflow.triggerWorkflow as GatewayFacade['executeWorkflow'], cancelWorkflow: workflow.cancelWorkflow, loadWorkflowRuns: workflow.loadWorkflowRuns as GatewayFacade['loadWorkflowRuns'], // === Config Actions === loadQuickConfig: config.loadQuickConfig, saveQuickConfig: config.saveQuickConfig, loadWorkspaceInfo: config.loadWorkspaceInfo, loadChannels: config.loadChannels, getChannel: config.getChannel, createChannel: config.createChannel, updateChannel: config.updateChannel, deleteChannel: config.deleteChannel, loadScheduledTasks: config.loadScheduledTasks, createScheduledTask: config.createScheduledTask, loadSkillsCatalog: config.loadSkillsCatalog, getSkill: config.getSkill, createSkill: config.createSkill, updateSkill: config.updateSkill, deleteSkill: config.deleteSkill, loadModels: config.loadModels, // === Security Actions === loadSecurityStatus: security.loadSecurityStatus, loadAuditLogs: security.loadAuditLogs, // === Session Actions === loadSessions: session.loadSessions, getSession: session.getSession, createSession: session.createSession, deleteSession: session.deleteSession, loadSessionMessages: session.loadSessionMessages, // === Legacy === sendMessage: async (message: string, sessionKey?: string) => { return conn.client.chat(message, { sessionKey }); }, }; if (selector) { return selector(facade); } return facade; } // === Facade Interface (matches the old GatewayStore shape) === interface GatewayFacade { // Connection state connectionState: ConnectionState; gatewayVersion: string | null; error: string | null; logs: GatewayLog[]; localGateway: LocalGatewayStatus; localGatewayBusy: boolean; isLoading: boolean; client: GatewayClient; // Data clones: Clone[]; usageStats: UsageStats | null; pluginStatus: PluginStatus[]; channels: ChannelInfo[]; scheduledTasks: ScheduledTask[]; skillsCatalog: SkillInfo[]; quickConfig: QuickConfig; workspaceInfo: WorkspaceInfo | null; models: GatewayModelChoice[]; modelsLoading: boolean; modelsError: string | null; // OpenFang Data hands: Hand[]; handRuns: Record; workflows: Workflow[]; triggers: Trigger[]; auditLogs: AuditLogEntry[]; securityStatus: SecurityStatus | null; securityStatusLoading: boolean; securityStatusError: string | null; approvals: Approval[]; sessions: Session[]; sessionMessages: Record; workflowRuns: Record; // Connection Actions connect: (url?: string, token?: string) => Promise; disconnect: () => void; clearLogs: () => void; refreshLocalGateway: () => Promise; startLocalGateway: () => Promise; stopLocalGateway: () => Promise; restartLocalGateway: () => Promise; // Agent Actions loadClones: () => Promise; createClone: (opts: { name: string; role?: string; nickname?: string; scenarios?: string[]; model?: string; workspaceDir?: string; restrictFiles?: boolean; privacyOptIn?: boolean; userName?: string; userRole?: string }) => Promise; updateClone: (id: string, updates: Partial) => Promise; deleteClone: (id: string) => Promise; loadUsageStats: () => Promise; loadPluginStatus: () => Promise; // Hand Actions loadHands: () => Promise; getHandDetails: (name: string) => Promise; loadHandRuns: (name: string, opts?: { limit?: number; offset?: number }) => Promise; triggerHand: (name: string, params?: Record) => Promise; approveHand: (name: string, runId: string, approved: boolean, reason?: string) => Promise; cancelHand: (name: string, runId: string) => Promise; loadTriggers: () => Promise; getTrigger: (id: string) => Promise; createTrigger: (trigger: { type: string; name?: string; enabled?: boolean; config?: Record; handName?: string; workflowId?: string }) => Promise; updateTrigger: (id: string, updates: { name?: string; enabled?: boolean; config?: Record; handName?: string; workflowId?: string }) => Promise; deleteTrigger: (id: string) => Promise; loadApprovals: (status?: ApprovalStatus) => Promise; respondToApproval: (approvalId: string, approved: boolean, reason?: string) => Promise; // Workflow Actions loadWorkflows: () => Promise; createWorkflow: (workflow: { name: string; description?: string; steps: Array<{ handName: string; name?: string; params?: Record; condition?: string }> }) => Promise; updateWorkflow: (id: string, updates: { name?: string; description?: string; steps?: Array<{ handName: string; name?: string; params?: Record; condition?: string }> }) => Promise; deleteWorkflow: (id: string) => Promise; executeWorkflow: (id: string, input?: Record) => Promise; cancelWorkflow: (id: string, runId: string) => Promise; loadWorkflowRuns: (workflowId: string, opts?: { limit?: number; offset?: number }) => Promise; // Config Actions loadQuickConfig: () => Promise; saveQuickConfig: (updates: Partial) => Promise; loadWorkspaceInfo: () => Promise; loadChannels: () => Promise; getChannel: (id: string) => Promise; createChannel: (channel: { type: string; name: string; config: Record; enabled?: boolean }) => Promise; updateChannel: (id: string, updates: { name?: string; config?: Record; enabled?: boolean }) => Promise; deleteChannel: (id: string) => Promise; loadScheduledTasks: () => Promise; createScheduledTask: (task: { name: string; schedule: string; scheduleType: 'cron' | 'interval' | 'once'; target?: { type: 'agent' | 'hand' | 'workflow'; id: string }; description?: string; enabled?: boolean }) => Promise; loadSkillsCatalog: () => Promise; getSkill: (id: string) => Promise; createSkill: (skill: { name: string; description?: string; triggers: Array<{ type: string; pattern?: string }>; actions: Array<{ type: string; params?: Record }>; enabled?: boolean }) => Promise; updateSkill: (id: string, updates: { name?: string; description?: string; triggers?: Array<{ type: string; pattern?: string }>; actions?: Array<{ type: string; params?: Record }>; enabled?: boolean }) => Promise; deleteSkill: (id: string) => Promise; loadModels: () => Promise; // Security Actions loadSecurityStatus: () => Promise; loadAuditLogs: (opts?: { limit?: number; offset?: number }) => Promise; // Session Actions loadSessions: (opts?: { limit?: number; offset?: number }) => Promise; getSession: (sessionId: string) => Promise; createSession: (agentId: string, metadata?: Record) => Promise; deleteSession: (sessionId: string) => Promise; loadSessionMessages: (sessionId: string, opts?: { limit?: number; offset?: number }) => Promise; // Legacy sendMessage: (message: string, sessionKey?: string) => Promise<{ runId: string }>; } // Dev-only: Expose stores to window for E2E testing if (import.meta.env.DEV && typeof window !== 'undefined') { (window as any).__ZCLAW_STORES__ = (window as any).__ZCLAW_STORES__ || {}; (window as any).__ZCLAW_STORES__.gateway = useGatewayStore; (window as any).__ZCLAW_STORES__.connection = useConnectionStore; (window as any).__ZCLAW_STORES__.agent = useAgentStore; (window as any).__ZCLAW_STORES__.hand = useHandStore; (window as any).__ZCLAW_STORES__.workflow = useWorkflowStore; (window as any).__ZCLAW_STORES__.config = useConfigStore; (window as any).__ZCLAW_STORES__.security = useSecurityStore; (window as any).__ZCLAW_STORES__.session = useSessionStore; // Dynamically import chatStore to avoid circular dependency import('./chatStore').then(({ useChatStore }) => { (window as any).__ZCLAW_STORES__.chat = useChatStore; }).catch(() => { // Ignore if chatStore is not available }); }