refactor(ao): background loops only orchestrate; cleanup/reconcile endpoints enqueue

- separate qB sync timer from the planning timer
- threshold cleanup, reserved-plan and pending-import sweeps, 6h reconcile enqueue
  engine tasks with time-slot idempotency keys instead of running inline
- pending-import sweep selects by completed_at (sync refreshes updated_at every round)
- POST /download-cache/cleanup and /library/reconcile return {taskId, runId} (202);
  web jumps to the engine run page
- test: a 50MB verifyAndImport in progress does not delay qB sync

Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
rootandClaude Opus 5.5 committed 2026-09-27 22:12:16 +08:00
1 parent a9cee69bd2
commit fe9693d4ca
13 files changed
+334 -68

No files matched your search

+2 -2
View File
@@ -102,14 +102,14 @@ export function useCacheVolumes() {
});
}
/** 手动清理:只清已验证入库且满足保留/H&R 的纳管任务 */
/** 手动清理:服务端入队 cleanupDownloadCache(只清已验证入库且满足保留/H&R 的纳管任务),返回任务 id */
export function useCacheCleanup() {
const qc = useQueryClient();
return useMutation({
mutationFn: (downloaderInstanceId?: string) =>
apiMutate("/api/v1/download-cache/cleanup", {
body: downloaderInstanceId ? { downloaderInstanceId } : {},
}).then((d) => z.object({ report: z.record(z.string(), z.unknown()) }).parse(d)),
}).then((d) => z.object({ taskId: z.string(), runId: z.string() }).parse(d)),
onSuccess: () => qc.invalidateQueries({ queryKey: downloadKeys.all }),
});
}
@@ -1,5 +1,6 @@
import type { Dashboard } from "@umt/contracts";
import { Brush, HardDrive } from "lucide-react";
import { useNavigate } from "react-router";
import { toast } from "sonner";
import { useCacheCleanup, useCacheVolumes } from "~/api/downloads.ts";
import { ConfirmDialog } from "~/components/console/confirm-dialog.tsx";
@@ -79,6 +80,7 @@ function VolumeBar({ v }: { v: Volume }) {
export function CacheVolumes() {
const vols = useCacheVolumes();
const cleanup = useCacheCleanup();
const navigate = useNavigate();
return (
<section className="pg-slab mb-6 rounded-xl p-4">
<div className="mb-3 flex flex-wrap items-center justify-between gap-2">
@@ -96,10 +98,8 @@ export function CacheVolumes() {
onConfirm={() =>
cleanup.mutateAsync(undefined).then(
(r) => {
const vols = (r.report as { volumes?: { removed?: unknown[]; skipped?: unknown[] }[] }).volumes ?? [];
const removed = vols.reduce((n, v) => n + (v.removed?.length ?? 0), 0);
const skipped = vols.reduce((n, v) => n + (v.skipped?.length ?? 0), 0);
toast.success(`清理完成:移除 ${removed} 个任务的缓存${skipped ? `,${skipped} 个因保留/H&R 跳过` : ""}`);
toast.success(`已排队清理任务 ${r.taskId.slice(0, 8)}`, { description: "后台执行,结果见任务详情" });
navigate(`/console/engine/${r.runId}`);
},
(e) => toast.error(errorMessage(e)),
)