UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

88 lines (87 loc) • 3.75 kB
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 };