memory-privacy 1.2.0 → 1.4.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 +2 -2
- package/package.json +1 -1
- package/utils.ts +45 -47
package/filters.ts
CHANGED
|
@@ -116,10 +116,10 @@ function isPathAllowedRelative(
|
|
|
116
116
|
}
|
|
117
117
|
|
|
118
118
|
if (identity.type === "peer") {
|
|
119
|
-
//
|
|
119
|
+
// 私聊中访问群记忆:通过 API 获取成员资格,按时间线过滤
|
|
120
120
|
const fileDate = extractDateFromPath(relPath);
|
|
121
121
|
if (!fileDate) return false;
|
|
122
|
-
return isWithinMembershipPeriod(
|
|
122
|
+
return isWithinMembershipPeriod(groupId, identity.peerId, fileDate);
|
|
123
123
|
}
|
|
124
124
|
|
|
125
125
|
return false;
|
package/package.json
CHANGED
package/utils.ts
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import fs from "fs";
|
|
2
2
|
import path from "path";
|
|
3
|
+
import { execSync } from "child_process";
|
|
3
4
|
import type { SessionIdentity } from "./identity.js";
|
|
4
5
|
|
|
5
6
|
// ========== 类型定义 ==========
|
|
@@ -7,17 +8,16 @@ import type { SessionIdentity } from "./identity.js";
|
|
|
7
8
|
export interface MemberPeriod {
|
|
8
9
|
joined: number; // Unix 毫秒时间戳
|
|
9
10
|
left: number | null;
|
|
10
|
-
reason?: string;
|
|
11
11
|
}
|
|
12
12
|
|
|
13
|
-
export interface
|
|
14
|
-
|
|
13
|
+
export interface MemberGroupInfo {
|
|
14
|
+
group_id: string;
|
|
15
15
|
periods: MemberPeriod[];
|
|
16
16
|
}
|
|
17
17
|
|
|
18
|
-
export interface
|
|
19
|
-
|
|
20
|
-
|
|
18
|
+
export interface MemberApiResponse {
|
|
19
|
+
user_id: string;
|
|
20
|
+
groups: MemberGroupInfo[];
|
|
21
21
|
}
|
|
22
22
|
|
|
23
23
|
export interface KnowledgeBaseRule {
|
|
@@ -67,22 +67,37 @@ export function extractKnowledgeBaseId(filePath: string): string {
|
|
|
67
67
|
return knowledgeIndex >= 0 && parts[knowledgeIndex + 1] ? parts[knowledgeIndex + 1] : "unknown-kb";
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
-
// ==========
|
|
70
|
+
// ========== API 调用 ==========
|
|
71
71
|
|
|
72
72
|
/**
|
|
73
|
-
*
|
|
73
|
+
* 根据 HELM_ENV 环境变量返回 API base URL
|
|
74
74
|
*/
|
|
75
|
-
export function
|
|
75
|
+
export function getApiBaseUrl(): string {
|
|
76
|
+
const env = (process.env.HELM_ENV || "dev").toLowerCase();
|
|
77
|
+
switch (env) {
|
|
78
|
+
case "prod": return "https://claw-server.csagentai.com";
|
|
79
|
+
case "staging": return "https://claw-server.csjkagent.com";
|
|
80
|
+
default: return "https://claw-server.csaiagent.com";
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
/**
|
|
85
|
+
* 同步调用 API 获取用户的群成员资格数据
|
|
86
|
+
* 使用 execSync + curl 实现同步 HTTP 请求
|
|
87
|
+
*/
|
|
88
|
+
export function fetchMemberGroupsSync(userId: string): MemberApiResponse | null {
|
|
76
89
|
try {
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
}
|
|
90
|
+
const baseUrl = getApiBaseUrl();
|
|
91
|
+
const url = `${baseUrl}/api/members?user_id=${encodeURIComponent(userId)}`;
|
|
92
|
+
const result = execSync(`curl -s --max-time 5 "${url}"`, { encoding: "utf-8" });
|
|
93
|
+
return JSON.parse(result) as MemberApiResponse;
|
|
80
94
|
} catch {
|
|
81
|
-
|
|
95
|
+
return null;
|
|
82
96
|
}
|
|
83
|
-
return null;
|
|
84
97
|
}
|
|
85
98
|
|
|
99
|
+
// ========== 文件加载 ==========
|
|
100
|
+
|
|
86
101
|
/**
|
|
87
102
|
* 加载知识库 ACL(带 mtime 缓存)
|
|
88
103
|
*/
|
|
@@ -111,29 +126,25 @@ export function loadKnowledgeACL(workspaceDir: string): KnowledgeACL {
|
|
|
111
126
|
/**
|
|
112
127
|
* 检查某个用户在某个日期是否处于群成员期内
|
|
113
128
|
*
|
|
129
|
+
* 通过外部 API 获取成员资格数据
|
|
114
130
|
* 规则:fileTimestamp >= joined && (left === null || fileTimestamp < left)
|
|
115
131
|
* fileTimestamp = 文件日期 00:00:00 UTC
|
|
116
132
|
*/
|
|
117
133
|
export function isWithinMembershipPeriod(
|
|
118
|
-
workspaceDir: string,
|
|
119
134
|
groupId: string,
|
|
120
135
|
peerId: string,
|
|
121
136
|
fileDate: string // "2026-03-17"
|
|
122
137
|
): boolean {
|
|
123
|
-
const
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
if (!registry) return false;
|
|
138
|
+
const response = fetchMemberGroupsSync(peerId);
|
|
139
|
+
if (!response || !response.groups) return false;
|
|
127
140
|
|
|
128
|
-
|
|
129
|
-
const
|
|
130
|
-
|
|
131
|
-
const member = memberKey ? registry.members[memberKey] : undefined;
|
|
132
|
-
if (!member || !member.periods || member.periods.length === 0) return false;
|
|
141
|
+
const lowerGroupId = groupId.toLowerCase();
|
|
142
|
+
const group = response.groups.find(g => g.group_id.toLowerCase() === lowerGroupId);
|
|
143
|
+
if (!group || !group.periods || group.periods.length === 0) return false;
|
|
133
144
|
|
|
134
145
|
const fileTimestamp = new Date(`${fileDate}T00:00:00Z`).getTime();
|
|
135
146
|
|
|
136
|
-
return
|
|
147
|
+
return group.periods.some((period) => {
|
|
137
148
|
const afterJoin = fileTimestamp >= period.joined;
|
|
138
149
|
const beforeLeft = period.left === null || fileTimestamp < period.left;
|
|
139
150
|
return afterJoin && beforeLeft;
|
|
@@ -160,7 +171,7 @@ export function toRelativeMemoryPath(absolutePath: string, workspaceDir: string)
|
|
|
160
171
|
/**
|
|
161
172
|
* 获取某个身份有权限访问的所有 memory 子目录(绝对路径列表)
|
|
162
173
|
*
|
|
163
|
-
* peer: peers/{peerId}/ + _acl.json 中 userids 包含 peerId 的知识库 +
|
|
174
|
+
* peer: peers/{peerId}/ + _acl.json 中 userids 包含 peerId 的知识库 + API 返回当前在群的群组
|
|
164
175
|
* group: groups/{groupId}/ + _acl.json 中 allowGroups 包含 groupId 的知识库
|
|
165
176
|
* owner: memory/ 整个目录
|
|
166
177
|
*/
|
|
@@ -191,29 +202,16 @@ export function getAllowedDirs(wsDir: string, identity: SessionIdentity): string
|
|
|
191
202
|
}
|
|
192
203
|
}
|
|
193
204
|
|
|
194
|
-
// 3.
|
|
195
|
-
const
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
const membersPath = path.join(groupPath, "_members.json");
|
|
202
|
-
const registry = loadMembersRegistry(membersPath);
|
|
203
|
-
if (!registry) continue;
|
|
204
|
-
|
|
205
|
-
const memberKey = Object.keys(registry.members)
|
|
206
|
-
.find(k => k.toLowerCase() === lowerPeerId);
|
|
207
|
-
if (!memberKey) continue;
|
|
208
|
-
|
|
209
|
-
const member = registry.members[memberKey];
|
|
210
|
-
const lastPeriod = member.periods?.[member.periods.length - 1];
|
|
211
|
-
if (lastPeriod && lastPeriod.left === null) {
|
|
212
|
-
dirs.push(groupPath);
|
|
213
|
-
}
|
|
205
|
+
// 3. 群组:通过 API 获取当前在群的群组(last period.left === null)
|
|
206
|
+
const response = fetchMemberGroupsSync(identity.peerId);
|
|
207
|
+
if (response && response.groups) {
|
|
208
|
+
for (const group of response.groups) {
|
|
209
|
+
const lastPeriod = group.periods?.[group.periods.length - 1];
|
|
210
|
+
if (lastPeriod && lastPeriod.left === null) {
|
|
211
|
+
dirs.push(path.join(memDir, "groups", group.group_id));
|
|
214
212
|
}
|
|
215
213
|
}
|
|
216
|
-
}
|
|
214
|
+
}
|
|
217
215
|
}
|
|
218
216
|
|
|
219
217
|
if (identity.type === "group") {
|