memory-privacy 1.1.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/identity.ts +7 -9
- package/index.ts +61 -40
- package/package.json +1 -1
package/identity.ts
CHANGED
|
@@ -10,7 +10,7 @@ export type SessionIdentity =
|
|
|
10
10
|
* 单聊: agent:{agentId}:palz:direct:{userId}:user_{userID}_lobster_{lobsterID}_release_{releaseName}
|
|
11
11
|
* 群聊: agent:{agentId}:palz:direct:{userId}:user_{userID}_lobster_{lobsterID}_group_{groupID}_release_{releaseName}
|
|
12
12
|
*/
|
|
13
|
-
export function resolveSessionIdentity(sessionKey: string | undefined
|
|
13
|
+
export function resolveSessionIdentity(sessionKey: string | undefined): SessionIdentity {
|
|
14
14
|
const sk = sessionKey ?? "";
|
|
15
15
|
// 取最后一段(会话ID)
|
|
16
16
|
const sessionPart = sk.split(":").pop() || "";
|
|
@@ -25,7 +25,7 @@ export function resolveSessionIdentity(sessionKey: string | undefined, config: a
|
|
|
25
25
|
const peerMatch = sessionPart.match(/user_(.+?)_lobster_(.+?)_release_(.+)$/);
|
|
26
26
|
if (peerMatch) {
|
|
27
27
|
const userID = peerMatch[1];
|
|
28
|
-
if (isOwner(userID
|
|
28
|
+
if (isOwner(userID)) return { type: "owner", userId: userID };
|
|
29
29
|
return { type: "peer", peerId: userID };
|
|
30
30
|
}
|
|
31
31
|
|
|
@@ -35,12 +35,10 @@ export function resolveSessionIdentity(sessionKey: string | undefined, config: a
|
|
|
35
35
|
|
|
36
36
|
/**
|
|
37
37
|
* 判断 userId 是否是主人
|
|
38
|
-
*
|
|
38
|
+
* 从环境变量 owner_claw_user_id 获取主人 ID
|
|
39
39
|
*/
|
|
40
|
-
export function isOwner(userId: string
|
|
41
|
-
const
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
return entryStr === userId || entryStr === "*";
|
|
45
|
-
});
|
|
40
|
+
export function isOwner(userId: string): boolean {
|
|
41
|
+
const ownerUserId = process.env.owner_claw_user_id || "";
|
|
42
|
+
if (!ownerUserId) return false;
|
|
43
|
+
return ownerUserId === userId;
|
|
46
44
|
}
|
package/index.ts
CHANGED
|
@@ -23,7 +23,7 @@ export default {
|
|
|
23
23
|
// 钩子 1: before_prompt_build — 用 memory_search 检索相关记忆注入上下文
|
|
24
24
|
// ====================================================================
|
|
25
25
|
api.on("before_prompt_build", async (event: any, ctx: any) => {
|
|
26
|
-
const identity = resolveSessionIdentity(ctx.sessionKey
|
|
26
|
+
const identity = resolveSessionIdentity(ctx.sessionKey);
|
|
27
27
|
const wsDir = ctx.workspaceDir || workspaceDir;
|
|
28
28
|
|
|
29
29
|
// 从 event.messages 提取用户最新消息作为搜索 query
|
|
@@ -40,37 +40,35 @@ export default {
|
|
|
40
40
|
|
|
41
41
|
let memoryBlock = "";
|
|
42
42
|
|
|
43
|
-
if (
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
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
63
|
}
|
|
64
|
-
}
|
|
65
|
-
//
|
|
66
|
-
const fallback = loadAllowedMemoryContent(wsDir, identity);
|
|
67
|
-
if (fallback) memoryBlock = `\n\n${fallback}`;
|
|
64
|
+
} catch {
|
|
65
|
+
// owner 搜索失败不回退
|
|
68
66
|
}
|
|
69
|
-
} catch {
|
|
70
|
-
// 搜索失败,回退到全量加载
|
|
71
|
-
const fallback = loadAllowedMemoryContent(wsDir, identity);
|
|
72
|
-
if (fallback) memoryBlock = `\n\n${fallback}`;
|
|
73
67
|
}
|
|
68
|
+
} else {
|
|
69
|
+
// 非 owner(peer / group):直接全量加载已过滤的记忆,不依赖 query
|
|
70
|
+
const fallback = loadAllowedMemoryContent(wsDir, identity);
|
|
71
|
+
if (fallback) memoryBlock = `\n\n${fallback}`;
|
|
74
72
|
}
|
|
75
73
|
|
|
76
74
|
switch (identity.type) {
|
|
@@ -102,7 +100,7 @@ export default {
|
|
|
102
100
|
// ====================================================================
|
|
103
101
|
api.registerTool(
|
|
104
102
|
(ctx: any) => {
|
|
105
|
-
const identity = resolveSessionIdentity(ctx.sessionKey
|
|
103
|
+
const identity = resolveSessionIdentity(ctx.sessionKey);
|
|
106
104
|
const wsDir = ctx.workspaceDir || workspaceDir;
|
|
107
105
|
|
|
108
106
|
const originalSearchTool = api.runtime.tools.createMemorySearchTool({
|
|
@@ -176,7 +174,7 @@ export default {
|
|
|
176
174
|
const filePath = (event.params?.path || event.params?.filePath || event.params?.file_path || "") as string;
|
|
177
175
|
if (!filePath) return;
|
|
178
176
|
|
|
179
|
-
const identity = resolveSessionIdentity(ctx.sessionKey
|
|
177
|
+
const identity = resolveSessionIdentity(ctx.sessionKey);
|
|
180
178
|
const lowerFilePath = filePath.toLowerCase();
|
|
181
179
|
|
|
182
180
|
// --- MEMORY.md 写入保护(非 owner 阻止)---
|
|
@@ -187,12 +185,15 @@ export default {
|
|
|
187
185
|
return; // owner 可以正常写 MEMORY.md
|
|
188
186
|
}
|
|
189
187
|
|
|
190
|
-
// ---
|
|
191
|
-
|
|
192
|
-
|
|
188
|
+
// --- 系统配置文件写入保护(非 owner 一律阻止)---
|
|
189
|
+
// USER.md / SOUL.md / TOOL.md 等只有 owner 能写,peer/group 只能读
|
|
190
|
+
const systemFiles = ["/user.md", "/soul.md", "/tool.md"];
|
|
191
|
+
const isSystemFile = systemFiles.some(f => lowerFilePath.endsWith(f) || lowerFilePath === f.slice(1));
|
|
192
|
+
if (isSystemFile) {
|
|
193
|
+
if (identity.type !== "owner") {
|
|
193
194
|
return { block: true, blockReason: "Access denied." };
|
|
194
195
|
}
|
|
195
|
-
return;
|
|
196
|
+
return;
|
|
196
197
|
}
|
|
197
198
|
|
|
198
199
|
// --- 以下只处理 memory/ 目录下的写入 ---
|
|
@@ -247,7 +248,7 @@ export default {
|
|
|
247
248
|
if (!filePath) return;
|
|
248
249
|
|
|
249
250
|
const wsDir = ctx.workspaceDir || workspaceDir;
|
|
250
|
-
const identity = resolveSessionIdentity(ctx.sessionKey
|
|
251
|
+
const identity = resolveSessionIdentity(ctx.sessionKey);
|
|
251
252
|
|
|
252
253
|
// 拦截 memory/ 路径的读取
|
|
253
254
|
if (filePath.includes("/memory/") || filePath.includes("memory/")) {
|
|
@@ -280,12 +281,18 @@ export default {
|
|
|
280
281
|
api.on("before_tool_call", (event: any, ctx: any) => {
|
|
281
282
|
if (event.toolName !== "exec" && event.toolName !== "process") return;
|
|
282
283
|
|
|
283
|
-
const identity = resolveSessionIdentity(ctx.sessionKey
|
|
284
|
+
const identity = resolveSessionIdentity(ctx.sessionKey);
|
|
284
285
|
if (identity.type === "owner") return;
|
|
285
286
|
|
|
286
|
-
|
|
287
|
+
let command = (event.params?.command || event.params?.cmd || "") as string;
|
|
287
288
|
if (!command) return;
|
|
288
289
|
|
|
290
|
+
// 展开 ~ 为绝对路径,防止用 ~ 绕过路径检测
|
|
291
|
+
const homeDir = process.env.HOME || require("os").homedir();
|
|
292
|
+
if (homeDir) {
|
|
293
|
+
command = command.replace(/(?<=^|[\s;|&"'(=])~/g, homeDir);
|
|
294
|
+
}
|
|
295
|
+
|
|
289
296
|
// MEMORY.md / USER.md 始终拦截非 owner(大小写不敏感)
|
|
290
297
|
const lowerCommand = command.toLowerCase();
|
|
291
298
|
if (lowerCommand.includes("memory.md") || lowerCommand.includes("user.md")) {
|
|
@@ -385,7 +392,7 @@ export default {
|
|
|
385
392
|
const filePath = (event.params?.path || event.params?.filePath || event.params?.file_path || "") as string;
|
|
386
393
|
if (!filePath) return;
|
|
387
394
|
|
|
388
|
-
const identity = resolveSessionIdentity(ctx.sessionKey
|
|
395
|
+
const identity = resolveSessionIdentity(ctx.sessionKey);
|
|
389
396
|
if (identity.type === "owner") return;
|
|
390
397
|
|
|
391
398
|
// 拦截 MEMORY.md(非 owner 不可读)
|
|
@@ -407,11 +414,25 @@ export default {
|
|
|
407
414
|
}
|
|
408
415
|
}, { priority: 10 });
|
|
409
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
|
+
|
|
410
431
|
// ====================================================================
|
|
411
432
|
// 钩子 7: agent_end — 将 agent 对话同步到 memory 文件
|
|
412
433
|
// ====================================================================
|
|
413
434
|
api.on("agent_end", (event: any, ctx: any) => {
|
|
414
|
-
const identity = resolveSessionIdentity(ctx.sessionKey
|
|
435
|
+
const identity = resolveSessionIdentity(ctx.sessionKey);
|
|
415
436
|
const wsDir = ctx.workspaceDir || workspaceDir;
|
|
416
437
|
const messages = event.messages || [];
|
|
417
438
|
|