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/index.ts CHANGED
@@ -1,210 +1,111 @@
1
- /**
2
- * memory-gateway 插件
3
- *
4
- * 替换 OpenClaw 默认的 memory-core 插件(通过 memory slot)
5
- * - memory_search → 路由到外部 Memory Gateway Search API
6
- * - memory_get → 路径分流:MEMORY 路径走 Gateway,其他路径走原始实现
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
- import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
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 hasMemorySearch = availableTools.has("memory_search");
44
- const hasMemoryGet = availableTools.has("memory_get");
45
- if (!hasMemorySearch && !hasMemoryGet) return [];
46
-
47
- let toolGuidance: string;
48
- if (hasMemorySearch && hasMemoryGet) {
49
- toolGuidance =
50
- "Before answering anything about prior work, decisions, dates, people, preferences, or todos: " +
51
- "run memory_search for semantic search on MEMORY.md + memory/*.md; then use memory_get for keyword retrieval on only the needed content. " +
52
- "If low confidence after search, say you checked.";
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
- "you MUST fill in the agent_id, sender_id, conversation_type, and group_id parameters " +
71
- "by extracting them from the UntrustedContext section in this conversation. " +
72
- "Look for lines like `agent_id: xxx`, `sender_id: xxx`, `conversation_type: xxx`, `group_id: xxx`. " +
73
- "If a field is not present, omit it."
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
- // 0. 从 openclaw.json pluginConfig 读取 Gateway 配置
150
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
151
- const gatewayUrl = (api.pluginConfig?.gatewayUrl as string | undefined) || "http://localhost:18790";
152
- const gatewayToken = (api.pluginConfig?.gatewayToken as string | undefined) || "";
153
- const ocToken = process.env.OPENCLAW_GATEWAY_TOKEN || "";
154
- const releaseName = ocToken.startsWith("oc-") ? ocToken.slice(3) : "";
155
- console.log("[memory-search-plugin] gatewayUrl:", gatewayUrl, "releaseName:", releaseName);
156
-
157
- const gateway = createGatewayClient({ gatewayUrl, gatewayToken });
158
-
159
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
160
- // 1. 注册 prompt section(和 memory-core 一致)
161
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
162
- api.registerMemoryPromptSection(buildPromptSection);
163
-
164
- // ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━
165
- // 2. 注册 memory_search → 调 Gateway Search API
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 MEMORY.md and memory/**/*.md for relevant memories. " +
177
- "You MUST extract agent_id, sender_id, conversation_type, group_id from the UntrustedContext in this conversation and pass them as parameters.",
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: "The search query",
82
+ description:
83
+ "The owner_id from UntrustedContext (agent owner's user_id, always present)",
184
84
  },
185
- agent_id: {
85
+ sender_id: {
186
86
  type: "string" as const,
187
- description: "The agent_id from UntrustedContext (e.g. 'main' or 'lobster_xxx')",
87
+ description: "The sender_id from UntrustedContext (current message sender)",
188
88
  },
189
- sender_id: {
89
+ agent_id: {
190
90
  type: "string" as const,
191
- description: "The sender_id from UntrustedContext",
91
+ description: "The agent_id from UntrustedContext",
192
92
  },
193
93
  conversation_type: {
194
94
  type: "string" as const,
195
- description: "The conversation_type from UntrustedContext: 'direct' or 'group'",
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 present in group chats)",
100
+ description: "The group_id from UntrustedContext (only in group chats)",
200
101
  },
201
102
  maxResults: {
202
103
  type: "number" as const,
203
- description: "Maximum number of results to return",
104
+ description: "Max results (default 20)",
204
105
  },
205
106
  minScore: {
206
107
  type: "number" as const,
207
- description: "Minimum similarity score threshold",
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
- agent_id?: string;
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
- // 优先用 LLM 传参,fallback 到 sessionKey 解析
233
- const agentId = params.agent_id?.trim() || fallbackAgentId || "main";
234
- const identity = (params.sender_id || params.conversation_type || params.group_id)
235
- ? resolveIdentityFromParams({
236
- sender_id: params.sender_id,
237
- conversation_type: params.conversation_type,
238
- group_id: params.group_id,
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-plugin] search: agent_id=${agentId} scene=${identity.scene} ` +
244
- `user_id=${identity.user_id} group_id=${identity.group_id} ` +
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 = formatSearchResults(data.results);
262
-
263
- return {
264
- content: [{ type: "text" as const, text }],
265
- details: {
266
- results: data.results,
267
- total: data.total,
268
- scene: data.scene,
269
- steps: data.steps,
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-plugin] search failed:", err.message);
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
- "Read a memory file or workspace file. " +
320
- "Supports reading MEMORY.md, memory/**/*.md, and other workspace files. " +
321
- "You MUST extract agent_id, sender_id, conversation_type, group_id from the UntrustedContext in this conversation and pass them as parameters.",
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
- path: {
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
- agent_id: {
208
+ owner_id: {
331
209
  type: "string" as const,
332
- description: "The agent_id from UntrustedContext (e.g. 'main' or 'lobster_xxx')",
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: "The conversation_type from UntrustedContext: 'direct' or 'group'",
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 present in group chats)",
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
- lines: {
229
+ limit: {
351
230
  type: "number" as const,
352
- description: "Number of lines to read",
231
+ description: "Max messages to return (default 20)",
353
232
  },
354
233
  },
355
- required: ["path"],
234
+ required: ["keyword"],
356
235
  },
357
236
 
358
237
  async execute(
359
238
  toolCallId: string,
360
239
  params: {
361
- path?: string;
362
- agent_id?: string;
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
- from?: number;
367
- lines?: number;
246
+ limit?: number;
368
247
  }
369
248
  ) {
370
- const path = params.path || "";
371
- if (!path) {
249
+ const keyword = params.keyword || "";
250
+ if (!keyword) {
372
251
  return {
373
- content: [{ type: "text" as const, text: "No path provided." }],
252
+ content: [{ type: "text" as const, text: "No keyword provided." }],
374
253
  };
375
254
  }
376
255
 
377
- // 懒加载原始 memory_get 实现,用于非 MEMORY 路径的 passthrough
378
- let originalGetTool: any = null;
379
- try {
380
- originalGetTool = api.runtime?.tools?.createMemoryGetTool({
381
- config: ctx.config,
382
- agentSessionKey: ctx.sessionKey,
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
- if (err.message?.includes('403')) {
424
- return {
425
- content: [
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
- if (originalGetTool) {
435
- console.warn(
436
- "[memory-search-plugin] falling back to local memory_get for:",
437
- path
438
- );
439
- return await originalGetTool.execute(toolCallId, { path, from: params.from, lines: params.lines });
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: `Cannot read ${path}: memory_get backend not available.`,
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
+ };
@@ -1,22 +1,19 @@
1
1
  {
2
2
  "id": "memory-search-plugin",
3
3
  "name": "Memory Search Plugin",
4
- "description": "Routes memory_search to external Memory Search Gateway with ACL, memory_get with path routing",
5
- "kind": "memory",
6
- "version": "0.8.0",
7
- "configSchema": {
8
- "type": "object",
9
- "additionalProperties": false,
10
- "properties": {
11
- "gatewayUrl": {
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
- "required": ["gatewayUrl"]
13
+ "MEMORY_GATEWAY_TOKEN": {
14
+ "type": "string",
15
+ "description": "Auth token for gateway",
16
+ "required": false
17
+ }
21
18
  }
22
19
  }