From 1e62de539c645731fb68f458153239d61148a5c1 Mon Sep 17 00:00:00 2001 From: "Fusion (runfusion.ai)" Date: Fri, 15 May 2026 23:08:09 -0700 Subject: [PATCH] =?UTF-8?q?feat(FN-4663):=20complete=20Step=203=20?= =?UTF-8?q?=E2=80=94=20add=20stranded=20refinement=20store=20helper?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fusion-Task-Id: FN-4663 Fusion-Task-Lineage: 80975e9f-51e2-42ee-9f61-5e065a3fed24 --- packages/core/src/store.ts | 69 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 69 insertions(+) diff --git a/packages/core/src/store.ts b/packages/core/src/store.ts index 58e6604e8..303813324 100644 --- a/packages/core/src/store.ts +++ b/packages/core/src/store.ts @@ -3412,6 +3412,75 @@ export class TaskStore extends EventEmitter { return sorted.slice(offset, offset + Math.max(0, limit)); } + async listStrandedRefinements(options?: { + freshnessThresholdMs?: number; + }): Promise; + nextRecoveryAt?: string; + ageMs: number; + }>> { + const defaultFreshnessThresholdMs = 10 * 60 * 1000; + const requestedThresholdMs = options?.freshnessThresholdMs; + const freshnessThresholdMs = Number.isFinite(requestedThresholdMs) && (requestedThresholdMs ?? 0) >= 0 + ? requestedThresholdMs as number + : defaultFreshnessThresholdMs; + + const selectClause = this.getTaskSelectClause(false); + const rows = this.db.prepare( + `SELECT ${selectClause} FROM tasks WHERE "sourceType" = 'task_refine' AND "column" = 'triage' ORDER BY createdAt ASC`, + ).all() as unknown as TaskRow[]; + + const now = Date.now(); + const stranded: Array<{ + task: Task; + reasons: Array<"untriaged-stale" | "awaiting-approval" | "failed" | "stuck-killed" | "recovery-backoff">; + nextRecoveryAt?: string; + ageMs: number; + }> = []; + + for (const row of rows) { + const task = this.rowToTask(row); + if (task.paused) { + continue; + } + + const reasons: Array<"untriaged-stale" | "awaiting-approval" | "failed" | "stuck-killed" | "recovery-backoff"> = []; + const createdAtMs = Date.parse(task.createdAt); + const ageMs = Number.isFinite(createdAtMs) ? Math.max(0, now - createdAtMs) : 0; + + if (task.status === undefined && ageMs > freshnessThresholdMs) { + reasons.push("untriaged-stale"); + } + if (task.status === "awaiting-approval") { + reasons.push("awaiting-approval"); + } + if (task.status === "failed") { + reasons.push("failed"); + } + if (task.status === "stuck-killed") { + reasons.push("stuck-killed"); + } + if (task.nextRecoveryAt) { + const nextRecoveryAtMs = Date.parse(task.nextRecoveryAt); + if (Number.isFinite(nextRecoveryAtMs) && nextRecoveryAtMs > now) { + reasons.push("recovery-backoff"); + } + } + + if (reasons.length > 0) { + stranded.push({ + task, + reasons, + nextRecoveryAt: task.nextRecoveryAt, + ageMs, + }); + } + } + + return stranded; + } + private clearStartupSlimListMemo(): void { this.startupSlimListMemo.clear(); }