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
302 lines
9.0 KiB
TypeScript
302 lines
9.0 KiB
TypeScript
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<string, unknown>;
|
|
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<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 Slice ===
|
|
|
|
export interface WorkflowStateSlice {
|
|
workflows: Workflow[];
|
|
workflowRuns: Record<string, ExtendedWorkflowRun[]>;
|
|
isLoading: boolean;
|
|
error: string | null;
|
|
client: WorkflowClient;
|
|
}
|
|
|
|
// === Store Actions Slice ===
|
|
|
|
export interface WorkflowActionsSlice {
|
|
setWorkflowStoreClient: (client: WorkflowClient) => void;
|
|
loadWorkflows: () => Promise<void>;
|
|
getWorkflow: (id: string) => Workflow | undefined;
|
|
createWorkflow: (workflow: WorkflowCreateOptions) => 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;
|
|
}
|
|
|
|
// === 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<WorkflowStateSlice & WorkflowActionsSlice>((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<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);
|
|
},
|
|
}));
|
|
|
|
// 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);
|
|
}
|