From 2a372577d4dd079f449e79830f58d05261e06ec3 Mon Sep 17 00:00:00 2001 From: Vincent Koc Date: Thu, 9 Apr 2026 09:20:28 +0100 Subject: [PATCH] fix(memory-core): route bundled helpers through facade --- src/cli/capability-cli.test.ts | 6 +- src/cli/capability-cli.ts | 8 +- .../doctor.memory-core-runtime.ts | 4 +- .../memory-core-bundled-runtime.test.ts | 78 +++++++++++++++++++ src/plugin-sdk/memory-core-bundled-runtime.ts | 54 +++++++++++++ 5 files changed, 141 insertions(+), 9 deletions(-) create mode 100644 src/plugin-sdk/memory-core-bundled-runtime.test.ts create mode 100644 src/plugin-sdk/memory-core-bundled-runtime.ts diff --git a/src/cli/capability-cli.test.ts b/src/cli/capability-cli.test.ts index 4d1519618be..fea7468a730 100644 --- a/src/cli/capability-cli.test.ts +++ b/src/cli/capability-cli.test.ts @@ -189,11 +189,11 @@ vi.mock("../plugins/memory-embedding-providers.js", () => ({ mocks.registerMemoryEmbeddingProvider as unknown as typeof import("../plugins/memory-embedding-providers.js").registerMemoryEmbeddingProvider, })); -vi.mock("../../extensions/memory-core/runtime-api.js", () => ({ +vi.mock("../plugin-sdk/memory-core-bundled-runtime.js", () => ({ createEmbeddingProvider: - mocks.createEmbeddingProvider as unknown as typeof import("../../extensions/memory-core/runtime-api.js").createEmbeddingProvider, + mocks.createEmbeddingProvider as unknown as typeof import("../plugin-sdk/memory-core-bundled-runtime.js").createEmbeddingProvider, registerBuiltInMemoryEmbeddingProviders: - mocks.registerBuiltInMemoryEmbeddingProviders as typeof import("../../extensions/memory-core/runtime-api.js").registerBuiltInMemoryEmbeddingProviders, + mocks.registerBuiltInMemoryEmbeddingProviders as typeof import("../plugin-sdk/memory-core-bundled-runtime.js").registerBuiltInMemoryEmbeddingProviders, })); vi.mock("../image-generation/runtime.js", () => ({ diff --git a/src/cli/capability-cli.ts b/src/cli/capability-cli.ts index a8d3cefee8c..b8fe6a062ed 100644 --- a/src/cli/capability-cli.ts +++ b/src/cli/capability-cli.ts @@ -1,10 +1,6 @@ import fs from "node:fs/promises"; import path from "node:path"; import type { Command } from "commander"; -import { - createEmbeddingProvider, - registerBuiltInMemoryEmbeddingProviders, -} from "../../extensions/memory-core/runtime-api.js"; import { agentCommand } from "../agents/agent-command.js"; import { resolveAgentDir, resolveDefaultAgentId } from "../agents/agent-scope.js"; import { @@ -30,6 +26,10 @@ import { import { getImageMetadata } from "../media/image-ops.js"; import { detectMime, extensionForMime, normalizeMimeType } from "../media/mime.js"; import { saveMediaBuffer } from "../media/store.js"; +import { + createEmbeddingProvider, + registerBuiltInMemoryEmbeddingProviders, +} from "../plugin-sdk/memory-core-bundled-runtime.js"; import { listMemoryEmbeddingProviders, registerMemoryEmbeddingProvider, diff --git a/src/gateway/server-methods/doctor.memory-core-runtime.ts b/src/gateway/server-methods/doctor.memory-core-runtime.ts index 8cf62c3a5b6..f7900e1fed7 100644 --- a/src/gateway/server-methods/doctor.memory-core-runtime.ts +++ b/src/gateway/server-methods/doctor.memory-core-runtime.ts @@ -2,5 +2,5 @@ export { removeBackfillDiaryEntries, previewGroundedRemMarkdown, writeBackfillDiaryEntries, -} from "../../../extensions/memory-core/api.js"; -export { removeGroundedShortTermCandidates } from "../../../extensions/memory-core/runtime-api.js"; + removeGroundedShortTermCandidates, +} from "../../plugin-sdk/memory-core-bundled-runtime.js"; diff --git a/src/plugin-sdk/memory-core-bundled-runtime.test.ts b/src/plugin-sdk/memory-core-bundled-runtime.test.ts new file mode 100644 index 00000000000..995f7adac59 --- /dev/null +++ b/src/plugin-sdk/memory-core-bundled-runtime.test.ts @@ -0,0 +1,78 @@ +import { beforeEach, describe, expect, it, vi } from "vitest"; + +const loadBundledPluginPublicSurfaceModuleSync = vi.hoisted(() => vi.fn()); +const createEmbeddingProviderImpl = vi.hoisted(() => vi.fn()); +const registerBuiltInMemoryEmbeddingProvidersImpl = vi.hoisted(() => vi.fn()); +const removeGroundedShortTermCandidatesImpl = vi.hoisted(() => vi.fn()); +const previewGroundedRemMarkdownImpl = vi.hoisted(() => vi.fn()); +const writeBackfillDiaryEntriesImpl = vi.hoisted(() => vi.fn()); +const removeBackfillDiaryEntriesImpl = vi.hoisted(() => vi.fn()); + +vi.mock("./facade-loader.js", async () => { + const actual = await vi.importActual("./facade-loader.js"); + return { + ...actual, + loadBundledPluginPublicSurfaceModuleSync, + }; +}); + +describe("plugin-sdk memory-core bundled runtime", () => { + beforeEach(() => { + createEmbeddingProviderImpl.mockReset().mockResolvedValue({ provider: { id: "openai" } }); + registerBuiltInMemoryEmbeddingProvidersImpl.mockReset(); + removeGroundedShortTermCandidatesImpl.mockReset().mockResolvedValue({ removed: 1 }); + previewGroundedRemMarkdownImpl.mockReset().mockResolvedValue({ files: [] }); + writeBackfillDiaryEntriesImpl.mockReset().mockResolvedValue({ writtenCount: 1 }); + removeBackfillDiaryEntriesImpl.mockReset().mockResolvedValue({ removedCount: 1 }); + loadBundledPluginPublicSurfaceModuleSync + .mockReset() + .mockImplementation(({ artifactBasename }) => { + if (artifactBasename === "runtime-api.js") { + return { + createEmbeddingProvider: createEmbeddingProviderImpl, + registerBuiltInMemoryEmbeddingProviders: registerBuiltInMemoryEmbeddingProvidersImpl, + removeGroundedShortTermCandidates: removeGroundedShortTermCandidatesImpl, + }; + } + if (artifactBasename === "api.js") { + return { + previewGroundedRemMarkdown: previewGroundedRemMarkdownImpl, + writeBackfillDiaryEntries: writeBackfillDiaryEntriesImpl, + removeBackfillDiaryEntries: removeBackfillDiaryEntriesImpl, + }; + } + throw new Error(`unexpected artifact ${String(artifactBasename)}`); + }); + }); + + it("keeps the bundled memory facade cold until a helper is used", async () => { + const module = await import("./memory-core-bundled-runtime.js"); + + expect(loadBundledPluginPublicSurfaceModuleSync).not.toHaveBeenCalled(); + await module.createEmbeddingProvider({} as never); + expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + dirName: "memory-core", + artifactBasename: "runtime-api.js", + }); + }); + + it("delegates doctor and embedding helpers through the bundled public surfaces", async () => { + const module = await import("./memory-core-bundled-runtime.js"); + + await module.previewGroundedRemMarkdown({} as never); + await module.removeGroundedShortTermCandidates({} as never); + module.registerBuiltInMemoryEmbeddingProviders({} as never); + + expect(previewGroundedRemMarkdownImpl).toHaveBeenCalledWith({} as never); + expect(removeGroundedShortTermCandidatesImpl).toHaveBeenCalledWith({} as never); + expect(registerBuiltInMemoryEmbeddingProvidersImpl).toHaveBeenCalledWith({} as never); + expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + dirName: "memory-core", + artifactBasename: "api.js", + }); + expect(loadBundledPluginPublicSurfaceModuleSync).toHaveBeenCalledWith({ + dirName: "memory-core", + artifactBasename: "runtime-api.js", + }); + }); +}); diff --git a/src/plugin-sdk/memory-core-bundled-runtime.ts b/src/plugin-sdk/memory-core-bundled-runtime.ts new file mode 100644 index 00000000000..ccab75ce939 --- /dev/null +++ b/src/plugin-sdk/memory-core-bundled-runtime.ts @@ -0,0 +1,54 @@ +// Manual facade. Keep loader boundary explicit. +type ApiFacadeModule = typeof import("@openclaw/memory-core/api.js"); +type RuntimeFacadeModule = typeof import("@openclaw/memory-core/runtime-api.js"); +import { loadBundledPluginPublicSurfaceModuleSync } from "./facade-loader.js"; + +function loadApiFacadeModule(): ApiFacadeModule { + return loadBundledPluginPublicSurfaceModuleSync({ + dirName: "memory-core", + artifactBasename: "api.js", + }); +} + +function loadRuntimeFacadeModule(): RuntimeFacadeModule { + return loadBundledPluginPublicSurfaceModuleSync({ + dirName: "memory-core", + artifactBasename: "runtime-api.js", + }); +} + +export const createEmbeddingProvider: RuntimeFacadeModule["createEmbeddingProvider"] = ((...args) => + loadRuntimeFacadeModule().createEmbeddingProvider( + ...args, + )) as RuntimeFacadeModule["createEmbeddingProvider"]; + +export const registerBuiltInMemoryEmbeddingProviders: RuntimeFacadeModule["registerBuiltInMemoryEmbeddingProviders"] = + ((...args) => + loadRuntimeFacadeModule().registerBuiltInMemoryEmbeddingProviders( + ...args, + )) as RuntimeFacadeModule["registerBuiltInMemoryEmbeddingProviders"]; + +export const removeGroundedShortTermCandidates: RuntimeFacadeModule["removeGroundedShortTermCandidates"] = + ((...args) => + loadRuntimeFacadeModule().removeGroundedShortTermCandidates( + ...args, + )) as RuntimeFacadeModule["removeGroundedShortTermCandidates"]; + +export const previewGroundedRemMarkdown: ApiFacadeModule["previewGroundedRemMarkdown"] = (( + ...args +) => + loadApiFacadeModule().previewGroundedRemMarkdown( + ...args, + )) as ApiFacadeModule["previewGroundedRemMarkdown"]; + +export const writeBackfillDiaryEntries: ApiFacadeModule["writeBackfillDiaryEntries"] = ((...args) => + loadApiFacadeModule().writeBackfillDiaryEntries( + ...args, + )) as ApiFacadeModule["writeBackfillDiaryEntries"]; + +export const removeBackfillDiaryEntries: ApiFacadeModule["removeBackfillDiaryEntries"] = (( + ...args +) => + loadApiFacadeModule().removeBackfillDiaryEntries( + ...args, + )) as ApiFacadeModule["removeBackfillDiaryEntries"];