Resolve 3 file conflict:
- docs/refactor/Refactoring_Checklist.md: giữ nội dung incoming (phía HEAD
trống ở đoạn conflict).
- tests/integration/test_routing_surfaces.py: khôi phục từ incoming (bị mất
ở merge trước đó), điều chỉnh lại cho khớp API hiện tại của
RoutingApplicationService (resolve()/RouteEvaluation/mode_resolver thay vì
route_turn()/mode_reader cũ), bỏ 2 test pin một lớp RoutingDecision không
còn tồn tại trên nhánh này.
- ui/folder_tab.py: chấp nhận xoá (deleted by them) — đã được thay thế hoàn
toàn bởi presentation/folder/* (R08-T12), không còn nơi nào import module
cũ.
Sửa thêm 2 chỗ lệch API bị auto-merge không báo conflict (phát hiện khi chạy
lại test):
- presentation/folder/ai_edit_model_resolver.py + ai_file_editor_dialog.py:
AiEditModelResolver.apply_routing() gọi route_turn() đã bị xoá khỏi
RoutingApplicationService — chuyển sang build_routing_application_service()
.resolve(RoutingRequest(...)) giống chat_panel.py/co4e_chat.py; sửa luôn
chữ ký _confirm_routing_switch nhận thêm timeout cho khớp contract confirm
mới.
- config.py: import JsonConfigRepository ở đầu file gây circular import với
core/tasks.py (cần CONFIG_DIR) qua chuỗi mới
infrastructure/persistence/json/task_repository_impl.py (R07). Dời import
xuống ngay trước chỗ dùng đầu tiên.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Replaces BaoCao_TeamHoa_R05_R06.md and BaoCao_TeamHoa_R07_R08.md with a
single BaoCao_TeamHoa_R05_R08.md covering all 4 EPICs (19/19 tasks) in
Team Hoa's scope, and updates the checklist's report links accordingly.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Team Hoa, EPIC R08 (UI/Application Separation) - Team Hoa scope only
(R08-T11 -> T14; R08-T01->T10 belong to Team Duy/Team Nam).
- R08-T11: ui/schedule_task_tab.py (795 lines) -> presentation/scheduling/
{kanban_board_widget,calendar_view_widget,ai_task_creator_dialog,
ai_task_import_dialog,run_history_dialog}.py + schedule_task_tab.py
shell. Kanban CRUD/drag-drop now goes through
application/scheduling/task_application_service.py (R07-T04) instead of
~30 lines of inline if/elif per drag target.
- R08-T12: ui/folder_tab.py (1587 lines, the largest of the four) ->
presentation/folder/{workspace_file_tree,document_preview_manager,
code_editor,office_document_renderer,ai_file_editor_dialog,
ai_edit_model_resolver,ai_edit_pipeline}.py + folder_tab.py shell.
Closes the R06-T05 loop: FileWorkspaceService existed since R06 with
zero production call sites (confirmed by grep); every plain-text write
(save/create/write_content) now goes through it, gaining path
containment and a Python-syntax warning the original code never had.
Pure helpers (_read_text, _is_probably_text, _pptx_available,
_split_code_block, _parse_ai_output) moved to
application/workspaces/{file_preview_helpers,ai_edit_output}.py.
- R08-T13: ui/dashboard_tab.py (437 lines) -> presentation/dashboard/
{token_usage_card_widget,usage_chart_widget,habits_widget}.py +
dashboard_tab.py shell, backed by a new
application/monitoring/dashboard_query_service.py (pricing/period/
summary queries the three widgets used to each recompute separately).
Directory-ownership note left in the checklist for Team Nam.
- R08-T14: ui/structure_graph_view.py (1035 lines) ->
presentation/graph/{graph_scene_items,graph_renderer,
graph_messages_view,graph_qa_widget}.py + structure_graph_view.py
shell. Extraction helpers (_pdf_to_markdown, _extract_file_contents)
moved to application/workspaces/graph_index_service.py (pure Python).
Renderer and Q&A panel talk only through signals
(node_selected/graph_rendered/raw_json_ready/project_changed) - neither
imports the other.
- presentation/shared/web_engine_support.py: HAS_WEB_ENGINE, previously
duplicated (folder_tab imported it FROM structure_graph_view.py) - now
one shared flag instead of one screen importing another screen's module.
All four old ui/*.py files deleted; app.py and ui/workspace_tab.py updated
to the new import paths (each god-file only had 1-2 real construction
sites, so import sites were updated directly rather than kept as a
strangler-fig shim - unlike core/tools.py at R05, which had dozens).
pytest: 377 pass (+94 vs the R07 baseline of 328; same 4 pre-existing
failures as the R05/R06 baseline, unrelated to this work).
scripts/check_imports.py: PASS. python -c "import cowork_local.app": OK.
Every new file < 400 lines (largest: graph_renderer.py, 391).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Team Hoa, EPIC R07 (Scheduling & Workflow Runtime) - Team Hoa scope only
(R07-T01 -> T05; R07-T06 Co4EWorkflowService is Team Nam's).
- R07-T01: infrastructure/persistence/json/task_repository_impl.py wraps
core/tasks.py's CRUD; core/tasks.py::save_task now writes through
atomic_write.write_json (same durability fix as R06-T02, save_task was
still doing a plain write_text).
- R07-T02: domain/tasks/schedule_calculator.py::ScheduleCalculator - the
cron/interval/daily/weekly/monthly due-time math extracted from
core/tasks.py, pure Python with is_holiday/make_cron injected so domain/
never imports core (ADR-001 I2). core/tasks.py keeps its old function
names as thin wrappers so every existing caller is unchanged. This was
previously untested; now has its own unit suite.
- R07-T03: infrastructure/qt/qt_scheduler_clock.py::QtSchedulerClock wraps
the QTimer TaskScheduler used to own directly, injected via a new
`clock=` constructor param (defaults to a real one). Originally planned
at platform/qt/... ; moved after confirming that name shadows the
stdlib platform module (used by core/windows_sandbox_vm.py,
core/appcontainer_sandbox.py) whenever the repo root is on sys.path.
tests/fakes/fake_clock.py lets scheduler dispatch be tested tick-by-tick
with no Qt event loop.
- R07-T04: application/scheduling/task_application_service.py centralizes
run_now/duplicate/pause/delete/bulk_delete and the Kanban drag-drop
business rules (move_to_status), currently only reachable by driving
the real ui/schedule_task_tab.py widget.
- R07-T05: application/scheduling/ai_task_planner_service.py wraps
core/ai_task_planner.py::plan_tasks and core/task_import.py::import_tasks
as a seam, plus the attachment-stamping step that used to only exist
inside the AI-create dialog's worker closure.
pytest: 328 pass (same 4 pre-existing failures as the R05/R06 baseline,
unrelated to this work - see docs/refactor/BaoCao_TeamHoa_R05_R06.md).
scripts/check_imports.py: PASS.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Không xung đột — Lâm động vào ui/co4e_tab.py và presentation/co4e/,
tôi động vào ui/settings_dialog.py và presentation/settings/. Đúng như
quy tắc phân chia sở hữu đặt ra từ đầu.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Bốn mục trong Cài đặt tách thành widget riêng dưới presentation/settings/:
general_settings_widget.py ngôn ngữ, giao diện, khay, gợi ý
provider_settings_widget.py provider, base URL, key, model + 2 nút nền
parameter_settings_widget.py đính kèm, cấu trúc, giới hạn sandbox
routing_settings_widget.py Auto Model Routing
Mỗi widget tự dựng control, tự nạp giá trị, tự có apply_to(data). Dialog chỉ
còn lắp ráp và gọi apply_to lúc lưu — _save từ 34 dòng xuống còn phần khung.
Làm lưới an toàn trước khi bóc: tests/ui/test_settings_dialog_dac_ta.py, 7
bài đặc tả hành vi hiện tại (mục nào có mặt, nạp đúng giá trị gì, lưu ghi vào
đúng ô nào, đổi % sang phân lẻ, xoá cache sau lưu). Bóc xong cả 7 vẫn xanh,
và trong lúc bóc chúng đã đỏ đúng hai lần ở chỗ đáng đỏ.
Đây là repo chưa từng có test Qt nào — thêm tests/ui/conftest.py dựng
QApplication offscreen. Offscreen là bắt buộc chứ không phải cho nhanh: máy
dev là máy làm việc thật, test bật cửa sổ lên là nó nhảy ra che màn hình.
Dọn kèm:
* bỏ vòng "dựng vào layout rồi lại gỡ ra" của mục Chung, cùng widget cao 0px
làm mốc cuộn — không cần nữa khi mục đó tự là một widget
* bỏ _select_combo, _secret, _model_combo, _with_load và 4 hàm provider khác
đã chuyển vào widget (127 dòng)
* bỏ 5 import chết theo (Dict, QSizePolicy, PROVIDER_LABELS, SegmentedControl,
LANGUAGES)
Giữ cầu tương thích: self.routing_*, self.prov_*, self.attach_* … thành
property trỏ vào widget con, vì 5 checker trong tools/ đọc thẳng tên cũ. Bỏ
được khi tools/ chuyển sang đọc self._provider_page.
Hai điều KHÔNG làm, ghi lại để khỏi tưởng là quên:
1. Plan ghi 4 widget và có tên `connector`. Thực tế UI connector đã dời khỏi
Cài đặt từ trước (ghi chú ở settings_dialog.py:180 bản cũ), nên số mục thật
là 5, không phải 4, và không có mục nào tên connector. Bốn mục bóc ra là 4
mục có thật; mục Bảo mật sandbox để nguyên trong dialog lần này.
2. Còn ~108 dòng chết của MS365 (_refresh_ms365_status, _ms365_sign_in,
_show_ms365_device_code, _ms365_sign_out): đọc self.ms365_status,
self.ms365_signin_btn, self.ms365_signout_btn — ba thuộc tính KHÔNG BAO GIỜ
được gán, và không hàm nào có người gọi. Gọi vào là AttributeError. Chưa
xoá vì đó là quyết định của anh Nam, không phải việc kèm theo của T07.
437 test xanh. check_dialogs, check_no_hscroll, check_design_parity đều qua.
Kèm docs/refactor/tin-gui-team-hoa.md — tin báo Hoa về platform/ -> adapters/
và bản vá Windows của AtomicJsonFile.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
conversation application service, AtomicJsonFile fix) vào sau khi tách 6
widget UI Co4E (N3)
Đã kiểm trước khi merge: ui/co4e_tab.py và ui/routing_toggle.py đều bị 2
bên cùng đụng, nhưng ở vùng dòng khác nhau hoàn toàn (bên kia sửa
_apply_co4e_routing/RoutingToggle cho R03-T05, N3 chỉ đụng phần dựng
sidebar/canvas/chat) — không có xung đột logic thật.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Lane N3 (Co4E Studio) — dùng bộ workflow refactor-god-file, mỗi bước có
characterization test trước khi tách, hậu kiểm ranh giới tầng sau mỗi bước:
- skills_list_panel.py / agent_list_panel.py — 2 khu vực sidebar
- co4e_canvas_widget.py + canvas_items.py + canvas_interaction_mixin.py —
Co4ECanvas tách 3 file (vượt 400 dòng nếu đứng một mình)
- node_property_panel.py + node_property_actions_mixin.py +
step_config_section.py — StepConfigPanel, cùng lý do
- co4e_run_control_widget.py — RunsPagePanel (trang Flow Status)
- co4e_chat_view.py — ChatPanel + _ChatInput + helper autocomplete
- palette_list.py — _PaletteList dời khỏi ui/co4e_tab.py, hết import ngược
presentation -> ui (agent/skills panel giờ import top-level)
ui/co4e_tab.py giảm 2089 -> 1878 dòng, chỉ còn phần wiring + business logic
(Co4ERunManager/AgentWorker chưa đổi — nằm ngoài phạm vi này, xem docstring
presentation/co4e/co4e_tab.py). ui/co4e_canvas.py và ui/co4e_config_panel.py
còn lại là compat shim re-export, không đổi API cho bên gọi.
Thêm tests/test_co4e_integration.py — dựng thật Co4ETab qua build_co4e_tab(),
lái luồng qua nhiều panel trong cùng instance (thêm node, mở/gập chat, chuyển
trang Flow Status rồi quay lại không mất state canvas) — bắt lỗi wiring
xuyên-panel mà characterization test từng panel riêng không thấy được.
Đã xác minh: pytest 348 passed/1 skipped, tools/check_co4e.py sạch, không
file nào >400 dòng, domain/application không import PySide6, và so pixel
before/after (git worktree tại HEAD cũ) ra 0/1.125.000 pixel khác biệt.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Nam chốt: không chờ Delta merge vào main, lấy sớm để va chạm nhỏ và sửa
ngay, thay vì dồn một cục lúc cả hai cùng lên main.
R04 chứa trọn R01 và R03 nên một lần merge là đủ cả ba: 96 file, +8260
dòng. Xung đột chỉ 5 file, đều là __init__.py add/add — hai team cùng
dựng khung thư mục nên đụng docstring. Giữ docstring của Gamma (nói rõ
ràng buộc "không import PySide6"), giữ mọi phần code của Delta.
Riêng tests/fakes/__init__.py: bỏ hai dòng import háo hức của Delta
(fake_provider, fake_tool_executor). fake_provider dùng
`from providers.base import ...` — import tuyệt đối, chỉ chạy được khi
cwd là gốc repo — nên nó làm đứt bài test "dùng fake mà không nạp config
thật". Không ai import ở cấp package; test của Delta gọi thẳng module
nên bỏ đi không ảnh hưởng họ. Đã ghi lý do vào docstring của gói.
Delta cũng xoá preview-desktop và "requirements (cloud copy).txt".
430 test xanh sau merge.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
R04-T03 — the turn lifecycle, extracted from `core/chat_agent.py::run_cowork`
into `application/conversations/`. The 260-line body mixed the lifecycle (step
budget, cancel checks, guard -> preview -> gate -> execute ordering, sandbox
tidy-up) with the machinery doing each step, and reaching any of it meant
standing up a Qt widget and a worker thread. It is now a plain object driven
through two Protocols and six callables (`turn_runtime.py`), with the concrete
`core/*` wiring confined to `core_runtime_adapter.py` — the same shape R03 used
for routing. Faithful port, not an improvement pass: where the original had a
quirk (the step-ceiling note only merges into the answer when the last message
is the assistant's) the quirk is preserved and commented.
R04-T04 — `ui/cowork_tab.py::build_job` no longer calls run_cowork. It captures
the widget's state at submit time, builds the request via the new
`cowork_turn_request.py` and executes it. `execute(..., messages=...)` hands the
widget's own list over because `_reattach_running_turn` replays from it WHILE
the worker appends and `_finalize_turn` slices it afterwards — a private list
would break both silently.
R04-T05 — `core/task_executors.py`'s cowork branch shares the same engine. All
five unattended-run behaviours stay put (plan reminder, history_ready, History
autosave per assistant message, timeout notice, plan_incomplete_reason), and
`_unattended_prompt` now expresses the load-bearing prefix order in one
readable call instead of three successive rebindings.
Verification: 74 new tests (364 passed, 1 skipped overall; check_imports PASS).
The two that matter most:
- `test_conversation_service_parity.py` runs the same scripted turn through
run_cowork AND the service and compares the event stream, the resulting
conversation and the advertised tool list across 7 scenarios;
- `test_task_executor_turn.py` was written BEFORE the migration and passed 8/8
against the old code, then unchanged against the new.
Known: `ui/cowork_tab.py` (416 -> 455) and `core/task_executors.py` (476 -> 524)
stay above the 400-LOC limit. Both were already over it before this change;
bringing them under needs the R08 / R07 decompositions.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Mirrors docs/refactor/BaoCao_TeamDuy_R01_R03_R04.md's structure: per-EPIC
results, test evidence, the two real bugs found and fixed, secondary
improvements, open items needing another team's sign-off, untested scope,
and what's next.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
EPIC R03 (Team Duy) — Model Providers & Routing. All six tasks done.
R03-T02 — Provider catalogue
domain/models/provider_descriptor.py ProviderDescriptor (frozen), WireProtocol, AuthKind
infrastructure/providers/provider_registry.py
thread-safe registry: id/alias lookup, dynamic
lookup by model id, adapter selection by protocol
providers/factory.py drops its own _REGISTRY table and delegates to the
registry, still raising ProviderError for callers
R03-T03 — RoutingApplicationService (pure Python, 4 modes)
application/model_routing/routing_models.py
RoutingMode (off/auto/manual/fallback),
RoutingRequest (immutable snapshot), RouteEvaluation,
RoutingOutcome
application/model_routing/routing_application_service.py
the single decision flow, reached through two narrow
ports plus a caller-supplied confirm callback, so no
Qt import is needed
application/model_routing/core_routing_adapter.py
binds the ports to core/routing and AppContext
Fallback is a new resilience mode: keep the selected model while it can serve the turn,
re-route only when it cannot. Wired end to end through config.py, state.py,
ui/routing_toggle.py and i18n.py (EN/JA/VI).
R03-T04 / T05 — Remove the duplicated routing flow
ui/chat_panel.py (#L638), ui/co4e_tab.py, ui/folder_tab.py each drop ~35 lines of copied
logic and call the shared service; the widgets now only build a RoutingRequest, host the
Manual-mode modal and render the outcome.
R03-T06 — Token usage as an event
infrastructure/telemetry/usage_sink.py UsageEvent + UsageEventSink protocol, with tracker,
in-memory and composite sinks
providers/openai_compat.py, providers/anthropic.py
publish a UsageEvent instead of writing to the
usage tracker themselves
core/usage_tracker.py adds current_context() so a sink can borrow and
restore a thread's attribution
R03-T01 — Contract tests
tests/contracts/test_providers.py parametrises over every provider in the registry: chat()
signature, canonical assistant message, normalised tool calls, response closed, tool schema
translation, ProviderError, list_models/test_connection, one UsageEvent per turn.
Test infrastructure fix (required to verify any of the above): tests/conftest.py used to put
the repository's PARENT directory on sys.path, so `import cowork_local.*` resolved against
whichever sibling folder happened to carry that name — on a dev machine, an unrelated older
checkout. The suite reported green while exercising different code. The conftest now binds
this checkout to the cowork_local name in sys.modules.
Verification
pytest tests/ 236 passed in ~1.8s (102 before this change)
scripts/check_imports.py PASS, 0 forbidden imports in domain/ and application/
new production files largest is 288 lines, all under the 400 LOC ceiling
new tests 134 (50 contract, 70 unit, 14 integration), all offline
scripts/run_quality_gate.py does not exist yet (R10-T02), so DoD item 7 was covered by
check_imports.py plus the full suite.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Mô tả hệ thống ĐANG CHẠY, không phải hệ thống mong muốn. Mọi khẳng định chỉ
tới file:dòng cụ thể, và mỗi tham chiếu đã được kiểm bằng script: mở đúng file,
đọc đúng dòng, đối chiếu nội dung có khớp điều đang nói không. Lần kiểm đầu bắt
được 3 tham chiếu thiếu tiền tố core/ và 2 số dòng lệch — dòng 249 là "No-op
for any other tool", câu về bộ phân loại luôn bật nằm ở 250.
Bốn điểm đáng chú ý trong tài liệu:
- Đây KHÔNG phải rào chắn an ninh. Chính agent_security.py nói vậy ở đầu
file, và hệ quả là mọi tầng AI đều mở khi hỏng. Ai đọc để đánh giá rủi ro
phải hiểu đúng chỗ này.
- Phân biệt quy tắc xác định và quy tắc do AI phán. Tắt hết công tắc trong
màn Cài đặt thì VẪN còn bộ phân loại mẫu và sandbox — đây là điểm dễ hiểu
nhầm nhất, vì mấy công tắc đó chỉ tắt phần AI.
- Trạng thái thứ ba: hỏi người dùng. Hệ thống đã có (chat_panel.py:1312) mà
chưa gọi tên; tool_policy.py gộp thành ALLOW/DENY/ASK.
- Mục 8 liệt kê 4 chỗ đã biết là yếu, để người sau khỏi tưởng đã kín: mở khi
hỏng, bí mật vẫn đi trong bộ nhớ (hệ quả của đường A), bộ luật OneDrive
không ký số, và ASK chưa nối được vào Co4E.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R06 (Team Hoa) - workspace/filesystem isolation, no cross-project
mutable state.
R06-T01 domain/workspaces/workspace_session.py
WorkspaceSession - project_id/workspace_root/sandbox_dir/allowed_paths
frozen snapshot + is_allowed(path), same "capture once at submit time"
shape as R04's ConversationExecutionRequest.
R06-T02 infrastructure/persistence/json/{atomic_write,workspace_repository_impl,conversation_repository_impl}.py
Real bug fixed: core/projects.py::save_project and core/history.py's
save_conversation/rename_conversation/set_pinned did a plain
path.write_text(json.dumps(...)) - two syscalls, no atomicity. A crash
between them leaves a half-written file that load_project/load_conversation
then silently treat as "missing". All four now write through
atomic_write.write_json (temp file + os.replace). WorkspaceRepository/
ConversationRepository are thin object-shaped facades over the same
(now-atomic) functions, for future application-layer callers.
NOTE: atomic_write.py is deliberately NOT named atomic_json_file.py -
R02-T01 (Team Nam) claims that filename for the same purpose app-wide;
see the checklist for the consolidation TODO.
R06-T03 infrastructure/filesystem/execution_workspace.py
ExecutionWorkspace names the output_dir/scratch_dir split that already
exists (core/chat_agent.py's flat workspace_root/.scratch) - does not
move anything.
R06-T04 ui/chat_panel.py
The actual race: ChatPanel._persist_session (saves a BACKGROUND turn's
conversation) resolved its save directory via a live
self.ctx.config.history_dir() read at save time. ui/workspace_tab.py::
_load_current mutates that same config field on every project switch, so
a turn still running when the user switched projects got saved into the
NEW project's history folder. Fixed by adding "home_history_dir" to the
per-turn ctx dict (same "home_*" snapshot convention already used for
session id/messages/title), captured at submit time. Verified with a real
offscreen-Qt test, not just a unit double:
tests/integration/test_history_dir_race.py.
R06-T05 application/workspaces/file_workspace_service.py
FileWorkspaceService - the File Explorer / AI Editor entry point for the
same safe read/write/edit operations the agent tool loop has, by calling
core/tools.py::execute_tool directly (same dispatch, same ToolContext
containment, same audit log) rather than reimplementing any of it.
New tests: tests/unit/test_workspace_session.py,
test_atomic_write_and_repositories.py, test_execution_workspace.py,
test_file_workspace_service.py, tests/integration/test_history_dir_race.py
(29 new tests, incl. 2 real offscreen-Qt integration tests).
Suite: 283 passed, 4 pre-existing failures unrelated to R05/R06 (see
checklist). check_imports: PASS. All new files < 400 LOC.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Đổi mô hình: không còn nhánh riêng mỗi người, cả ba cùng đẩy vào
gamma/refactor. Ba "nhánh" thành ba "làn" — vẫn chia việc như cũ, nhưng ranh
giới file bây giờ là thứ DUY NHẤT giữ ba người không giẫm chân, vì không còn
nhánh riêng làm vùng đệm.
Thêm quy ước số 4 cho nhánh chung, xếp vào nhóm bắt buộc: pull --rebase trước
mỗi lần đẩy; commit nhỏ, đẩy trong ngày; không bao giờ đẩy thứ làm
pytest tests -q đỏ, vì nhánh hỏng là hai người kia đứng hình.
Phần nghiệm thu đổi theo: trước đây so file giữa ba nhánh, giờ không còn ba
nhánh để so. Thay bằng git log --name-only --pretty=%an trên gamma/refactor —
không file nào được xuất hiện dưới hai tên khác nhau.
Hai quyết định đã chốt, ghi vào GammaTeam_decisions.md:
1. api_key: đường A — ConfigRepository ghép key từ SecretStore vào dict, 5
nơi đọc không đổi dòng nào, không cần báo Duy và Hoa.
2. 24 checker UI: đường A — ai dời file thì sửa checker ngay trong commit
đó, kèm ràng buộc phải nói rõ sửa gì và chạy check_probes_bite.py sau.
Không đưa vào CI sprint này vì chúng dựng MainWindow thật.
Baseline trong tài liệu cập nhật 90 -> 102 test.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R05 (Team Hoa) - one security/approval path for every tool call.
R05-T01 domain/tools/{tool_descriptor,tool_registry}.py
ToolCapability (READ/WRITE/EXECUTE/NETWORK, composable) + ToolDescriptor +
ToolRegistry, replacing three independently-maintained gating lists
(core/tools.py::WRITE_TOOLS, code_agent.py's WRITE_TOOLS|MS365_WRITE_TOOLS,
chat_agent.py's literal ("run_command","install_package") tuple) with one
capability lookup.
R05-T02 infrastructure/filesystem/{file_tools,command_tools,fetch_tools,tool_context}.py
core/tools.py's execute_tool if/elif chain split into per-concern modules.
core/tools.py is now a strangler-fig shim: re-exports ToolContext/ToolError,
dispatches through a {name: handler} dict built from the split modules.
core/tools.py: 566 -> 291 lines.
R05-T03 application/conversations/tool_policy_gateway.py
ToolPolicyGateway.allow(name, gate, payload) - capability-driven ALLOW vs
ask-the-gate decision. Wired into both chat_agent.py::run_cowork and
code_agent.py::run_code, replacing their separate hand-rolled checks.
Verified equivalent to the old hardcoded sets by test.
R05-T04 (behavior change, not just refactor)
MCP/connector tools (core/mcp_client.py, core/ext_connectors.py) reached
chat_agent.py via extra_executor(name, args) with NO permission check at
all. They are now tagged with a conservative default capability
(WRITE|EXECUTE|NETWORK - no MCP tool self-declares risk) and routed through
the SAME ToolPolicyGateway as built-ins. When "confirm before running
commands" is on, MCP/connector calls now prompt like run_command already
did - a real gap closed, and a user-visible change worth calling out.
R05-T05 infrastructure/mcp/mcp_source_manager.py
McpToolSourceManager extracts the connection cache/lock/start-or-skip
lifecycle out of state.py::AppContext (_mcp_connections/_conn_lock) into a
standalone, directly-testable class. AppContext.build_mcp_tools and
_ms365_builtin_connection now call ensure()/stop(); _ext_connections
(unified Connectors) is out of scope for this task and keeps its own lock.
New tests: tests/unit/test_tool_registry_and_policy.py,
test_code_agent_tool_policy.py, test_cowork_extra_tool_policy.py,
test_mcp_source_manager.py (26 new tests).
Suite: 254 passed, 4 pre-existing failures unrelated to R05 (2 EPIC R02
config-security, 2 environment-dependent routing tests - see checklist).
check_imports: PASS. All new files < 400 LOC.
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
N3 (Co4E) cần gọi tool nhưng Team Hoa chưa bắt đầu. Ba đường: N3 ngồi đợi
(trái nguyên tắc không team nào chặn team nào), N3 tự phỏng đoán (không ai
soi, chắc chắn phải sửa), hoặc viết một bản đề xuất để Hoa duyệt. Chọn cái
thứ ba.
Ranh giới giữ đúng sơ đồ phân hệ trong plan.md: domain/security/ là của
Gamma, application/conversations/tool_policy_gateway.py là của Hoa. Nên Gamma
định nghĩa hình dạng, Hoa cài đặt. Không đụng file nào của họ.
Hình dạng bám vào code đang chạy: SecurityVerdict (allowed/reason/layer) và
hộp thoại xin phép ở chat_panel.py:1312. Khác biệt duy nhất là gộp thành một
câu trả lời ba trạng thái ALLOW/DENY/ASK, thay vì bắt chỗ gọi tự nhớ hỏi hai
nơi.
Hai ràng buộc đưa vào có chủ đích, mỗi cái một test:
- DENY và ASK bắt buộc có reason, ném lỗi ngay lúc dựng. Người dùng cần
biết vì sao bị chặn và audit_log cần ghi lại.
- ASK không phải allowed. Đây là bẫy dễ mắc nhất: coi ASK như ALLOW thì
tool chạy trước khi có ai đồng ý.
Kèm FakeToolPolicyGateway lập trình được theo tên tool hoặc theo hàm, có ghi
lại đã hỏi những gì — test khẳng định được "có hỏi cổng không", không chỉ
"kết quả đúng không".
docs/refactor/GammaTeam_decisions.md thêm quyết định 3, kèm nguyên văn tin
nhắn cần gửi Hoa và ô đánh dấu đã gửi / đã xác nhận.
102 test xanh (96 + 6 mới). CASAN Check 1 sạch. domain/ và application/ có 0
import PySide6 — kiểm bằng AST, vì grep đếm ra 4 mà cả 4 là chữ "PySide6"
nằm trong chính docstring cảnh báo. Check 3 của Team Duy nên phân tích cú
pháp chứ đừng grep.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Ba khối, mỗi người một khối: cột trái là thứ phải có trong tay mới làm được kèm
nguồn, cột phải là thứ bắt buộc giao ra kèm người nhận. Nhãn 'có rồi' đánh dấu
những gì mục chung đã giao xong hôm nay (SecretStore, ConfigRepository, fake,
script CASAN).
Kèm bảng output bắt buộc với cả ba mỗi PR, mỗi dòng có lệnh tự kiểm.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Sáu việc trong "mục chung" của bản phân công, làm trước khi ba nhánh tính năng
tách ra.
1. Khung 5 tầng theo đúng đường dẫn plan.md: domain/ application/
infrastructure/ presentation/ platform/ + tests/fakes/ — 38 __init__.py.
Trước đó là 0 file, mà mọi task của cả ba người đều ghi vào đây.
Đã kiểm platform/ không che khuất module platform của stdlib.
2. Hợp đồng SecretStore và ConfigRepository (Protocol, chưa cài đặt) + fake
chạy trong bộ nhớ. Danh sách thuộc tính không bịa: đếm 156 lời gọi
ctx.config.* trong 29 file rồi lấy những cái dùng thật, xếp theo số lần.
Cố ý bỏ config.data (36 lời gọi, nhiều nhất) — bê dict thô sang kiến trúc
mới là bê nguyên vấn đề cũ.
3. tests/test_contracts.py — bài nghiệm thu, không phải test cho vui. Bài
chính chạy tiến trình riêng và khẳng định dùng fake KHÔNG kéo theo
cowork_local.config lẫn PySide6; đó là điều kiện để N2 và N3 code ngay hôm
nay thay vì đợi bản thật ngày 23 và 26/08.
4. scripts/audit_security.py — CASAN Check 1, Gamma chủ trì (hạn 30/08). Viết
sớm để kiểm liên tục trong lúc chuyển API key, không đợi tới ngày cổng.
Lần chạy đầu ra 3 báo động giả (secret_in_output là tên quy tắc, api_key="x"
là dữ liệu test) nên đã siết: ngưỡng độ dài, hằng liệt kê, hình dạng khoá
i18n, và dấu "# casan: allow" làm lối thoát chuẩn.
--self-test cắm 4 credential thật + 5 mẫu vô hại để chứng minh nó còn cắn
được — một máy quét không tìm thấy gì chỉ có giá trị nếu chứng minh được nó
biết tìm.
5. Ba check CASAN vào CI, chạy mọi PR thay vì dồn tới 30/08. Check 2 và 3
thuộc Team Hoa và Team Duy, chưa có script — bước CI bỏ qua nếu file chưa
tồn tại, để thêm cổng không làm đỏ CI của hai team kia.
6. docs/refactor/GammaTeam_decisions.md — hai quyết định chờ nhóm trưởng chốt:
provider_conf() còn trả api_key hay không (ảnh hưởng 5 nơi, 3 nằm ngoài
team), và số phận 24 checker UI sẽ vỡ khi file bị dời.
96 test xanh (90 cũ + 6 mới). CASAN Check 1: 0 credential lộ.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Trang HTML tự đứng một mình, mở bằng trình duyệt là xem được, không cần mạng.
Chia toàn bộ phần việc của team trong plan.md (R02, R07-T06, R08-T07…T10, R09,
CASAN Check 1) cho 3 người:
- Một mục chung nhóm trưởng làm trước, xong mới chia nhánh: dựng khung 5 thư
mục đích (hiện là 0 file), interface + fake cho Config/Secrets, chốt số phận
api_key, script CASAN Check 1, đưa 3 check vào CI, quyết số phận 24 checker
UI sẽ vỡ khi file bị dời.
- Ba nhánh tính năng ngang nhau, mỗi nhánh ~2.700 dòng: N1 cấu hình và vỏ ứng
dụng (nhóm trưởng giữ, vì chạm app.py / config.py / theme.py / i18n.py),
N2 giám sát, N3 Co4E.
- Bảy quy ước cho N2 và N3, ba trong đó là bắt buộc.
Số dòng code, 156 lời gọi ctx.config, 24 lời gọi audit_log.record và baseline
90 test đều đo trực tiếp trên main ngày 21/08, không lấy từ tài liệu.
Footer ghi rõ phần nào là đề xuất, phần nào lấy từ ba tài liệu gốc — mục chung,
cách chia nhánh, quy ước và nghiệm thu là đề xuất.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
docs/refactor/BaoCao_TeamDuy_R01_R03_R04.md records what was delivered against
each of the 16 tasks, the measured evidence (243 tests, 218 of them in 1.22s;
check_imports PASS; no production file over 400 LOC), the three real defects
found while working - the routing_application() deadlock, the swallowed
"notice" event, and the suite silently testing a different checkout - plus the
six open decisions and, explicitly, what was NOT tested (no manual app launch,
no real provider traffic, tools/check_*.py not run).
Refactoring_Checklist.md now links to it from the progress block.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The previous commit recorded Team Duy as owning R01/R02/R04/R10. That is wrong.
Feature_Architecture_Proposal.md line 7 and DeltaTeam_prompt.md line 17 both
state R01, R03, R04, R08 (Chat UI) and R10; R02 belongs to Team Nam, which is
also who owns the two failing config-security tests.
The completed work itself (R01, R03, R04) was already correct and is unchanged.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Verification gap closed. The suite proved the new services correct in isolation,
but three paths I had modified had no test actually running them:
tests/integration/test_task_executor_flow.py (7 tests)
The Schedule Task path after R04-T05. Pins that History is still re-saved from
the LIVE message list mid-run (the reason begin_turn() exists - the pre-turn
copy would have frozen progress at the first user message), that update_plan
tracking still reports an unfinished checklist, and that a failed run still
raises so execute_task writes error.txt.
tests/integration/test_routing_surfaces.py (11 tests)
Real offscreen CoworkTab/Co4ETab/FolderTab calling the shared routing service:
correct surface key per screen, Auto switches, Off does not consult the engine,
Manual switches only on approval, a pinned Admin agent still wins, and AI-Edit
still pins TaskType.CODING. Also pins the field contract ui/routing_toggle.py
reads off RoutingDecision (from_model/to_model as provider/model keys) - a
rename there would only fail inside a modal dialog.
Also updates docs/refactor/Refactoring_Checklist.md: the 16 completed R01/R03/R04
tasks, the Team Duy daily rows, and a status block recording the measured
numbers, the scope correction (team owns R01/R02/R04/R10), and what is still
outstanding.
Suite: 243 passed, 2 pre-existing failures (EPIC R02). Fast suite (unit +
contracts + characterization + routing): 218 passed in 1.16s.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R04 (Team Duy) - the turn lifecycle leaves the widget.
R04-T01 domain/agents/conversation_execution_request.py
Frozen snapshot of one turn, captured on the UI thread at submit time. The
job closure used to read widget/workspace state from inside the worker
thread, so a turn could run on a mix of submit-time and later state
depending on thread timing.
R04-T02 domain/agents/agent_event.py
13 frozen event types replacing untyped emit() dicts, with a two-way bridge
so existing widgets keep consuming the legacy shape until EPIC R08. Adds
TurnCompletedEvent - the end-of-turn signal the engine never had, which is
why a cancelled turn and a failed turn look identical to the UI today.
R04-T03 application/conversations/conversation_application_service.py
Runs a turn from a request and reports typed events. Never raises across the
worker boundary; TurnResult.raise_if_failed() preserves the existing
exception-based failure path. begin_turn()/execute_turn() expose the live
message list for callers that autosave history mid-run.
R04-T04 ui/cowork_tab.py::build_job -> snapshot + service.
R04-T05 core/task_executors.py::_run_agent -> same service (was a second,
slightly different assembly of the same call).
Caught while wiring the bridge: the first event vocabulary had no "notice"
event, so Agent Security warnings and auto-compaction notices would have been
silently swallowed. Added NoticeEvent plus a test that scans the engine sources
for emit() tags and fails when one has no typed counterpart.
New: tests/integration/ - real offscreen CoworkTab running a scripted turn end
to end (7 tests), including a characterisation of the extra provider call Agent
Security spends reviewing each request.
Suite: 225 passed, 2.74s. check_imports: PASS. All new files < 400 LOC.
2 pre-existing failures remain in test_config_security.py (EPIC R02/Team Nam).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R03 (Team Duy) - one provider catalogue, one routing flow, one usage seam.
R03-T01 tests/contracts/test_providers.py
29 contract tests every provider must satisfy: canonical assistant message,
streamed text == returned content, reasoning never joins the answer, parsed
tool arguments, ProviderError for every failure. Real adapters exercised
offline by stubbing Provider._request.
R03-T02 domain/models/provider_descriptor.py
infrastructure/providers/provider_registry.py
Provider facts declared once (was split across providers/factory.py,
DEFAULT_CONFIG and PROVIDER_LABELS). ProviderRegistry.build() also stamps the
descriptor id onto the instance, so ollama/github_copilot/codex usage is no
longer all attributed to "openai_compat", and never mutates the caller config.
R03-T03 application/model_routing/routing_application_service.py
Pure-Python routing policy with four modes: Off, Auto, Manual and the new
Fallback (switch only AFTER the current model fails). Depends on a RoutingPort
protocol; production wires the existing core.routing engine underneath.
R03-T04/T05 ui/chat_panel.py, ui/co4e_tab.py, ui/folder_tab.py
Three near-identical routing copies (~40 lines each) replaced by a call to
ctx.routing_application() plus a confirm callback. Mode vocabulary now lives
in one place (normalize_mode/is_valid_mode) instead of four literal tuples.
R03-T06 infrastructure/telemetry/usage_sink.py
Token usage extracted from both providers into UsageEvent + UsageEventSink.
Estimation pinned against core.usage_tracker so no recorded number changes.
Also fixes a deadlock introduced while wiring AppContext: routing_application()
held _routing_lock and called routing(), which takes the same non-reentrant lock.
Suite: 186 passed, 1.22s. check_imports: PASS. All new files < 400 LOC.
2 pre-existing failures remain in test_config_security.py (EPIC R02/Team Nam).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
EPIC R01 (Team Duy) - safety net before the parallel refactor starts.
R01-T01 docs/architecture/ADR-001-layered-architecture.md
4-tier boundaries, allowed dependency directions, invariants I1-I6 and
the strangler-fig migration strategy.
R01-T02 tests/fakes/{fake_provider,fake_tool_executor}.py
Scripted, offline Provider and extra-tool executor doubles.
R01-T03 scripts/check_imports.py
AST-based Clean Architecture Guard (CASAN Check 3). Also covers relative
imports and function-local imports; ASCII-only output for cp932 consoles.
R01-T04 tests/characterization/test_run_cowork.py
13 snapshot tests pinning run_cowork's current observable contract before
EPIC R04 moves its orchestration into application/.
R01-T05 docs/architecture/dormant-code.md
Import-graph scan: 43 unimported modules verified down to 6 genuinely
dormant items (~1887 LOC); the rest run via subprocess/CLI entry points.
tests/conftest.py binds `cowork_local` to THIS checkout by absolute path -
previously sys.path discovery could import a sibling checkout and the suite
would silently test the wrong code.
Suite: 104 passed, 1.08s (2 pre-existing failures in test_config_security.py
remain - config.py still ships a hardcoded default password, EPIC R02/Team Nam).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Copy of sections VI-IX from Feature_Architecture_Proposal.md
(roadmap, team assignment/KPI, anti-patterns, function migration map).
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>