import fs from "node:fs/promises"; import path from "node:path"; import { stripInternalRuntimeContext } from "../../agents/internal-runtime-context.js"; import { isHeartbeatUserMessage } from "../../auto-reply/heartbeat-filter.js"; import { HEARTBEAT_PROMPT } from "../../auto-reply/heartbeat.js"; import { stripInboundMetadata } from "../../auto-reply/reply/strip-inbound-meta.js"; import { HEARTBEAT_TOKEN, isSilentReplyPayloadText } from "../../auto-reply/tokens.js"; import {
isSessionArchiveArtifactName,
isUsageCountedSessionTranscriptFileName,
} from "../../config/sessions/artifacts.js"; import { resolveSessionTranscriptsDirForAgent } from "../../config/sessions/paths.js"; import { loadSessionStore } from "../../config/sessions/store-load.js"; import { isExecCompletionEvent } from "../../infra/heartbeat-events-filter.js"; import { redactSensitiveText } from "../../logging/redact.js"; import { createSubsystemLogger } from "../../logging/subsystem.js"; import { isCronRunSessionKey } from "../../sessions/session-key-utils.js"; import { hashText } from "./internal.js";
const log = createSubsystemLogger("memory"); const DREAMING_NARRATIVE_RUN_PREFIX = "dreaming-narrative-"; // Keep the historical one-line-per-message export shape for normal turns, but // wrap pathological long messages so downstream indexers never ingest a single // toxic line. Wrapped continuation lines still map back to the same JSONL line. // This limit applies to content only; the role label adds up to 11 chars. const SESSION_EXPORT_CONTENT_WRAP_CHARS = 800; const DIRECT_CRON_PROMPT_RE = /^\[cron:[^\]]+\]\s*/;
export type SessionFileEntry = {
path: string;
absPath: string;
mtimeMs: number;
size: number;
hash: string;
content: string; /** Maps each content line (0-indexed) to its 1-indexed JSONL source line. */
lineMap: number[]; /** Maps each content line (0-indexed) to epoch ms; 0 means unknown timestamp. */
messageTimestampsMs: number[]; /** True when this transcript belongs to an internal dreaming narrative run. */
generatedByDreamingNarrative?: boolean; /** True when this transcript belongs to an isolated cron run session. */
generatedByCronRun?: boolean;
};
export type BuildSessionEntryOptions = { /** Optional preclassification from a caller-managed dreaming transcript lookup. */
generatedByDreamingNarrative?: boolean; /** Optional preclassification from a caller-managed cron transcript lookup. */
generatedByCronRun?: boolean;
};
export type SessionTranscriptClassification = {
dreamingNarrativeTranscriptPaths: ReadonlySet<string>;
cronRunTranscriptPaths: ReadonlySet<string>;
};
function isCheckpointTranscriptFileName(fileName: string): boolean { return fileName.endsWith(".jsonl") && fileName.includes(".checkpoint.");
}
function isGeneratedSystemWrapperMessage(text: string, role: "user" | "assistant"): boolean { if (role !== "user") { returnfalse;
} return GENERATED_SYSTEM_MESSAGE_RE.test(text);
}
function isGeneratedCronPromptMessage(text: string, role: "user" | "assistant"): boolean { if (role !== "user") { returnfalse;
} return DIRECT_CRON_PROMPT_RE.test(text);
}
function isGeneratedHeartbeatPromptMessage(text: string, role: "user" | "assistant"): boolean { return role === "user" && isHeartbeatUserMessage({ role, content: text }, HEARTBEAT_PROMPT);
}
function sanitizeSessionText(text: string, role: "user" | "assistant"): string | null { const strippedInbound = stripInboundMetadataForUserRole(text, role); const strippedInternal = stripInternalRuntimeContext(strippedInbound); const normalized = normalizeSessionText(strippedInternal); if (!normalized) { returnnull;
} if (isGeneratedSystemWrapperMessage(normalized, role)) { returnnull;
} if (isGeneratedCronPromptMessage(normalized, role)) { returnnull;
} if (isGeneratedHeartbeatPromptMessage(normalized, role)) { returnnull;
} if (isSilentReplyPayloadText(normalized)) { returnnull;
} // Assistant-side machinery acks: HEARTBEAT_OK is the canonical "all clear, // nothing to do" reply to a heartbeat tick. Drop on the assistant side // directly so we do not have to rely on cross-message coupling with the // preceding user message (which a real user could spoof). if (role === "assistant" && normalized === HEARTBEAT_TOKEN) { returnnull;
} const withoutSystemEnvelope = normalized.replace(GENERATED_SYSTEM_MESSAGE_RE, "").trim(); if (isExecCompletionEvent(withoutSystemEnvelope)) { returnnull;
} return normalized;
}
function parseSessionTimestampMs(
record: { timestamp?: unknown },
message: { timestamp?: unknown },
): number { 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 * 1000 : value; if (Number.isFinite(ms) && ms > 0) { return ms;
}
} if (typeof value === "string") { const parsed = Date.parse(value); if (Number.isFinite(parsed) && parsed > 0) { return parsed;
}
}
} return0;
}
export async function buildSessionEntry(
absPath: string,
opts: BuildSessionEntryOptions = {},
): Promise<SessionFileEntry | null> { try { const stat = await fs.stat(absPath); if (shouldSkipTranscriptFileForDreaming(absPath)) { return {
path: sessionPathForFile(absPath),
absPath,
mtimeMs: stat.mtimeMs,
size: stat.size,
hash: hashText("\n\n"),
content: "",
lineMap: [],
messageTimestampsMs: [],
};
} const raw = await fs.readFile(absPath, "utf-8"); const lines = raw.split("\n"); const collected: string[] = []; const lineMap: number[] = []; const messageTimestampsMs: number[] = [];
let generatedByDreamingNarrative =
opts.generatedByDreamingNarrative ?? isDreamingNarrativeTranscriptFromSessionStore(absPath); const generatedByCronRun =
opts.generatedByCronRun ?? isCronRunTranscriptFromSessionStore(absPath); for (let jsonlIdx = 0; jsonlIdx < lines.length; jsonlIdx++) { const line = lines[jsonlIdx]; if (!line.trim()) { continue;
}
let record: unknown; try {
record = JSON.parse(line);
} catch { continue;
} if (!generatedByDreamingNarrative && isDreamingNarrativeGeneratedRecord(record)) {
generatedByDreamingNarrative = true;
} if (
!record || typeof record !== "object" ||
(record as { type?: unknown }).type !== "message"
) { continue;
} const message = (record as { message?: unknown }).message as
| { role?: unknown; content?: unknown }
| undefined; if (!message || typeof message.role !== "string") { continue;
} if (message.role !== "user" && message.role !== "assistant") { continue;
} const rawText = collectRawSessionText(message.content); if (rawText === null) { continue;
} const text = sanitizeSessionText(rawText, message.role); if (!text) { // Assistant-side machinery (silent replies, system wrappers) is already // dropped by sanitizeSessionText. We deliberately do NOT use the prior // user message's pattern-match to drop the next assistant message: // user-typed text can match those same patterns (`[cron:...]`, // `System (untrusted): ...`) and a cross-message drop would let users // exfiltrate real assistant replies from the dreaming corpus by // prefixing their own prompt. See PR #70737 review (aisle-research-bot). continue;
} if (generatedByDreamingNarrative || generatedByCronRun) { continue;
} const safe = redactSensitiveText(text, { mode: "tools" }); const label = message.role === "user" ? "User" : "Assistant"; const renderedLines = renderSessionExportLines(label, safe); const timestampMs = parseSessionTimestampMs(
record as { timestamp?: unknown },
message as { timestamp?: unknown },
);
collected.push(...renderedLines);
lineMap.push(...renderedLines.map(() => jsonlIdx + 1));
messageTimestampsMs.push(...renderedLines.map(() => timestampMs));
} const content = collected.join("\n"); return {
path: sessionPathForFile(absPath),
absPath,
mtimeMs: stat.mtimeMs,
size: stat.size,
hash: hashText(content + "\n" + lineMap.join(",") + "\n" + messageTimestampsMs.join(",")),
content,
lineMap,
messageTimestampsMs,
...(generatedByDreamingNarrative ? { generatedByDreamingNarrative: true } : {}),
...(generatedByCronRun ? { generatedByCronRun: true } : {}),
};
} catch (err) {
log.debug(`Failed reading session file ${absPath}: ${String(err)}`); returnnull;
}
}
Messung V0.5 in Prozent
¤ Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.0.14Bemerkung:
(vorverarbeitet am 2026-06-10)
¤
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung und die Messung sind noch experimentell.