|
| 1 | +import { log } from "../../../shared/logger"; |
| 2 | +import type { Database } from "../../../shared/sqlite"; |
| 3 | +import { getDreamState, setDreamState } from "./storage-dream-state"; |
| 4 | +import { deleteTaskScheduleRowsForProject } from "./storage-task-schedule"; |
| 5 | +import { identityHasProjectInput } from "./task-gates"; |
| 6 | + |
| 7 | +/** |
| 8 | + * Pruning visits every identity that holds schedule rows, so it runs at most |
| 9 | + * once per day across all processes sharing the store, not on every tick. |
| 10 | + */ |
| 11 | +export const IDLE_SCHEDULE_PRUNE_INTERVAL_MS = 24 * 60 * 60 * 1000; |
| 12 | + |
| 13 | +/** dream_state key holding the epoch-ms time of the last idle-identity prune. */ |
| 14 | +export const IDLE_SCHEDULE_PRUNE_AT_KEY = "schedule_idle_prune_at"; |
| 15 | + |
| 16 | +/** |
| 17 | + * Delete every schedule row of identities that can do nothing now. |
| 18 | + * |
| 19 | + * Any directory that hosted a session used to keep a full set of schedule rows |
| 20 | + * forever, even when it never produced a memory (chat folders, downloads, |
| 21 | + * scratch directories). The timer only visits directories open in a running |
| 22 | + * process, so those rows were never even evaluated again; they just piled up |
| 23 | + * in the store and on the dashboard. An identity with active memories is never |
| 24 | + * pruned. A pruned identity gets rows back from the normal seeding path once it |
| 25 | + * has input again. |
| 26 | + * |
| 27 | + * Throttled through dream_state to once per IDLE_SCHEDULE_PRUNE_INTERVAL_MS. |
| 28 | + * Returns the identities whose rows were deleted (empty when throttled). |
| 29 | + */ |
| 30 | +export function pruneIdleScheduleIdentities(db: Database, now: number): string[] { |
| 31 | + const last = Number(getDreamState(db, IDLE_SCHEDULE_PRUNE_AT_KEY)); |
| 32 | + if (Number.isFinite(last) && last > 0 && now - last < IDLE_SCHEDULE_PRUNE_INTERVAL_MS) { |
| 33 | + return []; |
| 34 | + } |
| 35 | + // Claim the slot before the scan: two processes racing here both prune, and |
| 36 | + // deleting the same rows twice is harmless. |
| 37 | + setDreamState(db, IDLE_SCHEDULE_PRUNE_AT_KEY, String(now)); |
| 38 | + |
| 39 | + const identities = db |
| 40 | + .prepare<[], { project_path: string }>( |
| 41 | + "SELECT DISTINCT project_path FROM task_schedule_state", |
| 42 | + ) |
| 43 | + .all() |
| 44 | + .map((row) => row.project_path); |
| 45 | + const pruned: string[] = []; |
| 46 | + for (const identity of identities) { |
| 47 | + if (identityHasProjectInput(db, identity, now)) continue; |
| 48 | + const removed = deleteTaskScheduleRowsForProject(db, identity); |
| 49 | + if (removed > 0) pruned.push(identity); |
| 50 | + } |
| 51 | + if (pruned.length > 0) { |
| 52 | + log( |
| 53 | + `[dreamer] pruned schedule rows of ${pruned.length} idle identit${pruned.length === 1 ? "y" : "ies"} (no memories, no recent sessions, no task input)`, |
| 54 | + ); |
| 55 | + } |
| 56 | + return pruned; |
| 57 | +} |
0 commit comments