mirror of
https://github.com/openclaw/openclaw.git
synced 2026-05-01 15:00:24 +00:00
refactor: move memory flush ownership into memory plugin
This commit is contained in:
@@ -3,6 +3,7 @@ import os from "node:os";
|
||||
import path from "node:path";
|
||||
import { afterAll, afterEach, describe, expect, it } from "vitest";
|
||||
import { emitDiagnosticEvent, resetDiagnosticEventsForTest } from "../infra/diagnostic-events.js";
|
||||
import { registerMemoryFlushPlanResolver, resolveMemoryFlushPlan } from "../memory/flush-plan.js";
|
||||
import { buildMemoryPromptSection, registerMemoryPromptSection } from "../memory/prompt-section.js";
|
||||
import { withEnv } from "../test-utils/env.js";
|
||||
import { clearPluginCommands, getPluginCommandSpecs } from "./command-registry-state.js";
|
||||
@@ -1048,9 +1049,17 @@ module.exports = { id: "skipped-scoped-only", register() { throw new Error("skip
|
||||
expect(scoped.providers.map((entry) => entry.provider.id)).toEqual(["deepseek"]);
|
||||
});
|
||||
|
||||
it("does not replace the active memory prompt section during non-activating loads", () => {
|
||||
it("does not replace active memory plugin registries during non-activating loads", () => {
|
||||
useNoBundledPlugins();
|
||||
registerMemoryPromptSection(() => ["active memory section"]);
|
||||
registerMemoryFlushPlanResolver(() => ({
|
||||
softThresholdTokens: 1,
|
||||
forceFlushTranscriptBytes: 2,
|
||||
reserveTokensFloor: 3,
|
||||
prompt: "active",
|
||||
systemPrompt: "active",
|
||||
relativePath: "memory/active.md",
|
||||
}));
|
||||
const plugin = writePlugin({
|
||||
id: "snapshot-memory",
|
||||
filename: "snapshot-memory.cjs",
|
||||
@@ -1059,6 +1068,14 @@ module.exports = { id: "skipped-scoped-only", register() { throw new Error("skip
|
||||
kind: "memory",
|
||||
register(api) {
|
||||
api.registerMemoryPromptSection(() => ["snapshot memory section"]);
|
||||
api.registerMemoryFlushPlan(() => ({
|
||||
softThresholdTokens: 10,
|
||||
forceFlushTranscriptBytes: 20,
|
||||
reserveTokensFloor: 30,
|
||||
prompt: "snapshot",
|
||||
systemPrompt: "snapshot",
|
||||
relativePath: "memory/snapshot.md",
|
||||
}));
|
||||
},
|
||||
};`,
|
||||
});
|
||||
@@ -1081,9 +1098,10 @@ module.exports = { id: "skipped-scoped-only", register() { throw new Error("skip
|
||||
expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([
|
||||
"active memory section",
|
||||
]);
|
||||
expect(resolveMemoryFlushPlan({})?.relativePath).toBe("memory/active.md");
|
||||
});
|
||||
|
||||
it("clears a newly-registered memory prompt section when plugin register fails", () => {
|
||||
it("clears newly-registered memory plugin registries when plugin register fails", () => {
|
||||
useNoBundledPlugins();
|
||||
const plugin = writePlugin({
|
||||
id: "failing-memory",
|
||||
@@ -1093,6 +1111,14 @@ module.exports = { id: "skipped-scoped-only", register() { throw new Error("skip
|
||||
kind: "memory",
|
||||
register(api) {
|
||||
api.registerMemoryPromptSection(() => ["stale failure section"]);
|
||||
api.registerMemoryFlushPlan(() => ({
|
||||
softThresholdTokens: 10,
|
||||
forceFlushTranscriptBytes: 20,
|
||||
reserveTokensFloor: 30,
|
||||
prompt: "failed",
|
||||
systemPrompt: "failed",
|
||||
relativePath: "memory/failed.md",
|
||||
}));
|
||||
throw new Error("memory register failed");
|
||||
},
|
||||
};`,
|
||||
@@ -1113,6 +1139,7 @@ module.exports = { id: "skipped-scoped-only", register() { throw new Error("skip
|
||||
|
||||
expect(registry.plugins.find((entry) => entry.id === "failing-memory")?.status).toBe("error");
|
||||
expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([]);
|
||||
expect(resolveMemoryFlushPlan({})).toBeNull();
|
||||
});
|
||||
|
||||
it("throws when activate:false is used without cache:false", () => {
|
||||
@@ -3374,14 +3401,24 @@ export const runtimeValue = helperValue;`,
|
||||
});
|
||||
|
||||
describe("clearPluginLoaderCache", () => {
|
||||
it("resets the registered memory prompt section builder", () => {
|
||||
it("resets registered memory plugin registries", () => {
|
||||
registerMemoryPromptSection(() => ["stale memory section"]);
|
||||
registerMemoryFlushPlanResolver(() => ({
|
||||
softThresholdTokens: 1,
|
||||
forceFlushTranscriptBytes: 2,
|
||||
reserveTokensFloor: 3,
|
||||
prompt: "stale",
|
||||
systemPrompt: "stale",
|
||||
relativePath: "memory/stale.md",
|
||||
}));
|
||||
expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([
|
||||
"stale memory section",
|
||||
]);
|
||||
expect(resolveMemoryFlushPlan({})?.relativePath).toBe("memory/stale.md");
|
||||
|
||||
clearPluginLoaderCache();
|
||||
|
||||
expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([]);
|
||||
expect(resolveMemoryFlushPlan({})).toBeNull();
|
||||
});
|
||||
});
|
||||
|
||||
@@ -8,6 +8,11 @@ import type { PluginInstallRecord } from "../config/types.plugins.js";
|
||||
import type { GatewayRequestHandler } from "../gateway/server-methods/types.js";
|
||||
import { openBoundaryFileSync } from "../infra/boundary-file-read.js";
|
||||
import { createSubsystemLogger } from "../logging/subsystem.js";
|
||||
import {
|
||||
clearMemoryFlushPlanResolver,
|
||||
getMemoryFlushPlanResolver,
|
||||
restoreMemoryFlushPlanResolver,
|
||||
} from "../memory/flush-plan.js";
|
||||
import {
|
||||
clearMemoryPromptSection,
|
||||
getMemoryPromptSectionBuilder,
|
||||
@@ -97,6 +102,7 @@ export class PluginLoadFailureError extends Error {
|
||||
|
||||
type CachedPluginState = {
|
||||
registry: PluginRegistry;
|
||||
memoryFlushPlanResolver: ReturnType<typeof getMemoryFlushPlanResolver>;
|
||||
memoryPromptBuilder: ReturnType<typeof getMemoryPromptSectionBuilder>;
|
||||
};
|
||||
|
||||
@@ -124,6 +130,7 @@ const LAZY_RUNTIME_REFLECTION_KEYS = [
|
||||
export function clearPluginLoaderCache(): void {
|
||||
registryCache.clear();
|
||||
openAllowlistWarningCache.clear();
|
||||
clearMemoryFlushPlanResolver();
|
||||
clearMemoryPromptSection();
|
||||
}
|
||||
|
||||
@@ -709,6 +716,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
const cached = getCachedPluginRegistry(cacheKey);
|
||||
if (cached) {
|
||||
restoreMemoryPromptSection(cached.memoryPromptBuilder);
|
||||
restoreMemoryFlushPlanResolver(cached.memoryFlushPlanResolver);
|
||||
if (shouldActivate) {
|
||||
activatePluginRegistry(cached.registry, cacheKey);
|
||||
}
|
||||
@@ -721,6 +729,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
if (shouldActivate) {
|
||||
clearPluginCommands();
|
||||
clearPluginInteractiveHandlers();
|
||||
clearMemoryFlushPlanResolver();
|
||||
clearMemoryPromptSection();
|
||||
}
|
||||
|
||||
@@ -1219,6 +1228,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
hookPolicy: entry?.hooks,
|
||||
registrationMode,
|
||||
});
|
||||
const previousMemoryFlushPlanResolver = getMemoryFlushPlanResolver();
|
||||
const previousMemoryPromptBuilder = getMemoryPromptSectionBuilder();
|
||||
|
||||
try {
|
||||
@@ -1234,11 +1244,13 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
// Snapshot loads should not replace process-global runtime prompt state.
|
||||
if (!shouldActivate) {
|
||||
restoreMemoryPromptSection(previousMemoryPromptBuilder);
|
||||
restoreMemoryFlushPlanResolver(previousMemoryFlushPlanResolver);
|
||||
}
|
||||
registry.plugins.push(record);
|
||||
seenIds.set(pluginId, candidate.origin);
|
||||
} catch (err) {
|
||||
restoreMemoryPromptSection(previousMemoryPromptBuilder);
|
||||
restoreMemoryFlushPlanResolver(previousMemoryFlushPlanResolver);
|
||||
recordPluginError({
|
||||
logger,
|
||||
registry,
|
||||
@@ -1274,6 +1286,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi
|
||||
if (cacheEnabled) {
|
||||
setCachedPluginRegistry(cacheKey, {
|
||||
registry,
|
||||
memoryFlushPlanResolver: getMemoryFlushPlanResolver(),
|
||||
memoryPromptBuilder: getMemoryPromptSectionBuilder(),
|
||||
});
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ import type {
|
||||
} from "../gateway/server-methods/types.js";
|
||||
import { registerInternalHook } from "../hooks/internal-hooks.js";
|
||||
import type { HookEntry } from "../hooks/types.js";
|
||||
import { registerMemoryFlushPlanResolver } from "../memory/flush-plan.js";
|
||||
import { registerMemoryPromptSection } from "../memory/prompt-section.js";
|
||||
import { resolveUserPath } from "../utils.js";
|
||||
import { registerPluginCommand, validatePluginCommandDefinition } from "./command-registration.js";
|
||||
@@ -1042,6 +1043,21 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) {
|
||||
}
|
||||
registerMemoryPromptSection(builder);
|
||||
},
|
||||
registerMemoryFlushPlan: (resolver) => {
|
||||
if (registrationMode !== "full") {
|
||||
return;
|
||||
}
|
||||
if (record.kind !== "memory") {
|
||||
pushDiagnostic({
|
||||
level: "error",
|
||||
pluginId: record.id,
|
||||
source: record.source,
|
||||
message: "only memory plugins can register a memory flush plan",
|
||||
});
|
||||
return;
|
||||
}
|
||||
registerMemoryFlushPlanResolver(resolver);
|
||||
},
|
||||
resolvePath: (input: string) => resolveUserPath(input),
|
||||
on: (hookName, handler, opts) =>
|
||||
registrationMode === "full"
|
||||
|
||||
@@ -1397,6 +1397,10 @@ export type OpenClawPluginApi = {
|
||||
registerMemoryPromptSection: (
|
||||
builder: import("../memory/prompt-section.js").MemoryPromptSectionBuilder,
|
||||
) => void;
|
||||
/** Register the pre-compaction flush plan resolver for this memory plugin (exclusive slot). */
|
||||
registerMemoryFlushPlan: (
|
||||
resolver: import("../memory/flush-plan.js").MemoryFlushPlanResolver,
|
||||
) => void;
|
||||
resolvePath: (input: string) => string;
|
||||
/** Register a lifecycle hook handler */
|
||||
on: <K extends PluginHookName>(
|
||||
|
||||
Reference in New Issue
Block a user