merge: cập nhật từ origin/feature/delta-team/epic-R04 (dọn dead code, gộp i18n/theme, CASAN Gate O, launcher)

Kéo 7 commit mới từ remote — chủ yếu dọn dẹp và siết chất lượng, không đổi
API đang dùng:
- Xoá 1.400 dòng mã chết + 5 gói rỗng còn sót sau các lần merge trước.
- Thêm CASAN Gate O (LOC) áp cho toàn cây mã.
- Sửa 5 checker UI hỏng sau đợt tách widget R08, vá 4 hồi quy.
- install.bat/run.bat, gộp requirements-test.txt vào requirements.txt.
- Gom i18n_*.py / theme_*.py rời rạc thành gói i18n/ và theme/.

Merge sạch, không có conflict marker nào (git tự resolve toàn bộ). Đã kiểm
tra lại 4 điểm đã vá ở 2 lần merge trước (config.py circular import,
ai_edit_model_resolver.py dùng resolve() thay route_turn(),
_confirm_routing_switch nhận timeout, RoutingApplicationService.resolve())
— cả 4 vẫn nguyên vẹn sau merge này.

pytest tests/: 793 passed — giống hệt số liệu trước khi merge, không phát
sinh fail/error mới (8 fail còn lại vẫn là do môi trường sandbox: thiếu
keyring, tên thư mục cowork-local vs cowork_local).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-08-30 12:20:17 +09:00
co-authored by Claude Sonnet 5
312 changed files with 6189 additions and 2171 deletions
@@ -40,6 +40,9 @@ class AtomicJsonFile:
"""
def __init__(self, path: Path, *, indent: int = 2):
"""Trỏ vào một file JSON. ``indent`` giữ file còn đọc và so sánh được bằng mắt
trong git diff.
"""
self.path = Path(path)
self.indent = indent
@@ -63,6 +66,10 @@ class AtomicJsonFile:
return default
def _quarantine(self) -> Path | None:
"""Đổi tên file JSON hỏng thành ``.bad-<mốc thời gian>`` thay vì xoá.
Giữ lại để còn cứu dữ liệu, và để lần ghi sau bắt đầu từ file sạch.
"""
stamp = datetime.now().strftime("%Y%m%d-%H%M%S")
target = self.path.with_suffix(self.path.suffix + f".bad-{stamp}")
try:
@@ -125,7 +132,9 @@ class AtomicJsonFile:
# ---- tiện ích -------------------------------------------------------
def exists(self) -> bool:
"""File đã tồn tại trên đĩa chưa."""
return self.path.exists()
def __repr__(self) -> str:
"""Biểu diễn ngắn kèm đường dẫn, cho log và thông báo lỗi."""
return f"AtomicJsonFile({self.path})"
@@ -22,6 +22,11 @@ class ConversationRepository:
``directory`` (defaults to the app's real ``HISTORY_DIR``)."""
def __init__(self, directory: Optional[Path] = None) -> None:
"""``directory`` để None thì dùng thư mục lịch sử mặc định.
Import muộn ngay trong thân hàm để nạp module này không kéo theo cả cây cấu
hình — test trỏ thẳng vào ``tmp_path``.
"""
if directory is not None:
self._directory = Path(directory)
else:
@@ -29,18 +34,28 @@ class ConversationRepository:
self._directory = HISTORY_DIR
def new_session_id(self) -> str:
"""Sinh id phiên mới cho một cuộc hội thoại."""
return new_session_id()
def save(self, kind: str, session_id: str, messages: List[Dict[str, Any]], **kwargs) -> Path:
"""Ghi hội thoại xuống đĩa (ghi nguyên tử) và trả về đường dẫn file."""
return save_conversation(self._directory, kind, session_id, messages, **kwargs)
def load(self, path: Path) -> Dict[str, Any]:
"""Đọc một hội thoại từ đường dẫn file."""
return load_conversation(path)
def list(self, query: str = "", **kwargs) -> List[Dict[str, Any]]:
"""Liệt kê hội thoại trong thư mục; ``query`` lọc theo tiêu đề và nội dung."""
return list_conversations(self._directory, query=query)
def _resolve_path(self, target: Any) -> Path:
"""Đổi id phiên (hoặc đường dẫn) thành đường dẫn file thật.
Nhận cả ba dạng: Path sẵn, đường dẫn tuyệt đối, và id phiên trần —
id trần thì dò theo mẫu ``*__<id>.json`` vì tiền tố là loại hội thoại
(cowork/co4e/...) mà chỗ gọi không phải lúc nào cũng biết.
"""
if isinstance(target, Path):
return target
p = Path(str(target))
@@ -51,12 +66,15 @@ class ConversationRepository:
return self._directory / f"cowork__{target}.json"
def rename(self, target: Any, new_title: str) -> None:
"""Đổi tiêu đề một hội thoại."""
rename_conversation(self._resolve_path(target), new_title)
def delete(self, target: Any) -> None:
"""Xoá hẳn một hội thoại khỏi đĩa."""
delete_conversation(self._resolve_path(target))
def set_pinned(self, target: Any, pinned: bool) -> None:
"""Ghim/bỏ ghim một hội thoại để nó nằm trên đầu danh sách lịch sử."""
set_pinned(self._resolve_path(target), pinned)
@@ -29,6 +29,11 @@ class TaskRepository:
folder)."""
def __init__(self, directory: Optional[Path] = None) -> None:
"""``directory`` để None thì dùng thư mục task mặc định.
Hai đường import cho cùng một hằng số: gói có thể được nạp dưới tên đầy đủ
``cowork_local`` hoặc dưới dạng tương đối tuỳ cách chạy.
"""
if directory is None:
try:
from cowork_local.core.tasks import TASKS_DIR
@@ -39,6 +44,7 @@ class TaskRepository:
self._directory = directory
def list(self) -> List[Dict[str, Any]]:
"""Liệt kê mọi task trong thư mục."""
try:
from cowork_local.core.tasks import list_tasks
except ImportError:
@@ -46,6 +52,7 @@ class TaskRepository:
return list_tasks(self._directory)
def get(self, task_id: str) -> Optional[Dict[str, Any]]:
"""Đọc một task theo id; trả về ``None`` nếu không có."""
try:
from cowork_local.core.tasks import load_task
except ImportError:
@@ -53,6 +60,7 @@ class TaskRepository:
return load_task(task_id, self._directory)
def save(self, task: Dict[str, Any]) -> Path:
"""Ghi task xuống đĩa (ghi nguyên tử) và trả về đường dẫn file."""
try:
from cowork_local.core.tasks import save_task
except ImportError:
@@ -60,6 +68,7 @@ class TaskRepository:
return save_task(task, self._directory)
def create(self, title: str = "", **overrides: Any) -> Dict[str, Any]:
"""Tạo một task mới trong bộ nhớ theo mẫu mặc định; chưa ghi đĩa."""
try:
from cowork_local.core.tasks import new_task
except ImportError:
@@ -67,6 +76,7 @@ class TaskRepository:
return new_task(title, **overrides)
def duplicate(self, task: Dict[str, Any]) -> Dict[str, Any]:
"""Nhân bản một task (id mới, trạng thái và lịch sử chạy được đặt lại)."""
try:
from cowork_local.core.tasks import duplicate_task
except ImportError:
@@ -74,6 +84,7 @@ class TaskRepository:
return duplicate_task(task)
def delete(self, task_id: str) -> None:
"""Xoá hẳn một task khỏi đĩa."""
try:
from cowork_local.core.tasks import delete_task
except ImportError:
@@ -16,6 +16,9 @@ class WorkspaceRepository:
``tmp_path`` so nothing touches the user's real config folder)."""
def __init__(self, directory: Optional[Path] = None) -> None:
"""``directory`` để None thì dùng thư mục dự án mặc định; import muộn để không
kéo cấu hình vào lúc nạp module.
"""
if directory is not None:
self._directory = Path(directory)
else:
@@ -23,25 +26,31 @@ class WorkspaceRepository:
self._directory = PROJECTS_DIR
def list(self) -> List["Project"]:
"""Liệt kê mọi project trong thư mục."""
from cowork_local.core.projects import list_projects
return list_projects(self._directory)
def get(self, project_id: str) -> Optional["Project"]:
"""Đọc một project theo id; trả về ``None`` nếu không có."""
from cowork_local.core.projects import load_project
return load_project(project_id, self._directory)
def save(self, project: "Project") -> None:
"""Ghi project xuống đĩa (ghi nguyên tử)."""
from cowork_local.core.projects import save_project
save_project(project, self._directory)
def create(self, name: str, **kwargs) -> "Project":
"""Tạo project mới và ghi ngay xuống đĩa."""
from cowork_local.core.projects import new_project
return new_project(name, directory=self._directory, **kwargs)
def new(self, name: str, **kwargs) -> "Project":
"""Bí danh của :meth:`create` — giữ cho mã cũ gọi ``new()`` vẫn chạy."""
return self.create(name, **kwargs)
def delete(self, project_id: str) -> bool:
"""Xoá project; trả về ``True`` nếu có project để xoá."""
from cowork_local.core.projects import delete_project
return delete_project(project_id, self._directory)