openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
128 lines (127 loc) • 5.82 kB
JavaScript
import { t as hasErrnoCode } from "./errno-CkbDOfLk.js";
import { m as shortenHomePath } from "./utils-P__uGsPB.js";
import { w as resolveStateDir } from "./paths-D2sRr1a_.js";
import { t as formatErrorMessage } from "./errors-Db3Ymjlb.js";
import { y as listOpenClawRegisteredAgentDatabases } from "./openclaw-agent-db-maintenance-wTIy-jt-.js";
import "./openclaw-agent-db-CWtDoRbC.js";
import { t as note } from "./note-DqHk3fA1.js";
import { n as deleteSessionCostUsageRollupsExcept } from "./session-cost-usage-cache.sqlite-CcTxDOOr.js";
import { i as maybeScrubConfigAuditLog } from "./doctor-config-audit-scrub-B5sLXutL.js";
import { t as runDoctorAgentDatabaseOperation } from "./doctor-agent-database-operation-DzkjHFCH.js";
import path from "node:path";
import fs from "node:fs/promises";
import os from "node:os";
//#region src/commands/doctor-usage-cost-cache.ts
/** Doctor cleanup for rebuildable legacy usage-cost cache sidecars. */
const LEGACY_USAGE_COST_TEMP_GRACE_MS = 1e4;
async function readFilesystemEntryOrMissing(filePath, read) {
try {
return await read();
} catch (error) {
if (hasErrnoCode(error, "ENOENT")) return null;
throw new Error(`${shortenHomePath(filePath)}: ${formatErrorMessage(error)}`, { cause: error });
}
}
function isLegacyUsageCostCacheTempName(name) {
return /^\.usage-cost-cache\.\d+\.[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}\.tmp$/u.test(name) || /^\.usage-cost-cache(?:\.json)?\.\d+\.tmp$/u.test(name) || /^\.usage-cost-cache\.json\.lock\.\d+(?:\.\d+)?\.tmp$/u.test(name);
}
async function detectLegacyUsageCostCacheFiles(params) {
const stateDir = resolveStateDir(params?.env ?? process.env, params?.homedir ?? os.homedir);
const sessionDirs = [path.join(stateDir, "sessions")];
const agentsDir = path.join(stateDir, "agents");
const agentEntries = await readFilesystemEntryOrMissing(agentsDir, () => fs.readdir(agentsDir, { withFileTypes: true })) ?? [];
for (const entry of agentEntries) if (entry.isDirectory()) sessionDirs.push(path.join(agentsDir, entry.name, "sessions"));
const files = [];
for (const sessionDir of sessionDirs) {
const entries = await readFilesystemEntryOrMissing(sessionDir, () => fs.readdir(sessionDir, { withFileTypes: true })) ?? [];
for (const entry of entries) {
if (!entry.isFile()) continue;
const filePath = path.join(sessionDir, entry.name);
if (entry.name === ".usage-cost-cache.json" || entry.name === ".usage-cost-cache.json.lock") {
files.push(filePath);
continue;
}
if (isLegacyUsageCostCacheTempName(entry.name)) {
const stats = await readFilesystemEntryOrMissing(filePath, () => fs.stat(filePath));
if (stats && Date.now() - stats.mtimeMs >= LEGACY_USAGE_COST_TEMP_GRACE_MS) files.push(filePath);
}
}
}
return files.toSorted();
}
async function maybeRemoveLegacyUsageCostCacheFiles(params) {
const files = await detectLegacyUsageCostCacheFiles(params).catch((error) => {
const command = params.shouldRepair ? "openclaw doctor --fix" : "openclaw doctor";
const action = params.shouldRepair ? "scan and cleanup" : "scan";
note([
`Legacy usage-cost cache ${action} could not be completed; ${params.shouldRepair ? "no sidecar files were removed" : "cache state may remain uninspected"}.`,
`- ${formatErrorMessage(error)}`,
`Resolve the filesystem error and rerun \`${command}\`.`
].join("\n"), "Usage cost cache");
return null;
});
if (!files) return;
if (files.length === 0) return;
if (!params.shouldRepair) {
note(`${files.length} rebuildable usage-cost cache ${files.length === 1 ? "file remains" : "files remain"}. Run \`openclaw doctor --fix\` to remove ${files.length === 1 ? "it" : "them"}.`, "Usage cost cache");
return;
}
const failures = [];
for (const filePath of files) await fs.rm(filePath, { force: true }).catch((error) => {
failures.push(`${filePath}: ${String(error)}`);
});
if (failures.length > 0) {
note(`Failed removing legacy usage-cost cache files:\n${failures.join("\n")}`, "Usage cost cache");
return;
}
note(`Removed ${files.length} rebuildable legacy usage-cost cache ${files.length === 1 ? "file" : "files"}; SQLite rebuilds the cache on demand.`, "Usage cost cache");
}
async function maybeRemoveLegacySkillUploadTree(params) {
const stateDir = resolveStateDir(params.env ?? process.env, params.homedir ?? os.homedir);
const uploadRoot = path.join(stateDir, "tmp", "skill-uploads");
const stats = await fs.lstat(uploadRoot).catch(() => null);
if (!stats) return;
if (!params.shouldRepair) {
note("Legacy skill-upload staging remains. Run `openclaw doctor --fix` to discard it; active uploads now live in SQLite and must be retried.", "Skill uploads");
return;
}
try {
if (stats.isSymbolicLink()) await fs.unlink(uploadRoot);
else await fs.rm(uploadRoot, {
recursive: true,
force: true
});
} catch (error) {
note(`Failed removing legacy skill-upload staging: ${String(error)}`, "Skill uploads");
return;
}
note("Removed legacy skill-upload staging; unfinished transient uploads must be retried.", "Skill uploads");
}
async function maybeRepairLegacyRuntimeFiles(shouldRepair, env) {
await maybeScrubConfigAuditLog({
shouldRepair,
env
});
await maybeRemoveLegacyUsageCostCacheFiles({
shouldRepair,
env
});
if (shouldRepair) {
for (const entry of listOpenClawRegisteredAgentDatabases({ env })) if ((await fs.stat(entry.path).catch(() => null))?.isFile()) runDoctorAgentDatabaseOperation({
agentId: entry.agentId,
path: entry.path,
run: () => deleteSessionCostUsageRollupsExcept({
agentId: entry.agentId,
databasePath: entry.path,
liveKeys: /* @__PURE__ */ new Set(),
rows: []
})
});
}
await maybeRemoveLegacySkillUploadTree({
shouldRepair,
env
});
}
//#endregion
export { maybeRepairLegacyRuntimeFiles };