openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
208 lines (207 loc) • 8.21 kB
JavaScript
import { r as extractErrorCode } from "./errors-BXgSefBE.js";
import "./error-runtime-C8vbtAJt.js";
import { l as clearMemoryCoreWorkspaceNamespace, 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 path from "node:path";
import fs from "node:fs/promises";
import { randomUUID } from "node:crypto";
//#region extensions/memory-core/src/dreaming-repair.ts
const DREAMS_FILENAMES = ["DREAMS.md", "dreams.md"];
const SESSION_CORPUS_RELATIVE_DIR = path.join("memory", ".dreams", "session-corpus");
const SESSION_INGESTION_RELATIVE_PATH = path.join("memory", ".dreams", "session-ingestion.json");
const REPAIR_ARCHIVE_RELATIVE_DIR = path.join(".openclaw-repair", "dreaming");
const DREAMING_NARRATIVE_RUN_PREFIX = "dreaming-narrative-";
const DREAMING_NARRATIVE_PROMPT_PREFIX = "Write a dream diary entry from these memory fragments";
function requireAbsoluteWorkspaceDir(rawWorkspaceDir) {
const trimmed = rawWorkspaceDir.trim();
if (!trimmed) throw new Error("workspaceDir is required");
if (!path.isAbsolute(trimmed)) throw new Error("workspaceDir must be an absolute path");
return path.resolve(trimmed);
}
async function resolveExistingDreamsPath(workspaceDir) {
for (const fileName of DREAMS_FILENAMES) {
const candidate = path.join(workspaceDir, fileName);
try {
await fs.access(candidate);
return candidate;
} catch (err) {
if (err.code !== "ENOENT") throw err;
}
}
}
async function listSessionCorpusFiles(sessionCorpusDir) {
return (await fs.readdir(sessionCorpusDir, { withFileTypes: true })).filter((entry) => entry.isFile() && entry.name.endsWith(".txt")).map((entry) => path.join(sessionCorpusDir, entry.name)).toSorted();
}
function isSuspiciousSessionCorpusLine(line) {
return line.includes(DREAMING_NARRATIVE_PROMPT_PREFIX) && (line.includes(DREAMING_NARRATIVE_RUN_PREFIX) || line.includes("dreaming-narrative-"));
}
function buildArchiveTimestamp(now) {
return now.toISOString().replace(/[:.]/g, "-");
}
async function ensureArchivablePath(targetPath) {
const stat = await fs.lstat(targetPath).catch((err) => {
if (extractErrorCode(err) === "ENOENT") return null;
throw err;
});
if (!stat) return null;
if (stat.isSymbolicLink()) throw new Error(`Refusing to archive symlinked path: ${targetPath}`);
if (stat.isDirectory()) return "dir";
if (stat.isFile()) return "file";
throw new Error(`Refusing to archive non-file artifact: ${targetPath}`);
}
async function moveToArchive(params) {
if (!await ensureArchivablePath(params.targetPath)) return null;
await fs.mkdir(params.archiveDir, { recursive: true });
const baseName = path.basename(params.targetPath);
const destination = path.join(params.archiveDir, `${baseName}.${randomUUID()}`);
await fs.rename(params.targetPath, destination);
return destination;
}
async function clearSessionIngestionState(workspaceDir) {
await Promise.all([clearMemoryCoreWorkspaceNamespace({
namespace: DREAMING_SESSION_INGESTION_FILES_NAMESPACE,
workspaceDir
}), clearMemoryCoreWorkspaceNamespace({
namespace: DREAMING_SESSION_INGESTION_SEEN_NAMESPACE,
workspaceDir
})]);
}
async function auditDreamingArtifacts(params) {
const workspaceDir = requireAbsoluteWorkspaceDir(params.workspaceDir);
const dreamsPath = await resolveExistingDreamsPath(workspaceDir);
const sessionCorpusDir = path.join(workspaceDir, SESSION_CORPUS_RELATIVE_DIR);
const sessionIngestionPath = path.join(workspaceDir, SESSION_INGESTION_RELATIVE_PATH);
const issues = [];
let sessionCorpusFileCount = 0;
let suspiciousSessionCorpusFileCount = 0;
let suspiciousSessionCorpusLineCount = 0;
let sessionIngestionExists = false;
if (dreamsPath) try {
await fs.access(dreamsPath);
} catch (err) {
issues.push({
severity: "error",
code: "dreaming-diary-unreadable",
message: `Dream diary could not be inspected: ${err.code ?? "error"}.`,
fixable: false
});
}
try {
const corpusFiles = await listSessionCorpusFiles(sessionCorpusDir);
sessionCorpusFileCount = corpusFiles.length;
for (const corpusFile of corpusFiles) {
const suspiciousLines = (await fs.readFile(corpusFile, "utf-8")).split(/\r?\n/).map((line) => line.trim()).filter((line) => line.length > 0 && isSuspiciousSessionCorpusLine(line));
if (suspiciousLines.length > 0) {
suspiciousSessionCorpusFileCount += 1;
suspiciousSessionCorpusLineCount += suspiciousLines.length;
}
}
} catch (err) {
if (err.code !== "ENOENT") issues.push({
severity: "error",
code: "dreaming-session-corpus-unreadable",
message: `Dreaming session corpus could not be inspected: ${err.code ?? "error"}.`,
fixable: false
});
}
try {
await fs.access(sessionIngestionPath);
sessionIngestionExists = true;
} catch (err) {
if (err.code !== "ENOENT") issues.push({
severity: "error",
code: "dreaming-session-ingestion-unreadable",
message: `Dreaming session-ingestion state could not be inspected: ${err.code ?? "error"}.`,
fixable: false
});
}
if (!sessionIngestionExists) try {
const ingestionNamespaces = [
DREAMING_SESSION_INGESTION_FILES_NAMESPACE,
DREAMING_SESSION_INGESTION_SEEN_NAMESPACE,
DREAMING_DAILY_INGESTION_NAMESPACE
];
for (const namespace of ingestionNamespaces) if ((await readMemoryCoreWorkspaceEntries({
namespace,
workspaceDir
})).length > 0) {
sessionIngestionExists = true;
break;
}
} catch {}
if (suspiciousSessionCorpusLineCount > 0) issues.push({
severity: "warn",
code: "dreaming-session-corpus-self-ingested",
message: `Dreaming session corpus appears to contain self-ingested narrative content (${suspiciousSessionCorpusLineCount} suspicious line${suspiciousSessionCorpusLineCount === 1 ? "" : "s"}).`,
fixable: true
});
return {
...dreamsPath ? { dreamsPath } : {},
sessionCorpusDir,
sessionCorpusFileCount,
suspiciousSessionCorpusFileCount,
suspiciousSessionCorpusLineCount,
sessionIngestionPath,
sessionIngestionExists,
issues
};
}
async function repairDreamingArtifacts(params) {
const workspaceDir = requireAbsoluteWorkspaceDir(params.workspaceDir);
const warnings = [];
const archivedPaths = [];
let archiveDir;
let archivedDreamsDiary = false;
let archivedSessionCorpus = false;
let archivedSessionIngestion = false;
const ensureArchiveDir = () => {
archiveDir ??= path.join(workspaceDir, REPAIR_ARCHIVE_RELATIVE_DIR, buildArchiveTimestamp(params.now ?? /* @__PURE__ */ new Date()));
return archiveDir;
};
const archivePathIfPresent = async (targetPath) => {
try {
return await moveToArchive({
targetPath,
archiveDir: ensureArchiveDir()
});
} catch (err) {
warnings.push(err instanceof Error ? err.message : String(err));
return null;
}
};
const sessionCorpusDestination = await archivePathIfPresent(path.join(workspaceDir, SESSION_CORPUS_RELATIVE_DIR));
if (sessionCorpusDestination) {
archivedSessionCorpus = true;
archivedPaths.push(sessionCorpusDestination);
}
const sessionIngestionDestination = await archivePathIfPresent(path.join(workspaceDir, SESSION_INGESTION_RELATIVE_PATH));
if (sessionIngestionDestination) {
archivedSessionIngestion = true;
archivedPaths.push(sessionIngestionDestination);
}
if (sessionCorpusDestination || sessionIngestionDestination) try {
await clearSessionIngestionState(workspaceDir);
} catch (err) {
warnings.push(`Failed clearing dreaming session-ingestion SQLite state: ${err instanceof Error ? err.message : String(err)}`);
}
if (params.archiveDiary) {
const dreamsPath = await resolveExistingDreamsPath(workspaceDir);
if (dreamsPath) {
const dreamsDestination = await archivePathIfPresent(dreamsPath);
if (dreamsDestination) {
archivedDreamsDiary = true;
archivedPaths.push(dreamsDestination);
}
}
}
return {
changed: archivedDreamsDiary || archivedSessionCorpus || archivedSessionIngestion,
...archiveDir ? { archiveDir } : {},
archivedDreamsDiary,
archivedSessionCorpus,
archivedSessionIngestion,
archivedPaths,
warnings
};
}
//#endregion
export { repairDreamingArtifacts as n, auditDreamingArtifacts as t };