refactor: move memory engine into memory plugin

This commit is contained in:
Peter Steinberger
2026-03-26 23:19:18 +00:00
parent 0e182dd3e1
commit cad83db8b2
42 changed files with 302 additions and 179 deletions

View File

@@ -6,7 +6,6 @@ import {
colorize,
defaultRuntime,
formatErrorMessage,
getMemorySearchManager,
isRich,
listMemoryFiles,
loadConfig,
@@ -25,6 +24,7 @@ import {
type OpenClawConfig,
} from "./api.js";
import type { MemoryCommandOptions, MemorySearchCommandOptions } from "./cli.types.js";
import { getMemorySearchManager } from "./runtime-api.js";
type MemoryManager = NonNullable<Awaited<ReturnType<typeof getMemorySearchManager>>["manager"]>;
type MemoryManagerPurpose = Parameters<typeof getMemorySearchManager>[0]["purpose"];

View File

@@ -24,13 +24,20 @@ vi.mock("./api.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./api.js")>();
return {
...actual,
getMemorySearchManager,
loadConfig,
resolveDefaultAgentId,
resolveCommandSecretRefsViaGateway,
};
});
vi.mock("./runtime-api.js", async (importOriginal) => {
const actual = await importOriginal<typeof import("./runtime-api.js")>();
return {
...actual,
getMemorySearchManager,
};
});
let registerMemoryCli: typeof import("./cli.js").registerMemoryCli;
let defaultRuntime: typeof import("./api.js").defaultRuntime;
let isVerbose: typeof import("./api.js").isVerbose;

View File

@@ -3,7 +3,7 @@ export type {
MemoryEmbeddingProbeResult,
MemorySearchManager,
MemorySearchResult,
} from "./types.js";
} from "../api.js";
export {
closeAllMemorySearchManagers,
getMemorySearchManager,

View File

@@ -1,31 +1,30 @@
import fs from "node:fs/promises";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { runGeminiEmbeddingBatches, type GeminiBatchRequest } from "./batch-gemini.js";
import {
OPENAI_BATCH_ENDPOINT,
type OpenAiBatchRequest,
runOpenAiEmbeddingBatches,
} from "./batch-openai.js";
import { type VoyageBatchRequest, runVoyageEmbeddingBatches } from "./batch-voyage.js";
import { enforceEmbeddingMaxInputTokens } from "./embedding-chunk-limits.js";
import {
estimateStructuredEmbeddingInputBytes,
estimateUtf8Bytes,
} from "./embedding-input-limits.js";
import { type EmbeddingInput, hasNonTextEmbeddingParts } from "./embedding-inputs.js";
import { buildGeminiEmbeddingRequest } from "./embeddings-gemini.js";
import {
buildGeminiEmbeddingRequest,
buildMultimodalChunkForIndexing,
chunkMarkdown,
createSubsystemLogger,
enforceEmbeddingMaxInputTokens,
estimateStructuredEmbeddingInputBytes,
estimateUtf8Bytes,
hasNonTextEmbeddingParts,
hashText,
parseEmbedding,
remapChunkLines,
runGeminiEmbeddingBatches,
runOpenAiEmbeddingBatches,
runVoyageEmbeddingBatches,
type EmbeddingInput,
type GeminiBatchRequest,
type MemoryChunk,
type MemoryFileEntry,
} from "./internal.js";
type MemorySource,
type OpenAiBatchRequest,
type SessionFileEntry,
type VoyageBatchRequest,
OPENAI_BATCH_ENDPOINT,
} from "../api.js";
import { MemoryManagerSyncOps } from "./manager-sync-ops.js";
import type { SessionFileEntry } from "./session-files.js";
import type { MemorySource } from "./types.js";
const VECTOR_TABLE = "chunks_vec";
const FTS_TABLE = "chunks_fts";

View File

@@ -1,6 +1,5 @@
import type { DatabaseSync } from "node:sqlite";
import { truncateUtf16Safe } from "../utils.js";
import { cosineSimilarity, parseEmbedding } from "./internal.js";
import { cosineSimilarity, parseEmbedding, truncateUtf16Safe } from "../api.js";
const vectorToBlob = (embedding: number[]): Buffer =>
Buffer.from(new Float32Array(embedding).buffer);

View File

@@ -4,52 +4,47 @@ import fs from "node:fs/promises";
import path from "node:path";
import type { DatabaseSync } from "node:sqlite";
import chokidar, { FSWatcher } from "chokidar";
import { resolveAgentDir } from "../agents/agent-scope.js";
import { ResolvedMemorySearchConfig } from "../agents/memory-search.js";
import { type OpenClawConfig } from "../config/config.js";
import { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { onSessionTranscriptUpdate } from "../sessions/transcript-events.js";
import { resolveUserPath } from "../utils.js";
import { DEFAULT_GEMINI_EMBEDDING_MODEL } from "./embeddings-gemini.js";
import { DEFAULT_MISTRAL_EMBEDDING_MODEL } from "./embeddings-mistral.js";
import { DEFAULT_OLLAMA_EMBEDDING_MODEL } from "./embeddings-ollama.js";
import { DEFAULT_OPENAI_EMBEDDING_MODEL } from "./embeddings-openai.js";
import { DEFAULT_VOYAGE_EMBEDDING_MODEL } from "./embeddings-voyage.js";
import {
DEFAULT_GEMINI_EMBEDDING_MODEL,
DEFAULT_MISTRAL_EMBEDDING_MODEL,
DEFAULT_OLLAMA_EMBEDDING_MODEL,
DEFAULT_OPENAI_EMBEDDING_MODEL,
DEFAULT_VOYAGE_EMBEDDING_MODEL,
buildCaseInsensitiveExtensionGlob,
buildFileEntry,
classifyMemoryMultimodalPath,
createEmbeddingProvider,
createSubsystemLogger,
ensureDir,
ensureMemoryIndexSchema,
getMemoryMultimodalExtensions,
hashText,
isFileMissingError,
listMemoryFiles,
listSessionFilesForAgent,
loadSqliteVecExtension,
normalizeExtraMemoryPaths,
onSessionTranscriptUpdate,
requireNodeSqlite,
resolveAgentDir,
resolveSessionTranscriptsDirForAgent,
resolveUserPath,
runWithConcurrency,
sessionPathForFile,
type MemoryFileEntry,
type MemorySource,
type MemorySyncProgressUpdate,
type OpenClawConfig,
type ResolvedMemorySearchConfig,
type SessionFileEntry,
type EmbeddingProvider,
type GeminiEmbeddingClient,
type MistralEmbeddingClient,
type OllamaEmbeddingClient,
type OpenAiEmbeddingClient,
type VoyageEmbeddingClient,
} from "./embeddings.js";
import { isFileMissingError } from "./fs-utils.js";
import {
buildFileEntry,
ensureDir,
hashText,
listMemoryFiles,
normalizeExtraMemoryPaths,
runWithConcurrency,
} from "./internal.js";
import { type MemoryFileEntry } from "./internal.js";
import { ensureMemoryIndexSchema } from "./memory-schema.js";
import {
buildCaseInsensitiveExtensionGlob,
classifyMemoryMultimodalPath,
getMemoryMultimodalExtensions,
} from "./multimodal.js";
import type { SessionFileEntry } from "./session-files.js";
import {
buildSessionEntry,
listSessionFilesForAgent,
sessionPathForFile,
} from "./session-files.js";
import { loadSqliteVecExtension } from "./sqlite-vec.js";
import { requireNodeSqlite } from "./sqlite.js";
import type { MemorySource, MemorySyncProgressUpdate } from "./types.js";
} from "../api.js";
type MemoryIndexMeta = {
model: string;

View File

@@ -1,11 +1,5 @@
import type { DatabaseSync } from "node:sqlite";
import { type FSWatcher } from "chokidar";
import { resolveAgentDir, resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import type { ResolvedMemorySearchConfig } from "../agents/memory-search.js";
import { resolveMemorySearchConfig } from "../agents/memory-search.js";
import type { OpenClawConfig } from "../config/config.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
import {
createEmbeddingProvider,
type EmbeddingProvider,
@@ -16,20 +10,25 @@ import {
type OllamaEmbeddingClient,
type OpenAiEmbeddingClient,
type VoyageEmbeddingClient,
} from "./embeddings.js";
extractKeywords,
readMemoryFile,
resolveAgentDir,
resolveAgentWorkspaceDir,
resolveGlobalSingleton,
resolveMemorySearchConfig,
type MemoryEmbeddingProbeResult,
type MemoryProviderStatus,
type MemorySearchManager,
type MemorySearchResult,
type MemorySource,
type MemorySyncProgressUpdate,
type OpenClawConfig,
type ResolvedMemorySearchConfig,
createSubsystemLogger,
} from "../api.js";
import { bm25RankToScore, buildFtsQuery, mergeHybridResults } from "./hybrid.js";
import { MemoryManagerEmbeddingOps } from "./manager-embedding-ops.js";
import { searchKeyword, searchVector } from "./manager-search.js";
import { extractKeywords } from "./query-expansion.js";
import { readMemoryFile } from "./read-file.js";
import type {
MemoryEmbeddingProbeResult,
MemoryProviderStatus,
MemorySearchManager,
MemorySearchResult,
MemorySource,
MemorySyncProgressUpdate,
} from "./types.js";
const SNIPPET_MAX_CHARS = 700;
const VECTOR_TABLE = "chunks_vec";
const FTS_TABLE = "chunks_fts";

View File

@@ -2,38 +2,39 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import readline from "node:readline";
import { resolveAgentWorkspaceDir } from "../agents/agent-scope.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveStateDir } from "../config/paths.js";
import { writeFileWithinRoot } from "../infra/fs-safe.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
import { isFileMissingError, statRegularFile } from "./fs-utils.js";
import { resolveCliSpawnInvocation, runCliCommand } from "./qmd-process.js";
import { deriveQmdScopeChannel, deriveQmdScopeChatType, isQmdScopeAllowed } from "./qmd-scope.js";
import {
listSessionFilesForAgent,
buildSessionEntry,
createSubsystemLogger,
deriveQmdScopeChannel,
deriveQmdScopeChatType,
extractKeywords,
isFileMissingError,
isQmdScopeAllowed,
listSessionFilesForAgent,
parseQmdQueryJson,
requireNodeSqlite,
resolveAgentWorkspaceDir,
resolveCliSpawnInvocation,
resolveGlobalSingleton,
resolveStateDir,
runCliCommand,
statRegularFile,
type MemoryEmbeddingProbeResult,
type MemoryProviderStatus,
type MemorySearchManager,
type MemorySearchResult,
type MemorySource,
type MemorySyncProgressUpdate,
type OpenClawConfig,
type QmdQueryResult,
type ResolvedMemoryBackendConfig,
type ResolvedQmdConfig,
type ResolvedQmdMcporterConfig,
type SessionFileEntry,
} from "./session-files.js";
import { requireNodeSqlite } from "./sqlite.js";
import type {
MemoryEmbeddingProbeResult,
MemoryProviderStatus,
MemorySearchManager,
MemorySearchResult,
MemorySource,
MemorySyncProgressUpdate,
} from "./types.js";
writeFileWithinRoot,
} from "../api.js";
type SqliteDatabase = import("node:sqlite").DatabaseSync;
import type {
ResolvedMemoryBackendConfig,
ResolvedQmdConfig,
ResolvedQmdMcporterConfig,
} from "./backend-config.js";
import { parseQmdQueryJson, type QmdQueryResult } from "./qmd-query-parser.js";
import { extractKeywords } from "./query-expansion.js";
const log = createSubsystemLogger("memory");

View File

@@ -1,13 +1,13 @@
import type { OpenClawConfig } from "../config/config.js";
import { createSubsystemLogger } from "../logging/subsystem.js";
import { resolveGlobalSingleton } from "../shared/global-singleton.js";
import type { ResolvedQmdConfig } from "./backend-config.js";
import { resolveMemoryBackendConfig } from "./backend-config.js";
import type {
MemoryEmbeddingProbeResult,
MemorySearchManager,
MemorySyncProgressUpdate,
} from "./types.js";
import {
createSubsystemLogger,
resolveGlobalSingleton,
resolveMemoryBackendConfig,
type MemoryEmbeddingProbeResult,
type MemorySearchManager,
type MemorySyncProgressUpdate,
type OpenClawConfig,
type ResolvedQmdConfig,
} from "../api.js";
const MEMORY_SEARCH_MANAGER_CACHE_KEY = Symbol.for("openclaw.memorySearchManagerCache");
type MemorySearchManagerCacheStore = {

View File

@@ -0,0 +1,2 @@
export * from "./api.js";
export * from "./memory/index.js";

View File

@@ -1,8 +1,5 @@
import {
getMemorySearchManager,
resolveMemoryBackendConfig,
type MemoryPluginRuntime,
} from "./api.js";
import { resolveMemoryBackendConfig, type MemoryPluginRuntime } from "./api.js";
import { closeAllMemorySearchManagers, getMemorySearchManager } from "./runtime-api.js";
export const memoryRuntime: MemoryPluginRuntime = {
async getMemorySearchManager(params) {
@@ -15,4 +12,7 @@ export const memoryRuntime: MemoryPluginRuntime = {
resolveMemoryBackendConfig(params) {
return resolveMemoryBackendConfig(params);
},
async closeAllMemorySearchManagers() {
await closeAllMemorySearchManagers();
},
};

View File

@@ -1 +1,2 @@
export { getMemorySearchManager, readAgentMemoryFile, resolveMemoryBackendConfig } from "./api.js";
export { readAgentMemoryFile, resolveMemoryBackendConfig } from "./api.js";
export { getMemorySearchManager } from "./runtime-api.js";

View File

@@ -8,7 +8,7 @@ import {
type MemoryToolRuntime = typeof import("./tools.runtime.js");
type MemorySearchManagerResult = Awaited<
ReturnType<(typeof import("./api.js"))["getMemorySearchManager"]>
ReturnType<(typeof import("./runtime-api.js"))["getMemorySearchManager"]>
>;
let memoryToolRuntimePromise: Promise<MemoryToolRuntime> | null = null;

View File

@@ -6,7 +6,7 @@ const loadDotEnvMock = vi.hoisted(() => vi.fn());
const normalizeEnvMock = vi.hoisted(() => vi.fn());
const ensurePathMock = vi.hoisted(() => vi.fn());
const assertRuntimeMock = vi.hoisted(() => vi.fn());
const closeAllMemorySearchManagersMock = vi.hoisted(() => vi.fn(async () => {}));
const closeActiveMemorySearchManagersMock = vi.hoisted(() => vi.fn(async () => {}));
const outputRootHelpMock = vi.hoisted(() => vi.fn());
const buildProgramMock = vi.hoisted(() => vi.fn());
const maybeRunCliInContainerMock = vi.hoisted(() =>
@@ -40,8 +40,8 @@ vi.mock("../infra/runtime-guard.js", () => ({
assertSupportedRuntime: assertRuntimeMock,
}));
vi.mock("../memory/search-manager.js", () => ({
closeAllMemorySearchManagers: closeAllMemorySearchManagersMock,
vi.mock("../plugins/memory-runtime.js", () => ({
closeActiveMemorySearchManagers: closeActiveMemorySearchManagersMock,
}));
vi.mock("./program/root-help.js", () => ({
@@ -69,7 +69,7 @@ describe("runCli exit behavior", () => {
expect(maybeRunCliInContainerMock).toHaveBeenCalledWith(["node", "openclaw", "status"]);
expect(tryRouteCliMock).toHaveBeenCalledWith(["node", "openclaw", "status"]);
expect(closeAllMemorySearchManagersMock).toHaveBeenCalledTimes(1);
expect(closeActiveMemorySearchManagersMock).toHaveBeenCalledTimes(1);
expect(exitSpy).not.toHaveBeenCalled();
exitSpy.mockRestore();
});
@@ -85,7 +85,7 @@ describe("runCli exit behavior", () => {
expect(tryRouteCliMock).not.toHaveBeenCalled();
expect(outputRootHelpMock).toHaveBeenCalledTimes(1);
expect(buildProgramMock).not.toHaveBeenCalled();
expect(closeAllMemorySearchManagersMock).toHaveBeenCalledTimes(1);
expect(closeActiveMemorySearchManagersMock).toHaveBeenCalledTimes(1);
expect(exitSpy).not.toHaveBeenCalled();
exitSpy.mockRestore();
});
@@ -104,7 +104,7 @@ describe("runCli exit behavior", () => {
]);
expect(loadDotEnvMock).not.toHaveBeenCalled();
expect(tryRouteCliMock).not.toHaveBeenCalled();
expect(closeAllMemorySearchManagersMock).not.toHaveBeenCalled();
expect(closeActiveMemorySearchManagersMock).not.toHaveBeenCalled();
});
it("propagates a handled container-target exit code", async () => {

View File

@@ -20,8 +20,8 @@ import { normalizeWindowsArgv } from "./windows-argv.js";
async function closeCliMemoryManagers(): Promise<void> {
try {
const { closeAllMemorySearchManagers } = await import("../memory/search-manager.js");
await closeAllMemorySearchManagers();
const { closeActiveMemorySearchManagers } = await import("../plugins/memory-runtime.js");
await closeActiveMemorySearchManagers();
} catch {
// Best-effort teardown for short-lived CLI processes.
}

View File

@@ -2,11 +2,14 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, beforeAll, beforeEach, expect, vi, type Mock } from "vitest";
import type {
MemoryIndexManager,
MemorySearchManager,
} from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import type { MemoryIndexManager, MemorySearchManager } from "./index.js";
type EmbeddingTestMocksModule = typeof import("./embedding.test-mocks.js");
type MemoryIndexModule = typeof import("./index.js");
type MemoryIndexModule = typeof import("../../extensions/memory-core/src/memory/index.js");
export function installEmbeddingManagerFixture(opts: {
fixturePrefix: string;
@@ -61,7 +64,7 @@ export function installEmbeddingManagerFixture(opts: {
const embeddingMocks = await import("./embedding.test-mocks.js");
embedBatch = embeddingMocks.getEmbedBatchMock();
resetEmbeddingMocks = embeddingMocks.resetEmbeddingMocks;
({ getMemorySearchManager } = await import("./index.js"));
({ getMemorySearchManager } = await import("../../extensions/memory-core/src/memory/index.js"));
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), opts.fixturePrefix));
workspaceDir = path.join(fixtureRoot, "workspace");
memoryDir = path.join(workspaceDir, "memory");

View File

@@ -1,5 +1,9 @@
import { describe, expect, it } from "vitest";
import { bm25RankToScore, buildFtsQuery, mergeHybridResults } from "./hybrid.js";
import {
bm25RankToScore,
buildFtsQuery,
mergeHybridResults,
} from "../../extensions/memory-core/src/memory/hybrid.js";
describe("memory hybrid helpers", () => {
it("buildFtsQuery tokenizes and AND-joins", () => {

View File

@@ -5,9 +5,9 @@ import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import "./test-runtime-mocks.js";
import type { MemoryIndexManager } from "./index.js";
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
type MemoryIndexModule = typeof import("./index.js");
type MemoryIndexModule = typeof import("../../extensions/memory-core/src/memory/index.js");
let getMemorySearchManager: MemoryIndexModule["getMemorySearchManager"];
let closeAllMemorySearchManagers: MemoryIndexModule["closeAllMemorySearchManagers"];
@@ -131,7 +131,8 @@ describe("memory index", () => {
beforeAll(async () => {
vi.resetModules();
await import("./test-runtime-mocks.js");
({ getMemorySearchManager, closeAllMemorySearchManagers } = await import("./index.js"));
({ getMemorySearchManager, closeAllMemorySearchManagers } =
await import("../../extensions/memory-core/src/memory/index.js"));
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mem-fixtures-"));
workspaceDir = path.join(fixtureRoot, "workspace");
memoryDir = path.join(workspaceDir, "memory");

View File

@@ -2,9 +2,9 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
import { closeAllMemorySearchManagers } from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import type { MemoryIndexManager } from "./index.js";
import { closeAllMemorySearchManagers } from "./index.js";
import { createOpenAIEmbeddingProviderMock } from "./test-embeddings-mock.js";
import { createMemoryManagerOrThrow } from "./test-manager.js";

View File

@@ -2,14 +2,14 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import type { MemoryIndexManager } from "./index.js";
let shouldFail = false;
type EmbeddingTestMocksModule = typeof import("./embedding.test-mocks.js");
type TestManagerHelpersModule = typeof import("./test-manager-helpers.js");
type MemoryIndexModule = typeof import("./index.js");
type MemoryIndexModule = typeof import("../../extensions/memory-core/src/memory/index.js");
describe("memory manager atomic reindex", () => {
let fixtureRoot = "";
@@ -32,7 +32,8 @@ describe("memory manager atomic reindex", () => {
embedBatch = embeddingMocks.getEmbedBatchMock();
resetEmbeddingMocks = embeddingMocks.resetEmbeddingMocks;
({ getRequiredMemoryIndexManager } = await import("./test-manager-helpers.js"));
({ closeAllMemorySearchManagers } = await import("./index.js"));
({ closeAllMemorySearchManagers } =
await import("../../extensions/memory-core/src/memory/index.js"));
vi.stubEnv("OPENCLAW_TEST_MEMORY_UNSAFE_REINDEX", "0");
resetEmbeddingMocks();
shouldFail = false;

View File

@@ -7,8 +7,9 @@ import type { OpenClawConfig } from "../config/config.js";
import { createOpenAIEmbeddingProviderMock } from "./test-embeddings-mock.js";
import { mockPublicPinnedHostname } from "./test-helpers/ssrf.js";
type MemoryIndexManager = import("./index.js").MemoryIndexManager;
type MemoryIndexModule = typeof import("./index.js");
type MemoryIndexManager =
import("../../extensions/memory-core/src/memory/index.js").MemoryIndexManager;
type MemoryIndexModule = typeof import("../../extensions/memory-core/src/memory/index.js");
const embedBatch = vi.fn(async (_texts: string[]) => [] as number[][]);
const embedQuery = vi.fn(async () => [0.5, 0.5, 0.5]);
@@ -121,7 +122,7 @@ describe("memory indexing with OpenAI batches", () => {
}),
}));
await import("./test-runtime-mocks.js");
({ getMemorySearchManager } = await import("./index.js"));
({ getMemorySearchManager } = await import("../../extensions/memory-core/src/memory/index.js"));
fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mem-batch-"));
workspaceDir = path.join(fixtureRoot, "workspace");

View File

@@ -3,12 +3,12 @@ import os from "node:os";
import path from "node:path";
import { setTimeout as sleep } from "node:timers/promises";
import { afterEach, beforeAll, beforeEach, describe, expect, it, vi } from "vitest";
import type { OpenClawConfig } from "../config/config.js";
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
import "./test-runtime-mocks.js";
import type { MemoryIndexManager } from "./index.js";
import type { OpenClawConfig } from "../config/config.js";
type MemoryIndexModule = typeof import("./index.js");
type ManagerModule = typeof import("./manager.js");
type MemoryIndexModule = typeof import("../../extensions/memory-core/src/memory/index.js");
type ManagerModule = typeof import("../../extensions/memory-core/src/memory/manager.js");
const hoisted = vi.hoisted(() => ({
providerCreateCalls: 0,
@@ -43,9 +43,10 @@ describe("memory manager cache hydration", () => {
let workspaceDir = "";
beforeAll(async () => {
({ getMemorySearchManager, closeAllMemorySearchManagers } = await import("./index.js"));
({ getMemorySearchManager, closeAllMemorySearchManagers } =
await import("../../extensions/memory-core/src/memory/index.js"));
({ closeAllMemoryIndexManagers, MemoryIndexManager: RawMemoryIndexManager } =
await import("./manager.js"));
await import("../../extensions/memory-core/src/memory/manager.js"));
});
beforeEach(async () => {

View File

@@ -2,6 +2,7 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import { DEFAULT_OLLAMA_EMBEDDING_MODEL } from "./embeddings-ollama.js";
import type {
@@ -11,7 +12,6 @@ import type {
OllamaEmbeddingClient,
OpenAiEmbeddingClient,
} from "./embeddings.js";
import type { MemoryIndexManager } from "./index.js";
const { createEmbeddingProviderMock } = vi.hoisted(() => ({
createEmbeddingProviderMock: vi.fn(),
@@ -25,7 +25,7 @@ vi.mock("./sqlite-vec.js", () => ({
loadSqliteVecExtension: async () => ({ ok: false, error: "sqlite-vec disabled in tests" }),
}));
type MemoryIndexModule = typeof import("./index.js");
type MemoryIndexModule = typeof import("../../extensions/memory-core/src/memory/index.js");
let getMemorySearchManager: MemoryIndexModule["getMemorySearchManager"];
let closeAllMemorySearchManagers: MemoryIndexModule["closeAllMemorySearchManagers"];
@@ -78,7 +78,8 @@ describe("memory manager mistral provider wiring", () => {
beforeEach(async () => {
vi.resetModules();
({ getMemorySearchManager, closeAllMemorySearchManagers } = await import("./index.js"));
({ getMemorySearchManager, closeAllMemorySearchManagers } =
await import("../../extensions/memory-core/src/memory/index.js"));
vi.clearAllMocks();
createEmbeddingProviderMock.mockReset();
workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-memory-mistral-"));

View File

@@ -2,9 +2,9 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterAll, afterEach, beforeAll, describe, expect, it, vi } from "vitest";
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import { resetEmbeddingMocks } from "./embedding.test-mocks.js";
import type { MemoryIndexManager } from "./index.js";
import { getRequiredMemoryIndexManager } from "./test-manager-helpers.js";
function createMemorySearchCfg(options: {

View File

@@ -3,9 +3,9 @@ import os from "node:os";
import path from "node:path";
import type { DatabaseSync } from "node:sqlite";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { MemoryIndexManager } from "../../extensions/memory-core/src/memory/manager.js";
import type { OpenClawConfig } from "../config/config.js";
import { resetEmbeddingMocks } from "./embedding.test-mocks.js";
import { MemoryIndexManager } from "./manager.js";
import { getRequiredMemoryIndexManager } from "./test-manager-helpers.js";
type ReadonlyRecoveryHarness = {

View File

@@ -1,5 +1,5 @@
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import { runDetachedMemorySync } from "./manager-sync-ops.js";
import { runDetachedMemorySync } from "../../extensions/memory-core/src/memory/manager-sync-ops.js";
describe("memory manager sync failures", () => {
beforeEach(() => {

View File

@@ -2,8 +2,8 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import type { MemoryIndexManager } from "./index.js";
vi.mock("./embeddings.js", () => {
return {
@@ -21,7 +21,7 @@ vi.mock("./embeddings.js", () => {
type MemoryInternalModule = typeof import("./internal.js");
type TestManagerModule = typeof import("./test-manager.js");
type MemoryIndexModule = typeof import("./index.js");
type MemoryIndexModule = typeof import("../../extensions/memory-core/src/memory/index.js");
let buildFileEntry: MemoryInternalModule["buildFileEntry"];
let createMemoryManagerOrThrow: TestManagerModule["createMemoryManagerOrThrow"];
@@ -57,7 +57,8 @@ describe("memory vector dedupe", () => {
vi.resetModules();
({ buildFileEntry } = await import("./internal.js"));
({ createMemoryManagerOrThrow } = await import("./test-manager.js"));
({ closeAllMemorySearchManagers } = await import("./index.js"));
({ closeAllMemorySearchManagers } =
await import("../../extensions/memory-core/src/memory/index.js"));
workspaceDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-mem-"));
indexPath = path.join(workspaceDir, "index.sqlite");
await seedMemoryWorkspace(workspaceDir);

View File

@@ -2,9 +2,9 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import type { MemorySearchConfig } from "../config/types.tools.js";
import type { MemoryIndexManager } from "./index.js";
const { watchMock } = vi.hoisted(() => ({
watchMock: vi.fn(() => ({
@@ -34,7 +34,7 @@ vi.mock("./embeddings.js", () => ({
}),
}));
type MemoryIndexModule = typeof import("./index.js");
type MemoryIndexModule = typeof import("../../extensions/memory-core/src/memory/index.js");
let getMemorySearchManager: MemoryIndexModule["getMemorySearchManager"];
let closeAllMemorySearchManagers: MemoryIndexModule["closeAllMemorySearchManagers"];
@@ -46,7 +46,8 @@ describe("memory watcher config", () => {
beforeEach(async () => {
vi.resetModules();
({ getMemorySearchManager, closeAllMemorySearchManagers } = await import("./index.js"));
({ getMemorySearchManager, closeAllMemorySearchManagers } =
await import("../../extensions/memory-core/src/memory/index.js"));
vi.clearAllMocks();
});

View File

@@ -8,7 +8,7 @@ import {
applyMMRToHybridResults,
DEFAULT_MMR_CONFIG,
type MMRItem,
} from "./mmr.js";
} from "../../extensions/memory-core/src/memory/mmr.js";
describe("tokenize", () => {
it("normalizes, filters, and deduplicates token sets", () => {

View File

@@ -88,9 +88,9 @@ vi.mock("node:child_process", async (importOriginal) => {
});
import { spawn as mockedSpawn } from "node:child_process";
import { QmdMemoryManager } from "../../extensions/memory-core/src/memory/qmd-manager.js";
import type { OpenClawConfig } from "../config/config.js";
import { resolveMemoryBackendConfig } from "./backend-config.js";
import { QmdMemoryManager } from "./qmd-manager.js";
import { requireNodeSqlite } from "./sqlite.js";
const spawnMock = mockedSpawn as unknown as Mock;

View File

@@ -96,7 +96,7 @@ const fallbackSearch = fallbackManager.search;
const mockMemoryIndexGet = vi.hoisted(() => vi.fn(async () => fallbackManager));
const mockCloseAllMemoryIndexManagers = vi.hoisted(() => vi.fn(async () => {}));
vi.mock("./qmd-manager.js", () => ({
vi.mock("../../extensions/memory-core/src/memory/qmd-manager.js", () => ({
QmdMemoryManager: {
create: vi.fn(async () => mockPrimary),
},
@@ -109,8 +109,11 @@ vi.mock("./manager-runtime.js", () => ({
closeAllMemoryIndexManagers: mockCloseAllMemoryIndexManagers,
}));
import { QmdMemoryManager } from "./qmd-manager.js";
import { closeAllMemorySearchManagers, getMemorySearchManager } from "./search-manager.js";
import { QmdMemoryManager } from "../../extensions/memory-core/src/memory/qmd-manager.js";
import {
closeAllMemorySearchManagers,
getMemorySearchManager,
} from "../../extensions/memory-core/src/memory/search-manager.js";
// eslint-disable-next-line @typescript-eslint/unbound-method -- mocked static function
const createQmdManagerMock = vi.mocked(QmdMemoryManager.create);

View File

@@ -2,12 +2,12 @@ import fs from "node:fs/promises";
import os from "node:os";
import path from "node:path";
import { afterEach, describe, expect, it } from "vitest";
import { mergeHybridResults } from "./hybrid.js";
import { mergeHybridResults } from "../../extensions/memory-core/src/memory/hybrid.js";
import {
applyTemporalDecayToHybridResults,
applyTemporalDecayToScore,
calculateTemporalDecayMultiplier,
} from "./temporal-decay.js";
} from "../../extensions/memory-core/src/memory/temporal-decay.js";
const DAY_MS = 24 * 60 * 60 * 1000;
const NOW_MS = Date.UTC(2026, 1, 10, 0, 0, 0);

View File

@@ -1,5 +1,5 @@
import type { MemoryIndexManager } from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import type { MemoryIndexManager } from "./index.js";
export async function getRequiredMemoryIndexManager(params: {
cfg: OpenClawConfig;
@@ -7,7 +7,8 @@ export async function getRequiredMemoryIndexManager(params: {
purpose?: "default" | "status";
}): Promise<MemoryIndexManager> {
await import("./embedding.test-mocks.js");
const { getMemorySearchManager } = await import("./index.js");
const { getMemorySearchManager } =
await import("../../extensions/memory-core/src/memory/index.js");
const result = await getMemorySearchManager({
cfg: params.cfg,
agentId: params.agentId ?? "main",

View File

@@ -1,5 +1,8 @@
import {
getMemorySearchManager,
type MemoryIndexManager,
} from "../../extensions/memory-core/src/memory/index.js";
import type { OpenClawConfig } from "../config/config.js";
import { getMemorySearchManager, type MemoryIndexManager } from "./index.js";
export async function createMemoryManagerOrThrow(
cfg: OpenClawConfig,

View File

@@ -5,11 +5,15 @@ export type { AnyAgentTool } from "../agents/tools/common.js";
export { resolveCronStyleNow } from "../agents/current-time.js";
export { DEFAULT_PI_COMPACTION_RESERVE_TOKENS_FLOOR } from "../agents/pi-settings.js";
export {
resolveAgentDir,
resolveAgentWorkspaceDir,
resolveDefaultAgentId,
resolveSessionAgentId,
} from "../agents/agent-scope.js";
export { resolveMemorySearchConfig } from "../agents/memory-search.js";
export {
resolveMemorySearchConfig,
type ResolvedMemorySearchConfig,
} from "../agents/memory-search.js";
export { jsonResult, readNumberParam, readStringParam } from "../agents/tools/common.js";
export { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
export { formatErrorMessage, withManager } from "../cli/cli-utils.js";
@@ -21,22 +25,112 @@ export { parseNonNegativeByteSize } from "../config/byte-size.js";
export { loadConfig } from "../config/config.js";
export { resolveStateDir } from "../config/paths.js";
export { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
export { listMemoryFiles, normalizeExtraMemoryPaths } from "../memory/internal.js";
export { readAgentMemoryFile } from "../memory/read-file.js";
export { writeFileWithinRoot } from "../infra/fs-safe.js";
export { createSubsystemLogger } from "../logging/subsystem.js";
export { resolveGlobalSingleton } from "../shared/global-singleton.js";
export { onSessionTranscriptUpdate } from "../sessions/transcript-events.js";
export {
buildFileEntry,
buildMultimodalChunkForIndexing,
chunkMarkdown,
cosineSimilarity,
ensureDir,
hashText,
listMemoryFiles,
normalizeExtraMemoryPaths,
parseEmbedding,
remapChunkLines,
runWithConcurrency,
type MemoryChunk,
type MemoryFileEntry,
} from "../memory/internal.js";
export { readAgentMemoryFile, readMemoryFile } from "../memory/read-file.js";
export { resolveMemoryBackendConfig } from "../memory/backend-config.js";
export type {
ResolvedMemoryBackendConfig,
ResolvedQmdConfig,
ResolvedQmdMcporterConfig,
} from "../memory/backend-config.js";
export type {
MemoryEmbeddingProbeResult,
MemoryProviderStatus,
MemorySearchManager,
MemorySearchResult,
MemorySource,
MemorySyncProgressUpdate,
} from "../memory/types.js";
export {
createEmbeddingProvider,
type EmbeddingProvider,
type EmbeddingProviderRequest,
type EmbeddingProviderResult,
type GeminiEmbeddingClient,
type MistralEmbeddingClient,
type OllamaEmbeddingClient,
type OpenAiEmbeddingClient,
type VoyageEmbeddingClient,
} from "../memory/embeddings.js";
export {
DEFAULT_GEMINI_EMBEDDING_MODEL,
buildGeminiEmbeddingRequest,
} from "../memory/embeddings-gemini.js";
export { DEFAULT_MISTRAL_EMBEDDING_MODEL } from "../memory/embeddings-mistral.js";
export { DEFAULT_OLLAMA_EMBEDDING_MODEL } from "../memory/embeddings-ollama.js";
export { DEFAULT_OPENAI_EMBEDDING_MODEL } from "../memory/embeddings-openai.js";
export { DEFAULT_VOYAGE_EMBEDDING_MODEL } from "../memory/embeddings-voyage.js";
export { runGeminiEmbeddingBatches, type GeminiBatchRequest } from "../memory/batch-gemini.js";
export {
OPENAI_BATCH_ENDPOINT,
runOpenAiEmbeddingBatches,
type OpenAiBatchRequest,
} from "../memory/batch-openai.js";
export { runVoyageEmbeddingBatches, type VoyageBatchRequest } from "../memory/batch-voyage.js";
export { enforceEmbeddingMaxInputTokens } from "../memory/embedding-chunk-limits.js";
export {
estimateStructuredEmbeddingInputBytes,
estimateUtf8Bytes,
} from "../memory/embedding-input-limits.js";
export { hasNonTextEmbeddingParts, type EmbeddingInput } from "../memory/embedding-inputs.js";
export {
buildCaseInsensitiveExtensionGlob,
classifyMemoryMultimodalPath,
getMemoryMultimodalExtensions,
} from "../memory/multimodal.js";
export { ensureMemoryIndexSchema } from "../memory/memory-schema.js";
export { loadSqliteVecExtension } from "../memory/sqlite-vec.js";
export { requireNodeSqlite } from "../memory/sqlite.js";
export { extractKeywords, isQueryStopWordToken } from "../memory/query-expansion.js";
export {
buildSessionEntry,
listSessionFilesForAgent,
sessionPathForFile,
type SessionFileEntry,
} from "../memory/session-files.js";
export { parseQmdQueryJson, type QmdQueryResult } from "../memory/qmd-query-parser.js";
export {
deriveQmdScopeChannel,
deriveQmdScopeChatType,
isQmdScopeAllowed,
} from "../memory/qmd-scope.js";
export { isFileMissingError, statRegularFile } from "../memory/fs-utils.js";
export { resolveCliSpawnInvocation, runCliCommand } from "../memory/qmd-process.js";
export {
hasConfiguredSecretInput,
normalizeResolvedSecretInputString,
} from "../config/types.secrets.js";
export { emptyPluginConfigSchema } from "../plugins/config-schema.js";
export { getMemorySearchManager } from "../memory/index.js";
export { parseAgentSessionKey } from "../routing/session-key.js";
export { defaultRuntime } from "../runtime.js";
export { colorize, isRich, theme } from "../terminal/theme.js";
export { formatDocsLink } from "../terminal/links.js";
export { detectMime } from "../media/mime.js";
export { setVerbose, isVerbose } from "../globals.js";
export { shortenHomeInString, shortenHomePath, resolveUserPath } from "../utils.js";
export {
shortenHomeInString,
shortenHomePath,
resolveUserPath,
truncateUtf16Safe,
} from "../utils.js";
export { splitShellArgs } from "../utils/shell-argv.js";
export { runTasksWithConcurrency } from "../utils/run-with-concurrency.js";
export type { OpenClawConfig } from "../config/config.js";
@@ -50,7 +144,6 @@ export type {
MemoryQmdSearchMode,
} from "../config/types.memory.js";
export type { SecretInput } from "../config/types.secrets.js";
export type { MemorySearchResult } from "../memory/types.js";
export type {
MemoryFlushPlan,
MemoryFlushPlanResolver,

View File

@@ -14,7 +14,7 @@ export { SILENT_REPLY_TOKEN } from "../auto-reply/tokens.js";
export { loadConfig } from "../config/config.js";
export { resolveStateDir } from "../config/paths.js";
export { resolveSessionTranscriptsDirForAgent } from "../config/sessions/paths.js";
export { getMemorySearchManager } from "../memory/index.js";
export { getMemorySearchManager } from "../../extensions/memory-core/src/memory/index.js";
export { listMemoryFiles, normalizeExtraMemoryPaths } from "../memory/internal.js";
export { readAgentMemoryFile } from "../memory/read-file.js";
export { resolveMemoryBackendConfig } from "../memory/backend-config.js";

View File

@@ -26,3 +26,8 @@ export async function getActiveMemorySearchManager(params: {
export function resolveActiveMemoryBackendConfig(params: { cfg: OpenClawConfig; agentId: string }) {
return ensureMemoryRuntime(params.cfg)?.resolveMemoryBackendConfig(params) ?? null;
}
export async function closeActiveMemorySearchManagers(cfg?: OpenClawConfig): Promise<void> {
const runtime = ensureMemoryRuntime(cfg);
await runtime?.closeAllMemorySearchManagers?.();
}

View File

@@ -56,6 +56,7 @@ export type MemoryPluginRuntime = {
cfg: OpenClawConfig;
agentId: string;
}): MemoryRuntimeBackendConfig;
closeAllMemorySearchManagers?(): Promise<void>;
};
type MemoryPluginState = {