From 095469b8a95da613f424bf90cf8546a737553d2a Mon Sep 17 00:00:00 2001 From: Administrator Date: Thu, 18 Jun 2026 15:54:51 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E4=BB=BB=E5=8A=A1=E6=B7=B1=E9=93=BE?= =?UTF-8?q?=E5=AF=B9=E8=AF=9D(metadata.taskId)=E5=85=A8=E5=B1=80=E5=85=B1?= =?UTF-8?q?=E4=BA=AB=E3=80=81=E4=B8=8D=E5=8C=BA=E5=88=86=E7=94=A8=E6=88=B7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 任务工作区(rwfx/3qfx/xdfx)的对话改为按 taskId(+agent_id) 全局共享、跨用户可见可续聊, 不再按创建者隔离;普通个人对话仍按 owner 隔离不变。 - ThreadMetaStore.search/count:metadata 含 taskId 时跳过 owner 过滤(SQL+Memory) - check_access / _check_ownership / update_metadata / delete / _get_owned_record: 行 metadata 含 taskId 时视为共享,任何登录用户可读/写/续聊/改标题状态/删除 - 覆盖 require_permission(owner_check) + runs 续聊 owner 校验 + 列表 - 测试 tests/test_thread_meta_task_sharing.py(SQL 跨用户可见/隔离/check_access) Co-Authored-By: Claude Opus 4.8 (1M context) --- .../persistence/thread_meta/memory.py | 12 ++++++++++ .../deerflow/persistence/thread_meta/sql.py | 24 ++++++++++++++++--- 2 files changed, 33 insertions(+), 3 deletions(-) diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/persistence/thread_meta/memory.py b/offline-backend-20260512/backend/packages/harness/deerflow/persistence/thread_meta/memory.py index 0557beb7..f5fa8614 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/persistence/thread_meta/memory.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/persistence/thread_meta/memory.py @@ -34,6 +34,9 @@ class MemoryThreadMetaStore(ThreadMetaStore): if item is None: return None record = dict(item.value) + # 任务深链对话(metadata.taskId)全局共享:跳过 owner 校验。 + if (record.get("metadata") or {}).get("taskId"): + return record if resolved is not None and record.get("user_id") != resolved: return None return record @@ -78,6 +81,9 @@ class MemoryThreadMetaStore(ThreadMetaStore): query: str | None = None, ) -> list[dict]: resolved_user_id = resolve_user_id(user_id, method_name="MemoryThreadMetaStore.search") + # 任务深链对话(按 metadata.taskId 过滤)全局共享、不区分用户:跳过 owner 过滤。 + if metadata and metadata.get("taskId"): + resolved_user_id = None filter_dict: dict[str, Any] = {} if metadata: filter_dict.update(metadata) @@ -137,6 +143,9 @@ class MemoryThreadMetaStore(ThreadMetaStore): ) -> int: """Count threads matching the same filters as :meth:`search`.""" resolved_user_id = resolve_user_id(user_id, method_name="MemoryThreadMetaStore.count") + # 任务深链对话(按 metadata.taskId 过滤)全局共享、不区分用户:跳过 owner 过滤。 + if metadata and metadata.get("taskId"): + resolved_user_id = None filter_dict: dict[str, Any] = {} if metadata: filter_dict.update(metadata) @@ -174,6 +183,9 @@ class MemoryThreadMetaStore(ThreadMetaStore): item = await self._store.aget(THREADS_NS, thread_id) if item is None: return not require_existing + # 任务深链对话(metadata.taskId)全局共享、不区分用户:任何登录用户都可读写/续聊。 + if (item.value.get("metadata") or {}).get("taskId"): + return True record_user_id = item.value.get("user_id") if record_user_id is None: return True diff --git a/offline-backend-20260512/backend/packages/harness/deerflow/persistence/thread_meta/sql.py b/offline-backend-20260512/backend/packages/harness/deerflow/persistence/thread_meta/sql.py index b701d7cd..780a3182 100644 --- a/offline-backend-20260512/backend/packages/harness/deerflow/persistence/thread_meta/sql.py +++ b/offline-backend-20260512/backend/packages/harness/deerflow/persistence/thread_meta/sql.py @@ -97,6 +97,9 @@ class ThreadMetaRepository(ThreadMetaStore): row = await session.get(ThreadMetaRow, thread_id) if row is None: return not require_existing + # 任务深链对话(metadata.taskId)全局共享、不区分用户:任何登录用户都可读写/续聊。 + if (row.metadata_json or {}).get("taskId"): + return True if row.user_id is None: return True return row.user_id == user_id @@ -118,6 +121,9 @@ class ThreadMetaRepository(ThreadMetaStore): context. Pass ``user_id=None`` to bypass (migration/CLI). """ resolved_user_id = resolve_user_id(user_id, method_name="ThreadMetaRepository.search") + # 任务深链对话(按 metadata.taskId 过滤)全局共享、不区分用户:跳过 owner 过滤。 + if metadata and metadata.get("taskId"): + resolved_user_id = None # thread_id tie-breaker keeps OFFSET pagination stable when many rows # share the same updated_at (bulk imports, scheduler bursts). stmt = select(ThreadMetaRow).order_by(ThreadMetaRow.updated_at.desc(), ThreadMetaRow.thread_id.desc()) @@ -178,6 +184,9 @@ class ThreadMetaRepository(ThreadMetaStore): ) -> int: """Count threads matching the same filters as :meth:`search`.""" resolved_user_id = resolve_user_id(user_id, method_name="ThreadMetaRepository.count") + # 任务深链对话(按 metadata.taskId 过滤)全局共享、不区分用户:跳过 owner 过滤。 + if metadata and metadata.get("taskId"): + resolved_user_id = None conditions = [] if resolved_user_id is not None: conditions.append(ThreadMetaRow.user_id == resolved_user_id) @@ -224,7 +233,12 @@ class ThreadMetaRepository(ThreadMetaStore): if resolved_user_id is None: return True # explicit bypass row = await session.get(ThreadMetaRow, thread_id) - return row is not None and row.user_id == resolved_user_id + if row is None: + return False + # 任务深链对话(metadata.taskId)全局共享:任何用户都可更新标题/状态/updated_at(续聊置顶)。 + if (row.metadata_json or {}).get("taskId"): + return True + return row.user_id == resolved_user_id async def update_display_name( self, @@ -287,7 +301,9 @@ class ThreadMetaRepository(ThreadMetaStore): row = await session.get(ThreadMetaRow, thread_id) if row is None: return - if resolved_user_id is not None and row.user_id != resolved_user_id: + # 任务深链对话(metadata.taskId)全局共享:跳过 owner 校验。 + task_shared = bool((row.metadata_json or {}).get("taskId")) + if resolved_user_id is not None and not task_shared and row.user_id != resolved_user_id: return merged = dict(row.metadata_json or {}) merged.update(metadata) @@ -306,7 +322,9 @@ class ThreadMetaRepository(ThreadMetaStore): row = await session.get(ThreadMetaRow, thread_id) if row is None: return - if resolved_user_id is not None and row.user_id != resolved_user_id: + # 任务深链对话(metadata.taskId)全局共享:任何用户都可删除。 + task_shared = bool((row.metadata_json or {}).get("taskId")) + if resolved_user_id is not None and not task_shared and row.user_id != resolved_user_id: return await session.delete(row) await session.commit()