openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
88 lines (87 loc) • 3.75 kB
JavaScript
import { n as resolvePreferredOpenClawTmpDir } from "./tmp-openclaw-dir-DR7bHNl9.mjs";
import { t as SESSION_ARCHIVE_ZSTD_SUFFIX } from "./transcript-file-names-BZ8jA63H.mjs";
import { t as resolveZstdCodec } from "./zstd-codec-C-vEb3Qf.mjs";
import fs from "node:fs";
import path from "node:path";
import { createHash, randomUUID } from "node:crypto";
import { walkDirectorySync } from "@openclaw/fs-safe/walk";
//#region src/config/sessions/archive-compression.ts
const zstdCodec = resolveZstdCodec();
function encodeSessionArchiveContent(content) {
const plain = Buffer.from(content, "utf8");
if (!zstdCodec || plain.length === 0) return {
bytes: plain,
suffix: ""
};
return {
bytes: zstdCodec.compress(plain),
suffix: SESSION_ARCHIVE_ZSTD_SUFFIX
};
}
function readSessionArchiveContentSync(filePath) {
if (!filePath.endsWith(".zst")) return fs.readFileSync(filePath, "utf8");
if (!zstdCodec) throw new Error(`Cannot read compressed transcript archive ${filePath}: this runtime lacks node:zlib zstd support`);
return zstdCodec.decompress(fs.readFileSync(filePath)).toString("utf8");
}
function decodeSessionArchiveBytes(bytes, compressed) {
if (!compressed) return Buffer.from(bytes).toString("utf8");
if (!zstdCodec) throw new Error("Cannot decode compressed transcript archive: this runtime lacks zstd support");
return zstdCodec.decompress(Buffer.from(bytes)).toString("utf8");
}
/**
* Materializes a compressed archive as a plain JSONL cache file and returns
* the readable path; plain archives pass through untouched. Source identity
* validates cached bytes so downstream readers need not handle compression.
*/
function materializeSessionArchiveForRead(filePath) {
if (!filePath.endsWith(".zst")) return filePath;
const cacheDir = path.join(resolvePreferredOpenClawTmpDir(), "session-archive-read-cache");
const pathKey = createHash("sha256").update(filePath).digest("hex").slice(0, 32);
let sourceStat;
try {
sourceStat = fs.statSync(filePath);
} catch (error) {
removeMaterializedArchiveCacheEntries(cacheDir, pathKey);
throw error;
}
const cachePath = path.join(cacheDir, `${pathKey}-${sourceStat.size}-${Math.trunc(sourceStat.mtimeMs)}.jsonl`);
sweepMaterializedArchiveCache(cacheDir);
if (fs.existsSync(cachePath)) return cachePath;
const content = readSessionArchiveContentSync(filePath);
removeMaterializedArchiveCacheEntries(cacheDir, pathKey, path.basename(cachePath));
fs.mkdirSync(cacheDir, {
recursive: true,
mode: 448
});
const tempPath = `${cachePath}.${process.pid}.${randomUUID()}.tmp`;
fs.writeFileSync(tempPath, content, {
encoding: "utf8",
mode: 384
});
fs.renameSync(tempPath, cachePath);
return cachePath;
}
const MATERIALIZED_ARCHIVE_CACHE_TTL_MS = 864e5;
let lastMaterializedArchiveCacheSweepMs = 0;
function sweepMaterializedArchiveCache(cacheDir) {
const now = Date.now();
if (now - lastMaterializedArchiveCacheSweepMs < MATERIALIZED_ARCHIVE_CACHE_TTL_MS / 24) return;
lastMaterializedArchiveCacheSweepMs = now;
for (const entry of walkDirectorySync(cacheDir, {
maxDepth: 1,
symlinks: "include"
}).entries) try {
if (now - fs.statSync(entry.path).mtimeMs > MATERIALIZED_ARCHIVE_CACHE_TTL_MS) fs.rmSync(entry.path, { force: true });
} catch {}
}
function removeMaterializedArchiveCacheEntries(cacheDir, pathKey, keepName) {
for (const entry of walkDirectorySync(cacheDir, {
maxDepth: 1,
symlinks: "include"
}).entries) {
if (!entry.name.startsWith(`${pathKey}-`) || entry.name === keepName || entry.name.endsWith(".tmp")) continue;
fs.rmSync(entry.path, { force: true });
}
}
//#endregion
export { readSessionArchiveContentSync as i, encodeSessionArchiveContent as n, materializeSessionArchiveForRead as r, decodeSessionArchiveBytes as t };