openclaw
Version:
Multi-channel AI gateway with extensible messaging integrations
794 lines (793 loc) • 37.9 kB
JavaScript
import { a as asOptionalRecord } from "./record-coerce-DItp3I4t.js";
import { n as getRuntimeConfig } from "./io.runtime-B9iJRs3w.js";
import { p as redactSensitiveText } from "./redact-BtvPPfTi.js";
import { n as normalizeAgentId } from "./agent-id-CeT3w4ap.js";
import { C as isCronRunSessionKey } from "./session-key-BnWWjqNc.js";
import { t as createSubsystemLogger } from "./subsystem-Dy2tqXOS.js";
import { m as stripInternalRuntimeContext } from "./internal-runtime-context-UgZVJlox.js";
import { i as stripInboundMetadata } from "./strip-inbound-meta-D5F_RINt.js";
import { i as isSilentReplyPayloadText } from "./tokens-DbeAFqg7.js";
import { f as parseUsageCountedSessionIdFromFileName, g as materializeSessionArchiveForRead, r as isCompactionCheckpointTranscriptFileName, s as isSessionArchiveArtifactName, u as isUsageCountedSessionTranscriptFileName } from "./artifacts-DMDO9PHy.js";
import { l as resolveSessionTranscriptsDirForAgent } from "./paths-CXdaYWF_.js";
import { _ as resolveSessionAgentId } from "./agent-scope-DbtJyKUL.js";
import { o as hasInterSessionUserProvenance } from "./input-provenance-DiG-Cwnd.js";
import "./session-accessor-YsytfDtG.js";
import { c as listSessionTranscriptInstances, k as listSessionTranscriptArchivesReadOnly } from "./session-accessor.sqlite-entry-CWk3jL7s.js";
import { n as parseSqliteSessionFileMarker } from "./legacy-sqlite-marker-COPKCuIN.js";
import { D as listSessionParticipantsReadOnly } from "./session-accessor.sqlite-transcript-state-DwF2owZS.js";
import { n as canonicalizeMainSessionAlias } from "./main-session-Br0F9dzh.js";
import { a as readRegularFile, s as statRegularFile } from "./fs-utils-BgbE2uPY.js";
import { n as HEARTBEAT_PROMPT } from "./heartbeat-ofet59jF.js";
import { r as isHeartbeatUserMessage } from "./heartbeat-filter-64LAFCDI.js";
import { n as retryTransientMemoryRead, r as hashText } from "./read-retry-SJrN5J_J.js";
import "./query-expansion-_C4ftD-k.js";
import { a as listSessionEntries, d as readTranscriptStatsSync, g as resolveTranscriptSessionKeyBySessionId, h as resolveStorePath, s as loadTranscriptEventsSync } from "./session-store-runtime-Btld46Zi.js";
import { a as isExecCompletionEvent } from "./heartbeat-events-filter-CEGCW5Gh.js";
import { t as classifySessionMessageOrigin } from "./session-provenance-HdsJkZBJ.js";
import { a as readTranscriptContentRevisionSync, i as isDreamingNarrativeSessionStoreKey, n as extractAgentIdFromSessionPath, r as extractAgentIdFromSessionsDir } from "./openclaw-runtime-session-DLzcC7An.js";
import fs from "node:fs";
import path from "node:path";
//#region packages/memory-host-sdk/src/host/session-reset-recall.ts
function eventId(event) {
if (!event || typeof event !== "object" || Array.isArray(event)) return;
const id = event.id;
return typeof id === "string" && id.trim() ? id : void 0;
}
/** Resolves the first raw transcript line owned by the current reset generation. */
function resolveSessionResetRecallCutoff(events) {
const resetIndex = events.findLastIndex((event) => event !== null && typeof event === "object" && !Array.isArray(event) && event.type === "reset");
if (resetIndex < 0) return { state: "absent" };
const reset = events[resetIndex];
if (reset.firstKeptEntryId === void 0) return {
state: "valid",
cutoffLine: resetIndex + 1
};
if (typeof reset.firstKeptEntryId !== "string" || !reset.firstKeptEntryId.trim()) return { state: "invalid" };
const keptIndex = events.findIndex((event, index) => index < resetIndex && eventId(event) === reset.firstKeptEntryId);
return keptIndex < 0 ? { state: "invalid" } : {
state: "valid",
cutoffLine: keptIndex + 1
};
}
//#endregion
//#region packages/memory-host-sdk/src/host/session-transcript-corpus.ts
function fileContentRevision(filePath) {
try {
const stat = fs.statSync(filePath, { bigint: true });
if (!stat.isFile()) return;
return `file:${stat.dev}:${stat.ino}:${stat.size}:${stat.mtimeNs}:${stat.ctimeNs}`;
} catch {
return;
}
}
function sqliteContentRevision(params) {
try {
return readTranscriptContentRevisionSync(params);
} catch {
return;
}
}
function isDreamingNarrativeSessionKeyLike(value) {
return typeof value === "string" && isDreamingNarrativeSessionStoreKey(value);
}
function normalizeComparablePath$1(pathname) {
const resolved = path.resolve(pathname);
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
}
function normalizeRealComparablePath(pathname) {
try {
return normalizeComparablePath$1(fs.realpathSync(pathname));
} catch {
try {
return normalizeComparablePath$1(path.join(fs.realpathSync(path.dirname(pathname)), path.basename(pathname)));
} catch {
return normalizeComparablePath$1(pathname);
}
}
}
function rememberArtifactDir(dirs, dir) {
dirs.set(normalizeRealComparablePath(dir), dir);
}
function classifySessionEntry(sessionKey, entry, cronGeneratedSessionKeys) {
const generatedByDreamingNarrative = isDreamingNarrativeSessionStoreKey(sessionKey) || isDreamingNarrativeSessionKeyLike(entry.spawnedBy);
const generatedByCronRun = cronGeneratedSessionKeys.has(sessionKey);
return {
generatedByDreamingNarrative,
generatedByCronRun,
sessionKind: generatedByCronRun ? "cron" : typeof entry.heartbeatIsolatedBaseSessionKey === "string" && entry.heartbeatIsolatedBaseSessionKey.trim() ? "heartbeat" : generatedByDreamingNarrative || Boolean(entry.spawnedBy) ? "subagent" : sessionKey.includes(":subagent:") ? "subagent" : "interactive"
};
}
function readParentSessionKeys(entry) {
const keys = /* @__PURE__ */ new Set();
for (const value of [entry?.parentSessionKey, entry?.spawnedBy]) {
if (typeof value !== "string") continue;
const trimmed = value.trim();
if (trimmed) keys.add(trimmed);
}
return [...keys];
}
function collectCronGeneratedSessionKeys(summaries) {
const entriesByKey = new Map(summaries.map((summary) => [summary.sessionKey, summary.entry]));
const cronGeneratedKeys = /* @__PURE__ */ new Set();
const cache = /* @__PURE__ */ new Map();
const resolving = /* @__PURE__ */ new Set();
const isCronGenerated = (sessionKey, entry) => {
if (isCronRunSessionKey(sessionKey)) {
cache.set(sessionKey, true);
cronGeneratedKeys.add(sessionKey);
return true;
}
const cached = cache.get(sessionKey);
if (cached !== void 0) return cached;
if (resolving.has(sessionKey)) return false;
resolving.add(sessionKey);
const generated = readParentSessionKeys(entry).some((parentKey) => isCronRunSessionKey(parentKey) || isCronGenerated(parentKey, entriesByKey.get(parentKey)));
resolving.delete(sessionKey);
cache.set(sessionKey, generated);
if (generated) cronGeneratedKeys.add(sessionKey);
return generated;
};
for (const summary of summaries) isCronGenerated(summary.sessionKey, summary.entry);
return cronGeneratedKeys;
}
function toSessionStoreCorpusEntry(agentId, storePath, summary, cronGeneratedSessionKeys, includeContentRevision) {
const sessionId = summary.entry.sessionId?.trim();
if (!sessionId) return null;
const sessionKey = summary.sessionKey.trim();
const classification = classifySessionEntry(summary.sessionKey, summary.entry, cronGeneratedSessionKeys);
const contentRevision = includeContentRevision ? sqliteContentRevision({
agentId,
sessionId,
...sessionKey ? { sessionKey } : {},
storePath
}) : void 0;
return {
agentId,
artifactKind: "active-session",
sessionFile: sessionKey,
sessionId,
...contentRevision ? { contentRevision } : {},
transcriptSource: "sqlite",
storePath,
...Number.isFinite(summary.entry.updatedAt) ? { updatedAtMs: summary.entry.updatedAt } : {},
...sessionKey ? { sessionKey } : {},
...classification.generatedByDreamingNarrative ? { generatedByDreamingNarrative: true } : {},
...classification.generatedByCronRun ? { generatedByCronRun: true } : {},
sessionKind: classification.sessionKind
};
}
function toRetainedSessionCorpusEntry(agentId, instance, sessionKey, storePath, cronGeneratedSessionKeys, includeContentRevision) {
if (!instance.provenanceKnown || instance.acpOwned || instance.entry.pluginOwnerId || instance.entry.hookExternalContentSource) return null;
const classification = classifySessionEntry(sessionKey, instance.entry, cronGeneratedSessionKeys);
const contentRevision = includeContentRevision ? sqliteContentRevision({
agentId,
sessionId: instance.sessionId,
...sessionKey ? { sessionKey } : {},
storePath
}) : void 0;
return {
agentId,
artifactKind: "retained-session",
sessionFile: sessionKey,
sessionId: instance.sessionId,
...contentRevision ? { contentRevision } : {},
storePath,
transcriptSource: "sqlite",
updatedAtMs: instance.updatedAtMs,
...sessionKey ? { sessionKey } : {},
...classification.generatedByDreamingNarrative ? { generatedByDreamingNarrative: true } : {},
...classification.generatedByCronRun ? { generatedByCronRun: true } : {},
sessionKind: classification.sessionKind
};
}
function listSessionTranscriptArtifactFiles(sessionsDir) {
try {
return fs.readdirSync(sessionsDir, { withFileTypes: true }).filter((entry) => entry.isFile()).map((entry) => entry.name).filter((name) => isUsageCountedSessionTranscriptFileName(name)).filter((name) => isSessionArchiveArtifactName(name)).map((name) => path.join(sessionsDir, name));
} catch (err) {
if (err.code === "ENOENT") return [];
throw err;
}
}
function toArtifactCorpusEntry(agentId, artifactPath, sessionId, primaryEntry, includeContentRevision = true) {
const contentRevision = includeContentRevision ? fileContentRevision(artifactPath) : void 0;
return {
agentId,
artifactKind: "archive-artifact",
sessionFile: artifactPath,
sessionId,
...contentRevision ? { contentRevision } : {},
...primaryEntry?.generatedByDreamingNarrative ? { generatedByDreamingNarrative: true } : {},
...primaryEntry?.generatedByCronRun ? { generatedByCronRun: true } : {},
sessionKind: primaryEntry?.sessionKind ?? "unknown"
};
}
function listSessionTranscriptCorpusEntriesForAgentSync(agentId, options = {}) {
const includeContentRevision = options.includeContentRevision !== false;
const normalizedAgentId = normalizeAgentId(agentId);
const cfg = getRuntimeConfig();
const configuredStore = cfg.session?.store;
const storePath = resolveStorePath(configuredStore, { agentId: normalizedAgentId });
const sessionsDir = path.dirname(storePath);
const fixedStoreOwnerAgentId = extractAgentIdFromSessionsDir(sessionsDir);
const isAgentOwnedFixedStore = fixedStoreOwnerAgentId !== null && normalizeAgentId(fixedStoreOwnerAgentId) === normalizedAgentId;
const isSharedFixedStore = typeof configuredStore === "string" && configuredStore.trim().length > 0 && !configuredStore.includes("{agentId}") && !isAgentOwnedFixedStore;
const activeEntriesBySessionId = /* @__PURE__ */ new Map();
const entryOwnersBySessionId = /* @__PURE__ */ new Map();
const artifactDirsByPath = /* @__PURE__ */ new Map();
rememberArtifactDir(artifactDirsByPath, sessionsDir);
rememberArtifactDir(artifactDirsByPath, resolveSessionTranscriptsDirForAgent(normalizedAgentId));
const sessionEntries = listSessionEntries({
agentId: normalizedAgentId,
hydrateSkillPromptRefs: false,
readOnly: options.readOnly === true,
storePath
});
const retainedInstances = options.includeRetainedSqlite ? listSessionTranscriptInstances({
agentId: normalizedAgentId,
hydrateSkillPromptRefs: false,
readConsistency: "latest",
storePath
}) : [];
const artifactPaths = [];
const scannedArtifactPaths = /* @__PURE__ */ new Set();
for (const artifactDir of artifactDirsByPath.values()) for (const artifactPath of listSessionTranscriptArtifactFiles(artifactDir)) {
const normalizedArtifactPath = normalizeRealComparablePath(artifactPath);
if (!scannedArtifactPaths.has(normalizedArtifactPath)) {
scannedArtifactPaths.add(normalizedArtifactPath);
artifactPaths.push(artifactPath);
}
}
const archivedIdentitiesByName = new Map(listSessionTranscriptArchivesReadOnly({
agentId: normalizedAgentId,
archiveNames: artifactPaths.map((artifactPath) => path.basename(artifactPath)),
storePath
}).map((archive) => [archive.archiveName, archive]));
const cronGeneratedSessionKeys = collectCronGeneratedSessionKeys([...retainedInstances.map(({ entry, sessionKey }) => ({
entry,
sessionKey
})), ...sessionEntries]);
for (const summary of sessionEntries) {
const sessionKey = isSharedFixedStore ? summary.sessionKey : canonicalizeMainSessionAlias({
cfg,
agentId: normalizedAgentId,
sessionKey: summary.sessionKey
});
const ownerAgentId = resolveSessionAgentId({
config: cfg,
sessionKey,
...isSharedFixedStore ? {} : { fallbackAgentId: normalizedAgentId }
});
const entry = toSessionStoreCorpusEntry(ownerAgentId, storePath, summary, cronGeneratedSessionKeys, includeContentRevision);
if (!entry) continue;
entryOwnersBySessionId.set(entry.sessionId, ownerAgentId);
if (ownerAgentId === normalizedAgentId) activeEntriesBySessionId.set(entry.sessionId, entry);
}
const includeUnownedArtifacts = !isSharedFixedStore;
const corpusEntries = [...activeEntriesBySessionId.values()];
if (options.includeRetainedSqlite) for (const instance of retainedInstances) {
if (activeEntriesBySessionId.has(instance.sessionId)) continue;
const sessionKey = isSharedFixedStore ? instance.sessionKey : canonicalizeMainSessionAlias({
cfg,
agentId: normalizedAgentId,
sessionKey: instance.sessionKey
});
const ownerAgentId = resolveSessionAgentId({
config: cfg,
sessionKey,
...isSharedFixedStore ? {} : { fallbackAgentId: normalizedAgentId }
});
if (ownerAgentId !== normalizedAgentId) continue;
const entry = toRetainedSessionCorpusEntry(ownerAgentId, instance, sessionKey, storePath, cronGeneratedSessionKeys, includeContentRevision);
if (entry?.transcriptSource === "sqlite") corpusEntries.push(entry);
}
for (const artifactPath of artifactPaths) {
const artifactName = path.basename(artifactPath);
const archivedIdentity = archivedIdentitiesByName.get(artifactName);
const primarySessionId = archivedIdentity?.sessionId ?? parseUsageCountedSessionIdFromFileName(artifactName);
if (!primarySessionId) continue;
const primaryEntry = activeEntriesBySessionId.get(primarySessionId);
const primaryOwner = entryOwnersBySessionId.get(primarySessionId);
if (primaryOwner && primaryOwner !== normalizedAgentId) continue;
if (!primaryOwner && !archivedIdentity && !includeUnownedArtifacts) continue;
corpusEntries.push({
...toArtifactCorpusEntry(normalizedAgentId, artifactPath, primarySessionId, primaryEntry, includeContentRevision),
...archivedIdentity?.sessionKey ? { sessionKey: archivedIdentity.sessionKey } : {},
...archivedIdentity ? { storePath } : {}
});
}
return corpusEntries;
}
/**
* Lists transcript corpus entries for memory indexing.
*
* Active sessions come from the session accessor seam; retained reset/delete
* transcript artifacts remain explicit file artifacts until core owns archive
* artifact enumeration.
*/
async function listSessionTranscriptCorpusEntriesForAgent(agentId, options = {}) {
return listSessionTranscriptCorpusEntriesForAgentSync(agentId, options);
}
//#endregion
//#region packages/memory-host-sdk/src/host/session-files.ts
const SESSION_EXPORT_CONTENT_WRAP_CHARS = 800;
const SESSION_ENTRY_PARSE_YIELD_LINES = 250;
const MAX_DATE_TIMESTAMP_MS = 864e13;
const DIRECT_CRON_PROMPT_RE = /^\[cron:[^\]]+\]\s*/;
function shouldSkipTranscriptFileForDreaming(absPath) {
const fileName = path.basename(absPath);
if (isCompactionCheckpointTranscriptFileName(fileName)) return true;
if (isSessionArchiveArtifactName(fileName) && !isUsageCountedSessionTranscriptFileName(fileName)) return true;
return false;
}
function isUsageCountedSessionArchiveTranscriptPath(absPath) {
const fileName = path.basename(absPath);
return isUsageCountedSessionTranscriptFileName(fileName) && isSessionArchiveArtifactName(fileName) && parseUsageCountedSessionIdFromFileName(fileName) !== null;
}
function hasDreamingNarrativeIdentity(value) {
return typeof value === "string" && isDreamingNarrativeSessionStoreKey(value);
}
function isDreamingNarrativeGeneratedRecord(record) {
const candidate = asOptionalRecord(record);
if (!candidate) return false;
const data = asOptionalRecord(candidate.data);
if (candidate.type === "custom" && candidate.customType === "openclaw:bootstrap-context:full" && typeof data?.runId === "string" && data.runId.startsWith("dreaming-narrative-")) return true;
const message = candidate.type === "message" ? asOptionalRecord(candidate.message) : void 0;
const metadata = asOptionalRecord(message?.["__openclaw"]);
return hasDreamingNarrativeIdentity(candidate.runId) || hasDreamingNarrativeIdentity(candidate.sessionKey) || hasDreamingNarrativeIdentity(data?.runId) || hasDreamingNarrativeIdentity(data?.sessionKey) || (message?.role === "assistant" || message?.role === "toolResult") && hasDreamingNarrativeIdentity(metadata?.runId);
}
function hasCronRunSessionKey(value) {
return typeof value === "string" && isCronRunSessionKey(value);
}
function isCronRunGeneratedRecord(record) {
const candidate = asOptionalRecord(record);
return hasCronRunSessionKey(candidate?.sessionKey) || hasCronRunSessionKey(asOptionalRecord(candidate?.data)?.sessionKey);
}
function normalizeComparablePath(pathname) {
const resolved = path.resolve(pathname);
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
}
function resolveSessionStoreTranscriptPath(sessionsDir, entry) {
const resolved = resolveSessionStoreTranscriptResolvedPath(sessionsDir, entry);
return resolved ? normalizeComparablePath(resolved) : null;
}
function resolveSessionStoreTranscriptResolvedPath(sessionsDir, entry) {
if (typeof entry?.sessionFile === "string" && entry.sessionFile.trim().length > 0) {
const sessionFile = entry.sessionFile.trim();
return path.isAbsolute(sessionFile) ? sessionFile : path.resolve(sessionsDir, sessionFile);
}
if (typeof entry?.sessionId === "string" && entry.sessionId.trim().length > 0) return path.join(sessionsDir, `${entry.sessionId.trim()}.jsonl`);
return null;
}
function isCanonicalSessionsDirForAgent(sessionsDir, agentId) {
return normalizeComparablePath(sessionsDir) === normalizeComparablePath(resolveSessionTranscriptsDirForAgent(agentId));
}
function loadSessionTranscriptClassificationForSessionsDir(sessionsDir) {
const agentId = extractAgentIdFromSessionsDir(sessionsDir);
if (agentId && isCanonicalSessionsDirForAgent(sessionsDir, agentId)) return classifySessionTranscriptCorpusEntries(listSessionTranscriptCorpusEntriesForAgentSync(agentId));
const store = readSessionTranscriptClassificationStore(path.join(sessionsDir, "sessions.json"));
const dreamingTranscriptPaths = /* @__PURE__ */ new Set();
const cronRunTranscriptPaths = /* @__PURE__ */ new Set();
for (const [sessionKey, entry] of Object.entries(store)) {
const transcriptPath = resolveSessionStoreTranscriptPath(sessionsDir, entry);
if (!transcriptPath) continue;
if (isDreamingNarrativeSessionStoreKey(sessionKey)) dreamingTranscriptPaths.add(transcriptPath);
if (isCronRunSessionKey(sessionKey)) cronRunTranscriptPaths.add(transcriptPath);
}
return {
dreamingNarrativeTranscriptPaths: dreamingTranscriptPaths,
cronRunTranscriptPaths
};
}
function readSessionTranscriptClassificationStore(storePath) {
try {
const parsed = JSON.parse(fs.readFileSync(storePath, "utf-8"));
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return {};
return parsed;
} catch {
return {};
}
}
function classifySessionTranscriptCorpusEntries(corpusEntries) {
const dreamingTranscriptPaths = /* @__PURE__ */ new Set();
const cronRunTranscriptPaths = /* @__PURE__ */ new Set();
for (const entry of corpusEntries) {
if (entry.transcriptSource === "sqlite") continue;
const normalizedPath = normalizeComparablePath(entry.sessionFile);
if (entry.generatedByDreamingNarrative) dreamingTranscriptPaths.add(normalizedPath);
if (entry.generatedByCronRun) cronRunTranscriptPaths.add(normalizedPath);
}
return {
dreamingNarrativeTranscriptPaths: dreamingTranscriptPaths,
cronRunTranscriptPaths
};
}
function classifySessionTranscriptFromSessionStore(absPath) {
const sessionsDir = path.dirname(absPath);
const normalizedAbsPath = normalizeComparablePath(absPath);
const primarySessionId = parseUsageCountedSessionIdFromFileName(path.basename(absPath));
const normalizedPrimaryPath = primarySessionId && isSessionArchiveArtifactName(path.basename(absPath)) ? normalizeComparablePath(path.join(sessionsDir, `${primarySessionId}.jsonl`)) : null;
const classification = loadSessionTranscriptClassificationForSessionsDir(sessionsDir);
const hasClassifiedPath = (paths) => paths.has(normalizedAbsPath) || normalizedPrimaryPath !== null && paths.has(normalizedPrimaryPath);
return {
generatedByDreamingNarrative: hasClassifiedPath(classification.dreamingNarrativeTranscriptPaths),
generatedByCronRun: hasClassifiedPath(classification.cronRunTranscriptPaths)
};
}
function sessionPathForFile(absPath) {
const agentId = extractAgentIdFromSessionPath(absPath);
return path.join("sessions", ...agentId ? [agentId] : [], path.basename(absPath)).replace(/\\/g, "/");
}
/** Returns the logical memory path for a live SQLite-backed session transcript. */
function sessionPathForSessionIdentity(agentId, sessionId) {
return path.join("sessions", normalizeAgentId(agentId), `${sessionId}.jsonl`).replace(/\\/g, "/");
}
/**
* Parses a deprecated path-shaped memory sync hint only when it points at an
* OpenClaw-owned usage-counted transcript in the canonical agent sessions dir.
*/
function parseCanonicalSessionSyncTargetFromPath(sessionFile) {
const trimmed = sessionFile.trim();
if (!trimmed) return null;
const resolved = path.resolve(trimmed);
const fileName = path.basename(resolved);
const sessionId = parseUsageCountedSessionIdFromFileName(fileName);
if (!sessionId || !isUsageCountedSessionTranscriptFileName(fileName)) return null;
const agentId = extractAgentIdFromSessionPath(resolved);
if (!agentId) return null;
const canonicalSessionsDir = normalizeComparablePath(resolveSessionTranscriptsDirForAgent(agentId));
if (normalizeComparablePath(path.dirname(resolved)) !== canonicalSessionsDir) return null;
return {
agentId,
sessionId
};
}
async function logSessionFileReadFailure(absPath, err) {
createSubsystemLogger("memory").debug(`Failed reading session file ${absPath}: ${String(err)}`);
}
function normalizeSessionText(value) {
return value.replace(/\s*\n+\s*/g, " ").replace(/\s+/g, " ").trim();
}
function collectRawSessionText(content) {
if (typeof content === "string") return content;
if (!Array.isArray(content)) return null;
const parts = [];
for (const block of content) {
if (!block || typeof block !== "object") continue;
const record = block;
if (record.type === "text" && typeof record.text === "string") parts.push(record.text);
}
return parts.length > 0 ? parts.join("\n") : null;
}
function isHighSurrogate(code) {
return code >= 55296 && code <= 56319;
}
function isLowSurrogate(code) {
return code >= 56320 && code <= 57343;
}
function splitLongSessionLine(text, maxChars = SESSION_EXPORT_CONTENT_WRAP_CHARS) {
const normalized = text.trim();
if (!normalized) return [];
if (normalized.length <= maxChars) return [normalized];
const segments = [];
let cursor = 0;
while (cursor < normalized.length) {
if (normalized.length - cursor <= maxChars) {
segments.push(normalized.slice(cursor).trim());
break;
}
const limit = cursor + maxChars;
let splitAt = limit;
for (let index = limit; index > cursor; index -= 1) if (normalized[index] === " ") {
splitAt = index;
break;
}
if (splitAt < normalized.length && splitAt > cursor && isHighSurrogate(normalized.charCodeAt(splitAt - 1)) && isLowSurrogate(normalized.charCodeAt(splitAt))) splitAt -= 1;
segments.push(normalized.slice(cursor, splitAt).trim());
cursor = splitAt;
while (cursor < normalized.length && normalized[cursor] === " ") cursor += 1;
}
return segments.filter(Boolean);
}
function renderSessionExportLines(label, text) {
return splitLongSessionLine(text).map((segment) => `${label}: ${segment}`);
}
const GENERATED_SYSTEM_MESSAGE_RE = /^System(?: \(untrusted\))?: \[[^\]]+\]\s*/;
function sanitizeSessionText(text, role) {
const strippedInbound = role === "user" ? stripInboundMetadata(text) : text;
const normalized = normalizeSessionText(stripInternalRuntimeContext(strippedInbound));
if (!normalized) return null;
if (role === "user" && (GENERATED_SYSTEM_MESSAGE_RE.test(normalized) || DIRECT_CRON_PROMPT_RE.test(normalized) || isHeartbeatUserMessage({
role,
content: normalized
}, HEARTBEAT_PROMPT))) return null;
if (isSilentReplyPayloadText(normalized)) return null;
if (role === "assistant" && normalized === "HEARTBEAT_OK") return null;
const withoutSystemEnvelope = normalized.replace(GENERATED_SYSTEM_MESSAGE_RE, "").trim();
if (isExecCompletionEvent(withoutSystemEnvelope)) return null;
return normalized;
}
function isRecalledMemoryMessage(message) {
const provenance = message.provenance;
return provenance?.kind === "internal_system" && (provenance.sourceTool === "memory_search" || provenance.sourceTool === "memory_get");
}
function parseSessionTimestampMs(record, message) {
const candidates = [message.timestamp, record.timestamp];
for (const value of candidates) {
if (typeof value === "number" && Number.isFinite(value)) {
const ms = value > 0 && value < 1e11 ? value * 1e3 : value;
if (Number.isFinite(ms) && ms > 0 && ms <= MAX_DATE_TIMESTAMP_MS) return Math.floor(ms);
}
if (typeof value === "string") {
const parsed = Date.parse(value);
if (Number.isFinite(parsed) && parsed > 0) return parsed;
}
}
return 0;
}
function resolveSessionEntryParseYieldLines(opts) {
const configured = opts.parseYieldEveryLines;
if (typeof configured === "number" && Number.isFinite(configured)) return Math.max(1, Math.floor(configured));
return SESSION_ENTRY_PARSE_YIELD_LINES;
}
function resolveBuildSessionSqliteIdentity(absPath, opts) {
if (opts.agentId && opts.sessionId && opts.storePath) return {
agentId: opts.agentId,
sessionId: opts.sessionId,
...opts.sessionKey ? { sessionKey: opts.sessionKey } : {},
storePath: opts.storePath
};
const marker = parseSqliteSessionFileMarker(absPath);
return marker && opts.sessionKey ? {
...marker,
sessionKey: opts.sessionKey
} : marker;
}
function statSessionEntrySync(absPath, opts = {}) {
const sqliteIdentity = resolveBuildSessionSqliteIdentity(absPath, opts);
if (sqliteIdentity) {
const stats = readTranscriptStatsSync({ ...sqliteIdentity });
return {
absPath,
path: sessionPathForSessionIdentity(sqliteIdentity.agentId, sqliteIdentity.sessionId),
mtimeMs: opts.updatedAtMs ?? stats.maxSeq,
size: stats.sizeBytes
};
}
try {
const stat = fs.statSync(absPath);
return stat.isFile() ? {
absPath,
path: sessionPathForFile(absPath),
mtimeMs: stat.mtimeMs,
size: stat.size
} : null;
} catch {
return null;
}
}
async function yieldSessionEntryParseIfNeeded(lineIndex, everyLines) {
if (lineIndex > 0 && lineIndex % everyLines === 0) await new Promise((resolve) => {
setImmediate(resolve);
});
}
async function buildSessionEntry(absPath, opts = {}) {
try {
const sqliteIdentity = resolveBuildSessionSqliteIdentity(absPath, opts);
const sqliteSource = sqliteIdentity ? (() => {
const stats = readTranscriptStatsSync(sqliteIdentity);
const records = loadTranscriptEventsSync(sqliteIdentity);
const resetRecallCutoff = resolveSessionResetRecallCutoff(records);
return {
mtimeMs: opts.updatedAtMs ?? stats.maxSeq,
path: sessionPathForSessionIdentity(sqliteIdentity.agentId, sqliteIdentity.sessionId),
records,
resetRecallCutoff,
size: stats.sizeBytes
};
})() : null;
let raw = "";
let mtimeMs;
let size;
let memoryPath;
if (sqliteSource) {
mtimeMs = sqliteSource.mtimeMs;
size = sqliteSource.size;
memoryPath = sqliteSource.path;
} else {
const regularFile = await statRegularFile(absPath);
if (regularFile.missing) return null;
const stat = regularFile.stat;
if (shouldSkipTranscriptFileForDreaming(absPath)) return {
path: sessionPathForFile(absPath),
absPath,
mtimeMs: stat.mtimeMs,
size: stat.size,
hash: hashText("\n\n"),
content: "",
lineMap: [],
messageTimestampsMs: [],
lineProvenance: [],
sessionKind: opts.sessionKind ?? "unknown"
};
raw = (await retryTransientMemoryRead(() => readRegularFile({ filePath: isUsageCountedSessionArchiveTranscriptPath(absPath) ? materializeSessionArchiveForRead(absPath) : absPath }), `read session transcript ${absPath}`)).buffer.toString("utf-8");
mtimeMs = stat.mtimeMs;
size = stat.size;
memoryPath = sessionPathForFile(absPath);
}
const collected = [];
const lineMap = [];
const messageTimestampsMs = [];
const lineProvenance = [];
const parseYieldEveryLines = resolveSessionEntryParseYieldLines(opts);
const sqliteSessionKey = sqliteIdentity && !opts.sessionKey ? resolveTranscriptSessionKeyBySessionId({
agentId: sqliteIdentity.agentId,
sessionId: sqliteIdentity.sessionId,
storePath: sqliteIdentity.storePath
}) : void 0;
const sessionStoreClassification = !sqliteIdentity && (opts.generatedByDreamingNarrative === void 0 || opts.generatedByCronRun === void 0) ? classifySessionTranscriptFromSessionStore(absPath) : null;
let generatedByDreamingNarrative = opts.generatedByDreamingNarrative ?? (sqliteSessionKey ? isDreamingNarrativeSessionStoreKey(sqliteSessionKey) : void 0) ?? sessionStoreClassification?.generatedByDreamingNarrative ?? false;
let generatedByCronRun = opts.generatedByCronRun ?? (sqliteSessionKey ? isCronRunSessionKey(sqliteSessionKey) : void 0) ?? sessionStoreClassification?.generatedByCronRun ?? false;
const sessionKind = opts.sessionKind ?? "unknown";
const allowArchiveRecordCronClassification = isUsageCountedSessionArchiveTranscriptPath(absPath);
let insideHeartbeatTurn = false;
let insideRecalledMemoryTurn = false;
let turnOrigin = "untrusted";
for (let jsonlIdx = 0, lineStart = 0; sqliteSource ? jsonlIdx < sqliteSource.records.length : lineStart <= raw.length; jsonlIdx++) {
await yieldSessionEntryParseIfNeeded(jsonlIdx, parseYieldEveryLines);
let record;
if (sqliteSource) record = sqliteSource.records[jsonlIdx];
else {
const newlineIndex = raw.indexOf("\n", lineStart);
const lineEnd = newlineIndex === -1 ? raw.length : newlineIndex;
const line = raw.slice(lineStart, lineEnd);
lineStart = newlineIndex === -1 ? raw.length + 1 : newlineIndex + 1;
if (!line.trim()) continue;
try {
record = JSON.parse(line);
} catch {
continue;
}
}
const identifiesDreamingNarrative = !generatedByDreamingNarrative && isDreamingNarrativeGeneratedRecord(record);
const identifiesCronRun = !generatedByCronRun && allowArchiveRecordCronClassification && isCronRunGeneratedRecord(record);
if (identifiesDreamingNarrative || identifiesCronRun) {
generatedByDreamingNarrative ||= identifiesDreamingNarrative;
generatedByCronRun ||= identifiesCronRun;
collected.length = 0;
lineMap.length = 0;
messageTimestampsMs.length = 0;
lineProvenance.length = 0;
}
if (!record || typeof record !== "object" || record.type !== "message") continue;
const message = record.message;
if (!message || typeof message.role !== "string") continue;
if (message.role !== "user" && message.role !== "assistant") continue;
const timestampMs = parseSessionTimestampMs(record, message);
opts.onTranscriptMessage?.(message, Math.max(0, Math.floor(timestampMs || mtimeMs)));
const inputProvenance = message.provenance;
const isHeartbeatUser = message.role === "user" && inputProvenance?.kind === "internal_system" && inputProvenance.sourceTool === "heartbeat";
if (message.role === "user") {
insideHeartbeatTurn = isHeartbeatUser;
insideRecalledMemoryTurn = isRecalledMemoryMessage(message);
turnOrigin = classifySessionMessageOrigin(message, turnOrigin);
}
if (message.role === "user" && hasInterSessionUserProvenance(message)) continue;
if (insideHeartbeatTurn || insideRecalledMemoryTurn || generatedByDreamingNarrative || generatedByCronRun) continue;
const rawText = collectRawSessionText(message.content);
if (rawText === null) continue;
const text = sanitizeSessionText(rawText, message.role);
if (!text) continue;
const safe = redactSensitiveText(text, { mode: "tools" });
const renderedLines = renderSessionExportLines(message.role === "user" ? "User" : "Assistant", safe);
const memoryProvenance = {
originClass: classifySessionMessageOrigin(message, turnOrigin),
sessionKind,
observedAt: Math.max(0, Math.floor(timestampMs || mtimeMs))
};
collected.push(...renderedLines);
lineMap.push(...renderedLines.map(() => jsonlIdx + 1));
messageTimestampsMs.push(...renderedLines.map(() => timestampMs));
lineProvenance.push(...renderedLines.map(() => memoryProvenance));
}
const content = collected.join("\n");
const entry = {
path: memoryPath,
absPath,
mtimeMs,
size,
hash: hashText(content + "\n" + lineMap.join(",") + "\n" + messageTimestampsMs.join(",") + "\n" + JSON.stringify(lineProvenance) + "\n" + JSON.stringify(sqliteSource?.resetRecallCutoff ?? { state: "absent" })),
content,
lineMap,
messageTimestampsMs,
lineProvenance,
sessionKind,
...generatedByDreamingNarrative ? { generatedByDreamingNarrative: true } : {},
...generatedByCronRun ? { generatedByCronRun: true } : {}
};
Object.defineProperty(entry, Symbol.for("openclaw.memory.sessionResetRecallCutoff"), {
configurable: false,
enumerable: false,
value: sqliteSource?.resetRecallCutoff ?? { state: "absent" },
writable: false
});
return entry;
} catch (err) {
logSessionFileReadFailure(absPath, err);
return null;
}
}
//#endregion
//#region src/plugin-sdk/memory-core-host-engine-sessions.ts
/** Private-local SDK subpath for memory session transcript helpers. */
function projectSessionMetadata(instance, participants = []) {
return {
agentId: instance.agentId,
sessionId: instance.sessionId,
sessionKey: instance.sessionKey,
resolution: "live",
...instance.sourceMetadata,
participants
};
}
/** Read authoritative admission facts without creating a missing agent database. */
function loadMemorySessionMetadata(params) {
const instance = listSessionTranscriptInstances(params, {
includeAllWindows: true,
sessionId: params.sessionId
}).find((candidate) => candidate.agentId === normalizeAgentId(params.agentId) && (!params.sessionKey || candidate.sessionKey === params.sessionKey));
return instance ? projectSessionMetadata(instance) : void 0;
}
/** Resolve explicit memory-forget selectors against authoritative session owners. */
function resolveMemorySessionTargets(params) {
const sessionIds = [...new Set(params.sessionIds ?? [])];
const hookSources = [...new Set(params.hookSources ?? [])];
const participants = [...new Set(params.participants ?? [])];
if (sessionIds.length === 0 && hookSources.length === 0 && participants.length === 0) return [];
const since = typeof params.since === "string" ? Date.parse(params.since) : params.since;
if (since !== void 0 && !Number.isFinite(since)) throw new Error(`Invalid memory session date: ${params.since}`);
const resolvedSelectors = /* @__PURE__ */ new Set();
const participantRecords = listSessionParticipantsReadOnly(params);
const targets = /* @__PURE__ */ new Map();
const instances = listSessionTranscriptInstances(params, { includeAllWindows: true }).filter((instance) => instance.agentId === normalizeAgentId(params.agentId)).toSorted((left, right) => left.sourceMetadata.createdAt - right.sourceMetadata.createdAt || left.sessionId.localeCompare(right.sessionId));
for (const instance of instances) {
const identities = (participantRecords.get(instance.sessionKey) ?? []).map(({ identity }) => identity);
const source = instance.sourceMetadata.hookExternalContentSource;
if (!sessionIds.includes(instance.sessionId) && !sessionIds.includes(instance.sessionKey) && !(source && hookSources.includes(source)) && !identities.some((identity) => participants.includes(identity.id))) continue;
resolvedSelectors.add(instance.sessionId);
resolvedSelectors.add(instance.sessionKey);
if (since === void 0 || instance.sourceMetadata.createdAt >= since) targets.set(instance.sessionId, projectSessionMetadata(instance, identities));
}
for (const archive of listSessionTranscriptArchivesReadOnly({
...params,
sessionIds
})) {
resolvedSelectors.add(archive.sessionId);
resolvedSelectors.add(archive.sessionKey);
if (targets.has(archive.sessionId) || since !== void 0 && archive.createdAt < since) continue;
targets.set(archive.sessionId, {
agentId: params.agentId,
sessionId: archive.sessionId,
sessionKey: archive.sessionKey,
resolution: "archived",
hookExternalContentSource: null,
channel: null,
accountId: null,
chatType: null,
createdAt: archive.createdAt,
participants: []
});
}
for (const sessionId of sessionIds) if (!resolvedSelectors.has(sessionId)) targets.set(sessionId, {
agentId: params.agentId,
sessionId,
resolution: "unresolved",
hookExternalContentSource: null,
channel: null,
accountId: null,
chatType: null,
participants: []
});
return [...targets.values()];
}
//#endregion
export { sessionPathForFile as a, listSessionTranscriptCorpusEntriesForAgent as c, parseCanonicalSessionSyncTargetFromPath as i, resolveMemorySessionTargets as n, sessionPathForSessionIdentity as o, buildSessionEntry as r, statSessionEntrySync as s, loadMemorySessionMetadata as t };