Plugins: preserve memory prompt reload state

This commit is contained in:
Josh Lehman 2026-03-20 06:53:49 -07:00
parent 6f8f78c652
commit 800b74e365
No known key found for this signature in database
GPG Key ID: D141B425AC7F876B
2 changed files with 73 additions and 0 deletions

View File

@ -1189,6 +1189,73 @@ module.exports = { id: "skipped-scoped-only", register() { throw new Error("skip
clearPluginCommands();
});
it("does not replace the active memory prompt section during non-activating loads", () => {
useNoBundledPlugins();
registerMemoryPromptSection(() => ["active memory section"]);
const plugin = writePlugin({
id: "snapshot-memory",
filename: "snapshot-memory.cjs",
body: `module.exports = {
id: "snapshot-memory",
kind: "memory",
register(api) {
api.registerMemoryPromptSection(() => ["snapshot memory section"]);
},
};`,
});
const scoped = loadOpenClawPlugins({
cache: false,
activate: false,
workspaceDir: plugin.dir,
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["snapshot-memory"],
slots: { memory: "snapshot-memory" },
},
},
onlyPluginIds: ["snapshot-memory"],
});
expect(scoped.plugins.find((entry) => entry.id === "snapshot-memory")?.status).toBe("loaded");
expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([
"active memory section",
]);
});
it("clears a newly-registered memory prompt section when plugin register fails", () => {
useNoBundledPlugins();
const plugin = writePlugin({
id: "failing-memory",
filename: "failing-memory.cjs",
body: `module.exports = {
id: "failing-memory",
kind: "memory",
register(api) {
api.registerMemoryPromptSection(() => ["stale failure section"]);
throw new Error("memory register failed");
},
};`,
});
const registry = loadOpenClawPlugins({
cache: false,
workspaceDir: plugin.dir,
config: {
plugins: {
load: { paths: [plugin.file] },
allow: ["failing-memory"],
slots: { memory: "failing-memory" },
},
},
onlyPluginIds: ["failing-memory"],
});
expect(registry.plugins.find((entry) => entry.id === "failing-memory")?.status).toBe("error");
expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([]);
});
it("throws when activate:false is used without cache:false", () => {
expect(() => loadOpenClawPlugins({ activate: false })).toThrow(
"activate:false requires cache:false",

View File

@ -1252,6 +1252,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
hookPolicy: entry?.hooks,
registrationMode,
});
const previousMemoryPromptBuilder = getMemoryPromptSectionBuilder();
try {
const result = register(api);
@ -1263,9 +1264,14 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
message: "plugin register returned a promise; async registration is ignored",
});
}
// Snapshot loads should not replace process-global runtime prompt state.
if (!shouldActivate) {
restoreMemoryPromptSection(previousMemoryPromptBuilder);
}
registry.plugins.push(record);
seenIds.set(pluginId, candidate.origin);
} catch (err) {
restoreMemoryPromptSection(previousMemoryPromptBuilder);
recordPluginError({
logger,
registry,