98cee81a61cad998396fca61ca2d569befd0b458
15
Commits
| Author | SHA1 | Message | Date | |
|---|---|---|---|---|
|
|
98cee81a61 |
merge: cập nhật từ origin/feature/delta-team/epic-R04 (dọn dead code, gộp i18n/theme, CASAN Gate O, launcher)
Kéo 7 commit mới từ remote — chủ yếu dọn dẹp và siết chất lượng, không đổi API đang dùng: - Xoá 1.400 dòng mã chết + 5 gói rỗng còn sót sau các lần merge trước. - Thêm CASAN Gate O (LOC) áp cho toàn cây mã. - Sửa 5 checker UI hỏng sau đợt tách widget R08, vá 4 hồi quy. - install.bat/run.bat, gộp requirements-test.txt vào requirements.txt. - Gom i18n_*.py / theme_*.py rời rạc thành gói i18n/ và theme/. Merge sạch, không có conflict marker nào (git tự resolve toàn bộ). Đã kiểm tra lại 4 điểm đã vá ở 2 lần merge trước (config.py circular import, ai_edit_model_resolver.py dùng resolve() thay route_turn(), _confirm_routing_switch nhận timeout, RoutingApplicationService.resolve()) — cả 4 vẫn nguyên vẹn sau merge này. pytest tests/: 793 passed — giống hệt số liệu trước khi merge, không phát sinh fail/error mới (8 fail còn lại vẫn là do môi trường sandbox: thiếu keyring, tên thư mục cowork-local vs cowork_local). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> |
||
|
|
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>
|
||
|
|
4f0010a971 |
merge: hoà cập nhật mới từ origin/feature/delta-team/epic-R04 (R08 Chat UI Hub + R10)
Đồ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
|
||
|
|
c7784defc3 |
merge: hoàn tất merge origin/feature/teamhoa/r05-r06 vào feature/delta-team/epic-R04
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> |
||
|
|
b8783526d0 | feat(R08): finalize Chat UI Hub components, AudioRecorderWidget, and integration tests (100% PASS) | ||
|
|
1de3336970 | merge: merge origin/feature/teamhoa/r05-r06 (R07/R08) into feature/delta-team/epic-R04 | ||
|
|
0e51356a7d |
feat(R08): split ScheduleTaskTab, FolderTab, DashboardTab, StructureGraphView
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>
|
||
|
|
69ab8e125b |
feat(R07): task repository, schedule calculator, Qt clock adapter, task/AI-planner services
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> |
||
|
|
f8e22f5f5b | merge: merge origin/gamma/refactor and origin/feature/teamhoa/r05-r06 into feature/delta-team/epic-R04 | ||
|
|
3665135c38 |
feat(R04): run every Cowork turn through ConversationApplicationService
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> |
||
|
|
19e6b4deb2 |
feat(R04): add the immutable turn snapshot and typed agent event stream
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>
|
||
|
|
f61c5474b0 |
feat(R03): unify model routing and centralise the provider catalogue
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>
|
||
|
|
cf542b7416 |
feat(R06): workspace session snapshot, atomic persistence, history-dir race fix
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>
|
||
|
|
15e1d3eb65 |
test(R03/R04): cover the three code paths that were changed but never executed
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> |
||
|
|
a53163ebaf |
feat(R04): immutable turn snapshot, typed agent events, conversation service
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> |