import { b as getLogger } from './paths-2M_UxD_H.js';
import { d as getBrainNativeDb } from './sqlite-Dsbe0wec.js';
import { a as addGraphEdge } from './graph-auto-populate2-DYsUNGSL.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';

//#region ../core/dist/memory/observer-reflector.js
/**
* Observer/Reflector — LLM-driven session compression for CLEO BRAIN.
*
* Implements the Mastra-style two-agent compression pattern that achieves
* 3-6x context compression on text and 5-40x on tool-call-heavy workloads.
*
* ## Architecture
*
* **Observer** (runs after each task or when observation count ≥ threshold):
* - Takes raw brain observations from the current session
* - Produces 3-5 dense, dated, prioritized observation notes (≤ 300 chars each)
* - Compresses verbose tool outputs into 1-line summaries
* - Stored as brain_observations with source='observer-compressed'
* - Linked to original observation IDs via brain_memory_links table
*
* **Reflector** (runs at session end when observation count ≥ threshold):
* - Takes accumulated observation notes from the current session
* - Restructures: combines related items, identifies cross-cutting patterns,
*   drops superseded observations
* - Outputs stored as brain_patterns and brain_learnings via existing store fns
* - Source tagged as 'reflector-synthesized'
*
* ## LLM Backend
*
* Routes through {@link executeForRole} (T9320), 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. `CLEO_OBSERVER_MODEL` continues to override the
* resolved model when set. When no credential is configured for the resolved
* provider, both functions silently no-op — the rest of the memory pipeline
* still runs normally.
*
* ## Configuration
*
* Add to `config.json` under `brain.observer`:
* ```json
* {
*   "brain": {
*     "observer": {
*       "enabled": true,
*       "threshold": 10
*     },
*     "reflector": {
*       "enabled": true
*     }
*   }
* }
* ```
*
* @task T554
* @epic T549
* @see .cleo/agent-outputs/R-llm-memory-systems-research.md §7 (Observer/Reflector Pattern)
*/
var log = getLogger("observer-reflector");
/**
* Logical role for observer/reflector LLM calls.
*
* Forwarded to {@link executeForRole} (T9320), which resolves the concrete
* provider/model/credential via the standard role chain (T9255). Observer and
* reflector are both consolidation-tier tasks (compress + restructure recent
* BRAIN observations). The `CLEO_OBSERVER_MODEL` env var still wins when set,
* preserving the historical override knob.
*
* @task T9255
*/
var OBSERVER_ROLE = "consolidation";
/** Maximum tokens for LLM response. Enough for 5 structured observations. */
var MAX_RESPONSE_TOKENS = 1024;
/** Default threshold: trigger observer after this many session observations. */
var DEFAULT_OBSERVER_THRESHOLD = 10;
/** Maximum observations passed to observer in a single batch. */
var OBSERVER_BATCH_LIMIT = 30;
/** Source tag written to brain_observations for observer-compressed entries. */
var OBSERVER_SOURCE = "observer-compressed";
/** Source tag written to brain_patterns / brain_learnings for reflector outputs. */
var REFLECTOR_SOURCE = "reflector-synthesized";
/**
* Load observer configuration for the project.
* Falls back to defaults on any error (enabled=true, threshold=10).
*/
async function loadObserverConfig(projectRoot) {
	try {
		const { loadConfig } = await import('./config2-CdsX487B.js');
		const observer = (await loadConfig(projectRoot)).brain?.["observer"];
		return {
			enabled: observer?.["enabled"] !== false,
			threshold: typeof observer?.["threshold"] === "number" ? observer["threshold"] : DEFAULT_OBSERVER_THRESHOLD
		};
	} catch {
		return {
			enabled: true,
			threshold: DEFAULT_OBSERVER_THRESHOLD
		};
	}
}
/**
* Load reflector configuration for the project.
* Falls back to defaults on any error (enabled=true).
*/
async function loadReflectorConfig(projectRoot) {
	try {
		const { loadConfig } = await import('./config2-CdsX487B.js');
		return { enabled: ((await loadConfig(projectRoot)).brain?.["reflector"])?.["enabled"] !== false };
	} catch {
		return { enabled: true };
	}
}
/**
* Issue a single LLM call for the observer/reflector 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). Replaces the previous hardcoded
* `fetch('https://api.anthropic.com/v1/messages')` so changing
* `config.llm.roles.consolidation.provider` actually changes the wire target.
*
* `CLEO_OBSERVER_MODEL` continues to override the resolved model when set —
* historical override knob preserved.
*
* Returns `null` when no credential is available or the call fails — callers
* fall back to the no-op path.
*
* @param projectRoot - Project root for config + tier-5 credential lookup.
* @param systemPrompt - System instruction for the LLM.
* @param userContent - User message content.
* @returns The assistant response text, or `null` on failure.
*
* @task T9320
*/
async function callAnthropicLlm(projectRoot, systemPrompt, userContent) {
	const envOverride = process.env["CLEO_OBSERVER_MODEL"];
	const modelOverride = envOverride !== void 0 && envOverride.length > 0 ? envOverride : void 0;
	return (await executeForRole(OBSERVER_ROLE, systemPrompt, userContent, {
		projectRoot,
		maxTokens: MAX_RESPONSE_TOKENS,
		...modelOverride !== void 0 ? { modelOverride } : {}
	}))?.content ?? null;
}
/**
* Attempt to parse the LLM response as JSON. Strips markdown code fences
* (```json ... ```) before parsing. Returns null on parse failure.
*/
function parseJsonResponse(text) {
	try {
		const cleaned = text.replace(/^```(?:json)?\s*/m, "").replace(/\s*```\s*$/m, "").trim();
		return JSON.parse(cleaned);
	} catch {
		return null;
	}
}
/**
* Query recent brain_observations for a session.
*
* Returns up to `limit` observations (most recent first) that:
* - Were created within the current session (`source_session_id = sessionId`)
* - Are not already observer-compressed (`source_type != 'observer-compressed'`)
* - Have not been soft-evicted (`invalid_at IS NULL`)
*
* Falls back to all uncompressed observations from the last 24 hours when
* sessionId is not provided or yields fewer than 2 results.
*/
function fetchSessionObservations(sessionId, limit) {
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return [];
	try {
		if (sessionId) {
			const rows = nativeDb.prepare(`
          SELECT id, type, title, narrative, created_at, source_session_id
          FROM brain_observations
          WHERE source_session_id = ?
            AND (source_type IS NULL OR source_type != ?)
            AND invalid_at IS NULL
          ORDER BY created_at DESC
          LIMIT ?
        `).all(sessionId, OBSERVER_SOURCE, limit);
			if (rows.length >= 2) return rows;
		}
		const cutoff = (/* @__PURE__ */ new Date(Date.now() - 1440 * 60 * 1e3)).toISOString().replace("T", " ").slice(0, 19);
		return nativeDb.prepare(`
        SELECT id, type, title, narrative, created_at, source_session_id
        FROM brain_observations
        WHERE created_at >= ?
          AND (source_type IS NULL OR source_type != ?)
          AND invalid_at IS NULL
        ORDER BY created_at DESC
        LIMIT ?
      `).all(cutoff, OBSERVER_SOURCE, limit);
	} catch {
		return [];
	}
}
/**
* Count uncompressed observations for a session.
* Used to check the trigger threshold before running the observer.
*/
function countSessionObservations(sessionId) {
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return 0;
	try {
		if (sessionId) {
			const row = nativeDb.prepare(`
          SELECT COUNT(*) as cnt
          FROM brain_observations
          WHERE source_session_id = ?
            AND (source_type IS NULL OR source_type != ?)
            AND invalid_at IS NULL
        `).get(sessionId, OBSERVER_SOURCE);
			if (row && row.cnt > 0) return row.cnt;
		}
		const cutoff = (/* @__PURE__ */ new Date(Date.now() - 1440 * 60 * 1e3)).toISOString().replace("T", " ").slice(0, 19);
		return nativeDb.prepare(`
        SELECT COUNT(*) as cnt
        FROM brain_observations
        WHERE created_at >= ?
          AND (source_type IS NULL OR source_type != ?)
          AND invalid_at IS NULL
      `).get(cutoff, OBSERVER_SOURCE)?.cnt ?? 0;
	} catch {
		return 0;
	}
}
/**
* Store a compressed observer note as a brain_observation entry.
*
* Uses source_type = 'observer-compressed' so it is excluded from
* future observer input batches (no infinite compression loops).
*
* @returns ID of the stored entry, or null on failure.
*/
function storeObserverNote(projectRoot, note, sessionId) {
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return null;
	try {
		const id = `O-${randomBytes(4).toString("hex")}`;
		const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
		const title = `[Observer p${note.priority}] ${note.observation.slice(0, 80)}`;
		const narrative = `${note.observation} (compressed from: ${note.source_ids.join(", ")})`;
		nativeDb.prepare(`
        INSERT INTO brain_observations
          (id, type, title, narrative, source_type, source_session_id, quality_score,
           memory_tier, created_at)
        VALUES (?, 'change', ?, ?, ?, ?, ?, 'short', ?)
      `).run(id, title, narrative, OBSERVER_SOURCE, sessionId ?? null, Math.max(.6, 1 - (note.priority - 1) * .1), now);
		for (const sourceId of note.source_ids) addGraphEdge(projectRoot, `observation:${id}`, `observation:${sourceId}`, "supersedes", .9, "observer-reflector").catch(() => void 0);
		return id;
	} catch (err) {
		const msg = err instanceof Error ? err.message : String(err);
		log.warn(`Failed to store observer note: ${msg}`);
		return null;
	}
}
/**
* Mark observations as superseded by the reflector.
* Sets invalid_at so they are excluded from future queries.
*/
function markSuperseded(ids) {
	if (ids.length === 0) return;
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return;
	const now = (/* @__PURE__ */ new Date()).toISOString().replace("T", " ").slice(0, 19);
	try {
		const BATCH = 100;
		for (let i = 0; i < ids.length; i += BATCH) {
			const batch = ids.slice(i, i + BATCH);
			const placeholders = batch.map(() => "?").join(",");
			nativeDb.prepare(`UPDATE brain_observations
           SET invalid_at = ?, updated_at = ?
           WHERE id IN (${placeholders}) AND invalid_at IS NULL`).run(now, now, ...batch);
		}
	} catch {}
}
/** System prompt for the Observer LLM call. */
var OBSERVER_SYSTEM_PROMPT = `You are compressing AI coding session activity into dense observation notes.
Input: recent observations and tool outputs from a CLEO agent coding session.
Output: 3-5 dated, prioritized observation notes (each ≤300 chars).
Focus on: state changes, decisions, errors, completed work.
Drop: verbose tool output, repetition, transient state.
Format: JSON array of objects with keys:
  - date: ISO date string (YYYY-MM-DD)
  - priority: integer 1-5 (1=architectural decision, 2=implementation choice, 3=constraint/bug, 4=routine action, 5=noise)
  - observation: string ≤300 chars — capture DECISION+RATIONALE not the discussion
  - source_ids: array of original observation IDs that this note compresses

Rules:
- For tool calls: capture the outcome, not the invocation
- For file reads: capture what was LEARNED, not what was read
- For errors: capture the ROOT CAUSE, not the stack trace
- Prefer priority 1-2 items; include priority 4-5 only if significant count
- ONLY return valid JSON array, no markdown fences or extra text`;
async function runObserver(projectRoot, sessionId, options) {
	const empty = {
		ran: false,
		stored: 0,
		compressedIds: [],
		notes: []
	};
	const { resolveCredentials } = await import('./credentials-DMWnF6hI.js');
	if (!resolveCredentials("anthropic").apiKey) return empty;
	const cfg = await loadObserverConfig(projectRoot);
	if (!cfg.enabled) return empty;
	try {
		const { getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
		await getBrainDb(projectRoot);
	} catch {
		return empty;
	}
	const effectiveThreshold = options?.thresholdOverride ?? cfg.threshold;
	if (countSessionObservations(sessionId) < effectiveThreshold) return empty;
	const observations = fetchSessionObservations(sessionId, OBSERVER_BATCH_LIMIT);
	if (observations.length < 2) return empty;
	const observationList = observations.map((obs) => `[${obs.id}] ${obs.created_at ?? "unknown"}: ${obs.title ?? ""} — ${(obs.narrative ?? "").slice(0, 200)}`).join("\n");
	const rawResponse = await callAnthropicLlm(projectRoot, OBSERVER_SYSTEM_PROMPT, `Recent observations to compress (${observations.length} entries):\n\n${observationList}`);
	if (!rawResponse) return empty;
	const notes = parseJsonResponse(rawResponse);
	if (!notes || !Array.isArray(notes) || notes.length === 0) {
		log.warn("Observer: failed to parse LLM response as ObserverNote[]");
		return empty;
	}
	let stored = 0;
	const compressedIds = [];
	for (const note of notes) {
		if (typeof note.observation !== "string" || !note.observation || !Array.isArray(note.source_ids)) continue;
		if (storeObserverNote(projectRoot, note, sessionId) !== null) {
			stored++;
			compressedIds.push(...note.source_ids);
		}
	}
	return {
		ran: true,
		stored,
		compressedIds: [...new Set(compressedIds)],
		notes
	};
}
/** System prompt for the Reflector LLM call. */
var REFLECTOR_SYSTEM_PROMPT = `You are restructuring accumulated observations from a coding agent session into durable knowledge.
Input: observation notes from the session (both raw and observer-compressed).
Output: extracted patterns + learnings.

Rules:
- Combine related observations into single insights
- Identify cross-cutting patterns (3+ similar events)
- Drop observations superseded by later ones (list their IDs in "superseded")
- Preserve ALL architectural decisions and their rationale
- Preserve ALL discovered constraints and bugs with root causes
- Add cross-cutting insights that emerge from the full picture

Format: JSON object with keys:
  - patterns: array of { pattern: string (≤300 chars), context: string (≤150 chars) }
  - learnings: array of { insight: string (≤300 chars), confidence: number 0.0-1.0 }
  - superseded: array of observation IDs (strings) that are now redundant

ONLY return valid JSON, no markdown fences or extra text.
Maximum 5 patterns, 5 learnings. Quality over quantity.`;
/**
* Run the Reflector on accumulated session observation notes.
*
* Triggered at session end when:
* 1. `brain.reflector.enabled` is true (default)
* 2. Sufficient observations exist to reflect on (≥ 3)
* 3. `ANTHROPIC_API_KEY` is set in the environment
*
* Steps:
* 1. Fetch recent observations (raw + observer-compressed) for the session
* 2. Call Anthropic LLM with Reflector prompt
* 3. Parse response as { patterns, learnings, superseded }
* 4. Store patterns via storePattern()
* 5. Store learnings via storeLearning()
* 6. Mark superseded observations as invalid (soft-evict)
*
* Always best-effort — errors are caught and logged, never thrown.
*
* @param projectRoot - Absolute path to project root.
* @param sessionId - Current CLEO session ID.
* @returns ReflectorResult with counts.
*/
async function runReflector(projectRoot, sessionId) {
	const empty = {
		ran: false,
		patternsStored: 0,
		learningsStored: 0,
		supersededIds: []
	};
	const { resolveCredentials } = await import('./credentials-DMWnF6hI.js');
	if (!resolveCredentials("anthropic").apiKey) return empty;
	if (!(await loadReflectorConfig(projectRoot)).enabled) return empty;
	try {
		const { getBrainDb } = await import('./memory-sqlite-Dg5uGJCo.js');
		await getBrainDb(projectRoot);
	} catch {
		return empty;
	}
	const nativeDb = getBrainNativeDb();
	if (!nativeDb) return empty;
	let observations = [];
	try {
		const cutoff = (/* @__PURE__ */ new Date(Date.now() - 2880 * 60 * 1e3)).toISOString().replace("T", " ").slice(0, 19);
		if (sessionId) observations = nativeDb.prepare(`
          SELECT id, type, title, narrative, created_at, source_type
          FROM brain_observations
          WHERE source_session_id = ?
            AND invalid_at IS NULL
          ORDER BY created_at DESC
          LIMIT 50
        `).all(sessionId);
		if (observations.length < 3) observations = nativeDb.prepare(`
          SELECT id, type, title, narrative, created_at, source_type
          FROM brain_observations
          WHERE created_at >= ?
            AND invalid_at IS NULL
          ORDER BY created_at DESC
          LIMIT 50
        `).all(cutoff);
	} catch {
		return empty;
	}
	if (observations.length < 3) return empty;
	const obsList = observations.map((obs) => `[${obs.id}] (${obs.source_type ?? "raw"}): ${obs.title ?? ""} — ${(obs.narrative ?? "").slice(0, 200)}`).join("\n");
	const rawResponse = await callAnthropicLlm(projectRoot, REFLECTOR_SYSTEM_PROMPT, `Session observations to synthesize (${observations.length} entries):\n\n${obsList}`);
	if (!rawResponse) return empty;
	const output = parseJsonResponse(rawResponse);
	if (!output || typeof output !== "object") {
		log.warn("Reflector: failed to parse LLM response");
		return empty;
	}
	let patternsStored = 0;
	let learningsStored = 0;
	const newEntryIds = [];
	if (Array.isArray(output.patterns)) for (const p of output.patterns) {
		if (typeof p.pattern !== "string" || !p.pattern) continue;
		try {
			const stored = await storePattern(projectRoot, {
				type: "workflow",
				pattern: p.pattern.slice(0, 500),
				context: (p.context ?? "").slice(0, 200),
				source: REFLECTOR_SOURCE
			});
			newEntryIds.push(`pattern:${stored.id}`);
			patternsStored++;
		} catch {}
	}
	if (Array.isArray(output.learnings)) for (const l of output.learnings) {
		if (typeof l.insight !== "string" || !l.insight) continue;
		const confidence = typeof l.confidence === "number" ? Math.max(.1, Math.min(1, l.confidence)) : .7;
		try {
			const stored = await storeLearning(projectRoot, {
				insight: l.insight.slice(0, 500),
				source: REFLECTOR_SOURCE,
				confidence,
				actionable: confidence >= .7
			});
			newEntryIds.push(`learning:${stored.id}`);
			learningsStored++;
		} catch {}
	}
	const supersededIds = Array.isArray(output.superseded) ? output.superseded.filter((id) => typeof id === "string") : [];
	markSuperseded(supersededIds);
	if (newEntryIds.length > 0 && supersededIds.length > 0) for (const newNodeId of newEntryIds) for (const obsId of supersededIds) addGraphEdge(projectRoot, newNodeId, `observation:${obsId}`, "supersedes", 1, "reflector-synthesized:session-end-reflection").catch(() => {});
	return {
		ran: true,
		patternsStored,
		learningsStored,
		supersededIds
	};
}

export { runReflector as a, runObserver as r };
//# sourceMappingURL=observer-reflector2-D8nxuglX.js.map
