openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
778 lines (777 loc) • 35.2 kB
JavaScript
import { c as redactSensitiveText } from "./redact-DduLliKq.js";
import { y as parseStrictNonNegativeInteger } from "./number-coercion-CJQ8TR--.js";
import "./parse-finite-number-Z7n6tXLk.js";
import { y as resolveStateDir } from "./paths-mvMm5bYV.js";
import { t as registerFatalErrorHook } from "./fatal-error-hooks-Cu2jsdBV.js";
import { i as replaceFileAtomicSync } from "./replace-file-BrS02dAb.js";
import { n as getDiagnosticStabilitySnapshot, t as MAX_DIAGNOSTIC_STABILITY_LIMIT } from "./diagnostic-stability-DLBSfQx0.js";
import process from "node:process";
import fs from "node:fs";
import path from "node:path";
import v8 from "node:v8";
//#region src/logging/diagnostic-stability-bundle.ts
const DIAGNOSTIC_STABILITY_BUNDLE_VERSION = 1;
const DEFAULT_DIAGNOSTIC_STABILITY_BUNDLE_LIMIT = MAX_DIAGNOSTIC_STABILITY_LIMIT;
const DEFAULT_DIAGNOSTIC_STABILITY_BUNDLE_RETENTION = 20;
const MAX_DIAGNOSTIC_STABILITY_BUNDLE_BYTES = 5 * 1024 * 1024;
const SAFE_REASON_CODE = /^[A-Za-z0-9_.:-]{1,120}$/u;
const BUNDLE_PREFIX = "openclaw-stability-";
const BUNDLE_SUFFIX = ".json";
const REDACTED_HOSTNAME = "<redacted-hostname>";
const MAX_SAFE_ERROR_MESSAGE_LENGTH = 500;
const MAX_ACTIVE_RESOURCE_TYPES = 25;
const MAX_SESSION_FILE_RESULTS = 20;
const MAX_SESSION_SCAN_AGENTS = 100;
const MAX_SESSION_SCAN_FILES = 5e3;
const CGROUP_V2_MEMORY_FILES = [
"current",
"max",
"high",
"peak",
"swap.current",
"swap.max"
];
const CGROUP_V2_MEMORY_EVENTS = ["events", "events.local"];
let fatalHookUnsubscribe = null;
function normalizeReason(reason) {
return SAFE_REASON_CODE.test(reason) ? reason : "unknown";
}
function formatBundleTimestamp(now) {
return now.toISOString().replace(/[:.]/g, "-");
}
function readErrorCode(error) {
if (!error || typeof error !== "object" || !("code" in error)) return;
const code = error.code;
if (typeof code === "string" && SAFE_REASON_CODE.test(code)) return code;
if (typeof code === "number" && Number.isFinite(code)) return String(code);
}
function readErrorName(error) {
if (!error || typeof error !== "object" || !("name" in error)) return;
const name = error.name;
return typeof name === "string" && SAFE_REASON_CODE.test(name) ? name : void 0;
}
function readErrorMessage(error) {
if (!error || typeof error !== "object" || !("message" in error)) return;
const message = error.message;
if (typeof message !== "string") return;
const sanitized = redactSensitiveText(message, { mode: "tools" }).replace(/\s+/gu, " ").trim();
if (!sanitized) return;
return sanitized.length > MAX_SAFE_ERROR_MESSAGE_LENGTH ? `${sanitized.slice(0, MAX_SAFE_ERROR_MESSAGE_LENGTH)}...` : sanitized;
}
function readSafeErrorMetadata(error) {
const name = readErrorName(error);
const code = readErrorCode(error);
const message = readErrorMessage(error);
if (!name && !code && !message) return;
return {
...name ? { name } : {},
...code ? { code } : {},
...message ? { message } : {}
};
}
function resolveDiagnosticStabilityBundleDir(options = {}) {
return path.join(options.stateDir ?? resolveStateDir(options.env ?? process.env), "logs", "stability");
}
function buildBundlePath(dir, now, reason) {
return path.join(dir, `${BUNDLE_PREFIX}${formatBundleTimestamp(now)}-${process.pid}-${normalizeReason(reason)}${BUNDLE_SUFFIX}`);
}
function isBundleFile(name) {
return name.startsWith(BUNDLE_PREFIX) && name.endsWith(BUNDLE_SUFFIX);
}
function isMissingFileError(error) {
return typeof error === "object" && error !== null && "code" in error && error.code === "ENOENT";
}
function readObject(value, label) {
if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error(`Invalid stability bundle: ${label} must be an object`);
return value;
}
function readNumber(value, label) {
if (typeof value !== "number" || !Number.isFinite(value)) throw new Error(`Invalid stability bundle: ${label} must be a finite number`);
return value;
}
function readOptionalPositiveInteger(value, label) {
if (value === void 0) return;
const parsed = readNumber(value, label);
return parsed >= 0 ? Math.floor(parsed) : void 0;
}
function readTimestampMs(value, label) {
const timestamp = readNumber(value, label);
if (Number.isNaN(new Date(timestamp).getTime())) throw new Error(`Invalid stability bundle: ${label} must be a valid timestamp`);
return timestamp;
}
function readOptionalNumber(value, label) {
if (value === void 0) return;
return readNumber(value, label);
}
function readString(value, label) {
if (typeof value !== "string") throw new Error(`Invalid stability bundle: ${label} must be a string`);
return value;
}
function readTimestampString(value, label) {
const timestamp = readString(value, label);
if (Number.isNaN(new Date(timestamp).getTime())) throw new Error(`Invalid stability bundle: ${label} must be a valid timestamp`);
return timestamp;
}
function readCodeString(value, label) {
const code = readString(value, label);
if (!SAFE_REASON_CODE.test(code)) throw new Error(`Invalid stability bundle: ${label} must be a safe diagnostic code`);
return code;
}
function readOptionalCodeString(value, label) {
if (value === void 0) return;
const code = readString(value, label);
return SAFE_REASON_CODE.test(code) ? code : void 0;
}
function assignOptionalNumber(target, key, value, label) {
const parsed = readOptionalNumber(value, label);
if (parsed !== void 0) target[key] = parsed;
}
function assignOptionalPositiveInteger(target, key, value, label) {
const parsed = readOptionalPositiveInteger(value, label);
if (parsed !== void 0) target[key] = parsed;
}
function assignOptionalCodeString(target, key, value, label) {
const parsed = readOptionalCodeString(value, label);
if (parsed !== void 0) target[key] = parsed;
}
function readMemoryUsage(value, label) {
const memory = readObject(value, label);
return {
rssBytes: readNumber(memory.rssBytes, `${label}.rssBytes`),
heapTotalBytes: readNumber(memory.heapTotalBytes, `${label}.heapTotalBytes`),
heapUsedBytes: readNumber(memory.heapUsedBytes, `${label}.heapUsedBytes`),
externalBytes: readNumber(memory.externalBytes, `${label}.externalBytes`),
arrayBuffersBytes: readNumber(memory.arrayBuffersBytes, `${label}.arrayBuffersBytes`)
};
}
function readHeapStatistics(value) {
if (value === void 0) return;
const source = readObject(value, "evidence.memoryPressure.heapStatistics");
const result = {};
assignOptionalPositiveInteger(result, "totalHeapSizeBytes", source.totalHeapSizeBytes, "evidence.memoryPressure.heapStatistics.totalHeapSizeBytes");
assignOptionalPositiveInteger(result, "totalHeapSizeExecutableBytes", source.totalHeapSizeExecutableBytes, "evidence.memoryPressure.heapStatistics.totalHeapSizeExecutableBytes");
assignOptionalPositiveInteger(result, "totalPhysicalSizeBytes", source.totalPhysicalSizeBytes, "evidence.memoryPressure.heapStatistics.totalPhysicalSizeBytes");
assignOptionalPositiveInteger(result, "totalAvailableSizeBytes", source.totalAvailableSizeBytes, "evidence.memoryPressure.heapStatistics.totalAvailableSizeBytes");
assignOptionalPositiveInteger(result, "usedHeapSizeBytes", source.usedHeapSizeBytes, "evidence.memoryPressure.heapStatistics.usedHeapSizeBytes");
assignOptionalPositiveInteger(result, "heapSizeLimitBytes", source.heapSizeLimitBytes, "evidence.memoryPressure.heapStatistics.heapSizeLimitBytes");
assignOptionalPositiveInteger(result, "mallocedMemoryBytes", source.mallocedMemoryBytes, "evidence.memoryPressure.heapStatistics.mallocedMemoryBytes");
assignOptionalPositiveInteger(result, "externalMemoryBytes", source.externalMemoryBytes, "evidence.memoryPressure.heapStatistics.externalMemoryBytes");
return Object.keys(result).length > 0 ? result : void 0;
}
function readHeapSpaces(value) {
if (value === void 0) return;
if (!Array.isArray(value)) throw new Error("Invalid stability bundle: evidence.memoryPressure.heapSpaces must be an array");
const spaces = [];
for (const [index, entry] of value.entries()) {
const source = readObject(entry, `evidence.memoryPressure.heapSpaces[${index}]`);
const spaceName = readOptionalCodeString(source.spaceName, `evidence.memoryPressure.heapSpaces[${index}].spaceName`);
if (!spaceName) continue;
spaces.push({
spaceName,
spaceSizeBytes: readOptionalPositiveInteger(source.spaceSizeBytes, `evidence.memoryPressure.heapSpaces[${index}].spaceSizeBytes`) ?? 0,
spaceUsedBytes: readOptionalPositiveInteger(source.spaceUsedBytes, `evidence.memoryPressure.heapSpaces[${index}].spaceUsedBytes`) ?? 0,
spaceAvailableBytes: readOptionalPositiveInteger(source.spaceAvailableBytes, `evidence.memoryPressure.heapSpaces[${index}].spaceAvailableBytes`) ?? 0,
physicalSpaceSizeBytes: readOptionalPositiveInteger(source.physicalSpaceSizeBytes, `evidence.memoryPressure.heapSpaces[${index}].physicalSpaceSizeBytes`) ?? 0
});
}
return spaces.length > 0 ? spaces : void 0;
}
function readCgroupMemorySummary(value) {
if (value === void 0) return;
const source = readObject(value, "evidence.memoryPressure.cgroup");
const version = readCodeString(source.version, "evidence.memoryPressure.cgroup.version");
if (version !== "v2") return;
const valuesSource = readObject(source.values, "evidence.memoryPressure.cgroup.values");
const values = {};
for (const [key, raw] of Object.entries(valuesSource)) {
if (!SAFE_REASON_CODE.test(key)) continue;
if (raw === "max") values[key] = "max";
else values[key] = readOptionalPositiveInteger(raw, `evidence.memoryPressure.cgroup.values.${key}`) ?? 0;
}
return {
version,
values,
events: readNumberMap(source.events, "evidence.memoryPressure.cgroup.events")
};
}
function readActiveResources(value) {
if (value === void 0) return;
const source = readObject(value, "evidence.memoryPressure.activeResources");
return {
total: readOptionalPositiveInteger(source.total, "evidence.memoryPressure.activeResources.total") ?? 0,
byType: readNumberMap(source.byType, "evidence.memoryPressure.activeResources.byType")
};
}
function readSessionFiles(value) {
if (value === void 0) return;
if (!Array.isArray(value)) throw new Error("Invalid stability bundle: evidence.memoryPressure.topSessionFiles must be an array");
const files = [];
for (const [index, entry] of value.entries()) {
const source = readObject(entry, `evidence.memoryPressure.topSessionFiles[${index}]`);
const relativePath = readString(source.relativePath, `evidence.memoryPressure.topSessionFiles[${index}].relativePath`);
if (path.isAbsolute(relativePath) || relativePath.includes("..") || relativePath.length > 300 || /[\r\n]/u.test(relativePath)) continue;
files.push({
relativePath: sanitizeSessionEvidencePath(relativePath),
sizeBytes: readOptionalPositiveInteger(source.sizeBytes, `evidence.memoryPressure.topSessionFiles[${index}].sizeBytes`) ?? 0,
mtimeMs: readOptionalPositiveInteger(source.mtimeMs, `evidence.memoryPressure.topSessionFiles[${index}].mtimeMs`) ?? 0
});
}
return files.length > 0 ? files : void 0;
}
function readMemoryPressureEvidence(value) {
if (value === void 0) return;
const pressure = readObject(value, "evidence.memoryPressure");
const level = readCodeString(pressure.level, "evidence.memoryPressure.level");
const reason = readCodeString(pressure.reason, "evidence.memoryPressure.reason");
if (level !== "warning" && level !== "critical" || !isMemoryPressureReason(reason)) return;
const heapStatistics = readHeapStatistics(pressure.heapStatistics);
const heapSpaces = readHeapSpaces(pressure.heapSpaces);
const cgroup = readCgroupMemorySummary(pressure.cgroup);
const activeResources = readActiveResources(pressure.activeResources);
const topSessionFiles = readSessionFiles(pressure.topSessionFiles);
return {
level,
reason,
memory: readMemoryUsage(pressure.memory, "evidence.memoryPressure.memory"),
...pressure.thresholdBytes !== void 0 ? { thresholdBytes: readNumber(pressure.thresholdBytes, "evidence.memoryPressure.thresholdBytes") } : {},
...pressure.rssGrowthBytes !== void 0 ? { rssGrowthBytes: readNumber(pressure.rssGrowthBytes, "evidence.memoryPressure.rssGrowthBytes") } : {},
...pressure.windowMs !== void 0 ? { windowMs: readNumber(pressure.windowMs, "evidence.memoryPressure.windowMs") } : {},
...heapStatistics ? { heapStatistics } : {},
...heapSpaces ? { heapSpaces } : {},
...cgroup ? { cgroup } : {},
...activeResources ? { activeResources } : {},
...topSessionFiles ? { topSessionFiles } : {}
};
}
function readBundleEvidence(value) {
if (value === void 0) return;
const memoryPressure = readMemoryPressureEvidence(readObject(value, "evidence").memoryPressure);
return memoryPressure ? { memoryPressure } : void 0;
}
function readNumberMap(value, label) {
const source = readObject(value, label);
const result = {};
for (const [key, entry] of Object.entries(source)) {
if (!SAFE_REASON_CODE.test(key)) continue;
result[key] = readNumber(entry, `${label}.${key}`);
}
return result;
}
function readOptionalMemorySummary(value) {
if (value === void 0) return;
const memory = readObject(value, "snapshot.summary.memory");
const latest = memory.latest === void 0 ? void 0 : readMemoryUsage(memory.latest, "snapshot.summary.memory.latest");
return {
...latest ? { latest } : {},
...memory.maxRssBytes !== void 0 ? { maxRssBytes: readNumber(memory.maxRssBytes, "snapshot.summary.memory.maxRssBytes") } : {},
...memory.maxHeapUsedBytes !== void 0 ? { maxHeapUsedBytes: readNumber(memory.maxHeapUsedBytes, "snapshot.summary.memory.maxHeapUsedBytes") } : {},
pressureCount: readNumber(memory.pressureCount, "snapshot.summary.memory.pressureCount")
};
}
function readOptionalPayloadLargeSummary(value) {
if (value === void 0) return;
const payloadLarge = readObject(value, "snapshot.summary.payloadLarge");
return {
count: readNumber(payloadLarge.count, "snapshot.summary.payloadLarge.count"),
rejected: readNumber(payloadLarge.rejected, "snapshot.summary.payloadLarge.rejected"),
truncated: readNumber(payloadLarge.truncated, "snapshot.summary.payloadLarge.truncated"),
chunked: readNumber(payloadLarge.chunked, "snapshot.summary.payloadLarge.chunked"),
bySurface: readNumberMap(payloadLarge.bySurface, "snapshot.summary.payloadLarge.bySurface")
};
}
function readStabilityEventRecord(value, label) {
const record = readObject(value, label);
const sanitized = {
seq: readNumber(record.seq, `${label}.seq`),
ts: readTimestampMs(record.ts, `${label}.ts`),
type: readCodeString(record.type, `${label}.type`)
};
assignOptionalCodeString(sanitized, "channel", record.channel, `${label}.channel`);
assignOptionalCodeString(sanitized, "pluginId", record.pluginId, `${label}.pluginId`);
assignOptionalCodeString(sanitized, "source", record.source, `${label}.source`);
assignOptionalCodeString(sanitized, "surface", record.surface, `${label}.surface`);
assignOptionalCodeString(sanitized, "action", record.action, `${label}.action`);
assignOptionalCodeString(sanitized, "reason", record.reason, `${label}.reason`);
assignOptionalCodeString(sanitized, "outcome", record.outcome, `${label}.outcome`);
assignOptionalCodeString(sanitized, "level", record.level, `${label}.level`);
assignOptionalCodeString(sanitized, "phase", record.phase, `${label}.phase`);
assignOptionalCodeString(sanitized, "detector", record.detector, `${label}.detector`);
assignOptionalCodeString(sanitized, "toolName", record.toolName, `${label}.toolName`);
assignOptionalCodeString(sanitized, "activeWorkKind", record.activeWorkKind, `${label}.activeWorkKind`);
assignOptionalCodeString(sanitized, "pairedToolName", record.pairedToolName, `${label}.pairedToolName`);
assignOptionalCodeString(sanitized, "provider", record.provider, `${label}.provider`);
assignOptionalCodeString(sanitized, "model", record.model, `${label}.model`);
assignOptionalNumber(sanitized, "durationMs", record.durationMs, `${label}.durationMs`);
assignOptionalNumber(sanitized, "requestBytes", record.requestBytes, `${label}.requestBytes`);
assignOptionalNumber(sanitized, "responseBytes", record.responseBytes, `${label}.responseBytes`);
assignOptionalNumber(sanitized, "timeToFirstByteMs", record.timeToFirstByteMs, `${label}.timeToFirstByteMs`);
assignOptionalNumber(sanitized, "costUsd", record.costUsd, `${label}.costUsd`);
assignOptionalNumber(sanitized, "count", record.count, `${label}.count`);
assignOptionalNumber(sanitized, "bytes", record.bytes, `${label}.bytes`);
assignOptionalNumber(sanitized, "limitBytes", record.limitBytes, `${label}.limitBytes`);
assignOptionalNumber(sanitized, "thresholdBytes", record.thresholdBytes, `${label}.thresholdBytes`);
assignOptionalNumber(sanitized, "rssGrowthBytes", record.rssGrowthBytes, `${label}.rssGrowthBytes`);
assignOptionalNumber(sanitized, "windowMs", record.windowMs, `${label}.windowMs`);
assignOptionalNumber(sanitized, "ageMs", record.ageMs, `${label}.ageMs`);
assignOptionalNumber(sanitized, "queueDepth", record.queueDepth, `${label}.queueDepth`);
assignOptionalNumber(sanitized, "queueSize", record.queueSize, `${label}.queueSize`);
assignOptionalNumber(sanitized, "queueLength", record.queueLength, `${label}.queueLength`);
assignOptionalNumber(sanitized, "waitMs", record.waitMs, `${label}.waitMs`);
assignOptionalNumber(sanitized, "active", record.active, `${label}.active`);
assignOptionalNumber(sanitized, "waiting", record.waiting, `${label}.waiting`);
assignOptionalNumber(sanitized, "queued", record.queued, `${label}.queued`);
assignOptionalNumber(sanitized, "droppedEvents", record.droppedEvents, `${label}.droppedEvents`);
assignOptionalNumber(sanitized, "droppedTrustedEvents", record.droppedTrustedEvents, `${label}.droppedTrustedEvents`);
assignOptionalNumber(sanitized, "droppedUntrustedEvents", record.droppedUntrustedEvents, `${label}.droppedUntrustedEvents`);
assignOptionalNumber(sanitized, "droppedPriorityEvents", record.droppedPriorityEvents, `${label}.droppedPriorityEvents`);
assignOptionalNumber(sanitized, "maxQueueLength", record.maxQueueLength, `${label}.maxQueueLength`);
assignOptionalNumber(sanitized, "drainBatchSize", record.drainBatchSize, `${label}.drainBatchSize`);
if (record.webhooks !== void 0) {
const webhooks = readObject(record.webhooks, `${label}.webhooks`);
sanitized.webhooks = {
received: readNumber(webhooks.received, `${label}.webhooks.received`),
processed: readNumber(webhooks.processed, `${label}.webhooks.processed`),
errors: readNumber(webhooks.errors, `${label}.webhooks.errors`)
};
}
if (record.memory !== void 0) sanitized.memory = readMemoryUsage(record.memory, `${label}.memory`);
if (record.usage !== void 0) {
const usage = readObject(record.usage, `${label}.usage`);
sanitized.usage = {
...usage.input !== void 0 ? { input: readNumber(usage.input, `${label}.usage.input`) } : {},
...usage.output !== void 0 ? { output: readNumber(usage.output, `${label}.usage.output`) } : {},
...usage.cacheRead !== void 0 ? { cacheRead: readNumber(usage.cacheRead, `${label}.usage.cacheRead`) } : {},
...usage.cacheWrite !== void 0 ? { cacheWrite: readNumber(usage.cacheWrite, `${label}.usage.cacheWrite`) } : {},
...usage.promptTokens !== void 0 ? { promptTokens: readNumber(usage.promptTokens, `${label}.usage.promptTokens`) } : {},
...usage.total !== void 0 ? { total: readNumber(usage.total, `${label}.usage.total`) } : {}
};
}
if (record.context !== void 0) {
const context = readObject(record.context, `${label}.context`);
sanitized.context = {
...context.limit !== void 0 ? { limit: readNumber(context.limit, `${label}.context.limit`) } : {},
...context.used !== void 0 ? { used: readNumber(context.used, `${label}.context.used`) } : {}
};
}
return sanitized;
}
function readStabilitySnapshot(value) {
const snapshot = readObject(value, "snapshot");
const generatedAt = readTimestampString(snapshot.generatedAt, "snapshot.generatedAt");
const capacity = readNumber(snapshot.capacity, "snapshot.capacity");
const count = readNumber(snapshot.count, "snapshot.count");
const dropped = readNumber(snapshot.dropped, "snapshot.dropped");
const firstSeq = readOptionalNumber(snapshot.firstSeq, "snapshot.firstSeq");
const lastSeq = readOptionalNumber(snapshot.lastSeq, "snapshot.lastSeq");
if (!Array.isArray(snapshot.events)) throw new Error("Invalid stability bundle: snapshot.events must be an array");
const events = snapshot.events.map((event, index) => readStabilityEventRecord(event, `snapshot.events[${index}]`));
const summary = readObject(snapshot.summary, "snapshot.summary");
return {
generatedAt,
capacity,
count,
dropped,
...firstSeq !== void 0 ? { firstSeq } : {},
...lastSeq !== void 0 ? { lastSeq } : {},
events,
summary: {
byType: readNumberMap(summary.byType, "snapshot.summary.byType"),
...summary.memory !== void 0 ? { memory: readOptionalMemorySummary(summary.memory) } : {},
...summary.payloadLarge !== void 0 ? { payloadLarge: readOptionalPayloadLargeSummary(summary.payloadLarge) } : {}
}
};
}
function parseDiagnosticStabilityBundle(value) {
const bundle = readObject(value, "bundle");
if (bundle.version !== 1) throw new Error(`Unsupported stability bundle version: ${String(bundle.version)}`);
const processInfo = readObject(bundle.process, "process");
readObject(bundle.host, "host");
const error = bundle.error === void 0 ? void 0 : readSafeErrorMetadata(bundle.error);
const evidence = readBundleEvidence(bundle.evidence);
return {
version: 1,
generatedAt: readTimestampString(bundle.generatedAt, "generatedAt"),
reason: normalizeReason(readString(bundle.reason, "reason")),
process: {
pid: readNumber(processInfo.pid, "process.pid"),
platform: readCodeString(processInfo.platform, "process.platform"),
arch: readCodeString(processInfo.arch, "process.arch"),
node: readCodeString(processInfo.node, "process.node"),
uptimeMs: readNumber(processInfo.uptimeMs, "process.uptimeMs")
},
host: { hostname: REDACTED_HOSTNAME },
...error ? { error } : {},
...evidence ? { evidence } : {},
snapshot: readStabilitySnapshot(bundle.snapshot)
};
}
function readPositiveMemoryFile(file) {
try {
const raw = fs.readFileSync(file, "utf8").trim();
if (raw === "max") return "max";
return parseStrictNonNegativeInteger(raw);
} catch {
return;
}
}
function readCgroupEventFile(file) {
try {
const events = {};
for (const line of fs.readFileSync(file, "utf8").split(/\r?\n/u)) {
const [key, raw] = line.trim().split(/\s+/u);
if (!key || !SAFE_REASON_CODE.test(key)) continue;
const value = parseStrictNonNegativeInteger(raw ?? "");
if (value !== void 0) events[key] = value;
}
return events;
} catch {
return {};
}
}
function resolveCgroupV2MemoryDir() {
if (process.platform !== "linux") return;
try {
const line = fs.readFileSync("/proc/self/cgroup", "utf8").split(/\r?\n/u).find((entry) => entry.startsWith("0::"));
if (!line) return;
const relative = line.slice(3).trim().replace(/^\/+/u, "");
return path.join("/sys/fs/cgroup", relative);
} catch {
return;
}
}
function collectCgroupMemorySummary() {
const dir = resolveCgroupV2MemoryDir();
if (!dir) return;
const values = {};
for (const name of CGROUP_V2_MEMORY_FILES) {
const value = readPositiveMemoryFile(path.join(dir, `memory.${name}`));
if (value !== void 0) values[name] = value;
}
const events = {};
for (const name of CGROUP_V2_MEMORY_EVENTS) {
const parsed = readCgroupEventFile(path.join(dir, `memory.${name}`));
for (const [key, value] of Object.entries(parsed)) events[name === "events" ? key : `${name}.${key}`] = value;
}
return Object.keys(values).length > 0 || Object.keys(events).length > 0 ? {
version: "v2",
values,
events
} : void 0;
}
function collectHeapStatistics() {
try {
const stats = v8.getHeapStatistics();
return {
totalHeapSizeBytes: stats.total_heap_size,
totalHeapSizeExecutableBytes: stats.total_heap_size_executable,
totalPhysicalSizeBytes: stats.total_physical_size,
totalAvailableSizeBytes: stats.total_available_size,
usedHeapSizeBytes: stats.used_heap_size,
heapSizeLimitBytes: stats.heap_size_limit,
mallocedMemoryBytes: stats.malloced_memory,
externalMemoryBytes: stats.external_memory
};
} catch {
return;
}
}
function collectHeapSpaces() {
try {
const spaces = v8.getHeapSpaceStatistics().map((space) => ({
spaceName: space.space_name,
spaceSizeBytes: space.space_size,
spaceUsedBytes: space.space_used_size,
spaceAvailableBytes: space.space_available_size,
physicalSpaceSizeBytes: space.physical_space_size
}));
return spaces.length > 0 ? spaces : void 0;
} catch {
return;
}
}
function collectActiveResources() {
try {
if (typeof process.getActiveResourcesInfo !== "function") return;
const names = process.getActiveResourcesInfo();
const byType = {};
for (const name of names) {
if (!SAFE_REASON_CODE.test(name)) continue;
byType[name] = (byType[name] ?? 0) + 1;
}
const sorted = Object.entries(byType).toSorted((a, b) => b[1] - a[1] || a[0].localeCompare(b[0])).slice(0, MAX_ACTIVE_RESOURCE_TYPES);
return {
total: names.length,
byType: Object.fromEntries(sorted)
};
} catch {
return;
}
}
function sanitizeSessionEvidencePath(relativePath) {
const parts = relativePath.split("/");
if (parts.length === 4 && parts[0] === "agents" && parts[2] === "sessions") return `agents/<agent>/sessions/${sanitizeSessionEvidenceFileName(parts[3])}`;
if (parts.length === 2 && parts[0] === "sessions") return `sessions/${sanitizeSessionEvidenceFileName(parts[1])}`;
return redactSensitiveText(relativePath, { mode: "tools" });
}
function sanitizeSessionEvidenceFileName(fileName) {
if (fileName === "sessions.json") return "sessions.json";
if (fileName.endsWith(".jsonl")) return "<session>.jsonl";
if (fileName.endsWith(".json")) return "<session>.json";
return "<session>";
}
function visitDirentsBounded(dir, maxEntries, visitor) {
if (maxEntries <= 0) return;
let handle;
try {
handle = fs.opendirSync(dir);
for (let count = 0; count < maxEntries; count += 1) {
const entry = handle.readSync();
if (!entry || visitor(entry) === false) return;
}
} catch {} finally {
try {
handle?.closeSync();
} catch {}
}
}
function pushSessionFileSummary(results, stateDir, file, relativePathOverride) {
try {
const stat = fs.statSync(file);
if (!stat.isFile()) return;
const relativePath = (relativePathOverride ?? path.relative(stateDir, file)).replace(/\\/gu, "/");
if (relativePath.startsWith("../") || path.isAbsolute(relativePath)) return;
results.push({
relativePath: sanitizeSessionEvidencePath(relativePath),
sizeBytes: stat.size,
mtimeMs: stat.mtimeMs
});
} catch {}
}
function scanSessionDirectory(params) {
const sessionsDir = path.resolve(params.sessionsDir);
if (params.seenDirs.has(sessionsDir)) return;
params.seenDirs.add(sessionsDir);
visitDirentsBounded(sessionsDir, MAX_SESSION_SCAN_FILES - params.scannedSessionEntries.count, (sessionEntry) => {
params.scannedSessionEntries.count += 1;
if (!sessionEntry.isFile() || !/\.(?:jsonl|json)$/u.test(sessionEntry.name)) return params.scannedSessionEntries.count < MAX_SESSION_SCAN_FILES;
pushSessionFileSummary(params.results, params.stateDir, path.join(sessionsDir, sessionEntry.name), path.posix.join(params.relativePrefix, sessionEntry.name));
return params.scannedSessionEntries.count < MAX_SESSION_SCAN_FILES;
});
}
function collectTopSessionFiles(stateDir, sessionStorePaths = []) {
const results = [];
const seenDirs = /* @__PURE__ */ new Set();
const scannedSessionEntries = { count: 0 };
try {
pushSessionFileSummary(results, stateDir, path.join(stateDir, "sessions.json"));
const agentsDir = path.join(stateDir, "agents");
visitDirentsBounded(agentsDir, MAX_SESSION_SCAN_AGENTS, (agentEntry) => {
if (!agentEntry.isDirectory() || scannedSessionEntries.count >= MAX_SESSION_SCAN_FILES) return;
scanSessionDirectory({
results,
stateDir,
sessionsDir: path.join(agentsDir, agentEntry.name, "sessions"),
relativePrefix: path.posix.join("agents", agentEntry.name, "sessions"),
seenDirs,
scannedSessionEntries
});
});
for (const storePath of sessionStorePaths) {
if (scannedSessionEntries.count >= MAX_SESSION_SCAN_FILES) break;
scanSessionDirectory({
results,
stateDir,
sessionsDir: path.dirname(path.resolve(storePath)),
relativePrefix: "sessions",
seenDirs,
scannedSessionEntries
});
}
} catch {}
const top = results.toSorted((a, b) => b.sizeBytes - a.sizeBytes || a.relativePath.localeCompare(b.relativePath)).slice(0, MAX_SESSION_FILE_RESULTS);
return top.length > 0 ? top : void 0;
}
function buildMemoryPressureEvidence(options) {
const stateDir = options.stateDir ?? resolveStateDir(options.env ?? process.env);
const heapStatistics = collectHeapStatistics();
const heapSpaces = collectHeapSpaces();
const cgroup = collectCgroupMemorySummary();
const activeResources = collectActiveResources();
const topSessionFiles = collectTopSessionFiles(stateDir, options.sessionStorePaths);
return { memoryPressure: {
level: options.pressure.level,
reason: options.pressure.reason,
memory: options.pressure.memory,
...options.pressure.thresholdBytes !== void 0 ? { thresholdBytes: options.pressure.thresholdBytes } : {},
...options.pressure.rssGrowthBytes !== void 0 ? { rssGrowthBytes: options.pressure.rssGrowthBytes } : {},
...options.pressure.windowMs !== void 0 ? { windowMs: options.pressure.windowMs } : {},
...heapStatistics ? { heapStatistics } : {},
...heapSpaces ? { heapSpaces } : {},
...cgroup ? { cgroup } : {},
...activeResources ? { activeResources } : {},
...topSessionFiles ? { topSessionFiles } : {}
} };
}
function isMemoryPressureReason(reason) {
return reason === "rss_threshold" || reason === "heap_threshold" || reason === "rss_growth";
}
function listDiagnosticStabilityBundleFilesSync(options = {}) {
const dir = resolveDiagnosticStabilityBundleDir(options);
try {
return fs.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && isBundleFile(entry.name)).map((entry) => {
const file = path.join(dir, entry.name);
return {
path: file,
mtimeMs: fs.statSync(file).mtimeMs
};
}).toSorted((a, b) => b.mtimeMs - a.mtimeMs || b.path.localeCompare(a.path));
} catch (error) {
if (isMissingFileError(error)) return [];
throw error;
}
}
function readDiagnosticStabilityBundleFileSync(file) {
try {
const stat = fs.statSync(file);
if (stat.size > 5242880) throw new Error(`Stability bundle is too large: ${stat.size} bytes exceeds ${MAX_DIAGNOSTIC_STABILITY_BUNDLE_BYTES}`);
const raw = fs.readFileSync(file, "utf8");
const bundle = parseDiagnosticStabilityBundle(JSON.parse(raw));
return {
status: "found",
path: file,
mtimeMs: stat.mtimeMs,
bundle
};
} catch (error) {
return {
status: "failed",
path: file,
error
};
}
}
function readLatestDiagnosticStabilityBundleSync(options = {}) {
try {
const latest = listDiagnosticStabilityBundleFilesSync(options)[0];
if (!latest) return {
status: "missing",
dir: resolveDiagnosticStabilityBundleDir(options)
};
return readDiagnosticStabilityBundleFileSync(latest.path);
} catch (error) {
return {
status: "failed",
error
};
}
}
function pruneOldBundles(dir, retention) {
if (!Number.isFinite(retention) || retention < 1) return;
try {
const entries = fs.readdirSync(dir, { withFileTypes: true }).filter((entry) => entry.isFile() && isBundleFile(entry.name)).map((entry) => {
const file = path.join(dir, entry.name);
let mtimeMs = 0;
try {
mtimeMs = fs.statSync(file).mtimeMs;
} catch {}
return {
file,
mtimeMs
};
}).toSorted((a, b) => b.mtimeMs - a.mtimeMs || b.file.localeCompare(a.file));
for (const entry of entries.slice(retention)) try {
fs.unlinkSync(entry.file);
} catch {}
} catch {}
}
function writeDiagnosticStabilityBundleSync(options) {
try {
const now = options.now ?? /* @__PURE__ */ new Date();
const snapshot = getDiagnosticStabilitySnapshot({ limit: options.limit ?? DEFAULT_DIAGNOSTIC_STABILITY_BUNDLE_LIMIT });
if (!options.includeEmpty && snapshot.count === 0) return {
status: "skipped",
reason: "empty"
};
const reason = normalizeReason(options.reason);
const error = options.error ? readSafeErrorMetadata(options.error) : void 0;
const bundle = {
version: 1,
generatedAt: now.toISOString(),
reason,
process: {
pid: process.pid,
platform: process.platform,
arch: process.arch,
node: process.versions.node,
uptimeMs: Math.round(process.uptime() * 1e3)
},
host: { hostname: REDACTED_HOSTNAME },
...error ? { error } : {},
...options.evidence ? { evidence: options.evidence } : {},
snapshot
};
const dir = resolveDiagnosticStabilityBundleDir(options);
const file = buildBundlePath(dir, now, reason);
replaceFileAtomicSync({
filePath: file,
content: `${JSON.stringify(bundle, null, 2)}\n`,
dirMode: 448,
mode: 384,
tempPrefix: ".openclaw-stability"
});
pruneOldBundles(dir, options.retention ?? 20);
return {
status: "written",
path: file,
bundle
};
} catch (error) {
return {
status: "failed",
error
};
}
}
function writeDiagnosticMemoryPressureBundleSync(options) {
return writeDiagnosticStabilityBundleSync({
...options,
reason: "diagnostic.memory.pressure.critical",
includeEmpty: true,
evidence: buildMemoryPressureEvidence(options)
});
}
function writeDiagnosticStabilityBundleForFailureSync(reason, error, options = {}) {
const result = writeDiagnosticStabilityBundleSync({
...options,
reason,
error,
includeEmpty: true
});
if (result.status === "written") return {
status: "written",
path: result.path,
message: `wrote stability bundle: ${result.path}`
};
if (result.status === "failed") return {
status: "failed",
error: result.error,
message: `failed to write stability bundle: ${String(result.error)}`
};
return result;
}
function installDiagnosticStabilityFatalHook(options = {}) {
if (fatalHookUnsubscribe) return;
fatalHookUnsubscribe = registerFatalErrorHook(({ reason, error }) => {
const result = writeDiagnosticStabilityBundleForFailureSync(reason, error, options);
return "message" in result ? result.message : void 0;
});
}
function uninstallDiagnosticStabilityFatalHook() {
fatalHookUnsubscribe?.();
fatalHookUnsubscribe = null;
}
function resetDiagnosticStabilityBundleForTest() {
uninstallDiagnosticStabilityFatalHook();
}
//#endregion
export { installDiagnosticStabilityFatalHook as a, readLatestDiagnosticStabilityBundleSync as c, uninstallDiagnosticStabilityFatalHook as d, writeDiagnosticMemoryPressureBundleSync as f, MAX_DIAGNOSTIC_STABILITY_BUNDLE_BYTES as i, resetDiagnosticStabilityBundleForTest as l, writeDiagnosticStabilityBundleSync as m, DEFAULT_DIAGNOSTIC_STABILITY_BUNDLE_RETENTION as n, listDiagnosticStabilityBundleFilesSync as o, writeDiagnosticStabilityBundleForFailureSync as p, DIAGNOSTIC_STABILITY_BUNDLE_VERSION as r, readDiagnosticStabilityBundleFileSync as s, DEFAULT_DIAGNOSTIC_STABILITY_BUNDLE_LIMIT as t, resolveDiagnosticStabilityBundleDir as u };