openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
368 lines (362 loc) • 14.6 kB
JavaScript
import { p as resolveUserPath } from "./utils-CCC-BEJH.js";
import { d as executeSqliteQuerySync, f as executeSqliteQueryTakeFirstSync, l as runSqliteImmediateTransactionSync, o as runOpenClawStateWriteTransaction, p as getNodeSqliteKysely, s as resolveOpenClawStateSqliteDir, t as OPENCLAW_SQLITE_BUSY_TIMEOUT_MS, u as clearNodeSqliteKyselyCacheForDatabase } from "./openclaw-state-db-ULtH_b7x.js";
import { n as requireNodeSqlite, t as configureSqliteWalMaintenance } from "./sqlite-wal-CP9DVd__.js";
import { u as normalizeAgentId } from "./session-key-B_NoIfpX.js";
import { s as resolveDefaultAgentDir, u as resolveRegisteredAgentIdForDir } from "./agent-scope-config-CgCYpZfK.js";
import fs, { chmodSync, existsSync, mkdirSync, statSync } from "node:fs";
import path from "node:path";
import { createHash } from "node:crypto";
//#region src/state/openclaw-agent-db.paths.ts
/** Resolve the SQLite file for one normalized agent id. */
function resolveOpenClawAgentSqlitePath(options) {
const agentId = normalizeAgentId(options.agentId);
return options.path ?? path.join(path.dirname(resolveOpenClawStateSqliteDir(options.env ?? process.env)), "agents", agentId, "agent", "openclaw-agent.sqlite");
}
//#endregion
//#region src/state/openclaw-agent-schema.generated.ts
/**
* This file was generated from the SQLite schema source.
* Please do not edit it manually.
*/
const OPENCLAW_AGENT_SCHEMA_SQL = `CREATE TABLE IF NOT EXISTS schema_meta (
meta_key TEXT NOT NULL PRIMARY KEY,
role TEXT NOT NULL,
schema_version INTEGER NOT NULL,
agent_id TEXT,
app_version TEXT,
created_at INTEGER NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS cache_entries (
scope TEXT NOT NULL,
key TEXT NOT NULL,
value_json TEXT,
blob BLOB,
expires_at INTEGER,
updated_at INTEGER NOT NULL,
PRIMARY KEY (scope, key)
);
CREATE INDEX IF NOT EXISTS idx_agent_cache_expiry
ON cache_entries(scope, expires_at, key)
WHERE expires_at IS NOT NULL;
CREATE INDEX IF NOT EXISTS idx_agent_cache_updated
ON cache_entries(scope, updated_at DESC, key);
CREATE TABLE IF NOT EXISTS auth_profile_store (
store_key TEXT NOT NULL PRIMARY KEY,
store_json TEXT NOT NULL,
updated_at INTEGER NOT NULL
);
CREATE TABLE IF NOT EXISTS auth_profile_state (
state_key TEXT NOT NULL PRIMARY KEY,
state_json TEXT NOT NULL,
updated_at INTEGER NOT NULL
);\n`;
//#endregion
//#region src/state/openclaw-agent-db.ts
/**
* Per-agent SQLite database lifecycle and shared-state registration.
*
* Each opened agent database is schema-owned by one normalized agent id, cached
* per pathname, protected with private file modes, and registered in the shared
* OpenClaw state database for discovery and maintenance.
*/
const OPENCLAW_AGENT_SCHEMA_VERSION = 1;
const OPENCLAW_AGENT_DB_DIR_MODE = 448;
const OPENCLAW_AGENT_DB_FILE_MODE = 384;
const OPENCLAW_AGENT_DB_SIDECAR_SUFFIXES = [
"",
"-shm",
"-wal"
];
const cachedDatabases = /* @__PURE__ */ new Map();
function readSqliteUserVersion(db) {
const row = db.prepare("PRAGMA user_version").get();
return Number(row?.user_version ?? 0);
}
function assertSupportedAgentSchemaVersion(db, pathname) {
const userVersion = readSqliteUserVersion(db);
if (userVersion > OPENCLAW_AGENT_SCHEMA_VERSION) throw new Error(`OpenClaw agent database ${pathname} uses newer schema version ${userVersion}; this OpenClaw build supports ${OPENCLAW_AGENT_SCHEMA_VERSION}.`);
}
function ensureOpenClawAgentDatabasePermissions(pathname, options) {
const dir = path.dirname(pathname);
const defaultPath = resolveOpenClawAgentSqlitePath({
agentId: options.agentId,
env: options.env
});
const isDefaultAgentDatabase = path.resolve(pathname) === path.resolve(defaultPath);
const dirExisted = existsSync(dir);
mkdirSync(dir, {
recursive: true,
mode: OPENCLAW_AGENT_DB_DIR_MODE
});
if (isDefaultAgentDatabase || !dirExisted) chmodSync(dir, OPENCLAW_AGENT_DB_DIR_MODE);
for (const suffix of OPENCLAW_AGENT_DB_SIDECAR_SUFFIXES) {
const candidate = `${pathname}${suffix}`;
if (existsSync(candidate)) chmodSync(candidate, OPENCLAW_AGENT_DB_FILE_MODE);
}
}
function readExistingSchemaMeta(db) {
if (!db.prepare("SELECT name FROM sqlite_master WHERE type = 'table' AND name = 'schema_meta'").get()) return null;
const row = db.prepare("SELECT role, agent_id FROM schema_meta WHERE meta_key = 'primary'").get();
if (!row) return null;
return {
agentId: typeof row.agent_id === "string" ? row.agent_id : null,
role: typeof row.role === "string" ? row.role : null
};
}
function assertExistingSchemaOwner(existing, agentId, pathname) {
if (!existing) return;
if (existing.role !== "agent") throw new Error(`OpenClaw agent database ${pathname} has schema role ${existing.role ?? "unknown"}; expected agent.`);
if (!existing.agentId) throw new Error(`OpenClaw agent database ${pathname} has no agent owner.`);
if (normalizeAgentId(existing.agentId) !== agentId) throw new Error(`OpenClaw agent database ${pathname} belongs to agent ${existing.agentId}; requested agent ${agentId}.`);
}
function ensureAgentSchema(db, agentId, pathname) {
assertSupportedAgentSchemaVersion(db, pathname);
assertExistingSchemaOwner(readExistingSchemaMeta(db), agentId, pathname);
db.exec(OPENCLAW_AGENT_SCHEMA_SQL);
const kysely = getNodeSqliteKysely(db);
db.exec(`PRAGMA user_version = ${OPENCLAW_AGENT_SCHEMA_VERSION};`);
const now = Date.now();
executeSqliteQuerySync(db, kysely.insertInto("schema_meta").values({
meta_key: "primary",
role: "agent",
schema_version: OPENCLAW_AGENT_SCHEMA_VERSION,
agent_id: agentId,
app_version: null,
created_at: now,
updated_at: now
}).onConflict((conflict) => conflict.column("meta_key").doUpdateSet({
role: "agent",
schema_version: OPENCLAW_AGENT_SCHEMA_VERSION,
agent_id: agentId,
app_version: null,
updated_at: now
})));
}
function registerAgentDatabase(params) {
let sizeBytes = null;
try {
sizeBytes = statSync(params.path).size;
} catch {
sizeBytes = null;
}
const lastSeenAt = Date.now();
runOpenClawStateWriteTransaction((database) => {
const db = getNodeSqliteKysely(database.db);
executeSqliteQuerySync(database.db, db.insertInto("agent_databases").values({
agent_id: params.agentId,
path: params.path,
schema_version: OPENCLAW_AGENT_SCHEMA_VERSION,
last_seen_at: lastSeenAt,
size_bytes: sizeBytes
}).onConflict((conflict) => conflict.columns(["agent_id", "path"]).doUpdateSet({
schema_version: OPENCLAW_AGENT_SCHEMA_VERSION,
last_seen_at: lastSeenAt,
size_bytes: sizeBytes
})));
}, { env: params.env });
}
/** Open or return a cached per-agent database after schema and owner validation. */
function openOpenClawAgentDatabase(options) {
const agentId = normalizeAgentId(options.agentId);
const databaseOptions = {
...options,
agentId
};
const pathname = resolveOpenClawAgentSqlitePath(databaseOptions);
const cached = cachedDatabases.get(pathname);
if (cached?.db.isOpen) {
if (cached.agentId !== agentId) throw new Error(`OpenClaw agent database ${pathname} is already open for agent ${cached.agentId}; requested agent ${agentId}.`);
registerAgentDatabase({
agentId,
path: pathname,
env: options.env
});
return cached;
}
if (cached) {
cached.walMaintenance.close();
clearNodeSqliteKyselyCacheForDatabase(cached.db);
cachedDatabases.delete(pathname);
}
ensureOpenClawAgentDatabasePermissions(pathname, databaseOptions);
const db = new (requireNodeSqlite()).DatabaseSync(pathname);
const walMaintenance = configureSqliteWalMaintenance(db, {
databaseLabel: `openclaw-agent:${agentId}`,
databasePath: pathname
});
db.exec("PRAGMA synchronous = NORMAL;");
db.exec(`PRAGMA busy_timeout = ${OPENCLAW_SQLITE_BUSY_TIMEOUT_MS};`);
db.exec("PRAGMA foreign_keys = ON;");
try {
ensureAgentSchema(db, agentId, pathname);
} catch (err) {
walMaintenance.close();
db.close();
throw err;
}
ensureOpenClawAgentDatabasePermissions(pathname, databaseOptions);
const database = {
agentId,
db,
path: pathname,
walMaintenance
};
cachedDatabases.set(pathname, database);
registerAgentDatabase({
agentId,
path: pathname,
env: options.env
});
return database;
}
/** Run a synchronous immediate transaction against an agent database. */
function runOpenClawAgentWriteTransaction(operation, options) {
const database = openOpenClawAgentDatabase(options);
const result = runSqliteImmediateTransactionSync(database.db, () => operation(database));
ensureOpenClawAgentDatabasePermissions(database.path, options);
return result;
}
//#endregion
//#region src/agents/auth-profiles/sqlite.ts
/**
* SQLite persistence adapter for auth profile secrets and runtime state.
* The public helpers expose raw JSON payloads so normalization stays in the
* store/state layers that own compatibility rules.
*/
const PRIMARY_ROW_KEY = "primary";
function resolveAgentDir(agentDir) {
return resolveUserPath(agentDir ?? resolveDefaultAgentDir({}));
}
function inferAgentIdFromDir(agentDir) {
const normalized = path.normalize(agentDir);
if (path.basename(normalized) === "agent") {
const parent = path.basename(path.dirname(normalized));
if (parent) return parent;
}
return `custom-${createHash("sha256").update(normalized).digest("hex").slice(0, 12)}`;
}
function resolveAuthProfileDatabaseOptions(agentDir) {
const dir = resolveAgentDir(agentDir);
return {
agentId: resolveRegisteredAgentIdForDir(dir) ?? inferAgentIdFromDir(dir),
path: path.join(dir, "openclaw-agent.sqlite")
};
}
/** Resolves the SQLite database path that stores auth profiles for an agent dir. */
function resolveAuthProfileDatabasePath(agentDir) {
return resolveAuthProfileDatabaseOptions(agentDir).path;
}
/** Resolves the SQLite database and sidecar paths used by auth profiles. */
function resolveAuthProfileDatabaseFilePaths(agentDir) {
const databasePath = resolveAuthProfileDatabasePath(agentDir);
return [
databasePath,
`${databasePath}-wal`,
`${databasePath}-shm`
];
}
function parseJsonCell(raw) {
if (!raw) return null;
try {
return JSON.parse(raw);
} catch {
return null;
}
}
function getAuthProfileKysely(db) {
return getNodeSqliteKysely(db);
}
function readAuthProfileJsonCellReadOnly(pathname, target) {
const db = new (requireNodeSqlite()).DatabaseSync(pathname, { readOnly: true });
try {
const kysely = getAuthProfileKysely(db);
if (target === "store") return parseJsonCell(executeSqliteQueryTakeFirstSync(db, kysely.selectFrom("auth_profile_store").select("store_json").where("store_key", "=", PRIMARY_ROW_KEY))?.store_json);
return parseJsonCell(executeSqliteQueryTakeFirstSync(db, kysely.selectFrom("auth_profile_state").select("state_json").where("state_key", "=", PRIMARY_ROW_KEY))?.state_json);
} catch {
return null;
} finally {
clearNodeSqliteKyselyCacheForDatabase(db);
db.close();
}
}
/** Reads the raw persisted secrets-store payload without coercing the schema. */
function readPersistedAuthProfileStoreRaw(agentDir, database) {
if (database) {
const db = getAuthProfileKysely(database.db);
return parseJsonCell(executeSqliteQueryTakeFirstSync(database.db, db.selectFrom("auth_profile_store").select("store_json").where("store_key", "=", PRIMARY_ROW_KEY))?.store_json);
}
const databasePath = resolveAuthProfileDatabasePath(agentDir);
if (!fs.existsSync(databasePath)) return null;
return readAuthProfileJsonCellReadOnly(databasePath, "store");
}
/** Reads the raw persisted runtime-state payload without coercing the schema. */
function readPersistedAuthProfileStateRaw(agentDir, database) {
if (database) {
const db = getAuthProfileKysely(database.db);
return parseJsonCell(executeSqliteQueryTakeFirstSync(database.db, db.selectFrom("auth_profile_state").select("state_json").where("state_key", "=", PRIMARY_ROW_KEY))?.state_json);
}
const databasePath = resolveAuthProfileDatabasePath(agentDir);
if (!fs.existsSync(databasePath)) return null;
return readAuthProfileJsonCellReadOnly(databasePath, "state");
}
/** Writes the raw persisted secrets-store payload inside the auth database. */
function writePersistedAuthProfileStoreRaw(payload, agentDir, database) {
const write = (target) => {
const db = getAuthProfileKysely(target.db);
executeSqliteQuerySync(target.db, db.insertInto("auth_profile_store").values({
store_key: PRIMARY_ROW_KEY,
store_json: JSON.stringify(payload),
updated_at: Date.now()
}).onConflict((conflict) => conflict.column("store_key").doUpdateSet({
store_json: JSON.stringify(payload),
updated_at: Date.now()
})));
};
if (database) {
write(database);
return;
}
runOpenClawAgentWriteTransaction(write, resolveAuthProfileDatabaseOptions(agentDir));
}
/** Deletes the persisted secrets-store row while leaving runtime state intact. */
function deletePersistedAuthProfileStoreRaw(agentDir, database) {
const remove = (target) => {
const db = getAuthProfileKysely(target.db);
executeSqliteQuerySync(target.db, db.deleteFrom("auth_profile_store").where("store_key", "=", PRIMARY_ROW_KEY));
};
if (database) {
remove(database);
return;
}
runOpenClawAgentWriteTransaction(remove, resolveAuthProfileDatabaseOptions(agentDir));
}
/** Writes or deletes the persisted runtime-state payload. */
function writePersistedAuthProfileStateRaw(payload, agentDir, database) {
const write = (target) => {
const db = getAuthProfileKysely(target.db);
if (!payload) {
executeSqliteQuerySync(target.db, db.deleteFrom("auth_profile_state").where("state_key", "=", PRIMARY_ROW_KEY));
return;
}
executeSqliteQuerySync(target.db, db.insertInto("auth_profile_state").values({
state_key: PRIMARY_ROW_KEY,
state_json: JSON.stringify(payload),
updated_at: Date.now()
}).onConflict((conflict) => conflict.column("state_key").doUpdateSet({
state_json: JSON.stringify(payload),
updated_at: Date.now()
})));
};
if (database) {
write(database);
return;
}
runOpenClawAgentWriteTransaction(write, resolveAuthProfileDatabaseOptions(agentDir));
}
/** Runs an auth-profile database write transaction for store/state updates. */
function runAuthProfileWriteTransaction(agentDir, operation) {
return runOpenClawAgentWriteTransaction(operation, resolveAuthProfileDatabaseOptions(agentDir));
}
//#endregion
export { resolveAuthProfileDatabasePath as a, writePersistedAuthProfileStoreRaw as c, resolveAuthProfileDatabaseFilePaths as i, readPersistedAuthProfileStateRaw as n, runAuthProfileWriteTransaction as o, readPersistedAuthProfileStoreRaw as r, writePersistedAuthProfileStateRaw as s, deletePersistedAuthProfileStoreRaw as t };