memory-privacy 1.6.0 → 1.7.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/filters.ts CHANGED
@@ -1,4 +1,4 @@
1
- import type { SessionIdentity } from "./identity.js";
1
+ import type { SessionIdentity } from "./identity";
2
2
  import {
3
3
  extractGroupIdFromPath,
4
4
  extractDateFromPath,
@@ -6,7 +6,7 @@ import {
6
6
  isWithinMembershipPeriod,
7
7
  loadKnowledgeACL,
8
8
  toRelativeMemoryPath,
9
- } from "./utils.js";
9
+ } from "./utils";
10
10
 
11
11
  /**
12
12
  * 过滤 memory_search 结果,按身份隔离
package/index.ts CHANGED
@@ -1,8 +1,8 @@
1
1
  import path from "path";
2
2
  import fs from "fs";
3
- import { resolveSessionIdentity } from "./identity.js";
4
- import { filterResultsByIdentity, isPathAllowed, getTargetDir, isWritePathCorrect } from "./filters.js";
5
- import { getAllowedDirs, loadAllowedMemoryContent } from "./utils.js";
3
+ import { resolveSessionIdentity } from "./identity";
4
+ import { isPathAllowed, getTargetDir, isWritePathCorrect } from "./filters";
5
+ import { getAllowedDirs } from "./utils";
6
6
 
7
7
  export default {
8
8
  id: "memory-privacy",
@@ -10,153 +10,14 @@ export default {
10
10
  description: "Multi-user memory isolation for IM + OpenClaw",
11
11
 
12
12
  register(api: any) {
13
- const config = api.config;
13
+ const log = api.logger || console;
14
14
  const workspaceDir: string =
15
15
  api.runtime?.config?.agents?.defaults?.workspace ??
16
16
  api.config?.agents?.defaults?.workspace ??
17
17
  process.env.OPENCLAW_WORKSPACE ??
18
18
  process.cwd();
19
19
 
20
- api.logger.info(`[memory-privacy] workspaceDir: ${workspaceDir}`);
21
-
22
- // ====================================================================
23
- // 钩子 1: before_prompt_build — 用 memory_search 检索相关记忆注入上下文
24
- // ====================================================================
25
- api.on("before_prompt_build", async (event: any, ctx: any) => {
26
- const identity = resolveSessionIdentity(ctx.sessionKey);
27
- const wsDir = ctx.workspaceDir || workspaceDir;
28
-
29
- // 从 event.messages 提取用户最新消息作为搜索 query
30
- const messages = event.messages || [];
31
- const lastUserMsg = [...messages].reverse().find((m: any) => m.role === "user");
32
- let query = "";
33
- if (lastUserMsg) {
34
- const content = lastUserMsg.content;
35
- if (typeof content === "string") query = content;
36
- else if (Array.isArray(content)) {
37
- query = content.filter((c: any) => c.type === "text").map((c: any) => c.text || "").join(" ");
38
- }
39
- }
40
-
41
- let memoryBlock = "";
42
-
43
- if (identity.type === "owner") {
44
- // owner 使用语义搜索(需要 query)
45
- if (query) {
46
- try {
47
- const searchTool = api.runtime.tools.createMemorySearchTool({
48
- config: ctx.config || config,
49
- agentSessionKey: ctx.sessionKey,
50
- });
51
-
52
- if (searchTool) {
53
- const rawResults = await searchTool.execute("prompt-build-search", { query });
54
- const details = rawResults?.details;
55
- const results = Array.isArray(details?.results) ? details.results : [];
56
- const snippets = results
57
- .map((r: any) => r.snippet || "")
58
- .filter((s: string) => s.length > 0);
59
-
60
- if (snippets.length > 0) {
61
- memoryBlock = `\n\n${snippets.join("\n\n")}`;
62
- }
63
- }
64
- } catch {
65
- // owner 搜索失败不回退
66
- }
67
- }
68
- } else {
69
- // 非 owner(peer / group):直接全量加载已过滤的记忆,不依赖 query
70
- const fallback = loadAllowedMemoryContent(wsDir, identity);
71
- if (fallback) memoryBlock = `\n\n${fallback}`;
72
- }
73
-
74
- switch (identity.type) {
75
- case "owner":
76
- return {
77
- prependContext:
78
- `` +
79
- memoryBlock,
80
- };
81
-
82
- case "peer":
83
- return {
84
- prependContext:
85
- `` +
86
- memoryBlock,
87
- };
88
-
89
- case "group":
90
- return {
91
- prependContext:
92
- `` +
93
- memoryBlock,
94
- };
95
- }
96
- });
97
-
98
- // ====================================================================
99
- // 钩子 2: registerTool — 包装 memory_search / memory_get
100
- // ====================================================================
101
- api.registerTool(
102
- (ctx: any) => {
103
- const identity = resolveSessionIdentity(ctx.sessionKey);
104
- const wsDir = ctx.workspaceDir || workspaceDir;
105
-
106
- const originalSearchTool = api.runtime.tools.createMemorySearchTool({
107
- config: ctx.config || config,
108
- agentSessionKey: ctx.sessionKey,
109
- });
110
-
111
- const originalGetTool = api.runtime.tools.createMemoryGetTool({
112
- config: ctx.config || config,
113
- agentSessionKey: ctx.sessionKey,
114
- });
115
-
116
- const tools: any[] = [];
117
-
118
- if (originalSearchTool) {
119
- const wrappedSearchTool = {
120
- ...originalSearchTool,
121
- execute: async (
122
- toolCallId: string,
123
- params: Record<string, unknown>,
124
- signal?: AbortSignal,
125
- onUpdate?: any
126
- ) => {
127
- const results = await originalSearchTool.execute(toolCallId, params, signal, onUpdate);
128
- return filterResultsByIdentity(results, identity, wsDir);
129
- },
130
- };
131
- tools.push(wrappedSearchTool);
132
- }
133
-
134
- if (originalGetTool) {
135
- const wrappedGetTool = {
136
- ...originalGetTool,
137
- execute: async (
138
- toolCallId: string,
139
- params: Record<string, unknown>,
140
- signal?: AbortSignal,
141
- onUpdate?: any
142
- ) => {
143
- const filePath = (params.path || params.filePath || "") as string;
144
- if (filePath && !isPathAllowed(filePath, identity, wsDir)) {
145
- return {
146
- content: [{ type: "text" as const, text: "No results found." }],
147
- details: { error: "access_denied" },
148
- };
149
- }
150
- return originalGetTool.execute(toolCallId, params, signal, onUpdate);
151
- },
152
- };
153
- tools.push(wrappedGetTool);
154
- }
155
-
156
- return tools.length > 0 ? tools : null;
157
- },
158
- { names: ["memory_search", "memory_get"] }
159
- );
20
+ log.info(`[memory-privacy] workspaceDir: ${workspaceDir}`);
160
21
 
161
22
  // ====================================================================
162
23
  // 钩子 3: before_tool_call — 写入拦截 + 重定向
@@ -223,7 +84,7 @@ export default {
223
84
  const targetDir = getTargetDir(identity);
224
85
  const correctedPath = path.join(wsDir, targetDir, filename);
225
86
 
226
- api.logger.info(`[memory-privacy] Redirecting write: ${filePath} → ${correctedPath}`);
87
+ log.info(`[memory-privacy] Redirecting write: ${filePath} → ${correctedPath}`);
227
88
 
228
89
  // 同时设置所有可能的路径参数名,确保不同工具都能接收
229
90
  const correctedParams = { ...event.params };
@@ -377,57 +238,11 @@ export default {
377
238
  }
378
239
  }
379
240
 
380
- api.logger.info(`[memory-privacy] Rewriting exec: ${command} → ${rewritten}`);
241
+ log.info(`[memory-privacy] Rewriting exec: ${command} → ${rewritten}`);
381
242
 
382
243
  return { params: { ...event.params, command: rewritten, cmd: rewritten } };
383
244
  }, { priority: 10 });
384
245
 
385
- // ====================================================================
386
- // 钩子 6: before_tool_call — memory_get 读取拦截
387
- // registerTool 包装未生效(添加新工具而非替换),用 before_tool_call 可靠拦截
388
- // ====================================================================
389
- api.on("before_tool_call", (event: any, ctx: any) => {
390
- if (event.toolName !== "memory_get") return;
391
-
392
- const filePath = (event.params?.path || event.params?.filePath || event.params?.file_path || "") as string;
393
- if (!filePath) return;
394
-
395
- const identity = resolveSessionIdentity(ctx.sessionKey);
396
- if (identity.type === "owner") return;
397
-
398
- // 拦截 MEMORY.md(非 owner 不可读)
399
- const lowerFilePath = filePath.toLowerCase();
400
- if (lowerFilePath.endsWith("/memory.md") || lowerFilePath.endsWith("\\memory.md") || lowerFilePath === "memory.md") {
401
- return { block: true, blockReason: "No results found." };
402
- }
403
-
404
- // 拦截 USER.md(group session 不可读)
405
- if (lowerFilePath.endsWith("/user.md") || lowerFilePath.endsWith("\\user.md") || lowerFilePath === "user.md") {
406
- if (identity.type === "group") {
407
- return { block: true, blockReason: "No results found." };
408
- }
409
- }
410
-
411
- const wsDir = ctx.workspaceDir || workspaceDir;
412
- if (!isPathAllowed(filePath, identity, wsDir)) {
413
- return { block: true, blockReason: "No results found." };
414
- }
415
- }, { priority: 10 });
416
-
417
- // ====================================================================
418
- // 钩子 6.5: before_tool_call — 非 owner 禁用 memory_search
419
- // 框架不 await async before_tool_call,无法做结果过滤
420
- // 直接 block,依赖 hook1 (before_prompt_build) 注入已过滤的记忆
421
- // ====================================================================
422
- api.on("before_tool_call", (event: any, ctx: any) => {
423
- if (event.toolName !== "memory_search") return;
424
-
425
- const identity = resolveSessionIdentity(ctx.sessionKey);
426
- if (identity.type === "owner") return;
427
-
428
- return { block: true, blockReason: "No results found." };
429
- }, { priority: 10 });
430
-
431
246
  // ====================================================================
432
247
  // 钩子 7: agent_end — 将 agent 对话同步到 memory 文件
433
248
  // ====================================================================
@@ -474,7 +289,7 @@ export default {
474
289
  if (assistantText) fs.appendFileSync(filePath!, `[${time}] AI助理: ${assistantText}\n`);
475
290
  });
476
291
 
477
- api.logger.info("[memory-privacy] Plugin registered successfully.");
292
+ log.info("[memory-privacy] Plugin registered successfully.");
478
293
  },
479
294
  };
480
295
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-privacy",
3
- "version": "1.6.0",
3
+ "version": "1.7.0",
4
4
  "type": "module",
5
5
  "main": "index.ts",
6
6
  "files": [
package/utils.ts CHANGED
@@ -1,7 +1,7 @@
1
1
  import fs from "fs";
2
2
  import path from "path";
3
3
  import { execSync } from "child_process";
4
- import type { SessionIdentity } from "./identity.js";
4
+ import type { SessionIdentity } from "./identity";
5
5
 
6
6
  // ========== 类型定义 ==========
7
7