Files
odysseus/tests/test_tui_round_cap.py
T
Yu-Wei ChangandAlexandre Teixeira e4cc8efa19 fix(search): keep reasoning out of web search queries and stop misrouting named targets (#7054)
* fix(llm): keep reasoning out of answers when thinking is off

Small-budget utility calls on reasoning models spend every token on
reasoning, and llm_call then returned reasoning_content as the answer. Web
search extraction sent that text to SearXNG as the query, which found
nothing, and session titles fell back as unusable.

Treat thinking_mode="off" as "only the answer": non-streaming calls now
return empty instead of substituting reasoning, matching the streaming path.
Callers that do not turn thinking off keep the existing fallback.

Turn thinking off for search query extraction, session titles without a
configured effort, scheduled task names, and deep research classification,
and strip inline think blocks from the extracted query.

* fix(routing): require computer task wording for named hosts

"on <word>" and "from <word>" counted as a named computer on their own, so
"Who is Jensen Huang from NVIDIA?" and "What happened on Monday?" routed to
shell tools and the workspace toolset.

A target now only counts next to wording that describes work on a machine,
such as "check disk usage on gpu-box" or "copy the logs from gpu-box". The
intent classifier and the agent loop's local computer check share the pattern.

* fix(chat): resolve follow-up references in web search queries

Query extraction only saw the latest message, so "Does he work with Lisa
Su?" lost who "he" was. Include the last four turns, capped per turn, and
instruct the model to use them only to resolve references. Incognito turns
do not use saved session history.

* test(llm): stop reasoning fallback tests hanging in isolation

The module replaced src.agent_tools and core.database with MagicMock before
importing agent_loop. strip_tool_blocks then returned a mock, and
_strip_think_blocks looped forever because a mock's find() never returns -1.
This hung whenever the file ran alone or first on a worker. Import the real
modules instead.

* test(search): remove extra blank line at EOF

---------

Co-authored-by: Alexandre Teixeira <general@alexandreteixeira.dev>
2026-11-06 02:11:20 +00:00

979 lines
34 KiB
Python

"""TUI agent turns use short lookup limits and adaptive coding turns."""
import json
from src.tool_policy import WEB_TOOL_NAMES
from routes.chat_routes import (
_effective_agent_rounds,
_effective_native_output_tokens,
_parse_client_runtime_context,
)
from src.agent_loop import (
_looks_like_local_computer_request,
_looks_like_explicit_tui_personal_domain_request,
_looks_like_explicit_tui_app_or_external_request,
_is_qwen_explicit_model_list_request,
_is_qwen_explicit_latest_email_request,
_route_tui_local_workspace_tools,
_tui_local_fallback_shell_command,
_tui_recover_invalid_local_tools,
ToolBlock,
_looks_like_malformed_tui_tool_call,
_tui_project_discovery_summary,
_parse_qwen_explicit_note_view,
_ody_qwen_terminal_tool_summary,
_tui_normalize_network_host_command,
_tui_normalize_workspace_host_command,
_tui_network_summary,
_tui_local_workspace_turn,
_web_search_unavailable_for_turn,
_is_host_bridge_failure_result,
_empty_response_fallback,
_normalize_ody_qwen_text_artifacts,
_workspace_coding_rules,
_looks_like_workspace_coding_request,
_workspace_pre_mutation_verification_block,
_tui_local_test_runner_command,
_tui_local_test_runner_host_shell_content,
_tui_local_smoke_test_runner_command,
_tui_normalize_pytest_command,
_tui_smoke_test_request,
_tui_explicit_full_test_request,
_calendar_context_owns_ambiguous_mutation,
_calendar_lookup_requires_fresh_tool,
_parse_simple_calendar_tool_request,
_memory_search_precedes_unrequested_list,
)
def test_all_tui_bridge_mutation_failures_are_terminal_transport_errors():
for result in (
{"error": "apply_patch: host bridge request failed: connection refused"},
{"error": "edit_file: bridge returned HTTP 502"},
{"error": "write_file: bridge returned invalid payload"},
{"output": "host_shell: no TUI host bridge advertised", "exit_code": 1},
{"stderr": "host_shell: missing TUI host bridge", "exit_code": 1},
{"error": "host bridge unavailable", "exit_code": 1},
):
assert _is_host_bridge_failure_result(result)
def test_tui_non_coding_turn_caps_stale_persisted_setting():
assert _effective_agent_rounds(100, {"surface": "odysseus-tui"}, 20) == 20
assert _effective_agent_rounds("bad", {"surface": "odysseus-tui"}, 20) == 20
def test_tui_coding_turn_uses_adaptive_stopping():
prompt = (
"Inspect this local repository, repair the parser, and run the focused tests."
)
assert _effective_agent_rounds(
100,
{"surface": "odysseus-tui"},
20,
message=prompt,
) is None
def test_tui_read_only_turn_keeps_short_round_cap():
assert _effective_agent_rounds(
100,
{"surface": "odysseus-tui"},
20,
message="Inspect this local repository and report its top-level files.",
) == 20
def test_existing_file_edits_allow_one_baseline_verification_command():
assert _workspace_pre_mutation_verification_block(
ToolBlock("host_shell", json.dumps({"command": "pytest -q tests/test_parser.py"}))
)
assert _workspace_pre_mutation_verification_block(
ToolBlock("bash", "python -m pytest -q")
)
assert not _workspace_pre_mutation_verification_block(
ToolBlock("host_shell", json.dumps({"command": "pwd && ls"}))
)
assert not _workspace_pre_mutation_verification_block(
ToolBlock("edit_file", "config.py")
)
def test_webui_non_workspace_turn_keeps_configured_round_limit():
assert _effective_agent_rounds(100, {"surface": "webui"}, 20) == 100
def test_unattended_native_terminal_agent_honors_explicit_round_budget():
assert _effective_agent_rounds(
6,
{
"surface": "odysseus-native",
"terminal_agent": True,
"unattended_mode": True,
"max_agent_rounds": 30,
},
20,
) == 30
def test_unattended_native_terminal_agent_without_budget_is_finite():
assert _effective_agent_rounds(
6,
{
"surface": "odysseus-native",
"terminal_agent": True,
"unattended_mode": True,
},
20,
) == 6
def test_attended_native_terminal_agent_keeps_configured_round_limit():
assert _effective_agent_rounds(
6,
{
"surface": "odysseus-native",
"terminal_agent": True,
"unattended_mode": False,
},
20,
) == 6
def test_unattended_native_runtime_can_bound_output_without_changing_ui_preset():
native = {
"surface": "odysseus-native",
"terminal_agent": True,
"unattended_mode": True,
"max_output_tokens": 8192,
}
assert _effective_native_output_tokens(32768, native) == 8192
assert _effective_native_output_tokens(
32768, {"surface": "webui", "max_output_tokens": 512}
) == 32768
assert _effective_native_output_tokens(
32768, {**native, "max_output_tokens": "bad"}
) == 32768
assert _effective_native_output_tokens(
32768, {**native, "max_output_tokens": 1}
) == 256
def test_native_runtime_context_preserves_only_a_bounded_output_cap():
context = _parse_client_runtime_context({
"surface": "odysseus-native",
"terminal_agent": True,
"unattended_mode": True,
"max_output_tokens": 8192,
})
assert context["max_output_tokens"] == 8192
assert _parse_client_runtime_context({
"surface": "odysseus-tui",
"terminal_agent": True,
"unattended_mode": True,
"max_output_tokens": 8192,
}).get("max_output_tokens") is None
def test_webui_workspace_coding_turn_uses_adaptive_stopping():
assert _effective_agent_rounds(
20,
{"surface": "webui"},
20,
message="Repair the parser and verify the change.",
workspace_agent_intent=True,
) is None
def test_network_prompts_are_classified_as_local_computer_work():
for prompt in (
"Find the local IP to SSH into ajax.",
"Resolve ajax on the local network and tell me its IP.",
"Tailscale is down; resolve ajax on the LAN.",
"Inspect DNS and the default route on this machine.",
):
assert _looks_like_local_computer_request(prompt), prompt
def test_named_targets_need_computer_task_wording_to_be_local_computer_work():
assert _looks_like_local_computer_request("check disk usage on gpu-box")
assert _looks_like_local_computer_request("copy the logs from gpu-box")
assert not _looks_like_local_computer_request("Who is Jensen Huang from NVIDIA?")
assert not _looks_like_local_computer_request("What is the best book on Rome?")
def test_network_lookup_replaces_ping_with_address_evidence():
normalized = _tui_normalize_network_host_command(
"ping -c 4 ajax.local",
"Resolve ajax on the local network and tell me its IP.",
)
assert normalized is not None
command, reason = normalized
assert "getent hosts ajax.local" in command
assert "ip -o -4 addr show" in command
assert "ip route show default" in command
assert "ping replaced" in reason
def test_network_connectivity_request_preserves_ping():
assert _tui_normalize_network_host_command(
"ping -c 4 ajax.local",
"Ping ajax to test connectivity.",
) is None
def test_network_fallback_includes_named_target_without_guessing_hosts():
command = _tui_local_fallback_shell_command(
"Find the local IP for ajax so I can SSH to it; Tailscale is down."
)
assert command is not None
assert "getent hosts ajax" in command
assert "ip route show default" in command
assert _tui_local_fallback_shell_command(
"Inspect the local network interfaces."
) == "ip -o -4 addr show; ip route show default"
def test_stale_read_tools_on_coding_turn_recover_to_one_host_probe():
blocks, recovered = _tui_recover_invalid_local_tools(
[
ToolBlock("get_workspace", "{}"),
ToolBlock("ls", "{}"),
],
"Fix the parser in this local TUI project.",
)
assert recovered is True
assert [block.tool_type for block in blocks] == ["host_shell"]
assert "git_root=" in json.loads(blocks[0].content)["command"]
def test_stale_mutating_tool_is_not_rewritten_to_shell():
blocks, recovered = _tui_recover_invalid_local_tools(
[ToolBlock("manage_notes", "{}")],
"Fix the parser in this local TUI project and run the tests.",
)
assert recovered is False
assert blocks == []
def test_workspace_host_command_replaces_metadata_placeholder():
normalized = _tui_normalize_workspace_host_command(
"cd session_cwd && npm test",
"/tmp/my project",
)
assert normalized == (
"cd '/tmp/my project' && npm test",
"metadata workspace placeholder replaced with active session cwd",
)
def test_malformed_qwen_local_tool_markup_is_detected_narrowly():
assert _looks_like_malformed_tui_tool_call(
"parameter=hos_shell parameter=command cd /repo python -m pytest"
)
assert _looks_like_malformed_tui_tool_call(
"hos_shell\ncommand\nls -la\n</command\n</hos_shell\n</"
)
assert not _looks_like_malformed_tui_tool_call(
"I can help with your host shell configuration."
)
def test_tool_failure_is_not_followed_by_a_blank_answer():
final_response, chunk = _empty_response_fallback(
full_response="",
round_reasoning="",
tool_events=[
{
"tool": "manage_notes",
"output": "Note 'missing' not found",
"exit_code": 1,
}
],
)
assert final_response == "manage_notes failed: Note 'missing' not found"
assert "manage_notes failed" in (chunk or "")
def test_successful_host_shell_output_is_not_lost_to_blank_followup():
final_response, chunk = _empty_response_fallback(
full_response="",
round_reasoning="",
tool_events=[
{"tool": "host_shell", "output": "No supported test runner found", "exit_code": 0}
],
)
assert final_response == "No supported test runner found"
assert "No supported test runner found" in (chunk or "")
def test_successful_calendar_output_is_not_lost_to_blank_followup():
final_response, chunk = _empty_response_fallback(
full_response="",
round_reasoning="",
tool_events=[
{
"tool": "manage_calendar",
"command": '{"action":"list_events"}',
"output": (
"Found 1 event:\n"
"- 2026-09-01T10:00:00 -> 2026-09-01T11:00:00: "
"[Planning review](#event-planning-review)"
),
"exit_code": 0,
}
],
)
assert "Planning review" in final_response
assert '"type": "final_response"' in (chunk or "")
def test_calendar_panel_open_beats_context_snapshot_for_blank_followup():
final_response, _ = _empty_response_fallback(
full_response="",
round_reasoning="",
tool_events=[
{
"tool": "ui_control",
"command": "open_panel calendar",
"output": "",
"exit_code": 0,
},
{
"tool": "manage_calendar",
"command": '{"action":"list_events"}',
"output": (
"Found 1 event:\n"
"- 2026-09-01T10:00:00 -> 2026-09-01T11:00:00: "
"[Planning review](#event-planning-review)"
),
"exit_code": 0,
"context_only": True,
},
],
)
assert final_response == "The calendar panel is open."
def test_calendar_lookup_parser_does_not_override_create_then_show_request():
from src.agent_loop import _parse_simple_calendar_tool_request
assert _parse_simple_calendar_tool_request(
'Create a one-hour event called "Ablation review" tomorrow at 2pm, '
"then show the calendar again so I can verify it."
) is None
def test_calendar_bounds_include_both_today_and_tomorrow():
from src.agent_loop import _calendar_bounds_for_prompt
assert _calendar_bounds_for_prompt(
"What's scheduled for today and tomorrow?", today="2026-08-30"
) == ("2026-08-30", "2026-09-01")
def test_recent_calendar_anchor_owns_ambiguous_delete_entry_followup():
messages = [{
"role": "assistant",
"content": (
"View event: [Forecast lock prep, 9:00 AM]"
"(#event-12345678-abcd-4321-abcd-123456789abc)"
),
}]
assert _calendar_context_owns_ambiguous_mutation(
"I finished early - go ahead and delete the prep entry.", messages
)
def test_explicit_task_language_overrides_recent_calendar_anchor():
messages = [{
"role": "assistant",
"content": (
"View event: [Forecast lock prep, 9:00 AM]"
"(#event-12345678-abcd-4321-abcd-123456789abc)"
),
}]
assert not _calendar_context_owns_ambiguous_mutation(
"Delete the scheduled task instead.", messages
)
def test_contextual_calendar_entry_lookup_requires_fresh_tool_and_title_query():
messages = [{
"role": "assistant",
"content": (
"View event: [Forecast scenario prep, 9:00 AM]"
"(#event-12345678-abcd-4321-abcd-123456789abc)"
),
}]
prompt = "Show me that prep entry so I can confirm it doesn't collide."
assert _calendar_lookup_requires_fresh_tool(
prompt,
{"notes_calendar_tasks"},
{"manage_calendar"},
messages,
)
tool, command = _parse_simple_calendar_tool_request(prompt, messages)
assert tool == "manage_calendar"
assert json.loads(command)["query"] == "Forecast scenario prep"
def test_memory_search_blocks_unrequested_full_list_escalation():
events = [{
"tool": "manage_memory",
"command": "search\nphone number",
"output": "No memories found matching 'phone number'.",
"exit_code": 0,
}]
assert _memory_search_precedes_unrequested_list(events, False)
assert not _memory_search_precedes_unrequested_list(events, True)
def test_explicit_note_contents_request_extracts_title_for_locator_followup():
assert _parse_qwen_explicit_note_view(
"Find my note called Japan, then show me its contents."
) == "Japan"
assert _parse_qwen_explicit_note_view("Find my note called Japan") is None
def test_note_view_summary_preserves_body_content():
assert _ody_qwen_terminal_tool_summary({
"tool": "manage_notes",
"command": '{"action":"view","id":"abc123"}',
"output": "AI: - [abc123] **Japan**\nTokyo itinerary",
}) == "- [abc123] **Japan**\nTokyo itinerary"
def test_test_request_gets_a_bounded_project_runner_fallback():
command = _tui_local_fallback_shell_command("Run the tests in this repo")
assert command is not None
assert "runner=.venv/bin/python" in command
assert "runner=venv/bin/python" in command
assert "else runner=python" in command
assert '"$runner" -m pytest -q' in command
assert "tests/test_tui_round_cap.py" not in command
assert "npm test" in command
assert "find ." not in command
def test_tui_test_runner_prefers_workspace_python_environment():
command = _tui_local_test_runner_command()
assert "[ -x .venv/bin/python ]" in command
assert "runner=.venv/bin/python" in command
assert "[ -x venv/bin/python ]" in command
assert "runner=venv/bin/python" in command
assert "git rev-parse --path-format=absolute --git-common-dir" in command
assert '$(dirname "$git_common")/.venv/bin/python' in command
assert "else runner=python" in command
assert '"$runner" -m pytest -q' in command
assert "git diff --name-only --diff-filter=ACMR HEAD" in command
assert 'tests/test_${stem}.py' in command
def test_tui_smoke_test_runner_is_repository_agnostic():
assert _tui_smoke_test_request("test now")
assert _tui_smoke_test_request("Run tests now")
assert _tui_smoke_test_request("Run a quick smoke test in this repo")
assert _tui_smoke_test_request("Run tests, but keep it bounded and small")
assert not _tui_smoke_test_request("Run the tests in this repo")
command = _tui_local_smoke_test_runner_command()
assert "tests/test_tui_round_cap.py" not in command
assert "runner=.venv/bin/python" in command
assert '"$runner" -m pytest -q' in command
assert "npm test" in command
def test_tui_host_shell_test_content_never_assumes_odysseus_test_paths():
broad = json.loads(_tui_local_test_runner_host_shell_content("Run the tests"))
smoke = json.loads(_tui_local_test_runner_host_shell_content("Run a quick smoke test"))
full = json.loads(_tui_local_test_runner_host_shell_content("Run the full test suite"))
assert _tui_explicit_full_test_request("Run the full test suite")
assert not _tui_explicit_full_test_request("Run the tests")
assert "tests/test_tui_round_cap.py" not in broad["command"]
assert "tests/test_tui_round_cap.py" not in smoke["command"]
assert "tests/test_tui_round_cap.py" not in full["command"]
assert broad["command"] == smoke["command"]
assert broad["command"] != full["command"]
assert "git diff --name-only" in broad["command"]
assert "git diff --name-only" not in full["command"]
assert broad["timeout"] == 120
assert smoke["timeout"] == 120
assert full["timeout"] == 120
def test_tui_pytest_normalizer_preserves_target_and_uses_shared_runner():
command = _tui_normalize_pytest_command(
"python3 -m pytest -q tests/test_client.py -k nested_error"
)
assert command is not None
assert '"$runner" -m pytest -q tests/test_client.py -k nested_error' in command
assert "git rev-parse --path-format=absolute --git-common-dir" in command
assert _tui_normalize_pytest_command("pytest -q | tee results.txt") is None
def test_bash_block_request_gets_a_generic_workspace_probe():
command = _tui_local_fallback_shell_command("Do a bash block.")
assert command == "pwd; whoami; uname -srm"
def test_filename_edit_in_explicit_workspace_is_coding_request():
assert _looks_like_workspace_coding_request(
'Edit fixture.py in the active workspace: change VALUE from "before" to "after".'
)
def test_coding_project_request_gets_a_bounded_workspace_probe():
command = _tui_local_fallback_shell_command("I want to work on the Odysseus tui")
assert command is not None
assert 'workspace=$PWD' in command
assert "git rev-parse --show-toplevel" in command
assert "git_roots:" in command
assert "ls -la" in command
assert "find /" not in command
def test_invalid_backend_workspace_tools_collapse_to_one_host_action():
blocks, recovered = _tui_recover_invalid_local_tools(
[ToolBlock("get_workspace", ""), ToolBlock("ls", '{"path":"/app/data"}')],
"I want to work on the Odysseus tui",
)
assert recovered is True
assert [block.tool_type for block in blocks] == ["host_shell"]
assert "workspace=$PWD" in blocks[0].content
assert "/app/data" not in blocks[0].content
def test_host_bridge_coding_rules_do_not_conflict_with_backend_workspace_rules():
rules = _workspace_coding_rules("/host/home/project", host_bridge=True)
assert "host bridge owns" in rules
assert "do not call backend `get_workspace`" in rules
assert "Start by orienting with `get_workspace`" not in rules
def test_qwen_transport_markers_do_not_leak_into_tui_answer():
assert _normalize_ody_qwen_text_artifacts(
"|start|\nThe local IP for ajax is 192.168.1.20.\n|end|"
) == "The local IP for ajax is 192.168.1.20."
def test_network_summary_distinguishes_tailscale_from_lan_address():
assert _tui_network_summary(
"100.67.207.85 ajax.tail.example\n"
"5: wlan0 inet 192.168.1.8/24 scope global\n",
"ajax",
) == (
"ajax resolves to `100.67.207.85`.\n"
"That is a Tailscale address, not a LAN address.\n"
"This host's LAN address is `192.168.1.8`."
)
def test_project_discovery_fallback_lists_real_project_markers_not_home_noise():
command = _tui_local_fallback_shell_command(
"Search my computer for the local project I was working on"
)
assert command is not None
assert "git_roots:" in command
assert "project_manifests:" in command
assert "node_modules" in command
assert "ls -la" not in command
def test_project_discovery_summary_is_bounded_and_uses_only_git_roots():
summary = _tui_project_discovery_summary(
"workspace=/home/user\n"
"git_roots:\n./project-a\n./project-a\n./project-b\n"
"project_manifests:\n./project-a/pyproject.toml\n"
)
assert summary == (
"Projects found in the active workspace:\n"
"- ./project-a\n"
"- ./project-b\n"
"Select a project path and I can inspect its files or run its tests."
)
def test_model_listing_has_a_deterministic_terminal_summary():
assert _ody_qwen_terminal_tool_summary({
"tool": "list_models",
"command": "",
"output": "Models:\n- qwen\n- gpt",
}) == "Models:\n- qwen\n- gpt"
def test_model_listing_summary_is_bounded():
output = "Available models (100 total):\n" + "\n".join(f"- model-{i}" for i in range(100))
summary = _ody_qwen_terminal_tool_summary({
"tool": "list_models",
"command": "",
"output": output,
})
assert "more models omitted" in summary
assert len(summary.splitlines()) == 25
def test_network_prompt_routes_away_from_container_shell():
routed = _route_tui_local_workspace_tools(
{"bash", "web_search"},
client_runtime_context={
"surface": "odysseus-tui",
"session_cwd": "/home/tester",
"host_shell_bridge": {"url": "http://host.docker.internal:17654/run"},
"runtime_execution_contract": {
"local_workspace_tasks": "use_host_shell_bridge",
},
},
text="Find the local IP to SSH into ajax.",
workspace=None,
)
assert routed is not None
assert "host_shell" in routed
assert "bash" not in routed
def test_local_only_prompt_gets_a_small_tool_surface():
routed = _route_tui_local_workspace_tools(
{"bash", "web_search", "web_fetch", "manage_memory", "mcp__builtin_browser__browser_click"},
client_runtime_context={
"surface": "odysseus-tui",
"session_cwd": "/home/tester",
"host_shell_bridge": {"url": "http://host.docker.internal:17654/run"},
"runtime_execution_contract": {
"local_workspace_tasks": "use_host_shell_bridge",
},
},
text="Inspect this local project using local tools only.",
workspace=None,
)
assert routed == {
"host_shell", "ask_user", "update_plan", "grep", "ls", "glob", "read_file",
}
def test_named_private_repo_discovery_stays_on_tui_host_surface():
prompt = "can you find odysseus private maintainer repo"
context = _host_bridge_context()
assert _tui_local_workspace_turn(
prompt,
workspace="/home/pewds/odysseus",
client_runtime_context=context,
)
assert _route_tui_local_workspace_tools(
{"get_workspace", "ls", "host_shell", "web_search"},
client_runtime_context=context,
text=prompt,
workspace="/home/pewds/odysseus",
) == {"host_shell", "ask_user", "update_plan"}
def test_bash_block_prompt_stays_on_tui_host_shell_surface():
routed = _route_tui_local_workspace_tools(
{"bash", "host_shell", "web_search"},
client_runtime_context=_host_bridge_context(),
text="Do a bash block.",
workspace=None,
)
assert routed == {"host_shell", "ask_user", "update_plan"}
def test_tui_session_cwd_test_now_routes_to_host_shell_without_bridge():
routed = _route_tui_local_workspace_tools(
{"web_search"},
client_runtime_context={"surface": "odysseus-tui", "session_cwd": "/tmp/repo"},
text="test now",
workspace="/tmp/repo",
)
assert routed == {"host_shell", "ask_user", "update_plan"}
def test_tui_session_cwd_personal_request_does_not_route_to_host_shell_without_bridge():
routed = _route_tui_local_workspace_tools(
{"manage_notes"},
client_runtime_context={"surface": "odysseus-tui", "session_cwd": "/tmp/repo"},
text="What's my notes?",
workspace="/tmp/repo",
)
assert routed == {"manage_notes"}
def test_tui_bridge_general_chat_does_not_route_to_host_shell():
for prompt in ("Where is Sweden on a map?", "sned links", "hi"):
routed = _route_tui_local_workspace_tools(
{"host_shell", "web_search"},
client_runtime_context=_host_bridge_context(),
text=prompt,
workspace=None,
)
assert routed == {"host_shell", "web_search"}
def test_local_coding_prompt_exposes_transactional_patch_tools():
routed = _route_tui_local_workspace_tools(
{"bash", "apply_patch", "todowrite", "web_search"},
client_runtime_context={
"surface": "odysseus-tui",
"session_cwd": "/home/tester/project",
"host_shell_bridge": {"url": "http://host.docker.internal:17654/run", "token": "x"},
"runtime_execution_contract": {
"local_workspace_tasks": "use_host_shell_bridge",
},
},
text="Fix the parser bug in this local project and run the tests.",
workspace=None,
)
assert routed == {
"host_shell", "ask_user", "update_plan", "read_file", "write_file",
"apply_patch", "edit_file", "todowrite", "grep", "ls", "glob",
}
def test_local_coding_prompt_routes_exact_edits_to_the_host_bridge():
routed = _route_tui_local_workspace_tools(
{"edit_file", "apply_patch", "todowrite"},
client_runtime_context={
"surface": "odysseus-tui",
"session_cwd": "/home/tester/project",
"host_shell_bridge": {"url": "http://host.docker.internal:17654/run", "token": "x"},
"runtime_execution_contract": {
"local_workspace_tasks": "use_host_shell_bridge",
},
},
text="Change status=old to status=new in the file config.txt and verify it.",
workspace=None,
)
assert "edit_file" in routed
def test_explicit_external_lookup_keeps_web_tools():
routed = _route_tui_local_workspace_tools(
{"bash", "web_search", "web_fetch"},
client_runtime_context={
"surface": "odysseus-tui",
"session_cwd": "/home/tester",
"host_shell_bridge": {"url": "http://host.docker.internal:17654/run"},
"runtime_execution_contract": {
"local_workspace_tasks": "use_host_shell_bridge",
},
},
text="Inspect this project, then check the latest GitHub issue online.",
workspace=None,
)
assert {"host_shell", "web_search", "web_fetch"}.issubset(routed)
def test_negated_web_request_stays_local_only():
routed = _route_tui_local_workspace_tools(
{"bash", "web_search", "web_fetch"},
client_runtime_context={
"surface": "odysseus-tui",
"session_cwd": "/home/tester",
"host_shell_bridge": {"url": "http://host.docker.internal:17654/run"},
"runtime_execution_contract": {
"local_workspace_tasks": "use_host_shell_bridge",
},
},
text="Inspect the local project. Do not search the web.",
workspace=None,
)
assert routed == {
"host_shell", "ask_user", "update_plan", "grep", "ls", "glob", "read_file",
}
def test_negated_web_phrase_does_not_make_local_project_external():
prompt = "Search my computer for the local project I was working on. Do not use the web."
assert not _looks_like_explicit_tui_app_or_external_request(prompt)
assert _tui_local_workspace_turn(
prompt,
workspace=None,
client_runtime_context=_host_bridge_context(),
)
def test_tui_local_turn_is_explicitly_narrowed():
assert _tui_local_workspace_turn(
"Find the local IP to SSH into ajax.",
workspace="/home/tester",
client_runtime_context={
"surface": "odysseus-tui",
"session_cwd": "/home/tester",
"host_shell_bridge": {"url": "http://host.docker.internal:17654/run"},
"runtime_execution_contract": {
"local_workspace_tasks": "use_host_shell_bridge",
},
},
)
def test_local_network_turn_is_not_short_circuited_by_web_disabled_guard():
context = _host_bridge_context()
assert not _web_search_unavailable_for_turn(
{"web", "files"},
set(WEB_TOOL_NAMES),
"Find the local IP for ajax; do not use web search.",
context,
None,
)
assert _web_search_unavailable_for_turn(
{"web"},
set(WEB_TOOL_NAMES),
"Search the web for the latest Qwen release.",
context,
None,
)
def test_local_project_turn_is_not_short_circuited_by_web_disabled_guard():
assert not _web_search_unavailable_for_turn(
{"web", "files"},
{"web_search", "web_fetch"},
"Find the local Odysseus TUI project; do not search the web.",
_host_bridge_context(),
None,
)
def _host_bridge_context():
return {
"surface": "odysseus-tui",
"session_cwd": "/home/tester/project",
"host_shell_bridge": {"url": "http://host.docker.internal:17654/run"},
"runtime_execution_contract": {
"local_workspace_tasks": "use_host_shell_bridge",
},
}
def test_active_workspace_does_not_hijack_saved_memory_lookup():
prompt = "Search saved memory for the Harbor Guji delay."
assert _looks_like_explicit_tui_personal_domain_request(prompt)
assert not _tui_local_workspace_turn(
prompt,
workspace="/home/tester/project",
client_runtime_context=_host_bridge_context(),
)
assert _route_tui_local_workspace_tools(
{"host_shell", "manage_memory", "search_chats"},
client_runtime_context=_host_bridge_context(),
text=prompt,
workspace="/home/tester/project",
) == {"host_shell", "manage_memory", "search_chats"}
def test_active_workspace_does_not_hijack_task_list():
prompt = "List my tasks."
assert _looks_like_explicit_tui_personal_domain_request(prompt)
assert not _tui_local_workspace_turn(
prompt,
workspace="/home/tester/project",
client_runtime_context=_host_bridge_context(),
)
def test_active_workspace_does_not_hijack_chat_session_list():
prompt = "List my chat sessions."
assert _looks_like_explicit_tui_personal_domain_request(prompt)
assert not _tui_local_workspace_turn(
prompt,
workspace="/home/tester/project",
client_runtime_context=_host_bridge_context(),
)
def test_rejected_backend_workspace_does_not_hijack_saved_memory_lookup():
prompt = "Search saved memory for the Harbor Guji delay. Do not use the workspace."
assert not _tui_local_workspace_turn(
prompt,
workspace=None,
client_runtime_context=_host_bridge_context(),
)
def test_active_workspace_does_not_hijack_prior_chat_search():
prompt = "Find the prior chat where we fixed the parser."
assert _looks_like_explicit_tui_personal_domain_request(prompt)
assert not _tui_local_workspace_turn(
prompt,
workspace="/home/tester/project",
client_runtime_context=_host_bridge_context(),
)
def test_code_file_named_notes_stays_on_host_workspace():
prompt = "Fix the parser bug in notes.py and run the tests."
assert not _looks_like_explicit_tui_personal_domain_request(prompt)
assert _tui_local_workspace_turn(
prompt,
workspace="/home/tester/project",
client_runtime_context=_host_bridge_context(),
)
def test_active_workspace_does_not_hijack_model_picker_or_cookbook_requests():
for prompt in (
"What models are running on Odysseus?",
"Switch to the Qwen model.",
):
assert _looks_like_explicit_tui_app_or_external_request(prompt)
assert not _tui_local_workspace_turn(
prompt,
workspace="/home/tester/project",
client_runtime_context=_host_bridge_context(),
)
def test_model_registry_requests_are_distinct_from_model_switches():
for prompt in (
"What models are running on the server?",
"List the available models.",
"Show served models.",
):
assert _is_qwen_explicit_model_list_request(prompt), prompt
for prompt in ("Switch to the Qwen model.", "Use the model qwen35."):
assert not _is_qwen_explicit_model_list_request(prompt), prompt
def test_latest_email_request_is_singular_but_email_search_is_not():
for prompt in ("What's my latest email?", "Show the newest mail."):
assert _is_qwen_explicit_latest_email_request(prompt), prompt
for prompt in ("Find emails from Runpod", "Search my inbox for messages about invoices"):
assert not _is_qwen_explicit_latest_email_request(prompt), prompt
def test_active_workspace_does_not_hijack_explicit_web_search():
prompt = "Search the web for the latest Qwen release."
assert _looks_like_explicit_tui_app_or_external_request(prompt)
assert not _tui_local_workspace_turn(
prompt,
workspace="/home/tester/project",
client_runtime_context=_host_bridge_context(),
)
def test_tdd_skill_plus_parser_task_stays_on_host_workspace():
prompt = "Use the TDD skill to fix the parser and run tests."
assert not _looks_like_explicit_tui_personal_domain_request(prompt)
assert _tui_local_workspace_turn(
prompt,
workspace="/home/tester/project",
client_runtime_context=_host_bridge_context(),
)
routed = _route_tui_local_workspace_tools(
{"host_shell", "manage_skills", "apply_patch", "todowrite"},
client_runtime_context=_host_bridge_context(),
text=prompt,
workspace="/home/tester/project",
)
assert "host_shell" in routed
assert "manage_skills" in routed