import { useCallback, useEffect, useMemo, useState } from "react"; import { useGateways } from "@/app/di"; import type { Agent, BackgroundCompletion, GatewayError, ScheduledProjectTask } from "@/domain"; import type { ScheduledTaskDefinitionInput } from "@/ports"; function message(error: unknown): string { return error && typeof error === "object" && "message" in error ? String((error as GatewayError).message) : String(error); } export function useScheduledTasks(projectId: string) { const { scheduledTask, workState, agent, system } = useGateways(); const [tasks, setTasks] = useState([]); const [agents, setAgents] = useState([]); const [runs, setRuns] = useState([]); const [loading, setLoading] = useState(true); const [busyId, setBusyId] = useState(null); const [error, setError] = useState(null); const refresh = useCallback(async () => { setLoading(true); try { const [definitions, roster, state] = await Promise.all([ scheduledTask.list(projectId), agent.listAgents(projectId).catch(() => []), workState.getProjectWorkState(projectId).catch(() => null), ]); setTasks(definitions); setAgents(roster); setRuns(state?.agents.flatMap((item) => item.backgroundTasks ?? []) ?? []); setError(null); } catch (cause) { setError(message(cause)); } finally { setLoading(false); } }, [agent, projectId, scheduledTask, workState]); useEffect(() => { void refresh(); }, [refresh]); useEffect(() => { let unsubscribe: (() => void) | undefined; let cancelled = false; void system.onDomainEvent((event) => { if (event.type === "backgroundTaskChanged" && event.projectId === projectId) { void refresh(); } }).then((stop) => { if (cancelled) stop(); else unsubscribe = stop; }); return () => { cancelled = true; unsubscribe?.(); }; }, [projectId, refresh, system]); const runsById = useMemo( () => new Map(runs.map((run) => [run.taskId, run])), [runs], ); const agentNames = useMemo( () => new Map(agents.map((item) => [item.id, item.name])), [agents], ); async function mutate(id: string, action: () => Promise): Promise { setBusyId(id); setError(null); try { await action(); await refresh(); return true; } catch (cause) { setError(message(cause)); return false; } finally { setBusyId(null); } } return { tasks, loading, busyId, error, runsById, agentNames, refresh, create: (input: ScheduledTaskDefinitionInput) => mutate("new", () => scheduledTask.create(projectId, input)), update: (task: ScheduledProjectTask, input: ScheduledTaskDefinitionInput) => mutate(task.id, () => scheduledTask.update(projectId, task.id, task.auditRevision, input)), remove: (task: ScheduledProjectTask) => mutate(task.id, () => scheduledTask.delete(projectId, task.id)), runNow: (task: ScheduledProjectTask) => mutate(task.id, () => scheduledTask.runNow(projectId, task.id)), setEnabled: (task: ScheduledProjectTask, enabled: boolean) => mutate(task.id, () => scheduledTask.setEnabled(projectId, task.id, enabled)), cancelRun: (task: ScheduledProjectTask) => task.lastTaskId ? mutate(task.id, () => workState.cancelBackgroundTask(task.lastTaskId!)) : Promise.resolve(undefined), }; }