import { useCallback, useEffect, useState } from "react"; import { shouldStartNextConfigMutation } from "@/features/agents/operations/configMutationGatePolicy"; import type { GatewayStatus } from "@/features/agents/operations/gatewayRestartPolicy"; import { randomUUID } from "@/lib/uuid"; export type ConfigMutationKind = | "create-agent" | "rename-agent" | "delete-agent" | "update-agent-execution-role" | "update-agent-permissions" | "update-agent-skills" | "update-skill-setup" | "repair-sandbox-tool-allowlist"; type QueuedConfigMutation = { id: string; kind: ConfigMutationKind; label: string; requiresIdleAgents: boolean; run: () => Promise; resolve: () => void; reject: (error: unknown) => void; }; export type ActiveConfigMutation = { kind: ConfigMutationKind; label: string; }; const mutationRequiresIdleAgents = (kind: ConfigMutationKind): boolean => kind === "create-agent" || kind === "rename-agent" || kind === "delete-agent"; export function useConfigMutationQueue(params: { status: GatewayStatus; hasRunningAgents: boolean; hasRestartBlockInProgress: boolean; }) { const [queuedConfigMutations, setQueuedConfigMutations] = useState([]); const [activeConfigMutation, setActiveConfigMutation] = useState( null ); const enqueueConfigMutation = useCallback( (params: { kind: ConfigMutationKind; label: string; run: () => Promise; requiresIdleAgents?: boolean; }) => new Promise((resolve, reject) => { const queued: QueuedConfigMutation = { id: randomUUID(), kind: params.kind, label: params.label, requiresIdleAgents: params.requiresIdleAgents ?? mutationRequiresIdleAgents(params.kind), run: params.run, resolve, reject, }; setQueuedConfigMutations((current) => [...current, queued]); }), [] ); useEffect(() => { if ( !shouldStartNextConfigMutation({ status: params.status, hasRunningAgents: params.hasRunningAgents, nextMutationRequiresIdleAgents: Boolean(queuedConfigMutations[0]?.requiresIdleAgents), hasActiveMutation: Boolean(activeConfigMutation), hasRestartBlockInProgress: params.hasRestartBlockInProgress, queuedCount: queuedConfigMutations.length, }) ) { return; } const next = queuedConfigMutations[0]; if (!next) return; setQueuedConfigMutations((current) => current.slice(1)); setActiveConfigMutation(next); }, [ activeConfigMutation, params.hasRestartBlockInProgress, params.hasRunningAgents, params.status, queuedConfigMutations, ]); useEffect(() => { if (!activeConfigMutation) return; let mounted = true; const run = async () => { try { await activeConfigMutation.run(); activeConfigMutation.resolve(); } catch (error) { activeConfigMutation.reject(error); } finally { if (mounted) { setActiveConfigMutation(null); } } }; void run(); return () => { mounted = false; }; }, [activeConfigMutation]); return { enqueueConfigMutation, queuedCount: queuedConfigMutations.length, queuedBlockedByRunningAgents: Boolean(queuedConfigMutations[0]?.requiresIdleAgents) && params.hasRunningAgents, activeConfigMutation: activeConfigMutation ? ({ kind: activeConfigMutation.kind, label: activeConfigMutation.label } satisfies ActiveConfigMutation) : null, }; }