xdfx/rwfx/3qfx 任务深链对话按 metadata.taskId 全局共享、不区分用户,但生成的
md/artifacts 与上传文件物理存在 users/{user_id}/threads/{thread_id}/ 下,user_id
之前取当前登录账号。导致 A 账号生成的文件,B 账号打开同一共享对话时后端去
users/B/ 找 → 404「找不到」。
新增 deerflow/runtime/thread_paths.py(resolve_path_user_id /
aresolve_path_user_id):带 taskId 的共享对话统一解析到对话创建者
(threads_meta.user_id),个人对话仍走当前登录(行为不变)。在所有线程文件路径关口替换:
写入源 ThreadDataMiddleware、present_files、uploads(manager+middleware+router)、
产物读写(path_utils → artifacts/skills router)、嵌入式 client.get_artifact。
正向结果进程内缓存;无 DB/内存态/无行均安全回退到当前登录。
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
48 lines
2.0 KiB
Python
48 lines
2.0 KiB
Python
"""Shared path resolution for thread virtual paths (e.g. mnt/user-data/outputs/...)."""
|
|
|
|
from pathlib import Path
|
|
|
|
from fastapi import HTTPException
|
|
|
|
from deerflow.config.paths import get_paths
|
|
from deerflow.runtime.thread_paths import aresolve_path_user_id, resolve_path_user_id
|
|
|
|
|
|
def resolve_thread_virtual_path(thread_id: str, virtual_path: str, *, user_id: str | None = None) -> Path:
|
|
"""Resolve a virtual path to the actual filesystem path under thread user-data.
|
|
|
|
Args:
|
|
thread_id: The thread ID.
|
|
virtual_path: The virtual path as seen inside the sandbox
|
|
(e.g., /mnt/user-data/outputs/file.txt).
|
|
user_id: Pre-resolved canonical user bucket. When ``None`` it is
|
|
resolved synchronously via :func:`resolve_path_user_id` (the
|
|
thread creator for task-shared threads, else the current
|
|
login). Async callers should pass the result of
|
|
:func:`aresolve_path_user_id` to avoid a blocking DB read.
|
|
|
|
Returns:
|
|
The resolved filesystem path.
|
|
|
|
Raises:
|
|
HTTPException: If the path is invalid or outside allowed directories.
|
|
"""
|
|
if user_id is None:
|
|
user_id = resolve_path_user_id(thread_id)
|
|
try:
|
|
return get_paths().resolve_virtual_path(thread_id, virtual_path, user_id=user_id)
|
|
except ValueError as e:
|
|
status = 403 if "traversal" in str(e) else 400
|
|
raise HTTPException(status_code=status, detail=str(e))
|
|
|
|
|
|
async def aresolve_thread_virtual_path(thread_id: str, virtual_path: str) -> Path:
|
|
"""Async variant of :func:`resolve_thread_virtual_path` for FastAPI handlers.
|
|
|
|
Resolves the canonical user bucket without blocking the event loop, so a
|
|
task-shared thread's artifacts resolve to the thread creator's directory
|
|
regardless of which account is currently logged in.
|
|
"""
|
|
user_id = await aresolve_path_user_id(thread_id)
|
|
return resolve_thread_virtual_path(thread_id, virtual_path, user_id=user_id)
|