refactor(phase-11): extract specialized stores from gatewayStore
Decompose monolithic gatewayStore.ts (1660 lines) into focused stores: - connectionStore.ts (444 lines) - WebSocket, auth, local gateway - agentStore.ts (256 lines) - Clones, usage stats, plugins - handStore.ts (498 lines) - Hands, triggers, approvals - workflowStore.ts (255 lines) - Workflows, runs - configStore.ts (537 lines) - QuickConfig, channels, skills Each store uses client injection pattern for loose coupling. Coordinator layer to be added in next commit. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
255
desktop/src/store/workflowStore.ts
Normal file
255
desktop/src/store/workflowStore.ts
Normal file
@@ -0,0 +1,255 @@
|
||||
import { create } from 'zustand';
|
||||
import { Workflow, WorkflowRun } from './gatewayStore';
|
||||
|
||||
// === 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<string, unknown>;
|
||||
condition?: string;
|
||||
}
|
||||
|
||||
export interface CreateWorkflowInput {
|
||||
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: CreateWorkflowInput): 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<string, unknown>): 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 ===
|
||||
|
||||
interface WorkflowState {
|
||||
workflows: Workflow[];
|
||||
workflowRuns: Record<string, ExtendedWorkflowRun[]>;
|
||||
isLoading: boolean;
|
||||
error: string | null;
|
||||
client: WorkflowClient;
|
||||
}
|
||||
|
||||
// === Store Actions ===
|
||||
|
||||
interface WorkflowActions {
|
||||
setWorkflowStoreClient: (client: WorkflowClient) => void;
|
||||
loadWorkflows: () => Promise<void>;
|
||||
getWorkflow: (id: string) => Workflow | undefined;
|
||||
createWorkflow: (workflow: CreateWorkflowInput) => Promise<Workflow | undefined>;
|
||||
updateWorkflow: (id: string, updates: UpdateWorkflowInput) => Promise<Workflow | undefined>;
|
||||
deleteWorkflow: (id: string) => Promise<void>;
|
||||
triggerWorkflow: (id: string, input?: Record<string, unknown>) => Promise<{ runId: string; status: string } | undefined>;
|
||||
cancelWorkflow: (id: string, runId: string) => Promise<void>;
|
||||
loadWorkflowRuns: (workflowId: string, opts?: { limit?: number; offset?: number }) => Promise<ExtendedWorkflowRun[]>;
|
||||
clearError: () => void;
|
||||
reset: () => void;
|
||||
}
|
||||
|
||||
// === Initial State ===
|
||||
|
||||
const initialState = {
|
||||
workflows: [],
|
||||
workflowRuns: {},
|
||||
isLoading: false,
|
||||
error: null,
|
||||
client: null as unknown as WorkflowClient,
|
||||
};
|
||||
|
||||
// === Store ===
|
||||
|
||||
export const useWorkflowStore = create<WorkflowState & WorkflowActions>((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: CreateWorkflowInput) => {
|
||||
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<string, unknown>) => {
|
||||
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);
|
||||
},
|
||||
}));
|
||||
|
||||
// Re-export types from gatewayStore for convenience
|
||||
export type { Workflow, WorkflowRun };
|
||||
Reference in New Issue
Block a user