memory-search-plugin 0.10.0 → 1.2.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 CHANGED
@@ -1,39 +1,26 @@
1
- /**
2
- * Memory Search Gateway HTTP 调用封装
3
- *
4
- * 封装与外部 Memory Search Gateway 服务的通信。
5
- * 请求/响应格式与后端 app/models.py 完全对齐。
6
- * Gateway 地址从 openclaw.json plugins.entries.memory-search-plugin.config.gatewayUrl 获取。
7
- */
8
-
9
1
  import type { Scene } from "./identity";
10
2
 
11
- // ── 类型定义(与后端 app/models.py 对齐)──────────────
12
-
13
- export type MemoryType = "owner" | "peer" | "group" | "knowledge";
3
+ // ── Search ──
14
4
 
15
5
  export interface MemorySearchRequest {
6
+ owner_id: string;
7
+ sender_id: string;
16
8
  agent_id: string;
17
- user_id: string;
18
9
  query: string;
19
10
  scene: Scene;
20
11
  group_id?: string | null;
21
- release_name?: string; // 用户隔离标识(如 user-xxx),owner/peer 搜索必须
22
- limit?: number; // 1-100, default 20
23
- threshold?: number; // 0.0-1.0, default 0.3
12
+ limit?: number;
13
+ threshold?: number;
24
14
  }
25
15
 
26
16
  export interface MemorySearchResult {
27
17
  id: string;
28
18
  content: string;
29
19
  score: number;
30
- memory_type: MemoryType;
31
- source_file?: string;
32
- created_at?: string;
33
- agent_id?: string;
34
- peer_id?: string;
20
+ memory_type: "owner" | "group" | "knowledge";
35
21
  group_id?: string;
36
22
  kb_id?: string;
23
+ created_at?: string;
37
24
  }
38
25
 
39
26
  export interface MemorySearchResponse {
@@ -43,23 +30,34 @@ export interface MemorySearchResponse {
43
30
  steps: string[];
44
31
  }
45
32
 
33
+ // ── Get (keyword search on memory_chat_logs) ──
34
+
46
35
  export interface MemoryGetRequest {
36
+ owner_id: string;
37
+ sender_id: string;
47
38
  agent_id: string;
48
- user_id: string;
49
- path: string;
50
- scene?: Scene;
39
+ keyword: string;
40
+ scene: Scene;
51
41
  group_id?: string | null;
52
- release_name?: string; // 用户隔离标识(如 user-xxx),owner/peer 搜索必须
53
- from?: number;
54
- lines?: number;
42
+ message_type?: "owner" | "group";
43
+ limit?: number;
44
+ offset?: number;
45
+ }
46
+
47
+ export interface MemoryGetMessage {
48
+ id: string;
49
+ formatted_text: string;
50
+ message_type: "owner" | "group";
51
+ group_id?: string | null;
52
+ message_ts: string;
55
53
  }
56
54
 
57
55
  export interface MemoryGetResponse {
58
- text: string;
59
- path: string;
56
+ messages: MemoryGetMessage[];
57
+ total: number;
60
58
  }
61
59
 
62
- // ── Gateway Client ─────────────────────────────────────
60
+ // ── Client ──
63
61
 
64
62
  export interface GatewayClientConfig {
65
63
  gatewayUrl: string;
@@ -73,43 +71,26 @@ export interface GatewayClient {
73
71
 
74
72
  export function createGatewayClient(config: GatewayClientConfig): GatewayClient {
75
73
  const baseUrl = config.gatewayUrl.replace(/\/+$/, "");
76
- const token = config.gatewayToken || "";
74
+ const headers: Record<string, string> = {
75
+ "Content-Type": "application/json",
76
+ ...(config.gatewayToken ? { Authorization: `Bearer ${config.gatewayToken}` } : {}),
77
+ };
77
78
 
78
- const authHeaders = token
79
- ? { Authorization: `Bearer ${token}` }
80
- : {};
79
+ async function post<T>(path: string, body: unknown): Promise<T> {
80
+ const resp = await fetch(`${baseUrl}${path}`, {
81
+ method: "POST",
82
+ headers,
83
+ body: JSON.stringify(body),
84
+ });
85
+ if (!resp.ok) {
86
+ const text = await resp.text().catch(() => "");
87
+ throw new Error(`Gateway ${path} failed: ${resp.status} ${text}`);
88
+ }
89
+ return resp.json() as Promise<T>;
90
+ }
81
91
 
82
92
  return {
83
- async callGatewaySearch(options) {
84
- const url = `${baseUrl}/api/memory/search`;
85
- const response = await fetch(url, {
86
- method: "POST",
87
- headers: { "Content-Type": "application/json", ...authHeaders },
88
- body: JSON.stringify(options),
89
- });
90
- if (!response.ok) {
91
- const errorBody = await response.text().catch(() => "");
92
- throw new Error(
93
- `Gateway search failed: ${response.status} ${response.statusText} ${errorBody}`
94
- );
95
- }
96
- return response.json() as Promise<MemorySearchResponse>;
97
- },
98
-
99
- async callGatewayGet(options) {
100
- const url = `${baseUrl}/api/memory/get`;
101
- const response = await fetch(url, {
102
- method: "POST",
103
- headers: { "Content-Type": "application/json", ...authHeaders },
104
- body: JSON.stringify(options),
105
- });
106
- if (!response.ok) {
107
- const errorBody = await response.text().catch(() => "");
108
- throw new Error(
109
- `Gateway get failed: ${response.status} ${response.statusText} ${errorBody}`
110
- );
111
- }
112
- return response.json() as Promise<MemoryGetResponse>;
113
- },
93
+ callGatewaySearch: (opts) => post("/api/memory/search", opts),
94
+ callGatewayGet: (opts) => post("/api/memory/get", opts),
114
95
  };
115
96
  }
package/identity.ts CHANGED
@@ -1,119 +1,59 @@
1
1
  /**
2
- * 身份解析模块
2
+ * 身份解析模块 — 仅 owner/group/knowledge 三种场景
3
3
  *
4
- * 方案 4:从 LLM 传入的工具参数解析身份信息。
5
- * LLM system prompt 中的 UntrustedContext 提取字段并填入工具参数。
4
+ * conversation_type = "group" → scene = "group"
5
+ * conversation_type = "direct" scene = "owner"
6
+ * knowledge 场景由特定工具调用或 LLM 判断触发
6
7
  *
7
- * owner 判断:通过环境变量 OPENCLAW_GATEWAY_TOKEN 获取 owner_id,
8
- * 与 sender_id 比对(与旧版逻辑一致)。
9
- *
10
- * 输出:
11
- * - scene: 对应后端 SearchRequest.scene(private_own / private_other / group)
12
- * - user_id: 当前对话用户的 ID
13
- * - group_id: 群聊时的群 ID(仅 group 场景)
8
+ * owner_id 用于 SQL 数据隔离,替代 release_name。
9
+ * 不依赖任何环境变量兜底。
14
10
  */
15
11
 
16
- /** 后端 models.py 定义的五种场景 */
17
- export type Scene =
18
- | "private_own"
19
- | "private_other"
20
- | "group"
21
- | "knowledge"
22
- | "admin";
12
+ export type Scene = "owner" | "group" | "knowledge";
23
13
 
24
- /** 解析后的身份信息,与后端 SearchRequest 的 user_id / scene / group_id 对齐 */
25
14
  export interface ResolvedIdentity {
26
15
  scene: Scene;
27
- user_id: string;
16
+ sender_id: string;
17
+ owner_id: string;
28
18
  group_id: string | null;
29
19
  }
30
20
 
31
- /** LLM 传入的身份相关参数(来自 UntrustedContext) */
32
21
  export interface IdentityParams {
33
22
  sender_id?: string;
34
- conversation_type?: string; // "direct" | "group"
23
+ owner_id?: string;
24
+ conversation_type?: string;
35
25
  group_id?: string;
36
26
  }
37
27
 
38
- /**
39
- * 从 LLM 工具参数解析身份 + 场景
40
- *
41
- * owner 判断逻辑与旧版一致:通过环境变量 OPENCLAW_GATEWAY_TOKEN 获取 owner_id,
42
- * 再与 sender_id 比对。
43
- *
44
- * 场景映射:
45
- * conversation_type="group" → scene = "group"
46
- * conversation_type="direct" + isOwner(sender_id) → scene = "private_own"
47
- * conversation_type="direct" + !isOwner → scene = "private_other"
48
- */
49
- export function resolveIdentityFromParams(params: IdentityParams): ResolvedIdentity {
50
- // ── 测试模式:环境变量覆盖 ──────────────────────────
28
+ export function resolveIdentity(params: IdentityParams): ResolvedIdentity {
51
29
  const testScene = process.env.MEMORY_GATEWAY_TEST_SCENE as Scene | undefined;
52
30
  if (testScene) {
53
- const result: ResolvedIdentity = {
31
+ return {
54
32
  scene: testScene,
55
- user_id: process.env.MEMORY_GATEWAY_TEST_USER_ID || "owner_A",
33
+ sender_id: process.env.MEMORY_GATEWAY_TEST_USER_ID || "test_user",
34
+ owner_id: process.env.MEMORY_GATEWAY_TEST_OWNER_ID || "test_owner",
56
35
  group_id: process.env.MEMORY_GATEWAY_TEST_GROUP_ID || null,
57
36
  };
58
- console.log("[memory-search-plugin] TEST MODE identity:", JSON.stringify(result));
59
- return result;
60
37
  }
61
38
 
62
- const userId = params.sender_id?.trim() || "unknown";
39
+ const senderId = params.sender_id?.trim() || "unknown";
40
+ const ownerId = params.owner_id?.trim() || "";
63
41
  const convType = (params.conversation_type || "direct").trim().toLowerCase();
42
+ const groupId = params.group_id?.trim() || null;
64
43
 
65
- // 1. 群聊
66
- if (convType === "group") {
67
- return {
68
- scene: "group",
69
- user_id: userId,
70
- group_id: params.group_id?.trim() || null,
71
- };
72
- }
73
-
74
- // 2. 私聊:通过环境变量比对 owner_id 判断 owner/peer
75
- if (isOwner(userId)) {
76
- return { scene: "private_own", user_id: userId, group_id: null };
44
+ if (!ownerId) {
45
+ console.warn(
46
+ "[identity] owner_id is empty — LLM failed to extract from UntrustedContext. " +
47
+ "owner queries will be skipped to prevent cross-user leak."
48
+ );
77
49
  }
78
50
 
79
- return { scene: "private_other", user_id: userId, group_id: null };
80
- }
81
-
82
- // ── 向后兼容:保留旧函数签名,sessionKey 仍可用时作为 fallback ──
83
-
84
- export function resolveIdentity(sessionKey: string | undefined): ResolvedIdentity {
85
- const sk = sessionKey ?? "";
86
- const sessionPart = sk.split(":").pop() || "";
87
-
88
- const groupMatch = sessionPart.match(
89
- /user_(.+?)_lobster_(.+?)_group_(.+?)_release_(.+)$/
90
- );
91
- if (groupMatch) {
92
- return { scene: "group", user_id: groupMatch[1], group_id: groupMatch[3] };
93
- }
94
-
95
- const peerMatch = sessionPart.match(
96
- /user_(.+?)_lobster_(.+?)_release_(.+)$/
97
- );
98
- if (peerMatch) {
99
- const userId = peerMatch[1];
100
- if (isOwner(userId)) {
101
- return { scene: "private_own", user_id: userId, group_id: null };
51
+ if (convType === "group") {
52
+ if (!groupId) {
53
+ console.warn("[identity] group scene but group_id is empty");
102
54
  }
103
- return { scene: "private_other", user_id: userId, group_id: null };
55
+ return { scene: "group", sender_id: senderId, owner_id: ownerId, group_id: groupId };
104
56
  }
105
57
 
106
- return { scene: "private_other", user_id: "unknown", group_id: null };
107
- }
108
-
109
- function isOwner(userId: string): boolean {
110
- const token = process.env.OPENCLAW_GATEWAY_TOKEN || "";
111
- if (!token) return false;
112
- const ownerUserId = token.split("-").pop() || "";
113
- return ownerUserId !== "" && ownerUserId === userId;
114
- }
115
-
116
- export function extractAgentId(sessionKey: string | undefined): string {
117
- const parts = (sessionKey || "").split(":");
118
- return parts.length >= 2 ? parts[1] : "main";
58
+ return { scene: "owner", sender_id: senderId, owner_id: ownerId, group_id: null };
119
59
  }