refactor: vá 4 hồi quy, tách 4 file chạm trần LOC, docstring lên 100%
Hồi quy đã vá
-------------
F-12 Kéo–thả hoặc dán tệp vào ô chat ném NameError. R08 tách `_Input` sang
`chat_input_box.py` nhưng để `_paths_from_mime()` ở lại
`composer_widget.py`, nên hai hàm sự kiện Qt gọi một cái tên không tồn
tại. Bốn hàm dùng chung chuyển sang `composer_mime.py` — module thứ ba
là chỗ duy nhất không lặp lại được lỗi này. Đo lại: cả thả lẫn dán đều
gắn 1 tệp, khớp bản trước refactor.
F-01 Đổi provider thì bộ chọn model AI-Edit không làm gì. Hook cũ kiểm
`folder.ai_model_combo`, thuộc tính R08-T12 đã dời sang
`ai_panel.resolver`. Làm mới vô điều kiện, đúng như tab cũ: lần lấy đầu
tiên hỏng thì đổi provider chính là lúc phải thử lại.
F-07 Hàng chọn kỳ của Dashboard bị đẩy xuống dưới các thẻ số liệu. Hàng này
lọc CẢ BA thẻ con chứ không riêng biểu đồ, nên để nó nằm dưới là bắt
người dùng đọc con số trước khi thấy con số đó tính cho kỳ nào. Kèm
theo: `TokenUsageCardWidget` bị bỏ sót `setContentsMargins(0,0,0,0)`
mà hai thẻ con còn lại đã có, đẩy cả hàng thẻ lệch 9px.
`check_layout_geometry` nay khớp TỪNG BYTE với bản trước refactor.
F-11 Hai lớp khai trùng tên phương thức; Python giữ bản sau nên bản đầu là
mã chết. `co4e_tab.py::showEvent` bản đầu gọi `_narrow_guard.attach()`
và không bao giờ chạy.
Tách file (F-09)
----------------
Bốn file chạm trần 400 dòng, mỗi lần cắt ra một trách nhiệm thật:
graph_renderer.py -> graph_scene_builder.py + graph_export.py
co4e_workflow_service.py -> co4e_run_history.py
json_config_repository.py -> config_sections.py
agents_admin_tab.py -> shared/agent_kind_visuals.py
File cuối còn xoá 3 bản sao của hàm đã có trong `shared/formatters.py`,
giống hệt đến từng dòng — nay định dạng thời gian và avatar không lệch nhau
giữa các bảng Giám sát nữa.
Docstring
---------
41,6% -> 100% (3.478/3.478 định nghĩa production), kể cả module dormant và
phương thức dunder. Toàn bộ phần bổ sung viết bằng tiếng Việt; comment tiếng
Anh có sẵn giữ nguyên — dịch ngược là một đợt riêng.
Seam chưa nối dây (F-05)
------------------------
9 seam mang nhãn `SEAM · dựng <ngày>` kèm hai câu: được nối khi nào, và để
dormant thì hỏng gì. Ngày lấy từ lịch sử git, không phải hạn tự đặt. Gate O
đọc nhãn đó và nhắc khi quá 30 ngày.
859 test xanh · 4/4 cổng CASAN · 19/24 checker khớp từng byte bản cũ.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -28,6 +28,7 @@ _HISTORY_CAP = 500 # keep the most-recent N runs on disk
|
||||
|
||||
|
||||
def _now_str() -> str:
|
||||
"""Mốc thời gian hiện tại dạng 'YYYY-MM-DD HH:MM' cho lịch sử run."""
|
||||
from datetime import datetime
|
||||
return datetime.now().strftime("%Y-%m-%d %H:%M")
|
||||
|
||||
@@ -44,6 +45,11 @@ class RunHandle:
|
||||
def __init__(self, run_id: str, wf_id: str, name: str, total: int,
|
||||
plan_mode: bool, manual: bool, created_by: str = "", created_at: str = "",
|
||||
project_id: str = ""):
|
||||
"""Một lượt chạy workflow đang sống trong bộ nhớ.
|
||||
|
||||
``total`` âm bị kẹp về 0 — số bước không thể âm, và để lọt xuống thì thanh
|
||||
tiến độ vẽ ngược.
|
||||
"""
|
||||
self.id = run_id
|
||||
self.wf_id = wf_id
|
||||
self.name = name
|
||||
@@ -64,9 +70,11 @@ class RunHandle:
|
||||
|
||||
@property
|
||||
def running(self) -> bool:
|
||||
"""Lượt chạy này còn đang chạy hay không."""
|
||||
return self.status == "running"
|
||||
|
||||
def progress_text(self) -> str:
|
||||
"""Chuỗi tiến độ 'xong/tổng'; chưa biết tổng thì hiện trạng thái."""
|
||||
return f"{self.done}/{self.total}" if self.total else self.status
|
||||
|
||||
# ---- persistence ------------------------------------------------------
|
||||
@@ -87,6 +95,7 @@ class RunHandle:
|
||||
|
||||
@classmethod
|
||||
def from_record(cls, rec: dict) -> "RunHandle":
|
||||
"""Dựng lại một ``RunHandle`` từ bản ghi đọc trong lịch sử trên đĩa."""
|
||||
from .co4e import workflow_from_dict
|
||||
rec = dict(rec or {})
|
||||
h = cls(str(rec.get("id", "")), str(rec.get("wf_id", "")),
|
||||
@@ -109,10 +118,18 @@ class RunHandle:
|
||||
|
||||
|
||||
class Co4ERunManager(QObject):
|
||||
"""Quản lý vòng đời nhiều lượt chạy luồng Co4E cùng lúc.
|
||||
|
||||
Flow Status lọc theo project, nên hầu hết truy vấn ở đây chỉ tính run thuộc
|
||||
workspace ĐANG chọn — xem ``_belongs``.
|
||||
"""
|
||||
changed = Signal() # any run's status/progress changed → refresh views
|
||||
event = Signal(str, dict) # (run_id, ev) — node-level events, for mirroring
|
||||
|
||||
def __init__(self, ctx):
|
||||
"""Dựng bộ quản lý run và khôi phục lịch sử cũ ngay, để tab Flow Status có nội
|
||||
dung ngay khi mở chứ không trống cho tới lần chạy đầu tiên.
|
||||
"""
|
||||
super().__init__()
|
||||
self.ctx = ctx
|
||||
self._runs: Dict[str, RunHandle] = {}
|
||||
@@ -126,10 +143,12 @@ class Co4ERunManager(QObject):
|
||||
|
||||
# ---- persistence ------------------------------------------------------
|
||||
def _history_path(self) -> Path:
|
||||
"""Đường dẫn file lịch sử run."""
|
||||
from .co4e import CO4E_DIR
|
||||
return CO4E_DIR / "run_history.json"
|
||||
|
||||
def _load_history(self) -> None:
|
||||
"""Khôi phục lịch sử run từ đĩa lúc khởi động; file hỏng thì bỏ qua lặng lẽ."""
|
||||
path = self._history_path()
|
||||
try:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
@@ -149,6 +168,7 @@ class Co4ERunManager(QObject):
|
||||
self._seq = max_seq # avoid minting ids that collide with history
|
||||
|
||||
def _save_history(self) -> None:
|
||||
"""Ghi ``_HISTORY_CAP`` run gần nhất xuống đĩa."""
|
||||
path = self._history_path()
|
||||
runs = list(self._runs.values())[-_HISTORY_CAP:]
|
||||
payload = {"runs": [h.to_record() for h in runs]}
|
||||
@@ -163,6 +183,7 @@ class Co4ERunManager(QObject):
|
||||
|
||||
# ---- lifecycle --------------------------------------------------------
|
||||
def _next_id(self) -> str:
|
||||
"""Sinh id run kế tiếp dạng 'runN'."""
|
||||
self._seq += 1
|
||||
return f"run{self._seq}"
|
||||
|
||||
@@ -198,6 +219,7 @@ class Co4ERunManager(QObject):
|
||||
|
||||
run_label = handle.name
|
||||
def job(worker: AgentWorker):
|
||||
"""Chạy nền: thực thi luồng, chuyển tiếp sự kiện tiến độ và cờ huỷ."""
|
||||
return co4e_runner.run_workflow(
|
||||
ctx, nodes, edges, out_dir, worker.emit_event, worker.is_cancelled,
|
||||
plan_mode=plan_mode, skill_map=sk, only_nodes=only, seed_outputs=seed,
|
||||
@@ -215,6 +237,7 @@ class Co4ERunManager(QObject):
|
||||
|
||||
# ---- worker callbacks -------------------------------------------------
|
||||
def _on_event(self, run_id: str, ev: dict) -> None:
|
||||
"""Nhận sự kiện từ luồng đang chạy và cập nhật trạng thái/tiến độ của run."""
|
||||
handle = self._runs.get(run_id)
|
||||
if handle is not None and isinstance(ev, dict):
|
||||
t = ev.get("type")
|
||||
@@ -229,6 +252,10 @@ class Co4ERunManager(QObject):
|
||||
self.event.emit(run_id, ev)
|
||||
|
||||
def _on_finished(self, run_id: str) -> None:
|
||||
"""Job kết thúc mà không phát ``run_done``: chốt trạng thái về 'done'.
|
||||
|
||||
Lẽ ra không xảy ra, nhưng thiếu bước này thì run kẹt ở 'running' mãi.
|
||||
"""
|
||||
handle = self._runs.get(run_id)
|
||||
if handle is not None and handle.status == "running":
|
||||
# job returned without a run_done event (shouldn't happen) — settle it
|
||||
@@ -236,6 +263,7 @@ class Co4ERunManager(QObject):
|
||||
self.changed.emit()
|
||||
|
||||
def _on_failed(self, run_id: str, err: str) -> None:
|
||||
"""Job ném lỗi: ghi lỗi vào bản ghi run và báo ra ngoài."""
|
||||
handle = self._runs.get(run_id)
|
||||
if handle is not None:
|
||||
handle.status = "error"
|
||||
@@ -245,6 +273,7 @@ class Co4ERunManager(QObject):
|
||||
|
||||
# ---- control ----------------------------------------------------------
|
||||
def stop(self, run_id: str) -> None:
|
||||
"""Yêu cầu dừng một run đang chạy."""
|
||||
handle = self._runs.get(run_id)
|
||||
if handle is not None and handle.worker is not None and handle.running:
|
||||
handle.worker.request_stop()
|
||||
@@ -253,6 +282,7 @@ class Co4ERunManager(QObject):
|
||||
|
||||
def stop_all(self) -> None:
|
||||
# Only the CURRENT workspace's runs (Flow Status is per-project).
|
||||
"""Dừng mọi run của workspace đang chọn."""
|
||||
for run_id in [r for r, h in self._runs.items() if self._belongs(h)]:
|
||||
self.stop(run_id)
|
||||
|
||||
@@ -269,6 +299,7 @@ class Co4ERunManager(QObject):
|
||||
self.changed.emit()
|
||||
|
||||
def remove(self, run_id: str) -> None:
|
||||
"""Xoá một run khỏi lịch sử; đang chạy thì dừng trước."""
|
||||
handle = self._runs.get(run_id)
|
||||
if handle is not None and handle.running:
|
||||
self.stop(run_id)
|
||||
@@ -277,6 +308,7 @@ class Co4ERunManager(QObject):
|
||||
|
||||
def clear_finished(self) -> None:
|
||||
# Only clear finished runs of the CURRENT workspace.
|
||||
"""Xoá mọi run đã kết thúc của workspace đang chọn, giữ nguyên run đang chạy."""
|
||||
for run_id in [r for r, h in self._runs.items() if not h.running and self._belongs(h)]:
|
||||
self._runs.pop(run_id, None)
|
||||
self.changed.emit()
|
||||
@@ -295,9 +327,11 @@ class Co4ERunManager(QObject):
|
||||
return list(self._runs.values())
|
||||
|
||||
def get(self, run_id: str) -> Optional[RunHandle]:
|
||||
"""Bản ghi của một run theo id; ``None`` nếu không có."""
|
||||
return self._runs.get(run_id)
|
||||
|
||||
def active_count(self) -> int:
|
||||
"""Số run đang chạy của workspace đang chọn."""
|
||||
return sum(1 for h in self._runs.values() if h.running and self._belongs(h))
|
||||
|
||||
def set_current_project(self, project_id: str) -> None:
|
||||
@@ -320,6 +354,11 @@ class Co4ERunManager(QObject):
|
||||
# tab), not in the config/install folder. One subfolder per flow keeps
|
||||
# runs tidy. Falls back to the global Cowork output dir when no workspace
|
||||
# is selected.
|
||||
"""Thư mục ghi kết quả của một luồng, tạo sẵn nếu chưa có.
|
||||
|
||||
Ưu tiên thư mục của workspace đang chọn để file rơi đúng chỗ người dùng làm
|
||||
việc (màn Thư mục), không rơi vào thư mục cài đặt.
|
||||
"""
|
||||
from .co4e import slugify
|
||||
base = self._output_root
|
||||
if base is None:
|
||||
|
||||
Reference in New Issue
Block a user