memory-core: precompute dreaming transcript lookup

This commit is contained in:
Josh Lehman
2026-04-15 10:41:55 -07:00
parent b99fd53711
commit f9fbbe5016
3 changed files with 58 additions and 10 deletions

View File

@@ -6,6 +6,8 @@ import type { OpenClawConfig, OpenClawPluginApi } from "openclaw/plugin-sdk/memo
import {
buildSessionEntry,
listSessionFilesForAgent,
loadDreamingNarrativeTranscriptPathSetForAgent,
normalizeSessionTranscriptPathForComparison,
parseUsageCountedSessionIdFromFileName,
sessionPathForFile,
} from "openclaw/plugin-sdk/memory-core-host-engine-qmd";
@@ -688,13 +690,25 @@ async function collectSessionIngestionBatches(params: {
const nextSeenMessages: Record<string, string[]> = { ...params.state.seenMessages };
let changed = false;
const sessionFiles: Array<{ agentId: string; absolutePath: string; sessionPath: string }> = [];
const sessionFiles: Array<{
agentId: string;
absolutePath: string;
generatedByDreamingNarrative: boolean;
sessionPath: string;
}> = [];
for (const agentId of agentIds) {
const files = await listSessionFilesForAgent(agentId);
const dreamingTranscriptPaths =
files.length > 0
? loadDreamingNarrativeTranscriptPathSetForAgent(agentId)
: new Set<string>();
for (const absolutePath of files) {
sessionFiles.push({
agentId,
absolutePath,
generatedByDreamingNarrative: dreamingTranscriptPaths.has(
normalizeSessionTranscriptPathForComparison(absolutePath),
),
sessionPath: sessionPathForFile(absolutePath),
});
}
@@ -751,7 +765,9 @@ async function collectSessionIngestionBatches(params: {
continue;
}
const entry = await buildSessionEntry(file.absolutePath);
const entry = await buildSessionEntry(file.absolutePath, {
generatedByDreamingNarrative: file.generatedByDreamingNarrative,
});
if (!entry) {
continue;
}

View File

@@ -4,7 +4,10 @@ export { extractKeywords, isQueryStopWordToken } from "./host/query-expansion.js
export {
buildSessionEntry,
listSessionFilesForAgent,
loadDreamingNarrativeTranscriptPathSetForAgent,
normalizeSessionTranscriptPathForComparison,
sessionPathForFile,
type BuildSessionEntryOptions,
type SessionFileEntry,
} from "./host/session-files.js";
export { parseUsageCountedSessionIdFromFileName } from "../config/sessions/artifacts.js";

View File

@@ -25,6 +25,11 @@ export type SessionFileEntry = {
generatedByDreamingNarrative?: boolean;
};
export type BuildSessionEntryOptions = {
/** Optional preclassification from a caller-managed dreaming transcript lookup. */
generatedByDreamingNarrative?: boolean;
};
function isDreamingNarrativeBootstrapRecord(record: unknown): boolean {
if (!record || typeof record !== "object" || Array.isArray(record)) {
return false;
@@ -98,6 +103,10 @@ function normalizeComparablePath(pathname: string): string {
return process.platform === "win32" ? resolved.toLowerCase() : resolved;
}
export function normalizeSessionTranscriptPathForComparison(pathname: string): string {
return normalizeComparablePath(pathname);
}
function resolveSessionStoreTranscriptPath(
sessionsDir: string,
entry: { sessionFile?: unknown; sessionId?: unknown } | undefined,
@@ -115,21 +124,37 @@ function resolveSessionStoreTranscriptPath(
return null;
}
function isDreamingNarrativeTranscriptFromSessionStore(absPath: string): boolean {
const sessionsDir = path.dirname(absPath);
export function loadDreamingNarrativeTranscriptPathSetForSessionsDir(
sessionsDir: string,
): ReadonlySet<string> {
const storePath = path.join(sessionsDir, "sessions.json");
const normalizedAbsPath = normalizeComparablePath(absPath);
const store = loadSessionStore(storePath);
const dreamingTranscriptPaths = new Set<string>();
for (const [sessionKey, entry] of Object.entries(store)) {
if (!isDreamingNarrativeSessionStoreKey(sessionKey)) {
continue;
}
const transcriptPath = resolveSessionStoreTranscriptPath(sessionsDir, entry);
if (transcriptPath === normalizedAbsPath) {
return true;
if (transcriptPath) {
dreamingTranscriptPaths.add(transcriptPath);
}
}
return false;
return dreamingTranscriptPaths;
}
export function loadDreamingNarrativeTranscriptPathSetForAgent(
agentId: string,
): ReadonlySet<string> {
return loadDreamingNarrativeTranscriptPathSetForSessionsDir(
resolveSessionTranscriptsDirForAgent(agentId),
);
}
function isDreamingNarrativeTranscriptFromSessionStore(absPath: string): boolean {
const sessionsDir = path.dirname(absPath);
const normalizedAbsPath = normalizeComparablePath(absPath);
const dreamingTranscriptPaths = loadDreamingNarrativeTranscriptPathSetForSessionsDir(sessionsDir);
return dreamingTranscriptPaths.has(normalizedAbsPath);
}
export async function listSessionFilesForAgent(agentId: string): Promise<string[]> {
@@ -207,7 +232,10 @@ function parseSessionTimestampMs(
return 0;
}
export async function buildSessionEntry(absPath: string): Promise<SessionFileEntry | null> {
export async function buildSessionEntry(
absPath: string,
opts: BuildSessionEntryOptions = {},
): Promise<SessionFileEntry | null> {
try {
const stat = await fs.stat(absPath);
const raw = await fs.readFile(absPath, "utf-8");
@@ -215,7 +243,8 @@ export async function buildSessionEntry(absPath: string): Promise<SessionFileEnt
const collected: string[] = [];
const lineMap: number[] = [];
const messageTimestampsMs: number[] = [];
let generatedByDreamingNarrative = isDreamingNarrativeTranscriptFromSessionStore(absPath);
let generatedByDreamingNarrative =
opts.generatedByDreamingNarrative ?? isDreamingNarrativeTranscriptFromSessionStore(absPath);
for (let jsonlIdx = 0; jsonlIdx < lines.length; jsonlIdx++) {
const line = lines[jsonlIdx];
if (!line.trim()) {