diff --git a/CHANGELOG.md b/CHANGELOG.md index 40b22bf18e3..0bd0b75bbf5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -43,6 +43,7 @@ Docs: https://docs.openclaw.ai - WebChat/exec approvals: use native approval UI guidance in agent system prompts instead of telling agents to paste manual `/approve` commands in webchat sessions. Thanks @vincentkoc. - Plugins/install: forward `--dangerously-force-unsafe-install` through archive and npm-spec plugin installs so the documented override reaches the security scanner on those install paths. (#58879) Thanks @ryanlee-gemini. - Chat/error replies: stop leaking raw provider/runtime failures into external chat channels, return a friendly retry message instead, and add a specific `/new` hint for Bedrock toolResult/toolUse session mismatches. (#58831) Thanks @ImLukeF. +- Memory/session indexing: keep full reindexes from skipping session transcripts when sync is triggered by `session-start` or `watch`, so restart-driven reindexes preserve session memory (#39732) thanks @upupc ## 2026.3.31 diff --git a/extensions/memory-core/src/memory/manager-sync-ops.ts b/extensions/memory-core/src/memory/manager-sync-ops.ts index 11d10561818..c10c5dc0c3d 100644 --- a/extensions/memory-core/src/memory/manager-sync-ops.ts +++ b/extensions/memory-core/src/memory/manager-sync-ops.ts @@ -680,13 +680,13 @@ export abstract class MemoryManagerSyncOps { if (params?.force) { return true; } + if (needsFullReindex) { + return true; + } const reason = params?.reason; if (reason === "session-start" || reason === "watch") { return false; } - if (needsFullReindex) { - return true; - } return this.sessionsDirty && this.sessionsDirtyFiles.size > 0; } diff --git a/extensions/memory-core/src/memory/manager.session-reindex.test.ts b/extensions/memory-core/src/memory/manager.session-reindex.test.ts new file mode 100644 index 00000000000..91875148636 --- /dev/null +++ b/extensions/memory-core/src/memory/manager.session-reindex.test.ts @@ -0,0 +1,78 @@ +import fs from "node:fs/promises"; +import os from "node:os"; +import path from "node:path"; +import type { OpenClawConfig } from "openclaw/plugin-sdk/memory-core-host-engine-foundation"; +import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it } from "vitest"; +import type { MemoryIndexManager } from "./index.js"; +import { getRequiredMemoryIndexManager } from "./test-manager-helpers.js"; + +describe("memory manager session reindex gating", () => { + let fixtureRoot = ""; + let caseId = 0; + let workspaceDir: string; + let indexPath: string; + let manager: MemoryIndexManager | null = null; + + beforeAll(async () => { + fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mem-session-reindex-")); + }); + + beforeEach(async () => { + workspaceDir = path.join(fixtureRoot, `case-${caseId++}`); + await fs.mkdir(path.join(workspaceDir, "memory"), { recursive: true }); + await fs.writeFile(path.join(workspaceDir, "MEMORY.md"), "Hello memory."); + indexPath = path.join(workspaceDir, "index.sqlite"); + }); + + afterEach(async () => { + if (manager) { + await manager.close(); + manager = null; + } + }); + + afterAll(async () => { + if (!fixtureRoot) { + return; + } + await fs.rm(fixtureRoot, { recursive: true, force: true }); + }); + + it("keeps session syncing enabled for full reindexes triggered from session-start/watch", async () => { + const cfg = { + agents: { + defaults: { + workspace: workspaceDir, + memorySearch: { + provider: "openai", + model: "mock-embed", + store: { path: indexPath, vector: { enabled: false } }, + cache: { enabled: false }, + query: { minScore: 0, hybrid: { enabled: false } }, + chunking: { tokens: 4000, overlap: 0 }, + experimental: { sessionMemory: true }, + sources: ["memory", "sessions"], + sync: { watch: false, onSessionStart: false, onSearch: false }, + }, + }, + list: [{ id: "main", default: true }], + }, + } as OpenClawConfig; + + manager = await getRequiredMemoryIndexManager({ cfg, agentId: "main" }); + + const shouldSyncSessions = ( + manager as unknown as { + shouldSyncSessions: ( + params?: { reason?: string; force?: boolean }, + needsFullReindex?: boolean, + ) => boolean; + } + ).shouldSyncSessions.bind(manager); + + expect(shouldSyncSessions({ reason: "session-start" }, true)).toBe(true); + expect(shouldSyncSessions({ reason: "watch" }, true)).toBe(true); + expect(shouldSyncSessions({ reason: "session-start" }, false)).toBe(false); + expect(shouldSyncSessions({ reason: "watch" }, false)).toBe(false); + }); +});