import { DEFAULT_REFLECT_MIN_GROUP_SIZE } from './experience-reflect.mjs'; function readFlag(value, fallback = false) { if (value == null || value === '') return fallback; const normalized = String(value).trim().toLowerCase(); if (['1', 'true', 'yes', 'on'].includes(normalized)) return true; if (['0', 'false', 'no', 'off'].includes(normalized)) return false; return fallback; } function bounded(value, fallback, min, max) { const parsed = Number(value); if (!Number.isFinite(parsed)) return fallback; return Math.min(max, Math.max(min, parsed)); } export function resolveExperienceReflectPolicy(env = process.env) { const intervalHours = Math.round( bounded(env.EXPERIENCE_REFLECT_INTERVAL_HOURS, 6, 1, 168), ); return { enabled: readFlag(env.EXPERIENCE_REFLECT_ENABLED, false), intervalMs: intervalHours * 3_600_000, minGroupSize: Math.round( bounded(env.EXPERIENCE_REFLECT_MIN_GROUP_SIZE, DEFAULT_REFLECT_MIN_GROUP_SIZE, 2, 20), ), scopes: String(env.EXPERIENCE_REFLECT_SCOPES ?? 'global') .split(/[\s,]+/u) .map((scope) => scope.trim()) .filter(Boolean) .slice(0, 20), runOnStart: readFlag(env.EXPERIENCE_REFLECT_RUN_ON_START, true), }; } export async function runExperienceReflectOnce({ experienceService, policy = resolveExperienceReflectPolicy(), logger = console, } = {}) { if (!experienceService || typeof experienceService.reflect !== 'function') { return { ok: false, skipped: true, reason: 'experience_service_unavailable' }; } if (!policy.enabled) { return { ok: true, skipped: true, reason: 'disabled' }; } const scopes = policy.scopes.length > 0 ? policy.scopes : ['global']; const results = []; let created = 0; let archived = 0; for (const scope of scopes) { const result = await experienceService.reflect({ scope, minGroupSize: policy.minGroupSize, }); results.push({ scope, ...result }); created += Number(result?.created ?? 0); archived += Number(result?.archived ?? 0); } if (created > 0 || archived > 0) { logger.log?.( `[ExperienceReflect] created=${created} archived=${archived} scopes=${scopes.join(',')}`, ); } return { ok: true, skipped: false, created, archived, scopes, results, }; } export function startExperienceReflectWorker({ experienceService, env = process.env, logger = console, setIntervalFn = setInterval, } = {}) { const policy = resolveExperienceReflectPolicy(env); if (!policy.enabled || !experienceService?.reflect) { return { stop() {}, policy }; } let stopped = false; let running = false; const runOnce = async () => { if (stopped || running) return; running = true; try { await runExperienceReflectOnce({ experienceService, policy, logger, }); } catch (error) { logger.warn?.( '[ExperienceReflect] worker run failed:', error instanceof Error ? error.message : error, ); } finally { running = false; } }; if (policy.runOnStart) { void runOnce(); } const timer = setIntervalFn(runOnce, policy.intervalMs); timer?.unref?.(); logger.log?.( `[ExperienceReflect] worker enabled (interval=${policy.intervalMs}ms, minGroup=${policy.minGroupSize}, scopes=${policy.scopes.join(',') || 'global'})`, ); return { policy, stop() { stopped = true; clearInterval(timer); }, runOnce, }; }