mindforge-memory 6.4.1__py3-none-any.whl
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.
- MindForge.py +20 -0
- mindforge/__init__.py +111 -0
- mindforge/adapters/__init__.py +18 -0
- mindforge/adapters/claude_adapter.py +84 -0
- mindforge/adapters/crewai_memory.py +76 -0
- mindforge/adapters/generic_api.py +183 -0
- mindforge/adapters/langgraph_store.py +463 -0
- mindforge/adapters/openclaw_adapter.py +91 -0
- mindforge/api/__init__.py +1 -0
- mindforge/api/server.py +1567 -0
- mindforge/cli/__init__.py +1 -0
- mindforge/cli/main.py +13607 -0
- mindforge/core/__init__.py +37 -0
- mindforge/core/embedding.py +847 -0
- mindforge/core/encryption.py +597 -0
- mindforge/core/indexer.py +363 -0
- mindforge/core/mindforge.py +6298 -0
- mindforge/core/paths.py +43 -0
- mindforge/core/query.py +501 -0
- mindforge/core/storage.py +18656 -0
- mindforge/core/types.py +314 -0
- mindforge/core/version.py +56 -0
- mindforge/daemon/__init__.py +0 -0
- mindforge/daemon/server.py +277 -0
- mindforge/mcp/__init__.py +4 -0
- mindforge/mcp/server.py +2559 -0
- mindforge/modules/__init__.py +138 -0
- mindforge/modules/categorizer.py +151 -0
- mindforge/modules/conflict_detector.py +356 -0
- mindforge/modules/connectors.py +331 -0
- mindforge/modules/context_window.py +337 -0
- mindforge/modules/event_bus.py +572 -0
- mindforge/modules/evolution.py +210 -0
- mindforge/modules/experience.py +402 -0
- mindforge/modules/federated.py +722 -0
- mindforge/modules/federated_acl.py +393 -0
- mindforge/modules/follow_up.py +315 -0
- mindforge/modules/hybrid_search.py +565 -0
- mindforge/modules/importer.py +374 -0
- mindforge/modules/integrator.py +196 -0
- mindforge/modules/intent_router.py +359 -0
- mindforge/modules/knowledge_graph.py +395 -0
- mindforge/modules/memory_compressor.py +592 -0
- mindforge/modules/memory_decay.py +474 -0
- mindforge/modules/memory_passport.py +250 -0
- mindforge/modules/multi_agent.py +534 -0
- mindforge/modules/multimodal.py +304 -0
- mindforge/modules/outcome_learning.py +186 -0
- mindforge/modules/personality.py +275 -0
- mindforge/modules/principled_forgetting.py +258 -0
- mindforge/modules/privacy.py +736 -0
- mindforge/modules/recall.py +263 -0
- mindforge/modules/session_focus.py +305 -0
- mindforge/modules/share_conflict.py +521 -0
- mindforge/modules/skill_extractor.py +340 -0
- mindforge/modules/skill_pack.py +205 -0
- mindforge/modules/timeline_viz.py +206 -0
- mindforge/modules/v6.py +931 -0
- mindforge_memory-6.4.1.dist-info/METADATA +281 -0
- mindforge_memory-6.4.1.dist-info/RECORD +64 -0
- mindforge_memory-6.4.1.dist-info/WHEEL +5 -0
- mindforge_memory-6.4.1.dist-info/entry_points.txt +3 -0
- mindforge_memory-6.4.1.dist-info/licenses/LICENSE +21 -0
- mindforge_memory-6.4.1.dist-info/top_level.txt +2 -0
|
@@ -0,0 +1,534 @@
|
|
|
1
|
+
"""
|
|
2
|
+
MindForge 多 Agent 记忆空间(正式 — v6.0.0)
|
|
3
|
+
|
|
4
|
+
多 Agent 共享记忆空间:权限隔离、角色协作、冲突解决。
|
|
5
|
+
|
|
6
|
+
v6.0.0 起本模块为正式能力(此前为 v6.0.0 前瞻预览)。
|
|
7
|
+
与联邦记忆(federated.py,跨节点对等共享)不同,本模块面向同一本地库内的
|
|
8
|
+
多 Agent 协作场景:多个 Agent 在共享空间中交换记忆,并按角色隔离权限。
|
|
9
|
+
|
|
10
|
+
核心概念:
|
|
11
|
+
- Space(记忆空间):命名的共享容器,归属某个 owner Agent
|
|
12
|
+
- Member(成员):加入空间的 Agent,角色为 owner / editor / reader
|
|
13
|
+
- Item(空间条目):被共享进空间的记忆引用,带版本号(冲突时 last-write-wins)
|
|
14
|
+
|
|
15
|
+
隐私护栏:PRIVATE / STRICT 级别的记忆永远禁止进入共享空间。
|
|
16
|
+
"""
|
|
17
|
+
|
|
18
|
+
import time
|
|
19
|
+
import uuid
|
|
20
|
+
from dataclasses import dataclass
|
|
21
|
+
from enum import Enum
|
|
22
|
+
from typing import Any, Dict, List, Optional
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class SpaceRole(Enum):
|
|
26
|
+
"""空间成员角色"""
|
|
27
|
+
OWNER = "owner"
|
|
28
|
+
EDITOR = "editor"
|
|
29
|
+
READER = "reader"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
_ROLE_RANK = {
|
|
33
|
+
SpaceRole.OWNER: 3,
|
|
34
|
+
SpaceRole.EDITOR: 2,
|
|
35
|
+
SpaceRole.READER: 1,
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
# 允许共享的隐私级别(PRIVATE / STRICT 禁止共享)
|
|
39
|
+
_SHAREABLE_PRIVACY = {"PUBLIC", "INTERNAL"}
|
|
40
|
+
|
|
41
|
+
# 空间策略
|
|
42
|
+
SPACE_POLICIES = ("shared", "broadcast")
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
@dataclass
|
|
46
|
+
class AgentSpace:
|
|
47
|
+
"""记忆空间"""
|
|
48
|
+
space_id: str
|
|
49
|
+
name: str
|
|
50
|
+
owner_agent: str
|
|
51
|
+
description: str = ""
|
|
52
|
+
policy: str = "shared" # shared=协作读写 / broadcast=仅 owner 可写
|
|
53
|
+
created_at: float = 0.0
|
|
54
|
+
updated_at: float = 0.0
|
|
55
|
+
|
|
56
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
57
|
+
return {
|
|
58
|
+
"space_id": self.space_id,
|
|
59
|
+
"name": self.name,
|
|
60
|
+
"owner_agent": self.owner_agent,
|
|
61
|
+
"description": self.description,
|
|
62
|
+
"policy": self.policy,
|
|
63
|
+
"created_at": self.created_at,
|
|
64
|
+
"updated_at": self.updated_at,
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
@dataclass
|
|
69
|
+
class SpaceMember:
|
|
70
|
+
"""空间成员"""
|
|
71
|
+
space_id: str
|
|
72
|
+
agent_id: str
|
|
73
|
+
role: SpaceRole = SpaceRole.READER
|
|
74
|
+
joined_at: float = 0.0
|
|
75
|
+
|
|
76
|
+
def to_dict(self) -> Dict[str, Any]:
|
|
77
|
+
return {
|
|
78
|
+
"space_id": self.space_id,
|
|
79
|
+
"agent_id": self.agent_id,
|
|
80
|
+
"role": self.role.value,
|
|
81
|
+
"joined_at": self.joined_at,
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
class MultiAgentMemoryManager:
|
|
86
|
+
"""多 Agent 记忆空间管理器(实验性)
|
|
87
|
+
|
|
88
|
+
通过 SQLite 持久化空间/成员/条目,随主库一起备份。
|
|
89
|
+
所有变更操作都会写入审计日志(复用 storage._add_audit)。
|
|
90
|
+
"""
|
|
91
|
+
|
|
92
|
+
EXPERIMENTAL = False
|
|
93
|
+
TARGET_VERSION = "6.0.0"
|
|
94
|
+
|
|
95
|
+
def __init__(self, storage):
|
|
96
|
+
self.storage = storage
|
|
97
|
+
self._init_tables()
|
|
98
|
+
|
|
99
|
+
# ===== 内部基础设施 =====
|
|
100
|
+
|
|
101
|
+
def _conn(self):
|
|
102
|
+
return self.storage._get_conn()
|
|
103
|
+
|
|
104
|
+
def _init_tables(self):
|
|
105
|
+
conn = self._conn()
|
|
106
|
+
conn.executescript("""
|
|
107
|
+
CREATE TABLE IF NOT EXISTS agent_spaces (
|
|
108
|
+
id TEXT PRIMARY KEY,
|
|
109
|
+
name TEXT NOT NULL UNIQUE,
|
|
110
|
+
description TEXT DEFAULT '',
|
|
111
|
+
owner_agent TEXT NOT NULL,
|
|
112
|
+
policy TEXT DEFAULT 'shared',
|
|
113
|
+
created_at REAL,
|
|
114
|
+
updated_at REAL
|
|
115
|
+
);
|
|
116
|
+
|
|
117
|
+
CREATE TABLE IF NOT EXISTS agent_space_members (
|
|
118
|
+
space_id TEXT NOT NULL,
|
|
119
|
+
agent_id TEXT NOT NULL,
|
|
120
|
+
role TEXT DEFAULT 'reader',
|
|
121
|
+
joined_at REAL,
|
|
122
|
+
PRIMARY KEY (space_id, agent_id),
|
|
123
|
+
FOREIGN KEY (space_id) REFERENCES agent_spaces(id)
|
|
124
|
+
);
|
|
125
|
+
CREATE INDEX IF NOT EXISTS idx_space_members_agent
|
|
126
|
+
ON agent_space_members(agent_id);
|
|
127
|
+
|
|
128
|
+
CREATE TABLE IF NOT EXISTS agent_space_items (
|
|
129
|
+
space_id TEXT NOT NULL,
|
|
130
|
+
memory_id TEXT NOT NULL,
|
|
131
|
+
added_by TEXT DEFAULT '',
|
|
132
|
+
added_at REAL,
|
|
133
|
+
version INTEGER DEFAULT 1,
|
|
134
|
+
PRIMARY KEY (space_id, memory_id),
|
|
135
|
+
FOREIGN KEY (space_id) REFERENCES agent_spaces(id),
|
|
136
|
+
FOREIGN KEY (memory_id) REFERENCES memories(id)
|
|
137
|
+
);
|
|
138
|
+
CREATE INDEX IF NOT EXISTS idx_space_items_memory
|
|
139
|
+
ON agent_space_items(memory_id);
|
|
140
|
+
""")
|
|
141
|
+
conn.commit()
|
|
142
|
+
|
|
143
|
+
def _row_to_space(self, row) -> AgentSpace:
|
|
144
|
+
return AgentSpace(
|
|
145
|
+
space_id=row["id"],
|
|
146
|
+
name=row["name"],
|
|
147
|
+
owner_agent=row["owner_agent"],
|
|
148
|
+
description=row["description"] or "",
|
|
149
|
+
policy=row["policy"] or "shared",
|
|
150
|
+
created_at=row["created_at"] or 0.0,
|
|
151
|
+
updated_at=row["updated_at"] or 0.0,
|
|
152
|
+
)
|
|
153
|
+
|
|
154
|
+
def _resolve_space(self, space_ref: str) -> Optional[AgentSpace]:
|
|
155
|
+
"""按 ID 或名称解析空间"""
|
|
156
|
+
conn = self._conn()
|
|
157
|
+
row = conn.execute(
|
|
158
|
+
"SELECT * FROM agent_spaces WHERE id = ?", (space_ref,)
|
|
159
|
+
).fetchone()
|
|
160
|
+
if row is None:
|
|
161
|
+
row = conn.execute(
|
|
162
|
+
"SELECT * FROM agent_spaces WHERE name = ?", (space_ref,)
|
|
163
|
+
).fetchone()
|
|
164
|
+
return self._row_to_space(row) if row else None
|
|
165
|
+
|
|
166
|
+
def _get_role(self, space_id: str, agent_id: str) -> Optional[SpaceRole]:
|
|
167
|
+
conn = self._conn()
|
|
168
|
+
row = conn.execute(
|
|
169
|
+
"SELECT role FROM agent_space_members WHERE space_id = ? AND agent_id = ?",
|
|
170
|
+
(space_id, agent_id),
|
|
171
|
+
).fetchone()
|
|
172
|
+
if row is None:
|
|
173
|
+
return None
|
|
174
|
+
try:
|
|
175
|
+
return SpaceRole(row["role"])
|
|
176
|
+
except ValueError:
|
|
177
|
+
return SpaceRole.READER
|
|
178
|
+
|
|
179
|
+
def check_permission(self, space_id: str, agent_id: str,
|
|
180
|
+
required: SpaceRole = SpaceRole.READER) -> bool:
|
|
181
|
+
"""检查 Agent 在空间中是否具备所需角色(含)以上权限"""
|
|
182
|
+
role = self._get_role(space_id, agent_id)
|
|
183
|
+
if role is None:
|
|
184
|
+
return False
|
|
185
|
+
return _ROLE_RANK[role] >= _ROLE_RANK[required]
|
|
186
|
+
|
|
187
|
+
# ===== 空间管理 =====
|
|
188
|
+
|
|
189
|
+
def create_space(self, name: str, owner_agent: str,
|
|
190
|
+
description: str = "", policy: str = "shared") -> Dict[str, Any]:
|
|
191
|
+
"""创建记忆空间,创建者自动成为 owner 成员"""
|
|
192
|
+
name = (name or "").strip()
|
|
193
|
+
if not name:
|
|
194
|
+
return {"success": False, "error": "空间名称不能为空"}
|
|
195
|
+
if len(name) > 64:
|
|
196
|
+
return {"success": False, "error": "空间名称不能超过 64 字符"}
|
|
197
|
+
if not owner_agent:
|
|
198
|
+
return {"success": False, "error": "必须指定 owner Agent(--agent)"}
|
|
199
|
+
if policy not in SPACE_POLICIES:
|
|
200
|
+
return {"success": False,
|
|
201
|
+
"error": f"无效的空间策略: {policy}(可选: {'/'.join(SPACE_POLICIES)})"}
|
|
202
|
+
|
|
203
|
+
conn = self._conn()
|
|
204
|
+
if conn.execute("SELECT 1 FROM agent_spaces WHERE name = ?",
|
|
205
|
+
(name,)).fetchone():
|
|
206
|
+
return {"success": False, "error": f"空间名称已存在: {name}"}
|
|
207
|
+
|
|
208
|
+
now = time.time()
|
|
209
|
+
space_id = f"space_{uuid.uuid4().hex[:12]}"
|
|
210
|
+
conn.execute(
|
|
211
|
+
"INSERT INTO agent_spaces (id, name, description, owner_agent, policy,"
|
|
212
|
+
" created_at, updated_at) VALUES (?, ?, ?, ?, ?, ?, ?)",
|
|
213
|
+
(space_id, name, (description or "").strip(), owner_agent, policy, now, now),
|
|
214
|
+
)
|
|
215
|
+
conn.execute(
|
|
216
|
+
"INSERT INTO agent_space_members (space_id, agent_id, role, joined_at)"
|
|
217
|
+
" VALUES (?, ?, ?, ?)",
|
|
218
|
+
(space_id, owner_agent, SpaceRole.OWNER.value, now),
|
|
219
|
+
)
|
|
220
|
+
conn.commit()
|
|
221
|
+
self.storage._add_audit("space_create", space_id, owner_agent, "",
|
|
222
|
+
"INTERNAL", {"name": name, "policy": policy})
|
|
223
|
+
return {"success": True, "space_id": space_id, "name": name,
|
|
224
|
+
"owner_agent": owner_agent, "policy": policy}
|
|
225
|
+
|
|
226
|
+
def get_space(self, space_ref: str) -> Optional[Dict[str, Any]]:
|
|
227
|
+
"""获取空间详情(含成员数与条目数)"""
|
|
228
|
+
space = self._resolve_space(space_ref)
|
|
229
|
+
if space is None:
|
|
230
|
+
return None
|
|
231
|
+
conn = self._conn()
|
|
232
|
+
members = conn.execute(
|
|
233
|
+
"SELECT COUNT(*) AS c FROM agent_space_members WHERE space_id = ?",
|
|
234
|
+
(space.space_id,)).fetchone()["c"]
|
|
235
|
+
items = conn.execute(
|
|
236
|
+
"SELECT COUNT(*) AS c FROM agent_space_items WHERE space_id = ?",
|
|
237
|
+
(space.space_id,)).fetchone()["c"]
|
|
238
|
+
data = space.to_dict()
|
|
239
|
+
data["member_count"] = members
|
|
240
|
+
data["item_count"] = items
|
|
241
|
+
return data
|
|
242
|
+
|
|
243
|
+
def list_spaces(self, agent_id: str = "") -> List[Dict[str, Any]]:
|
|
244
|
+
"""列出空间;指定 agent_id 时仅列出其加入的空间"""
|
|
245
|
+
conn = self._conn()
|
|
246
|
+
if agent_id:
|
|
247
|
+
rows = conn.execute(
|
|
248
|
+
"SELECT s.* FROM agent_spaces s"
|
|
249
|
+
" JOIN agent_space_members m ON m.space_id = s.id"
|
|
250
|
+
" WHERE m.agent_id = ? ORDER BY s.created_at DESC",
|
|
251
|
+
(agent_id,)).fetchall()
|
|
252
|
+
else:
|
|
253
|
+
rows = conn.execute(
|
|
254
|
+
"SELECT * FROM agent_spaces ORDER BY created_at DESC").fetchall()
|
|
255
|
+
result = []
|
|
256
|
+
for row in rows:
|
|
257
|
+
space = self._row_to_space(row)
|
|
258
|
+
info = self.get_space(space.space_id)
|
|
259
|
+
if agent_id:
|
|
260
|
+
role = self._get_role(space.space_id, agent_id)
|
|
261
|
+
info["my_role"] = role.value if role else None
|
|
262
|
+
result.append(info)
|
|
263
|
+
return result
|
|
264
|
+
|
|
265
|
+
def delete_space(self, space_ref: str, actor: str) -> Dict[str, Any]:
|
|
266
|
+
"""删除空间(仅 owner);空间内条目仅移除引用,不删除原始记忆"""
|
|
267
|
+
space = self._resolve_space(space_ref)
|
|
268
|
+
if space is None:
|
|
269
|
+
return {"success": False, "error": "空间不存在"}
|
|
270
|
+
if space.owner_agent != actor:
|
|
271
|
+
return {"success": False, "error": "仅空间 owner 可以删除空间"}
|
|
272
|
+
conn = self._conn()
|
|
273
|
+
conn.execute("DELETE FROM agent_space_items WHERE space_id = ?",
|
|
274
|
+
(space.space_id,))
|
|
275
|
+
conn.execute("DELETE FROM agent_space_members WHERE space_id = ?",
|
|
276
|
+
(space.space_id,))
|
|
277
|
+
conn.execute("DELETE FROM agent_spaces WHERE id = ?", (space.space_id,))
|
|
278
|
+
conn.commit()
|
|
279
|
+
self.storage._add_audit("space_delete", space.space_id, actor, "",
|
|
280
|
+
"INTERNAL", {"name": space.name})
|
|
281
|
+
return {"success": True, "space_id": space.space_id, "name": space.name}
|
|
282
|
+
|
|
283
|
+
# ===== 成员管理 =====
|
|
284
|
+
|
|
285
|
+
def add_member(self, space_ref: str, agent_id: str,
|
|
286
|
+
role: str = "reader", actor: str = "") -> Dict[str, Any]:
|
|
287
|
+
"""添加成员。
|
|
288
|
+
|
|
289
|
+
- owner 可添加任意角色成员;
|
|
290
|
+
- 非 owner 只能以 reader 身份加入自己(self-join)。
|
|
291
|
+
"""
|
|
292
|
+
space = self._resolve_space(space_ref)
|
|
293
|
+
if space is None:
|
|
294
|
+
return {"success": False, "error": "空间不存在"}
|
|
295
|
+
agent_id = (agent_id or "").strip()
|
|
296
|
+
if not agent_id:
|
|
297
|
+
return {"success": False, "error": "Agent ID 不能为空"}
|
|
298
|
+
try:
|
|
299
|
+
role_enum = SpaceRole(role)
|
|
300
|
+
except ValueError:
|
|
301
|
+
return {"success": False,
|
|
302
|
+
"error": f"无效角色: {role}(可选: owner/editor/reader)"}
|
|
303
|
+
|
|
304
|
+
is_owner_action = (actor == space.owner_agent)
|
|
305
|
+
if not is_owner_action:
|
|
306
|
+
# 非 owner:仅允许 self-join 为 reader
|
|
307
|
+
if agent_id != actor:
|
|
308
|
+
return {"success": False, "error": "仅 owner 可以添加其他成员"}
|
|
309
|
+
if role_enum != SpaceRole.READER:
|
|
310
|
+
return {"success": False, "error": "自助加入仅能使用 reader 角色"}
|
|
311
|
+
if role_enum == SpaceRole.OWNER:
|
|
312
|
+
return {"success": False, "error": "不能添加 owner 角色成员"}
|
|
313
|
+
elif role_enum == SpaceRole.OWNER:
|
|
314
|
+
return {"success": False, "error": "暂不支持转移 owner(v6.0.0 提供)"}
|
|
315
|
+
|
|
316
|
+
conn = self._conn()
|
|
317
|
+
existing = self._get_role(space.space_id, agent_id)
|
|
318
|
+
if existing is not None:
|
|
319
|
+
if not is_owner_action:
|
|
320
|
+
return {"success": False, "error": f"{agent_id} 已在空间中"}
|
|
321
|
+
# owner 调整成员角色
|
|
322
|
+
conn.execute(
|
|
323
|
+
"UPDATE agent_space_members SET role = ?"
|
|
324
|
+
" WHERE space_id = ? AND agent_id = ?",
|
|
325
|
+
(role_enum.value, space.space_id, agent_id))
|
|
326
|
+
conn.commit()
|
|
327
|
+
return {"success": True, "space_id": space.space_id,
|
|
328
|
+
"agent_id": agent_id, "role": role_enum.value,
|
|
329
|
+
"updated": True}
|
|
330
|
+
|
|
331
|
+
conn.execute(
|
|
332
|
+
"INSERT INTO agent_space_members (space_id, agent_id, role, joined_at)"
|
|
333
|
+
" VALUES (?, ?, ?, ?)",
|
|
334
|
+
(space.space_id, agent_id, role_enum.value, time.time()))
|
|
335
|
+
conn.commit()
|
|
336
|
+
self.storage._add_audit("space_join", space.space_id, actor or agent_id, "",
|
|
337
|
+
"INTERNAL", {"agent_id": agent_id, "role": role_enum.value})
|
|
338
|
+
return {"success": True, "space_id": space.space_id,
|
|
339
|
+
"agent_id": agent_id, "role": role_enum.value, "updated": False}
|
|
340
|
+
|
|
341
|
+
def remove_member(self, space_ref: str, agent_id: str,
|
|
342
|
+
actor: str) -> Dict[str, Any]:
|
|
343
|
+
"""移除成员(owner 操作,或成员自行退出);owner 不可被移除"""
|
|
344
|
+
space = self._resolve_space(space_ref)
|
|
345
|
+
if space is None:
|
|
346
|
+
return {"success": False, "error": "空间不存在"}
|
|
347
|
+
if agent_id == space.owner_agent:
|
|
348
|
+
return {"success": False, "error": "不能移除空间 owner"}
|
|
349
|
+
is_owner_action = (actor == space.owner_agent)
|
|
350
|
+
if not is_owner_action and agent_id != actor:
|
|
351
|
+
return {"success": False, "error": "仅 owner 可以移除其他成员"}
|
|
352
|
+
conn = self._conn()
|
|
353
|
+
cur = conn.execute(
|
|
354
|
+
"DELETE FROM agent_space_members WHERE space_id = ? AND agent_id = ?",
|
|
355
|
+
(space.space_id, agent_id))
|
|
356
|
+
conn.commit()
|
|
357
|
+
if cur.rowcount == 0:
|
|
358
|
+
return {"success": False, "error": f"{agent_id} 不在空间中"}
|
|
359
|
+
self.storage._add_audit("space_leave", space.space_id, actor, "",
|
|
360
|
+
"INTERNAL", {"agent_id": agent_id})
|
|
361
|
+
return {"success": True, "space_id": space.space_id, "agent_id": agent_id}
|
|
362
|
+
|
|
363
|
+
def list_members(self, space_ref: str) -> List[Dict[str, Any]]:
|
|
364
|
+
"""列出空间成员"""
|
|
365
|
+
space = self._resolve_space(space_ref)
|
|
366
|
+
if space is None:
|
|
367
|
+
return []
|
|
368
|
+
conn = self._conn()
|
|
369
|
+
rows = conn.execute(
|
|
370
|
+
"SELECT * FROM agent_space_members WHERE space_id = ?"
|
|
371
|
+
" ORDER BY joined_at ASC", (space.space_id,)).fetchall()
|
|
372
|
+
members = []
|
|
373
|
+
for row in rows:
|
|
374
|
+
try:
|
|
375
|
+
role = SpaceRole(row["role"])
|
|
376
|
+
except ValueError:
|
|
377
|
+
role = SpaceRole.READER
|
|
378
|
+
members.append(SpaceMember(
|
|
379
|
+
space_id=row["space_id"], agent_id=row["agent_id"],
|
|
380
|
+
role=role, joined_at=row["joined_at"] or 0.0,
|
|
381
|
+
).to_dict())
|
|
382
|
+
return members
|
|
383
|
+
|
|
384
|
+
# ===== 记忆共享 =====
|
|
385
|
+
|
|
386
|
+
def share_memory(self, space_ref: str, memory_id: str,
|
|
387
|
+
actor: str) -> Dict[str, Any]:
|
|
388
|
+
"""共享记忆到空间。
|
|
389
|
+
|
|
390
|
+
权限:editor 及以上(broadcast 策略下仅 owner)。
|
|
391
|
+
护栏:PRIVATE / STRICT 记忆禁止共享;回收站记忆禁止共享。
|
|
392
|
+
冲突:重复共享同一条记忆 = last-write-wins,条目版本号 +1。
|
|
393
|
+
"""
|
|
394
|
+
space = self._resolve_space(space_ref)
|
|
395
|
+
if space is None:
|
|
396
|
+
return {"success": False, "error": "空间不存在"}
|
|
397
|
+
|
|
398
|
+
required = SpaceRole.OWNER if space.policy == "broadcast" else SpaceRole.EDITOR
|
|
399
|
+
if not self.check_permission(space.space_id, actor, required):
|
|
400
|
+
return {"success": False,
|
|
401
|
+
"error": f"权限不足:{space.policy} 策略需要 {required.value} 角色"}
|
|
402
|
+
|
|
403
|
+
entry = self.storage.get_memory(memory_id)
|
|
404
|
+
if entry is None:
|
|
405
|
+
return {"success": False, "error": "记忆不存在"}
|
|
406
|
+
if entry.category == "trash":
|
|
407
|
+
return {"success": False, "error": "回收站中的记忆不能共享"}
|
|
408
|
+
if entry.privacy.value not in _SHAREABLE_PRIVACY:
|
|
409
|
+
return {"success": False,
|
|
410
|
+
"error": f"隐私护栏:{entry.privacy.value} 级别记忆禁止进入共享空间"}
|
|
411
|
+
|
|
412
|
+
conn = self._conn()
|
|
413
|
+
existing = conn.execute(
|
|
414
|
+
"SELECT version FROM agent_space_items"
|
|
415
|
+
" WHERE space_id = ? AND memory_id = ?",
|
|
416
|
+
(space.space_id, memory_id)).fetchone()
|
|
417
|
+
|
|
418
|
+
now = time.time()
|
|
419
|
+
if existing:
|
|
420
|
+
# 冲突解决:last-write-wins,版本号递增
|
|
421
|
+
new_version = existing["version"] + 1
|
|
422
|
+
conn.execute(
|
|
423
|
+
"UPDATE agent_space_items SET added_by = ?, added_at = ?, version = ?"
|
|
424
|
+
" WHERE space_id = ? AND memory_id = ?",
|
|
425
|
+
(actor, now, new_version, space.space_id, memory_id))
|
|
426
|
+
conn.commit()
|
|
427
|
+
return {"success": True, "space_id": space.space_id,
|
|
428
|
+
"memory_id": memory_id, "version": new_version,
|
|
429
|
+
"conflict_resolved": "last-write-wins"}
|
|
430
|
+
|
|
431
|
+
conn.execute(
|
|
432
|
+
"INSERT INTO agent_space_items (space_id, memory_id, added_by, added_at,"
|
|
433
|
+
" version) VALUES (?, ?, ?, ?, 1)",
|
|
434
|
+
(space.space_id, memory_id, actor, now))
|
|
435
|
+
conn.commit()
|
|
436
|
+
self.storage._add_audit("space_share", memory_id, actor, "",
|
|
437
|
+
entry.privacy.value, {"space_id": space.space_id})
|
|
438
|
+
return {"success": True, "space_id": space.space_id,
|
|
439
|
+
"memory_id": memory_id, "version": 1,
|
|
440
|
+
"conflict_resolved": None}
|
|
441
|
+
|
|
442
|
+
def unshare_memory(self, space_ref: str, memory_id: str,
|
|
443
|
+
actor: str) -> Dict[str, Any]:
|
|
444
|
+
"""从空间移除记忆引用(不删除原始记忆)"""
|
|
445
|
+
space = self._resolve_space(space_ref)
|
|
446
|
+
if space is None:
|
|
447
|
+
return {"success": False, "error": "空间不存在"}
|
|
448
|
+
if not self.check_permission(space.space_id, actor, SpaceRole.EDITOR):
|
|
449
|
+
return {"success": False, "error": "权限不足:需要 editor 及以上角色"}
|
|
450
|
+
conn = self._conn()
|
|
451
|
+
cur = conn.execute(
|
|
452
|
+
"DELETE FROM agent_space_items WHERE space_id = ? AND memory_id = ?",
|
|
453
|
+
(space.space_id, memory_id))
|
|
454
|
+
conn.commit()
|
|
455
|
+
if cur.rowcount == 0:
|
|
456
|
+
return {"success": False, "error": "该记忆不在空间中"}
|
|
457
|
+
return {"success": True, "space_id": space.space_id, "memory_id": memory_id}
|
|
458
|
+
|
|
459
|
+
def list_space_memories(self, space_ref: str, actor: str,
|
|
460
|
+
limit: int = 50, offset: int = 0) -> Dict[str, Any]:
|
|
461
|
+
"""列出空间中的记忆(reader 及以上可读)"""
|
|
462
|
+
space = self._resolve_space(space_ref)
|
|
463
|
+
if space is None:
|
|
464
|
+
return {"success": False, "error": "空间不存在", "items": []}
|
|
465
|
+
if not self.check_permission(space.space_id, actor, SpaceRole.READER):
|
|
466
|
+
return {"success": False, "error": "权限不足:仅空间成员可读取",
|
|
467
|
+
"items": []}
|
|
468
|
+
limit = max(1, min(200, int(limit)))
|
|
469
|
+
conn = self._conn()
|
|
470
|
+
rows = conn.execute(
|
|
471
|
+
"SELECT i.memory_id, i.added_by, i.added_at, i.version"
|
|
472
|
+
" FROM agent_space_items i"
|
|
473
|
+
" WHERE i.space_id = ? ORDER BY i.added_at DESC LIMIT ? OFFSET ?",
|
|
474
|
+
(space.space_id, limit, offset)).fetchall()
|
|
475
|
+
items = []
|
|
476
|
+
for row in rows:
|
|
477
|
+
entry = self.storage.get_memory(row["memory_id"])
|
|
478
|
+
if entry is None:
|
|
479
|
+
continue # 原始记忆已被删除
|
|
480
|
+
items.append({
|
|
481
|
+
"memory_id": row["memory_id"],
|
|
482
|
+
"content_preview": (entry.content or "")[:80],
|
|
483
|
+
"category": entry.category,
|
|
484
|
+
"tags": entry.tags,
|
|
485
|
+
"privacy": entry.privacy.value,
|
|
486
|
+
"source_agent": entry.source_agent,
|
|
487
|
+
"added_by": row["added_by"],
|
|
488
|
+
"added_at": row["added_at"],
|
|
489
|
+
"version": row["version"],
|
|
490
|
+
})
|
|
491
|
+
return {"success": True, "space_id": space.space_id,
|
|
492
|
+
"space_name": space.name, "items": items, "count": len(items)}
|
|
493
|
+
|
|
494
|
+
# ===== 统计 =====
|
|
495
|
+
|
|
496
|
+
def space_stats(self, space_ref: str = "") -> Dict[str, Any]:
|
|
497
|
+
"""空间统计;不指定空间时返回全局概览"""
|
|
498
|
+
conn = self._conn()
|
|
499
|
+
if space_ref:
|
|
500
|
+
space = self._resolve_space(space_ref)
|
|
501
|
+
if space is None:
|
|
502
|
+
return {"success": False, "error": "空间不存在"}
|
|
503
|
+
by_role = conn.execute(
|
|
504
|
+
"SELECT role, COUNT(*) AS c FROM agent_space_members"
|
|
505
|
+
" WHERE space_id = ? GROUP BY role", (space.space_id,)).fetchall()
|
|
506
|
+
top_contributors = conn.execute(
|
|
507
|
+
"SELECT added_by, COUNT(*) AS c FROM agent_space_items"
|
|
508
|
+
" WHERE space_id = ? GROUP BY added_by ORDER BY c DESC LIMIT 5",
|
|
509
|
+
(space.space_id,)).fetchall()
|
|
510
|
+
return {
|
|
511
|
+
"success": True,
|
|
512
|
+
"space": self.get_space(space.space_id),
|
|
513
|
+
"members_by_role": {r["role"]: r["c"] for r in by_role},
|
|
514
|
+
"top_contributors": [
|
|
515
|
+
{"agent_id": r["added_by"], "shared": r["c"]}
|
|
516
|
+
for r in top_contributors
|
|
517
|
+
],
|
|
518
|
+
}
|
|
519
|
+
spaces = conn.execute("SELECT COUNT(*) AS c FROM agent_spaces").fetchone()["c"]
|
|
520
|
+
members = conn.execute(
|
|
521
|
+
"SELECT COUNT(*) AS c FROM agent_space_members").fetchone()["c"]
|
|
522
|
+
items = conn.execute(
|
|
523
|
+
"SELECT COUNT(*) AS c FROM agent_space_items").fetchone()["c"]
|
|
524
|
+
agents = conn.execute(
|
|
525
|
+
"SELECT COUNT(DISTINCT agent_id) AS c FROM agent_space_members"
|
|
526
|
+
).fetchone()["c"]
|
|
527
|
+
return {
|
|
528
|
+
"success": True,
|
|
529
|
+
"total_spaces": spaces,
|
|
530
|
+
"total_memberships": members,
|
|
531
|
+
"total_shared_items": items,
|
|
532
|
+
"participating_agents": agents,
|
|
533
|
+
"version": "6.0.0",
|
|
534
|
+
}
|