openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
198 lines (197 loc) • 7.91 kB
JavaScript
import { Y as resolveMemoryDreamingWorkspaces } from "../../dreaming-SqhFI2Et.js";
import "../../memory-core-host-status-DVjlVr90.js";
import { c as SHORT_TERM_RECALL_NAMESPACE, g as writeMemoryCoreWorkspaceEntry, h as writeMemoryCoreWorkspaceEntries, m as readMemoryCoreWorkspaceEntries, n as DREAMING_SESSION_INGESTION_FILES_NAMESPACE, o as SHORT_TERM_META_NAMESPACE, r as DREAMING_SESSION_INGESTION_SEEN_NAMESPACE, s as SHORT_TERM_PHASE_SIGNAL_NAMESPACE, t as DREAMING_DAILY_INGESTION_NAMESPACE, u as configureMemoryCoreDreamingState } from "../../dreaming-state-D7W5VtYL.js";
import { a as SHORT_TERM_PHASE_SIGNAL_RELATIVE_PATH, f as normalizeShortTermPhaseSignalStore, o as SHORT_TERM_STORE_RELATIVE_PATH, p as normalizeShortTermRecallStore } from "../../short-term-promotion-DuBGW686.js";
import { a as normalizeSessionIngestionState, i as normalizeDailyIngestionState, n as SESSION_INGESTION_STATE_RELATIVE_PATH, t as DAILY_INGESTION_STATE_RELATIVE_PATH } from "../../dreaming-phases-c7FuN3LG.js";
import path from "node:path";
import fs from "node:fs/promises";
//#region extensions/memory-core/doctor-contract-api.ts
function resolveConfiguredWorkspaces(config, env) {
return resolveMemoryDreamingWorkspaces(config, { env }).map((entry) => entry.workspaceDir);
}
async function fileExists(filePath) {
try {
return (await fs.stat(filePath)).isFile();
} catch {
return false;
}
}
async function readJsonFile(filePath) {
return JSON.parse(await fs.readFile(filePath, "utf8"));
}
async function archiveLegacySource(params) {
const archivedPath = `${params.filePath}.migrated`;
if (await fileExists(archivedPath)) {
params.warnings.push(`Left migrated Memory Core ${params.label} source in place because ${archivedPath} already exists`);
return;
}
try {
await fs.rename(params.filePath, archivedPath);
params.changes.push(`Archived Memory Core ${params.label} legacy source -> ${archivedPath}`);
} catch (err) {
params.warnings.push(`Failed archiving Memory Core ${params.label} legacy source: ${String(err)}`);
}
}
async function collectLegacySources(config, env) {
const sources = [];
for (const workspaceDir of resolveConfiguredWorkspaces(config, env)) {
const candidates = [
{
label: "daily ingestion",
relativePath: DAILY_INGESTION_STATE_RELATIVE_PATH
},
{
label: "session ingestion",
relativePath: SESSION_INGESTION_STATE_RELATIVE_PATH
},
{
label: "short-term recall",
relativePath: SHORT_TERM_STORE_RELATIVE_PATH
},
{
label: "phase signals",
relativePath: SHORT_TERM_PHASE_SIGNAL_RELATIVE_PATH
}
];
for (const candidate of candidates) {
const filePath = path.join(workspaceDir, candidate.relativePath);
if (await fileExists(filePath)) sources.push({
workspaceDir,
label: candidate.label,
filePath
});
}
}
return sources;
}
async function workspaceHasRows(namespace, workspaceDir) {
return (await readMemoryCoreWorkspaceEntries({
namespace,
workspaceDir
})).length > 0;
}
async function migrateDailyIngestion(source) {
const state = normalizeDailyIngestionState(await readJsonFile(source.filePath));
await writeMemoryCoreWorkspaceEntries({
namespace: DREAMING_DAILY_INGESTION_NAMESPACE,
workspaceDir: source.workspaceDir,
entries: Object.entries(state.files).map(([key, value]) => ({
key,
value
}))
});
return Object.keys(state.files).length;
}
async function migrateSessionIngestion(source) {
const state = normalizeSessionIngestionState(await readJsonFile(source.filePath));
const seenEntries = Object.entries(state.seenMessages).flatMap(([scope, hashes]) => Array.from({ length: Math.ceil(hashes.length / 512) }, (_, index) => ({
key: `${scope}:${index}`,
value: {
scope,
index,
hashes: hashes.slice(index * 512, (index + 1) * 512)
}
})));
await Promise.all([writeMemoryCoreWorkspaceEntries({
namespace: DREAMING_SESSION_INGESTION_FILES_NAMESPACE,
workspaceDir: source.workspaceDir,
entries: Object.entries(state.files).map(([key, value]) => ({
key,
value
}))
}), writeMemoryCoreWorkspaceEntries({
namespace: DREAMING_SESSION_INGESTION_SEEN_NAMESPACE,
workspaceDir: source.workspaceDir,
entries: seenEntries
})]);
return Object.keys(state.files).length + Object.keys(state.seenMessages).length;
}
async function migrateShortTermRecall(source) {
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
const state = normalizeShortTermRecallStore(await readJsonFile(source.filePath), nowIso);
await Promise.all([writeMemoryCoreWorkspaceEntries({
namespace: SHORT_TERM_RECALL_NAMESPACE,
workspaceDir: source.workspaceDir,
entries: Object.entries(state.entries).map(([key, value]) => ({
key,
value
}))
}), writeMemoryCoreWorkspaceEntry({
namespace: SHORT_TERM_META_NAMESPACE,
workspaceDir: source.workspaceDir,
key: "recall",
value: { updatedAt: state.updatedAt }
})]);
return Object.keys(state.entries).length;
}
async function migratePhaseSignals(source) {
const nowIso = (/* @__PURE__ */ new Date()).toISOString();
const state = normalizeShortTermPhaseSignalStore(await readJsonFile(source.filePath), nowIso);
await Promise.all([writeMemoryCoreWorkspaceEntries({
namespace: SHORT_TERM_PHASE_SIGNAL_NAMESPACE,
workspaceDir: source.workspaceDir,
entries: Object.entries(state.entries).map(([key, value]) => ({
key,
value
}))
}), writeMemoryCoreWorkspaceEntry({
namespace: SHORT_TERM_META_NAMESPACE,
workspaceDir: source.workspaceDir,
key: "phase",
value: { updatedAt: state.updatedAt }
})]);
return Object.keys(state.entries).length;
}
function targetNamespacesForSource(label) {
if (label === "daily ingestion") return [DREAMING_DAILY_INGESTION_NAMESPACE];
if (label === "session ingestion") return [DREAMING_SESSION_INGESTION_FILES_NAMESPACE, DREAMING_SESSION_INGESTION_SEEN_NAMESPACE];
if (label === "short-term recall") return [SHORT_TERM_RECALL_NAMESPACE];
return [SHORT_TERM_PHASE_SIGNAL_NAMESPACE];
}
async function migrateSource(source) {
if (source.label === "daily ingestion") return await migrateDailyIngestion(source);
if (source.label === "session ingestion") return await migrateSessionIngestion(source);
if (source.label === "short-term recall") return await migrateShortTermRecall(source);
return await migratePhaseSignals(source);
}
const stateMigrations = [{
id: "memory-core-dreams-json-to-sqlite",
label: "Memory Core dreaming state",
async detectLegacyState(params) {
configureMemoryCoreDreamingState(params.context.openPluginStateKeyedStore);
const sources = await collectLegacySources(params.config, params.env);
if (sources.length === 0) return null;
return { preview: sources.map((source) => `- Memory Core ${source.label}: ${source.filePath} -> SQLite plugin state`) };
},
async migrateLegacyState(params) {
configureMemoryCoreDreamingState(params.context.openPluginStateKeyedStore);
const changes = [];
const warnings = [];
for (const source of await collectLegacySources(params.config, params.env)) {
if ((await Promise.all(targetNamespacesForSource(source.label).map((namespace) => workspaceHasRows(namespace, source.workspaceDir)))).some(Boolean)) {
warnings.push(`Skipped Memory Core ${source.label} import for ${source.workspaceDir} because SQLite rows already exist; left legacy source in place`);
continue;
}
let imported;
try {
imported = await migrateSource(source);
} catch (err) {
warnings.push(`Skipped Memory Core ${source.label} import for ${source.workspaceDir} because the legacy source could not be imported: ${String(err)}`);
continue;
}
changes.push(`Migrated Memory Core ${source.label} -> SQLite plugin state (${imported} row(s))`);
await archiveLegacySource({
filePath: source.filePath,
label: source.label,
changes,
warnings
});
}
return {
changes,
warnings
};
}
}];
//#endregion
export { stateMigrations };