openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
278 lines (277 loc) • 15 kB
JavaScript
import { o as normalizeLowercaseStringOrEmpty } from "./string-coerce-1VuiLaaR.mjs";
import { i as resolveRequiredHomeDir, t as expandHomePrefix } from "./home-dir-Dc6nN6HB.mjs";
import { o as safeRealpathSync } from "./boundary-path-CAv_plX7.mjs";
import { t as hasErrnoCode } from "./errno-FR4CRyya.mjs";
import { r as resolveStateDir } from "./state-dir-CYkBUd_G.mjs";
import { n as normalizeAgentId } from "./agent-id-D1-Q6vR4.mjs";
import { r as isIncognitoSessionKey } from "./session-key-CmZ4o9-l.mjs";
import { l as resolveAgentIdFromSessionKey } from "./session-key-DY3MOX4N.mjs";
import { c as resolveIncognitoOpenClawAgentSqlitePath, s as resolveExplicitIncognitoAgentSqliteTarget } from "./openclaw-agent-db.paths-BbmZlEP5.mjs";
import { r as isCompactionCheckpointTranscriptFileName } from "./transcript-file-names-BZ8jA63H.mjs";
import "./artifacts-C6u8F4sP.mjs";
import fs, { readdirSync } from "node:fs";
import path from "node:path";
import os from "node:os";
function resolveConcreteSessionStorePath(storePath) {
const trimmed = storePath?.trim();
if (!trimmed || trimmed === "(multiple)" || trimmed.includes("{agentId}")) return;
return trimmed;
}
/** Resolves only the legacy unsuffixed target, without reading ownership state. */
function resolveUnsuffixedSqliteTargetFromSessionStorePath(storePath) {
const concrete = resolveConcreteSessionStorePath(storePath);
if (!concrete) throw new Error("Session storage requires a concrete store path");
const resolved = path.resolve(concrete);
if (resolved.endsWith(".sqlite")) {
const agentId = resolveAgentIdFromSqliteDatabasePath(resolved);
return {
path: resolved,
...agentId ? { agentId } : { shared: true }
};
}
const sessionsDir = path.dirname(resolved);
if (path.basename(resolved) !== "sessions.json") {
const sqliteBaseName = path.basename(resolved, path.extname(resolved)) || "openclaw-agent";
return { path: path.join(sessionsDir, `${sqliteBaseName}.sqlite`) };
}
if (path.basename(sessionsDir) !== "sessions") return { path: path.join(sessionsDir, "openclaw-agent.sqlite") };
const agentDir = path.dirname(sessionsDir);
if (path.basename(path.dirname(agentDir)) !== "agents") return { path: path.join(sessionsDir, "openclaw-agent.sqlite") };
return {
agentId: normalizeAgentId(path.basename(agentDir)),
path: path.join(agentDir, "agent", "openclaw-agent.sqlite")
};
}
/** List inspection candidates without opening stores or assigning writable ownership. */
function listSqliteTargetCandidatePathsForSessionStorePath(storePath, agentId) {
const unsuffixedTarget = resolveUnsuffixedSqliteTargetFromSessionStorePath(storePath);
if (unsuffixedTarget.agentId || unsuffixedTarget.shared) return [unsuffixedTarget.path];
const directory = path.dirname(unsuffixedTarget.path);
const baseName = path.basename(unsuffixedTarget.path, ".sqlite");
const prefix = agentId ? `${baseName}.${normalizeAgentId(agentId)}` : void 0;
const candidateNames = /* @__PURE__ */ new Set([path.basename(unsuffixedTarget.path)]);
try {
for (const fileName of readdirSync(directory)) {
const databaseName = fileName.replace(/-(?:wal|shm|journal)$/u, "");
if (databaseName.startsWith(`${baseName}.`) && databaseName.endsWith(".sqlite") && (!prefix || parseSqliteTargetSuffixIndex(databaseName, prefix) !== void 0)) candidateNames.add(databaseName);
}
} catch (error) {
if (!hasErrnoCode(error, "ENOENT")) throw error;
}
return [...candidateNames].map((fileName) => path.join(directory, fileName));
}
function parseSqliteTargetSuffixIndex(fileName, prefix) {
if (fileName === `${prefix}.sqlite`) return 1;
if (!fileName.startsWith(`${prefix}.`) || !fileName.endsWith(".sqlite")) return;
const rawValue = fileName.slice(prefix.length + 1, -7);
if (!/^[1-9]\d*$/.test(rawValue)) return;
const value = Number(rawValue);
return Number.isSafeInteger(value) && value >= 2 && String(value) === rawValue ? value : void 0;
}
/** Extracts the agent id from the canonical per-agent SQLite database path. */
function resolveAgentIdFromSqliteDatabasePath(databasePath) {
if (path.basename(databasePath) !== "openclaw-agent.sqlite") return;
const agentDbDir = path.dirname(databasePath);
if (path.basename(agentDbDir) !== "agent") return;
const agentDir = path.dirname(agentDbDir);
if (path.basename(path.dirname(agentDir)) !== "agents") return;
return normalizeAgentId(path.basename(agentDir));
}
//#endregion
//#region src/config/sessions/paths.ts
/** Incognito key identity takes precedence over an explicit durable store path. */
function resolveExplicitSessionStorePathForScope(scope) {
const explicit = resolveExplicitIncognitoAgentSqliteTarget(scope.storePath, {
agentId: isIncognitoSessionKey(scope.sessionKey) ? resolveAgentIdFromSessionKey(scope.sessionKey) : scope.agentId,
env: scope.env
});
if (explicit) return explicit.path;
if (isIncognitoSessionKey(scope.sessionKey)) return resolveIncognitoOpenClawAgentSqlitePath({
agentId: resolveAgentIdFromSessionKey(scope.sessionKey),
env: scope.env
});
return scope.storePath || void 0;
}
function resolveSessionTranscriptsDirForAgent(agentId, env = process.env, homedir = () => resolveRequiredHomeDir(env, os.homedir)) {
if (!agentId?.trim()) throw new Error("Session storage path requires an explicit agent id.");
const root = resolveStateDir(env, homedir);
const id = normalizeAgentId(agentId);
return path.join(root, "agents", id, "sessions");
}
function resolveDefaultSessionStorePath(agentId) {
return path.join(resolveSessionTranscriptsDirForAgent(agentId), "sessions.json");
}
/** Store selectors and explicit databases share the owning agent's session artifact directory. */
function resolveSessionArtifactDirectory(storePath) {
const storeDir = path.dirname(storePath);
return path.basename(storeDir) === "agent" ? path.join(path.dirname(storeDir), "sessions") : storeDir;
}
const SQLITE_TRANSCRIPT_TARGET_PREFIX = "sqlite:";
function resolveSessionFilePathOptions(params) {
const agentId = params.agentId?.trim();
const storePath = params.storePath?.trim();
if (storePath && storePath !== "(multiple)") {
const sessionsDir = path.dirname(path.resolve(storePath));
return agentId ? {
sessionsDir,
agentId
} : { sessionsDir };
}
if (agentId) return { agentId };
}
const SAFE_SESSION_ID_RE = /^[\p{L}\p{N}][\p{L}\p{N}\p{M}._-]{0,127}$/u;
function validateSessionId(sessionId) {
const trimmed = sessionId.trim();
if (trimmed !== trimmed.normalize("NFC") || !SAFE_SESSION_ID_RE.test(trimmed) || Buffer.byteLength(`${trimmed}.jsonl`, "utf8") > 255 || isCompactionCheckpointTranscriptFileName(`${trimmed}.jsonl`)) throw new Error(`Invalid session ID: ${sessionId}`);
return trimmed;
}
function resolveSessionsDir(opts) {
const sessionsDir = opts?.sessionsDir?.trim();
if (sessionsDir) return path.resolve(sessionsDir);
return resolveSessionTranscriptsDirForAgent(opts?.agentId ?? "");
}
function resolvePathFromAgentSessionsDir(agentSessionsDir, candidateAbsPath) {
const agentBase = safeRealpathSync(path.resolve(agentSessionsDir)) ?? path.resolve(agentSessionsDir);
const realCandidate = safeRealpathSync(candidateAbsPath) ?? candidateAbsPath;
const relative = path.relative(agentBase, realCandidate);
if (!relative || relative.startsWith("..") || path.isAbsolute(relative)) return resolveRerootedSessionPath(agentBase, candidateAbsPath);
return path.resolve(agentBase, relative);
}
function resolveRerootedSessionPath(agentSessionsDir, candidateAbsPath) {
const parsed = resolveAgentSessionsPathParts(candidateAbsPath);
if (!parsed) return;
const relativeSegments = parsed.parts.slice(parsed.sessionsIndex + 1);
if (relativeSegments.length === 0) return;
const rerooted = path.resolve(agentSessionsDir, ...relativeSegments);
const contained = path.relative(agentSessionsDir, rerooted);
if (!contained || contained.startsWith("..") || path.isAbsolute(contained)) return;
return fs.existsSync(rerooted) ? rerooted : void 0;
}
function resolveSiblingAgentSessionsDir(baseSessionsDir, agentId) {
const agentsDir = resolveAgentsDirFromSessionStorePath(path.join(baseSessionsDir, "sessions.json"));
return agentsDir ? path.join(agentsDir, normalizeAgentId(agentId), "sessions") : void 0;
}
function resolveAgentSessionsPathParts(candidateAbsPath) {
const parts = path.normalize(path.resolve(candidateAbsPath)).split(path.sep).filter(Boolean);
const sessionsIndex = parts.lastIndexOf("sessions");
if (sessionsIndex < 2 || parts[sessionsIndex - 2] !== "agents") return null;
return {
parts,
sessionsIndex
};
}
function extractAgentIdFromAbsoluteSessionPath(candidateAbsPath) {
const parsed = resolveAgentSessionsPathParts(candidateAbsPath);
if (!parsed) return;
const { parts, sessionsIndex } = parsed;
return parts[sessionsIndex - 1] || void 0;
}
function resolveStructuralSessionFallbackPath(candidateAbsPath, expectedAgentId) {
const parsed = resolveAgentSessionsPathParts(candidateAbsPath);
if (!parsed) return;
const { parts, sessionsIndex } = parsed;
const agentIdPart = parts[sessionsIndex - 1];
if (!agentIdPart) return;
const normalizedAgentId = normalizeAgentId(agentIdPart);
if (normalizedAgentId !== normalizeLowercaseStringOrEmpty(agentIdPart)) return;
if (normalizedAgentId !== normalizeAgentId(expectedAgentId)) return;
const relativeSegments = parts.slice(sessionsIndex + 1);
if (relativeSegments.length !== 1) return;
const fileName = relativeSegments[0];
if (!fileName || fileName === "." || fileName === "..") return;
return path.normalize(path.resolve(candidateAbsPath));
}
function resolvePathWithinSessionsDir(sessionsDir, candidate, opts) {
const trimmed = candidate.trim();
if (!trimmed) throw new Error("Session file path must not be empty");
const resolvedBase = path.resolve(sessionsDir);
const realBase = safeRealpathSync(resolvedBase) ?? resolvedBase;
const realTrimmed = path.isAbsolute(trimmed) ? safeRealpathSync(trimmed) ?? trimmed : trimmed;
const normalized = path.isAbsolute(realTrimmed) ? path.relative(realBase, realTrimmed) : realTrimmed;
if (normalized.startsWith("..") && path.isAbsolute(realTrimmed)) {
const tryAgentFallback = (agentId) => {
const normalizedAgentId = normalizeAgentId(agentId);
const siblingSessionsDir = resolveSiblingAgentSessionsDir(realBase, normalizedAgentId);
if (siblingSessionsDir) {
const siblingResolved = resolvePathFromAgentSessionsDir(siblingSessionsDir, realTrimmed);
if (siblingResolved) return siblingResolved;
}
return resolvePathFromAgentSessionsDir(resolveSessionTranscriptsDirForAgent(normalizedAgentId), realTrimmed);
};
const explicitAgentId = opts?.agentId?.trim();
if (explicitAgentId) {
const resolvedFromAgent = tryAgentFallback(explicitAgentId);
if (resolvedFromAgent) return resolvedFromAgent;
}
const extractedAgentId = extractAgentIdFromAbsoluteSessionPath(realTrimmed);
if (extractedAgentId) {
const resolvedFromPath = tryAgentFallback(extractedAgentId);
if (resolvedFromPath) return resolvedFromPath;
const structuralFallback = resolveStructuralSessionFallbackPath(realTrimmed, extractedAgentId);
if (structuralFallback) return structuralFallback;
}
}
if (!normalized || normalized.startsWith("..") || path.isAbsolute(normalized)) throw new Error("Session file path must be within sessions directory");
return path.resolve(realBase, normalized);
}
function resolveSessionTranscriptPathInDir(sessionId, sessionsDir, topicId) {
const safeSessionId = validateSessionId(sessionId);
const safeTopicId = typeof topicId === "string" ? encodeURIComponent(topicId) : typeof topicId === "number" ? String(topicId) : void 0;
const fileName = safeTopicId !== void 0 ? `${safeSessionId}-topic-${safeTopicId}.jsonl` : `${safeSessionId}.jsonl`;
if (Buffer.byteLength(fileName, "utf8") > 255) throw new Error(`Invalid session transcript filename: ${fileName}`);
return resolvePathWithinSessionsDir(sessionsDir, fileName);
}
function resolveSessionTranscriptPath(sessionId, agentId, topicId) {
return resolveSessionTranscriptPathInDir(sessionId, resolveSessionTranscriptsDirForAgent(agentId), topicId);
}
function resolveSessionFilePathCore(sessionId, entry, opts) {
const sessionsDir = resolveSessionsDir(opts);
const candidate = entry && "sessionFile" in entry && typeof entry.sessionFile === "string" ? entry.sessionFile.trim() : void 0;
if (candidate) {
if (candidate.startsWith(SQLITE_TRANSCRIPT_TARGET_PREFIX)) return candidate;
try {
return resolvePathWithinSessionsDir(sessionsDir, candidate, { agentId: opts?.agentId });
} catch {}
}
return resolveSessionTranscriptPathInDir(sessionId, sessionsDir);
}
var SessionStoreAgentIdRequiredError = class extends Error {
constructor() {
super("Session store path requires an explicit agent id.");
this.name = "SessionStoreAgentIdRequiredError";
}
};
/** Resolves fixed literal paths without an owner; derived or templated paths require agentId. */
function resolveSessionStorePathCore(store, opts) {
const env = opts?.env ?? process.env;
const homedir = () => resolveRequiredHomeDir(env, os.homedir);
if (!store) {
if (!opts?.agentId?.trim()) throw new SessionStoreAgentIdRequiredError();
const agentId = normalizeAgentId(opts.agentId);
return path.join(resolveSessionTranscriptsDirForAgent(agentId, env, homedir), "sessions.json");
}
let expandedStore = store;
if (expandedStore.includes("{agentId}")) {
if (!opts?.agentId?.trim()) throw new SessionStoreAgentIdRequiredError();
const agentId = normalizeAgentId(opts.agentId);
expandedStore = expandedStore.replaceAll("{agentId}", agentId);
}
if (expandedStore.startsWith("~")) expandedStore = expandHomePrefix(expandedStore, {
home: resolveRequiredHomeDir(env, homedir),
env,
homedir
});
return path.isAbsolute(expandedStore) ? path.resolve(expandedStore) : path.resolve(resolveStateDir(env, homedir), expandedStore);
}
function resolveAgentsDirFromSessionStorePath(storePath) {
const candidateAbsPath = path.resolve(storePath);
if (path.basename(candidateAbsPath) !== "sessions.json") return;
const sessionsDir = path.dirname(candidateAbsPath);
if (path.basename(sessionsDir) !== "sessions") return;
const agentDir = path.dirname(sessionsDir);
const agentsDir = path.dirname(agentDir);
if (path.basename(agentsDir) !== "agents") return;
return agentsDir;
}
//#endregion
export { resolveSessionArtifactDirectory as a, resolveSessionStorePathCore as c, resolveSessionTranscriptsDirForAgent as d, validateSessionId as f, resolveUnsuffixedSqliteTargetFromSessionStorePath as g, resolveConcreteSessionStorePath as h, resolveExplicitSessionStorePathForScope as i, resolveSessionTranscriptPath as l, parseSqliteTargetSuffixIndex as m, resolveAgentsDirFromSessionStorePath as n, resolveSessionFilePathCore as o, listSqliteTargetCandidatePathsForSessionStorePath as p, resolveDefaultSessionStorePath as r, resolveSessionFilePathOptions as s, SessionStoreAgentIdRequiredError as t, resolveSessionTranscriptPathInDir as u };