From eae846e3e550cdc98c500bd06bb1af0ca63e0550 Mon Sep 17 00:00:00 2001 From: Vincent Koc <25068+vincentkoc@users.noreply.github.com> Date: Sat, 11 Jul 2026 02:18:02 -0700 Subject: [PATCH] refactor(memory): inject local service host hooks --- extensions/memory-core/cli-metadata.ts | 4 +- extensions/memory-core/index.test.ts | 20 +++++- extensions/memory-core/index.ts | 72 +++++++++++-------- extensions/memory-core/src/cli.runtime.ts | 42 +++++++++-- extensions/memory-core/src/cli.test.ts | 22 +++++- extensions/memory-core/src/cli.ts | 63 ++++++++++------ .../src/memory/embedding-local-service.ts | 4 ++ extensions/memory-core/src/tools.shared.ts | 4 ++ extensions/memory-core/src/tools.test.ts | 19 +++++ extensions/memory-core/src/tools.ts | 6 ++ 10 files changed, 198 insertions(+), 58 deletions(-) diff --git a/extensions/memory-core/cli-metadata.ts b/extensions/memory-core/cli-metadata.ts index 6ea28710ab0e..612c757fe312 100644 --- a/extensions/memory-core/cli-metadata.ts +++ b/extensions/memory-core/cli-metadata.ts @@ -9,7 +9,9 @@ export default definePluginEntry({ api.registerCli( async ({ program }) => { const { registerMemoryCli } = await import("./cli.js"); - registerMemoryCli(program); + registerMemoryCli(program, { + acquireLocalService: api.runtime.llm?.acquireLocalService, + }); }, { descriptors: [ diff --git a/extensions/memory-core/index.test.ts b/extensions/memory-core/index.test.ts index 847740a29b11..19be7ca455da 100644 --- a/extensions/memory-core/index.test.ts +++ b/extensions/memory-core/index.test.ts @@ -13,12 +13,21 @@ import { import { buildPromptSection } from "./src/prompt-section.js"; const closeMemorySearchManagerMock = vi.hoisted(() => vi.fn(async () => {})); +const getMemorySearchManagerMock = vi.hoisted(() => vi.fn(async () => null)); +const createMemoryRuntimeMock = vi.hoisted(() => + vi.fn(() => ({ + closeAllMemorySearchManagers: vi.fn(async () => {}), + closeMemorySearchManager: closeMemorySearchManagerMock, + getMemorySearchManager: getMemorySearchManagerMock, + })), +); vi.mock("./src/runtime-provider.js", () => ({ + createMemoryRuntime: createMemoryRuntimeMock, memoryRuntime: { closeAllMemorySearchManagers: vi.fn(async () => {}), closeMemorySearchManager: closeMemorySearchManagerMock, - getMemorySearchManager: vi.fn(async () => null), + getMemorySearchManager: getMemorySearchManagerMock, }, })); @@ -121,6 +130,15 @@ describe("memory-core plugin runtime registration", () => { expect(closeMemorySearchManagerMock).toHaveBeenCalledWith({ cfg, agentId: "main" }); }); + + it("binds the host local-service hook to the registered memory runtime", async () => { + const runtime = registerMemoryCoreRuntime(); + const cfg = {} as OpenClawConfig; + + await runtime.getMemorySearchManager({ cfg, agentId: "main" }); + + expect(createMemoryRuntimeMock).toHaveBeenCalledWith(hostRuntime.llm.acquireLocalService); + }); }); describe("buildMemoryFlushPlan", () => { diff --git a/extensions/memory-core/index.ts b/extensions/memory-core/index.ts index d77c867c3141..da09aedad4e7 100644 --- a/extensions/memory-core/index.ts +++ b/extensions/memory-core/index.ts @@ -18,7 +18,7 @@ import type { TSchema } from "typebox"; import { configureMemoryCoreDreamingState } from "./src/dreaming-state.js"; import { registerShortTermPromotionDreaming } from "./src/dreaming.js"; import { buildMemoryFlushPlan } from "./src/flush-plan.js"; -import { configureMemoryCoreEmbeddingLocalService } from "./src/memory/embedding-local-service.js"; +import type { MemoryCoreAcquireLocalService } from "./src/memory/embedding-local-service.js"; import { buildPromptSection } from "./src/prompt-section.js"; type MemoryToolsModule = typeof import("./src/tools.js"); @@ -30,6 +30,7 @@ type MemoryToolOptions = { agentSessionKey?: string; sandboxed?: boolean; oneShotCliRun?: boolean; + acquireLocalService?: MemoryCoreAcquireLocalService; }; const loadMemoryToolsModule = createLazyRuntimeModule(() => import("./src/tools.js")); @@ -140,7 +141,10 @@ function createLazyMemoryGetTool(options: MemoryToolOptions): AnyAgentTool | nul }); } -function resolveMemoryToolOptions(ctx: OpenClawPluginToolContext): MemoryToolOptions { +function resolveMemoryToolOptions( + ctx: OpenClawPluginToolContext, + acquireLocalService?: MemoryCoreAcquireLocalService, +): MemoryToolOptions { const getConfig = () => ctx.getRuntimeConfig?.() ?? ctx.runtimeConfig ?? ctx.config; return { config: getConfig(), @@ -149,33 +153,39 @@ function resolveMemoryToolOptions(ctx: OpenClawPluginToolContext): MemoryToolOpt agentSessionKey: ctx.sessionKey, sandboxed: ctx.sandboxed, oneShotCliRun: ctx.oneShotCliRun, + ...(acquireLocalService ? { acquireLocalService } : {}), + }; +} + +function createLazyMemoryRuntime( + acquireLocalService?: MemoryCoreAcquireLocalService, +): MemoryPluginRuntime { + return { + async getMemorySearchManager(params) { + const { createMemoryRuntime } = await loadRuntimeProviderModule(); + return await createMemoryRuntime(acquireLocalService).getMemorySearchManager(params); + }, + resolveMemoryBackendConfig(params) { + return resolveMemoryBackendConfig(params); + }, + async closeAllMemorySearchManagers() { + const { memoryRuntime: runtime } = await loadRuntimeProviderModule(); + await runtime.closeAllMemorySearchManagers?.(); + }, + async closeMemorySearchManager(params) { + const { memoryRuntime: runtime } = await loadRuntimeProviderModule(); + await runtime.closeMemorySearchManager?.(params); + }, }; } -const memoryRuntime: MemoryPluginRuntime = { - async getMemorySearchManager(params) { - const { memoryRuntime: runtime } = await loadRuntimeProviderModule(); - return await runtime.getMemorySearchManager(params); - }, - resolveMemoryBackendConfig(params) { - return resolveMemoryBackendConfig(params); - }, - async closeAllMemorySearchManagers() { - const { memoryRuntime: runtime } = await loadRuntimeProviderModule(); - await runtime.closeAllMemorySearchManagers?.(); - }, - async closeMemorySearchManager(params) { - const { memoryRuntime: runtime } = await loadRuntimeProviderModule(); - await runtime.closeMemorySearchManager?.(params); - }, -}; export default definePluginEntry({ id: "memory-core", name: "Memory (Core)", description: "File-backed memory search tools and CLI", kind: "memory", register(api) { - configureMemoryCoreEmbeddingLocalService(api.runtime.llm?.acquireLocalService); + const acquireLocalService = api.runtime.llm?.acquireLocalService; configureMemoryCoreDreamingState((options: OpenKeyedStoreOptions) => api.runtime.state.openKeyedStore(options), ); @@ -183,7 +193,7 @@ export default definePluginEntry({ api.registerMemoryCapability({ promptBuilder: buildPromptSection, flushPlanResolver: buildMemoryFlushPlan, - runtime: memoryRuntime, + runtime: createLazyMemoryRuntime(acquireLocalService), publicArtifacts: { async listArtifacts(params) { const { listMemoryCorePublicArtifacts } = await import("./src/public-artifacts.js"); @@ -192,13 +202,19 @@ export default definePluginEntry({ }, }); - api.registerTool((ctx) => createLazyMemorySearchTool(resolveMemoryToolOptions(ctx)), { - names: ["memory_search"], - }); + api.registerTool( + (ctx) => createLazyMemorySearchTool(resolveMemoryToolOptions(ctx, acquireLocalService)), + { + names: ["memory_search"], + }, + ); - api.registerTool((ctx) => createLazyMemoryGetTool(resolveMemoryToolOptions(ctx)), { - names: ["memory_get"], - }); + api.registerTool( + (ctx) => createLazyMemoryGetTool(resolveMemoryToolOptions(ctx, acquireLocalService)), + { + names: ["memory_get"], + }, + ); api.registerCommand({ name: "dreaming", @@ -214,7 +230,7 @@ export default definePluginEntry({ api.registerCli( async ({ program }) => { const { registerMemoryCli } = await import("./cli.js"); - registerMemoryCli(program); + registerMemoryCli(program, { acquireLocalService }); }, { descriptors: [ diff --git a/extensions/memory-core/src/cli.runtime.ts b/extensions/memory-core/src/cli.runtime.ts index 87574cca1d78..bf7d1e920888 100644 --- a/extensions/memory-core/src/cli.runtime.ts +++ b/extensions/memory-core/src/cli.runtime.ts @@ -52,6 +52,10 @@ import { } from "./dreaming-repair.js"; import { asRecord } from "./dreaming-shared.js"; import { resolveShortTermPromotionDreamingConfig } from "./dreaming.js"; +import type { + MemoryCoreAcquireLocalService, + MemoryCoreLocalServiceHost, +} from "./memory/embedding-local-service.js"; import { formatMemoryVectorDegradedWriteReason } from "./memory/manager-vector-warning.js"; import { previewGroundedRemMarkdown } from "./rem-evidence.js"; import { previewRemHarness } from "./rem-harness.js"; @@ -540,6 +544,7 @@ async function withMemoryManagerForAgent(params: { cfg: OpenClawConfig; agentId: string; purpose?: MemoryManagerPurpose; + acquireLocalService?: MemoryCoreAcquireLocalService; run: (manager: MemoryManager) => Promise; }): Promise { const managerParams: Parameters[0] = { @@ -549,6 +554,9 @@ async function withMemoryManagerForAgent(params: { if (params.purpose) { managerParams.purpose = params.purpose; } + if (params.acquireLocalService) { + managerParams.acquireLocalService = params.acquireLocalService; + } await withManager({ getManager: () => getMemorySearchManager(managerParams), onMissing: (error) => defaultRuntime.log(error ?? "Memory search disabled."), @@ -740,7 +748,10 @@ async function scanMemorySources(params: { return { sources: scans, totalFiles, issues }; } -export async function runMemoryStatus(opts: MemoryCommandOptions) { +export async function runMemoryStatus( + opts: MemoryCommandOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { setVerbose(Boolean(opts.verbose)); const { config: cfg, diagnostics } = await loadMemoryCommandConfig("memory status"); emitMemorySecretResolveDiagnostics(diagnostics, { json: Boolean(opts.json) }); @@ -763,6 +774,7 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { cfg, agentId, purpose: managerPurpose, + acquireLocalService: hostOptions?.acquireLocalService, run: async (manager) => { const deep = Boolean(opts.deep || opts.index); let embeddingProbe: MemoryEmbeddingProbeResult | undefined; @@ -1166,7 +1178,10 @@ export async function runMemoryStatus(opts: MemoryCommandOptions) { } } -export async function runMemoryIndex(opts: MemoryCommandOptions) { +export async function runMemoryIndex( + opts: MemoryCommandOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { setVerbose(Boolean(opts.verbose)); const { config: cfg, diagnostics } = await loadMemoryCommandConfig("memory index"); emitMemorySecretResolveDiagnostics(diagnostics); @@ -1176,6 +1191,7 @@ export async function runMemoryIndex(opts: MemoryCommandOptions) { cfg, agentId, purpose: "cli", + acquireLocalService: hostOptions?.acquireLocalService, run: async (manager) => { try { const syncFn = manager.sync ? manager.sync.bind(manager) : undefined; @@ -1329,6 +1345,7 @@ export async function runMemoryIndex(opts: MemoryCommandOptions) { export async function runMemorySearch( queryArg: string | undefined, opts: MemorySearchCommandOptions, + hostOptions?: MemoryCoreLocalServiceHost, ) { const query = opts.query ?? queryArg; if (!query) { @@ -1352,6 +1369,7 @@ export async function runMemorySearch( cfg, agentId, purpose: "cli", + acquireLocalService: hostOptions?.acquireLocalService, run: async (manager) => { const sessionKey = buildCliMemorySearchSessionKey(agentId); let results: Awaited>; @@ -1416,7 +1434,10 @@ export async function runMemorySearch( }); } -export async function runMemoryPromote(opts: MemoryPromoteCommandOptions) { +export async function runMemoryPromote( + opts: MemoryPromoteCommandOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const { config: cfg, diagnostics } = await loadMemoryCommandConfig("memory promote"); emitMemorySecretResolveDiagnostics(diagnostics, { json: Boolean(opts.json) }); const agentId = resolveAgent(cfg, opts.agent); @@ -1425,6 +1446,7 @@ export async function runMemoryPromote(opts: MemoryPromoteCommandOptions) { cfg, agentId, purpose: "status", + acquireLocalService: hostOptions?.acquireLocalService, run: async (manager) => { const status = manager.status(); const workspaceDir = status.workspaceDir?.trim(); @@ -1594,6 +1616,7 @@ export async function runMemoryPromote(opts: MemoryPromoteCommandOptions) { export async function runMemoryPromoteExplain( selectorArg: string | undefined, opts: MemoryPromoteExplainOptions, + hostOptions?: MemoryCoreLocalServiceHost, ) { const selector = selectorArg?.trim(); if (!selector) { @@ -1610,6 +1633,7 @@ export async function runMemoryPromoteExplain( cfg, agentId, purpose: "status", + acquireLocalService: hostOptions?.acquireLocalService, run: async (manager) => { const status = manager.status(); const workspaceDir = status.workspaceDir?.trim(); @@ -1709,7 +1733,10 @@ export async function runMemoryPromoteExplain( }); } -export async function runMemoryRemHarness(opts: MemoryRemHarnessOptions) { +export async function runMemoryRemHarness( + opts: MemoryRemHarnessOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const { config: cfg, diagnostics } = await loadMemoryCommandConfig("memory rem-harness"); emitMemorySecretResolveDiagnostics(diagnostics, { json: Boolean(opts.json) }); const agentId = resolveAgent(cfg, opts.agent); @@ -1718,6 +1745,7 @@ export async function runMemoryRemHarness(opts: MemoryRemHarnessOptions) { cfg, agentId, purpose: "status", + acquireLocalService: hostOptions?.acquireLocalService, run: async (manager) => { const status = manager.status(); const managerWorkspaceDir = status.workspaceDir?.trim(); @@ -1889,7 +1917,10 @@ export async function runMemoryRemHarness(opts: MemoryRemHarnessOptions) { }); } -export async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) { +export async function runMemoryRemBackfill( + opts: MemoryRemBackfillOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const { config: cfg, diagnostics } = await loadMemoryCommandConfig("memory rem-backfill"); emitMemorySecretResolveDiagnostics(diagnostics, { json: Boolean(opts.json) }); const agentId = resolveAgent(cfg, opts.agent); @@ -1898,6 +1929,7 @@ export async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) { cfg, agentId, purpose: "status", + acquireLocalService: hostOptions?.acquireLocalService, run: async (manager) => { const status = manager.status(); const workspaceDir = status.workspaceDir?.trim(); diff --git a/extensions/memory-core/src/cli.test.ts b/extensions/memory-core/src/cli.test.ts index 900733db65b6..cf320414fcbf 100644 --- a/extensions/memory-core/src/cli.test.ts +++ b/extensions/memory-core/src/cli.test.ts @@ -216,10 +216,13 @@ describe("memory cli", () => { expect(loggedOutput(spy)).not.toContain(expected); } - async function runMemoryCli(args: string[]) { + async function runMemoryCli( + args: string[], + hostOptions?: Parameters[1], + ) { const program = new Command(); program.name("test"); - registerMemoryCli(program); + registerMemoryCli(program, hostOptions); await program.parseAsync(["memory", ...args], { from: "user" }); } @@ -1444,6 +1447,21 @@ describe("memory cli", () => { expect(close).toHaveBeenCalled(); }); + it("passes the host local-service hook to CLI memory managers", async () => { + const close = vi.fn(async () => {}); + mockManager({ search: vi.fn(async () => []), close }); + const acquireLocalService = vi.fn(async () => undefined); + + await runMemoryCli(["search", "hello"], { acquireLocalService }); + + expect(getMemorySearchManager).toHaveBeenCalledWith({ + cfg: {}, + agentId: "main", + purpose: "cli", + acquireLocalService, + }); + }); + it("accepts --query for memory search", async () => { const close = vi.fn(async () => {}); const search = vi.fn(async () => []); diff --git a/extensions/memory-core/src/cli.ts b/extensions/memory-core/src/cli.ts index 2306adbda288..a63c800152ca 100644 --- a/extensions/memory-core/src/cli.ts +++ b/extensions/memory-core/src/cli.ts @@ -18,6 +18,7 @@ import type { MemoryRemHarnessOptions, MemorySearchCommandOptions, } from "./cli.types.js"; +import type { MemoryCoreLocalServiceHost } from "./memory/embedding-local-service.js"; import { DEFAULT_PROMOTION_MIN_RECALL_COUNT, DEFAULT_PROMOTION_MIN_SCORE, @@ -28,42 +29,62 @@ const loadMemoryCliRuntime = createLazyRuntimeModule(() => import("./cli.runtime const DECIMAL_NUMBER_RE = /^[+-]?(?:\d+(?:\.\d+)?|\.\d+)$/; -export async function runMemoryStatus(opts: MemoryCommandOptions) { +export async function runMemoryStatus( + opts: MemoryCommandOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const runtime = await loadMemoryCliRuntime(); - await runtime.runMemoryStatus(opts); + await runtime.runMemoryStatus(opts, hostOptions); } -async function runMemoryIndex(opts: MemoryCommandOptions) { +async function runMemoryIndex( + opts: MemoryCommandOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const runtime = await loadMemoryCliRuntime(); - await runtime.runMemoryIndex(opts); + await runtime.runMemoryIndex(opts, hostOptions); } -async function runMemorySearch(queryArg: string | undefined, opts: MemorySearchCommandOptions) { +async function runMemorySearch( + queryArg: string | undefined, + opts: MemorySearchCommandOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const runtime = await loadMemoryCliRuntime(); - await runtime.runMemorySearch(queryArg, opts); + await runtime.runMemorySearch(queryArg, opts, hostOptions); } -async function runMemoryPromote(opts: MemoryPromoteCommandOptions) { +async function runMemoryPromote( + opts: MemoryPromoteCommandOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const runtime = await loadMemoryCliRuntime(); - await runtime.runMemoryPromote(opts); + await runtime.runMemoryPromote(opts, hostOptions); } async function runMemoryPromoteExplain( selectorArg: string | undefined, opts: MemoryPromoteExplainOptions, + hostOptions?: MemoryCoreLocalServiceHost, ) { const runtime = await loadMemoryCliRuntime(); - await runtime.runMemoryPromoteExplain(selectorArg, opts); + await runtime.runMemoryPromoteExplain(selectorArg, opts, hostOptions); } -async function runMemoryRemHarness(opts: MemoryRemHarnessOptions) { +async function runMemoryRemHarness( + opts: MemoryRemHarnessOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const runtime = await loadMemoryCliRuntime(); - await runtime.runMemoryRemHarness(opts); + await runtime.runMemoryRemHarness(opts, hostOptions); } -async function runMemoryRemBackfill(opts: MemoryRemBackfillOptions) { +async function runMemoryRemBackfill( + opts: MemoryRemBackfillOptions, + hostOptions?: MemoryCoreLocalServiceHost, +) { const runtime = await loadMemoryCliRuntime(); - await runtime.runMemoryRemBackfill(opts); + await runtime.runMemoryRemBackfill(opts, hostOptions); } function invalidCliArgument(message: string): Error & { code: string; exitCode: number } { @@ -100,7 +121,7 @@ function parseMemoryCliNonNegativeIntegerOption(value: string, flag: string): nu return parsed; } -export function registerMemoryCli(program: Command) { +export function registerMemoryCli(program: Command, hostOptions?: MemoryCoreLocalServiceHost) { const memory = program .command("memory") .description("Search, inspect, and reindex memory files") @@ -158,7 +179,7 @@ export function registerMemoryCli(program: Command) { .option("--fix", "Repair stale recall locks and normalize promotion metadata") .option("--verbose", "Verbose logging", false) .action(async (opts: MemoryCommandOptions & { force?: boolean }) => { - await runMemoryStatus(opts); + await runMemoryStatus(opts, hostOptions); }); memory @@ -168,7 +189,7 @@ export function registerMemoryCli(program: Command) { .option("--force", "Force full reindex", false) .option("--verbose", "Verbose logging", false) .action(async (opts: MemoryCommandOptions) => { - await runMemoryIndex(opts); + await runMemoryIndex(opts, hostOptions); }); memory @@ -185,7 +206,7 @@ export function registerMemoryCli(program: Command) { ) .option("--json", "Print JSON") .action(async (queryArg: string | undefined, opts: MemorySearchCommandOptions) => { - await runMemorySearch(queryArg, opts); + await runMemorySearch(queryArg, opts, hostOptions); }); memory @@ -214,7 +235,7 @@ export function registerMemoryCli(program: Command) { .option("--include-promoted", "Include already promoted candidates", false) .option("--json", "Print JSON") .action(async (opts: MemoryPromoteCommandOptions) => { - await runMemoryPromote(opts); + await runMemoryPromote(opts, hostOptions); }); memory @@ -225,7 +246,7 @@ export function registerMemoryCli(program: Command) { .option("--include-promoted", "Include already promoted candidates", false) .option("--json", "Print JSON") .action(async (selectorArg: string | undefined, opts: MemoryPromoteExplainOptions) => { - await runMemoryPromoteExplain(selectorArg, opts); + await runMemoryPromoteExplain(selectorArg, opts, hostOptions); }); memory @@ -237,7 +258,7 @@ export function registerMemoryCli(program: Command) { .option("--include-promoted", "Include already promoted deep candidates", false) .option("--json", "Print JSON") .action(async (opts: MemoryRemHarnessOptions) => { - await runMemoryRemHarness(opts); + await runMemoryRemHarness(opts, hostOptions); }); memory @@ -258,7 +279,7 @@ export function registerMemoryCli(program: Command) { ) .option("--json", "Print JSON") .action(async (opts: MemoryRemBackfillOptions) => { - await runMemoryRemBackfill(opts); + await runMemoryRemBackfill(opts, hostOptions); }); memory.action(() => { diff --git a/extensions/memory-core/src/memory/embedding-local-service.ts b/extensions/memory-core/src/memory/embedding-local-service.ts index f783d384e8ec..a5edc6fa02ad 100644 --- a/extensions/memory-core/src/memory/embedding-local-service.ts +++ b/extensions/memory-core/src/memory/embedding-local-service.ts @@ -7,3 +7,7 @@ export type MemoryCoreAcquireLocalService = ( }, signal?: AbortSignal | null, ) => Promise<{ release: () => void } | undefined>; + +export type MemoryCoreLocalServiceHost = { + acquireLocalService?: MemoryCoreAcquireLocalService; +}; diff --git a/extensions/memory-core/src/tools.shared.ts b/extensions/memory-core/src/tools.shared.ts index dff3233adc9c..bb003e19b209 100644 --- a/extensions/memory-core/src/tools.shared.ts +++ b/extensions/memory-core/src/tools.shared.ts @@ -11,6 +11,7 @@ import { } from "openclaw/plugin-sdk/memory-core-host-runtime-core"; import { normalizeLowercaseStringOrEmpty } from "openclaw/plugin-sdk/string-coerce-runtime"; import { Type } from "typebox"; +import type { MemoryCoreAcquireLocalService } from "./memory/embedding-local-service.js"; type MemorySearchManagerResult = Awaited< ReturnType<(typeof import("./memory/index.js"))["getMemorySearchManager"]> >; @@ -21,6 +22,7 @@ type MemoryToolOptions = { agentSessionKey?: string; sandboxed?: boolean; oneShotCliRun?: boolean; + acquireLocalService?: MemoryCoreAcquireLocalService; }; export const loadMemoryToolRuntime = createLazyRuntimeModule(() => import("./tools.runtime.js")); @@ -59,6 +61,7 @@ export async function getMemoryManagerContextWithPurpose(params: { cfg: OpenClawConfig; agentId: string; purpose?: "default" | "status" | "cli"; + acquireLocalService?: MemoryCoreAcquireLocalService; }): Promise< | { manager: NonNullable; @@ -74,6 +77,7 @@ export async function getMemoryManagerContextWithPurpose(params: { cfg: params.cfg, agentId: params.agentId, purpose: params.purpose, + ...(params.acquireLocalService ? { acquireLocalService: params.acquireLocalService } : {}), }); return manager ? { diff --git a/extensions/memory-core/src/tools.test.ts b/extensions/memory-core/src/tools.test.ts index 72bf2dd43dcb..274b01b17e25 100644 --- a/extensions/memory-core/src/tools.test.ts +++ b/extensions/memory-core/src/tools.test.ts @@ -169,6 +169,25 @@ describe("memory_search unavailable payloads", () => { expect(details.results.map((entry) => entry.score)).toEqual([1, 1, 1, 2]); }); + it("passes the host local-service hook to tool memory managers", async () => { + const acquireLocalService = vi.fn(async () => undefined); + const tool = createMemorySearchTool({ + config: asOpenClawConfig({ + agents: { list: [{ id: "main", default: true }] }, + }), + acquireLocalService, + }); + if (!tool) { + throw new Error("tool missing"); + } + + await tool.execute("local-service-hook", { query: "hello" }); + + expect(getMemorySearchManagerMockParams()).toEqual([ + expect.objectContaining({ acquireLocalService }), + ]); + }); + it("returns explicit unavailable metadata for quota failures", async () => { setMemorySearchImpl(async () => { throw new Error("openai embeddings failed: 429 insufficient_quota"); diff --git a/extensions/memory-core/src/tools.ts b/extensions/memory-core/src/tools.ts index c0293fa4ee71..5b6027277e98 100644 --- a/extensions/memory-core/src/tools.ts +++ b/extensions/memory-core/src/tools.ts @@ -23,6 +23,7 @@ import { resolveMemoryDeepDreamingConfig, } from "openclaw/plugin-sdk/memory-core-host-status"; import { asRecord } from "./dreaming-shared.js"; +import type { MemoryCoreAcquireLocalService } from "./memory/embedding-local-service.js"; import { filterMemorySearchHitsBySessionVisibility } from "./session-search-visibility.js"; import { recordShortTermRecalls } from "./short-term-promotion.js"; import { @@ -453,6 +454,7 @@ export function createMemorySearchTool(options: { agentSessionKey?: string; sandboxed?: boolean; oneShotCliRun?: boolean; + acquireLocalService?: MemoryCoreAcquireLocalService; }) { return createMemoryTool({ options, @@ -525,6 +527,7 @@ export function createMemorySearchTool(options: { cfg, agentId, purpose: memoryManagerPurpose, + acquireLocalService: options.acquireLocalService, }), ), ) @@ -614,6 +617,7 @@ export function createMemorySearchTool(options: { cfg, agentId, purpose: memoryManagerPurpose, + acquireLocalService: options.acquireLocalService, }), ); if ("error" in refreshed) { @@ -775,6 +779,7 @@ export function createMemoryGetTool(options: { agentId?: string; agentSessionKey?: string; sandboxed?: boolean; + acquireLocalService?: MemoryCoreAcquireLocalService; }) { return createMemoryTool({ options, @@ -839,6 +844,7 @@ export function createMemoryGetTool(options: { cfg, agentId, purpose: "status", + acquireLocalService: options.acquireLocalService, }); if ("error" in memory) { return jsonResult({ path: relPath, text: "", disabled: true, error: memory.error });