openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
154 lines (153 loc) • 8.11 kB
JavaScript
import { i as getNodeSqliteKysely, n as executeSqliteQuerySync } from "./kysely-sync-BMSZn9yz.mjs";
import { i as tableExists } from "./openclaw-state-db-schema-helpers-DsjDX6BL.mjs";
import { a as runSqliteImmediateTransactionSync } from "./sqlite-transaction-DlnLAQiX.mjs";
import { c as openOpenClawAgentDatabase } from "./openclaw-agent-db-CFSa_Qik.mjs";
import { n as withOpenClawAgentDatabaseReadOnly } from "./openclaw-agent-db-readonly-C4g5X1l9.mjs";
import { n as readMemoryEntryOriginsInDatabase, r as recordMemoryEntryOriginsInDatabase, t as ensureMemoryEntryOriginsSchema } from "./memory-core-host-engine-storage-CTEDJ14W.mjs";
import "./sqlite-runtime-COSukRWw.mjs";
import { r as memorySessionTombstonesExist, t as ensureMemorySessionTombstones } from "./memory-session-tombstones-C1mWWpWO.mjs";
import { a as readDreamsFile, t as DREAMS_FILENAMES } from "./dreaming-dreams-file-AdUPMXsZ.mjs";
import { a as extractPromotionKeys } from "./short-term-promotion-memory-write-D6Gb2vpy.mjs";
import path from "node:path";
//#region extensions/memory-core/src/memory-entry-origins.ts
const TOMBSTONE_INSERT_BATCH_SIZE = 128;
const ensuredDatabases = /* @__PURE__ */ new WeakSet();
function openMemoryOriginDatabase(agentId) {
const db = openOpenClawAgentDatabase({ agentId }).db;
if (!ensuredDatabases.has(db)) {
ensureMemoryEntryOriginsSchema(db);
ensuredDatabases.add(db);
}
return db;
}
function listMemoryEntryOrigins(params) {
if (params.sessionIds?.length === 0 || params.entryKeys?.length === 0) return [];
const result = withOpenClawAgentDatabaseReadOnly(({ db }) => {
if (!ensuredDatabases.has(db) && !tableExists(db, "memory_entry_origins")) return [];
return readMemoryEntryOriginsInDatabase(db, params);
}, params);
return result.found ? result.value : [];
}
function listMemorySessionTombstones(params) {
if (params.sessionIds?.length === 0) return [];
const result = withOpenClawAgentDatabaseReadOnly(({ db }) => {
if (!memorySessionTombstonesExist(db)) return [];
let query = getNodeSqliteKysely(db).selectFrom("memory_session_tombstones").selectAll().where("agent_id", "=", params.agentId);
if (params.sessionIds) query = query.where("session_id", "in", params.sessionIds);
return executeSqliteQuerySync(db, query.orderBy("session_id", "asc")).rows.map((row) => ({
sessionId: row.session_id,
agentId: row.agent_id,
reason: row.reason,
createdAt: row.created_at
}));
}, params);
return result.found ? result.value : [];
}
/** Record on the supplied connection; the caller retains write admission. */
function recordMemorySessionTombstonesInDatabase(db, params) {
const sessionIds = [...new Set(params.sessionIds)];
if (sessionIds.length === 0) return 0;
ensureMemorySessionTombstones(db);
const reason = params.reason ?? "forgotten";
const createdAt = params.createdAt ?? Date.now();
return runSqliteImmediateTransactionSync(db, () => {
const kysely = getNodeSqliteKysely(db);
let recorded = 0;
for (let start = 0; start < sessionIds.length; start += TOMBSTONE_INSERT_BATCH_SIZE) {
const result = executeSqliteQuerySync(db, kysely.insertInto("memory_session_tombstones").values(sessionIds.slice(start, start + TOMBSTONE_INSERT_BATCH_SIZE).map((sessionId) => ({
session_id: sessionId,
agent_id: params.agentId,
reason,
created_at: createdAt
}))).onConflict((conflict) => conflict.column("session_id").doNothing()));
recorded += Number(result.numAffectedRows ?? 0n);
}
if (recorded > 0) executeSqliteQuerySync(db, kysely.updateTable("memory_index_state").set((expression) => ({ revision: expression("revision", "+", 1) })).where("id", "=", 1));
return recorded;
});
}
function recordMemoryEntryOrigins(params) {
if (params.origins.length === 0) return [];
const db = openMemoryOriginDatabase(params.agentId);
return runSqliteImmediateTransactionSync(db, () => recordMemoryEntryOriginsInDatabase(db, params));
}
function deleteMemoryEntryOrigins(params) {
if (params.entryKeys.length === 0 || params.sessionIds?.length === 0) return 0;
const existing = withOpenClawAgentDatabaseReadOnly(({ db }) => {
if (!ensuredDatabases.has(db) && !tableExists(db, "memory_entry_origins")) return false;
let query = getNodeSqliteKysely(db).selectFrom("memory_entry_origins").select("entry_key").where("agent_id", "=", params.agentId).where("entry_key", "in", params.entryKeys);
if (params.sessionIds) query = query.where("session_id", "in", params.sessionIds);
return executeSqliteQuerySync(db, query.limit(1)).rows.length > 0;
}, params);
if (!existing.found || !existing.value) return 0;
return deleteMemoryEntryOriginsInDatabase(openMemoryOriginDatabase(params.agentId), params);
}
/** Mutate only the supplied connection; callers retain its admission and lifetime. */
function deleteMemoryEntryOriginsInDatabase(db, params) {
if (params.entryKeys.length === 0 || params.sessionIds?.length === 0 || !tableExists(db, "memory_entry_origins")) return 0;
return runSqliteImmediateTransactionSync(db, () => {
let query = getNodeSqliteKysely(db).deleteFrom("memory_entry_origins").where("agent_id", "=", params.agentId).where("entry_key", "in", params.entryKeys);
if (params.sessionIds) query = query.where("session_id", "in", params.sessionIds);
return Number(executeSqliteQuerySync(db, query).numAffectedRows ?? 0n);
});
}
function reserveMemoryEntryOrigins(params) {
const previousLines = params.previousMemory.replace(/\r\n/gu, "\n").split("\n");
const operationParents = params.operations.map((operation) => {
const parentKeys = /* @__PURE__ */ new Set([operation.candidateKey]);
for (const entry of operation.priorEntries) {
const entryIndex = previousLines.findIndex((line) => line.trim() === entry);
const marker = previousLines[entryIndex - 1]?.trim();
const parentKey = /^<!--\s*openclaw-memory-promotion:([^\n]*?)\s*-->$/u.exec(marker ?? "")?.[1]?.trim();
if (parentKey) parentKeys.add(parentKey);
}
return {
operation,
parentKeys
};
});
const affectedKeys = [...new Set(operationParents.flatMap(({ parentKeys }) => [...parentKeys]))];
const reservations = [];
const rollback = () => {
for (const reservation of reservations.toReversed()) deleteMemoryEntryOrigins(reservation);
};
try {
for (const agentId of [...new Set(params.agentIds)].toSorted()) {
const origins = listMemoryEntryOrigins({
agentId,
entryKeys: affectedKeys
});
for (const { operation, parentKeys } of operationParents) {
const added = recordMemoryEntryOrigins({
agentId,
origins: origins.filter((origin) => parentKeys.has(origin.entryKey)),
entryKey: operation.candidateKey
});
if (added.length > 0) reservations.push({
agentId,
entryKeys: [operation.candidateKey],
sessionIds: added.map((origin) => origin.sessionId)
});
}
}
} catch (error) {
rollback();
throw error;
}
return rollback;
}
async function pruneMemoryEntryOrigins(params) {
const entryKeys = [...new Set(params.entryKeys)].filter((key) => !params.retainedEntryKeys.has(key));
if (entryKeys.length === 0) return;
const diaries = await Promise.all(DREAMS_FILENAMES.map((name) => readDreamsFile(path.join(params.workspaceDir, name), params.workspaceDir)));
const diaryKeys = new Set(diaries.flatMap(extractPromotionKeys));
for (const agentId of new Set(params.agentIds)) {
const indexed = withOpenClawAgentDatabaseReadOnly(({ db }) => new Set(executeSqliteQuerySync(db, getNodeSqliteKysely(db).selectFrom("memory_index_chunks").select("text").where("source", "=", "memory").where("text", "like", "%openclaw-memory-promotion:%")).rows.flatMap(({ text }) => extractPromotionKeys(text))), { agentId });
deleteMemoryEntryOrigins({
agentId,
entryKeys: entryKeys.filter((key) => !diaryKeys.has(key) && !(indexed.found && indexed.value.has(key)))
});
}
}
//#endregion
export { recordMemoryEntryOrigins as a, pruneMemoryEntryOrigins as i, listMemoryEntryOrigins as n, recordMemorySessionTombstonesInDatabase as o, listMemorySessionTombstones as r, reserveMemoryEntryOrigins as s, deleteMemoryEntryOriginsInDatabase as t };