import { d as getBrainNativeDb } from './sqlite-Dsbe0wec.js';
import { t as typedAll } from './typed-query-l1D_QElt.js';
import { s as storeLearning } from './learnings2-2m2p7lzT.js';
import { e as executeForRole } from './role-executor-C4-3bmgC.js';
import { s as storePattern } from './patterns2-DYKfsbwN.js';
import { randomBytes } from 'node:crypto';
import './chunk-DDP5xfzh.js';
import 'node:module';
import './paths-2M_UxD_H.js';
import 'node:fs';
import 'node:path';
import 'node:os';
import 'node:events';
import 'node:diagnostics_channel';
import 'fs';
import 'events';
import 'util';
import 'path';
import 'assert';
import 'worker_threads';
import 'module';
import 'node:url';
import 'url';
import 'buffer';
import 'node:child_process';
import 'node:process';
import 'node:async_hooks';
import './dual-scope-db-ATrwq0LH.js';
import './indexes-aYlULWG3.js';
import './utils-BvHPJOVk.js';
import './conduit-Ca_OFWR_.js';
import './unique-constraint-BM-Apx1H.js';
import './memory-schema-Bi7cChwo.js';
import './agent-registry-BtE5uhMO.js';
import './nexus-sqlite2-DgTuv9BQ.js';
import './nexus-schema2-BEd-ol6e.js';
import './logger-ChCwlV5k.js';
import 'node:sqlite';
import './lock-BoHF25mx.js';
import './index-IVoIz4D5.js';
import 'constants';
import 'stream';
import './memory-accessor2-BzlgmY-Z.js';
import './graph-auto-populate2-DYsUNGSL.js';
import './quality-scoring-CCPuRQmJ.js';
import './temporal-supersession-BZ46j30w.js';
import './credential-pool-B04qO8at.js';
import './config-B0nEjgSh.js';
import './json-DXl7iDYg.js';
import 'node:fs/promises';
import './index-Ca4C2M45.js';
import './role-resolver-DF9BQ_U6.js';
import 'node:stream';
import 'crypto';
import 'os';
import 'node:http2';
import 'fs/promises';
import 'node:net';

//#region ../core/dist/memory/sleep-consolidation.js
/**
* Sleep-Time Consolidation — LLM-driven background memory hygiene for CLEO BRAIN.
*
* Implements the "sleep-time compute" pattern inspired by Letta OS: after a
* session ends, a cheap LLM pass runs in the background to:
*   1. Merge near-duplicate entries (embedding similarity > 0.85)
*   2. Prune short-tier stale entries with low quality (7d old, quality < 0.4)
*   3. Synthesize frequently-cited learnings into higher-quality patterns
*   4. Extract cross-cutting insights from clusters of related observations
*
* All LLM calls route through {@link executeForRole} (T9320), which in turn
* uses `resolveLLMForRole('consolidation')` (T9255) — so the provider + model
* + credential come from `config.llm.roles.consolidation` →
* `config.llm.default` → `config.llm.daemon` → implicit fallback, and the
* matching transport (Anthropic Messages, OpenAI chat-completions, or any
* future api-mode) is selected automatically. No credential = silent no-op
* for LLM steps; structural steps still run. All errors are caught and
* logged — nothing here may block session end.
*
* ## Configuration
*
* Add to `config.json` under `brain.sleepConsolidation`:
* ```json
* {
*   "brain": {
*     "sleepConsolidation": {
*       "enabled": true
*     }
*   }
* }
* ```
*
* @task T555
* @epic T549
* @see packages/core/src/memory/observer-reflector.ts (Observer/Reflector pattern)
* @see packages/core/src/memory/brain-lifecycle.ts (runConsolidation)
*/
/**
* Logical role for sleep-consolidation LLM calls.
*
* Forwarded to {@link executeForRole}, which resolves the concrete
* provider/model/credential via `resolveLLMForRole('consolidation')` — so the
* provider + model + credential come from `config.llm.roles.consolidation` →
* `config.llm.default` → `config.llm.daemon` → implicit fallback (see
* {@link IMPLICIT_FALLBACK_MODEL} in `llm/role-resolver.ts`).
*
* @task T9255
*/
var SLEEP_ROLE = "consolidation";
/** Embedding similarity threshold above which two entries are considered duplicates. */
var DUPLICATE_SIMILARITY_THRESHOLD = .85;
/** Minimum age (days) before a short-tier entry can be pruned for low quality. */
var STALE_AGE_DAYS = 7;
/** Maximum quality score for a short-tier entry to be considered for pruning. */
var PRUNE_QUALITY_THRESHOLD = .4;
/** Minimum citation count to trigger pattern synthesis for a learning. */
var SYNTHESIS_CITATION_MIN = 3;
/** Maximum tokens for LLM responses. */
var MAX_RESPONSE_TOKENS = 1024;
/** Source tag written to brain_observations for sleep-consolidation results. */
var SLEEP_SOURCE = "sleep-consolidation";
/**
* Load sleep consolidation configuration from the project config.
* Defaults to enabled=true when config is missing or unreadable.
*
* @param projectRoot - Project root directory.
*/
async function loadSleepConfig(projectRoot) {
	try {
		const { loadConfig } = await import('./config2-CdsX487B.js');
		return { enabled: ((await loadConfig(projectRoot)).brain?.["sleepConsolidation"])?.["enabled"] !== false };
	} catch {
		return { enabled: true };
	}
}
/**
* Issue a single LLM call for the consolidation role.
*
* Routes through {@link executeForRole} which resolves
* provider/model/credential via the standard role chain (T9255) and dispatches
* to the matching transport — Anthropic Messages, OpenAI chat-completions, or
* any future API mode — based on the resolved provider. The previous hardcoded
* `fetch('https://api.anthropic.com/v1/messages')` is gone, so changing
* `config.llm.roles.consolidation.provider` actually changes the wire target.
*
* 30-second hard deadline via {@link AbortSignal.timeout} prevents open network
* handles from blocking test process teardown (T753 root cause).
*
* Returns `null` when no credential is available or the call fails — callers
* fall back to structural heuristics.
*
* @param projectRoot - Project root for config + tier-5 credential lookup.
* @param systemPrompt - System instruction for the LLM.
* @param userContent - User message content.
*
* @task T9320
*/
async function callLlm(projectRoot, systemPrompt, userContent) {
	return (await executeForRole(SLEEP_ROLE, systemPrompt, userContent, {
		projectRoot,
		maxTokens: MAX_RESPONSE_TOKENS,
		signal: AbortSignal.timeout(3e4)
	}))?.content ?? null;
}
/**
* Compress a plain-text payload via the injected {@link ContextEngine} when
* the payload is large enough to warrant it.
*
* Converts the text to a single `user` TransportMessage, calls
* {@link ContextEngine.compress}, and extracts the summary from the first
* returned assistant message. Falls back to the original text on any error
* so the consolidation pipeline is never blocked by a compression failure.
*
* @param text - Raw text payload to (potentially) compress.
* @param engine - Optional context engine. When absent the original text is returned.
* @param topic - Topic hint forwarded to {@link ContextEngine.compress}.
*/
async function _maybeCompress(text, engine, topic) {
	if (engine === void 0) return text;
	const msgs = [{
		role: "user",
		content: text
	}];
	const estimatedTokens = Math.ceil(text.length / 4);
	if (!engine.shouldCompress(estimatedTokens, 1e5)) return text;
	try {
		const summaryMsg = (await engine.compress(msgs, topic)).compressedMessages.find((m) => m.role === "assistant");
		return summaryMsg && typeof summaryMsg.content === "string" ? summaryMsg.content : text;
	} catch {
		return text;
	}
}
/**
* Attempt to parse LLM response as JSON. Strips markdown code fences before
* parsing. Returns null on parse failure.
*/
function parseJson(text) {
	try {
		const cleaned = text.replace(/^```(?:json)?\s*/m, "").replace(/\s*```\s*$/m, "").trim();
		return JSON.parse(cleaned);
	} catch {
		return null;
	}
}
/**
* Compute cosine similarity between two Float32 embedding buffers.
*
* Returns 0 when either buffer is null, empty, or different lengths.
* Embeddings are stored as raw Buffer of 4-byte floats (sqlite-vec format).
*
* @param a - First embedding buffer.
* @param b - Second embedding buffer.
*/
function cosineSimilarity(a, b) {
	if (!a || !b || a.length === 0 || a.length !== b.length) return 0;
	const floatCount = Math.floor(a.length / 4);
	if (floatCount === 0) return 0;
	let dot = 0;
	let normA = 0;
	let normB = 0;
	for (let i = 0; i < floatCount; i++) {
		const va = a.readFloatLE(i * 4);
		const vb = b.readFloatLE(i * 4);
		dot += va * vb;
		normA += va * va;
		normB += vb * vb;
	}
	const denom = Math.sqrt(normA) * Math.sqrt(normB);
	return denom === 0 ? 0 : dot / denom;
}
/**
* Find near-duplicate entries using embedding cosine similarity > 0.85.
*
* For each pair above the threshold, asks the LLM whether to keep/merge.
* The LLM confirms or overrides the merge decision. Kept entry gains the
* evicted entry's citation count. Duplicates are soft-evicted (invalid_at set).
*
* Falls back to structural merge (keep higher quality) when no API key is
* available or the LLM call fails.
*
* @param projectRoot - Project root for brain.db resolution.
*/
async function stepMergeDuplicates(projectRoot) {
	const { getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
	await getBrainDb(projectRoot);
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return {
		merged: 0,
		llmDecisions: 0
	};
	let merged = 0;
	let llmDecisions = 0;
	const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
	let rows;
	try {
		rows = typedAll(nativeDb.prepare(`
        SELECT id, title, narrative, quality_score, citation_count, memory_tier, created_at, embedding
        FROM brain_observations
        WHERE embedding IS NOT NULL
          AND invalid_at IS NULL
          AND memory_tier = 'short'
        ORDER BY quality_score DESC
        LIMIT 200
      `));
	} catch {
		return {
			merged: 0,
			llmDecisions: 0
		};
	}
	if (rows.length < 2) return {
		merged: 0,
		llmDecisions: 0
	};
	const pairs = [];
	for (let i = 0; i < rows.length; i++) for (let j = i + 1; j < rows.length; j++) {
		const sim = cosineSimilarity(rows[i].embedding, rows[j].embedding);
		if (sim >= DUPLICATE_SIMILARITY_THRESHOLD) pairs.push({
			a: rows[i],
			b: rows[j],
			similarity: sim
		});
	}
	if (pairs.length === 0) return {
		merged: 0,
		llmDecisions: 0
	};
	const pairBatch = pairs.slice(0, 10);
	const pairDescriptions = pairBatch.map(({ a, b, similarity }, idx) => ({
		pair: idx,
		similarity: Math.round(similarity * 100) / 100,
		a: {
			id: a.id,
			text: `${a.title ?? ""} ${a.narrative ?? ""}`.trim().slice(0, 120)
		},
		b: {
			id: b.id,
			text: `${b.title ?? ""} ${b.narrative ?? ""}`.trim().slice(0, 120)
		}
	}));
	const systemPrompt = "You are a memory deduplication assistant. Given pairs of nearly-identical memory entries evaluate whether they should be merged. For each pair output: {\"pair\":N,\"merge\":true/false,\"keep\":\"<id>\"}. Output a JSON array only, no prose. Merge when content is substantially the same; keep when content is distinct.";
	const userContent = `Memory entry pairs to evaluate:\n${JSON.stringify(pairDescriptions, null, 2)}`;
	let decisions = [];
	const rawResponse = await callLlm(projectRoot, systemPrompt, userContent);
	if (rawResponse) {
		const parsed = parseJson(rawResponse);
		if (Array.isArray(parsed)) {
			decisions = parsed;
			llmDecisions = decisions.filter((d) => d.merge).length;
		}
	}
	const processedIds = /* @__PURE__ */ new Set();
	for (let idx = 0; idx < pairBatch.length; idx++) {
		const { a, b } = pairBatch[idx];
		if (processedIds.has(a.id) || processedIds.has(b.id)) continue;
		const decision = decisions.find((d) => d.pair === idx);
		if (!(decision ? decision.merge : true)) continue;
		let keepId;
		let evictId;
		if (decision?.keep === a.id || decision?.keep === b.id) {
			keepId = decision.keep;
			evictId = keepId === a.id ? b.id : a.id;
		} else {
			keepId = (a.quality_score ?? .5) >= (b.quality_score ?? .5) ? a.id : b.id;
			evictId = keepId === a.id ? b.id : a.id;
		}
		const keepRow = a.id === keepId ? a : b;
		const evictRow = a.id === evictId ? a : b;
		const combinedCitations = (keepRow.citation_count ?? 0) + (evictRow.citation_count ?? 0);
		try {
			nativeDb.prepare(`UPDATE brain_observations SET invalid_at = ?, updated_at = ? WHERE id = ?`).run(now, now, evictId);
			if (combinedCitations > (keepRow.citation_count ?? 0)) nativeDb.prepare(`UPDATE brain_observations SET citation_count = ?, updated_at = ? WHERE id = ?`).run(combinedCitations, now, keepId);
			merged++;
			processedIds.add(a.id);
			processedIds.add(b.id);
		} catch {}
	}
	return {
		merged,
		llmDecisions
	};
}
/**
* Prune short-tier entries older than STALE_AGE_DAYS with quality < PRUNE_QUALITY_THRESHOLD.
*
* Before evicting, asks the LLM whether any entries should be preserved despite
* their low score. Preserved entries have their quality_score bumped to 0.5 so
* they survive future prune passes.
*
* @param projectRoot - Project root for brain.db resolution.
*/
async function stepPruneStale(projectRoot) {
	const { getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
	await getBrainDb(projectRoot);
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return {
		pruned: 0,
		preserved: 0
	};
	const staleCutoff = (/* @__PURE__ */ new Date(Date.now() - STALE_AGE_DAYS * 24 * 60 * 60 * 1e3)).toISOString().replace("T", " ").slice(0, 19);
	const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
	let candidates;
	try {
		candidates = typedAll(nativeDb.prepare(`
        SELECT id, title, narrative, quality_score, citation_count, memory_tier, created_at, embedding
        FROM brain_observations
        WHERE memory_tier = 'short'
          AND invalid_at IS NULL
          AND quality_score IS NOT NULL
          AND quality_score < ?
          AND created_at < ?
        ORDER BY quality_score ASC
        LIMIT 50
      `), PRUNE_QUALITY_THRESHOLD, staleCutoff);
	} catch {
		return {
			pruned: 0,
			preserved: 0
		};
	}
	if (candidates.length === 0) return {
		pruned: 0,
		preserved: 0
	};
	const candidateDescriptions = candidates.slice(0, 20).map((row) => ({
		id: row.id,
		age_days: Math.round((Date.now() - new Date(row.created_at ?? 0).getTime()) / (1440 * 60 * 1e3)),
		quality: Math.round((row.quality_score ?? 0) * 100) / 100,
		citations: row.citation_count,
		text: `${row.title ?? ""} ${row.narrative ?? ""}`.trim().slice(0, 100)
	}));
	const systemPrompt = "You are a memory curator. Given a list of low-quality, stale memory entries, decide which ones are worth preserving (i.e. contain unique, non-redundant information that would be hard to reconstruct). Return a JSON array of IDs to preserve: {\"preserve\":[\"id1\",\"id2\",...]}. Only preserve entries with genuinely unique information. When in doubt, allow eviction.";
	const userContent = `Candidate entries for eviction:\n${JSON.stringify(candidateDescriptions, null, 2)}`;
	let preserveIds = /* @__PURE__ */ new Set();
	const rawResponse = await callLlm(projectRoot, systemPrompt, userContent);
	if (rawResponse) {
		const parsed = parseJson(rawResponse);
		if (parsed && Array.isArray(parsed.preserve)) preserveIds = new Set(parsed.preserve);
	}
	let pruned = 0;
	let preserved = 0;
	for (const row of candidates) if (preserveIds.has(row.id)) try {
		nativeDb.prepare(`UPDATE brain_observations SET quality_score = 0.5, updated_at = ? WHERE id = ?`).run(now, row.id);
		preserved++;
	} catch {}
	else try {
		nativeDb.prepare(`UPDATE brain_observations SET invalid_at = ?, updated_at = ? WHERE id = ?`).run(now, now, row.id);
		pruned++;
	} catch {}
	return {
		pruned,
		preserved
	};
}
/**
* Find learnings cited >= SYNTHESIS_CITATION_MIN times and ask the LLM to
* synthesize them into a single higher-quality pattern entry.
*
* The synthesized pattern is stored via storePattern() with
* source='sleep-consolidation'. The original learnings are left intact.
*
* @param projectRoot - Project root for brain.db resolution.
* @param contextEngine - Optional engine used to compress oversized learning batches.
*/
async function stepStrengthenPatterns(projectRoot, contextEngine) {
	const { getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
	await getBrainDb(projectRoot);
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return {
		synthesized: 0,
		patternsGenerated: 0
	};
	let candidates;
	try {
		candidates = typedAll(nativeDb.prepare(`
        SELECT id, insight, confidence, citation_count, source, memory_tier
        FROM brain_learnings
        WHERE citation_count >= ?
          AND invalid_at IS NULL
        ORDER BY citation_count DESC, confidence DESC
        LIMIT 10
      `), SYNTHESIS_CITATION_MIN);
	} catch {
		return {
			synthesized: 0,
			patternsGenerated: 0
		};
	}
	if (candidates.length === 0) return {
		synthesized: 0,
		patternsGenerated: 0
	};
	let existingPatterns;
	try {
		existingPatterns = typedAll(nativeDb.prepare(`
        SELECT id, pattern, context, impact, frequency, memory_tier
        FROM brain_patterns
        WHERE source_type = ?
          AND invalid_at IS NULL
        ORDER BY frequency DESC
        LIMIT 5
      `), SLEEP_SOURCE);
	} catch {
		existingPatterns = [];
	}
	const existingPatternTexts = existingPatterns.map((p) => p.pattern.slice(0, 80)).join("; ");
	const learningDescriptions = candidates.map((l) => ({
		id: l.id,
		insight: l.insight.slice(0, 200),
		citations: l.citation_count,
		confidence: Math.round(l.confidence * 100) / 100
	}));
	const rawResponse = await callLlm(projectRoot, "You are a knowledge synthesizer. Given frequently-cited learnings, extract 1-3 higher-order patterns that capture the essence of what has been repeatedly confirmed. Each pattern should be actionable and generalizable. Return JSON: {\"patterns\":[{\"pattern\":\"...\",\"context\":\"...\",\"impact\":\"high|medium|low\"}]}. Skip patterns already captured in the existing list. Output JSON only, no prose.", await _maybeCompress(`Frequently-cited learnings to synthesize:\n${JSON.stringify(learningDescriptions, null, 2)}\n\nAlready captured patterns (do not duplicate): ${existingPatternTexts || "none"}`, contextEngine, "learning synthesis"));
	if (!rawResponse) return {
		synthesized: candidates.length,
		patternsGenerated: 0
	};
	const parsed = parseJson(rawResponse);
	if (!parsed || !Array.isArray(parsed.patterns)) return {
		synthesized: candidates.length,
		patternsGenerated: 0
	};
	let patternsGenerated = 0;
	for (const p of parsed.patterns) {
		if (!p.pattern?.trim()) continue;
		try {
			const impact = p.impact === "high" || p.impact === "medium" || p.impact === "low" ? p.impact : "medium";
			await storePattern(projectRoot, {
				type: "optimization",
				pattern: p.pattern.slice(0, 500),
				context: (p.context ?? "").slice(0, 500),
				impact,
				source: SLEEP_SOURCE
			});
			patternsGenerated++;
		} catch {}
	}
	return {
		synthesized: candidates.length,
		patternsGenerated
	};
}
/**
* Cluster recent observations by shared entity overlap and ask the LLM to
* extract a cross-cutting insight for each cluster.
*
* Insights are stored as brain_observations with source='sleep-consolidation'
* and memory_tier='medium' (they represent synthesized knowledge).
*
* @param projectRoot - Project root for brain.db resolution.
* @param contextEngine - Optional engine used to compress oversized cluster payloads.
*/
async function stepGenerateInsights(projectRoot, contextEngine) {
	const { getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
	await getBrainDb(projectRoot);
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return {
		clustersProcessed: 0,
		insightsStored: 0
	};
	const recent14d = (/* @__PURE__ */ new Date(Date.now() - 336 * 60 * 60 * 1e3)).toISOString().replace("T", " ").slice(0, 19);
	let observations;
	try {
		observations = typedAll(nativeDb.prepare(`
        SELECT id,
               COALESCE(title, '') || ' ' || COALESCE(narrative, '') AS text
        FROM brain_observations
        WHERE created_at >= ?
          AND invalid_at IS NULL
          AND (source_type IS NULL OR source_type NOT IN (?, 'observer-compressed'))
        ORDER BY quality_score DESC
        LIMIT 60
      `), recent14d, SLEEP_SOURCE);
	} catch {
		return {
			clustersProcessed: 0,
			insightsStored: 0
		};
	}
	if (observations.length < 5) return {
		clustersProcessed: 0,
		insightsStored: 0
	};
	const STOP = new Set([
		"this",
		"that",
		"with",
		"from",
		"have",
		"been",
		"will",
		"when",
		"then",
		"they",
		"were",
		"also",
		"into",
		"over",
		"some",
		"more",
		"very",
		"just",
		"each",
		"both"
	]);
	function tokenize(text) {
		const tokens = text.toLowerCase().replace(/[^a-z0-9\s]/g, " ").split(/\s+/).filter((t) => t.length >= 4 && !STOP.has(t));
		return new Set(tokens);
	}
	const tokenSets = observations.map((o) => ({
		id: o.id,
		text: o.text,
		tokens: tokenize(o.text)
	}));
	const clusters = [];
	for (const obs of tokenSets) {
		let placed = false;
		for (const cluster of clusters) {
			const firstText = tokenSets.find((t) => t.id === cluster.memberIds[0]);
			if (!firstText) continue;
			let shared = 0;
			for (const tok of obs.tokens) if (firstText.tokens.has(tok)) shared++;
			if (shared >= 3) {
				cluster.memberIds.push(obs.id);
				cluster.texts.push(obs.text.slice(0, 120));
				placed = true;
				break;
			}
		}
		if (!placed && clusters.length < 5) clusters.push({
			memberIds: [obs.id],
			texts: [obs.text.slice(0, 120)]
		});
	}
	const validClusters = clusters.filter((c) => c.memberIds.length >= 3);
	if (validClusters.length === 0) return {
		clustersProcessed: 0,
		insightsStored: 0
	};
	const clusterDescriptions = validClusters.map((c, i) => ({
		cluster: i,
		entries: c.texts.slice(0, 5)
	}));
	const rawResponse = await callLlm(projectRoot, "You are a cross-domain insight extractor. Given clusters of related memory entries, identify one cross-cutting insight per cluster that would not be obvious from any single entry. Return JSON: {\"insights\":[{\"cluster\":N,\"insight\":\"...\",\"confidence\":0.0-1.0}]}. Only include high-value insights (confidence >= 0.7). Output JSON only, no prose.", await _maybeCompress(`Memory clusters to analyse:\n${JSON.stringify(clusterDescriptions, null, 2)}`, contextEngine, "insight extraction"));
	if (!rawResponse) return {
		clustersProcessed: validClusters.length,
		insightsStored: 0
	};
	const parsed = parseJson(rawResponse);
	if (!parsed || !Array.isArray(parsed.insights)) return {
		clustersProcessed: validClusters.length,
		insightsStored: 0
	};
	let insightsStored = 0;
	const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
	for (const insight of parsed.insights) {
		if (!insight.insight?.trim()) continue;
		const confidence = Math.max(0, Math.min(1, insight.confidence ?? .7));
		if (confidence < .7) continue;
		try {
			await storeLearning(projectRoot, {
				insight: insight.insight.slice(0, 500),
				source: SLEEP_SOURCE,
				confidence,
				actionable: true,
				application: "Cross-cutting insight synthesized from clustered observations"
			});
			insightsStored++;
		} catch {}
	}
	if (insightsStored > 0) try {
		const runId = `O-${randomBytes(4).toString("hex")}`;
		nativeDb.prepare(`
          INSERT INTO brain_observations
            (id, type, title, narrative, source_type, quality_score, memory_tier, created_at)
          VALUES (?, 'change', ?, ?, ?, 0.6, 'short', ?)
        `).run(runId, `[sleep-consolidation] Insight generation run`, `Generated ${insightsStored} cross-cutting insights from ${validClusters.length} clusters.`, SLEEP_SOURCE, now);
	} catch {}
	return {
		clustersProcessed: validClusters.length,
		insightsStored
	};
}
/**
* Run the full sleep-time consolidation pipeline for CLEO BRAIN.
*
* This is the main entry point for LLM-driven background memory hygiene.
* It is designed to run after session end (via setImmediate) and must never
* throw — all errors are caught and logged.
*
* Steps (in order):
*   1. Merge duplicates — embedding-similarity-based dedup with LLM confirmation
*   2. Prune stale — evict low-quality short-tier entries; LLM may preserve some
*   3. Strengthen patterns — synthesize frequently-cited learnings into patterns
*   4. Generate insights — extract cross-cutting insights from observation clusters
*
* Graceful degradation: when no Anthropic API key is available, LLM steps
* silently skip their LLM call and fall back to structural heuristics.
*
* @param projectRoot - Project root directory for brain.db resolution.
* @param contextEngine - Optional context engine for LLM-backed compression of
*   large narrative batches. When provided, oversized observation payloads are
*   compressed via {@link ContextEngine.compress} before being sent to the LLM,
*   staying within the model's context window. When absent, raw payloads are
*   used unchanged (the existing behaviour).
* @returns Aggregated result counts from each step.
*/
async function runSleepConsolidation(projectRoot, contextEngine) {
	const empty = {
		ran: false,
		mergeDuplicates: {
			merged: 0,
			llmDecisions: 0
		},
		pruneStale: {
			pruned: 0,
			preserved: 0
		},
		strengthenPatterns: {
			synthesized: 0,
			patternsGenerated: 0
		},
		generateInsights: {
			clustersProcessed: 0,
			insightsStored: 0
		}
	};
	let config;
	try {
		config = await loadSleepConfig(projectRoot);
	} catch {
		config = { enabled: true };
	}
	if (!config.enabled) return empty;
	const result = {
		ran: true,
		mergeDuplicates: {
			merged: 0,
			llmDecisions: 0
		},
		pruneStale: {
			pruned: 0,
			preserved: 0
		},
		strengthenPatterns: {
			synthesized: 0,
			patternsGenerated: 0
		},
		generateInsights: {
			clustersProcessed: 0,
			insightsStored: 0
		}
	};
	try {
		result.mergeDuplicates = await stepMergeDuplicates(projectRoot);
	} catch (err) {
		console.warn("[sleep-consolidation] Step 1 (merge duplicates) failed:", err);
	}
	try {
		result.pruneStale = await stepPruneStale(projectRoot);
	} catch (err) {
		console.warn("[sleep-consolidation] Step 2 (prune stale) failed:", err);
	}
	try {
		result.strengthenPatterns = await stepStrengthenPatterns(projectRoot, contextEngine);
	} catch (err) {
		console.warn("[sleep-consolidation] Step 3 (strengthen patterns) failed:", err);
	}
	try {
		result.generateInsights = await stepGenerateInsights(projectRoot, contextEngine);
	} catch (err) {
		console.warn("[sleep-consolidation] Step 4 (generate insights) failed:", err);
	}
	try {
		result.dreamerUpgrade = await runDreamerUpgrade(projectRoot);
	} catch (err) {
		console.warn("[sleep-consolidation] Wave 6 dreamer upgrade failed:", err);
	}
	return result;
}
/**
* Run the Wave 6 dreamer upgrade:
*   Step 5: Compute Bayesian surprisal scores for recent observations
*   Step 6: Build RPTree from high-surprisal observations
*   Step 7: Dispatch consolidation specialists in surprisal-priority order
*
* Gracefully degrades when:
*   - No embeddings available → returns neutral scores, skips tree/specialists
*   - No LLM backend → specialists no-op silently
*   - Any step errors → logged, not thrown
*
* @param projectRoot - Project root for DB resolution.
* @returns DreamerUpgradeResult with counts from each step.
*
* @task T1146
*/
async function runDreamerUpgrade(_projectRoot) {
	const result = {
		surprisalScored: 0,
		treeNodesWritten: 0,
		treeObsAssigned: 0,
		specialistsCreated: 0,
		specialistsRan: 0,
		specialistsSkipped: 0
	};
	const [{ computeSurprisalBatch }, { buildSurprisalTree }, { dispatchSpecialists }] = await Promise.all([
		import('./surprisal-LZPyxhS7.js'),
		import('./surprisal-tree-CApfzK6D.js'),
		import('./specialists-BIQ5CzQO.js')
	]);
	const { getBrainNativeDb } = await import('./memory-sqlite-Dg5uGJCo.js');
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) {
		console.warn("[dreamer-upgrade] No database available; skipping all steps.");
		return result;
	}
	const rawObs = nativeDb.prepare(`SELECT o.id, o.type, o.title, o.narrative, o.project, o.peer_id,
              o.source_session_id, e.embedding, o.created_at
       FROM brain_observations o
       LEFT JOIN brain_embeddings e ON e.id = o.id
       WHERE o.level IS NULL OR o.level = 'explicit'
       ORDER BY o.created_at DESC
       LIMIT 100`).all();
	if (rawObs.length === 0) return result;
	const surprisalResults = computeSurprisalBatch(rawObs.map((r) => {
		let embedding = null;
		if (r.embedding instanceof Uint8Array || Buffer.isBuffer(r.embedding)) {
			const buf = r.embedding;
			embedding = Array.from(new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4));
		}
		return {
			id: r.id,
			embedding
		};
	}), { db: nativeDb });
	result.surprisalScored = surprisalResults.length;
	const obsWithEmbeddings = rawObs.filter((r) => r.embedding instanceof Uint8Array || Buffer.isBuffer(r.embedding)).map((r) => {
		const buf = r.embedding;
		return {
			id: r.id,
			embedding: Array.from(new Float32Array(buf.buffer, buf.byteOffset, buf.byteLength / 4))
		};
	});
	if (obsWithEmbeddings.length >= 2) {
		const treeResult = buildSurprisalTree(obsWithEmbeddings, { db: nativeDb });
		result.treeNodesWritten = treeResult.nodesWritten;
		result.treeObsAssigned = treeResult.obsAssigned;
	}
	const specialistResults = await dispatchSpecialists(rawObs.map((r) => ({
		id: r.id,
		type: String(r.type ?? ""),
		title: r.title != null ? String(r.title) : null,
		narrative: r.narrative != null ? String(r.narrative) : null,
		project: r.project != null ? String(r.project) : null,
		peerId: String(r.peer_id ?? "global"),
		sourceSessionId: r.source_session_id != null ? String(r.source_session_id) : null
	})), surprisalResults, { db: nativeDb });
	result.specialistsCreated = specialistResults.totalCreated;
	result.specialistsRan = specialistResults.specialists.filter((s) => !s.skipped).length;
	result.specialistsSkipped = specialistResults.totalSkipped;
	return result;
}

export { runSleepConsolidation };
//# sourceMappingURL=sleep-consolidation-BBHx9FOA.js.map
