memory-search-plugin 0.10.0 → 1.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/gateway-client.ts +45 -64
- package/identity.ts +28 -88
- package/index.js +136 -277
- package/index.ts +160 -330
- package/openclaw.plugin.json +13 -16
- package/package.json +1 -1
package/index.ts
CHANGED
|
@@ -1,210 +1,111 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
*
|
|
8
|
-
* 基于:
|
|
9
|
-
* - OpenClaw_记忆隐私分阶段改造技术方案_V1.md(第 5.1 / 6.1 / 7.2 节)
|
|
10
|
-
* - 存储方案设计-V2-虾生态2.md(第 4 节可见性规则)
|
|
11
|
-
* - 三种场景-存储与查询方案.md(三场景可见性 + 存储 payload 格式)
|
|
12
|
-
*
|
|
13
|
-
* 场景可见性:
|
|
14
|
-
* private_own: owner + peer(全部) + group(并集) + knowledge(用户ACL)
|
|
15
|
-
* private_other: peer(仅自己) + group(交集) + knowledge(用户ACL)
|
|
16
|
-
* group: group(仅当前群) + knowledge(群ACL)
|
|
17
|
-
* knowledge: knowledge(用户ACL)
|
|
18
|
-
* admin: 全部
|
|
19
|
-
*
|
|
20
|
-
* 原理:
|
|
21
|
-
* memory-core 只注册了 memory_search 和 memory_get 两个工具。
|
|
22
|
-
* append / insert / replace 等文件编辑工具不属于 memory slot,替换后不受影响。
|
|
23
|
-
* 原始的 memory_get 实现可以通过 api.runtime.tools.createMemoryGetTool() 获取,
|
|
24
|
-
* 用于非 MEMORY 路径的 passthrough。
|
|
25
|
-
*/
|
|
1
|
+
import { resolveIdentity } from "./identity";
|
|
2
|
+
import {
|
|
3
|
+
createGatewayClient,
|
|
4
|
+
type GatewayClient,
|
|
5
|
+
type GatewayClientConfig,
|
|
6
|
+
} from "./gateway-client";
|
|
26
7
|
|
|
27
|
-
|
|
28
|
-
import { createGatewayClient } from "./gateway-client";
|
|
29
|
-
import { resolveIdentityFromParams, resolveIdentity, extractAgentId } from "./identity";
|
|
30
|
-
import type { IdentityParams } from "./identity";
|
|
31
|
-
import type { MemorySearchResult } from "./gateway-client";
|
|
32
|
-
|
|
33
|
-
// ── Prompt Section ──────────────────────────────────────
|
|
34
|
-
// 告诉 LLM 如何使用 memory 工具(和 memory-core 的逻辑一致)
|
|
8
|
+
// ── Prompt Section ──
|
|
35
9
|
|
|
36
10
|
const buildPromptSection = ({
|
|
37
11
|
availableTools,
|
|
38
|
-
citationsMode,
|
|
39
12
|
}: {
|
|
40
13
|
availableTools: Set<string>;
|
|
41
|
-
citationsMode: string;
|
|
42
14
|
}) => {
|
|
43
|
-
const
|
|
44
|
-
const
|
|
45
|
-
if (!
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
"
|
|
51
|
-
"
|
|
52
|
-
|
|
53
|
-
} else if (hasMemorySearch) {
|
|
54
|
-
toolGuidance =
|
|
55
|
-
"Before answering anything about prior work, decisions, dates, people, preferences, or todos: " +
|
|
56
|
-
"run memory_search on MEMORY.md + memory/*.md and answer from the matching results. " +
|
|
57
|
-
"If low confidence after search, say you checked.";
|
|
58
|
-
} else {
|
|
59
|
-
toolGuidance =
|
|
60
|
-
"Before answering anything about prior work, decisions, dates, people, preferences, or todos " +
|
|
61
|
-
"that already point to a specific memory file or note: " +
|
|
62
|
-
"run memory_get to pull only the needed lines. " +
|
|
63
|
-
"If low confidence after reading them, say you checked.";
|
|
64
|
-
}
|
|
65
|
-
|
|
66
|
-
const lines = ["## Memory Recall", toolGuidance];
|
|
67
|
-
|
|
68
|
-
lines.push(
|
|
15
|
+
const hasSearch = availableTools.has("memory_search");
|
|
16
|
+
const hasGet = availableTools.has("memory_get");
|
|
17
|
+
if (!hasSearch && !hasGet) return [];
|
|
18
|
+
|
|
19
|
+
const lines = [
|
|
20
|
+
"## Memory Recall",
|
|
21
|
+
"Before answering anything about prior work, decisions, dates, people, preferences, or todos: " +
|
|
22
|
+
"run memory_search for semantic search. " +
|
|
23
|
+
"If low confidence after search, say you checked.",
|
|
24
|
+
"",
|
|
69
25
|
"**Context extraction:** When calling memory_search or memory_get, " +
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
26
|
+
"you MUST extract the following from the UntrustedContext section and pass as parameters: " +
|
|
27
|
+
"`owner_id`, `sender_id`, `agent_id`, `conversation_type`, `group_id`. " +
|
|
28
|
+
"Look for lines like `owner_id: xxx`, `sender_id: xxx`, etc. " +
|
|
29
|
+
"If a field is not present, omit it. " +
|
|
30
|
+
"Do NOT invent or guess values — only use what is explicitly stated in UntrustedContext.",
|
|
31
|
+
"",
|
|
32
|
+
];
|
|
75
33
|
|
|
76
|
-
if (citationsMode === "off") {
|
|
77
|
-
lines.push(
|
|
78
|
-
"Citations are disabled: do not mention file paths or line numbers in replies unless the user explicitly asks."
|
|
79
|
-
);
|
|
80
|
-
} else {
|
|
81
|
-
lines.push(
|
|
82
|
-
"Citations: include Source: <path#line> when it helps the user verify memory snippets."
|
|
83
|
-
);
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
lines.push("");
|
|
87
34
|
return lines;
|
|
88
35
|
};
|
|
89
36
|
|
|
90
|
-
// ──
|
|
91
|
-
|
|
92
|
-
/**
|
|
93
|
-
* 判断是否是 MEMORY 路径
|
|
94
|
-
* V1 方案第一阶段接管的路径:MEMORY.md 和 memory/**\/*.md
|
|
95
|
-
*/
|
|
96
|
-
function isMemoryPath(path: string): boolean {
|
|
97
|
-
const normalized = path.replace(/\\/g, "/").replace(/^\.\//, "");
|
|
98
|
-
return (
|
|
99
|
-
normalized === "MEMORY.md" ||
|
|
100
|
-
normalized.startsWith("memory/") ||
|
|
101
|
-
normalized.endsWith("/MEMORY.md") ||
|
|
102
|
-
normalized.includes("/memory/")
|
|
103
|
-
);
|
|
104
|
-
}
|
|
105
|
-
|
|
106
|
-
// ── 结果格式化 ──────────────────────────────────────────
|
|
37
|
+
// ── Plugin Entry ──
|
|
107
38
|
|
|
108
|
-
|
|
109
|
-
* 将 Gateway 搜索结果格式化为 LLM 友好的文本
|
|
110
|
-
* 包含 memory_type、来源文件和相似度信息,方便 LLM 做 citation
|
|
111
|
-
* 字段对应 V2 存储方案的 payload 结构
|
|
112
|
-
*
|
|
113
|
-
* 注意:后端返回的相似度字段是 score(不是 similarity)
|
|
114
|
-
*/
|
|
115
|
-
function formatSearchResults(results: MemorySearchResult[]): string {
|
|
116
|
-
if (results.length === 0) {
|
|
117
|
-
return "No relevant memories found.";
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
return results
|
|
121
|
-
.map((r, i) => {
|
|
122
|
-
// 构建来源描述:memory_type + 归属对象
|
|
123
|
-
let sourceDesc = r.memory_type;
|
|
124
|
-
if (r.memory_type === "peer" && r.peer_id) {
|
|
125
|
-
sourceDesc = `peer/${r.peer_id}`;
|
|
126
|
-
} else if (r.memory_type === "group" && r.group_id) {
|
|
127
|
-
sourceDesc = `group/${r.group_id}`;
|
|
128
|
-
} else if (r.memory_type === "knowledge" && r.kb_id) {
|
|
129
|
-
sourceDesc = `knowledge/${r.kb_id}`;
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
const filePart = r.source_file ? ` | ${r.source_file}` : "";
|
|
133
|
-
const header = `[${i + 1}] (${sourceDesc}, score: ${r.score.toFixed(2)}${filePart})`;
|
|
134
|
-
return `${header}\n${r.content}`;
|
|
135
|
-
})
|
|
136
|
-
.join("\n\n");
|
|
137
|
-
}
|
|
138
|
-
|
|
139
|
-
// ── 插件定义 ────────────────────────────────────────────
|
|
140
|
-
|
|
141
|
-
export default definePluginEntry({
|
|
39
|
+
export default {
|
|
142
40
|
id: "memory-search-plugin",
|
|
143
|
-
name: "Memory Search Plugin",
|
|
144
|
-
description: "Routes memory to external Memory Search Gateway with ACL",
|
|
145
|
-
kind: "memory" as const,
|
|
146
41
|
|
|
147
42
|
register(api: any) {
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
//
|
|
43
|
+
const config: GatewayClientConfig = {
|
|
44
|
+
gatewayUrl:
|
|
45
|
+
api.getConfig?.("MEMORY_GATEWAY_URL") ||
|
|
46
|
+
process.env.MEMORY_GATEWAY_URL ||
|
|
47
|
+
"",
|
|
48
|
+
gatewayToken:
|
|
49
|
+
api.getConfig?.("MEMORY_GATEWAY_TOKEN") ||
|
|
50
|
+
process.env.MEMORY_GATEWAY_TOKEN ||
|
|
51
|
+
"",
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
if (!config.gatewayUrl) {
|
|
55
|
+
console.warn("[memory-search-plugin] MEMORY_GATEWAY_URL not set, plugin disabled");
|
|
56
|
+
return;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const gateway: GatewayClient = createGatewayClient(config);
|
|
60
|
+
|
|
61
|
+
// ── Prompt Section ──
|
|
62
|
+
api.registerPromptSection?.({
|
|
63
|
+
id: "memory-recall",
|
|
64
|
+
position: "top",
|
|
65
|
+
build: buildPromptSection,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
// ── memory_search (vector search on memory_pipeline_facts) ──
|
|
167
69
|
api.registerTool(
|
|
168
70
|
(ctx: any) => {
|
|
169
|
-
// fallback: 如果 sessionKey 仍可用,保留兼容
|
|
170
|
-
const fallbackIdentity = ctx.sessionKey ? resolveIdentity(ctx.sessionKey) : null;
|
|
171
|
-
const fallbackAgentId = ctx.sessionKey ? extractAgentId(ctx.sessionKey) : null;
|
|
172
|
-
|
|
173
71
|
return {
|
|
174
72
|
name: "memory_search",
|
|
175
73
|
description:
|
|
176
|
-
"Semantically search
|
|
177
|
-
"
|
|
74
|
+
"Semantically search memories. " +
|
|
75
|
+
"Extract owner_id, sender_id, agent_id, conversation_type, group_id from UntrustedContext.",
|
|
178
76
|
parameters: {
|
|
179
77
|
type: "object" as const,
|
|
180
78
|
properties: {
|
|
181
|
-
query: {
|
|
79
|
+
query: { type: "string" as const, description: "The search query" },
|
|
80
|
+
owner_id: {
|
|
182
81
|
type: "string" as const,
|
|
183
|
-
description:
|
|
82
|
+
description:
|
|
83
|
+
"The owner_id from UntrustedContext (agent owner's user_id, always present)",
|
|
184
84
|
},
|
|
185
|
-
|
|
85
|
+
sender_id: {
|
|
186
86
|
type: "string" as const,
|
|
187
|
-
description: "The
|
|
87
|
+
description: "The sender_id from UntrustedContext (current message sender)",
|
|
188
88
|
},
|
|
189
|
-
|
|
89
|
+
agent_id: {
|
|
190
90
|
type: "string" as const,
|
|
191
|
-
description: "The
|
|
91
|
+
description: "The agent_id from UntrustedContext",
|
|
192
92
|
},
|
|
193
93
|
conversation_type: {
|
|
194
94
|
type: "string" as const,
|
|
195
|
-
description:
|
|
95
|
+
description:
|
|
96
|
+
"The conversation_type from UntrustedContext: 'direct' or 'group'",
|
|
196
97
|
},
|
|
197
98
|
group_id: {
|
|
198
99
|
type: "string" as const,
|
|
199
|
-
description: "The group_id from UntrustedContext (only
|
|
100
|
+
description: "The group_id from UntrustedContext (only in group chats)",
|
|
200
101
|
},
|
|
201
102
|
maxResults: {
|
|
202
103
|
type: "number" as const,
|
|
203
|
-
description: "
|
|
104
|
+
description: "Max results (default 20)",
|
|
204
105
|
},
|
|
205
106
|
minScore: {
|
|
206
107
|
type: "number" as const,
|
|
207
|
-
description: "
|
|
108
|
+
description: "Min similarity score 0-1 (default 0.3)",
|
|
208
109
|
},
|
|
209
110
|
},
|
|
210
111
|
required: ["query"],
|
|
@@ -214,8 +115,9 @@ export default definePluginEntry({
|
|
|
214
115
|
toolCallId: string,
|
|
215
116
|
params: {
|
|
216
117
|
query?: string;
|
|
217
|
-
|
|
118
|
+
owner_id?: string;
|
|
218
119
|
sender_id?: string;
|
|
120
|
+
agent_id?: string;
|
|
219
121
|
conversation_type?: string;
|
|
220
122
|
group_id?: string;
|
|
221
123
|
maxResults?: number;
|
|
@@ -229,65 +131,49 @@ export default definePluginEntry({
|
|
|
229
131
|
};
|
|
230
132
|
}
|
|
231
133
|
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
})
|
|
240
|
-
: (fallbackIdentity || resolveIdentityFromParams({}));
|
|
134
|
+
const identity = resolveIdentity({
|
|
135
|
+
sender_id: params.sender_id,
|
|
136
|
+
owner_id: params.owner_id,
|
|
137
|
+
conversation_type: params.conversation_type,
|
|
138
|
+
group_id: params.group_id,
|
|
139
|
+
});
|
|
140
|
+
const agentId = params.agent_id?.trim() || "main";
|
|
241
141
|
|
|
242
142
|
console.log(
|
|
243
|
-
`[memory-search
|
|
244
|
-
|
|
245
|
-
`release_name=${releaseName} ` +
|
|
246
|
-
`(source=${params.sender_id ? "llm_params" : "fallback"})`
|
|
143
|
+
`[memory-search] search: agent=${agentId} scene=${identity.scene} ` +
|
|
144
|
+
`sender=${identity.sender_id} owner=${identity.owner_id} group=${identity.group_id}`
|
|
247
145
|
);
|
|
248
146
|
|
|
249
147
|
try {
|
|
250
148
|
const data = await gateway.callGatewaySearch({
|
|
149
|
+
owner_id: identity.owner_id,
|
|
150
|
+
sender_id: identity.sender_id,
|
|
251
151
|
agent_id: agentId,
|
|
252
|
-
user_id: identity.user_id,
|
|
253
152
|
query,
|
|
254
153
|
scene: identity.scene,
|
|
255
154
|
group_id: identity.group_id,
|
|
256
|
-
release_name: releaseName || undefined,
|
|
257
155
|
limit: params.maxResults || 20,
|
|
258
156
|
threshold: params.minScore || 0.3,
|
|
259
157
|
});
|
|
260
158
|
|
|
261
|
-
const text =
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
159
|
+
const text =
|
|
160
|
+
data.results.length === 0
|
|
161
|
+
? "No relevant memories found."
|
|
162
|
+
: data.results
|
|
163
|
+
.map((r, i) => {
|
|
164
|
+
const tag =
|
|
165
|
+
r.memory_type === "group" && r.group_id
|
|
166
|
+
? `group/${r.group_id}`
|
|
167
|
+
: r.memory_type === "knowledge" && r.kb_id
|
|
168
|
+
? `knowledge/${r.kb_id}`
|
|
169
|
+
: r.memory_type;
|
|
170
|
+
return `[${i + 1}] (${tag}, score: ${r.score.toFixed(2)})\n${r.content}`;
|
|
171
|
+
})
|
|
172
|
+
.join("\n\n");
|
|
173
|
+
|
|
174
|
+
return { content: [{ type: "text" as const, text }] };
|
|
272
175
|
} catch (err: any) {
|
|
273
|
-
console.error("[memory-search
|
|
274
|
-
|
|
275
|
-
try {
|
|
276
|
-
const fallbackTool =
|
|
277
|
-
api.runtime?.tools?.createMemorySearchTool({
|
|
278
|
-
config: ctx.config,
|
|
279
|
-
agentSessionKey: ctx.sessionKey,
|
|
280
|
-
});
|
|
281
|
-
if (fallbackTool) {
|
|
282
|
-
console.warn(
|
|
283
|
-
"[memory-search-plugin] falling back to local memory_search"
|
|
284
|
-
);
|
|
285
|
-
return await fallbackTool.execute(toolCallId, { query, maxResults: params.maxResults, minScore: params.minScore });
|
|
286
|
-
}
|
|
287
|
-
} catch {
|
|
288
|
-
// 降级也失败
|
|
289
|
-
}
|
|
290
|
-
|
|
176
|
+
console.error("[memory-search] search failed:", err.message);
|
|
291
177
|
return {
|
|
292
178
|
content: [
|
|
293
179
|
{
|
|
@@ -303,162 +189,120 @@ export default definePluginEntry({
|
|
|
303
189
|
{ names: ["memory_search"] }
|
|
304
190
|
);
|
|
305
191
|
|
|
306
|
-
//
|
|
307
|
-
// 3. 注册 memory_get → 路径分流
|
|
308
|
-
// MEMORY 路径 → Gateway
|
|
309
|
-
// 其他路径 → 原始实现(passthrough)
|
|
310
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
192
|
+
// ── memory_get (keyword search on memory_chat_logs) ──
|
|
311
193
|
api.registerTool(
|
|
312
194
|
(ctx: any) => {
|
|
313
|
-
const fallbackIdentity = ctx.sessionKey ? resolveIdentity(ctx.sessionKey) : null;
|
|
314
|
-
const fallbackAgentId = ctx.sessionKey ? extractAgentId(ctx.sessionKey) : null;
|
|
315
|
-
|
|
316
195
|
return {
|
|
317
196
|
name: "memory_get",
|
|
318
197
|
description:
|
|
319
|
-
"
|
|
320
|
-
"
|
|
321
|
-
"
|
|
198
|
+
"Search raw chat messages by keyword. " +
|
|
199
|
+
"Queries the original message log, not the extracted facts. " +
|
|
200
|
+
"Extract owner_id, sender_id, agent_id, conversation_type, group_id from UntrustedContext.",
|
|
322
201
|
parameters: {
|
|
323
202
|
type: "object" as const,
|
|
324
203
|
properties: {
|
|
325
|
-
|
|
204
|
+
keyword: {
|
|
326
205
|
type: "string" as const,
|
|
327
|
-
description:
|
|
328
|
-
"Relative path to the file (e.g. MEMORY.md, memory/notes.md, SOUL.md)",
|
|
206
|
+
description: "Keyword to search in chat messages",
|
|
329
207
|
},
|
|
330
|
-
|
|
208
|
+
owner_id: {
|
|
331
209
|
type: "string" as const,
|
|
332
|
-
description: "The
|
|
210
|
+
description: "The owner_id from UntrustedContext",
|
|
333
211
|
},
|
|
334
212
|
sender_id: {
|
|
335
213
|
type: "string" as const,
|
|
336
214
|
description: "The sender_id from UntrustedContext",
|
|
337
215
|
},
|
|
216
|
+
agent_id: {
|
|
217
|
+
type: "string" as const,
|
|
218
|
+
description: "The agent_id from UntrustedContext",
|
|
219
|
+
},
|
|
338
220
|
conversation_type: {
|
|
339
221
|
type: "string" as const,
|
|
340
|
-
description:
|
|
222
|
+
description:
|
|
223
|
+
"The conversation_type from UntrustedContext: 'direct' or 'group'",
|
|
341
224
|
},
|
|
342
225
|
group_id: {
|
|
343
226
|
type: "string" as const,
|
|
344
|
-
description: "The group_id from UntrustedContext (only
|
|
345
|
-
},
|
|
346
|
-
from: {
|
|
347
|
-
type: "number" as const,
|
|
348
|
-
description: "Starting line number (1-indexed)",
|
|
227
|
+
description: "The group_id from UntrustedContext (only in group chats)",
|
|
349
228
|
},
|
|
350
|
-
|
|
229
|
+
limit: {
|
|
351
230
|
type: "number" as const,
|
|
352
|
-
description: "
|
|
231
|
+
description: "Max messages to return (default 20)",
|
|
353
232
|
},
|
|
354
233
|
},
|
|
355
|
-
required: ["
|
|
234
|
+
required: ["keyword"],
|
|
356
235
|
},
|
|
357
236
|
|
|
358
237
|
async execute(
|
|
359
238
|
toolCallId: string,
|
|
360
239
|
params: {
|
|
361
|
-
|
|
362
|
-
|
|
240
|
+
keyword?: string;
|
|
241
|
+
owner_id?: string;
|
|
363
242
|
sender_id?: string;
|
|
243
|
+
agent_id?: string;
|
|
364
244
|
conversation_type?: string;
|
|
365
245
|
group_id?: string;
|
|
366
|
-
|
|
367
|
-
lines?: number;
|
|
246
|
+
limit?: number;
|
|
368
247
|
}
|
|
369
248
|
) {
|
|
370
|
-
const
|
|
371
|
-
if (!
|
|
249
|
+
const keyword = params.keyword || "";
|
|
250
|
+
if (!keyword) {
|
|
372
251
|
return {
|
|
373
|
-
content: [{ type: "text" as const, text: "No
|
|
252
|
+
content: [{ type: "text" as const, text: "No keyword provided." }],
|
|
374
253
|
};
|
|
375
254
|
}
|
|
376
255
|
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
} catch {
|
|
385
|
-
// memory-core 被替换后 api.runtime.tools 不可用,忽略
|
|
386
|
-
}
|
|
387
|
-
|
|
388
|
-
// ── 路径分流 ──
|
|
389
|
-
if (isMemoryPath(path)) {
|
|
390
|
-
const agentId = params.agent_id?.trim() || fallbackAgentId || "main";
|
|
391
|
-
const identity = (params.sender_id || params.conversation_type || params.group_id)
|
|
392
|
-
? resolveIdentityFromParams({
|
|
393
|
-
sender_id: params.sender_id,
|
|
394
|
-
conversation_type: params.conversation_type,
|
|
395
|
-
group_id: params.group_id,
|
|
396
|
-
})
|
|
397
|
-
: (fallbackIdentity || resolveIdentityFromParams({}));
|
|
398
|
-
|
|
399
|
-
console.log(
|
|
400
|
-
`[memory-search-plugin] get: path=${path} agent_id=${agentId} scene=${identity.scene} ` +
|
|
401
|
-
`user_id=${identity.user_id} release_name=${releaseName} (source=${params.sender_id ? "llm_params" : "fallback"})`
|
|
402
|
-
);
|
|
403
|
-
|
|
404
|
-
try {
|
|
405
|
-
const data = await gateway.callGatewayGet({
|
|
406
|
-
agent_id: agentId,
|
|
407
|
-
user_id: identity.user_id,
|
|
408
|
-
path,
|
|
409
|
-
scene: identity.scene,
|
|
410
|
-
group_id: identity.group_id,
|
|
411
|
-
release_name: releaseName || undefined,
|
|
412
|
-
from: params.from,
|
|
413
|
-
lines: params.lines,
|
|
414
|
-
});
|
|
415
|
-
|
|
416
|
-
return {
|
|
417
|
-
content: [{ type: "text" as const, text: data.text }],
|
|
418
|
-
details: { path: data.path, text: data.text },
|
|
419
|
-
};
|
|
420
|
-
} catch (err: any) {
|
|
421
|
-
console.error("[memory-search-plugin] get failed:", err.message);
|
|
256
|
+
const identity = resolveIdentity({
|
|
257
|
+
sender_id: params.sender_id,
|
|
258
|
+
owner_id: params.owner_id,
|
|
259
|
+
conversation_type: params.conversation_type,
|
|
260
|
+
group_id: params.group_id,
|
|
261
|
+
});
|
|
262
|
+
const agentId = params.agent_id?.trim() || "main";
|
|
422
263
|
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
type: "text" as const,
|
|
428
|
-
text: `Access denied: ${path}`,
|
|
429
|
-
},
|
|
430
|
-
],
|
|
431
|
-
};
|
|
432
|
-
}
|
|
264
|
+
console.log(
|
|
265
|
+
`[memory-search] get: keyword="${keyword}" agent=${agentId} scene=${identity.scene} ` +
|
|
266
|
+
`owner=${identity.owner_id} group=${identity.group_id}`
|
|
267
|
+
);
|
|
433
268
|
|
|
434
|
-
|
|
435
|
-
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
269
|
+
try {
|
|
270
|
+
const data = await gateway.callGatewayGet({
|
|
271
|
+
owner_id: identity.owner_id,
|
|
272
|
+
sender_id: identity.sender_id,
|
|
273
|
+
agent_id: agentId,
|
|
274
|
+
keyword,
|
|
275
|
+
scene: identity.scene,
|
|
276
|
+
group_id: identity.group_id,
|
|
277
|
+
limit: params.limit || 20,
|
|
278
|
+
});
|
|
441
279
|
|
|
280
|
+
if (data.messages.length === 0) {
|
|
442
281
|
return {
|
|
443
282
|
content: [
|
|
444
|
-
{
|
|
445
|
-
type: "text" as const,
|
|
446
|
-
text: `Failed to read ${path}: Gateway unavailable.`,
|
|
447
|
-
},
|
|
283
|
+
{ type: "text" as const, text: "No matching messages found." },
|
|
448
284
|
],
|
|
449
|
-
details: { path, text: "", error: err.message },
|
|
450
285
|
};
|
|
451
286
|
}
|
|
452
|
-
} else {
|
|
453
|
-
if (originalGetTool) {
|
|
454
|
-
return await originalGetTool.execute(toolCallId, { path, from: params.from, lines: params.lines });
|
|
455
|
-
}
|
|
456
287
|
|
|
288
|
+
const text = data.messages
|
|
289
|
+
.map((m) => {
|
|
290
|
+
const tag =
|
|
291
|
+
m.message_type === "group" && m.group_id
|
|
292
|
+
? `group/${m.group_id}`
|
|
293
|
+
: m.message_type;
|
|
294
|
+
return `[${tag}] ${m.formatted_text}`;
|
|
295
|
+
})
|
|
296
|
+
.join("\n");
|
|
297
|
+
|
|
298
|
+
return { content: [{ type: "text" as const, text }] };
|
|
299
|
+
} catch (err: any) {
|
|
300
|
+
console.error("[memory-search] get failed:", err.message);
|
|
457
301
|
return {
|
|
458
302
|
content: [
|
|
459
303
|
{
|
|
460
304
|
type: "text" as const,
|
|
461
|
-
text:
|
|
305
|
+
text: "Message search temporarily unavailable.",
|
|
462
306
|
},
|
|
463
307
|
],
|
|
464
308
|
};
|
|
@@ -468,19 +312,5 @@ export default definePluginEntry({
|
|
|
468
312
|
},
|
|
469
313
|
{ names: ["memory_get"] }
|
|
470
314
|
);
|
|
471
|
-
|
|
472
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
473
|
-
// 4. 保留 CLI 命令(和 memory-core 一致)
|
|
474
|
-
// ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
|
|
475
|
-
api.registerCli(
|
|
476
|
-
({ program }: any) => {
|
|
477
|
-
try {
|
|
478
|
-
api.runtime?.tools?.registerMemoryCli(program);
|
|
479
|
-
} catch {
|
|
480
|
-
// memory-core 被替换后 api.runtime.tools 不可用,忽略
|
|
481
|
-
}
|
|
482
|
-
},
|
|
483
|
-
{ commands: ["memory"] }
|
|
484
|
-
);
|
|
485
315
|
},
|
|
486
|
-
}
|
|
316
|
+
};
|
package/openclaw.plugin.json
CHANGED
|
@@ -1,22 +1,19 @@
|
|
|
1
1
|
{
|
|
2
2
|
"id": "memory-search-plugin",
|
|
3
3
|
"name": "Memory Search Plugin",
|
|
4
|
-
"
|
|
5
|
-
"
|
|
6
|
-
"
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
"
|
|
12
|
-
"type": "string",
|
|
13
|
-
"description": "Memory Search Gateway API 地址,如 http://your-host:8888"
|
|
14
|
-
},
|
|
15
|
-
"gatewayToken": {
|
|
16
|
-
"type": "string",
|
|
17
|
-
"description": "Bearer Token,留空则不认证"
|
|
18
|
-
}
|
|
4
|
+
"version": "1.1.0",
|
|
5
|
+
"description": "Memory search and retrieval with owner_id-based isolation (no release_name)",
|
|
6
|
+
"main": "index.ts",
|
|
7
|
+
"config": {
|
|
8
|
+
"MEMORY_GATEWAY_URL": {
|
|
9
|
+
"type": "string",
|
|
10
|
+
"description": "URL of the claw-memory gateway service",
|
|
11
|
+
"required": true
|
|
19
12
|
},
|
|
20
|
-
"
|
|
13
|
+
"MEMORY_GATEWAY_TOKEN": {
|
|
14
|
+
"type": "string",
|
|
15
|
+
"description": "Auth token for gateway",
|
|
16
|
+
"required": false
|
|
17
|
+
}
|
|
21
18
|
}
|
|
22
19
|
}
|