import { create } from 'zustand'; 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 { runId?: string; run_id?: string; id?: string; workflowId?: string; workflow_id?: string; status?: string; startedAt?: string; started_at?: string; completedAt?: string; completed_at?: string; currentStep?: number; current_step?: number; totalSteps?: number; total_steps?: number; error?: string; result?: unknown; step?: string; } export interface WorkflowStep { handName: string; name?: string; params?: Record; condition?: string; } export interface WorkflowCreateOptions { name: string; description?: string; steps: WorkflowStep[]; } export interface UpdateWorkflowInput { name?: string; description?: string; steps?: WorkflowStep[]; } // Extended WorkflowRun with additional fields from API export interface ExtendedWorkflowRun extends WorkflowRun { startedAt?: string; completedAt?: string; error?: string; } // === Client Interface === interface WorkflowClient { listWorkflows(): Promise<{ workflows: { id: string; name: string; steps: number; description?: string; createdAt?: string }[] } | null>; createWorkflow(workflow: WorkflowCreateOptions): Promise<{ id: string; name: string } | null>; updateWorkflow(id: string, updates: UpdateWorkflowInput): Promise<{ id: string; name: string } | null>; deleteWorkflow(id: string): Promise<{ status: string }>; executeWorkflow(id: string, input?: Record): Promise<{ runId: string; status: string } | null>; cancelWorkflow(workflowId: string, runId: string): Promise<{ status: string }>; listWorkflowRuns(workflowId: string, opts?: { limit?: number; offset?: number }): Promise<{ runs: RawWorkflowRun[] } | null>; } // === Store State Slice === export interface WorkflowStateSlice { workflows: Workflow[]; workflowRuns: Record; isLoading: boolean; error: string | null; client: WorkflowClient; } // === Store Actions Slice === export interface WorkflowActionsSlice { setWorkflowStoreClient: (client: WorkflowClient) => void; loadWorkflows: () => Promise; getWorkflow: (id: string) => Workflow | undefined; createWorkflow: (workflow: WorkflowCreateOptions) => Promise; updateWorkflow: (id: string, updates: UpdateWorkflowInput) => Promise; deleteWorkflow: (id: string) => Promise; triggerWorkflow: (id: string, input?: Record) => Promise<{ runId: string; status: string } | undefined>; cancelWorkflow: (id: string, runId: string) => Promise; loadWorkflowRuns: (workflowId: string, opts?: { limit?: number; offset?: number }) => Promise; clearError: () => void; reset: () => void; } // === Combined Store Type === export type WorkflowStore = WorkflowStateSlice & WorkflowActionsSlice; // === Initial State === const initialState = { workflows: [], workflowRuns: {}, isLoading: false, error: null, client: null as unknown as WorkflowClient, }; // === Store === export const useWorkflowStore = create((set, get) => ({ ...initialState, setWorkflowStoreClient: (client: WorkflowClient) => { set({ client }); }, loadWorkflows: async () => { set({ isLoading: true, error: null }); try { const result = await get().client.listWorkflows(); const workflows: Workflow[] = (result?.workflows || []).map(w => ({ id: w.id, name: w.name, steps: w.steps, description: w.description, createdAt: w.createdAt, })); set({ workflows, isLoading: false }); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to load workflows'; set({ error: message, isLoading: false }); } }, getWorkflow: (id: string) => { return get().workflows.find(w => w.id === id); }, createWorkflow: async (workflow: WorkflowCreateOptions) => { set({ error: null }); try { const result = await get().client.createWorkflow(workflow); if (result) { const newWorkflow: Workflow = { id: result.id, name: result.name, steps: workflow.steps.length, description: workflow.description, }; set(state => ({ workflows: [...state.workflows, newWorkflow] })); return newWorkflow; } return undefined; } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to create workflow'; set({ error: message }); return undefined; } }, updateWorkflow: async (id: string, updates: UpdateWorkflowInput) => { set({ error: null }); try { const result = await get().client.updateWorkflow(id, updates); if (result) { set(state => ({ workflows: state.workflows.map(w => w.id === id ? { ...w, name: updates.name ?? w.name, description: updates.description ?? w.description, steps: updates.steps?.length ?? w.steps, } : w ), })); return get().workflows.find(w => w.id === id); } return undefined; } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to update workflow'; set({ error: message }); return undefined; } }, deleteWorkflow: async (id: string) => { set({ error: null }); try { await get().client.deleteWorkflow(id); set(state => ({ workflows: state.workflows.filter(w => w.id !== id), workflowRuns: (() => { const { [id]: _, ...rest } = state.workflowRuns; return rest; })(), })); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to delete workflow'; set({ error: message }); throw err; } }, triggerWorkflow: async (id: string, input?: Record) => { set({ error: null }); try { const result = await get().client.executeWorkflow(id, input); return result ? { runId: result.runId, status: result.status } : undefined; } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to trigger workflow'; set({ error: message }); return undefined; } }, cancelWorkflow: async (id: string, runId: string) => { set({ error: null }); try { await get().client.cancelWorkflow(id, runId); // Refresh workflows to update status await get().loadWorkflows(); } catch (err: unknown) { const message = err instanceof Error ? err.message : 'Failed to cancel workflow'; set({ error: message }); throw err; } }, loadWorkflowRuns: async (workflowId: string, opts?: { limit?: number; offset?: number }) => { try { const result = await get().client.listWorkflowRuns(workflowId, opts); const runs: ExtendedWorkflowRun[] = (result?.runs || []).map((r: RawWorkflowRun) => ({ runId: r.runId || r.run_id || r.id || '', status: r.status || 'unknown', startedAt: r.startedAt || r.started_at, completedAt: r.completedAt || r.completed_at, step: r.currentStep?.toString() || r.current_step?.toString() || r.step, result: r.result, error: r.error, })); // Store runs by workflow ID set(state => ({ workflowRuns: { ...state.workflowRuns, [workflowId]: runs }, })); return runs; } catch { return []; } }, clearError: () => { set({ error: null }); }, reset: () => { set(initialState); }, })); // Types are now defined locally in this file (no longer imported from gatewayStore) // === Client Injection === /** * Helper to create a WorkflowClient adapter from a GatewayClient. */ function createWorkflowClientFromGateway(client: GatewayClient): WorkflowClient { return { listWorkflows: () => client.listWorkflows(), createWorkflow: (workflow) => client.createWorkflow(workflow), updateWorkflow: (id, updates) => client.updateWorkflow(id, updates), deleteWorkflow: (id) => client.deleteWorkflow(id), executeWorkflow: (id, input) => client.executeWorkflow(id, input), cancelWorkflow: (workflowId, runId) => client.cancelWorkflow(workflowId, runId), listWorkflowRuns: (workflowId, opts) => client.listWorkflowRuns(workflowId, opts), }; } /** * Sets the client for the workflow store. * Called by the coordinator during initialization. */ export function setWorkflowStoreClient(client: unknown): void { const workflowClient = createWorkflowClientFromGateway(client as GatewayClient); useWorkflowStore.getState().setWorkflowStoreClient(workflowClient); }