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

+3 -1
View File
@@ -51,6 +51,8 @@ export interface MockQbCall {
query: Record<string, string>;
form: Record<string, string>;
status: number;
/** 收到请求的时刻(Date.now()) */
at: number;
}
export interface MockQbFault {
@@ -533,7 +535,7 @@ export async function createMockQb(opts: MockQbOptions = {}): Promise<MockQb> {
const body = await readBody(req);
const ct = req.headers["content-type"] ?? "";
const form = ct.includes("urlencoded") ? Object.fromEntries(new URLSearchParams(body.toString())) : {};
const call: MockQbCall = { method: req.method ?? "GET", endpoint, query, form, status: 0 };
const call: MockQbCall = { method: req.method ?? "GET", endpoint, query, form, status: 0, at: Date.now() };
calls.push(call);
const send = (status: number, text: string, headers: Record<string, string> = {}) => {
call.status = status;