openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
144 lines (143 loc) • 5.87 kB
JavaScript
import { c as isRecord } from "./record-coerce-DItp3I4t.js";
import { r as resolveRealpathOrAbsolute } from "./boundary-path-DMNeww4q.js";
import { r as isPathInside } from "./path-guards-Cp-mGr3-.js";
import { i as readRegularFileSync } from "./regular-file-Equ4rrfE.js";
import { n as readFileWindowFullySync } from "./file-read-DtMn74uz.js";
import { i as resolveSessionFilePathCore } from "./paths-CXdaYWF_.js";
import { n as parseSqliteSessionFileMarker } from "./legacy-sqlite-marker-COPKCuIN.js";
import { a as resolveTrajectoryFilePath, o as resolveTrajectoryPointerFilePath, s as safeTrajectorySessionFileName, t as TRAJECTORY_POINTER_FILE_MAX_BYTES } from "./paths-DBZSw_GC.js";
import fs from "node:fs";
import path from "node:path";
//#region src/trajectory/cleanup.ts
function isPathWithinDir(parentDir, filePath) {
const resolvedParent = resolveRealpathOrAbsolute(parentDir);
const resolvedFile = resolveRealpathOrAbsolute(filePath);
return resolvedFile !== resolvedParent && isPathInside(resolvedParent, resolvedFile);
}
function isRegularNonSymlinkFile(filePath) {
try {
const lst = fs.lstatSync(filePath);
if (!lst.isFile() || lst.isSymbolicLink()) return false;
return fs.statSync(filePath).isFile();
} catch {
return false;
}
}
function readTrajectoryPointerFile(pointerPath, sessionId) {
try {
const { buffer } = readRegularFileSync({
filePath: pointerPath,
maxBytes: TRAJECTORY_POINTER_FILE_MAX_BYTES
});
const parsed = JSON.parse(buffer.toString("utf8"));
if (!isRecord(parsed)) return null;
if (parsed.traceSchema !== "openclaw-trajectory-pointer" || parsed.schemaVersion !== 1 || parsed.sessionId !== sessionId || typeof parsed.runtimeFile !== "string" || !parsed.runtimeFile.trim()) return null;
return { runtimeFile: path.resolve(parsed.runtimeFile) };
} catch {
return null;
}
}
function readFirstNonEmptyLine(filePath) {
let fd = null;
try {
fd = fs.openSync(filePath, "r");
const buffer = Buffer.alloc(65536);
const bytesRead = readFileWindowFullySync(fd, buffer, 0);
if (bytesRead <= 0) return null;
for (const line of buffer.subarray(0, bytesRead).toString("utf8").split(/\r?\n/u)) {
const trimmed = line.trim();
if (trimmed) return trimmed;
}
return null;
} catch {
return null;
} finally {
if (fd !== null) try {
fs.closeSync(fd);
} catch {}
}
}
function runtimeFileStartsWithSessionEvent(filePath, sessionId) {
if (!isRegularNonSymlinkFile(filePath)) return false;
const firstLine = readFirstNonEmptyLine(filePath);
if (!firstLine) return false;
try {
const parsed = JSON.parse(firstLine);
return isRecord(parsed) && parsed.traceSchema === "openclaw-trajectory" && parsed.schemaVersion === 1 && parsed.source === "runtime" && parsed.sessionId === sessionId;
} catch {
return false;
}
}
async function removeRegularFile(filePath, kind) {
if (!isRegularNonSymlinkFile(filePath)) return null;
await fs.promises.rm(filePath, { force: true });
return {
kind,
path: path.resolve(filePath)
};
}
function resolveRemovedSessionFile(params) {
try {
return resolveSessionFilePathCore(params.sessionId, params.sessionFile ? { sessionFile: params.sessionFile } : void 0, { sessionsDir: path.dirname(params.storePath) });
} catch {
return null;
}
}
function mayRemoveRuntimeTarget(params) {
const resolved = resolveRealpathOrAbsolute(params.filePath);
const withinStoreDir = isPathWithinDir(params.storeDir, resolved);
if (resolveRealpathOrAbsolute(params.defaultRuntimePath) === resolved) return !params.restrictToStoreDir || withinStoreDir;
if (params.restrictToStoreDir && withinStoreDir) return true;
const expectedName = `${safeTrajectorySessionFileName(params.sessionId)}.jsonl`;
if (path.basename(resolved) !== expectedName) return false;
return runtimeFileStartsWithSessionEvent(resolved, params.sessionId);
}
async function removeSessionTrajectoryArtifacts(params) {
const sessionFile = resolveRemovedSessionFile(params);
if (!sessionFile) return [];
const storeDir = path.dirname(path.resolve(params.storePath));
const restrictToStoreDir = params.restrictToStoreDir === true;
const removed = [];
const sqliteMarker = parseSqliteSessionFileMarker(sessionFile);
if (sqliteMarker && (sqliteMarker.sessionId !== params.sessionId || path.resolve(sqliteMarker.storePath) !== path.resolve(params.storePath))) return [];
const pointerPath = sqliteMarker ? void 0 : resolveTrajectoryPointerFilePath(sessionFile);
const pointer = pointerPath ? readTrajectoryPointerFile(pointerPath, params.sessionId) : null;
const defaultRuntimePath = resolveTrajectoryFilePath({
env: {},
sessionFile,
sessionId: params.sessionId
});
const runtimeCandidates = /* @__PURE__ */ new Set([defaultRuntimePath]);
if (pointer?.runtimeFile) runtimeCandidates.add(pointer.runtimeFile);
for (const runtimePath of runtimeCandidates) {
if (!mayRemoveRuntimeTarget({
defaultRuntimePath,
filePath: runtimePath,
sessionId: params.sessionId,
storeDir,
restrictToStoreDir
})) continue;
const deleted = await removeRegularFile(runtimePath, "runtime");
if (deleted) removed.push(deleted);
}
if (pointerPath && (!restrictToStoreDir || isPathWithinDir(storeDir, pointerPath))) {
const deletedPointer = await removeRegularFile(pointerPath, "pointer");
if (deletedPointer) removed.push(deletedPointer);
}
return removed;
}
async function removeRemovedSessionTrajectoryArtifacts(params) {
const removed = [];
for (const [sessionId, sessionFile] of params.removedSessionFiles) {
if (params.referencedSessionIds.has(sessionId)) continue;
removed.push(...await removeSessionTrajectoryArtifacts({
sessionId,
sessionFile,
storePath: params.storePath,
restrictToStoreDir: params.restrictToStoreDir
}));
}
return removed;
}
//#endregion
export { removeRemovedSessionTrajectoryArtifacts, removeSessionTrajectoryArtifacts };