fix(memory): preserve active recall tool agent context (#76380) · openclaw/openclaw@f74983e
Takhoffman
·
2026-05-03
·
via Recent Commits to openclaw:main
| Original file line number | Diff line number | Diff line change |
|---|
@@ -33,6 +33,7 @@ Docs: https://docs.openclaw.ai
|
33 | 33 | - 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`. |
34 | 34 | - 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. |
35 | 35 | - Active Memory: keep non-empty `memory_search` results from being fast-failed as empty when debug telemetry reports zero hits. |
| 36 | +- 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. |
36 | 37 | - 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. |
37 | 38 | - 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. |
38 | 39 | - 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. |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { |
2 | 2 | jsonResult, |
3 | 3 | resolveMemorySearchConfig, |
4 | | -resolveSessionAgentId, |
| 4 | +resolveSessionAgentIds, |
5 | 5 | type MemoryPluginRuntime, |
6 | 6 | type OpenClawConfig, |
7 | 7 | } from "openclaw/plugin-sdk/memory-core-host-runtime-core"; |
@@ -23,6 +23,7 @@ type RuntimeProviderModule = typeof import("./src/runtime-provider.js");
|
23 | 23 | type MemoryToolOptions = { |
24 | 24 | config?: OpenClawConfig; |
25 | 25 | getConfig?: () => OpenClawConfig | undefined; |
| 26 | +agentId?: string; |
26 | 27 | agentSessionKey?: string; |
27 | 28 | sandboxed?: boolean; |
28 | 29 | }; |
@@ -49,9 +50,10 @@ function hasMemoryToolContext(options: MemoryToolOptions): boolean {
|
49 | 50 | if (!cfg) { |
50 | 51 | return false; |
51 | 52 | } |
52 | | -const agentId = resolveSessionAgentId({ |
| 53 | +const { sessionAgentId: agentId } = resolveSessionAgentIds({ |
53 | 54 | sessionKey: options.agentSessionKey, |
54 | 55 | config: cfg, |
| 56 | +agentId: options.agentId, |
55 | 57 | }); |
56 | 58 | return Boolean(resolveMemorySearchConfig(cfg, agentId)); |
57 | 59 | } |
@@ -145,6 +147,7 @@ function resolveMemoryToolOptions(ctx: OpenClawPluginToolContext): MemoryToolOpt
|
145 | 147 | return { |
146 | 148 | config: getConfig(), |
147 | 149 | getConfig, |
| 150 | +agentId: ctx.agentId, |
148 | 151 | agentSessionKey: ctx.sessionKey, |
149 | 152 | sandboxed: ctx.sandboxed, |
150 | 153 | }; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -52,7 +52,7 @@ const stubManager = {
|
52 | 52 | close: vi.fn(), |
53 | 53 | }; |
54 | 54 | |
55 | | -const getMemorySearchManagerMock = vi.fn(async (_params: { cfg?: unknown }) => ({ |
| 55 | +const getMemorySearchManagerMock = vi.fn(async (_params: { cfg?: unknown; agentId?: string }) => ({ |
56 | 56 | manager: stubManager, |
57 | 57 | })); |
58 | 58 | const readAgentMemoryFileMock = vi.fn( |
@@ -118,6 +118,10 @@ export function getMemorySearchManagerMockConfigs(): unknown[] {
|
118 | 118 | return getMemorySearchManagerMock.mock.calls.map(([params]) => params.cfg); |
119 | 119 | } |
120 | 120 | |
| 121 | +export function getMemorySearchManagerMockParams(): Array<{ cfg?: unknown; agentId?: string }> { |
| 122 | +return getMemorySearchManagerMock.mock.calls.map(([params]) => params); |
| 123 | +} |
| 124 | + |
121 | 125 | export function getReadAgentMemoryFileMockCalls(): number { |
122 | 126 | return readAgentMemoryFileMock.mock.calls.length; |
123 | 127 | } |
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { |
2 | 2 | listMemoryCorpusSupplements, |
3 | 3 | resolveMemorySearchConfig, |
4 | | -resolveSessionAgentId, |
| 4 | +resolveSessionAgentIds, |
5 | 5 | type MemoryCorpusSearchResult, |
6 | 6 | type AnyAgentTool, |
7 | 7 | type OpenClawConfig, |
@@ -16,6 +16,7 @@ type MemorySearchManagerResult = Awaited<
|
16 | 16 | type MemoryToolOptions = { |
17 | 17 | config?: OpenClawConfig; |
18 | 18 | getConfig?: () => OpenClawConfig | undefined; |
| 19 | +agentId?: string; |
19 | 20 | agentSessionKey?: string; |
20 | 21 | }; |
21 | 22 | |
@@ -54,9 +55,10 @@ function resolveMemoryToolContext(options: MemoryToolOptions) {
|
54 | 55 | if (!cfg) { |
55 | 56 | return null; |
56 | 57 | } |
57 | | -const agentId = resolveSessionAgentId({ |
| 58 | +const { sessionAgentId: agentId } = resolveSessionAgentIds({ |
58 | 59 | sessionKey: options.agentSessionKey, |
59 | 60 | config: cfg, |
| 61 | +agentId: options.agentId, |
60 | 62 | }); |
61 | 63 | if (!resolveMemorySearchConfig(cfg, agentId)) { |
62 | 64 | return null; |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -12,10 +12,12 @@ export function createDefaultMemoryToolConfig(): OpenClawConfig {
|
12 | 12 | |
13 | 13 | export function createMemorySearchToolOrThrow(params?: { |
14 | 14 | config?: OpenClawConfig; |
| 15 | +agentId?: string; |
15 | 16 | agentSessionKey?: string; |
16 | 17 | }) { |
17 | 18 | const tool = createMemorySearchTool({ |
18 | 19 | config: params?.config ?? createDefaultMemoryToolConfig(), |
| 20 | + ...(params?.agentId ? { agentId: params.agentId } : {}), |
19 | 21 | ...(params?.agentSessionKey ? { agentSessionKey: params.agentSessionKey } : {}), |
20 | 22 | }); |
21 | 23 | if (!tool) { |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import { beforeEach, describe, expect, it } from "vitest"; |
2 | 2 | import { |
3 | 3 | getMemorySearchManagerMockConfigs, |
| 4 | +getMemorySearchManagerMockParams, |
4 | 5 | resetMemoryToolMockState, |
5 | 6 | setMemoryBackend, |
6 | 7 | setMemorySearchImpl, |
@@ -107,6 +108,25 @@ describe("memory_search unavailable payloads", () => {
|
107 | 108 | ); |
108 | 109 | }); |
109 | 110 | |
| 111 | +it("uses explicit plugin context agent over synthetic active-memory session keys", async () => { |
| 112 | +const tool = createMemorySearchToolOrThrow({ |
| 113 | +config: asOpenClawConfig({ |
| 114 | +agents: { |
| 115 | +list: [ |
| 116 | +{ id: "main", default: true, memorySearch: { enabled: false } }, |
| 117 | +{ id: "recall", memorySearch: { enabled: true } }, |
| 118 | +], |
| 119 | +}, |
| 120 | +}), |
| 121 | +agentId: "recall", |
| 122 | +agentSessionKey: "explicit:user-session:active-memory:abc123", |
| 123 | +}); |
| 124 | + |
| 125 | +await tool.execute("recall", { query: "favorite food" }); |
| 126 | + |
| 127 | +expect(getMemorySearchManagerMockParams().at(-1)?.agentId).toBe("recall"); |
| 128 | +}); |
| 129 | + |
110 | 130 | it("re-resolves config when executing a previously created tool", async () => { |
111 | 131 | const startupConfig = asOpenClawConfig({ |
112 | 132 | agents: { |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -183,6 +183,7 @@ async function executeMemoryReadResult<T>(params: {
|
183 | 183 | export function createMemorySearchTool(options: { |
184 | 184 | config?: OpenClawConfig; |
185 | 185 | getConfig?: () => OpenClawConfig | undefined; |
| 186 | +agentId?: string; |
186 | 187 | agentSessionKey?: string; |
187 | 188 | sandboxed?: boolean; |
188 | 189 | }) { |
@@ -346,6 +347,7 @@ export function createMemorySearchTool(options: {
|
346 | 347 | export function createMemoryGetTool(options: { |
347 | 348 | config?: OpenClawConfig; |
348 | 349 | getConfig?: () => OpenClawConfig | undefined; |
| 350 | +agentId?: string; |
349 | 351 | agentSessionKey?: string; |
350 | 352 | }) { |
351 | 353 | return createMemoryTool({ |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -109,6 +109,34 @@ describe("openclaw plugin tool context", () => {
|
109 | 109 | ); |
110 | 110 | }); |
111 | 111 | |
| 112 | +it("uses requester agent override for synthetic embedded session keys", () => { |
| 113 | +const recallWorkspace = path.join(process.cwd(), "tmp-recall-workspace"); |
| 114 | +const config = { |
| 115 | +agents: { |
| 116 | +defaults: { workspace: path.join(process.cwd(), "tmp-default-workspace") }, |
| 117 | +list: [ |
| 118 | +{ id: "main", default: true }, |
| 119 | +{ id: "recall", workspace: recallWorkspace }, |
| 120 | +], |
| 121 | +}, |
| 122 | +} as never; |
| 123 | +const result = resolveOpenClawPluginToolInputs({ |
| 124 | +options: { |
| 125 | + config, |
| 126 | +agentSessionKey: "explicit:user-session:active-memory:abc123", |
| 127 | +requesterAgentIdOverride: "recall", |
| 128 | +}, |
| 129 | +resolvedConfig: config, |
| 130 | +}); |
| 131 | + |
| 132 | +expect(result.context).toEqual( |
| 133 | +expect.objectContaining({ |
| 134 | +agentId: "recall", |
| 135 | +workspaceDir: recallWorkspace, |
| 136 | +}), |
| 137 | +); |
| 138 | +}); |
| 139 | + |
112 | 140 | it("forwards browser session wiring", () => { |
113 | 141 | const result = resolveOpenClawPluginToolInputs({ |
114 | 142 | options: { |
|
| Original file line number | Diff line number | Diff line change |
|---|
|
1 | 1 | import type { OpenClawConfig } from "../config/types.openclaw.js"; |
2 | 2 | import { normalizeDeliveryContext } from "../utils/delivery-context.js"; |
3 | 3 | import type { GatewayMessageChannel } from "../utils/message-channel.js"; |
4 | | -import { resolveAgentWorkspaceDir, resolveSessionAgentId } from "./agent-scope.js"; |
| 4 | +import { resolveAgentWorkspaceDir, resolveSessionAgentIds } from "./agent-scope.js"; |
5 | 5 | import type { ToolFsPolicy } from "./tool-fs-policy.js"; |
6 | 6 | import { resolveWorkspaceRoot } from "./workspace-dir.js"; |
7 | 7 | |
@@ -16,6 +16,7 @@ export type OpenClawPluginToolOptions = {
|
16 | 16 | config?: OpenClawConfig; |
17 | 17 | fsPolicy?: ToolFsPolicy; |
18 | 18 | requesterSenderId?: string | null; |
| 19 | +requesterAgentIdOverride?: string; |
19 | 20 | senderIsOwner?: boolean; |
20 | 21 | sessionId?: string; |
21 | 22 | sandboxBrowserBridgeUrl?: string; |
@@ -31,9 +32,10 @@ export function resolveOpenClawPluginToolInputs(params: {
|
31 | 32 | getRuntimeConfig?: () => OpenClawConfig | undefined; |
32 | 33 | }) { |
33 | 34 | const { options, resolvedConfig, runtimeConfig, getRuntimeConfig } = params; |
34 | | -const sessionAgentId = resolveSessionAgentId({ |
| 35 | +const { sessionAgentId } = resolveSessionAgentIds({ |
35 | 36 | sessionKey: options?.agentSessionKey, |
36 | 37 | config: resolvedConfig, |
| 38 | +agentId: options?.requesterAgentIdOverride, |
37 | 39 | }); |
38 | 40 | const inferredWorkspaceDir = |
39 | 41 | options?.workspaceDir || !resolvedConfig |
|
| Original file line number | Diff line number | Diff line change |
|---|
@@ -8,7 +8,11 @@ export {
|
8 | 8 | type AnyAgentTool, |
9 | 9 | } from "../agents/tools/common.js"; |
10 | 10 | export { resolveCronStyleNow } from "../agents/current-time.js"; |
11 | | -export { resolveDefaultAgentId, resolveSessionAgentId } from "../agents/agent-scope.js"; |
| 11 | +export { |
| 12 | +resolveDefaultAgentId, |
| 13 | +resolveSessionAgentId, |
| 14 | +resolveSessionAgentIds, |
| 15 | +} from "../agents/agent-scope.js"; |
12 | 16 | export { resolveMemorySearchConfig } from "../agents/memory-search.js"; |
13 | 17 | export { parseNonNegativeByteSize } from "../config/byte-size.js"; |
14 | 18 | export { getRuntimeConfig, loadConfig } from "../config/config.js"; |
|
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。