Files
cowork-local/core/permissions.py
T
anhtnm1andClaude Opus 5 e29a0ccdbd 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>
2026-08-30 10:41:45 +09:00

66 lines
2.6 KiB
Python

"""Permission gate for the Code agent.
In ``auto`` mode every action is approved immediately. In ``confirm`` mode the
agent thread blocks on a threading event while the UI shows a preview dialog and
the user approves or rejects. Cancelling the task releases any pending wait.
"""
from __future__ import annotations
import threading
from typing import Any, Callable, Dict, Optional
RequestFn = Callable[[Dict[str, Any]], None]
class PermissionGate:
"""Cổng phê duyệt tool: chặn lượt chạy lại và chờ người dùng đồng ý.
Ba chế độ: 'auto' cho qua hết, 'confirm' hỏi trước mỗi lệnh có rủi ro, và
'deny' chặn thẳng. Dùng ``threading.Event`` để luồng nền đứng chờ trong khi
luồng giao diện hiện hộp thoại.
"""
def __init__(self, mode: str = "confirm", on_request: Optional[RequestFn] = None,
agent_role: str = ""):
"""``mode`` quyết định cách xử: hỏi, cho qua hết, hay chặn hết.
``on_request`` là hàm hiện hộp thoại; để None (không có giao diện) thì cổng
rơi về quyết định mặc định của ``mode`` thay vì treo mãi.
"""
self.mode = mode
self.on_request = on_request
self.agent_role = agent_role
self._event = threading.Event()
self._approved = False
def set_mode(self, mode: str) -> None:
"""Đổi chế độ phê duyệt giữa chừng."""
self.mode = mode
def request(self, action: Dict[str, Any]) -> bool:
"""Block (in confirm mode) until the action is approved or rejected."""
from . import audit_log
if self.mode == "auto":
audit_log.record("permission", str(action.get("name", "")), True,
"auto mode", agent_role=self.agent_role)
return True
self._approved = False
self._event.clear()
if self.on_request:
self.on_request(action)
self._event.wait()
audit_log.record("permission", str(action.get("name", "")), self._approved,
"user approved" if self._approved else "user rejected",
agent_role=self.agent_role)
return self._approved
def resolve(self, approved: bool) -> None:
"""Người dùng đã trả lời: ghi kết quả và đánh thức luồng đang chờ."""
self._approved = approved
self._event.set()
def cancel(self) -> None:
"""Unblock any pending request, treating it as rejected."""
self._approved = False
self._event.set()