Bốn công tắc của nhóm này (xác nhận lệnh, chặn mạng, bật lớp bảo mật agent, AI kiểm tra lệnh) dựng ra ở trạng thái setEnabled(False) và chỉ mở khi nhập đúng mật khẩu. Theo yêu cầu, bỏ hẳn bước đó: form luôn bật/tắt được. Gỡ ô nhập, nút Mở khoá, nhãn trạng thái khoá, _sandbox_unlock() và _sandbox_password_matches(); dọn 10 khoá i18n thành chết và 2 field trong tools/check_dialogs.py. Giữ nguyên agent_security.sandbox_pw ở config.py — yêu cầu chỉ nói tới màn hình, không nói tới tầng cấu hình. Khoá này vốn không phải rào bảo mật: docstring của _sandbox_unlock() đã tự ghi "khoá phía giao diện để chặn bấm nhầm ... KHÔNG phải cơ chế bảo mật thật". Rào thật nằm ở sandbox lúc chạy lệnh. Vùng này thuộc diện SECURITY.md yêu cầu Cowork Team soát thêm. 10 bài test cũ (SEC-20260907-01) chốt các đường không được mở khoá nay mất đối tượng kiểm, thay bằng 12 bài chốt hành vi mới: bốn công tắc sửa được ngay, không còn widget mật khẩu, kèm guardrail quét mã nguồn chặn khoá lại. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
141 lines
4.9 KiB
Python
141 lines
4.9 KiB
Python
"""Check the left-list + right-panel navigation in Settings and Task editor.
|
|
|
|
Both dialogs are navigated the same way, as the audit page asks: a list of the
|
|
real group boxes on the left, one panel shown at a time on the right. So the
|
|
test is that picking a row swaps the panel, that the rows match the groups, and
|
|
— since this is a rearrangement — that no input control went missing.
|
|
|
|
Run: python tools/check_dialogs.py
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
import os
|
|
import sys
|
|
|
|
sys.stdout.reconfigure(encoding="utf-8", errors="replace")
|
|
from pathlib import Path
|
|
|
|
REPO = Path(__file__).resolve().parent.parent
|
|
sys.path.insert(0, str(REPO.parent))
|
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
|
os.environ.setdefault("QT_QPA_PLATFORM", "offscreen")
|
|
|
|
from capture_screens import _apply_theme, _isolate_home, _load_fonts # noqa: E402
|
|
|
|
# Every field each dialog must still offer after the move.
|
|
SETTINGS_FIELDS = [
|
|
"language_combo", "theme_combo", "tray_chk", "notify_chk",
|
|
"provider_combo", "prov_base", "prov_key", "prov_model",
|
|
"sandbox_confirm",
|
|
"sandbox_block_network", "sec_enabled", "ai_check",
|
|
]
|
|
TASK_FIELDS = [
|
|
"title_edit", "desc_edit", "gen_desc_btn", "priority_combo", "status_combo",
|
|
"workspace_combo", "provider_combo", "model_combo", "skill_combo",
|
|
"sched_enabled", "run_at_edit", "files_list", "files_add_btn", "links_list",
|
|
"links_add_btn", "next_combo", "run_next_combo", "pass_output_chk",
|
|
"depends_list", "retry_spin", "timeout_spin", "approval_chk",
|
|
]
|
|
|
|
|
|
def check(name, dlg, app, expect_rows, fields):
|
|
fails = []
|
|
print(f"--- {name} ---")
|
|
idx, stack = dlg.section_list, dlg.section_stack
|
|
rows = [idx.item(i).text() for i in range(idx.count())]
|
|
print(f"muc : {rows}")
|
|
if len(rows) != expect_rows:
|
|
fails.append(f"{name}: cho {expect_rows} muc, thay {len(rows)}")
|
|
if idx.count() != stack.count():
|
|
fails.append(f"{name}: {idx.count()} muc nhung {stack.count()} panel")
|
|
|
|
# Picking a row must swap the panel — and each panel must hold something.
|
|
swapped, empty = 0, []
|
|
for i in range(idx.count()):
|
|
idx.setCurrentRow(i)
|
|
for _ in range(3):
|
|
app.processEvents()
|
|
if stack.currentIndex() == i:
|
|
swapped += 1
|
|
page = stack.widget(i).widget()
|
|
if not page.findChildren(type(page)):
|
|
empty.append(rows[i])
|
|
print(f"chon muc -> doi panel : {swapped}/{idx.count()}")
|
|
if swapped != idx.count():
|
|
fails.append(f"{name}: chon muc khong doi panel")
|
|
if empty:
|
|
fails.append(f"{name}: panel rong {empty}")
|
|
|
|
missing = [f for f in fields if getattr(dlg, f, None) is None]
|
|
print(f"field con nguyen : {len(fields) - len(missing)}/{len(fields)}")
|
|
if missing:
|
|
fails.append(f"{name}: mat field {missing}")
|
|
return fails
|
|
|
|
|
|
def main() -> int:
|
|
sandbox = _isolate_home()
|
|
from PySide6.QtWidgets import QApplication
|
|
|
|
app = QApplication([])
|
|
_load_fonts()
|
|
|
|
_apply_theme(app) # measure the styled widget, not a bare one
|
|
from cowork_local.config import AppConfig, CONFIG_DIR
|
|
assert str(sandbox) in str(CONFIG_DIR), f"isolation failed: {CONFIG_DIR}"
|
|
|
|
from cowork_local.i18n import set_language, tr
|
|
from cowork_local.state import AppContext
|
|
from cowork_local.ui.settings_dialog import SettingsDialog
|
|
from cowork_local.ui.task_editor_dialog import TaskEditorDialog
|
|
|
|
set_language("vi")
|
|
ctx = AppContext(AppConfig.load())
|
|
fails = []
|
|
|
|
s = SettingsDialog(ctx)
|
|
s.resize(900, 640)
|
|
s.show()
|
|
app.processEvents()
|
|
fails += check("Cai dat", s, app, 5, SETTINGS_FIELDS)
|
|
|
|
t = TaskEditorDialog(ctx=ctx)
|
|
t.resize(900, 640)
|
|
t.show()
|
|
app.processEvents()
|
|
fails += check("Task editor", t, app, 5, TASK_FIELDS)
|
|
|
|
# Both dialogs must be navigated the SAME way — that is the stated point.
|
|
same = (type(s.section_list) is type(t.section_list)
|
|
and type(s.section_stack) is type(t.section_stack))
|
|
print()
|
|
print(f"hai hop thoai cung kieu dieu huong: {same}")
|
|
if not same:
|
|
fails.append("hai hop thoai dieu huong khac kieu")
|
|
|
|
for lang in ("vi", "en", "ja"):
|
|
set_language(lang)
|
|
print(f" {lang}: general={tr('settings.group.general')!r} "
|
|
f"basic={tr('schedtask.g_basic')!r}")
|
|
set_language("vi")
|
|
|
|
print()
|
|
if fails:
|
|
print("*** LOI ***")
|
|
for x in fails:
|
|
print(" " + x)
|
|
return 1
|
|
print("KET QUA: hai hop thoai dung danh sach trai + panel phai, khong mat field")
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
_rc = main()
|
|
# Qt (WebEngine especially) crashes during interpreter teardown with
|
|
# 0xC0000409 AFTER the work is done, which would mask the real result —
|
|
# and check_probes_bite reads these exit codes to decide whether a probe
|
|
# caught its mutation. Leave immediately with the verdict instead.
|
|
sys.stdout.flush()
|
|
sys.stderr.flush()
|
|
os._exit(_rc)
|