// ======================================== // Queue Execution Store // ======================================== // Zustand store for unified queue execution state management. // Tracks both InSession and Orchestrator execution paths, // bridging them into a single observable state for UI consumption. import { create } from 'zustand'; import { devtools } from 'zustand/middleware'; // ========== Types ========== export type QueueExecutionType = 'session' | 'orchestrator'; export type QueueExecutionStatus = 'pending' | 'running' | 'completed' | 'failed'; export type QueueExecutionMode = 'analysis' | 'write'; export interface QueueExecution { /** Unique execution identifier */ id: string; /** Associated queue item ID */ queueItemId: string; /** Associated issue ID */ issueId: string; /** Associated solution ID */ solutionId: string; /** Execution path type */ type: QueueExecutionType; /** CLI session key (session type only) */ sessionKey?: string; /** Orchestrator flow ID (orchestrator type only) */ flowId?: string; /** Orchestrator execution ID (orchestrator type only) */ execId?: string; /** CLI tool used for execution */ tool: string; /** Execution mode */ mode: QueueExecutionMode; /** Current execution status */ status: QueueExecutionStatus; /** ISO timestamp when execution started */ startedAt: string; /** ISO timestamp when execution completed or failed */ completedAt?: string; /** Error message if execution failed */ error?: string; } export interface QueueExecutionStats { running: number; completed: number; failed: number; total: number; } export interface QueueExecutionState { /** All tracked executions keyed by execution ID */ executions: Record; } export interface QueueExecutionActions { /** Add a new execution to the store */ addExecution: (exec: QueueExecution) => void; /** Update the status of an existing execution */ updateStatus: (id: string, status: QueueExecutionStatus, error?: string) => void; /** Remove a single execution by ID */ removeExecution: (id: string) => void; /** Remove all completed and failed executions */ clearCompleted: () => void; /** Reset workspace-scoped queue execution state */ resetState: () => void; } export type QueueExecutionStore = QueueExecutionState & QueueExecutionActions; // ========== Initial State ========== const initialState: QueueExecutionState = { executions: {}, }; // ========== Store ========== export const useQueueExecutionStore = create()( devtools( (set) => ({ ...initialState, // ========== Execution Lifecycle ========== addExecution: (exec: QueueExecution) => { set( (state) => ({ executions: { ...state.executions, [exec.id]: exec, }, }), false, 'addExecution' ); }, updateStatus: (id: string, status: QueueExecutionStatus, error?: string) => { set( (state) => { const existing = state.executions[id]; if (!existing) return state; const isTerminal = status === 'completed' || status === 'failed'; return { executions: { ...state.executions, [id]: { ...existing, status, completedAt: isTerminal ? new Date().toISOString() : existing.completedAt, error: error ?? existing.error, }, }, }; }, false, 'updateStatus' ); }, removeExecution: (id: string) => { set( (state) => { const { [id]: _removed, ...remaining } = state.executions; return { executions: remaining }; }, false, 'removeExecution' ); }, clearCompleted: () => { set( (state) => { const active: Record = {}; for (const [id, exec] of Object.entries(state.executions)) { if (exec.status !== 'completed' && exec.status !== 'failed') { active[id] = exec; } } return { executions: active }; }, false, 'clearCompleted' ); }, resetState: () => { set({ ...initialState }, false, 'resetState'); }, }), { name: 'QueueExecutionStore' } ) ); // ========== Selectors ========== /** Stable empty array to avoid new references */ const EMPTY_EXECUTIONS: QueueExecution[] = []; /** Select all executions as a record */ export const selectQueueExecutions = (state: QueueExecutionStore) => state.executions; /** * Select only currently running executions. * WARNING: Returns new array each call — use with useMemo in components. */ export const selectActiveExecutions = (state: QueueExecutionStore): QueueExecution[] => { const all = Object.values(state.executions); const running = all.filter((exec) => exec.status === 'running'); return running.length === 0 ? EMPTY_EXECUTIONS : running; }; /** * Select executions for a specific queue item. * WARNING: Returns new array each call — use with useMemo in components. */ export const selectByQueueItem = (queueItemId: string) => (state: QueueExecutionStore): QueueExecution[] => { const matched = Object.values(state.executions).filter( (exec) => exec.queueItemId === queueItemId ); return matched.length === 0 ? EMPTY_EXECUTIONS : matched; }; /** Compute execution statistics by status */ export const selectExecutionStats = (state: QueueExecutionStore): QueueExecutionStats => { const all = Object.values(state.executions); return { running: all.filter((e) => e.status === 'running').length, completed: all.filter((e) => e.status === 'completed').length, failed: all.filter((e) => e.status === 'failed').length, total: all.length, }; }; /** Check if any execution is currently running */ export const selectHasActiveExecution = (state: QueueExecutionStore): boolean => { return Object.values(state.executions).some((exec) => exec.status === 'running'); };