(sdk: ISdk, kv: StateKV)
| 93 | } |
| 94 | |
| 95 | export function registerEvictFunction(sdk: ISdk, kv: StateKV): void { |
| 96 | sdk.registerFunction("mem::evict", |
| 97 | async (data: { dryRun?: boolean }): Promise<EvictionStats> => { |
| 98 | const dryRun = data?.dryRun ?? false; |
| 99 | const { decrementImageRef } = await import("./image-refs.js"); |
| 100 | |
| 101 | const configOverride = await kv |
| 102 | .get<Partial<EvictionConfig>>(KV.config, "eviction") |
| 103 | .catch(() => null); |
| 104 | const cfg = { ...DEFAULTS, ...configOverride }; |
| 105 | |
| 106 | const now = Date.now(); |
| 107 | const stats: EvictionStats = { |
| 108 | staleSessions: 0, |
| 109 | lowImportanceObs: 0, |
| 110 | capEvictions: 0, |
| 111 | expiredMemories: 0, |
| 112 | nonLatestMemories: 0, |
| 113 | dryRun, |
| 114 | }; |
| 115 | |
| 116 | let recoveredStaleSessions = 0; |
| 117 | const sessions = await kv.list<Session>(KV.sessions).catch(() => []); |
| 118 | const summaries = await kv |
| 119 | .list<SessionSummary>(KV.summaries) |
| 120 | .catch(() => []); |
| 121 | const summaryIds = new Set(summaries.map((s) => s.sessionId)); |
| 122 | |
| 123 | for (const session of sessions) { |
| 124 | if (!session.startedAt) continue; |
| 125 | const age = now - new Date(session.startedAt).getTime(); |
| 126 | const staleDays = cfg.staleSessionDays * MS_PER_DAY; |
| 127 | if (age > staleDays && !summaryIds.has(session.id)) { |
| 128 | if (dryRun) { |
| 129 | stats.staleSessions++; |
| 130 | } else { |
| 131 | const observations = await kv |
| 132 | .list<CompressedObservation | RawObservation>( |
| 133 | KV.observations(session.id), |
| 134 | ) |
| 135 | .catch((err) => { |
| 136 | logger.warn("Stale session observation scan failed", { |
| 137 | sessionId: session.id, |
| 138 | error: err instanceof Error ? err.message : String(err), |
| 139 | }); |
| 140 | return null; |
| 141 | }); |
| 142 | if (!observations) continue; |
| 143 | |
| 144 | let recovered = false; |
| 145 | const hasCompressedObservations = observations.some( |
| 146 | isCompressedObservation, |
| 147 | ); |
| 148 | if (hasCompressedObservations) { |
| 149 | recovered = await recoverStaleSession(sdk, session.id); |
| 150 | if (!recovered) continue; |
| 151 | recoveredStaleSessions++; |
| 152 | } else if (observations.length > 0) { |
no test coverage detected