From 8f156df4ac83380568da23c6e73dcad6ba8d19f4 Mon Sep 17 00:00:00 2001 From: Sergio Cadavid Date: Sun, 12 Apr 2026 05:54:55 -0500 Subject: [PATCH] fix(plugins): restore cached memory capability on cache hits (#65240) Merged via squash. Prepared head SHA: 4209f056a1d1f497f0816614f79f60488e8e1d17 Co-authored-by: sercada <24389792+sercada@users.noreply.github.com> Co-authored-by: vincentkoc <25068+vincentkoc@users.noreply.github.com> Reviewed-by: @vincentkoc --- CHANGELOG.md | 1 + src/plugins/loader.test.ts | 69 ++++++++++++++++++++++++++++++++++++++ src/plugins/loader.ts | 4 +++ 3 files changed, 74 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9865a2cc8bc..8f19135c707 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -15,6 +15,7 @@ Docs: https://docs.openclaw.ai - Agents/Anthropic replay: preserve immutable signed-thinking replay safety across stored and live reruns, keep non-thinking embedded `tool_result` user blocks intact, and drop conflicting preserved tool IDs before validation so retries stop degrading into omitted tool calls. (#65126) Thanks @shakkernerd. - Telegram/direct sessions: keep commentary-only assistant fallback payloads out of visible direct delivery, so Codex planning chatter cannot leak into Telegram DMs when a run has no `final_answer` text. (#65112) Thanks @vincentkoc. - Infra/net: fix multipart FormData fields (including `model`) being silently dropped when a guarded runtime fetch body crosses a FormData implementation boundary, restoring OpenAI audio transcription requests that failed with HTTP 400. (#64349) Thanks @petr-sloup. +- Plugins/memory: restore cached memory capability public artifacts on plugin-registry cache hits so memory-backed artifact surfaces stay visible after warm loads. Thanks @sercada and @vincentkoc. ## 2026.4.11 diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index c6f2fb43a7d..93eafabe015 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -39,7 +39,9 @@ import { } from "./memory-embedding-providers.js"; import { buildMemoryPromptSection, + clearMemoryPluginState, getMemoryRuntime, + listActiveMemoryPublicArtifacts, listMemoryCorpusSupplements, registerMemoryCorpusSupplement, registerMemoryFlushPlanResolver, @@ -1809,6 +1811,73 @@ module.exports = { id: "throws-after-import", register() {} };`, expect(listMemoryEmbeddingProviders()).toEqual([]); }); + it("restores cached memory capability public artifacts on cache hits", async () => { + useNoBundledPlugins(); + const workspaceDir = makeTempDir(); + const absolutePath = path.join(workspaceDir, "MEMORY.md"); + fs.writeFileSync(absolutePath, "# Memory\n"); + const plugin = writePlugin({ + id: "cached-memory-capability", + filename: "cached-memory-capability.cjs", + body: `module.exports = { + id: "cached-memory-capability", + kind: "memory", + register(api) { + api.registerMemoryCapability({ + publicArtifacts: { + async listArtifacts() { + return [{ + kind: "memory-root", + workspaceDir: ${JSON.stringify(workspaceDir)}, + relativePath: "MEMORY.md", + absolutePath: ${JSON.stringify(absolutePath)}, + agentIds: ["main"], + contentType: "markdown", + }]; + }, + }, + }); + }, + };`, + }); + + const options = { + workspaceDir: plugin.dir, + config: { + plugins: { + load: { paths: [plugin.file] }, + allow: ["cached-memory-capability"], + slots: { memory: "cached-memory-capability" }, + }, + }, + onlyPluginIds: ["cached-memory-capability"], + }; + + const expectedArtifacts = [ + { + kind: "memory-root", + workspaceDir, + relativePath: "MEMORY.md", + absolutePath, + agentIds: ["main"], + contentType: "markdown" as const, + }, + ]; + + const first = loadOpenClawPlugins(options); + await expect(listActiveMemoryPublicArtifacts({ cfg: {} as never })).resolves.toEqual( + expectedArtifacts, + ); + + clearMemoryPluginState(); + + const second = loadOpenClawPlugins(options); + expect(second).toBe(first); + await expect(listActiveMemoryPublicArtifacts({ cfg: {} as never })).resolves.toEqual( + expectedArtifacts, + ); + }); + it("throws when activate:false is used without cache:false", () => { expect(() => loadOpenClawPlugins({ activate: false })).toThrow( "activate:false requires cache:false", diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index 5f8f089824d..e172f265e46 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -48,6 +48,7 @@ import { } from "./memory-embedding-providers.js"; import { clearMemoryPluginState, + getMemoryCapabilityRegistration, getMemoryFlushPlanResolver, getMemoryPromptSectionBuilder, getMemoryRuntime, @@ -148,6 +149,7 @@ export class PluginLoadReentryError extends Error { type CachedPluginState = { registry: PluginRegistry; + memoryCapability: ReturnType; memoryCorpusSupplements: ReturnType; agentHarnesses: ReturnType; compactionProviders: ReturnType; @@ -1104,6 +1106,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi restoreRegisteredCompactionProviders(cached.compactionProviders); restoreRegisteredMemoryEmbeddingProviders(cached.memoryEmbeddingProviders); restoreMemoryPluginState({ + capability: cached.memoryCapability, corpusSupplements: cached.memoryCorpusSupplements, promptBuilder: cached.memoryPromptBuilder, promptSupplements: cached.memoryPromptSupplements, @@ -1799,6 +1802,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi if (cacheEnabled) { setCachedPluginRegistry(cacheKey, { + memoryCapability: getMemoryCapabilityRegistration(), memoryCorpusSupplements: listMemoryCorpusSupplements(), registry, agentHarnesses: listRegisteredAgentHarnesses(),