UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

289 lines (288 loc) • 13.1 kB
import { t as formatErrorMessage } from "./errors-O33D01XW.mjs"; import { a as getNodeSqliteKysely, r as executeSqliteQuerySync, u as sqliteStringSet } from "./kysely-sync-K7ftY-g4.mjs"; import { t as openNodeSqliteDatabase } from "./node-sqlite-XIcwmOrl.mjs"; import { i as tableExists } from "./openclaw-state-db-schema-helpers-BlZefrPy.mjs"; import { c as MEMORY_INDEX_META_TABLE, f as MEMORY_INDEX_CHUNKS_TABLE, h as MEMORY_INDEX_SOURCES_TABLE } from "./memory-schema-BA04oCjG.mjs"; import { i as withFreshOpenClawAgentDatabaseReadOnly, n as openOpenClawAgentDatabaseReadOnly } from "./openclaw-agent-db-readonly-open-hQ4ohjUT.mjs"; import { n as withOpenClawAgentDatabaseReadOnly } from "./openclaw-agent-db-readonly-C4wDuLrp.mjs"; import { n as serveWorkerTasks } from "./worker-task-server-BC_7U18u.mjs"; import { t as loadSqliteVecExtension } from "./sqlite-vec-BcbJNyja.mjs"; import "./memory-core-host-engine-knn-DTjLOT03.mjs"; import "./error-runtime-BmospGvZ.mjs"; import "./worker-task-server-OcaoXQUy.mjs"; import { a as readCuratedMemoryTriggerCandidates, o as readCuratedProjectMemoryCandidates } from "./engine-storage-yT207ZoA.mjs"; import { n as readMemoryEntryOriginsInDatabase } from "./memory-core-host-engine-storage-g5_Iw_VB.mjs"; import "./sqlite-runtime-Ch9K5Xik.mjs"; import { a as extractPromotionKeys } from "./short-term-promotion-memory-write-CKky4xPy.mjs"; import { n as referencesSession, r as scrubMemoryContent } from "./memory-forget-content-6V7NKQaY.mjs"; import { t as isMemorySessionIndexable } from "./manager-session-sync-state-CC68bJ0_.mjs"; import { c as readMemoryRecallData, i as searchChunksByEmbedding, l as readMemoryRetrievalIndexState, m as buildFtsQuery, n as searchKeyword, p as bm25RankToScore, r as searchPathKeyword } from "./manager-search-CyY6k9P_.mjs"; import "./memory-core-host-engine-schema-C2UMfTcl.mjs"; //#region extensions/memory-core/src/memory-entry-origin-reads.ts function queryOrigins(db, request) { if (request.kind === "origin-rows") return { kind: request.kind, rows: db && tableExists(db, "memory_entry_origins") ? readMemoryEntryOriginsInDatabase(db, request) : [] }; if (request.kind === "origin-exists") { let exists = false; if (db && tableExists(db, "memory_entry_origins")) { let query = getNodeSqliteKysely(db).selectFrom("memory_entry_origins").select("entry_key").where("agent_id", "=", request.agentId).where("entry_key", "in", sqliteStringSet(request.entryKeys)); if (request.sessionIds) query = query.where("session_id", "in", sqliteStringSet(request.sessionIds)); exists = executeSqliteQuerySync(db, query.limit(1)).rows.length > 0; } return { kind: request.kind, exists }; } if (request.kind === "session-tombstones") { if (!db || !tableExists(db, "memory_session_tombstones")) return { kind: request.kind, rows: [] }; let query = getNodeSqliteKysely(db).selectFrom("memory_session_tombstones").selectAll().where("agent_id", "=", request.agentId); if (request.sessionIds) query = query.where("session_id", "in", sqliteStringSet(request.sessionIds)); return { kind: request.kind, rows: 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 })) }; } const keys = db ? executeSqliteQuerySync(db, getNodeSqliteKysely(db).selectFrom("memory_index_chunks").select("text").where("source", "=", "memory").where("text", "like", "%openclaw-memory-promotion:%")).rows.flatMap(({ text }) => extractPromotionKeys(text)) : []; return { kind: request.kind, keys }; } function readMemoryOriginsInWorker(request) { const result = withFreshOpenClawAgentDatabaseReadOnly(({ db }) => queryOrigins(db, request), { agentId: request.agentId, path: request.databasePath, env: { OPENCLAW_STATE_DIR: request.stateDir } }); return result.found ? result.value : queryOrigins(void 0, request); } //#endregion //#region extensions/memory-core/src/memory-forget-index-read.ts async function readMemoryForgetIndexInWorker(request) { const options = { agentId: request.agentId, path: request.databasePath, env: { OPENCLAW_STATE_DIR: request.stateDir } }; const params = { agentId: request.agentId, changedPaths: new Set(request.changedPaths), removedPaths: new Set(request.removedPaths), sessionIds: new Set(request.sessionIds), excludedSessionIds: new Set(request.excludedSessionIds), entryKeys: new Set(request.entryKeys), corpusSnippets: new Set(request.corpusSnippets) }; const result = withFreshOpenClawAgentDatabaseReadOnly(({ db }) => { const kysely = getNodeSqliteKysely(db); const indexedChunks = executeSqliteQuerySync(db, kysely.selectFrom("memory_index_chunks as chunk").select([ "chunk.id", "chunk.path", "chunk.source" ]).$if(tableExists(db, "memory_index_chunk_provenance"), (query) => query.leftJoin("memory_index_chunk_provenance as provenance", "provenance.chunk_id", "chunk.id").select(["provenance.origin_class as originClass", "provenance.session_kind as sessionKind"])).select((eb) => eb.case("chunk.source").when("sessions").then("").else(eb.ref("chunk.text")).end().as("text"))).rows; const changedPaths = new Set(params.changedPaths); for (const chunk of indexedChunks) if (chunk.source === "memory" && scrubMemoryContent({ ...params, content: chunk.text }).content !== chunk.text) changedPaths.add(chunk.path); const chunks = indexedChunks.filter((chunk) => changedPaths.has(chunk.path) || referencesSession(chunk.path, params.agentId, params.sessionIds) || params.sessionIds.size > 0 && chunk.source === "sessions" && (chunk.originClass === "system" || !isMemorySessionIndexable({ sessionKind: chunk.sessionKind ?? "unknown" }) || referencesSession(chunk.path, params.agentId, params.excludedSessionIds))); const removedSessionPaths = new Set(chunks.filter((chunk) => chunk.source === "sessions").map((chunk) => chunk.path)); const sources = executeSqliteQuerySync(db, kysely.selectFrom("memory_index_sources").select(["path", "source"])).rows.filter((source) => params.removedPaths.has(source.path) || source.source === "sessions" && removedSessionPaths.has(source.path)); const chunkIds = chunks.map((chunk) => chunk.id); const ftsRows = chunkIds.length > 0 && tableExists(db, "memory_index_chunks_fts") ? executeSqliteQuerySync(db, kysely.selectFrom("memory_index_chunks_fts").select((eb) => eb.fn.countAll().as("count")).where("rowid", "in", kysely.selectFrom("memory_index_chunks").select("chunk_rowid").where("id", "in", chunkIds))).rows[0].count : 0; const hasVectorTable = tableExists(db, "memory_index_chunks_vec"); let embeddingCacheRows = 0; if (params.sessionIds.size > 0 && tableExists(db, "memory_embedding_cache")) embeddingCacheRows = executeSqliteQuerySync(db, kysely.selectFrom("memory_embedding_cache").select((eb) => eb.fn.countAll().as("count"))).rows[0].count; return { chunks: chunks.map(({ id, path, source }) => ({ id, path, source })), sources, ftsRows, embeddingCacheRows, hasVectorTable }; }, options); if (!result.found) return { chunks: [], sources: [], ftsRows: 0, vectorRows: 0, embeddingCacheRows: 0, hasVectorTable: false }; let vectorRows = 0; let extensionPath; if (result.value.hasVectorTable && result.value.chunks.length > 0) { const probe = openNodeSqliteDatabase(":memory:", { allowExtension: true }); let preparedExtensionPath; try { const loaded = await loadSqliteVecExtension({ db: probe }); if (!loaded.ok || !loaded.extensionPath) throw new Error(`memory forget cannot inspect vector index: ${loaded.error ?? "load failed"}`); preparedExtensionPath = loaded.extensionPath; } finally { probe.close(); } extensionPath = preparedExtensionPath; const vectorResult = withFreshOpenClawAgentDatabaseReadOnly(({ db }) => { db.enableLoadExtension(true); db.loadExtension(preparedExtensionPath); const vectorKysely = getNodeSqliteKysely(db); return executeSqliteQuerySync(db, vectorKysely.selectFrom("memory_index_chunks_vec").select((eb) => eb.fn.countAll().as("count")).where("id", "in", result.value.chunks.map((chunk) => chunk.id))).rows[0].count; }, options, { allowExtension: true }); vectorRows = vectorResult.found ? vectorResult.value : 0; } return { ...result.value, vectorRows, extensionPath }; } //#endregion //#region extensions/memory-core/src/memory/manager-status-presence.ts const MEMORY_INDEX_META_KEY = "memory_index_meta_v1"; /** Inspect existing memory tables without constructing or migrating a manager. */ function inspectMemoryIndexPresenceInWorker(databasePath) { let db; try { db = openNodeSqliteDatabase(databasePath, { readOnly: true }); const builtInMemoryTableSets = [{ meta: MEMORY_INDEX_META_TABLE, sources: MEMORY_INDEX_SOURCES_TABLE, chunks: MEMORY_INDEX_CHUNKS_TABLE }, { meta: "meta", sources: "files", chunks: "chunks" }]; const builtInMemoryTables = builtInMemoryTableSets.flatMap(({ meta, sources, chunks }) => [ meta, sources, chunks ]); const tableNames = new Set(db.prepare(`SELECT name FROM sqlite_master WHERE type = 'table' AND name IN (${builtInMemoryTables.map(() => "?").join(", ")})`).all(...builtInMemoryTables).map((row) => row.name).filter((name) => typeof name === "string")); for (const tables of builtInMemoryTableSets) { if (tableNames.has(tables.meta) && db.prepare(`SELECT 1 AS ok FROM ${tables.meta} WHERE key = ? LIMIT 1`).get(MEMORY_INDEX_META_KEY)) return true; for (const tableName of [tables.sources, tables.chunks]) if (tableNames.has(tableName) && db.prepare(`SELECT 1 AS ok FROM ${tableName} LIMIT 1`).get()) return true; } return false; } catch { return false; } finally { try { db?.close(); } catch {} } } //#endregion //#region extensions/memory-core/src/memory/manager-search.worker.ts serveWorkerTasks(async (input) => { const request = input; if (request.kind === "prewarm") return { kind: "prewarm" }; if (request.kind === "presence") return { kind: "presence", present: inspectMemoryIndexPresenceInWorker(request.databasePath) }; if (request.kind === "forget-index-plan") return { kind: request.kind, plan: await readMemoryForgetIndexInWorker(request) }; if (request.kind === "origin-rows" || request.kind === "origin-exists" || request.kind === "session-tombstones" || request.kind === "origin-index-keys") return readMemoryOriginsInWorker(request); if (request.kind === "recall-metadata") { const result = withOpenClawAgentDatabaseReadOnly(({ db }) => readMemoryRecallData(db, request), { agentId: request.agentId, path: request.databasePath }); if (!result.found) throw new Error(`Memory search database unavailable: ${result.reason}`); return { kind: "recall-metadata", ...result.value }; } const opened = openOpenClawAgentDatabaseReadOnly({ agentId: request.agentId, path: request.databasePath }); if (!opened.found) { if (opened.reason === "database-missing" && (request.kind === "index-state" || request.kind === "keyword" && request.includeIndexState)) { const state = { meta: null, hasIndexedChunks: false, hasFtsContent: false, vectorState: { state: "empty" } }; return request.kind === "index-state" ? { kind: "index-state", state } : { kind: "keyword", indexState: state, body: { rows: [] }, path: { rows: [] } }; } throw new Error(`Memory search database unavailable: ${opened.reason}`); } const { db } = opened.database; try { if (request.kind === "index-state") return { kind: "index-state", state: readMemoryRetrievalIndexState(db) }; if (request.kind === "curated") { const provenanceRepairPending = request.checkProvenanceRepair && executeSqliteQuerySync(db, getNodeSqliteKysely(db).selectFrom("memory_index_sources").select("hash").where("source", "=", "memory").where("hash", "=", "").limit(1)).rows.length > 0; return { kind: "curated", provenanceRepairPending, rows: provenanceRepairPending ? [] : request.projectsOnly ? readCuratedProjectMemoryCandidates(db, request.limit, request.activeProjectKeys ?? []) : readCuratedMemoryTriggerCandidates(db, request.limit, request.activeProjectKeys) }; } if (request.kind === "vector") return { kind: "vector", rows: await searchChunksByEmbedding({ ...request.query, db }) }; const indexState = request.includeIndexState ? readMemoryRetrievalIndexState(db) : void 0; return { kind: "keyword", body: await searchKeyword({ ...request.query.body, db, buildFtsQuery, bm25RankToScore }).then((rows) => ({ rows })).catch((error) => ({ rows: [], error: formatErrorMessage(error) })), path: await searchPathKeyword({ ...request.query.path, db, buildFtsQuery, bm25RankToScore }).then((rows) => ({ rows })).catch((error) => ({ rows: [], error: formatErrorMessage(error) })), ...indexState ? { indexState } : {} }; } finally { opened.database.close(); } }); //#endregion export {};