UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

1,208 lines (1,207 loc) 48 kB
import { n as asNullableRecord } from "./record-coerce-DHZ4bFlT.js"; import { i as formatErrorMessage } from "./errors-BXgSefBE.js"; import { _ as uniqueStrings, l as normalizeStringEntries } from "./string-normalization-WNUDCpXX.js"; import { t as appendRegularFile } from "./regular-file-BD2zl6_l.js"; import { H as formatMemoryDreamingDay, X as resolveMemoryLightDreamingConfig, Y as resolveMemoryDreamingWorkspaces, Z as resolveMemoryRemDreamingConfig } from "./dreaming-SqhFI2Et.js"; import { C as parseUsageCountedSessionIdFromFileName } from "./paths-NEwU8m3X.js"; import "./security-runtime-CQm7DD1u.js"; import "./string-coerce-runtime-CEGJWkQ_.js"; import "./memory-core-host-status-DVjlVr90.js"; import { f as loadSessionTranscriptClassificationForAgent, l as buildSessionEntry, m as sessionPathForFile, p as normalizeSessionTranscriptPathForComparison, u as listSessionFilesForAgent } from "./engine-qmd-BXdBKAnj.js"; import "./memory-core-host-engine-qmd-C4kBQq_B.js"; import { h as writeMemoryCoreWorkspaceEntries, m as readMemoryCoreWorkspaceEntries, n as DREAMING_SESSION_INGESTION_FILES_NAMESPACE, r as DREAMING_SESSION_INGESTION_SEEN_NAMESPACE, t as DREAMING_DAILY_INGESTION_NAMESPACE } from "./dreaming-state-D7W5VtYL.js"; import { n as normalizeTrimmedString } from "./dreaming-shared-BChAYuyu.js"; import { f as runDetachedDreamNarrative, u as generateAndAppendDreamNarrative } from "./dreaming-narrative-msOWBXVS.js"; import { t as writeDailyDreamingPhaseBlock } from "./dreaming-markdown-BigNnY14.js"; import { n as textSimilarity } from "./tokenize-CMndJ6Pz.js"; import { _ as recordDreamingPhaseSignals, b as recordShortTermRecalls, g as readShortTermRecallEntries, h as readLightStagedKeys, l as filterLiveShortTermRecallEntries, y as recordRemConsideredPhaseSignals } from "./short-term-promotion-DuBGW686.js"; import path from "node:path"; import fs from "node:fs/promises"; import { createHash } from "node:crypto"; //#region extensions/memory-core/src/dreaming-phases.ts const LIGHT_SLEEP_EVENT_TEXT = "__openclaw_memory_core_light_sleep__"; const REM_SLEEP_EVENT_TEXT = "__openclaw_memory_core_rem_sleep__"; const MEMORY_DAY_RE = /^\d{4}-\d{2}-\d{2}$/; const DAILY_MEMORY_FILENAME_RE = /^(\d{4}-\d{2}-\d{2})(?:-[^/]+)?\.md$/i; const DAILY_INGESTION_STATE_RELATIVE_PATH = path.join("memory", ".dreams", "daily-ingestion.json"); const DAILY_INGESTION_SCORE = .62; const DAILY_INGESTION_MAX_SNIPPET_CHARS = 280; const DAILY_INGESTION_MIN_SNIPPET_CHARS = 8; const DAILY_INGESTION_MAX_CHUNK_LINES = 4; const SESSION_INGESTION_STATE_RELATIVE_PATH = path.join("memory", ".dreams", "session-ingestion.json"); const SESSION_CORPUS_RELATIVE_DIR = path.join("memory", ".dreams", "session-corpus"); const SESSION_INGESTION_SCORE = .58; const SESSION_INGESTION_MAX_SNIPPET_CHARS = 280; const SESSION_INGESTION_MIN_SNIPPET_CHARS = 12; const SESSION_INGESTION_MAX_MESSAGES_PER_SWEEP = 240; const SESSION_INGESTION_MAX_MESSAGES_PER_FILE = 80; const SESSION_INGESTION_MIN_MESSAGES_PER_FILE = 12; const SESSION_INGESTION_MAX_TRACKED_MESSAGES_PER_SESSION = 4096; const SESSION_INGESTION_MAX_TRACKED_SCOPES = 2048; const SESSION_CHECKPOINT_TRANSCRIPT_FILENAME_RE = /\.checkpoint\..+\.jsonl$/i; const GENERIC_DAY_HEADING_RE = /^(?:(?:mon|monday|tue|tues|tuesday|wed|wednesday|thu|thur|thurs|thursday|fri|friday|sat|saturday|sun|sunday)(?:,\s+)?)?(?:(?:jan|january|feb|february|mar|march|apr|april|may|jun|june|jul|july|aug|august|sep|sept|september|oct|october|nov|november|dec|december)\s+\d{1,2}(?:st|nd|rd|th)?(?:,\s*\d{4})?|\d{1,2}[/-]\d{1,2}(?:[/-]\d{2,4})?|\d{4}[/-]\d{2}[/-]\d{2})$/i; const MANAGED_DAILY_DREAMING_BLOCKS = [{ heading: "## Light Sleep", startMarker: "<!-- openclaw:dreaming:light:start -->", endMarker: "<!-- openclaw:dreaming:light:end -->" }, { heading: "## REM Sleep", startMarker: "<!-- openclaw:dreaming:rem:start -->", endMarker: "<!-- openclaw:dreaming:rem:end -->" }]; function resolveWorkspaces(params) { const fallbackWorkspaceDir = normalizeTrimmedString(params.fallbackWorkspaceDir); const workspaceCandidates = params.cfg ? resolveMemoryDreamingWorkspaces(params.cfg, { primaryWorkspaceDir: fallbackWorkspaceDir, primaryAgentId: "main" }).map((entry) => entry.workspaceDir) : []; const seen = /* @__PURE__ */ new Set(); const workspaces = workspaceCandidates.filter((workspaceDir) => { if (seen.has(workspaceDir)) return false; seen.add(workspaceDir); return true; }); if (workspaces.length === 0 && fallbackWorkspaceDir) workspaces.push(fallbackWorkspaceDir); return workspaces; } function calculateLookbackCutoffMs(nowMs, lookbackDays) { return nowMs - Math.max(0, lookbackDays) * 24 * 60 * 60 * 1e3; } function isDayWithinLookback(day, cutoffMs) { const dayMs = Date.parse(`${day}T23:59:59.999Z`); return Number.isFinite(dayMs) && dayMs >= cutoffMs; } function normalizeDailyListMarker(line) { return line.replace(/^\d+\.\s+/, "").replace(/^[-*+]\s+/, "").trim(); } function normalizeDailyHeading(line) { const match = line.trim().match(/^#{1,6}\s+(.+)$/); if (!match) return null; const heading = match[1] ? normalizeDailyListMarker(match[1]) : ""; if (!heading || DAILY_MEMORY_FILENAME_RE.test(heading) || isGenericDailyHeading(heading)) return null; return heading.slice(0, DAILY_INGESTION_MAX_SNIPPET_CHARS).replace(/\s+/g, " "); } function isGenericDailyHeading(heading) { const normalized = heading.trim().replace(/\s+/g, " "); if (!normalized) return true; const lower = normalized.toLowerCase(); if (lower === "today" || lower === "yesterday" || lower === "tomorrow") return true; if (lower === "morning" || lower === "afternoon" || lower === "evening" || lower === "night") return true; return GENERIC_DAY_HEADING_RE.test(normalized); } function normalizeDailySnippet(line) { const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("#") || trimmed.startsWith("<!--")) return null; const withoutListMarker = normalizeDailyListMarker(trimmed); if (withoutListMarker.length < DAILY_INGESTION_MIN_SNIPPET_CHARS) return null; return withoutListMarker.slice(0, DAILY_INGESTION_MAX_SNIPPET_CHARS).replace(/\s+/g, " "); } const REM_REFLECTION_TAG_BLACKLIST = new Set([ "assistant", "user", "system", "subagent", "the" ]); function buildDailyChunkSnippet(heading, chunkLines, chunkKind) { const joiner = chunkKind === "list" ? "; " : " "; const body = chunkLines.join(joiner).trim(); return (heading ? `${heading}: ${body}` : body).slice(0, DAILY_INGESTION_MAX_SNIPPET_CHARS).replace(/\s+/g, " ").trim(); } function buildDailySnippetChunks(lines, limit) { const chunks = []; let activeHeading = null; let chunkLines = []; let chunkKind = null; let chunkStartLine = 0; let chunkEndLine = 0; const flushChunk = () => { if (chunkLines.length === 0) { chunkKind = null; chunkStartLine = 0; chunkEndLine = 0; return; } const snippet = buildDailyChunkSnippet(activeHeading, chunkLines, chunkKind); if (snippet.length >= DAILY_INGESTION_MIN_SNIPPET_CHARS) chunks.push({ startLine: chunkStartLine, endLine: chunkEndLine, snippet }); chunkLines = []; chunkKind = null; chunkStartLine = 0; chunkEndLine = 0; }; for (let index = 0; index < lines.length; index += 1) { const line = lines[index]; if (typeof line !== "string") continue; const heading = normalizeDailyHeading(line); if (heading) { flushChunk(); activeHeading = heading; continue; } const trimmed = line.trim(); if (!trimmed || trimmed.startsWith("<!--")) { flushChunk(); continue; } const snippet = normalizeDailySnippet(line); if (!snippet) { flushChunk(); continue; } const nextKind = /^([-*+]\s+|\d+\.\s+)/.test(trimmed) ? "list" : "paragraph"; const nextChunkLines = chunkLines.length === 0 ? [snippet] : [...chunkLines, snippet]; const candidateSnippet = buildDailyChunkSnippet(activeHeading, nextChunkLines, nextKind); if (chunkLines.length > 0 && (chunkKind !== nextKind || chunkLines.length >= DAILY_INGESTION_MAX_CHUNK_LINES || candidateSnippet.length > DAILY_INGESTION_MAX_SNIPPET_CHARS)) flushChunk(); if (chunkLines.length === 0) { chunkStartLine = index + 1; chunkKind = nextKind; } chunkLines.push(snippet); chunkEndLine = index + 1; if (chunks.length >= limit) break; } flushChunk(); return chunks.slice(0, limit); } function findManagedDailyDreamingHeadingIndex(lines, startIndex, heading) { for (let index = startIndex - 1; index >= 0; index -= 1) { const trimmed = lines[index]?.trim() ?? ""; if (!trimmed) continue; return trimmed === heading ? index : null; } return null; } function isManagedDailyDreamingBoundary(line, blockByStartMarker) { const trimmed = line.trim(); return /^#{1,6}\s+/.test(trimmed) || blockByStartMarker.has(trimmed); } function stripManagedDailyDreamingLines(lines) { const blockByStartMarker = new Map(MANAGED_DAILY_DREAMING_BLOCKS.map((block) => [block.startMarker, block])); const sanitized = [...lines]; for (let index = 0; index < sanitized.length; index += 1) { const block = blockByStartMarker.get(sanitized[index]?.trim() ?? ""); if (!block) continue; let stripUntilIndex = -1; for (let cursor = index + 1; cursor < sanitized.length; cursor += 1) { const line = sanitized[cursor]; if ((line?.trim() ?? "") === block.endMarker) { stripUntilIndex = cursor; break; } if (line && isManagedDailyDreamingBoundary(line, blockByStartMarker)) { stripUntilIndex = cursor - 1; break; } } if (stripUntilIndex < index) continue; const startIndex = findManagedDailyDreamingHeadingIndex(lines, index, block.heading) ?? index; for (let cursor = startIndex; cursor <= stripUntilIndex; cursor += 1) sanitized[cursor] = ""; index = stripUntilIndex; } return sanitized; } function entryWithinLookback(entry, cutoffMs) { if ((entry.recallDays ?? []).some((day) => isDayWithinLookback(day, cutoffMs))) return true; const lastRecalledAtMs = Date.parse(entry.lastRecalledAt); return Number.isFinite(lastRecalledAtMs) && lastRecalledAtMs >= cutoffMs; } function filterRecallEntriesWithinLookback(params) { const cutoffMs = calculateLookbackCutoffMs(params.nowMs, params.lookbackDays); return params.entries.filter((entry) => entryWithinLookback(entry, cutoffMs)); } function parseDailyMemoryFileName(fileName) { const day = fileName.match(DAILY_MEMORY_FILENAME_RE)?.[1]; return day ? { fileName, day, canonical: fileName.toLowerCase() === `${day}.md` } : null; } function compareDailyMemoryFilesByNewestDay(left, right) { const dayOrder = right.day.localeCompare(left.day); if (dayOrder !== 0) return dayOrder; if (left.canonical !== right.canonical) return left.canonical ? -1 : 1; return left.fileName.localeCompare(right.fileName); } function resolveWorkspaceMemoryRelativePath(workspaceDir, filePath) { const relativePath = path.relative(workspaceDir, filePath).replace(/\\/g, "/"); if (relativePath && relativePath !== ".." && !relativePath.startsWith("../")) return relativePath; return `memory/${path.basename(filePath)}`; } function normalizeDailyIngestionState(raw) { const filesRaw = asNullableRecord(asNullableRecord(raw)?.files); if (!filesRaw) return { version: 1, files: {} }; const files = {}; for (const [key, value] of Object.entries(filesRaw)) { const file = asNullableRecord(value); if (!file || typeof key !== "string" || key.trim().length === 0) continue; const mtimeMs = Number(file.mtimeMs); const size = Number(file.size); if (!Number.isFinite(mtimeMs) || mtimeMs < 0 || !Number.isFinite(size) || size < 0) continue; const lastDreamingDayIngested = normalizeMemoryDay(file.lastDreamingDayIngested); files[key] = { mtimeMs: Math.floor(mtimeMs), size: Math.floor(size), ...lastDreamingDayIngested ? { lastDreamingDayIngested } : {} }; } return { version: 1, files }; } function normalizeMemoryDay(value) { if (typeof value !== "string") return; const day = value.trim(); return MEMORY_DAY_RE.test(day) ? day : void 0; } async function readDailyIngestionState(workspaceDir) { const entries = await readMemoryCoreWorkspaceEntries({ namespace: DREAMING_DAILY_INGESTION_NAMESPACE, workspaceDir }); return normalizeDailyIngestionState({ version: 1, files: Object.fromEntries(entries.map((entry) => [entry.key, entry.value])) }); } async function writeDailyIngestionState(workspaceDir, state) { await writeMemoryCoreWorkspaceEntries({ namespace: DREAMING_DAILY_INGESTION_NAMESPACE, workspaceDir, entries: Object.entries(state.files).map(([key, value]) => ({ key, value })) }); } function normalizeWorkspaceKey(workspaceDir) { const resolved = path.resolve(workspaceDir).replace(/\\/g, "/"); return process.platform === "win32" ? resolved.toLowerCase() : resolved; } function normalizeSessionIngestionState(raw) { const record = asNullableRecord(raw); const filesRaw = asNullableRecord(record?.files); const files = {}; if (filesRaw) for (const [key, value] of Object.entries(filesRaw)) { const file = asNullableRecord(value); if (!file || key.trim().length === 0) continue; const mtimeMs = Number(file.mtimeMs); const size = Number(file.size); if (!Number.isFinite(mtimeMs) || mtimeMs < 0 || !Number.isFinite(size) || size < 0) continue; const lineCountRaw = Number(file.lineCount); const lastContentLineRaw = Number(file.lastContentLine); const lineCount = Number.isFinite(lineCountRaw) && lineCountRaw >= 0 ? Math.floor(lineCountRaw) : 0; const lastContentLine = Number.isFinite(lastContentLineRaw) && lastContentLineRaw >= 0 ? Math.floor(lastContentLineRaw) : 0; files[key] = { mtimeMs: Math.floor(mtimeMs), size: Math.floor(size), contentHash: typeof file.contentHash === "string" ? file.contentHash.trim() : "", lineCount, lastContentLine: Math.min(lineCount, lastContentLine) }; } const seenMessagesRaw = asNullableRecord(record?.seenMessages); const seenMessages = {}; if (seenMessagesRaw) for (const [scope, value] of Object.entries(seenMessagesRaw)) { if (scope.trim().length === 0 || !Array.isArray(value)) continue; const unique = normalizeStringEntries([...new Set(value.filter((entry) => typeof entry === "string"))]).slice(-4096); if (unique.length > 0) seenMessages[scope] = unique; } return { version: 3, files, seenMessages }; } async function readSessionIngestionState(workspaceDir) { const [fileEntries, seenChunks] = await Promise.all([readMemoryCoreWorkspaceEntries({ namespace: DREAMING_SESSION_INGESTION_FILES_NAMESPACE, workspaceDir }), readMemoryCoreWorkspaceEntries({ namespace: DREAMING_SESSION_INGESTION_SEEN_NAMESPACE, workspaceDir })]); const seenMessages = {}; const chunksByScope = /* @__PURE__ */ new Map(); for (const chunk of seenChunks) { const scope = chunk.value.scope.trim(); if (!scope) continue; const chunks = chunksByScope.get(scope) ?? []; chunks.push({ index: chunk.value.index, hashes: chunk.value.hashes }); chunksByScope.set(scope, chunks); } for (const [scope, chunks] of chunksByScope) seenMessages[scope] = chunks.toSorted((a, b) => a.index - b.index).flatMap((chunk) => chunk.hashes); return normalizeSessionIngestionState({ version: 3, files: Object.fromEntries(fileEntries.map((entry) => [entry.key, entry.value])), seenMessages }); } async function writeSessionIngestionState(workspaceDir, state) { const seenEntries = Object.entries(state.seenMessages).flatMap(([scope, hashes]) => Array.from({ length: Math.ceil(hashes.length / 512) }, (_, index) => { const chunkHashes = hashes.slice(index * 512, (index + 1) * 512); return { key: `${scope}:${index}`, value: { scope, index, hashes: chunkHashes } }; })); await Promise.all([writeMemoryCoreWorkspaceEntries({ namespace: DREAMING_SESSION_INGESTION_FILES_NAMESPACE, workspaceDir, entries: Object.entries(state.files).map(([key, value]) => ({ key, value })) }), writeMemoryCoreWorkspaceEntries({ namespace: DREAMING_SESSION_INGESTION_SEEN_NAMESPACE, workspaceDir, entries: seenEntries })]); } function trimTrackedSessionScopes(seenMessages) { const keys = Object.keys(seenMessages); if (keys.length <= SESSION_INGESTION_MAX_TRACKED_SCOPES) return seenMessages; const keep = new Set(keys.toSorted().slice(-2048)); const next = {}; for (const [scope, hashes] of Object.entries(seenMessages)) if (keep.has(scope)) next[scope] = hashes; return next; } function normalizeSessionCorpusSnippet(value) { return value.replace(/\s+/g, " ").trim().slice(0, SESSION_INGESTION_MAX_SNIPPET_CHARS); } function hashSessionMessageId(value) { return createHash("sha1").update(value).digest("hex"); } function buildSessionScopeKey(agentId, absolutePath) { const fileName = path.basename(absolutePath); return `${agentId}:${parseUsageCountedSessionIdFromFileName(fileName) ?? fileName}`; } function mergeTrackedMessageHashes(existing, additions) { if (additions.length === 0) return existing; const seen = new Set(existing); const next = existing.slice(); for (const hash of additions) if (!seen.has(hash)) { seen.add(hash); next.push(hash); } if (next.length <= SESSION_INGESTION_MAX_TRACKED_MESSAGES_PER_SESSION) return next; return next.slice(-4096); } function areStringArraysEqual(a, b) { if (a.length !== b.length) return false; for (let index = 0; index < a.length; index += 1) if (a[index] !== b[index]) return false; return true; } function buildSessionStateKey(agentId, absolutePath) { return `${agentId}:${sessionPathForFile(absolutePath)}`; } function isCheckpointSessionTranscriptPath(absolutePath) { return SESSION_CHECKPOINT_TRANSCRIPT_FILENAME_RE.test(path.basename(absolutePath)); } function buildSessionRenderedLine(params) { return `[${`${params.agentId}/${params.sessionPath}#L${params.lineNumber}`}] ${params.snippet}`.slice(0, 344); } function resolveSessionAgentsForWorkspace(params) { const { cfg, workspaceDir, primaryWorkspaceDir } = params; if (!cfg) return []; const target = normalizeWorkspaceKey(workspaceDir); const match = resolveMemoryDreamingWorkspaces(cfg, { primaryWorkspaceDir, primaryAgentId: "main" }).find((entry) => normalizeWorkspaceKey(entry.workspaceDir) === target); if (!match) return []; return uniqueStrings(match.agentIds.filter((agentId) => agentId.trim().length > 0)).toSorted(); } async function appendSessionCorpusLines(params) { if (params.lines.length === 0) return []; const relativePath = path.posix.join("memory", ".dreams", "session-corpus", `${params.day}.txt`); const absolutePath = path.join(params.workspaceDir, SESSION_CORPUS_RELATIVE_DIR, `${params.day}.txt`); await fs.mkdir(path.dirname(absolutePath), { recursive: true }); let existing = ""; try { existing = await fs.readFile(absolutePath, "utf-8"); } catch (err) { if (err?.code !== "ENOENT") throw err; } const normalizedExisting = existing.replace(/\r\n/g, "\n"); const existingLineCount = normalizedExisting.length === 0 ? 0 : normalizedExisting.endsWith("\n") ? normalizedExisting.slice(0, -1).split("\n").length : normalizedExisting.split("\n").length; await appendRegularFile({ filePath: absolutePath, content: `${params.lines.map((entry) => entry.rendered).join("\n")}\n`, rejectSymlinkParents: true }); return params.lines.map((entry, index) => { const lineNumber = existingLineCount + index + 1; return { path: relativePath, startLine: lineNumber, endLine: lineNumber, score: SESSION_INGESTION_SCORE, snippet: entry.snippet, source: "memory" }; }); } async function collectSessionIngestionBatches(params) { if (!params.cfg) return { batches: [], nextState: { version: 3, files: {}, seenMessages: {} }, changed: Object.keys(params.state.files).length > 0 || Object.keys(params.state.seenMessages).length > 0 }; const agentIds = resolveSessionAgentsForWorkspace({ cfg: params.cfg, workspaceDir: params.workspaceDir, primaryWorkspaceDir: params.primaryWorkspaceDir }); const cutoffMs = calculateLookbackCutoffMs(params.nowMs, params.lookbackDays); const batchByDay = /* @__PURE__ */ new Map(); const nextFiles = {}; const nextSeenMessages = { ...params.state.seenMessages }; let changed = false; const sessionFiles = []; for (const agentId of agentIds) { const files = await listSessionFilesForAgent(agentId); const transcriptClassification = files.length > 0 ? loadSessionTranscriptClassificationForAgent(agentId) : { dreamingNarrativeTranscriptPaths: /* @__PURE__ */ new Set(), cronRunTranscriptPaths: /* @__PURE__ */ new Set() }; for (const absolutePath of files) { if (isCheckpointSessionTranscriptPath(absolutePath)) continue; const normalizedPath = normalizeSessionTranscriptPathForComparison(absolutePath); sessionFiles.push({ agentId, absolutePath, generatedByDreamingNarrative: transcriptClassification.dreamingNarrativeTranscriptPaths.has(normalizedPath), generatedByCronRun: transcriptClassification.cronRunTranscriptPaths.has(normalizedPath), sessionPath: sessionPathForFile(absolutePath) }); } } const sortedFiles = sessionFiles.toSorted((a, b) => { if (a.agentId !== b.agentId) return a.agentId.localeCompare(b.agentId); return a.sessionPath.localeCompare(b.sessionPath); }); const totalCap = SESSION_INGESTION_MAX_MESSAGES_PER_SWEEP; let remaining = totalCap; const perFileCap = Math.min(SESSION_INGESTION_MAX_MESSAGES_PER_FILE, Math.max(SESSION_INGESTION_MIN_MESSAGES_PER_FILE, Math.ceil(totalCap / Math.max(1, sortedFiles.length)))); for (const file of sortedFiles) { if (remaining <= 0) break; const stateKey = buildSessionStateKey(file.agentId, file.absolutePath); const previous = params.state.files[stateKey]; const stat = await fs.stat(file.absolutePath).catch((err) => { if (err?.code === "ENOENT") return null; throw err; }); if (!stat) { if (previous) changed = true; continue; } const fingerprint = { mtimeMs: Math.floor(Math.max(0, stat.mtimeMs)), size: Math.floor(Math.max(0, stat.size)) }; const cursorAtEnd = previous !== void 0 && previous.lastContentLine >= previous.lineCount; if (Boolean(previous) && previous.mtimeMs === fingerprint.mtimeMs && previous.size === fingerprint.size && previous.contentHash.length > 0 && cursorAtEnd) { nextFiles[stateKey] = previous; continue; } const entry = await buildSessionEntry(file.absolutePath, { generatedByDreamingNarrative: file.generatedByDreamingNarrative, generatedByCronRun: file.generatedByCronRun }); if (!entry) continue; if (entry.generatedByDreamingNarrative || entry.generatedByCronRun) { nextFiles[stateKey] = { mtimeMs: fingerprint.mtimeMs, size: fingerprint.size, contentHash: entry.hash.trim(), lineCount: entry.lineMap.length, lastContentLine: entry.lineMap.length }; if (!previous || previous.mtimeMs !== fingerprint.mtimeMs || previous.size !== fingerprint.size || previous.contentHash !== entry.hash.trim() || previous.lineCount !== entry.lineMap.length || previous.lastContentLine !== entry.lineMap.length) changed = true; continue; } const contentHash = entry.hash.trim(); if (previous && previous.mtimeMs === fingerprint.mtimeMs && previous.size === fingerprint.size && previous.contentHash === contentHash && previous.lineCount === entry.lineMap.length && previous.lastContentLine >= previous.lineCount) { nextFiles[stateKey] = previous; continue; } const sessionScope = buildSessionScopeKey(file.agentId, file.absolutePath); const previousSeen = nextSeenMessages[sessionScope] ?? []; const seenSet = new Set(previousSeen); const newSeenHashes = []; const lines = entry.content.length > 0 ? entry.content.split("\n") : []; const lineCount = lines.length; let cursor = previous && previous.mtimeMs === fingerprint.mtimeMs && previous.size === fingerprint.size && previous.contentHash === contentHash && previous.lineCount === lineCount ? Math.max(0, Math.min(previous.lastContentLine, lineCount)) : 0; const fileCap = Math.max(1, Math.min(perFileCap, remaining)); let fileCount = 0; let lastScannedContentLine = cursor; for (let index = cursor; index < lines.length; index += 1) { if (fileCount >= fileCap || remaining <= 0) break; lastScannedContentLine = index + 1; const snippet = normalizeSessionCorpusSnippet(lines[index] ?? ""); if (snippet.length < SESSION_INGESTION_MIN_SNIPPET_CHARS) continue; const lineNumber = entry.lineMap[index] ?? index + 1; const messageTimestampMs = entry.messageTimestampsMs[index] ?? 0; const day = formatMemoryDreamingDay(messageTimestampMs > 0 ? messageTimestampMs : fingerprint.mtimeMs, params.timezone); if (!isDayWithinLookback(day, cutoffMs)) continue; const messageHash = hashSessionMessageId(`${sessionScope}\n${messageTimestampMs > 0 ? `ts:${Math.floor(messageTimestampMs)}` : `line:${lineNumber}`}\n${snippet}`); if (seenSet.has(messageHash)) continue; const rendered = buildSessionRenderedLine({ agentId: file.agentId, sessionPath: file.sessionPath, lineNumber, snippet }); const bucket = batchByDay.get(day) ?? []; bucket.push({ day, snippet, rendered }); batchByDay.set(day, bucket); seenSet.add(messageHash); newSeenHashes.push(messageHash); fileCount += 1; remaining -= 1; } if (lastScannedContentLine < cursor) lastScannedContentLine = cursor; cursor = Math.max(0, Math.min(lastScannedContentLine, lineCount)); nextFiles[stateKey] = { mtimeMs: fingerprint.mtimeMs, size: fingerprint.size, contentHash, lineCount, lastContentLine: cursor }; const mergedSeen = mergeTrackedMessageHashes(previousSeen, newSeenHashes); nextSeenMessages[sessionScope] = mergedSeen; if (!areStringArraysEqual(mergedSeen, previousSeen)) changed = true; if (!previous || previous.mtimeMs !== fingerprint.mtimeMs || previous.size !== fingerprint.size || previous.contentHash !== contentHash || previous.lineCount !== lineCount || previous.lastContentLine !== cursor) changed = true; } for (const [key, state] of Object.entries(params.state.files)) { if (!Object.hasOwn(nextFiles, key)) { changed = true; continue; } const next = nextFiles[key]; if (!next || next.mtimeMs !== state.mtimeMs || next.size !== state.size) changed = true; if (next && typeof state.contentHash === "string" && state.contentHash.trim().length > 0 && next.contentHash !== state.contentHash) changed = true; if (!next || next.lineCount !== state.lineCount || next.lastContentLine !== state.lastContentLine) changed = true; } const trimmedSeenMessages = trimTrackedSessionScopes(nextSeenMessages); for (const [scope, hashes] of Object.entries(trimmedSeenMessages)) if (!areStringArraysEqual(params.state.seenMessages[scope] ?? [], hashes)) changed = true; for (const scope of Object.keys(params.state.seenMessages)) if (!Object.hasOwn(trimmedSeenMessages, scope)) changed = true; const batches = []; for (const day of [...batchByDay.keys()].toSorted()) { const lines = batchByDay.get(day) ?? []; if (lines.length === 0) continue; const results = await appendSessionCorpusLines({ workspaceDir: params.workspaceDir, day, lines }); if (results.length > 0) batches.push({ day, results }); } return { batches, nextState: { version: 3, files: nextFiles, seenMessages: trimmedSeenMessages }, changed }; } async function ingestSessionTranscriptSignals(params) { const state = await readSessionIngestionState(params.workspaceDir); const collected = await collectSessionIngestionBatches({ workspaceDir: params.workspaceDir, cfg: params.cfg, primaryWorkspaceDir: params.primaryWorkspaceDir, lookbackDays: params.lookbackDays, nowMs: params.nowMs, timezone: params.timezone, state }); const ingestionDayBucket = formatMemoryDreamingDay(params.nowMs, params.timezone); for (const batch of collected.batches) await recordShortTermRecalls({ workspaceDir: params.workspaceDir, query: `__dreaming_sessions__:${batch.day}`, results: batch.results, signalType: "daily", dedupeByQueryPerDay: true, dayBucket: ingestionDayBucket, nowMs: params.nowMs, timezone: params.timezone }); if (collected.changed) await writeSessionIngestionState(params.workspaceDir, collected.nextState); } async function collectDailyIngestionBatches(params) { const memoryDir = path.join(params.workspaceDir, "memory"); const cutoffMs = calculateLookbackCutoffMs(params.nowMs, params.lookbackDays); const files = (await fs.readdir(memoryDir, { withFileTypes: true }).catch((err) => { if (err?.code === "ENOENT") return []; throw err; })).filter((entry) => entry.isFile()).map((entry) => { const file = parseDailyMemoryFileName(entry.name); if (!file) return null; if (!isDayWithinLookback(file.day, cutoffMs)) return null; return file; }).filter((entry) => entry !== null).toSorted(compareDailyMemoryFilesByNewestDay); const batches = []; const nextFiles = {}; let changed = false; const totalCap = Math.max(20, params.limit * 4); const perFileCap = Math.max(6, Math.ceil(totalCap / Math.max(1, Math.max(files.length, 1)))); let total = 0; for (const file of files) { const relativePath = `memory/${file.fileName}`; const filePath = path.join(memoryDir, file.fileName); const stat = await fs.stat(filePath).catch((err) => { if (err?.code === "ENOENT") return null; throw err; }); if (!stat) continue; const fingerprint = { mtimeMs: Math.floor(Math.max(0, stat.mtimeMs)), size: Math.floor(Math.max(0, stat.size)) }; nextFiles[relativePath] = fingerprint; const previous = params.state.files[relativePath]; const unchanged = previous !== void 0 && previous.mtimeMs === fingerprint.mtimeMs && previous.size === fingerprint.size; const previousDreamingDay = normalizeMemoryDay(previous?.lastDreamingDayIngested); if (unchanged && previousDreamingDay === params.ingestionDreamingDay) { nextFiles[relativePath] = { ...fingerprint, lastDreamingDayIngested: previousDreamingDay }; continue; } changed = true; const raw = await fs.readFile(filePath, "utf-8").catch((err) => { if (err?.code === "ENOENT") return ""; throw err; }); if (!raw) continue; const chunks = buildDailySnippetChunks(stripManagedDailyDreamingLines(raw.split(/\r?\n/)), perFileCap); const results = []; for (const chunk of chunks) { results.push({ path: relativePath, startLine: chunk.startLine, endLine: chunk.endLine, score: DAILY_INGESTION_SCORE, snippet: chunk.snippet, source: "memory" }); if (results.length >= perFileCap || total + results.length >= totalCap) break; } if (results.length === 0) continue; batches.push({ day: file.day, results }); total += results.length; nextFiles[relativePath] = { ...fingerprint, lastDreamingDayIngested: params.ingestionDreamingDay }; if (total >= totalCap) break; } if (!changed) { const previousKeys = Object.keys(params.state.files); const nextKeys = Object.keys(nextFiles); if (previousKeys.length !== nextKeys.length || previousKeys.some((key) => !Object.hasOwn(nextFiles, key))) changed = true; } return { batches, nextState: { version: 1, files: nextFiles }, changed }; } async function ingestDailyMemorySignals(params) { const state = await readDailyIngestionState(params.workspaceDir); const ingestionDayBucket = formatMemoryDreamingDay(params.nowMs, params.timezone); const collected = await collectDailyIngestionBatches({ workspaceDir: params.workspaceDir, lookbackDays: params.lookbackDays, limit: params.limit, nowMs: params.nowMs, ingestionDreamingDay: ingestionDayBucket, state }); for (const batch of collected.batches) await recordShortTermRecalls({ workspaceDir: params.workspaceDir, query: `__dreaming_daily__:${batch.day}`, results: batch.results, signalType: "daily", dedupeByQueryPerDay: true, dayBucket: ingestionDayBucket, nowMs: params.nowMs, timezone: params.timezone }); if (collected.changed) await writeDailyIngestionState(params.workspaceDir, collected.nextState); } async function seedHistoricalDailyMemorySignals(params) { const normalizedPaths = uniqueStrings(normalizeStringEntries(params.filePaths)); if (normalizedPaths.length === 0) return { importedFileCount: 0, importedSignalCount: 0, skippedPaths: [] }; const resolved = normalizedPaths.map((filePath) => { const fileName = path.basename(filePath); const file = parseDailyMemoryFileName(fileName); if (!file) return { filePath, fileName, relativePath: "", file: null }; return { filePath, fileName, relativePath: resolveWorkspaceMemoryRelativePath(params.workspaceDir, filePath), file }; }).toSorted((a, b) => { if (a.file && b.file) return compareDailyMemoryFilesByNewestDay(a.file, b.file); if (a.file) return -1; if (b.file) return 1; return a.filePath.localeCompare(b.filePath); }); const valid = resolved.filter((entry) => Boolean(entry.file)); const skippedPaths = resolved.filter((entry) => !entry.file).map((entry) => entry.filePath); const totalCap = Math.max(20, params.limit * 4); const perFileCap = Math.max(6, Math.ceil(totalCap / Math.max(1, valid.length))); let importedSignalCount = 0; let importedFileCount = 0; for (const entry of valid) { if (importedSignalCount >= totalCap) break; const raw = await fs.readFile(entry.filePath, "utf-8").catch((err) => { if (err?.code === "ENOENT") { skippedPaths.push(entry.filePath); return ""; } throw err; }); if (!raw) continue; const chunks = buildDailySnippetChunks(stripManagedDailyDreamingLines(raw.split(/\r?\n/)), perFileCap); const results = []; for (const chunk of chunks) { results.push({ path: entry.relativePath, startLine: chunk.startLine, endLine: chunk.endLine, score: DAILY_INGESTION_SCORE, snippet: chunk.snippet, source: "memory" }); if (results.length >= perFileCap || importedSignalCount + results.length >= totalCap) break; } if (results.length === 0) continue; await recordShortTermRecalls({ workspaceDir: params.workspaceDir, query: `__dreaming_daily__:${entry.file.day}`, results, signalType: "daily", dedupeByQueryPerDay: true, dayBucket: formatMemoryDreamingDay(params.nowMs, params.timezone), nowMs: params.nowMs, timezone: params.timezone }); importedSignalCount += results.length; importedFileCount += 1; } return { importedFileCount, importedSignalCount, skippedPaths }; } function entryAverageScore(entry) { const signalCount = Math.max(0, Math.floor(entry.recallCount ?? 0) + Math.floor(entry.dailyCount ?? 0) + Math.floor(entry.groundedCount ?? 0)); return signalCount > 0 ? Math.max(0, Math.min(1, entry.totalScore / signalCount)) : 0; } function dedupeEntries(entries, threshold) { const deduped = []; for (const entry of entries) { const duplicate = deduped.find((candidate) => candidate.path === entry.path && textSimilarity(candidate.snippet, entry.snippet) >= threshold); if (duplicate) { if (entry.recallCount > duplicate.recallCount) duplicate.recallCount = entry.recallCount; duplicate.totalScore = Math.max(duplicate.totalScore, entry.totalScore); duplicate.maxScore = Math.max(duplicate.maxScore, entry.maxScore); duplicate.queryHashes = uniqueStrings([...duplicate.queryHashes, ...entry.queryHashes]); duplicate.recallDays = [...new Set([...duplicate.recallDays, ...entry.recallDays])].toSorted(); duplicate.conceptTags = uniqueStrings([...duplicate.conceptTags, ...entry.conceptTags]); duplicate.lastRecalledAt = Date.parse(entry.lastRecalledAt) > Date.parse(duplicate.lastRecalledAt) ? entry.lastRecalledAt : duplicate.lastRecalledAt; continue; } deduped.push({ ...entry }); } return deduped; } function buildLightDreamingBody(entries) { if (entries.length === 0) return ["- No notable updates."]; const lines = []; for (const entry of entries) { const snippet = entry.snippet || "(no snippet captured)"; lines.push(`- Candidate: ${snippet}`); lines.push(` - confidence: ${entryAverageScore(entry).toFixed(2)}`); lines.push(` - evidence: ${entry.path}:${entry.startLine}-${entry.endLine}`); lines.push(` - recalls: ${entry.recallCount}`); lines.push(` - status: staged`); } return lines; } function calculateCandidateTruthConfidence(entry) { const recallStrength = Math.min(1, Math.log1p(entry.recallCount) / Math.log1p(6)); const averageScore = entryAverageScore(entry); const consolidation = Math.min(1, (entry.recallDays?.length ?? 0) / 3); const conceptual = Math.min(1, (entry.conceptTags?.length ?? 0) / 6); return Math.max(0, Math.min(1, averageScore * .45 + recallStrength * .25 + consolidation * .2 + conceptual * .1)); } function selectRemCandidateTruths(entries, limit) { if (limit <= 0) return []; return dedupeEntries(entries.filter((entry) => !entry.promotedAt), .88).map((entry) => ({ key: entry.key, snippet: entry.snippet || "(no snippet captured)", confidence: calculateCandidateTruthConfidence(entry), evidence: `${entry.path}:${entry.startLine}-${entry.endLine}` })).filter((entry) => entry.confidence >= .45).toSorted((a, b) => b.confidence - a.confidence || a.snippet.localeCompare(b.snippet)).slice(0, limit); } function buildRemReflections(entries, limit, minPatternStrength) { const tagStats = /* @__PURE__ */ new Map(); for (const entry of entries) for (const tag of entry.conceptTags) { if (!tag || REM_REFLECTION_TAG_BLACKLIST.has(tag.toLowerCase())) continue; const stat = tagStats.get(tag) ?? { count: 0, evidence: /* @__PURE__ */ new Set() }; stat.count += 1; stat.evidence.add(`${entry.path}:${entry.startLine}-${entry.endLine}`); tagStats.set(tag, stat); } const ranked = [...tagStats.entries()].map(([tag, stat]) => { return { tag, strength: Math.min(1, stat.count / Math.max(1, entries.length) * 2), stat }; }).filter((entry) => entry.strength >= minPatternStrength).toSorted((a, b) => b.strength - a.strength || b.stat.count - a.stat.count || a.tag.localeCompare(b.tag)).slice(0, limit); if (ranked.length === 0) return ["- No strong patterns surfaced."]; const lines = []; for (const entry of ranked) { lines.push(`- Theme: \`${entry.tag}\` kept surfacing across ${entry.stat.count} memories.`); lines.push(` - confidence: ${entry.strength.toFixed(2)}`); lines.push(` - evidence: ${[...entry.stat.evidence].slice(0, 3).join(", ")}`); lines.push(` - note: reflection`); } return lines; } function previewRemDreaming(params) { const reflections = buildRemReflections(params.entries, params.limit, params.minPatternStrength); const candidateSelections = selectRemCandidateTruths(params.entries, Math.max(1, Math.min(3, params.limit))); const candidateTruths = candidateSelections.map((entry) => ({ snippet: entry.snippet, confidence: entry.confidence, evidence: entry.evidence })); const candidateKeys = uniqueStrings(candidateSelections.map((entry) => entry.key)); const bodyLines = [ "### Reflections", ...reflections, "", "### Possible Lasting Truths", ...candidateTruths.length > 0 ? candidateTruths.map((entry) => `- ${entry.snippet} [confidence=${entry.confidence.toFixed(2)} evidence=${entry.evidence}]`) : ["- No strong candidate truths surfaced."] ]; return { sourceEntryCount: params.entries.length, reflections, candidateTruths, candidateKeys, bodyLines }; } async function runLightDreaming(params) { const nowMs = Number.isFinite(params.nowMs) ? params.nowMs : Date.now(); await ingestDailyMemorySignals({ workspaceDir: params.workspaceDir, lookbackDays: params.config.lookbackDays, limit: params.config.limit, nowMs, timezone: params.config.timezone }); await ingestSessionTranscriptSignals({ workspaceDir: params.workspaceDir, cfg: params.cfg, primaryWorkspaceDir: params.primaryWorkspaceDir, lookbackDays: params.config.lookbackDays, nowMs, timezone: params.config.timezone }); const entries = dedupeEntries((await filterLiveShortTermRecallEntries({ workspaceDir: params.workspaceDir, entries: filterRecallEntriesWithinLookback({ entries: await readShortTermRecallEntries({ workspaceDir: params.workspaceDir, nowMs }), nowMs, lookbackDays: params.config.lookbackDays }) })).toSorted((a, b) => { const byTime = Date.parse(b.lastRecalledAt) - Date.parse(a.lastRecalledAt); if (byTime !== 0) return byTime; return b.recallCount - a.recallCount; }).slice(0, params.config.limit), params.config.dedupeSimilarity); const capped = entries.slice(0, params.config.limit); const bodyLines = buildLightDreamingBody(capped); await writeDailyDreamingPhaseBlock({ workspaceDir: params.workspaceDir, phase: "light", bodyLines, nowMs, timezone: params.config.timezone, storage: params.config.storage }); await recordDreamingPhaseSignals({ workspaceDir: params.workspaceDir, phase: "light", keys: capped.map((entry) => entry.key), nowMs }); if (params.config.enabled && entries.length > 0 && params.config.storage.mode !== "separate") params.logger.info(`memory-core: light dreaming staged ${Math.min(entries.length, params.config.limit)} candidate(s) [workspace=${params.workspaceDir}].`); if (params.subagent && capped.length > 0) { const themes = uniqueStrings(capped.flatMap((e) => e.conceptTags).filter(Boolean)); const data = { phase: "light", snippets: capped.map((e) => e.snippet).filter(Boolean), ...themes.length > 0 ? { themes } : {} }; if (params.detachNarratives) runDetachedDreamNarrative({ subagent: params.subagent, workspaceDir: params.workspaceDir, data, nowMs, timezone: params.config.timezone, model: params.config.execution?.model, logger: params.logger }); else await generateAndAppendDreamNarrative({ subagent: params.subagent, workspaceDir: params.workspaceDir, data, nowMs, timezone: params.config.timezone, model: params.config.execution?.model, logger: params.logger }); } } async function runRemDreaming(params) { const nowMs = Number.isFinite(params.nowMs) ? params.nowMs : Date.now(); await ingestDailyMemorySignals({ workspaceDir: params.workspaceDir, lookbackDays: params.config.lookbackDays, limit: params.config.limit, nowMs, timezone: params.config.timezone }); await ingestSessionTranscriptSignals({ workspaceDir: params.workspaceDir, cfg: params.cfg, primaryWorkspaceDir: params.primaryWorkspaceDir, lookbackDays: params.config.lookbackDays, nowMs, timezone: params.config.timezone }); const allEntries = await filterLiveShortTermRecallEntries({ workspaceDir: params.workspaceDir, entries: filterRecallEntriesWithinLookback({ entries: await readShortTermRecallEntries({ workspaceDir: params.workspaceDir, nowMs }), nowMs, lookbackDays: params.config.lookbackDays }) }); const lightKeys = await readLightStagedKeys({ workspaceDir: params.workspaceDir, nowMs }); const stagedEntries = lightKeys.size > 0 ? allEntries.filter((entry) => lightKeys.has(entry.key)) : []; const entries = stagedEntries.length > 0 ? stagedEntries : allEntries; const preview = previewRemDreaming({ entries, limit: params.config.limit, minPatternStrength: params.config.minPatternStrength }); await writeDailyDreamingPhaseBlock({ workspaceDir: params.workspaceDir, phase: "rem", bodyLines: preview.bodyLines, nowMs, timezone: params.config.timezone, storage: params.config.storage }); if (stagedEntries.length > 0) await recordRemConsideredPhaseSignals({ workspaceDir: params.workspaceDir, keys: stagedEntries.map((entry) => entry.key), nowMs }); await recordDreamingPhaseSignals({ workspaceDir: params.workspaceDir, phase: "rem", keys: preview.candidateKeys, nowMs }); if (params.config.enabled && entries.length > 0 && params.config.storage.mode !== "separate") params.logger.info(`memory-core: REM dreaming wrote reflections from ${entries.length} recent memory trace(s) [workspace=${params.workspaceDir}].`); if (params.subagent && entries.length > 0) { const snippets = preview.candidateTruths.map((t) => t.snippet).filter(Boolean); const themes = preview.reflections.filter((r) => !r.startsWith("- No strong") && !r.startsWith(" -")); const data = { phase: "rem", snippets: snippets.length > 0 ? snippets : entries.slice(0, 8).map((e) => e.snippet).filter(Boolean), ...themes.length > 0 ? { themes } : {} }; if (params.detachNarratives) runDetachedDreamNarrative({ subagent: params.subagent, workspaceDir: params.workspaceDir, data, nowMs, timezone: params.config.timezone, model: params.config.execution?.model, logger: params.logger }); else await generateAndAppendDreamNarrative({ subagent: params.subagent, workspaceDir: params.workspaceDir, data, nowMs, timezone: params.config.timezone, model: params.config.execution?.model, logger: params.logger }); } } async function runDreamingSweepPhases(params) { const sweepNowMs = Number.isFinite(params.nowMs) ? params.nowMs : Date.now(); const light = resolveMemoryLightDreamingConfig({ pluginConfig: params.pluginConfig, cfg: params.cfg }); if (light.enabled && light.limit > 0) await runLightDreaming({ workspaceDir: params.workspaceDir, cfg: params.cfg, config: light, logger: params.logger, subagent: params.subagent, nowMs: sweepNowMs, detachNarratives: params.detachNarratives }); const rem = resolveMemoryRemDreamingConfig({ pluginConfig: params.pluginConfig, cfg: params.cfg }); if (rem.enabled && rem.limit > 0) await runRemDreaming({ workspaceDir: params.workspaceDir, cfg: params.cfg, config: rem, logger: params.logger, subagent: params.subagent, nowMs: sweepNowMs, detachNarratives: params.detachNarratives }); } async function runPhaseIfTriggered(params) { const hasEventToken = params.cleanedBody.trim().split(/\s+/).includes(params.eventText); if (params.trigger !== "heartbeat" || !hasEventToken) return; if (!params.config.enabled) return { handled: true, reason: `memory-core: ${params.phase} dreaming disabled` }; const primaryWorkspaceDir = normalizeTrimmedString(params.workspaceDir); const workspaces = resolveWorkspaces({ cfg: params.cfg, fallbackWorkspaceDir: primaryWorkspaceDir }); if (workspaces.length === 0) { params.logger.warn(`memory-core: ${params.phase} dreaming skipped because no memory workspace is available.`); return { handled: true, reason: `memory-core: ${params.phase} dreaming missing workspace` }; } if (params.config.limit === 0) { params.logger.info(`memory-core: ${params.phase} dreaming skipped because limit=0.`); return { handled: true, reason: `memory-core: ${params.phase} dreaming disabled by limit` }; } for (const workspaceDir of workspaces) try { if (params.phase === "light") await runLightDreaming({ workspaceDir, cfg: params.cfg, primaryWorkspaceDir, config: params.config, logger: params.logger, subagent: params.subagent }); else await runRemDreaming({ workspaceDir, cfg: params.cfg, primaryWorkspaceDir, config: params.config, logger: params.logger, subagent: params.subagent }); } catch (err) { params.logger.error(`memory-core: ${params.phase} dreaming failed for workspace ${workspaceDir}: ${formatErrorMessage(err)}`); } return { handled: true, reason: `memory-core: ${params.phase} dreaming processed` }; } const testing = { runPhaseIfTriggered, previewRemDreaming, readDailyIngestionState, readSessionIngestionState, dedupeEntries, constants: { LIGHT_SLEEP_EVENT_TEXT, REM_SLEEP_EVENT_TEXT } }; //#endregion export { normalizeSessionIngestionState as a, seedHistoricalDailyMemorySignals as c, normalizeDailyIngestionState as i, testing as l, SESSION_INGESTION_STATE_RELATIVE_PATH as n, previewRemDreaming as o, filterRecallEntriesWithinLookback as r, runDreamingSweepPhases as s, DAILY_INGESTION_STATE_RELATIVE_PATH as t };