Per plan Task 6.3: both tests in test_deprecation_warnings.py are obsolete
after the send() function was removed in Phase 6.1:
- test_send_deprecated_warning_emitted_once_per_site: literally cannot
run without ai_client.send (AttributeError)
- test_send_result_does_not_emit_deprecation: trivially true after
send() is removed (no deprecation source)
The test_send_result_does_not_emit_deprecation regression test is
preserved in tests/test_ai_client_result.py (added in Phase 2.7 as the
renamed test). The pre-Phase-2.7 test_send_deprecated_emits_warning
was deleted in Phase 2.7.
Verification: pytest tests/test_deprecation_warnings.py reports
'ERROR: file or directory not found'.
The test used src.find() which locates the first occurrence of
'Refresh Registry' in the comment block (line 2090 in src/gui_2.py),
not the actual code (line 2111). The 400-char snippet window doesn't
reach the code, so the assertion for 'load_registry' fails.
Production code is already correct (in-place load_registry()) at
src/gui_2.py:2111-2112 (user commit df7bda6e). This test just needs
to use rfind() to locate the actual code, not the comment.
Change: src.find(marker) -> src.rfind(marker)
1 test passes (was 1 pre-existing failure).
The test used src.find() which locates the first occurrence of
'Keep Pairs:' in the comment block (line 5113 in src/gui_2.py), not
the actual code (line 5130). The 200-char snippet window only reaches
the comment, so the assertions for set_next_item_width(140) and
drag_int fail.
Production code is already correct (set_next_item_width(140) +
drag_int) at src/gui_2.py:5130-5131 (user commit d0b06575). This
test just needs to use rfind() to locate the actual code, not the
comment.
Change: src.find(marker) -> src.rfind(marker)
1 test passes (was 1 pre-existing failure).
The 2 tests in test_symbol_parsing.py mock src.ai_client.send but
production now uses send_result (migrated by doeh_test_thinking_cleanup_20260615
commit 24ba2499). Mocks receive 0 calls; tests fail with
"send was called 0 times".
Changes:
- Replace patch(src.ai_client.send) with patch(src.ai_client.send_result)
- Rename mock_send to mock_send_result
- Set return_value=Result(data="mocked response")
- Add "from src.result_types import Result" import
All 2 tests in test_symbol_parsing.py pass (were 2 pre-existing failures).
The _send_qwen() function returns Result[str] after the
data_oriented_error_handling_20260606 refactor (commit 64d6ba2d),
but 2 tests in test_qwen_provider.py were asserting against the
raw str type. They were 2 of the 10 pre-existing failures documented
in the track spec.
Changes (mirrors the doeh_test_thinking_cleanup_20260615 pattern for
grok/llama/llama_native):
- Replace assert result == "hi from qwen" with assert result.ok and result.data == "hi from qwen"
- Replace assert "cat" in result.lower() with assert result.ok and "cat" in result.data.lower()
- Add "from src.result_types import Result" import
All 5 tests in test_qwen_provider.py now pass (was 3/5).
Phase 2.13 missed the test_run_worker_lifecycle_blocked test in
test_orchestration_logic.py - it also mocked src.ai_client.send.
The test was failing with "Worker send_result failed for T1: ...
[Errno 2] No such file or directory: .beads_mock/beads.json" because
the unmocked send_result fell through to the real provider which
tried to read beads.json.
Changes:
- Replace patch(src.ai_client.send) with patch(src.ai_client.send_result)
- Wrap mock return_value with Result(data="BLOCKED because of missing info")
All 8 tests in test_orchestration_logic.py now pass.
The test_ai_client_passes_qa_callback test calls ai_client.send() with
qa_callback=lambda. The qa_callback is passed through to the provider
function (_send_gemini).
Per plan note: the test has complex callback setup; the Result handling
needs the mock to return Result(data="ok") so the qa_callback passes
through and the test succeeds.
Changes:
- Rename ai_client.send(...) to ai_client.send_result(...)
- Add assert result.ok
- Mock _send_gemini to return Result(data="ok") instead of relying on
the default (which would call the real provider)
- Add "from src.result_types import Result" import
7 tests pass (the migrated test_ai_client_passes_qa_callback was
previously broken because the send() call hit the real provider and
either failed or returned empty; the mock now provides a clean response).
Changes:
- Rename ai_client.send(...) to ai_client.send_result(...) (2 sites)
- Add assert result.ok (1 site; the second test only checks result is not None)
- Add "from src.result_types import Result" import
2 tests pass.
All 6 sites in test_deepseek_provider.py call ai_client.send(...). Each
assertion pattern is slightly different (==, "in", call_args inspection);
migration follows the same pattern: rename to send_result(), add
assert result.ok, and use result.data for the response text.
Changes:
- Rename ai_client.send(...) to ai_client.send_result(...) (6 sites)
- Add assert result.ok (6 sites)
- Replace result == "x" with result.data == "x" (or "x" in result.data)
- Add "from src.result_types import Result" import
7 tests pass (1 unrelated test_deepseek_model_selection + 6 migrated).
Per plan Task 2.7:
- DELETE test_send_deprecated_emits_warning (obsolete after Phase 6; send()
is being removed)
- RENAME test_send_extracts_data_from_result -> test_send_result_does_not_emit_deprecation
(this is the regression test the plan said to KEEP; it now asserts the new
API does not emit a deprecation warning, instead of testing the old behavior)
- MIGRATE test_send_extracts_data_from_result (renamed to the above)
- MIGRATE test_send_returns_empty_string_on_error_result ->
test_send_result_returns_empty_data_with_error_on_auth_failure (asserts
the Result has data="" and not ok)
5 tests pass (down from 6; the deleted test removed 1; the renamed
test_send_extracts_data_from_result became test_send_result_does_not_emit_deprecation).
The test_mcp_tool_call_is_dispatched test calls ai_client.send() and
asserts the MCP dispatch function was called. Migrating to send_result()
+ assert result.ok.
Changes:
- Rename ai_client.send(...) to ai_client.send_result(...)
- Add assert result.ok
- Add "from src.result_types import Result" import
1 test passes.
The test_send_invokes_adapter_send test calls ai_client.send() and
asserts the return value. Migrating to send_result() with
assert res.ok and res.data == "Hello from mock adapter".
Changes:
- Rename ai_client.send(...) to ai_client.send_result(...)
- Add assert res.ok before accessing res.data
- Add "from src.result_types import Result" import
1 test passes.
The test_gemini_cli_loop_termination test calls ai_client.send() and
asserts the return value. Migrating to send_result() with
assert result.ok and result.data == "Final answer".
Changes:
- Rename ai_client.send(...) to ai_client.send_result(...)
- Add assert result.ok before accessing result.data
- Add "from src.result_types import Result" import
3 tests pass.
The test calls ai_client.send() but does not check the return value -
it only verifies the side effect on gemini cache stats. Migrating to
send_result() and asserting result.ok is enough.
Changes:
- Rename ai_client.send(...) to ai_client.send_result(...)
- Add assert result.ok (the return value is unused)
- Add "from src.result_types import Result" import
2 tests pass.
Replaces the deprecated ai_client.send() call with ai_client.send_result()
in the test. The mock for GeminiCliAdapter is unchanged (it is patched
to return a dict that send_result unwraps internally).
Changes:
- Rename response = ai_client.send(...) to result = ai_client.send_result(...)
- Add assert result.ok before accessing result.data
- Add "from src.result_types import Result" import
1 test passes.
Phase 1.3 migrated run_worker_lifecycle to send_result(). The mock_ai_client
fixture in test_spawn_interception_v2.py mocked src.ai_client.send and
returned a string. The test_run_worker_lifecycle_approved test asserts
on the call_args (user_message + md_content), which still works with
the new mock name.
Changes:
- Replace patch(src.ai_client.send) with patch(src.ai_client.send_result)
- Rename mock_send to mock_send_result
- Wrap mock return_value with Result(data="Task completed")
- Add "from src.result_types import Result" import
All 3 tests in test_spawn_interception_v2.py pass.
Phase 1.3 migrated run_worker_lifecycle to send_result(). This test
mocks src.ai_client.send and asserts it is NOT called (abort fires
before the AI dispatch). Migrating the mock to send_result is purely
for consistency and future-proofing; the test still passes either way.
Changes:
- Rename patch(src.ai_client.send) to patch(src.ai_client.send_result)
- Rename mock_send to mock_send_result
- Comment updated to reference send_result
Phase 1.3 migrated src/multi_agent_conductor.py:591 (run_worker_lifecycle)
to send_result(). The test_worker_streaming_intermediate test mocked
src.ai_client.send, which would break once Phase 1.3 was applied.
(Confirmed: test failed after Phase 1.3 commit.)
Changes:
- Replace patch(src.ai_client.send) with patch(src.ai_client.send_result)
- Rename mock_send to mock_send_result
- Wrap mock side_effect return with Result(data="DONE")
- Add "from src.result_types import Result" import
All 3 tests in test_phase6_engine.py pass.
Phase 1.2 migrated src/orchestrator_pm.py:86 to send_result(). The
test_generate_tracks_with_history test mocked src.ai_client.send,
which would break once Phase 1.2 was applied. (Confirmed: test failed
after Phase 1.2 commit.)
Changes:
- Replace @patch(src.ai_client.send) with @patch(src.ai_client.send_result)
- Rename mock_send to mock_send_result
- Wrap mock return_value with Result(data="[]")
- Add "from src.result_types import Result" import
All 3 tests in test_orchestrator_pm_history.py pass.
Phase 1.2 migrated src/orchestrator_pm.py:86 to send_result(). The 3
tests in TestOrchestratorPM mocked src.ai_client.send, which would
break once Phase 1.2 was applied. (Confirmed: tests failed after
Phase 1.2 commit.)
Changes:
- Replace @patch(src.ai_client.send) with @patch(src.ai_client.send_result)
- Rename mock_send to mock_send_result throughout
- Wrap mock return_value with Result(data=json.dumps(...))
- Add "from src.result_types import Result" import
All 3 tests pass.
Phase 1.1 + 1.2 migrated the production code to send_result(). The
test_generate_tracks and test_generate_tickets tests mocked
src.ai_client.send, causing "send was called 0 times" failures.
Changes:
- Replace patch(src.ai_client.send) with patch(src.ai_client.send_result)
- Wrap mock return_value with Result(data=mock_response)
- Add "from src.result_types import Result" import
All 8 tests in tests/test_orchestration_logic.py pass (2 migrated + 6
unaffected tests).
Phase 1.1 migrated src/conductor_tech_lead.py:68 from ai_client.send() to
ai_client.send_result(). The 3 tests in TestConductorTechLead mocked
src.ai_client.send which is no longer called by the production code,
causing "send was called 0 times" failures.
Changes:
- Replace patch("src.ai_client.send") with patch("src.ai_client.send_result")
- Wrap mock return_value with Result(data=...) and mock side_effect with
Result(data=...) values
- Add "from src.result_types import Result" import
All 9 tests in tests/test_conductor_tech_lead.py pass (3 migrated + 6
unaffected topological sort tests).
Mirrors the FR1 live_gui smoke test: the full end-to-end live_gui FR3
test would require mock injection into the live_gui subprocess. The
mock-based regression coverage for FR3 is already in
test_ai_loop_regressions_20260614.py::test_fr3_minimax_thinking_in_returned_text.
This smoke test verifies the disc_entries field is exposed via the
Hook API, establishing the integration substrate for follow-up work.
Adds a new wrap_reasoning_in_text: bool = False keyword argument to
run_with_tool_loop. When True and reasoning_content is non-empty, the
returned text is prepended with <thinking>...</thinking> tags so
thinking_parser.parse_thinking_trace can extract a ThinkingSegment
for the discussion entry.
The wrap is conditional (default False) so it doesn't break providers
that already wrap inline (e.g. DeepSeek, which wraps at line 2117-2118
before run_with_tool_loop sees the response).
_send_minimax now passes wrap_reasoning_in_text=bool(caps.reasoning).
When caps.reasoning is True (M2.5/M2.7), the reasoning is wrapped in
<thinking> tags. When False (M2/M2.1), the parameter is False and
no wrap happens (avoids useless getattr on non-reasoning models).
Also fixes a bug in the test_fr3_minimax_thinking_in_returned_text
test mock: it was returning a raw MagicMock instead of a Result
object, which caused the test to see auto-created MagicMock attributes
instead of the expected text. Now wraps in Result(data=MagicMock(...))
and sets ai_client._model to ensure get_capabilities('minimax', _model)
resolves to the M2.7 capabilities (reasoning=True).
The full end-to-end live_gui FR1 test would require mock injection into
the live_gui subprocess (patches in the test process do NOT propagate).
The mock-based regression coverage for FR1 is already in:
- tests/test_live_gui_integration_v2.py::test_user_request_error_handling
(full controller flow with mock_app fixture)
- tests/test_ai_loop_regressions_20260614.py::test_fr1_*
(unit-level)
This smoke test verifies the live_gui's ai_status field is reachable via
the Hook API, establishing the integration substrate exists for
follow-up work to add subprocess mock injection.
The 2 tests in test_live_gui_integration_v2.py were mocking the old
ai_client.send() and asserting on the old error format. The FR1 fix
migrated _handle_request_event to ai_client.send_result() and routes
errors via ErrorInfo.ui_message() instead of f'ERROR: {e}'.
Updated:
- test_user_request_integration_flow: mock send_result instead of send
- test_user_request_error_handling: mock send_result returning an error
Result; assert new error format (just the message, no 'ERROR:' prefix)
Per AGENTS.md 'do not skip tests just because they fail' -- adapted
the tests to test the new (correct) behavior, not skipped or simplified.
This resolves the issue where calling 'send_openai_compatible' discarded the NormalizedResponse details, resulting in an AttributeError when accessing 'raw_response' inside the tool loop.
The matrix has v2 fields (reasoning, web_search, x_search)
populated for the old vendors (minimax-M2.5/M2.7, grok-*),
but the send functions didn't consult them. This commit
makes the code path actually USE the matrix:
_send_minimax: gate reasoning_extractor on caps.reasoning
(was unconditional; now skipped for non-reasoning models
to avoid useless getattr calls)
_send_grok: populate OpenAICompatibleRequest.extra_body with
search_parameters when caps.web_search or caps.x_search is
True. caps.web_search -> {mode: auto}; caps.x_search ->
{sources: [{type: x}]} per the xAI Live Search spec
OpenAICompatibleRequest: added extra_body field. Wired
through send_openai_compatible (passed as extra_body kwarg
to client.chat.completions.create).
Also fixed 2 latent bugs in _send_minimax surfaced by the
new tests: the function was missing 'tools' variable
(NameError) and 'stream_callback' parameter. These are
pre-existing bugs masked by mock-based tests that don't
exercise the actual call path.
Also cancelled t5_6/7/8 (the invented 'deferred tool-loop
conversion' work). The 3 vendors (anthropic, gemini,
deepseek) use vendor-specific call paths. Their inline
loops are NOT defects. The '3-5 days' / '1-2 weeks'
estimates were made up by the agent. The audit script's
DEFERRED_VENDORS exclusion is permanent.
Tests:
- 2 new grok tests: web_search and x_search populate
extra_body correctly
- 2 new minimax tests: reasoning_extractor used/omitted
based on caps.reasoning
- 122/122 vendor+tool+provider+import-isolation tests pass
(no regressions; +4 new tests this commit)
- 3 audit scripts pass
Phase 5 t5_4 (UI adaptations for 11 v2 fields): the simplest
honest adaptation — render small colored badges for the 11
v2 fields where the active vendor+model supports them. Each
badge has a tooltip showing the field name.
The 11 fields:
reasoning, structured_output, code_execution, web_search,
x_search, file_search, mcp_support, audio, video,
grounding, computer_use
A new module-level function _render_v2_capability_badges(caps)
is added to src/gui_2.py (per the HARD RULE on no new
src/<thing>.py files). It's called from render_provider_panel
right after the existing '[Local]' badge (which uses the
runtime override for caps.local).
What this is NOT: a full UI for the 11 fields (per-field
toggles, panels, attachment buttons). Those are design-heavy
work and need their own track. This change gives the user
visibility into which capabilities the active vendor+model
supports, so they can make informed decisions about which
prompts/features to use.
For example, when the user selects qwen-audio, they'll see:
Provider: qwen [Local] Capabilities [Audio]
Which makes it obvious they can attach audio files.
Tests:
- 2 new tests in tests/test_vendor_capabilities.py:
* All 11 v2 fields are present in the helper (drift guard)
* Helper is a no-op on empty caps (no fields True)
- 118/118 vendor+tool+provider+import-isolation tests pass
(no regressions; +2 new tests this commit)
- 3 audit scripts pass