Files
horus-3d/src/features/agents/operations/useConfigMutationQueue.ts
T
Luke The Dev 4fa4f13558 First Release of Claw3D (#11)
Co-authored-by: iamlukethedev <iamlukethedev@users.noreply.github.com>
2026-03-19 23:14:04 -05:00

124 lines
3.5 KiB
TypeScript

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<void>;
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<QueuedConfigMutation[]>([]);
const [activeConfigMutation, setActiveConfigMutation] = useState<QueuedConfigMutation | null>(
null
);
const enqueueConfigMutation = useCallback(
(params: {
kind: ConfigMutationKind;
label: string;
run: () => Promise<void>;
requiresIdleAgents?: boolean;
}) =>
new Promise<void>((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,
};
}