Đồng nghiệp đã push thêm 10 commit lên nhánh trong lúc đang xử lý merge
trước đó (R08-T01..T06 chat_panel.py split, R08 folder/dashboard/graph/
scheduling hoàn thiện, R10 CI Quality Gates + Contributor Recipes + E2E
smoke test). Resolve conflict:
- application/monitoring/__init__.py, domain/tasks/__init__.py,
infrastructure/persistence/json/__init__.py: chỉ khác docstring — giữ bản
HEAD (đầy đủ ngữ cảnh EPIC hơn), hợp nhất __all__ khi cần
(MonitoringQueryService).
- tests/fakes/__init__.py: hợp nhất __getattr__ để lazy-load cả
FakeToolExecutor lẫn ToolInvocation (bản HEAD thiếu ToolInvocation), bỏ
entry "FakeClock" bị lặp trong __all__.
- tests/integration/test_routing_surfaces.py (deleted by them): khôi phục
lại bản đã sửa ở lần merge trước — verify lại: API routing
(RoutingApplicationService.resolve/_apply_routing/_apply_co4e_routing)
không đổi sau khi chat_panel.py chuyển sang presentation/chat/*, 9/9 test
vẫn pass trên code đã merge.
Ghi chú (không sửa, ngoài phạm vi merge): tests/fakes/__init__.py trên nhánh
remote export "ToolInvocation" từ fake_tool_executor.py nhưng class này đã
bị xoá nhầm từ commit chung 10739f1 (breakdown folder tree epic R01) — hiện
là dead code, không ai import, nhưng sẽ raise ImportError nếu có test nào
sau này thử dùng.
Đã chạy pytest tests/: 793 passed (không phát sinh fail mới so với lần
merge trước — 8 fail còn lại đều do môi trường sandbox: thiếu package
keyring, và tên thư mục checkout "cowork-local" thay vì "cowork_local"
khiến vài test spawn-subprocess không import được package).
Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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>
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>
R04-T01 — `domain/agents/conversation_execution_request.py`: a frozen
snapshot of everything one chat turn needs. Turn inputs previously lived in a
closure plus a 15-key ctx dict inside `ui/chat_panel.py::_start_turn`, and the
worker thread kept reading the widget back while it ran, so every later click
was visible to work already in flight. The request also owns the prompt
composition rules (instruction prefix separator, session notes, model-switch
review note) that were inline in that closure.
R04-T02 — `domain/agents/agent_event.py`: 13 frozen event types replacing the
untyped `{"type": ...}` dicts, whose only specification was the 130-line
if/elif chain in `_on_event`. Each event serialises back to the exact legacy
dict, so the presentation layer is untouched; `agent_event_codec.py` parses the
other way and is a temporary shim, isolated so R08 can delete it in one move.
`assistant_done` is deliberately NOT the end of a turn (it fires once per
provider call), so it maps to AssistantMessageCompletedEvent while the new
TurnCompletedEvent reports the turn itself.
R04-T03 (part) — `domain/agents/agent_result.py`: one named outcome for a
finished turn, replacing the message list / 3-tuple / reconstructed-from-side-
effects trio the three callers each read differently.
Verification: 66 tests. Beyond the unit tests,
`tests/integration/test_agent_event_bridge.py` runs the REAL `run_cowork` loop
offline and asserts every dict it emits is recognised and round-trips
byte-for-byte — a guard against an event type nobody modelled or a key whose
meaning silently drifted.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
`tests/test_project_context_mcp_template.py` imported `mcp` at module scope,
but the SDK is a runtime dependency (requirements.txt) and is deliberately
absent from requirements-test.txt — the only thing CI installs. Collection
therefore aborted for the ENTIRE suite before a single test ran.
The guard now sits inside the one test that touches the SDK, so the other
cases in the file (pure-Python contract checks) keep running on CI instead
of being skipped along with it.
Unrelated to the R04 refactor; kept as its own commit so it can be cherry-
picked to main on its own.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>