fix(history): "Tất cả project" và số liệu mỗi project đọc đủ mọi thư mục

Lịch sử hội thoại KHÔNG nằm chung một chỗ: _bind_project đặt
config._project_history_dir thành <workspace của project>/.cowork_history mỗi
lần chọn project khác — cố ý, để chia sẻ thư mục project là chia sẻ cả lịch sử.

Nhưng hai chỗ đọc lại chỉ đọc MỘT thư mục, gây hai triệu chứng cùng gốc:
- ui/sidebar.py đọc history_dir() (thư mục của project ĐANG mở), nên khung
  "Tất cả project…" dựng đủ tiêu đề nhóm mà mọi nhóm trừ một đều rỗng;
- _project_counts gọi list_conversations() KHÔNG tham số, tức đọc HISTORY_DIR
  toàn cục nơi không có hội thoại nào của project, nên mọi dòng project đều
  đếm "0 đoạn chat".

Thêm history_dirs() + list_conversations_by_project(), giữ đúng thứ tự cũ (ghim
trước, mới nhất trước) và chống trùng. Thư mục là chủ sở hữu có thẩm quyền: hội
thoại nằm trong workspace của project nào thì thuộc project đó, kể cả khi trường
project_id trong file đã cũ vì project bị đổi thư mục.

HISTORY_SUBDIR + project_history_dir() gom đường dẫn về một định nghĩa duy nhất
— chuỗi ".cowork_history" từng nằm rải trong ui/workspace_tab.py.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-09-10 01:34:36 +09:00
committed by thanhnv
co-authored by Claude Opus 5
parent 61cadac385
commit b699e16185
4 changed files with 230 additions and 1 deletions
+52
View File
@@ -132,6 +132,58 @@ def _matches_query(query: str, title: str, messages: List[Dict[str, Any]]) -> bo
return False
def history_dirs() -> list:
"""Các cặp ``(project_id, thư mục lịch sử)`` của MỌI project, cộng thư mục
mặc định cho hội thoại chưa thuộc project nào.
Có hàm này vì lịch sử KHÔNG nằm chung một chỗ, mà nằm trong thư mục làm việc
của từng project. Ai chỉ gọi ``list_conversations()`` một lần sẽ chỉ thấy
hội thoại của project đang mở — hoặc, nếu gọi không tham số, không thấy cái
nào cả. Đó chính là hai lỗi đã xảy ra: khung "Tất cả project…" hiện nhóm
rỗng cho mọi project trừ một, và mọi dòng project đều đếm "0 đoạn chat".
"""
from ..config import HISTORY_DIR
from .projects import list_projects, project_history_dir
pairs = [("default", HISTORY_DIR)]
for project in list_projects():
pairs.append((project.project_id, project_history_dir(project)))
return pairs
def list_conversations_by_project(pairs, query: str = "") -> List[Dict[str, Any]]:
"""Gộp lịch sử hội thoại của NHIỀU project. ``pairs`` là các cặp
``(project_id, directory)``.
Lịch sử KHÔNG nằm chung một chỗ: ``WorkspaceTab`` đặt
``config._project_history_dir`` thành ``<workspace của project>/.cowork_history``
mỗi lần người dùng chọn project khác, nên ``config.history_dir()`` chỉ trả về
thư mục của project ĐANG mở. Một lần gọi :func:`list_conversations` vì thế
chỉ thấy được hội thoại của project đó — khung "Tất cả project…" dựng đủ
tiêu đề nhóm cho mọi project nhưng mọi nhóm trừ một đều rỗng.
Thư mục là chủ sở hữu có thẩm quyền: hội thoại nằm trong thư mục làm việc của
project nào thì thuộc project đó, kể cả khi trường ``project_id`` ghi trong
file đã cũ (project bị đổi thư mục chẳng hạn).
"""
seen: set = set()
items: List[Dict[str, Any]] = []
for project_id, directory in pairs:
if directory is None:
continue
for meta in list_conversations(directory, query=query):
key = str(meta["path"])
if key in seen:
continue
seen.add(key)
if project_id:
meta["project_id"] = project_id
items.append(meta)
# Cùng thứ tự mà list_conversations dùng: ghim lên đầu, rồi mới nhất trước.
items.sort(key=lambda d: (not d["pinned"], -d["mtime"]))
return items
def list_conversations(directory: Optional[Path] = None, query: str = "") -> List[Dict[str, Any]]:
"""List saved conversations, most recent first (pinned always on top).