diff --git a/src/agents/pi-embedded-runner/model.test.ts b/src/agents/pi-embedded-runner/model.test.ts index 0a4dbbdc719..ce0e9540b29 100644 --- a/src/agents/pi-embedded-runner/model.test.ts +++ b/src/agents/pi-embedded-runner/model.test.ts @@ -1689,53 +1689,6 @@ describe("resolveModel", () => { }); }); - it("lets official openai-codex metadata override legacy unmarked models-add rows", () => { - mockDiscoveredModel(discoverModels, { - provider: "openai-codex", - modelId: "gpt-5.5", - templateModel: { - ...buildOpenAICodexForwardCompatExpectation("gpt-5.5"), - name: "GPT-5.5", - cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, - contextWindow: 400_000, - }, - }); - - const cfg = { - models: { - providers: { - "openai-codex": { - baseUrl: "https://chatgpt.com/backend-api", - api: "openai-codex-responses", - models: [ - { - ...makeModel("gpt-5.5"), - api: "openai-codex-responses", - reasoning: true, - input: ["text", "image"], - cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, - contextWindow: 400_000, - contextTokens: 272_000, - maxTokens: 128_000, - }, - ], - }, - }, - }, - } as unknown as OpenClawConfig; - - const result = resolveModelForTest("openai-codex", "gpt-5.5", "/tmp/agent", cfg); - - expect(result.error).toBeUndefined(); - expect(result.model).toMatchObject({ - provider: "openai-codex", - id: "gpt-5.5", - cost: { input: 5, output: 30, cacheRead: 0.5, cacheWrite: 0 }, - contextWindow: 400_000, - maxTokens: 128_000, - }); - }); - it("resolves openai-codex gpt-5.5 even when discovery omits the OAuth catalog row", () => { const result = resolveModelForTest("openai-codex", "gpt-5.5"); diff --git a/src/agents/pi-embedded-runner/model.ts b/src/agents/pi-embedded-runner/model.ts index 81bbfcb8504..3a37b1ced92 100644 --- a/src/agents/pi-embedded-runner/model.ts +++ b/src/agents/pi-embedded-runner/model.ts @@ -27,7 +27,6 @@ import { shouldSuppressBuiltInModel, shouldUnconditionallySuppress, } from "../model-suppression.js"; -import { isLegacyModelsAddCodexMetadataModel } from "../openai-codex-models-add-legacy.js"; import { discoverAuthStorage, discoverModels } from "../pi-model-discovery.js"; import { attachModelProviderRequestTransport, @@ -397,12 +396,10 @@ function resolveConfiguredProviderConfig( } function isModelsAddMetadataModel(params: { - provider: string; model: NonNullable[number] | undefined; }) { return ( - (params.model as { metadataSource?: unknown } | undefined)?.metadataSource === "models-add" || - isLegacyModelsAddCodexMetadataModel(params) + (params.model as { metadataSource?: unknown } | undefined)?.metadataSource === "models-add" ); } @@ -528,8 +525,7 @@ function applyConfiguredProviderOverrides(params: { ? findConfiguredProviderModel(providerConfig, params.provider, discoveredModel.id) : undefined); const metadataOverrideModel = - params.preferDiscoveredModelMetadata && - isModelsAddMetadataModel({ provider: params.provider, model: configuredModel }) + params.preferDiscoveredModelMetadata && isModelsAddMetadataModel({ model: configuredModel }) ? undefined : configuredModel; const discoveredHeaders = sanitizeModelHeaders(discoveredModel.headers, { diff --git a/src/auto-reply/reply/agent-runner-memory.test.ts b/src/auto-reply/reply/agent-runner-memory.test.ts index fd90f501d68..8a871c7f865 100644 --- a/src/auto-reply/reply/agent-runner-memory.test.ts +++ b/src/auto-reply/reply/agent-runner-memory.test.ts @@ -5,7 +5,8 @@ import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import type { SessionEntry } from "../../config/sessions.js"; import { clearMemoryPluginState, - registerMemoryFlushPlanResolver, + registerMemoryCapability, + type MemoryFlushPlanResolver, } from "../../plugins/memory-state.js"; import type { TemplateContext } from "../templating.js"; import { @@ -21,6 +22,10 @@ const runEmbeddedPiAgentMock = vi.fn(); const refreshQueuedFollowupSessionMock = vi.fn(); const incrementCompactionCountMock = vi.fn(); +function registerMemoryFlushPlanResolverForTest(resolver: MemoryFlushPlanResolver): void { + registerMemoryCapability("memory-core", { flushPlanResolver: resolver }); +} + function createReplyOperation() { return { abortSignal: new AbortController().signal, @@ -34,7 +39,7 @@ describe("runMemoryFlushIfNeeded", () => { beforeEach(async () => { rootDir = await fs.mkdtemp(path.join(os.tmpdir(), "openclaw-memory-unit-")); - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 4_000, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 20_000, @@ -180,7 +185,7 @@ describe("runMemoryFlushIfNeeded", () => { }); it("runs memory flush on the configured maintenance model without active fallbacks", async () => { - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 4_000, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 20_000, @@ -317,7 +322,7 @@ describe("runMemoryFlushIfNeeded", () => { `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`, "utf8", ); - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 1, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 0, @@ -367,7 +372,7 @@ describe("runMemoryFlushIfNeeded", () => { `${JSON.stringify({ message: { role: "user", content: "x".repeat(5_000) } })}\n`, "utf8", ); - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 1, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 0, @@ -442,7 +447,7 @@ describe("runMemoryFlushIfNeeded", () => { })}\n`, "utf8", ); - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 4_000, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 0, @@ -493,7 +498,7 @@ describe("runMemoryFlushIfNeeded", () => { })}\n`, "utf8", ); - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 4_000, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 0, @@ -553,7 +558,7 @@ describe("runMemoryFlushIfNeeded", () => { ].join("\n"), "utf8", ); - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 4_000, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 0, @@ -725,7 +730,7 @@ describe("runMemoryFlushIfNeeded", () => { }, }, }; - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 4_000, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 20_000, diff --git a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts index 9b9d9b95413..77a87282ccb 100644 --- a/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts +++ b/src/auto-reply/reply/agent-runner.misc.runreplyagent.test.ts @@ -17,7 +17,8 @@ import { } from "../../infra/diagnostic-events.js"; import { clearMemoryPluginState, - registerMemoryFlushPlanResolver, + registerMemoryCapability, + type MemoryFlushPlanResolver, } from "../../plugins/memory-state.js"; import type { TemplateContext } from "../templating.js"; import type { FollowupRun, QueueSettings } from "./queue.js"; @@ -37,6 +38,10 @@ function createCliBackendTestConfig() { }; } +function registerMemoryFlushPlanResolverForTest(resolver: MemoryFlushPlanResolver): void { + registerMemoryCapability("memory-core", { flushPlanResolver: resolver }); +} + const runEmbeddedPiAgentMock = vi.fn(); const runCliAgentMock = vi.fn(); const runWithModelFallbackMock = vi.fn(); @@ -2140,7 +2145,7 @@ describe("runReplyAgent fallback reasoning tags", () => { }); it("enforces during memory flush on fallback providers", async () => { - registerMemoryFlushPlanResolver(() => ({ + registerMemoryFlushPlanResolverForTest(() => ({ softThresholdTokens: 1_000, forceFlushTranscriptBytes: 1_000_000_000, reserveTokensFloor: 20_000, diff --git a/src/commands/doctor/shared/legacy-config-core-normalizers.ts b/src/commands/doctor/shared/legacy-config-core-normalizers.ts index 4e29b126697..04d610656d4 100644 --- a/src/commands/doctor/shared/legacy-config-core-normalizers.ts +++ b/src/commands/doctor/shared/legacy-config-core-normalizers.ts @@ -1,5 +1,4 @@ import { migrateLegacyRuntimeModelRef } from "../../../agents/model-runtime-aliases.js"; -import { isLegacyModelsAddCodexMetadataModel } from "../../../agents/openai-codex-models-add-legacy.js"; import { normalizeProviderId } from "../../../agents/provider-id.js"; import { resolveSingleAccountKeysToMove } from "../../../channels/plugins/setup-promotion-helpers.js"; import { resolveNormalizedProviderModelMaxTokens } from "../../../config/defaults.js"; @@ -12,6 +11,7 @@ import { } from "../../../shared/string-coerce.js"; import { sanitizeForLog } from "../../../terminal/ansi.js"; import { isRecord } from "./legacy-config-record-shared.js"; +import { isLegacyModelsAddCodexMetadataModel } from "./legacy-models-add-metadata.js"; export { normalizeLegacyTalkConfig } from "./legacy-talk-config-normalizer.js"; export function normalizeLegacyCommandsConfig( diff --git a/src/agents/openai-codex-models-add-legacy.test.ts b/src/commands/doctor/shared/legacy-models-add-metadata.test.ts similarity index 86% rename from src/agents/openai-codex-models-add-legacy.test.ts rename to src/commands/doctor/shared/legacy-models-add-metadata.test.ts index 957ed4e1fda..862e47bac43 100644 --- a/src/agents/openai-codex-models-add-legacy.test.ts +++ b/src/commands/doctor/shared/legacy-models-add-metadata.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; -import type { ModelDefinitionConfig } from "../config/types.models.js"; -import { isLegacyModelsAddCodexMetadataModel } from "./openai-codex-models-add-legacy.js"; +import type { ModelDefinitionConfig } from "../../../config/types.models.js"; +import { isLegacyModelsAddCodexMetadataModel } from "./legacy-models-add-metadata.js"; function buildLegacyModel(id: string): Partial { return { diff --git a/src/agents/openai-codex-models-add-legacy.ts b/src/commands/doctor/shared/legacy-models-add-metadata.ts similarity index 86% rename from src/agents/openai-codex-models-add-legacy.ts rename to src/commands/doctor/shared/legacy-models-add-metadata.ts index 4853e2dea6f..c55a234644e 100644 --- a/src/agents/openai-codex-models-add-legacy.ts +++ b/src/commands/doctor/shared/legacy-models-add-metadata.ts @@ -1,5 +1,5 @@ -import type { ModelDefinitionConfig } from "../config/types.models.js"; -import { normalizeProviderId } from "./provider-id.js"; +import { normalizeProviderId } from "../../../agents/provider-id.js"; +import type { ModelDefinitionConfig } from "../../../config/types.models.js"; const LEGACY_MODELS_ADD_CODEX_MODEL_IDS = new Set(["gpt-5.5", "gpt-5.5-pro"]); diff --git a/src/plugins/loader.runtime-registry.test.ts b/src/plugins/loader.runtime-registry.test.ts index eeff5987471..19d5f36bccb 100644 --- a/src/plugins/loader.runtime-registry.test.ts +++ b/src/plugins/loader.runtime-registry.test.ts @@ -16,11 +16,9 @@ import { buildMemoryPromptSection, getMemoryRuntime, listMemoryCorpusSupplements, + registerMemoryCapability, registerMemoryCorpusSupplement, - registerMemoryFlushPlanResolver, registerMemoryPromptSupplement, - registerMemoryPromptSection, - registerMemoryRuntime, resolveMemoryFlushPlan, } from "./memory-state.js"; import type { PluginRecord } from "./registry-types.js"; @@ -590,22 +588,24 @@ describe("clearPluginLoaderCache", () => { search: async () => [], get: async () => null, }); - registerMemoryPromptSection(() => ["stale memory section"]); registerMemoryPromptSupplement("memory-wiki", () => ["stale wiki supplement"]); - registerMemoryFlushPlanResolver(() => ({ - softThresholdTokens: 1, - forceFlushTranscriptBytes: 2, - reserveTokensFloor: 3, - prompt: "stale", - systemPrompt: "stale", - relativePath: "memory/stale.md", - })); - registerMemoryRuntime({ - async getMemorySearchManager() { - return { manager: null }; - }, - resolveMemoryBackendConfig() { - return { backend: "builtin" as const }; + registerMemoryCapability("memory-core", { + promptBuilder: () => ["stale memory section"], + flushPlanResolver: () => ({ + softThresholdTokens: 1, + forceFlushTranscriptBytes: 2, + reserveTokensFloor: 3, + prompt: "stale", + systemPrompt: "stale", + relativePath: "memory/stale.md", + }), + runtime: { + async getMemorySearchManager() { + return { manager: null }; + }, + resolveMemoryBackendConfig() { + return { backend: "builtin" as const }; + }, }, }); expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual([ @@ -652,7 +652,9 @@ describe("clearPluginRegistryLoadCache", () => { id: "still-live", create: async () => ({ provider: null }), }); - registerMemoryPromptSection(() => ["still live"]); + registerMemoryCapability("memory-core", { + promptBuilder: () => ["still live"], + }); clearPluginRegistryLoadCache(); diff --git a/src/plugins/loader.test.ts b/src/plugins/loader.test.ts index 01ec3fa24bc..67da8248698 100644 --- a/src/plugins/loader.test.ts +++ b/src/plugins/loader.test.ts @@ -73,11 +73,9 @@ import { listActiveMemoryPublicArtifacts, listMemoryCorpusSupplements, listMemoryPromptSupplements, + registerMemoryCapability, registerMemoryCorpusSupplement, - registerMemoryFlushPlanResolver, registerMemoryPromptSupplement, - registerMemoryPromptSection, - registerMemoryRuntime, resolveMemoryFlushPlan, } from "./memory-state.js"; import { ensureOpenClawPluginSdkAlias } from "./plugin-sdk-dist-alias.js"; @@ -2388,16 +2386,7 @@ module.exports = { id: "throws-after-import", register() {} };`, search: async () => [], get: async () => null, }); - registerMemoryPromptSection(() => ["active memory section"]); registerMemoryPromptSupplement("memory-wiki", () => ["active wiki supplement"]); - registerMemoryFlushPlanResolver(() => ({ - softThresholdTokens: 1, - forceFlushTranscriptBytes: 2, - reserveTokensFloor: 3, - prompt: "active", - systemPrompt: "active", - relativePath: "memory/active.md", - })); const activeRuntime = { async getMemorySearchManager() { return { manager: null, error: "active" }; @@ -2406,7 +2395,18 @@ module.exports = { id: "throws-after-import", register() {} };`, return { backend: "builtin" as const }; }, }; - registerMemoryRuntime(activeRuntime); + registerMemoryCapability("memory-core", { + promptBuilder: () => ["active memory section"], + flushPlanResolver: () => ({ + softThresholdTokens: 1, + forceFlushTranscriptBytes: 2, + reserveTokensFloor: 3, + prompt: "active", + systemPrompt: "active", + relativePath: "memory/active.md", + }), + runtime: activeRuntime, + }); const plugin = writePlugin({ id: "snapshot-memory", filename: "snapshot-memory.cjs", diff --git a/src/plugins/loader.ts b/src/plugins/loader.ts index fcda0eb1f33..5baacb089e4 100644 --- a/src/plugins/loader.ts +++ b/src/plugins/loader.ts @@ -93,9 +93,6 @@ import { import { clearMemoryPluginState, getMemoryCapabilityRegistration, - getMemoryFlushPlanResolver, - getMemoryPromptSectionBuilder, - getMemoryRuntime, listMemoryCorpusSupplements, listMemoryPromptSupplements, restoreMemoryPluginState, @@ -245,10 +242,7 @@ type CachedPluginState = { agentHarnesses: ReturnType; compactionProviders: ReturnType; memoryEmbeddingProviders: ReturnType; - memoryFlushPlanResolver: ReturnType; - memoryPromptBuilder: ReturnType; memoryPromptSupplements: ReturnType; - memoryRuntime: ReturnType; }; const MAX_PLUGIN_REGISTRY_CACHE_ENTRIES = 128; @@ -1487,10 +1481,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi restoreMemoryPluginState({ capability: cached.state.memoryCapability, corpusSupplements: cached.state.memoryCorpusSupplements, - promptBuilder: cached.state.memoryPromptBuilder, promptSupplements: cached.state.memoryPromptSupplements, - flushPlanResolver: cached.state.memoryFlushPlanResolver, - runtime: cached.state.memoryRuntime, }); activatePluginRegistry( cached.state.registry, @@ -2303,11 +2294,8 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi const previousDetachedTaskRuntimeRegistration = getDetachedTaskLifecycleRuntimeRegistration(); const previousMemoryCapability = getMemoryCapabilityRegistration(); const previousMemoryEmbeddingProviders = listRegisteredMemoryEmbeddingProviders(); - const previousMemoryFlushPlanResolver = getMemoryFlushPlanResolver(); - const previousMemoryPromptBuilder = getMemoryPromptSectionBuilder(); const previousMemoryCorpusSupplements = listMemoryCorpusSupplements(); const previousMemoryPromptSupplements = listMemoryPromptSupplements(); - const previousMemoryRuntime = getMemoryRuntime(); try { withProfile( @@ -2324,10 +2312,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi restoreMemoryPluginState({ capability: previousMemoryCapability, corpusSupplements: previousMemoryCorpusSupplements, - promptBuilder: previousMemoryPromptBuilder, promptSupplements: previousMemoryPromptSupplements, - flushPlanResolver: previousMemoryFlushPlanResolver, - runtime: previousMemoryRuntime, }); } registry.plugins.push(record); @@ -2342,10 +2327,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi restoreMemoryPluginState({ capability: previousMemoryCapability, corpusSupplements: previousMemoryCorpusSupplements, - promptBuilder: previousMemoryPromptBuilder, promptSupplements: previousMemoryPromptSupplements, - flushPlanResolver: previousMemoryFlushPlanResolver, - runtime: previousMemoryRuntime, }); recordPluginError({ logger, @@ -2413,10 +2395,7 @@ export function loadOpenClawPlugins(options: PluginLoadOptions = {}): PluginRegi agentHarnesses: listRegisteredAgentHarnesses(), compactionProviders: listRegisteredCompactionProviders(), memoryEmbeddingProviders: listRegisteredMemoryEmbeddingProviders(), - memoryFlushPlanResolver: getMemoryFlushPlanResolver(), - memoryPromptBuilder: getMemoryPromptSectionBuilder(), memoryPromptSupplements: listMemoryPromptSupplements(), - memoryRuntime: getMemoryRuntime(), }, onlyPluginIds, ); diff --git a/src/plugins/memory-state.test.ts b/src/plugins/memory-state.test.ts index 8aebbba2baf..77056417bc5 100644 --- a/src/plugins/memory-state.test.ts +++ b/src/plugins/memory-state.test.ts @@ -4,8 +4,6 @@ import { buildMemoryPromptSection, clearMemoryPluginState, getMemoryCapabilityRegistration, - getMemoryFlushPlanResolver, - getMemoryPromptSectionBuilder, getMemoryRuntime, hasMemoryRuntime, listMemoryCorpusSupplements, @@ -54,10 +52,7 @@ function createMemoryStateSnapshot() { return { capability: getMemoryCapabilityRegistration(), corpusSupplements: listMemoryCorpusSupplements(), - promptBuilder: getMemoryPromptSectionBuilder(), promptSupplements: listMemoryPromptSupplements(), - flushPlanResolver: getMemoryFlushPlanResolver(), - runtime: getMemoryRuntime(), }; } @@ -66,16 +61,13 @@ function registerMemoryState(params: { relativePath?: string; runtime?: ReturnType; }) { - if (params.promptSection) { - registerMemoryPromptSection(() => params.promptSection ?? []); - } - if (params.relativePath) { - const relativePath = params.relativePath; - registerMemoryFlushPlanResolver(() => createMemoryFlushPlan(relativePath)); - } - if (params.runtime) { - registerMemoryRuntime(params.runtime); - } + registerMemoryCapability("memory-core", { + ...(params.promptSection ? { promptBuilder: () => params.promptSection ?? [] } : {}), + ...(params.relativePath + ? { flushPlanResolver: () => createMemoryFlushPlan(params.relativePath ?? "") } + : {}), + ...(params.runtime ? { runtime: params.runtime } : {}), + }); } describe("memory plugin state", () => { @@ -102,7 +94,22 @@ describe("memory plugin state", () => { ]); }); - it("prefers the registered memory capability over legacy split state", async () => { + it("adapts deprecated split registration to the unified memory capability", async () => { + const runtime = createMemoryRuntime(); + + registerMemoryPromptSection(() => ["legacy prompt"]); + registerMemoryFlushPlanResolver(() => createMemoryFlushPlan("memory/legacy.md")); + registerMemoryRuntime(runtime); + + expect(buildMemoryPromptSection({ availableTools: new Set() })).toEqual(["legacy prompt"]); + expect(resolveMemoryFlushPlan({})?.relativePath).toBe("memory/legacy.md"); + expect(getMemoryRuntime()).toBe(runtime); + expect(getMemoryCapabilityRegistration()).toMatchObject({ + pluginId: "legacy-memory-v1", + }); + }); + + it("prefers the registered memory capability over earlier legacy split state", async () => { const runtime = createMemoryRuntime(); registerMemoryPromptSection(() => ["legacy prompt"]); diff --git a/src/plugins/memory-state.ts b/src/plugins/memory-state.ts index 474019c7f8e..b64b76f4162 100644 --- a/src/plugins/memory-state.ts +++ b/src/plugins/memory-state.ts @@ -137,19 +137,12 @@ export type MemoryPluginCapabilityRegistration = { capability: MemoryPluginCapability; }; +const LEGACY_MEMORY_COMPAT_PLUGIN_ID = "legacy-memory-v1"; + type MemoryPluginState = { capability?: MemoryPluginCapabilityRegistration; corpusSupplements: MemoryCorpusSupplementRegistration[]; promptSupplements: MemoryPromptSupplementRegistration[]; - // LEGACY(memory-v1): kept for external plugins still registering the older - // split memory surfaces. Prefer `registerMemoryCapability(...)`. - promptBuilder?: MemoryPromptSectionBuilder; - // LEGACY(memory-v1): remove after external memory plugins migrate to the - // unified capability registration path. - flushPlanResolver?: MemoryFlushPlanResolver; - // LEGACY(memory-v1): remove after external memory plugins migrate to the - // unified capability registration path. - runtime?: MemoryPluginRuntime; }; const memoryPluginState: MemoryPluginState = { @@ -175,6 +168,14 @@ export function registerMemoryCapability( memoryPluginState.capability = { pluginId, capability: { ...capability } }; } +function patchMemoryCapability(pluginId: string, patch: MemoryPluginCapability): void { + const current = + memoryPluginState.capability?.pluginId === pluginId + ? memoryPluginState.capability.capability + : {}; + registerMemoryCapability(pluginId, { ...current, ...patch }); +} + export function getMemoryCapabilityRegistration(): MemoryPluginCapabilityRegistration | undefined { return memoryPluginState.capability ? { @@ -190,7 +191,14 @@ export function listMemoryCorpusSupplements(): MemoryCorpusSupplementRegistratio /** @deprecated Use registerMemoryCapability(pluginId, { promptBuilder }) instead. */ export function registerMemoryPromptSection(builder: MemoryPromptSectionBuilder): void { - memoryPluginState.promptBuilder = builder; + registerMemoryPromptSectionForPlugin(LEGACY_MEMORY_COMPAT_PLUGIN_ID, builder); +} + +export function registerMemoryPromptSectionForPlugin( + pluginId: string, + builder: MemoryPromptSectionBuilder, +): void { + patchMemoryCapability(pluginId, { promptBuilder: builder }); } export function registerMemoryPromptSupplement( @@ -209,9 +217,7 @@ export function buildMemoryPromptSection(params: { citationsMode?: MemoryCitationsMode; }): string[] { const primary = normalizeMemoryPromptLines( - memoryPluginState.capability?.capability.promptBuilder?.(params) ?? - memoryPluginState.promptBuilder?.(params) ?? - [], + memoryPluginState.capability?.capability.promptBuilder?.(params) ?? [], ); const supplements = memoryPluginState.promptSupplements // Keep supplement order stable even if plugin registration order changes. @@ -228,7 +234,7 @@ function normalizeMemoryPromptLines(value: unknown): string[] { } export function getMemoryPromptSectionBuilder(): MemoryPromptSectionBuilder | undefined { - return memoryPluginState.capability?.capability.promptBuilder ?? memoryPluginState.promptBuilder; + return memoryPluginState.capability?.capability.promptBuilder; } export function listMemoryPromptSupplements(): MemoryPromptSupplementRegistration[] { @@ -237,34 +243,41 @@ export function listMemoryPromptSupplements(): MemoryPromptSupplementRegistratio /** @deprecated Use registerMemoryCapability(pluginId, { flushPlanResolver }) instead. */ export function registerMemoryFlushPlanResolver(resolver: MemoryFlushPlanResolver): void { - memoryPluginState.flushPlanResolver = resolver; + registerMemoryFlushPlanResolverForPlugin(LEGACY_MEMORY_COMPAT_PLUGIN_ID, resolver); +} + +export function registerMemoryFlushPlanResolverForPlugin( + pluginId: string, + resolver: MemoryFlushPlanResolver, +): void { + patchMemoryCapability(pluginId, { flushPlanResolver: resolver }); } export function resolveMemoryFlushPlan(params: { cfg?: OpenClawConfig; nowMs?: number; }): MemoryFlushPlan | null { - return ( - memoryPluginState.capability?.capability.flushPlanResolver?.(params) ?? - memoryPluginState.flushPlanResolver?.(params) ?? - null - ); + return memoryPluginState.capability?.capability.flushPlanResolver?.(params) ?? null; } export function getMemoryFlushPlanResolver(): MemoryFlushPlanResolver | undefined { - return ( - memoryPluginState.capability?.capability.flushPlanResolver ?? - memoryPluginState.flushPlanResolver - ); + return memoryPluginState.capability?.capability.flushPlanResolver; } /** @deprecated Use registerMemoryCapability(pluginId, { runtime }) instead. */ export function registerMemoryRuntime(runtime: MemoryPluginRuntime): void { - memoryPluginState.runtime = runtime; + registerMemoryRuntimeForPlugin(LEGACY_MEMORY_COMPAT_PLUGIN_ID, runtime); +} + +export function registerMemoryRuntimeForPlugin( + pluginId: string, + runtime: MemoryPluginRuntime, +): void { + patchMemoryCapability(pluginId, { runtime }); } export function getMemoryRuntime(): MemoryPluginRuntime | undefined { - return memoryPluginState.capability?.capability.runtime ?? memoryPluginState.runtime; + return memoryPluginState.capability?.capability.runtime; } export function hasMemoryRuntime(): boolean { @@ -318,19 +331,13 @@ export function restoreMemoryPluginState(state: MemoryPluginState): void { } : undefined; memoryPluginState.corpusSupplements = [...state.corpusSupplements]; - memoryPluginState.promptBuilder = state.promptBuilder; memoryPluginState.promptSupplements = [...state.promptSupplements]; - memoryPluginState.flushPlanResolver = state.flushPlanResolver; - memoryPluginState.runtime = state.runtime; } export function clearMemoryPluginState(): void { memoryPluginState.capability = undefined; memoryPluginState.corpusSupplements = []; - memoryPluginState.promptBuilder = undefined; memoryPluginState.promptSupplements = []; - memoryPluginState.flushPlanResolver = undefined; - memoryPluginState.runtime = undefined; } export const _resetMemoryPluginState = clearMemoryPluginState; diff --git a/src/plugins/registry.ts b/src/plugins/registry.ts index 3d69ab7aa82..607590c7126 100644 --- a/src/plugins/registry.ts +++ b/src/plugins/registry.ts @@ -92,10 +92,10 @@ import { import { registerMemoryCapability, registerMemoryCorpusSupplement, - registerMemoryFlushPlanResolver, + registerMemoryFlushPlanResolverForPlugin, registerMemoryPromptSupplement, - registerMemoryPromptSection, - registerMemoryRuntime, + registerMemoryPromptSectionForPlugin, + registerMemoryRuntimeForPlugin, } from "./memory-state.js"; import { normalizeRegisteredProvider } from "./provider-validation.js"; import { createEmptyPluginRegistry } from "./registry-empty.js"; @@ -2380,7 +2380,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) { }); return; } - registerMemoryPromptSection(builder); + registerMemoryPromptSectionForPlugin(record.id, builder); }, registerMemoryPromptSupplement: (builder) => { if (typeof builder !== "function") { @@ -2415,7 +2415,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) { }); return; } - registerMemoryFlushPlanResolver(resolver); + registerMemoryFlushPlanResolverForPlugin(record.id, resolver); }, registerMemoryRuntime: (runtime) => { if (!hasKind(record.kind, "memory")) { @@ -2435,7 +2435,7 @@ export function createPluginRegistry(registryParams: PluginRegistryParams) { }); return; } - registerMemoryRuntime(runtime); + registerMemoryRuntimeForPlugin(record.id, runtime); }, registerMemoryEmbeddingProvider: (adapter) => { if (hasKind(record.kind, "memory")) {