diff --git a/CHANGELOG.md b/CHANGELOG.md index d12a3a31cec..26871fed99b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,7 @@ Docs: https://docs.openclaw.ai - Doctor/plugins: update configured plugin installs whose stale manifests still declare channels without `channelConfigs`, so beta upgrades repair old Discord-style package payloads during `doctor --fix`. - Doctor/plugins: repair configured external plugin installs whose persisted install record points at a missing package directory, so upgrades reconcile phantom npm metadata before plugin runtime validation. Thanks @vincentkoc. - Active Memory: keep non-empty `memory_search` results from being fast-failed as empty when debug telemetry reports zero hits. +- Active Memory: preserve the target agent context when building embedded recall plugin tools so `memory_search` and `memory_get` stay available for explicit recall sessions. Fixes #76343. Thanks @Countermarch. - Plugins/externalization: repair missing configured plugin installs from npm by default, reserve ClawHub downloads for explicit `clawhubSpec` metadata, and cover agent-runtime/env-selected plugin repair. Thanks @vincentkoc. - Plugins/install: allow official catalog-matched npm channel plugins such as Feishu to pass the trusted install scanner path while keeping spoofed package names blocked. Thanks @vincentkoc. - Feishu: keep timeout env parsing separate from the HTTP client wrapper so package security scans no longer report a false env-harvesting hit during install. Thanks @vincentkoc. diff --git a/extensions/memory-core/index.ts b/extensions/memory-core/index.ts index b12cef8345d..94bbe3a23cf 100644 --- a/extensions/memory-core/index.ts +++ b/extensions/memory-core/index.ts @@ -1,7 +1,7 @@ import { jsonResult, resolveMemorySearchConfig, - resolveSessionAgentId, + resolveSessionAgentIds, type MemoryPluginRuntime, type OpenClawConfig, } from "openclaw/plugin-sdk/memory-core-host-runtime-core"; @@ -23,6 +23,7 @@ type RuntimeProviderModule = typeof import("./src/runtime-provider.js"); type MemoryToolOptions = { config?: OpenClawConfig; getConfig?: () => OpenClawConfig | undefined; + agentId?: string; agentSessionKey?: string; sandboxed?: boolean; }; @@ -49,9 +50,10 @@ function hasMemoryToolContext(options: MemoryToolOptions): boolean { if (!cfg) { return false; } - const agentId = resolveSessionAgentId({ + const { sessionAgentId: agentId } = resolveSessionAgentIds({ sessionKey: options.agentSessionKey, config: cfg, + agentId: options.agentId, }); return Boolean(resolveMemorySearchConfig(cfg, agentId)); } @@ -145,6 +147,7 @@ function resolveMemoryToolOptions(ctx: OpenClawPluginToolContext): MemoryToolOpt return { config: getConfig(), getConfig, + agentId: ctx.agentId, agentSessionKey: ctx.sessionKey, sandboxed: ctx.sandboxed, }; diff --git a/extensions/memory-core/src/memory-tool-manager-mock.ts b/extensions/memory-core/src/memory-tool-manager-mock.ts index c5273de3596..8303ceabdf0 100644 --- a/extensions/memory-core/src/memory-tool-manager-mock.ts +++ b/extensions/memory-core/src/memory-tool-manager-mock.ts @@ -52,7 +52,7 @@ const stubManager = { close: vi.fn(), }; -const getMemorySearchManagerMock = vi.fn(async (_params: { cfg?: unknown }) => ({ +const getMemorySearchManagerMock = vi.fn(async (_params: { cfg?: unknown; agentId?: string }) => ({ manager: stubManager, })); const readAgentMemoryFileMock = vi.fn( @@ -118,6 +118,10 @@ export function getMemorySearchManagerMockConfigs(): unknown[] { return getMemorySearchManagerMock.mock.calls.map(([params]) => params.cfg); } +export function getMemorySearchManagerMockParams(): Array<{ cfg?: unknown; agentId?: string }> { + return getMemorySearchManagerMock.mock.calls.map(([params]) => params); +} + export function getReadAgentMemoryFileMockCalls(): number { return readAgentMemoryFileMock.mock.calls.length; } diff --git a/extensions/memory-core/src/tools.shared.ts b/extensions/memory-core/src/tools.shared.ts index 754664670de..4bbeeea5301 100644 --- a/extensions/memory-core/src/tools.shared.ts +++ b/extensions/memory-core/src/tools.shared.ts @@ -1,7 +1,7 @@ import { listMemoryCorpusSupplements, resolveMemorySearchConfig, - resolveSessionAgentId, + resolveSessionAgentIds, type MemoryCorpusSearchResult, type AnyAgentTool, type OpenClawConfig, @@ -16,6 +16,7 @@ type MemorySearchManagerResult = Awaited< type MemoryToolOptions = { config?: OpenClawConfig; getConfig?: () => OpenClawConfig | undefined; + agentId?: string; agentSessionKey?: string; }; @@ -54,9 +55,10 @@ function resolveMemoryToolContext(options: MemoryToolOptions) { if (!cfg) { return null; } - const agentId = resolveSessionAgentId({ + const { sessionAgentId: agentId } = resolveSessionAgentIds({ sessionKey: options.agentSessionKey, config: cfg, + agentId: options.agentId, }); if (!resolveMemorySearchConfig(cfg, agentId)) { return null; diff --git a/extensions/memory-core/src/tools.test-helpers.ts b/extensions/memory-core/src/tools.test-helpers.ts index 54c84deadff..60aa54d934c 100644 --- a/extensions/memory-core/src/tools.test-helpers.ts +++ b/extensions/memory-core/src/tools.test-helpers.ts @@ -12,10 +12,12 @@ export function createDefaultMemoryToolConfig(): OpenClawConfig { export function createMemorySearchToolOrThrow(params?: { config?: OpenClawConfig; + agentId?: string; agentSessionKey?: string; }) { const tool = createMemorySearchTool({ config: params?.config ?? createDefaultMemoryToolConfig(), + ...(params?.agentId ? { agentId: params.agentId } : {}), ...(params?.agentSessionKey ? { agentSessionKey: params.agentSessionKey } : {}), }); if (!tool) { diff --git a/extensions/memory-core/src/tools.test.ts b/extensions/memory-core/src/tools.test.ts index 489205eada2..4f8a42a3fc4 100644 --- a/extensions/memory-core/src/tools.test.ts +++ b/extensions/memory-core/src/tools.test.ts @@ -1,6 +1,7 @@ import { beforeEach, describe, expect, it } from "vitest"; import { getMemorySearchManagerMockConfigs, + getMemorySearchManagerMockParams, resetMemoryToolMockState, setMemoryBackend, setMemorySearchImpl, @@ -107,6 +108,25 @@ describe("memory_search unavailable payloads", () => { ); }); + it("uses explicit plugin context agent over synthetic active-memory session keys", async () => { + const tool = createMemorySearchToolOrThrow({ + config: asOpenClawConfig({ + agents: { + list: [ + { id: "main", default: true, memorySearch: { enabled: false } }, + { id: "recall", memorySearch: { enabled: true } }, + ], + }, + }), + agentId: "recall", + agentSessionKey: "explicit:user-session:active-memory:abc123", + }); + + await tool.execute("recall", { query: "favorite food" }); + + expect(getMemorySearchManagerMockParams().at(-1)?.agentId).toBe("recall"); + }); + it("re-resolves config when executing a previously created tool", async () => { const startupConfig = asOpenClawConfig({ agents: { diff --git a/extensions/memory-core/src/tools.ts b/extensions/memory-core/src/tools.ts index 54ea39efe3c..bcbf73cf84a 100644 --- a/extensions/memory-core/src/tools.ts +++ b/extensions/memory-core/src/tools.ts @@ -183,6 +183,7 @@ async function executeMemoryReadResult(params: { export function createMemorySearchTool(options: { config?: OpenClawConfig; getConfig?: () => OpenClawConfig | undefined; + agentId?: string; agentSessionKey?: string; sandboxed?: boolean; }) { @@ -346,6 +347,7 @@ export function createMemorySearchTool(options: { export function createMemoryGetTool(options: { config?: OpenClawConfig; getConfig?: () => OpenClawConfig | undefined; + agentId?: string; agentSessionKey?: string; }) { return createMemoryTool({ diff --git a/src/agents/openclaw-tools.plugin-context.test.ts b/src/agents/openclaw-tools.plugin-context.test.ts index 2c83f7b9b5c..70fec5b31cf 100644 --- a/src/agents/openclaw-tools.plugin-context.test.ts +++ b/src/agents/openclaw-tools.plugin-context.test.ts @@ -109,6 +109,34 @@ describe("openclaw plugin tool context", () => { ); }); + it("uses requester agent override for synthetic embedded session keys", () => { + const recallWorkspace = path.join(process.cwd(), "tmp-recall-workspace"); + const config = { + agents: { + defaults: { workspace: path.join(process.cwd(), "tmp-default-workspace") }, + list: [ + { id: "main", default: true }, + { id: "recall", workspace: recallWorkspace }, + ], + }, + } as never; + const result = resolveOpenClawPluginToolInputs({ + options: { + config, + agentSessionKey: "explicit:user-session:active-memory:abc123", + requesterAgentIdOverride: "recall", + }, + resolvedConfig: config, + }); + + expect(result.context).toEqual( + expect.objectContaining({ + agentId: "recall", + workspaceDir: recallWorkspace, + }), + ); + }); + it("forwards browser session wiring", () => { const result = resolveOpenClawPluginToolInputs({ options: { diff --git a/src/agents/openclaw-tools.plugin-context.ts b/src/agents/openclaw-tools.plugin-context.ts index 18906193512..125ca87e2f8 100644 --- a/src/agents/openclaw-tools.plugin-context.ts +++ b/src/agents/openclaw-tools.plugin-context.ts @@ -1,7 +1,7 @@ import type { OpenClawConfig } from "../config/types.openclaw.js"; import { normalizeDeliveryContext } from "../utils/delivery-context.js"; import type { GatewayMessageChannel } from "../utils/message-channel.js"; -import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "./agent-scope.js"; +import { resolveAgentWorkspaceDir, resolveSessionAgentIds } from "./agent-scope.js"; import type { ToolFsPolicy } from "./tool-fs-policy.js"; import { resolveWorkspaceRoot } from "./workspace-dir.js"; @@ -16,6 +16,7 @@ export type OpenClawPluginToolOptions = { config?: OpenClawConfig; fsPolicy?: ToolFsPolicy; requesterSenderId?: string | null; + requesterAgentIdOverride?: string; senderIsOwner?: boolean; sessionId?: string; sandboxBrowserBridgeUrl?: string; @@ -31,9 +32,10 @@ export function resolveOpenClawPluginToolInputs(params: { getRuntimeConfig?: () => OpenClawConfig | undefined; }) { const { options, resolvedConfig, runtimeConfig, getRuntimeConfig } = params; - const sessionAgentId = resolveSessionAgentId({ + const { sessionAgentId } = resolveSessionAgentIds({ sessionKey: options?.agentSessionKey, config: resolvedConfig, + agentId: options?.requesterAgentIdOverride, }); const inferredWorkspaceDir = options?.workspaceDir || !resolvedConfig diff --git a/src/plugin-sdk/memory-core-host-runtime-core.ts b/src/plugin-sdk/memory-core-host-runtime-core.ts index 6442dd1835a..93450933f8c 100644 --- a/src/plugin-sdk/memory-core-host-runtime-core.ts +++ b/src/plugin-sdk/memory-core-host-runtime-core.ts @@ -8,7 +8,11 @@ export { type AnyAgentTool, } from "../agents/tools/common.js"; export { resolveCronStyleNow } from "../agents/current-time.js"; -export { resolveDefaultAgentId, resolveSessionAgentId } from "../agents/agent-scope.js"; +export { + resolveDefaultAgentId, + resolveSessionAgentId, + resolveSessionAgentIds, +} from "../agents/agent-scope.js"; export { resolveMemorySearchConfig } from "../agents/memory-search.js"; export { parseNonNegativeByteSize } from "../config/byte-size.js"; export { getRuntimeConfig, loadConfig } from "../config/config.js";