Files
odysseus/tests/test_client_tool_routing.py
T

1241 lines
42 KiB
Python

from __future__ import annotations
import ast
import asyncio
import base64
import json
import re
from pathlib import Path
from types import SimpleNamespace
from unittest.mock import patch
from unittest.mock import AsyncMock
from routes.chat_routes import _agent_turn_cwd
import routes.chat_routes as chat_routes
from src import tool_execution as _te
from src.agent_loop import _is_explicit_local_network_request
# Hold module-object references (not just from-imported names): other test
# modules re-import src.tool_execution via sys.modules pops, so string-target
# patches can land on a DIFFERENT fresh module object than the one these
# tests execute from. patch.object on the held reference always matches.
execute_tool_block = _te.execute_tool_block
_bridge_post = _te._bridge_post
_client_bridge = _te._client_bridge
_ROUTED_BRIDGE_TOOLS = _te._ROUTED_BRIDGE_TOOLS
async def _execute_tool_block_for_unit_tests(*args, **kwargs):
"""Use the explicit non-security-context test mode for dispatch tests."""
kwargs.setdefault("security_context", _te.NO_TOOL_SECURITY_CONTEXT)
return await _te.execute_tool_block(*args, **kwargs)
execute_tool_block = _execute_tool_block_for_unit_tests
ROOT = Path(__file__).resolve().parent.parent
BRIDGE_CTX = {
"surface": "odysseus-tui",
"session_cwd": "/home/pewds/project",
"host_shell_bridge": {"url": "http://127.0.0.1:47475/run", "token": "secret"},
}
def test_client_bridge_extracts_valid_bridge_only():
assert _client_bridge(BRIDGE_CTX)["token"] == "secret"
assert _client_bridge({"host_shell_bridge": BRIDGE_CTX["host_shell_bridge"]}) is None
assert _client_bridge({
"surface": "odysseus-tui",
"host_shell_bridge": {"url": "http://8.8.8.8:47475/run", "token": "x"},
}) is None
assert _client_bridge({"host_shell_bridge": {"url": "", "token": "x"}}) is None
assert _client_bridge({"host_shell_bridge": {"url": "http://x/run"}}) is None
assert _client_bridge(None) is None
assert _client_bridge({"host_shell_bridge": "nope"}) is None
def test_local_network_requests_are_distinct_from_web_search():
assert _is_explicit_local_network_request(
"Show the local IPv4 interfaces and default route"
)
assert _is_explicit_local_network_request(
"Find the LAN IP for ajax so I can SSH to it"
)
assert not _is_explicit_local_network_request(
"Search the web for the latest local network troubleshooting guide"
)
def _run(coro):
return asyncio.run(coro)
class _admin_gate:
"""Bypass the admin/single-user gates in tool dispatch for tests.
Production TUI turns carry the owner (admin on this deployment), so the
public-tool gate passes there; unit tests have no auth manager.
"""
def __enter__(self):
self._patch = patch.object(_te, "_owner_is_admin", lambda owner: True)
self._patch.start()
return self
def __exit__(self, *exc):
self._patch.stop()
return False
def test_routed_bash_sends_command_to_run_endpoint():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload, kwargs))
return {"stdout": "host-side", "stderr": "", "exit_code": 0, "cwd": "/home/pewds/project"}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="bash", content="pwd && ls"),
client_runtime_context=dict(BRIDGE_CTX),
)
)
path, payload, kwargs = calls[0]
assert path == "/run"
assert payload["command"] == "pwd && ls"
assert isinstance(payload["request_id"], str) and payload["request_id"]
assert kwargs["err_prefix"] == "bash"
assert result["stdout"] == "host-side"
assert desc.startswith("bash:")
def test_routed_python_uses_exec_argv_not_shell():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload))
return {"stdout": "", "stderr": "", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
_run(
execute_tool_block(
SimpleNamespace(tool_type="python", content='print("hi")'),
client_runtime_context=dict(BRIDGE_CTX),
)
)
path, payload = calls[0]
assert path == "/run"
assert payload["exec"] == ["python3", "-I", "-c", 'print("hi")']
assert isinstance(payload["request_id"], str) and payload["request_id"]
assert "command" not in payload
def test_routed_read_file_uses_read_endpoint():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload))
return {"output": "file body", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="read_file", content="/home/pewds/project/main.py"),
client_runtime_context=dict(BRIDGE_CTX),
)
)
path, payload = calls[0]
assert path == "/read"
assert payload == {"path": "/home/pewds/project/main.py"}
assert result["output"] == "file body"
assert "read_file" in desc
def test_routed_read_file_preserves_json_line_range():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload))
return {"output": "selected lines", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(
tool_type="read_file",
content='{"path":"odysseus_tui/client.py","offset":55,"limit":40}',
),
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert calls == [
(
"/read",
{"path": "odysseus_tui/client.py", "offset": 55, "limit": 40},
)
]
assert desc == "read_file: odysseus_tui/client.py"
assert result["output"] == "selected lines"
def test_routed_read_file_rejects_malformed_json_without_host_call():
calls = []
async def fake_bridge_post(*args, **kwargs):
calls.append((args, kwargs))
return {"output": "unexpected", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="read_file", content='{"path":"broken"'),
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert desc == "read_file: invalid arguments"
assert result["exit_code"] == 1
assert calls == []
def test_routed_read_file_rejects_empty_path_without_host_call():
calls = []
async def fake_bridge_post(*args, **kwargs):
calls.append((args, kwargs))
return {"output": "unexpected", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="read_file", content=""),
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert desc == "read_file: invalid arguments"
assert result["exit_code"] == 1
assert calls == []
def test_routed_read_file_rejects_invalid_line_range_without_host_call():
calls = []
async def fake_bridge_post(*args, **kwargs):
calls.append((args, kwargs))
return {"output": "unexpected", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
for offset, limit in ((-1, 1), (0, -1), ("1", 2), (True, 2)):
desc, result = _run(
execute_tool_block(
SimpleNamespace(
tool_type="read_file",
content=json.dumps({"path": "main.py", "offset": offset, "limit": limit}),
),
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert desc == "read_file: invalid arguments"
assert result["exit_code"] == 1
assert calls == []
def test_routed_read_file_rejects_non_string_or_multiline_path():
calls = []
async def fake_bridge_post(*args, **kwargs):
calls.append((args, kwargs))
return {"output": "unexpected", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
for path in (123, ["main.py"], "main.py\nother.py"):
desc, result = _run(execute_tool_block(
SimpleNamespace(
tool_type="read_file",
content=json.dumps({"path": path}),
),
client_runtime_context=dict(BRIDGE_CTX),
))
assert desc == "read_file: invalid arguments"
assert result["exit_code"] == 1
assert calls == []
def test_routed_write_file_sends_base64_body(tmp_path):
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload))
return {"output": "wrote", "exit_code": 0}
target = tmp_path / "out.txt"
body = "line one\nline two with 'quotes' and $vars\n"
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
_run(
execute_tool_block(
SimpleNamespace(tool_type="write_file", content=f"{target}\n{body}"),
client_runtime_context=dict(BRIDGE_CTX),
)
)
path, payload = calls[0]
assert path == "/write"
assert payload["path"] == str(target)
assert base64.b64decode(payload["content_b64"]).decode("utf-8") == body
assert re.fullmatch(r"[A-Za-z0-9_-]+", payload["request_id"])
def test_routed_write_file_decodes_native_json_arguments():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload))
return {"output": "wrote", "exit_code": 0}
body = "print('héllo')\n"
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(
tool_type="write_file",
content=json.dumps({"path": "src/demo.py", "content": body}),
),
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert desc == "write_file: src/demo.py"
assert result["exit_code"] == 0
assert calls[0][0] == "/write"
assert calls[0][1]["path"] == "src/demo.py"
assert base64.b64decode(calls[0][1]["content_b64"]).decode("utf-8") == body
def test_routed_write_file_rejects_malformed_native_json_without_host_call():
calls = []
async def fake_bridge_post(*args, **kwargs):
calls.append((args, kwargs))
return {"output": "unexpected", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="write_file", content='{"path":"broken"'),
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert desc == "write_file: invalid arguments"
assert result["exit_code"] == 1
assert calls == []
def test_routed_write_file_rejects_non_text_content_without_host_call():
calls = []
async def fake_bridge_post(*args, **kwargs):
calls.append((args, kwargs))
return {"output": "unexpected", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(
tool_type="write_file",
content=json.dumps({"path": "data.json", "content": {"value": 1}}),
),
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert desc == "write_file: invalid arguments"
assert result == {"error": "write_file: content must be a string", "exit_code": 1}
assert calls == []
def test_routed_file_mutations_reject_non_string_or_multiline_path():
calls = []
async def fake_bridge_post(*args, **kwargs):
calls.append((args, kwargs))
return {"output": "unexpected", "exit_code": 0}
cases = (
("write_file", {"path": 123, "content": "body"}),
("write_file", {"path": "a.txt\nb.txt", "content": "body"}),
("edit_file", {"path": ["a.txt"], "old_string": "a", "new_string": "b"}),
("edit_file", {"path": "a.txt\nb.txt", "old_string": "a", "new_string": "b"}),
)
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
for tool, args in cases:
desc, result = _run(execute_tool_block(
SimpleNamespace(tool_type=tool, content=json.dumps(args)),
client_runtime_context=dict(BRIDGE_CTX),
))
assert desc == f"{tool}: invalid arguments"
assert result["exit_code"] == 1
assert calls == []
def test_routed_bash_background_marker_detaches_on_client():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload))
return {"output": "detached", "exit_code": 0, "detached": True}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="bash", content="#!bg\nsleep 300"),
session_id="sid",
client_runtime_context=dict(BRIDGE_CTX),
)
)
path, payload = calls[0]
assert path == "/run"
assert payload["detach"] is True
assert payload["command"] == "sleep 300"
assert result["output"] == "detached"
def test_routed_implicit_long_bash_is_detached_and_polled():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append(payload)
if len(calls) == 1:
return {"status": "running", "job_id": "job-implicit", "output": "started"}
return {"status": "done", "job_id": "job-implicit", "output": "finished", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
_, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="bash", content="sleep 22; printf done"),
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert calls[0]["detach"] is True
assert calls[0]["command"] == "sleep 22; printf done"
assert calls[1] == {"job_id": "job-implicit"}
assert result["output"] == "finished"
def test_no_bridge_falls_back_to_backend_execution():
async def fake_mcp(tool, content, progress_cb=None, **kwargs):
return {"output": f"backend-side {tool}", "exit_code": 0}
with patch.object(_te, "_owner_is_admin", lambda owner: True), \
patch.object(_te, "_call_mcp_tool", fake_mcp):
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="bash", content="pwd"),
client_runtime_context={"surface": "webui"},
)
)
assert result["output"] == "backend-side bash"
def test_disabled_tool_still_blocked_with_bridge_present():
with _admin_gate():
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="bash", content="pwd"),
disabled_tools={"bash"},
client_runtime_context=dict(BRIDGE_CTX),
)
)
assert "BLOCKED" in desc
assert result["exit_code"] == 1
def test_routed_tools_set_matches_computer_tools():
assert _ROUTED_BRIDGE_TOOLS == {
"bash", "python", "grep", "ls", "glob", "list_dir", "find_files",
"read_file", "write_file", "edit_file",
}
def test_routed_list_dir_preserves_range_and_uses_list_endpoint():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload, kwargs))
return {"output": "src\ntests", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(execute_tool_block(
SimpleNamespace(
tool_type="list_dir",
content='{"path":"src","offset":2,"limit":10}',
),
client_runtime_context=dict(BRIDGE_CTX),
))
assert desc == "list_dir: src"
assert calls[0][0] == "/list"
assert calls[0][1] == {
"path": "src", "offset": 2, "limit": 10, "recursive": True,
}
assert result["exit_code"] == 0
def test_routed_find_files_uses_find_endpoint():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload, kwargs))
return {"output": "src/main.py", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(execute_tool_block(
SimpleNamespace(
tool_type="find_files",
content='{"pattern":"\\\\.py$","path":"src"}',
),
client_runtime_context=dict(BRIDGE_CTX),
))
assert desc == "find_files: \\.py$"
assert calls[0][0] == "/find"
assert calls[0][1] == {"pattern": "\\.py$", "path": "src"}
assert result["exit_code"] == 0
def test_canonical_ls_and_glob_route_to_tui_discovery_endpoints():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload))
return {"output": "ok", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
ls_desc, _ = _run(execute_tool_block(
SimpleNamespace(tool_type="ls", content='{"path":"src"}'),
client_runtime_context=dict(BRIDGE_CTX),
))
glob_desc, _ = _run(execute_tool_block(
SimpleNamespace(tool_type="glob", content='{"pattern":"**/*.py","path":"src"}'),
client_runtime_context=dict(BRIDGE_CTX),
))
assert ls_desc == "ls: src"
assert calls[0] == (
"/list",
{"path": "src", "offset": 0, "limit": 0, "recursive": False},
)
assert glob_desc == "glob: **/*.py"
assert calls[1][0] == "/find"
assert calls[1][1] == {"path": "src", "glob": "**/*.py"}
def test_canonical_grep_routes_structured_search_to_tui_host():
calls = []
async def fake_bridge_post(bridge, path, payload, **kwargs):
calls.append((path, payload))
return {"output": "src/main.py:1:needle", "exit_code": 0}
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
desc, result = _run(execute_tool_block(
SimpleNamespace(
tool_type="grep",
content=(
'{"pattern":"needle","path":"src","glob":"**/*.py",'
'"ignore_case":true,"max_results":25}'
),
),
client_runtime_context=dict(BRIDGE_CTX),
))
assert desc == "grep: needle"
assert calls == [("/grep", {
"pattern": "needle", "path": "src", "glob": "**/*.py",
"ignore_case": True, "max_results": 25,
})]
assert result["exit_code"] == 0
def test_routed_discovery_tools_reject_malformed_arguments_without_host_call():
calls = []
async def fake_bridge_post(*args, **kwargs):
calls.append((args, kwargs))
return {"output": "unexpected", "exit_code": 0}
cases = (
("list_dir", {"path": 123}),
("list_dir", {"path": ".", "offset": "2"}),
("find_files", {"pattern": ""}),
("find_files", {"pattern": ["py"], "path": "."}),
("grep", {"pattern": "needle", "ignore_case": "yes"}),
("grep", {"pattern": "needle", "max_results": 0}),
("grep", {"pattern": ["needle"]}),
)
with _admin_gate(), patch.object(_te, "_bridge_post", fake_bridge_post):
for tool, args in cases:
desc, result = _run(execute_tool_block(
SimpleNamespace(tool_type=tool, content=json.dumps(args)),
client_runtime_context=dict(BRIDGE_CTX),
))
assert desc == f"{tool}: invalid arguments"
assert result["exit_code"] == 1
assert calls == []
def test_agent_turn_cwd_prefers_raw_host_cwd_when_bridge_advertised():
sess = SimpleNamespace(cwd="/workspace/project") # translated container path
assert _agent_turn_cwd(sess, dict(BRIDGE_CTX)) == "/home/pewds/project"
def test_agent_turn_cwd_keeps_session_cwd_without_bridge():
sess = SimpleNamespace(cwd="/workspace/project")
ctx = {"surface": "odysseus-tui", "session_cwd": "/home/pewds/project"}
assert _agent_turn_cwd(sess, ctx) == "/workspace/project"
assert _agent_turn_cwd(sess, {"surface": "webui"}) == "/workspace/project"
def test_bridge_post_targets_endpoint_paths():
class FakeResp:
status_code = 200
def json(self):
return {"stdout": "ok", "exit_code": 0}
text = ""
posted = {}
class FakeClient:
def __init__(self, timeout=None):
posted["timeout"] = timeout
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def post(self, url, headers=None, json=None):
posted["url"] = url
posted["headers"] = headers
posted["json"] = json
return FakeResp()
import httpx
with patch.object(httpx, "AsyncClient", FakeClient):
result = _run(
_bridge_post(
{"url": "http://127.0.0.1:47475/run", "token": "secret"},
"/write",
{"path": "/tmp/x", "content_b64": "aGk="},
timeout_s=30.0,
err_prefix="write_file",
)
)
assert posted["url"] == "http://127.0.0.1:47475/write"
assert posted["headers"] == {"x-odysseus-tui-bridge-token": "secret"}
assert result["exit_code"] == 0
assert posted["timeout"].connect == 5.0
assert posted["timeout"].write == 10.0
assert posted["timeout"].read == 35.0
def test_bridge_post_rejects_untrusted_url_without_network_call():
class UnexpectedClient:
def __init__(self, *args, **kwargs):
raise AssertionError("invalid bridge URL must not reach the network")
import httpx
with patch.object(httpx, "AsyncClient", UnexpectedClient):
result = _run(
_bridge_post(
{"url": "http://8.8.8.8:47475/run", "token": "secret"},
"/run",
{"command": "true"},
timeout_s=30.0,
err_prefix="bash",
)
)
assert result == {"error": "bash: invalid TUI host bridge", "exit_code": 1}
def test_bridge_post_treats_empty_or_error_payload_as_failure():
class FakeResp:
status_code = 200
text = ""
def __init__(self, payload):
self.payload = payload
def json(self):
return self.payload
payloads = iter(({}, {"error": "token rejected"}))
class FakeClient:
def __init__(self, timeout=None):
pass
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def post(self, *args, **kwargs):
return FakeResp(next(payloads))
import httpx
with patch.object(httpx, "AsyncClient", FakeClient):
empty = _run(_bridge_post(BRIDGE_CTX["host_shell_bridge"], "/run", {}, timeout_s=1, err_prefix="bash"))
errored = _run(_bridge_post(BRIDGE_CTX["host_shell_bridge"], "/run", {}, timeout_s=1, err_prefix="bash"))
assert empty == {"error": "bash: bridge returned an empty response", "exit_code": 1}
assert errored == {"error": "token rejected", "exit_code": 1}
def test_bridge_post_http_or_payload_error_overrides_false_success_code():
class FakeResp:
text = ""
def __init__(self, status_code, payload):
self.status_code = status_code
self.payload = payload
def json(self):
return self.payload
responses = iter((
FakeResp(401, {"error": "token rejected", "exit_code": 0}),
FakeResp(200, {"error": "host failed", "exit_code": 0}),
))
class FakeClient:
def __init__(self, timeout=None):
pass
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def post(self, *args, **kwargs):
return next(responses)
import httpx
with patch.object(httpx, "AsyncClient", FakeClient):
unauthorized = _run(_bridge_post(
BRIDGE_CTX["host_shell_bridge"], "/run", {}, timeout_s=1, err_prefix="bash"
))
host_error = _run(_bridge_post(
BRIDGE_CTX["host_shell_bridge"], "/run", {}, timeout_s=1, err_prefix="bash"
))
assert unauthorized["exit_code"] == 1
assert host_error["exit_code"] == 1
def test_bridge_post_rejects_malformed_exit_code():
class FakeResp:
status_code = 200
text = ""
def json(self):
return {"output": "looks successful", "exit_code": "zero"}
class FakeClient:
def __init__(self, timeout=None):
pass
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def post(self, *args, **kwargs):
return FakeResp()
import httpx
with patch.object(httpx, "AsyncClient", FakeClient):
result = _run(_bridge_post(
BRIDGE_CTX["host_shell_bridge"], "/run", {}, timeout_s=1,
err_prefix="bash",
))
assert result == {
"error": "bash: bridge returned an invalid exit_code",
"exit_code": 1,
}
def test_bridge_post_reports_empty_invalid_json_response():
class FakeResp:
status_code = 200
text = ""
def json(self):
raise ValueError("not JSON")
class FakeClient:
def __init__(self, timeout=None):
pass
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def post(self, *args, **kwargs):
return FakeResp()
import httpx
with patch.object(httpx, "AsyncClient", FakeClient):
result = _run(_bridge_post(
BRIDGE_CTX["host_shell_bridge"], "/run", {}, timeout_s=1,
err_prefix="bash",
))
assert result == {
"error": "bash: bridge returned invalid JSON",
"exit_code": 1,
}
def test_bridge_post_cancellation_schedules_host_operation_cancel():
class FakeClient:
def __init__(self, timeout=None):
pass
async def __aenter__(self):
return self
async def __aexit__(self, *exc):
return False
async def post(self, *args, **kwargs):
self.post_entered.set()
await asyncio.Event().wait()
async def exercise(path):
import httpx
entered = asyncio.Event()
FakeClient.post_entered = entered
cancel_request = AsyncMock()
with patch.object(httpx, "AsyncClient", FakeClient), patch.object(
_te, "_cancel_bridge_request", cancel_request
):
task = asyncio.create_task(_bridge_post(
BRIDGE_CTX["host_shell_bridge"],
path,
{"command": "sleep 900", "request_id": "request-123"},
timeout_s=900,
err_prefix="bash",
))
await entered.wait()
task.cancel()
try:
await task
except asyncio.CancelledError:
pass
await asyncio.sleep(0)
cancel_request.assert_awaited_once_with(
BRIDGE_CTX["host_shell_bridge"], "request-123"
)
for path in ("/run", "/write", "/edit"):
_run(exercise(path))
# --------------------------------------------------------------------- #
# chat_routes wiring — source-inspection style (matches dev-tree tests)
# --------------------------------------------------------------------- #
def test_chat_stream_parses_and_injects_tui_runtime_context():
source = (ROOT / "routes" / "chat_routes.py").read_text(encoding="utf-8")
tree = ast.parse(source)
chat_stream = next(
node for node in ast.walk(tree)
if isinstance(node, ast.AsyncFunctionDef) and node.name == "chat_stream"
)
chat_stream_source = ast.get_source_segment(source, chat_stream)
assert "_parse_client_runtime_context(" in chat_stream_source
assert 'form_data.get("client_runtime_context")' in chat_stream_source
assert "_client_runtime_context_system_message(" in chat_stream_source
assert "disabled_tools=disabled_tools" in chat_stream_source
assert "ctx.messages.insert(0, runtime_msg)" in chat_stream_source
assert "cwd=_agent_turn_cwd(sess, client_runtime_context)" in chat_stream_source
assert "client_runtime_context=client_runtime_context" in chat_stream_source
assert "_stream_agent_with_execution_bridge(" in chat_stream_source
assert "_external_execution_bridge(client_runtime_context)" in chat_stream_source
def test_native_runtime_context_accepts_only_loopback_execution_bridge():
base = {
"surface": "odysseus-native",
"terminal_agent": True,
"interaction_mode": "cook",
"unattended_mode": True,
}
valid = chat_routes._parse_client_runtime_context({
**base,
"external_execution_bridge": {
"url": "http://127.0.0.1:47123/execute",
"token": "a-secure-request-token",
"supported_tools": ["send_email", "send_email", "bad tool"],
},
})
assert valid["external_execution_bridge"] == {
"url": "http://127.0.0.1:47123/execute",
"token": "a-secure-request-token",
"supported_tools": ["send_email"],
}
for url in (
"https://127.0.0.1:47123/execute",
"http://example.com:47123/execute",
"http://127.0.0.1:47123/",
):
parsed = chat_routes._parse_client_runtime_context({
**base,
"external_execution_bridge": {
"url": url,
"token": "a-secure-request-token",
"supported_tools": ["send_email"],
},
})
assert "external_execution_bridge" not in parsed
def test_chat_stream_restores_persisted_workspace_for_resumed_sessions():
source = Path(chat_routes.__file__).read_text()
tree = ast.parse(source)
chat_stream = next(
node for node in ast.walk(tree)
if isinstance(node, ast.AsyncFunctionDef) and node.name == "chat_stream"
)
chat_stream_source = ast.get_source_segment(source, chat_stream)
assert "_resolve_persisted_session_workspace(" in chat_stream_source
assert "current_workspace=workspace" in chat_stream_source
assert "current_rejected=workspace_rejected" in chat_stream_source
def test_context_info_advertises_workspace_and_tool_policy():
source = (ROOT / "routes" / "session_routes.py").read_text(encoding="utf-8")
tree = ast.parse(source)
context_info = next(
node for node in ast.walk(tree)
if isinstance(node, ast.AsyncFunctionDef) and node.name == "get_context_info"
)
fn_source = ast.get_source_segment(source, context_info)
assert "workspace_mount_pairs" in fn_source
assert "backend_workspace_path" in fn_source
assert '"tool_policy"' in fn_source
assert '"computer_tools"' in fn_source
# --------------------------------------------------------------------- #
# runtime-context sanitization (helpers ported verbatim from dev tree)
# --------------------------------------------------------------------- #
def test_runtime_context_preserves_sanitized_active_skills():
from routes.chat_routes import (
_client_runtime_context_system_message,
_parse_legacy_client_runtime_context as _parse_client_runtime_context,
)
context = _parse_client_runtime_context(
{
"surface": "odysseus-tui",
"session_cwd": "/home/pewds/odysseus-tui",
"active_skills": ["tdd", "`openai-docs`", "../bad", "tdd"],
"active_skill_details": [
{
"name": "tdd",
"description": " Red-green-refactor\nworkflow ",
"source": "file: /home/pewds/.codex/skills/tdd/SKILL.md",
},
{"name": "../bad", "description": "drop me"},
],
}
)
assert context["active_skills"] == ["tdd", "openai-docs"]
assert context["session_cwd"] == "/home/pewds/odysseus-tui"
assert context["active_skill_details"] == [
{
"name": "tdd",
"description": "Red-green-refactor workflow",
"source": "file: /home/pewds/.codex/skills/tdd/SKILL.md",
}
]
message = _client_runtime_context_system_message(context)
assert message is not None
assert "session_cwd: /home/pewds/odysseus-tui" in message["content"]
assert "active_skills: tdd, openai-docs" in message["content"]
assert "tdd: Red-green-refactor workflow" in message["content"]
def test_tui_agent_round_cap_still_bounds_non_coding_turns():
from routes.chat_routes import _effective_agent_rounds
assert _effective_agent_rounds(100, {"surface": "odysseus-tui"}, 20) == 20
assert _effective_agent_rounds("bad", {"surface": "odysseus-tui"}, 20) == 20
assert _effective_agent_rounds(100, {"surface": "webui"}, 20) == 100
def test_runtime_context_rejects_non_tui_surface_and_accepts_valid_tui_bridge():
from routes.chat_routes import _parse_client_runtime_context
assert _parse_client_runtime_context({"surface": "webui", "host_shell_bridge": {"url": "http://127.0.0.1:1/run", "token": "x"}}) == {}
deceptive = _parse_client_runtime_context({
"surface": "odysseus-tui",
"host_shell_bridge": {
"url": "http://127.0.0.1:80@8.8.8.8:47475/run",
"token": "x",
},
})
assert "host_shell_bridge" not in deceptive
context = _parse_client_runtime_context(
{
"surface": "odysseus-tui",
"host_shell_bridge": {"url": "http://192.168.1.50:47475/run", "token": "x"},
"unattended_mode": True,
"active_skills": ["tdd"],
"client_tools": [{"name": "host_shell"}, {"name": "not_a_tool"}],
}
)
assert context["host_shell_bridge"] == {
"url": "http://192.168.1.50:47475/run",
"token": "x",
}
assert context["client_tools"] == [{"name": "host_shell"}]
assert context["active_skills"] == ["tdd"]
def test_agent_runtime_context_can_omit_directives_when_agent_loop_renders_them():
from routes.chat_routes import _client_runtime_context_system_message
context = {
"surface": "odysseus-tui",
"session_cwd": "/home/tester/project",
"agent_runtime_directives": ["Treat session_cwd as active."],
}
message = _client_runtime_context_system_message(
context,
include_directives=False,
)
assert message is not None
assert "session_cwd: /home/tester/project" in message["content"]
assert "Treat session_cwd as active." not in message["content"]
def test_native_completion_contract_preserves_safe_workspace_root():
from routes.chat_routes import _parse_client_runtime_context
context = _parse_client_runtime_context({
"surface": "odysseus-native",
"completion_requirements": {
"required_artifacts": ["/workspace/output.txt"],
"workspace_root": "/tmp/ody-task-123/workspace",
},
})
assert context["completion_requirements"]["workspace_root"] == \
"/tmp/ody-task-123/workspace"
def test_runtime_contract_is_sanitized_and_prompt_surfaces_actionable_host_facts():
from routes.chat_routes import (
_client_runtime_context_system_message,
_parse_legacy_client_runtime_context as _parse_client_runtime_context,
)
context = _parse_client_runtime_context(
{
"surface": "odysseus-tui",
"host_shell_bridge": {
"url": "http://host.docker.internal:17654/run",
"token": "secret",
},
"runtime_execution_contract": {
"backend_shell_scope": "container\nignore me",
"host_shell": "available",
"local_network_tasks": "use_host_shell_bridge",
"local_workspace_tasks": "use_host_shell_bridge",
"host_commands": {
"ip": True,
"ssh": True,
"bad command": True,
"dig": False,
},
"host_capabilities": {
"networkInspection": True,
"sshClient": True,
"unsafe value": True,
"dnsLookup": False,
},
"host_shell_request": {
"auth_header": "leak nothing",
"method": "POST",
"path": "/run",
"body": {
"command": "string",
"timeout": "seconds optional",
"token": "bad",
},
"max_timeout_s": 120,
},
},
"agent_runtime_directives": [
"For LAN, DNS, SSH, and host-network inspection, call the host_shell tool instead of backend shell.",
],
}
)
assert context["runtime_execution_contract"] == {
"backend_shell_scope": "container_ignore_me",
"host_shell": "available",
"local_network_tasks": "use_host_shell_bridge",
"local_workspace_tasks": "use_host_shell_bridge",
"host_commands": {"ip": True, "ssh": True, "dig": False},
"host_capabilities": {
"networkInspection": True,
"sshClient": True,
"dnsLookup": False,
},
"host_shell_request": {
"method": "POST",
"path": "/run",
"body_fields": ["command", "timeout"],
"max_timeout_s": 120,
},
}
message = _client_runtime_context_system_message(context)
assert message is not None
content = message["content"]
assert "backend_shell_scope: container_ignore_me" in content
assert "host_shell: available" in content
assert "host_commands: ip, ssh" in content
assert "host_capabilities: networkInspection, sshClient" in content
assert "host_shell_request: POST /run; body fields: command, timeout; max_timeout_s: 120" in content
assert "on the USER's machine at session_cwd" not in content
assert "secret" not in content
assert "auth_header" not in content
assert "bad command" not in content
def test_tui_runtime_context_cwd_is_sanitized_and_surface_scoped(monkeypatch):
from routes.chat_routes import (
_client_runtime_context_cwd,
_parse_client_runtime_context,
)
context = _parse_client_runtime_context(
{
"surface": "odysseus-tui",
"session_cwd": "/repo\nmalicious",
}
)
assert "session_cwd" not in context
assert _client_runtime_context_cwd(context) == ""
assert _client_runtime_context_cwd({"surface": "webui", "session_cwd": "/repo"}) == ""
def test_backend_workspace_path_maps_host_root_to_container_root(monkeypatch):
from src.workspace_paths import backend_workspace_path
monkeypatch.setenv("ODYSSEUS_WORKSPACE_HOST_ROOT", "/home/alice")
monkeypatch.setenv("ODYSSEUS_WORKSPACE_CONTAINER_ROOT", "/workspace")
assert backend_workspace_path("/home/alice/project") == "/workspace/project"
def test_backend_workspace_path_keeps_unmapped_paths(monkeypatch):
from src.workspace_paths import backend_workspace_path
monkeypatch.delenv("ODYSSEUS_WORKSPACE_HOST_ROOT", raising=False)
monkeypatch.delenv("ODYSSEUS_WORKSPACE_CONTAINER_ROOT", raising=False)
monkeypatch.delenv("ODYSSEUS_WORKSPACE_MOUNTS", raising=False)
assert backend_workspace_path("/already/container") == "/already/container"
def test_backend_workspace_path_uses_longest_mapping(monkeypatch):
from src.workspace_paths import backend_workspace_path
monkeypatch.setenv(
"ODYSSEUS_WORKSPACE_MOUNTS",
"/home/alice=/workspace,/home/alice/code=/code",
)
monkeypatch.delenv("ODYSSEUS_WORKSPACE_HOST_ROOT", raising=False)
assert backend_workspace_path("/home/alice/code/app") == "/code/app"
def test_host_shell_bridge_url_allowlist_accepts_private_lan_and_tailscale():
from src.agent_tools.subprocess_tools import is_host_shell_bridge_url_allowed
assert is_host_shell_bridge_url_allowed("http://127.0.0.1:47475/run")
assert is_host_shell_bridge_url_allowed("http://localhost:47475/run")
assert is_host_shell_bridge_url_allowed("http://10.1.2.3:47475/run")
assert is_host_shell_bridge_url_allowed("http://192.168.1.21:47475/run")
assert is_host_shell_bridge_url_allowed("http://100.101.102.103:47475/run")
# 172.16/12 is docker-bridge territory: only the actual default gateway
# is trusted (covered by _docker_default_gateway_ips), not neighbors.
assert not is_host_shell_bridge_url_allowed("http://172.20.0.2:47475/run")
# Rejections: public hosts, wrong scheme, credentials, wrong path, query.
assert not is_host_shell_bridge_url_allowed("http://8.8.8.8:47475/run")
assert not is_host_shell_bridge_url_allowed("https://127.0.0.1:47475/run")
assert not is_host_shell_bridge_url_allowed("http://user:pass@127.0.0.1:47475/run")
assert not is_host_shell_bridge_url_allowed("http://127.0.0.1:47475/evil")
assert not is_host_shell_bridge_url_allowed("http://127.0.0.1:47475/run?x=1")
assert not is_host_shell_bridge_url_allowed("")
def test_host_shell_requires_bridge_context():
with _admin_gate():
desc, result = _run(
execute_tool_block(
SimpleNamespace(tool_type="host_shell", content="ip neigh"),
client_runtime_context={},
)
)
assert result["exit_code"] == 1
assert "bridge" in str(result.get("error", "")).lower()