惯性聚合 高效追踪和阅读你感兴趣的博客、新闻、科技资讯
阅读原文 在惯性聚合中打开

推荐订阅源

Hugging Face - Blog
Hugging Face - Blog
量子位
H
Help Net Security
Microsoft Azure Blog
Microsoft Azure Blog
MongoDB | Blog
MongoDB | Blog
小众软件
小众软件
爱范儿
爱范儿
博客园 - 【当耐特】
Vercel News
Vercel News
S
SegmentFault 最新的问题
M
MIT News - Artificial intelligence
F
Fortinet All Blogs
Apple Machine Learning Research
Apple Machine Learning Research
GbyAI
GbyAI
博客园 - 叶小钗
博客园_首页
V
Visual Studio Blog
宝玉的分享
宝玉的分享
B
Blog
MyScale Blog
MyScale Blog
C
Check Point Blog
博客园 - 三生石上(FineUI控件)
L
LangChain Blog
V
V2EX

Recent Commits to openclaw:main

test: merge chat side-result checks · openclaw/openclaw@ddd2c2a test: merge cron history checks · openclaw/openclaw@f7eb746 test: merge responsive navigation shell checks · openclaw/openclaw@c2e4b47 docs(changelog): add codex oauth fixes · openclaw/openclaw@628e6cd test: merge navigation routing cases · openclaw/openclaw@5d8cecb Tests: mock channel registry bundled fallback · openclaw/openclaw@2b08233 Secrets: avoid broad web search discovery for single plugin config · openclaw/openclaw@a464f59 test: merge config view browser checks · openclaw/openclaw@20cf511 fix(status): align oauth health with runtime · openclaw/openclaw@eed7116 feat: add macOS screen snapshots for monitor preview (#67954) thanks … · openclaw/openclaw@f377db1 fix: report shared auth scopes in hello-ok (#67810) thanks @BunsDev · openclaw/openclaw@0b6c39b Auto-reply: avoid eager bundled route fallback · openclaw/openclaw@3ea1bf4 Tests: narrow session binding contract setup · openclaw/openclaw@54e4e16 fix(macOS): enable undo/redo in webchat composer text input (#34962) · openclaw/openclaw@00951dc Tests: speed up channel setup promotion · openclaw/openclaw@82b529a Docs: refresh agent instructions · openclaw/openclaw@5775fe2 fix(auth): serialize OAuth refresh across agents to fix #26322 (#67876) · openclaw/openclaw@8e79080 test: allow ollama public surface boundary test · openclaw/openclaw@7d4f1a6 Docs: add test performance guardrails · openclaw/openclaw@89706d3 Tests: restore context-engine usage proof · openclaw/openclaw@e4c4f95 Tests: slim context engine runtime coverage · openclaw/openclaw@74c198f ci: retry failed custom checkouts · openclaw/openclaw@0ee5baf test: trim duplicate provider auth onboarding cases · openclaw/openclaw@1ffc02e matrix: fix sessions_spawn --thread subagent session spawning (#67643) · openclaw/openclaw@1ce2596 test: reduce auth choice fixture churn · openclaw/openclaw@857b9cd test: mock health status config boundaries · openclaw/openclaw@9d5ab4a test: mock onboard config io boundary · openclaw/openclaw@299694d test: mock legacy state plugin boundaries · openclaw/openclaw@2713089 test: mock channel install boundaries · openclaw/openclaw@b945248 test: mock doctor preview channel boundaries · openclaw/openclaw@b1a3ad4
fix(memory-core): migrate dreaming cleanup lifecycle (#96...
jalehman · 2026-06-24 · via Recent Commits to openclaw:main

@@ -14,12 +14,7 @@ import {

1414

import { resolveGlobalMap } from "openclaw/plugin-sdk/global-singleton";

1515

import { resolveStateDir } from "openclaw/plugin-sdk/memory-core-host-runtime-core";

1616

import { getRuntimeConfig } from "openclaw/plugin-sdk/runtime-config-snapshot";

17-

import { pathExists } from "openclaw/plugin-sdk/security-runtime";

18-

import {

19-

loadSessionStore,

20-

resolveStorePath,

21-

updateSessionStore,

22-

} from "openclaw/plugin-sdk/session-store-runtime";

17+

import { cleanupSessionLifecycleArtifacts } from "openclaw/plugin-sdk/session-store-runtime";

2318

import { readDreamsFile, resolveDreamsPath, updateDreamsFile } from "./dreaming-dreams-file.js";

24192520

// ── Types ──────────────────────────────────────────────────────────────

@@ -108,7 +103,6 @@ const NARRATIVE_MESSAGE_SETTLE_DELAYS_MS = [50, 150, 300, 750] as const;

108103

const DREAMING_SESSION_KEY_PREFIX = "dreaming-narrative-";

109104

const DREAMING_TRANSCRIPT_RUN_MARKER = '"runId":"dreaming-narrative-';

110105

const DREAMING_ORPHAN_MIN_AGE_MS = 300_000;

111-

const SAFE_SESSION_ID_RE = /^[a-z0-9][a-z0-9._-]{0,127}$/i;

112106

const DIARY_START_MARKER = "<!-- openclaw:dreaming:diary:start -->";

113107

const DIARY_END_MARKER = "<!-- openclaw:dreaming:diary:end -->";

114108

const BACKFILL_ENTRY_MARKER = "openclaw:dreaming:backfill-entry";

@@ -776,80 +770,6 @@ export async function appendNarrativeEntry(params: {

776770777771

// ── Orchestrator ───────────────────────────────────────────────────────

778772779-

function normalizeComparablePath(pathname: string): string {

780-

return process.platform === "win32" ? pathname.toLowerCase() : pathname;

781-

}

782-783-

async function normalizeSessionFileForComparison(params: {

784-

sessionsDir: string;

785-

sessionFile: string;

786-

}): Promise<string | null> {

787-

const trimmed = params.sessionFile.trim();

788-

if (!trimmed) {

789-

return null;

790-

}

791-

const resolved = path.isAbsolute(trimmed) ? trimmed : path.resolve(params.sessionsDir, trimmed);

792-

try {

793-

return normalizeComparablePath(await fs.realpath(resolved));

794-

} catch {

795-

return normalizeComparablePath(path.resolve(resolved));

796-

}

797-

}

798-799-

function isDreamingSessionStoreKey(sessionKey: string): boolean {

800-

const firstSeparator = sessionKey.indexOf(":");

801-

if (firstSeparator < 0) {

802-

return sessionKey.startsWith(DREAMING_SESSION_KEY_PREFIX);

803-

}

804-

const secondSeparator = sessionKey.indexOf(":", firstSeparator + 1);

805-

const sessionSegment = secondSeparator < 0 ? sessionKey : sessionKey.slice(secondSeparator + 1);

806-

return sessionSegment.startsWith(DREAMING_SESSION_KEY_PREFIX);

807-

}

808-809-

// A dreaming store row is reclaimable once its narrative run is finished. The

810-

// happy path deletes the session in `finally`, but when `deleteSession` throws

811-

// (e.g. request-scoped subagent runtime) the row is left behind referencing a

812-

// still-present transcript, so the missing-transcript check alone never reaps

813-

// it and the session lingers in the sidebar forever (issue #88322). Reclaim a

814-

// dreaming row when its transcript is missing, or when the transcript has aged

815-

// past the orphan threshold (a live narrative refreshes its transcript well

816-

// within that window, so active runs are never reaped).

817-

async function isReclaimableDreamingStoreEntry(

818-

normalizedSessionFile: string | null,

819-

): Promise<boolean> {

820-

if (!normalizedSessionFile || !(await pathExists(normalizedSessionFile))) {

821-

return true;

822-

}

823-

try {

824-

const stat = await fs.stat(normalizedSessionFile);

825-

return Date.now() - stat.mtimeMs >= DREAMING_ORPHAN_MIN_AGE_MS;

826-

} catch {

827-

return true;

828-

}

829-

}

830-831-

async function normalizeSessionEntryPathForComparison(params: {

832-

sessionsDir: string;

833-

entry: { sessionFile?: string; sessionId?: string } | undefined;

834-

}): Promise<string | null> {

835-

const sessionFile = typeof params.entry?.sessionFile === "string" ? params.entry.sessionFile : "";

836-

if (sessionFile) {

837-

return normalizeSessionFileForComparison({

838-

sessionsDir: params.sessionsDir,

839-

sessionFile,

840-

});

841-

}

842-

const sessionId =

843-

typeof params.entry?.sessionId === "string" ? params.entry.sessionId.trim() : "";

844-

if (!SAFE_SESSION_ID_RE.test(sessionId)) {

845-

return null;

846-

}

847-

return normalizeSessionFileForComparison({

848-

sessionsDir: params.sessionsDir,

849-

sessionFile: `${sessionId}.jsonl`,

850-

});

851-

}

852-853773

async function scrubDreamingNarrativeArtifacts(logger: Logger): Promise<void> {

854774

const cfg = getRuntimeConfig();

855775

const agentsDir = path.join(resolveStateDir(), "agents");

@@ -868,112 +788,20 @@ async function scrubDreamingNarrativeArtifacts(logger: Logger): Promise<void> {

868788

continue;

869789

}

870790871-

const storePath = resolveStorePath(cfg.session?.store, { agentId: agentEntry.name });

872-

const sessionsDir = path.dirname(storePath);

873-

let store: Record<string, { sessionFile?: string; sessionId?: string } | undefined>;

874791

try {

875-

store = loadSessionStore(storePath) as Record<

876-

string,

877-

{ sessionFile?: string; sessionId?: string } | undefined

878-

>;

879-

} catch {

880-

continue;

881-

}

882-883-

const referencedSessionFiles = new Set<string>();

884-

let needsStoreUpdate = false;

885-

for (const [key, entry] of Object.entries(store)) {

886-

const normalizedSessionFile = await normalizeSessionEntryPathForComparison({

887-

sessionsDir,

888-

entry,

792+

const result = await cleanupSessionLifecycleArtifacts({

793+

agentId: agentEntry.name,

794+

archiveRemovedEntryTranscripts: false,

795+

sessionStore: cfg.session?.store,

796+

sessionKeySegmentPrefix: DREAMING_SESSION_KEY_PREFIX,

797+

transcriptContentMarker: DREAMING_TRANSCRIPT_RUN_MARKER,

798+

orphanTranscriptMinAgeMs: DREAMING_ORPHAN_MIN_AGE_MS,

889799

});

890-

if (normalizedSessionFile) {

891-

referencedSessionFiles.add(normalizedSessionFile);

892-

}

893-

if (!isDreamingSessionStoreKey(key)) {

894-

continue;

895-

}

896-

if (await isReclaimableDreamingStoreEntry(normalizedSessionFile)) {

897-

needsStoreUpdate = true;

898-

}

899-

}

900-901-

if (needsStoreUpdate) {

902-

referencedSessionFiles.clear();

903-

prunedEntries += await updateSessionStore(storePath, async (lockedStore) => {

904-

let prunedForAgent = 0;

905-

for (const [key, entry] of Object.entries(lockedStore)) {

906-

const normalizedSessionFile = await normalizeSessionEntryPathForComparison({

907-

sessionsDir,

908-

entry,

909-

});

910-

if (!isDreamingSessionStoreKey(key)) {

911-

if (normalizedSessionFile) {

912-

referencedSessionFiles.add(normalizedSessionFile);

913-

}

914-

continue;

915-

}

916-

if (await isReclaimableDreamingStoreEntry(normalizedSessionFile)) {

917-

// Drop the row and leave the transcript unreferenced so the orphan

918-

// transcript pass below archives the aged-out (or missing) file.

919-

delete lockedStore[key];

920-

prunedForAgent += 1;

921-

continue;

922-

}

923-

if (normalizedSessionFile) {

924-

referencedSessionFiles.add(normalizedSessionFile);

925-

}

926-

}

927-

return prunedForAgent;

928-

});

929-

}

930-931-

let sessionFiles: Dirent[];

932-

try {

933-

sessionFiles = await fs.readdir(sessionsDir, { withFileTypes: true });

800+

prunedEntries += result.removedEntries;

801+

archivedOrphans += result.archivedTranscriptArtifacts;

934802

} catch {

935803

continue;

936804

}

937-938-

for (const fileEntry of sessionFiles) {

939-

if (!fileEntry.isFile() || !fileEntry.name.endsWith(".jsonl")) {

940-

continue;

941-

}

942-

const transcriptPath = path.join(sessionsDir, fileEntry.name);

943-

const normalizedTranscriptPath =

944-

(await normalizeSessionFileForComparison({

945-

sessionsDir,

946-

sessionFile: fileEntry.name,

947-

})) ?? normalizeComparablePath(transcriptPath);

948-

if (referencedSessionFiles.has(normalizedTranscriptPath)) {

949-

continue;

950-

}

951-

let stat;

952-

try {

953-

stat = await fs.stat(transcriptPath);

954-

} catch {

955-

continue;

956-

}

957-

if (Date.now() - stat.mtimeMs < DREAMING_ORPHAN_MIN_AGE_MS) {

958-

continue;

959-

}

960-

let content;

961-

try {

962-

content = await fs.readFile(transcriptPath, "utf-8");

963-

} catch {

964-

continue;

965-

}

966-

if (!content.includes(DREAMING_TRANSCRIPT_RUN_MARKER)) {

967-

continue;

968-

}

969-

const archivedPath = `${transcriptPath}.deleted.${Date.now()}`;

970-

try {

971-

await fs.rename(transcriptPath, archivedPath);

972-

archivedOrphans += 1;

973-

} catch {

974-

// best-effort scrubber

975-

}

976-

}

977805

}

978806979807

if (prunedEntries > 0 || archivedOrphans > 0) {