chore: import upstream snapshot with attribution

This commit is contained in:
wehub-resource-sync
2026-07-13 13:12:33 +08:00
commit aacb60a4af
3387 changed files with 981307 additions and 0 deletions
+366
View File
@@ -0,0 +1,366 @@
from __future__ import annotations
import asyncio
import importlib
import inspect
import sys
from typing import Any, cast
import pytest
from opensquilla.cli.repl.session_state import ChatSessionState
from opensquilla.cli.repl.stream import TurnResult, UsageSummary
from opensquilla.cli.tui.contracts import TuiOutputHandle
def test_gateway_runtime_has_no_raw_prompt_application_dependency(monkeypatch) -> None:
monkeypatch.delitem(
sys.modules,
"opensquilla.cli.repl.gateway_runtime",
raising=False,
)
original_import = __import__
blocked_module = "prompt" + "_toolkit"
def _guarded_import(name, globals=None, locals=None, fromlist=(), level=0): # noqa: ANN001
if name == blocked_module or name.startswith(f"{blocked_module}."):
raise AssertionError(f"gateway runtime imported {blocked_module} via {name}")
return original_import(name, globals, locals, fromlist, level)
monkeypatch.setattr("builtins.__import__", _guarded_import)
module = importlib.import_module("opensquilla.cli.repl.gateway_runtime")
source = inspect.getsource(module)
assert "ChatApplication" not in source
def test_gateway_session_context_mirrors_state_to_legacy_scope() -> None:
from opensquilla.cli.repl.gateway_runtime import GatewaySessionContext
state = ChatSessionState(session_key="agent:main:original", model="gateway/original")
context = GatewaySessionContext.create(state)
assert context.scope["session_key"] == "agent:main:original"
assert context.scope["state"] is state
assert context.scope["model"] == "gateway/original"
state.session_key = "agent:main:slash"
state.model = "gateway/slash-model"
context.sync_from_state()
assert context.session_key == "agent:main:slash"
assert context.model == "gateway/slash-model"
assert context.scope["session_key"] == "agent:main:slash"
assert context.scope["model"] == "gateway/slash-model"
@pytest.mark.asyncio
async def test_gateway_runtime_connects_to_configured_gateway_target(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import gateway_runtime
class _FakeGatewayClient:
connected_url: str | None = None
connected_token: str | None = None
closed = False
async def connect(self, url: str, *, token: str | None = None) -> None:
type(self).connected_url = url
type(self).connected_token = token
async def create_session(self, model: str | None = None) -> str:
return "agent:main:new"
async def resolve_session(self, key: str) -> dict[str, str]:
return {"model": "gateway/resolved"}
async def close(self) -> None:
type(self).closed = True
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
monkeypatch.setenv("OPENSQUILLA_GATEWAY_URL", "http://127.0.0.1:18790")
monkeypatch.setenv("OPENSQUILLA_GATEWAY_TOKEN", "branch-token")
async def fake_run_concurrent_repl(
*,
scope: gateway_runtime.GatewayRuntimeScope,
dispatch,
abort_active_turn=None,
) -> None:
return None
deps = gateway_runtime.GatewayRuntimeDependencies(
stream_response=cast(Any, None),
handle_slash_command=cast(Any, None),
run_input_loop=fake_run_concurrent_repl,
get_tui_output=lambda _scope: None,
is_exit_command=lambda _value: False,
notify=lambda _notice: None,
)
await gateway_runtime.run_gateway_chat(
model=None,
session_id=None,
deps=deps,
)
assert _FakeGatewayClient.connected_url == "ws://127.0.0.1:18790/ws"
assert _FakeGatewayClient.connected_token == "branch-token"
assert _FakeGatewayClient.closed is True
@pytest.mark.asyncio
async def test_gateway_runtime_dispatches_messages_slash_commands_and_exit(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import gateway_runtime
class _FakeGatewayClient:
instances: list[_FakeGatewayClient] = []
def __init__(self) -> None:
self.connected = False
self.closed = False
self.create_calls: list[str | None] = []
self.resolve_calls: list[str] = []
self.abort_calls: list[str] = []
_FakeGatewayClient.instances.append(self)
async def connect(self, url: str, *, token: str | None = None) -> None:
self.connected = True
async def create_session(self, model: str | None = None) -> str:
self.create_calls.append(model)
return "agent:main:new"
async def resolve_session(self, key: str) -> dict[str, str]:
self.resolve_calls.append(key)
return {"model": "gateway/resolved"}
async def abort_session(self, key: str) -> dict[str, object]:
self.abort_calls.append(key)
return {"aborted": True, "key": key}
async def close(self) -> None:
self.closed = True
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
output = cast(TuiOutputHandle, object())
captured: dict[str, Any] = {}
async def fake_stream_response(
client: object,
session_key: str,
message: str,
elevated_state: dict[str, str | None] | None = None,
attachments: list[dict] | None = None,
*,
tui_output: object | None = None,
) -> TurnResult:
captured["stream"] = {
"client": client,
"session_key": session_key,
"message": message,
"elevated_state": elevated_state,
"attachments": attachments,
"tui_output": tui_output,
}
return TurnResult(
text="assistant reply",
usage=UsageSummary(input_tokens=2, output_tokens=3),
model_after="gateway/after",
)
async def fake_handle_slash_command(
cmd: str,
state: ChatSessionState,
client: object,
elevated_state: dict[str, str | None],
*,
tui_output: object | None = None,
) -> bool:
captured["slash"] = {
"cmd": cmd,
"client": client,
"elevated_state": elevated_state,
"tui_output": tui_output,
}
state.session_key = "agent:main:slash"
state.model = "gateway/slash-model"
return True
async def fake_run_concurrent_repl(
*,
scope: gateway_runtime.GatewayRuntimeScope,
dispatch,
abort_active_turn=None,
) -> None:
captured["initial_scope"] = dict(scope)
captured["abort_active_turn"] = abort_active_turn
assert await dispatch("hello") is True
active_state = cast(ChatSessionState, scope["state"])
captured["state_after_message"] = active_state
assert active_state.model == "gateway/after"
assert active_state.transcript.to_markdown()
assert active_state.usage.input_tokens == 2
assert active_state.usage.output_tokens == 3
assert await dispatch("/reset") is True
captured["scope_after_slash"] = dict(scope)
assert await dispatch("/exit") is False
def fake_get_tui_output(
_scope: gateway_runtime.GatewayRuntimeScope,
) -> TuiOutputHandle | None:
return output
deps = gateway_runtime.GatewayRuntimeDependencies(
stream_response=fake_stream_response,
handle_slash_command=fake_handle_slash_command,
run_input_loop=fake_run_concurrent_repl,
get_tui_output=fake_get_tui_output,
is_exit_command=lambda value: value.strip() == "/exit",
notify=lambda notice: captured.setdefault("notices", []).append(notice),
)
await gateway_runtime.run_gateway_chat(
model="anthropic/claude-sonnet-4",
session_id=None,
deps=deps,
)
client = _FakeGatewayClient.instances[-1]
assert client.connected is True
assert client.closed is True
assert client.create_calls == ["anthropic/claude-sonnet-4"]
assert client.resolve_calls == ["agent:main:new"]
assert captured["abort_active_turn"] is not None
await captured["abort_active_turn"]()
assert client.abort_calls == []
assert captured["initial_scope"]["session_key"] == "agent:main:new"
assert captured["initial_scope"]["model"] == "gateway/resolved"
assert captured["stream"]["client"] is client
assert captured["stream"]["session_key"] == "agent:main:new"
assert captured["stream"]["message"] == "hello"
assert captured["stream"]["elevated_state"] == {"mode": None}
assert captured["stream"]["tui_output"] is output
assert captured["slash"]["cmd"] == "/reset"
assert captured["slash"]["client"] is client
assert captured["slash"]["elevated_state"] == {"mode": None}
assert captured["slash"]["tui_output"] is output
assert captured["scope_after_slash"]["session_key"] == "agent:main:slash"
assert captured["scope_after_slash"]["model"] == "gateway/slash-model"
assert [notice.kind for notice in captured["notices"]] == [
"created",
"model",
"welcome",
"goodbye",
]
@pytest.mark.asyncio
async def test_gateway_abort_targets_active_turn_session_after_session_changes(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import gateway_runtime
class _FakeGatewayClient:
instances: list[_FakeGatewayClient] = []
def __init__(self) -> None:
self.abort_calls: list[str] = []
_FakeGatewayClient.instances.append(self)
async def connect(self, url: str, *, token: str | None = None) -> None:
return None
async def create_session(self, model: str | None = None) -> str:
return "agent:main:old"
async def resolve_session(self, key: str) -> dict[str, str]:
return {"model": "gateway/old"}
async def abort_session(self, key: str) -> dict[str, object]:
self.abort_calls.append(key)
return {"aborted": True, "key": key}
async def close(self) -> None:
return None
monkeypatch.setattr("opensquilla.cli.gateway_client.GatewayClient", _FakeGatewayClient)
stream_started = asyncio.Event()
release_stream = asyncio.Event()
async def fake_stream_response(
_client: object,
session_key: str,
_message: str,
_elevated_state: dict[str, str | None] | None = None,
_attachments: list[dict] | None = None,
*,
tui_output: object | None = None,
) -> TurnResult:
assert session_key == "agent:main:old"
stream_started.set()
await release_stream.wait()
return TurnResult(
text="assistant reply",
usage=UsageSummary(input_tokens=1, output_tokens=1),
model_after="gateway/old",
)
async def fake_handle_slash_command(
_cmd: str,
_state: ChatSessionState,
_client: object,
_elevated_state: dict[str, str | None],
*,
tui_output: object | None = None,
) -> bool:
return True
async def fake_run_concurrent_repl(
*,
scope: gateway_runtime.GatewayRuntimeScope,
dispatch,
abort_active_turn=None,
) -> None:
assert abort_active_turn is not None
turn = asyncio.create_task(dispatch("hello"))
await asyncio.wait_for(stream_started.wait(), timeout=2.0)
active_state = cast(ChatSessionState, scope["state"])
active_state.session_key = "agent:main:new"
active_state.model = "gateway/new"
scope["session_key"] = active_state.session_key
scope["model"] = active_state.model
await abort_active_turn()
release_stream.set()
assert await asyncio.wait_for(turn, timeout=2.0) is True
deps = gateway_runtime.GatewayRuntimeDependencies(
stream_response=fake_stream_response,
handle_slash_command=fake_handle_slash_command,
run_input_loop=fake_run_concurrent_repl,
get_tui_output=lambda _scope: None,
is_exit_command=lambda _value: False,
notify=lambda _notice: None,
)
await gateway_runtime.run_gateway_chat(
model=None,
session_id=None,
deps=deps,
)
client = _FakeGatewayClient.instances[-1]
assert client.abort_calls == ["agent:main:old"]
+136
View File
@@ -0,0 +1,136 @@
from __future__ import annotations
import importlib
import inspect
import sys
from pathlib import Path
from typing import Any
import pytest
def test_input_assets_has_no_raw_prompt_application_dependency(monkeypatch) -> None:
monkeypatch.delitem(
sys.modules,
"opensquilla.cli.repl.input_assets",
raising=False,
)
original_import = __import__
blocked_module = "prompt" + "_toolkit"
def _guarded_import(name, globals=None, locals=None, fromlist=(), level=0): # noqa: ANN001
if name == blocked_module or name.startswith(f"{blocked_module}."):
raise AssertionError(f"input assets imported {blocked_module} via {name}")
return original_import(name, globals, locals, fromlist, level)
monkeypatch.setattr("builtins.__import__", _guarded_import)
module = importlib.import_module("opensquilla.cli.repl.input_assets")
source = inspect.getsource(module)
assert "ChatApplication" not in source
def test_input_assets_wrap_existing_file_and_path_helpers(tmp_path: Path) -> None:
from opensquilla.cli.repl import input_assets
csv_path = tmp_path / "data.csv"
csv_path.write_text("a,b\n1,2\n", encoding="utf-8")
png_path = tmp_path / "screen.png"
png_path.write_bytes(b"\x89PNG\r\n\x1a\npayload")
file_prompt, file_attachments = input_assets.file_prompt_and_attachments(
f"/file {csv_path} summarize",
upload_callable=None,
)
image_prompt, image_attachments = input_assets.image_prompt_and_attachments(
f"/image {png_path} describe",
)
path_prompt, path_attachments = input_assets.path_prompt_and_attachments(
f"/path {csv_path} inspect",
)
assert file_prompt == "summarize"
assert file_attachments[0]["type"] == "text/csv"
assert input_assets.image_prompt_from_command(f"/image {png_path} describe") == "describe"
assert image_prompt == "describe"
assert image_attachments[0]["type"] == "image/png"
assert "inspect" in path_prompt
assert str(csv_path.resolve(strict=False)) in path_prompt
assert path_attachments == []
def test_gateway_slash_adapter_uses_input_bridge_for_path(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import slash_adapter
captured: dict[str, Any] = {}
def fake_path_prompt_and_attachments(command: str) -> tuple[str, list[dict[str, Any]]]:
captured["command"] = command
return "inspect from input assets", []
monkeypatch.setattr(
slash_adapter._input_bridge,
"path_prompt_and_attachments",
fake_path_prompt_and_attachments,
)
prompt, attachments = slash_adapter.path_prompt_and_attachments("/path /repo inspect")
assert captured["command"] == "/path /repo inspect"
assert prompt == "inspect from input assets"
assert attachments == []
def test_turn_bridge_default_image_builder_uses_input_bridge(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import turn_bridge
from opensquilla.cli.tui import turn_stream_defaults
captured: dict[str, str] = {}
def fake_image_prompt_and_attachments(command: str) -> tuple[str, list[dict[str, str]]]:
captured["command"] = command
return "describe via input assets", [{"type": "image/png", "data": "x", "name": "x.png"}]
monkeypatch.setattr(
turn_stream_defaults._input_bridge,
"image_prompt_and_attachments",
fake_image_prompt_and_attachments,
)
prompt, attachments = turn_bridge.image_prompt_and_attachments("/image x.png describe")
assert captured["command"] == "/image x.png describe"
assert prompt == "describe via input assets"
assert attachments == [{"type": "image/png", "data": "x", "name": "x.png"}]
def test_standalone_slash_adapter_uses_input_bridge_for_path(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import standalone_slash_adapter
captured: dict[str, Any] = {}
def fake_path_prompt_and_attachments(command: str) -> tuple[str, list[dict[str, Any]]]:
captured["command"] = command
return "standalone inspect from input bridge", []
monkeypatch.setattr(
standalone_slash_adapter._input_bridge,
"path_prompt_and_attachments",
fake_path_prompt_and_attachments,
)
prompt, attachments = standalone_slash_adapter._path_prompt_and_attachments(
"/path /repo inspect"
)
assert captured["command"] == "/path /repo inspect"
assert prompt == "standalone inspect from input bridge"
assert attachments == []
+151
View File
@@ -0,0 +1,151 @@
from __future__ import annotations
from typing import Any
import pytest
def test_input_bridge_announces_image_attachment_with_supplied_console(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import input_bridge
captured: dict[str, str] = {}
prints: list[str] = []
class FakeConsole:
def print(self, message: str) -> None:
prints.append(message)
def fake_image_prompt_and_attachments(
command: str,
) -> tuple[str, list[dict[str, str]]]:
captured["command"] = command
return (
"describe it",
[{"type": "image/png", "data": "x" * 3072, "name": "screen.png"}],
)
monkeypatch.setattr(
input_bridge._input_assets,
"image_prompt_and_attachments",
fake_image_prompt_and_attachments,
)
prompt, attachments = input_bridge.image_prompt_and_attachments(
"/image screen.png describe it",
output_console=FakeConsole(),
)
assert captured["command"] == "/image screen.png describe it"
assert prompt == "describe it"
assert attachments == [
{"type": "image/png", "data": "x" * 3072, "name": "screen.png"}
]
assert prints == ["[dim]Sending image: screen.png (3KB base64)[/dim]"]
def test_input_bridge_announces_image_attachment_with_default_console(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import input_bridge
prints: list[str] = []
class FakeConsole:
def print(self, message: str) -> None:
prints.append(message)
def fake_image_prompt_and_attachments(
command: str,
) -> tuple[str, list[dict[str, str]]]:
return (
"describe it",
[{"type": "image/png", "data": "x" * 2048, "name": "screen.png"}],
)
monkeypatch.setattr(
input_bridge._input_assets,
"image_prompt_and_attachments",
fake_image_prompt_and_attachments,
)
monkeypatch.setattr(input_bridge, "console", FakeConsole())
prompt, attachments = input_bridge.image_prompt_and_attachments(
"/image screen.png describe it"
)
assert prompt == "describe it"
assert attachments == [
{"type": "image/png", "data": "x" * 2048, "name": "screen.png"}
]
assert prints == ["[dim]Sending image: screen.png (2KB base64)[/dim]"]
def test_input_bridge_does_not_announce_when_image_has_no_attachments(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import input_bridge
prints: list[str] = []
class FakeConsole:
def print(self, message: str) -> None:
prints.append(message)
def fake_image_prompt_and_attachments(
command: str,
) -> tuple[str, list[dict[str, str]]]:
return "describe it", []
monkeypatch.setattr(
input_bridge._input_assets,
"image_prompt_and_attachments",
fake_image_prompt_and_attachments,
)
prompt, attachments = input_bridge.image_prompt_and_attachments(
"/image missing.png describe it",
output_console=FakeConsole(),
)
assert prompt == "describe it"
assert attachments == []
assert prints == []
@pytest.mark.asyncio
async def test_input_bridge_wraps_async_file_prompt_builder(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import input_bridge
captured: dict[str, Any] = {}
async def fake_async_file_prompt_and_attachments(
command: str,
*,
upload_callable: Any | None = None,
) -> tuple[str, list[dict[str, Any]]]:
captured["command"] = command
captured["upload_callable"] = upload_callable
return "summarize", [{"type": "text/plain", "name": "note.txt"}]
monkeypatch.setattr(
input_bridge._input_assets,
"async_file_prompt_and_attachments",
fake_async_file_prompt_and_attachments,
)
def fake_upload(*args: Any) -> str:
return "u-file"
prompt, attachments = await input_bridge.async_file_prompt_and_attachments(
"/file note.txt summarize",
upload_callable=fake_upload,
)
assert captured["command"] == "/file note.txt summarize"
assert captured["upload_callable"] is fake_upload
assert prompt == "summarize"
assert attachments == [{"type": "text/plain", "name": "note.txt"}]
+319
View File
@@ -0,0 +1,319 @@
from __future__ import annotations
import logging
from io import StringIO
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import pytest
import typer
class FakeConsole:
def __init__(self, *, is_terminal: bool = True) -> None:
self.is_terminal = is_terminal
self.clears = 0
self.prints: list[Any] = []
def clear(self) -> None:
self.clears += 1
def print(self, payload: Any) -> None:
self.prints.append(payload)
class FakeTerminalStream(StringIO):
def isatty(self) -> bool:
return True
def test_launch_bridge_prepares_terminal_and_quiets_logs(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.tui.adapters import launch_bridge
calls: list[str] = []
class FakeStdin:
def isatty(self) -> bool:
return True
monkeypatch.setattr(
launch_bridge,
"quiet_logs_for_interactive_chat",
lambda: calls.append("quiet"),
)
console = FakeConsole(is_terminal=True)
launch_bridge.prepare_interactive_chat(
input_stream=FakeStdin(),
output_console=console,
)
assert calls == ["quiet"]
assert console.clears == 1
def test_launch_bridge_routes_interactive_structlog_to_file(
monkeypatch: pytest.MonkeyPatch,
tmp_path: Path,
capsys: pytest.CaptureFixture[str],
) -> None:
import structlog
from opensquilla.cli.tui.adapters import launch_bridge
original_config = structlog.get_config()
root = logging.getLogger()
original_root_handlers = list(root.handlers)
original_root_level = root.level
monkeypatch.setenv("OPENSQUILLA_LOG_DIR", str(tmp_path))
monkeypatch.delenv("OPENSQUILLA_LOG_LEVEL", raising=False)
for handler in original_root_handlers:
root.removeHandler(handler)
terminal_stream = FakeTerminalStream()
root.addHandler(logging.StreamHandler(terminal_stream))
try:
launch_bridge.quiet_logs_for_interactive_chat()
structlog.get_logger("opensquilla.test").warning(
"ui.hidden_warning",
answer=42,
)
logging.getLogger("opensquilla.test").warning("ui.hidden_stdlib_warning")
captured = capsys.readouterr()
assert captured.out == ""
assert captured.err == ""
assert terminal_stream.getvalue() == ""
log_text = (tmp_path / "interactive.log").read_text()
assert "ui.hidden_warning" in log_text
assert "ui.hidden_stdlib_warning" in log_text
finally:
for handler in list(root.handlers):
root.removeHandler(handler)
handler.close()
for handler in original_root_handlers:
root.addHandler(handler)
root.setLevel(original_root_level)
handle = getattr(launch_bridge, "_INTERACTIVE_STRUCTLOG_FILE", None)
if handle is not None:
handle.close()
setattr(launch_bridge, "_INTERACTIVE_STRUCTLOG_FILE", None)
structlog.configure(**original_config)
def test_launch_bridge_rejects_non_interactive_input() -> None:
from opensquilla.cli.tui.adapters import launch_bridge
class FakeStdin:
def isatty(self) -> bool:
return False
with pytest.raises(typer.Exit) as exc_info:
launch_bridge.prepare_interactive_chat(
input_stream=FakeStdin(),
output_console=FakeConsole(is_terminal=True),
)
assert exc_info.value.exit_code == 2
def test_launch_bridge_prints_standalone_banner_and_runs_standalone(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.tui.adapters import launch_bridge
calls: list[dict[str, Any]] = []
console = FakeConsole(is_terminal=True)
async def fake_standalone(**kwargs: Any) -> None:
calls.append(kwargs)
monkeypatch.setattr(launch_bridge, "prepare_interactive_chat", lambda **_kwargs: None)
monkeypatch.setattr(launch_bridge, "validate_tui_backend_or_exit", lambda: "native")
launch_bridge.launch_chat(
model="openai/test",
session_id="agent:main:test",
standalone=True,
workspace="repo",
workspace_strict=True,
timeout=7.25,
standalone_runner=fake_standalone,
gateway_runner=None,
output_console=console,
)
assert len(console.prints) == 3
assert "OpenSquilla Chat" in str(console.prints[0].renderable)
assert console.prints[1] == "[dim]Model: openai/test[/dim]"
assert console.prints[2] == "[dim]Session: agent:main:test[/dim]"
assert calls == [
{
"model": "openai/test",
"session_id": "agent:main:test",
"workspace": "repo",
"workspace_strict": True,
"timeout": 7.25,
}
]
def test_launch_bridge_suppresses_native_banner_for_opentui_backend(
monkeypatch: pytest.MonkeyPatch,
) -> None:
# The OpenTUI host draws its own full-screen footer; printing the native
# banner first only makes it flash for ~1s before OpenTUI takes the screen.
from opensquilla.cli.tui.adapters import launch_bridge
calls: list[dict[str, Any]] = []
console = FakeConsole(is_terminal=True)
async def fake_standalone(**kwargs: Any) -> None:
calls.append(kwargs)
monkeypatch.setattr(launch_bridge, "prepare_interactive_chat", lambda **_kwargs: None)
monkeypatch.setattr(launch_bridge, "validate_tui_backend_or_exit", lambda: "opentui")
launch_bridge.launch_chat(
model="openai/test",
session_id="agent:main:test",
standalone=True,
workspace="repo",
workspace_strict=True,
timeout=7.25,
standalone_runner=fake_standalone,
gateway_runner=None,
output_console=console,
)
# No native chrome printed to the main screen, but the runner still launches.
assert console.prints == []
assert len(calls) == 1
assert calls[0]["model"] == "openai/test"
def test_launch_bridge_warns_gateway_workspace_options_without_forwarding(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.tui.adapters import launch_bridge
calls: list[dict[str, Any]] = []
console = FakeConsole(is_terminal=True)
async def fake_gateway(**kwargs: Any) -> None:
calls.append(kwargs)
monkeypatch.setattr(launch_bridge, "prepare_interactive_chat", lambda **_kwargs: None)
launch_bridge.launch_chat(
model="",
session_id="",
standalone=False,
workspace="repo",
workspace_strict=True,
timeout=None,
standalone_runner=None,
gateway_runner=fake_gateway,
output_console=console,
)
assert calls == [{"model": None, "session_id": None}]
assert len(console.prints) == 1
assert "--workspace only affects --standalone chat" in str(console.prints[0])
def test_launch_chat_command_uses_typed_overrides() -> None:
from opensquilla.cli.chat.launch import (
ChatCommandLaunchOverrides,
ChatCommandRequest,
)
from opensquilla.cli.tui.adapters import launch_bridge
calls: list[dict[str, Any]] = []
async def fake_standalone(**kwargs: Any) -> None:
return None
async def fake_gateway(**kwargs: Any) -> None:
return None
def fake_launch_chat(**kwargs: Any) -> None:
calls.append(kwargs)
launch_bridge.launch_chat_command(
ChatCommandRequest(
model="openai/test",
session_id="agent:main:test",
standalone=True,
workspace="repo",
workspace_strict=True,
timeout=7.25,
),
overrides=ChatCommandLaunchOverrides(
launch_chat=fake_launch_chat,
standalone_runner=fake_standalone,
gateway_runner=fake_gateway,
),
)
assert calls == [
{
"model": "openai/test",
"session_id": "agent:main:test",
"standalone": True,
"workspace": "repo",
"workspace_strict": True,
"timeout": 7.25,
"standalone_runner": fake_standalone,
"gateway_runner": fake_gateway,
}
]
def test_launch_chat_command_keeps_legacy_override_mapping() -> None:
from opensquilla.cli.chat.launch import ChatCommandRequest
from opensquilla.cli.tui.adapters import launch_bridge
calls: list[dict[str, Any]] = []
async def fake_standalone(**kwargs: Any) -> None:
return None
async def fake_gateway(**kwargs: Any) -> None:
return None
def fake_launch_chat(**kwargs: Any) -> None:
calls.append(kwargs)
launch_bridge.launch_chat_command(
ChatCommandRequest(
model="openai/test",
session_id="agent:main:test",
standalone=False,
workspace="",
workspace_strict=None,
timeout=None,
),
legacy_overrides={
"_launch_bridge": SimpleNamespace(launch_chat=fake_launch_chat),
"_standalone_repl": fake_standalone,
"_gateway_chat": fake_gateway,
},
)
assert calls == [
{
"model": "openai/test",
"session_id": "agent:main:test",
"standalone": False,
"workspace": "",
"workspace_strict": None,
"timeout": None,
"standalone_runner": fake_standalone,
"gateway_runner": fake_gateway,
}
]
@@ -0,0 +1,280 @@
from __future__ import annotations
import asyncio
import io
import sys
from collections.abc import AsyncIterator, Callable
from contextlib import asynccontextmanager
from types import SimpleNamespace
from typing import Any
import pytest
from opensquilla.engine.commands import Surface
class _FakeOutputHandle:
approval_surface = Surface.CLI_GATEWAY
async def write_through(self, payload: str) -> None:
return None
async def send_message(self, message_type: str, payload: dict) -> None:
return None
def stream_output(self):
@asynccontextmanager
async def _cm() -> AsyncIterator[Callable[[str], None]]:
yield lambda _payload: None
return _cm()
class _FakeOpenTuiSurface:
output_handle = _FakeOutputHandle()
def __init__(self) -> None:
self.writes: list[str] = []
async def next_line(self) -> str | None:
return None
def set_cancel_callback(self, cb: Callable[[], None] | None) -> None:
return None
def set_shutdown_callback(self, cb: Callable[[], None] | None) -> None:
return None
def emit_eof(self) -> None:
return None
async def write_through(self, payload: str) -> None:
self.writes.append(payload)
async def send_message(self, message_type: str, payload: dict) -> None:
self.writes.append(f"{message_type}:{payload.get('text', '')}")
@property
def redraw_callback(self) -> Callable[[], None]:
return lambda: None
@pytest.mark.asyncio
async def test_opentui_chat_runtime_exposes_tui_output_and_reuses_runtime(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.tui.opentui import runtime as opentui_runtime
scope: dict[str, Any] = {
"model": "model-a",
"session_key": "session-a",
"tool_ctx": SimpleNamespace(workspace_dir="/tmp/opentui-workspace"),
}
captured: dict[str, Any] = {}
fake_surface = _FakeOpenTuiSurface()
@asynccontextmanager
async def fake_open_opentui_surface(**kwargs: Any):
captured["surface_kwargs"] = kwargs
yield fake_surface
async def fake_run_tui_runtime(**kwargs: Any):
captured["runtime_kwargs"] = kwargs
async with kwargs["surface_factory"]() as yielded:
assert yielded is fake_surface
hooks = kwargs["hooks"]
assert not opentui_runtime.get_tui_output(scope)
hooks.expose_surface(fake_surface)
output = opentui_runtime.get_tui_output(scope)
captured["output"] = output
captured["manager"] = getattr(output, "plugin_manager", None)
hooks.clear_exposed_surface()
return object()
monkeypatch.setattr(opentui_runtime, "open_opentui_surface", fake_open_opentui_surface)
monkeypatch.setattr(opentui_runtime, "run_tui_runtime", fake_run_tui_runtime)
async def fake_dispatch(_value: str) -> bool:
return True
await opentui_runtime.run_opentui_chat_runtime(
surface=Surface.CLI_GATEWAY,
scope=scope,
dispatch=fake_dispatch,
queue_max_size=8,
)
assert captured["surface_kwargs"] == {
"surface": Surface.CLI_GATEWAY,
"model": "model-a",
"session_id": "session-a",
"workspace_dir": "/tmp/opentui-workspace",
}
assert captured["runtime_kwargs"]["dispatch"] is fake_dispatch
assert captured["runtime_kwargs"]["config"].concurrent_input_during_turn is True
assert opentui_runtime.get_tui_output(scope) is None
assert getattr(captured["output"], "_output_handle", None) is fake_surface.output_handle
assert captured["manager"] is not None
@pytest.mark.asyncio
async def test_opentui_chat_runtime_forwards_workspace_dir_from_tool_context(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.tui.opentui import runtime as opentui_runtime
scope: dict[str, Any] = {
"model": "model-a",
"session_key": "session-a",
"tool_ctx": SimpleNamespace(workspace_dir="/tmp/workspace-a"),
}
captured: dict[str, Any] = {}
fake_surface = _FakeOpenTuiSurface()
@asynccontextmanager
async def fake_open_opentui_surface(**kwargs: Any):
captured["surface_kwargs"] = kwargs
yield fake_surface
async def fake_run_tui_runtime(**kwargs: Any):
async with kwargs["surface_factory"]() as yielded:
assert yielded is fake_surface
monkeypatch.setattr(opentui_runtime, "open_opentui_surface", fake_open_opentui_surface)
monkeypatch.setattr(opentui_runtime, "run_tui_runtime", fake_run_tui_runtime)
async def fake_dispatch(_value: str) -> bool:
return True
await opentui_runtime.run_opentui_chat_runtime(
surface=Surface.CLI_GATEWAY,
scope=scope,
dispatch=fake_dispatch,
queue_max_size=8,
)
assert captured["surface_kwargs"]["workspace_dir"] == "/tmp/workspace-a"
@pytest.mark.asyncio
async def test_opentui_chat_runtime_uses_footer_native_echo_hooks(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.tui.adapters import runtime_helpers
from opensquilla.cli.tui.opentui import runtime as opentui_runtime
assert runtime_helpers.classify_chat_input("/help") is not None
scope: dict[str, Any] = {"model": "model-a", "session_key": "session-a"}
fake_surface = _FakeOpenTuiSurface()
@asynccontextmanager
async def fake_open_opentui_surface(**_kwargs: Any):
yield fake_surface
async def fake_run_tui_runtime(**kwargs: Any):
hooks = kwargs["hooks"]
await hooks.on_user_input_echo(fake_surface, "hello opentui")
await hooks.on_user_input_echo(fake_surface, "中文输入 CJK混合ASCII")
await hooks.on_queued_turn_start(fake_surface)
return object()
monkeypatch.setattr(opentui_runtime, "open_opentui_surface", fake_open_opentui_surface)
monkeypatch.setattr(opentui_runtime, "run_tui_runtime", fake_run_tui_runtime)
async def fake_dispatch(_value: str) -> bool:
return True
await opentui_runtime.run_opentui_chat_runtime(
surface=Surface.CLI_GATEWAY,
scope=scope,
dispatch=fake_dispatch,
queue_max_size=8,
)
joined_writes = "".join(fake_surface.writes)
assert "你 / you" not in joined_writes
assert "prompt.echo:hello opentui" in joined_writes
assert "中文输入 CJK混合ASCII" in joined_writes
assert "running queued input" in joined_writes
@pytest.mark.asyncio
async def test_opentui_chat_runtime_reprints_exit_notices_to_real_stderr(
monkeypatch: pytest.MonkeyPatch,
) -> None:
"""The surface-error notice carries the only copy of a sidecar crash
reason, and the Goodbye notice is emitted with the bridge already doomed.
Both must reach the real terminal stderr after teardown instead of dying
with the captured console."""
from opensquilla.cli.tui.opentui import runtime as opentui_runtime
scope: dict[str, Any] = {"model": "model-a", "session_key": "session-a"}
fake_surface = _FakeOpenTuiSurface()
@asynccontextmanager
async def fake_open_opentui_surface(**_kwargs: Any):
yield fake_surface
async def fake_run_tui_runtime(**kwargs: Any):
hooks = kwargs["hooks"]
hooks.notice("[red]Input surface error: OpenTUI host exited with code 7[/red]")
hooks.notice("[yellow]Goodbye.[/yellow]")
return object()
monkeypatch.setattr(opentui_runtime, "open_opentui_surface", fake_open_opentui_surface)
monkeypatch.setattr(opentui_runtime, "run_tui_runtime", fake_run_tui_runtime)
fake_stderr = io.StringIO()
monkeypatch.setattr(sys, "stderr", fake_stderr)
async def fake_dispatch(_value: str) -> bool:
return True
await opentui_runtime.run_opentui_chat_runtime(
surface=Surface.CLI_GATEWAY,
scope=scope,
dispatch=fake_dispatch,
queue_max_size=8,
)
output = fake_stderr.getvalue()
assert "Input surface error: OpenTUI host exited with code 7" in output
assert "Goodbye" in output
@pytest.mark.asyncio
async def test_forward_console_notice_prunes_completed_pending_tasks() -> None:
"""The session-scoped pending set must shed tasks as their sends finish —
every captured console line schedules one, so retaining completed tasks
grows without bound over a long interactive session."""
from opensquilla.cli.tui.backend.output_binding import TuiOutputBinding
from opensquilla.cli.tui.opentui import runtime as opentui_runtime
scope: dict[str, Any] = {}
TuiOutputBinding(scope).expose(_FakeOutputHandle())
pending: set[asyncio.Task[None]] = set()
for index in range(5):
opentui_runtime.forward_console_notice(scope, f"line-{index}", pending_tasks=pending)
assert len(pending) == 5
await asyncio.gather(*pending)
# Done callbacks run one call_soon hop after completion.
await asyncio.sleep(0)
assert pending == set()
def test_opentui_notice_renders_through_captured_console() -> None:
from opensquilla.cli.tui.opentui.notice_capture import capture_stdout_as_notices
from opensquilla.cli.tui.opentui.runtime import opentui_notice
lines: list[str] = []
# Runtime notices must render as clean styled host notices, never as raw Rich
# markup. With the capture installed, console.print is forwarded line-by-line.
with capture_stdout_as_notices(lines.append):
opentui_notice({}, "[yellow]Hello[/yellow]")
joined = "".join(lines)
assert "Hello" in joined
assert "[yellow]" not in joined # markup must be rendered out, not leaked verbatim
+542
View File
@@ -0,0 +1,542 @@
from __future__ import annotations
from contextlib import asynccontextmanager
from types import SimpleNamespace
from typing import Any, cast
import pytest
from rich.console import Console
from rich.panel import Panel
from opensquilla.cli.repl import gateway_runtime, standalone_runtime
from opensquilla.cli.repl.session_state import ChatSessionState
from opensquilla.cli.repl.stream import TurnResult
from opensquilla.engine.commands import Surface
REMOVED_TEXT_BACKEND = "text" + "ual"
REMOVED_BACKEND_IDS = ["terminal", REMOVED_TEXT_BACKEND, f"live-{REMOVED_TEXT_BACKEND}"]
async def _fake_gateway_stream(*args: Any, **kwargs: Any) -> TurnResult:
return TurnResult(text="gateway")
async def _fake_gateway_slash(*args: Any, **kwargs: Any) -> bool:
return True
async def _fake_standalone_stream(*args: Any, **kwargs: Any) -> TurnResult:
return TurnResult(text="standalone")
def _fake_error_panel(message: str, *, title: str = "Error") -> Panel:
return Panel(message, title=title)
class _FakeStreamOutput:
async def __aenter__(self):
return lambda _payload: None
async def __aexit__(self, exc_type, exc, tb) -> None:
return None
class _FakeOutputHandle:
approval_surface = Surface.CLI_GATEWAY
async def write_through(self, payload: str) -> None:
return None
def stream_output(self) -> _FakeStreamOutput:
return _FakeStreamOutput()
def set_toolbar(self, key: str, value: object | None) -> None:
return None
def invalidate(self) -> None:
return None
class _FakeTuiSurface:
output_handle = _FakeOutputHandle()
async def next_line(self) -> str | None:
return None
def set_cancel_callback(self, cb) -> None:
return None
def set_shutdown_callback(self, cb) -> None:
return None
def emit_eof(self) -> None:
return None
async def write_through(self, payload: str) -> None:
return None
@property
def redraw_callback(self):
return lambda: None
class _RecordingConsole:
def __init__(self) -> None:
self.printed: list[object] = []
def print(self, value: object) -> None:
self.printed.append(value)
def test_gateway_runtime_notifier_maps_all_notice_kinds() -> None:
from opensquilla.cli.repl import runtime_bridge
output_console = _RecordingConsole()
notify = runtime_bridge._gateway_runtime_notifier(
output_console,
_fake_error_panel,
)
for notice in (
gateway_runtime.GatewayRuntimeNotice(
kind="created",
session_key="agent:main:new",
),
gateway_runtime.GatewayRuntimeNotice(
kind="resumed",
session_key="agent:main:old",
),
gateway_runtime.GatewayRuntimeNotice(kind="resume_model_ignored"),
gateway_runtime.GatewayRuntimeNotice(kind="model", model="openai/test"),
gateway_runtime.GatewayRuntimeNotice(kind="welcome"),
gateway_runtime.GatewayRuntimeNotice(kind="goodbye"),
gateway_runtime.GatewayRuntimeNotice(kind="unknown_command"),
gateway_runtime.GatewayRuntimeNotice(kind="error", message="boom"),
):
notify(notice)
assert output_console.printed[0] == (
"[dim]Connected to gateway. Session: agent:main:new[/dim]"
)
assert output_console.printed[1] == (
"[dim]Connected to gateway. Resuming session: agent:main:old[/dim]"
)
assert output_console.printed[2] == (
"[yellow]Note: --model is honored only at session creation; ignored "
"when resuming a session.[/yellow]"
)
assert output_console.printed[3] == "[dim]Model: openai/test[/dim]"
assert isinstance(output_console.printed[4], Panel)
assert output_console.printed[5] == "[yellow]Goodbye.[/yellow]"
assert output_console.printed[6] == "[red]Unknown command.[/red] [dim]Use /help.[/dim]"
assert isinstance(output_console.printed[7], Panel)
@pytest.mark.asyncio
async def test_gateway_runtime_bridge_assembles_gateway_dependencies(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
captured: dict[str, Any] = {}
output_console = _RecordingConsole()
async def fake_run_gateway_chat(**kwargs: Any) -> None:
captured.update(kwargs)
monkeypatch.setattr(gateway_runtime, "run_gateway_chat", fake_run_gateway_chat)
await runtime_bridge.run_gateway_chat(
model="openai/test",
session_id="agent:main:test",
stream_response=_fake_gateway_stream,
handle_slash_command=_fake_gateway_slash,
output_console=output_console,
error_panel_factory=_fake_error_panel,
)
deps = cast(gateway_runtime.GatewayRuntimeDependencies, captured["deps"])
assert captured["model"] == "openai/test"
assert captured["session_id"] == "agent:main:test"
assert deps.stream_response is _fake_gateway_stream
assert deps.handle_slash_command is _fake_gateway_slash
assert callable(deps.run_input_loop)
assert deps.get_tui_output is runtime_bridge.get_tui_output
deps.notify(gateway_runtime.GatewayRuntimeNotice(kind="error", message="boom"))
assert isinstance(output_console.printed[-1], Panel)
@pytest.mark.asyncio
async def test_gateway_runtime_bridge_resolves_default_repl_runner_at_call_time(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
captured: dict[str, Any] = {}
async def fake_run_gateway_chat(**kwargs: Any) -> None:
captured.update(kwargs)
runner_kwargs: dict[str, Any] = {}
async def replacement_run_concurrent_repl(**kwargs: Any) -> None:
runner_kwargs.update(kwargs)
return None
async def fake_dispatch(_value: str) -> bool:
return True
async def fake_abort() -> None:
return None
monkeypatch.setattr(gateway_runtime, "run_gateway_chat", fake_run_gateway_chat)
monkeypatch.setattr(runtime_bridge, "run_concurrent_repl", replacement_run_concurrent_repl)
await runtime_bridge.run_gateway_chat(
model=None,
session_id=None,
stream_response=_fake_gateway_stream,
handle_slash_command=_fake_gateway_slash,
)
deps = cast(gateway_runtime.GatewayRuntimeDependencies, captured["deps"])
scope = {
"session_key": "agent:main:test",
"state": ChatSessionState(session_key="agent:main:test"),
"model": None,
}
await deps.run_input_loop(
scope=scope,
dispatch=fake_dispatch,
abort_active_turn=fake_abort,
)
assert runner_kwargs["surface"] is Surface.CLI_GATEWAY
assert runner_kwargs["scope"] is scope
assert runner_kwargs["dispatch"] is fake_dispatch
assert runner_kwargs["abort_active_turn"] is fake_abort
@pytest.mark.asyncio
async def test_gateway_runtime_bridge_owns_default_turn_callbacks(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
captured: dict[str, Any] = {}
async def fake_run_gateway_chat(**kwargs: Any) -> None:
captured.update(kwargs)
monkeypatch.setattr(gateway_runtime, "run_gateway_chat", fake_run_gateway_chat)
await runtime_bridge.run_gateway_chat(model=None, session_id=None)
deps = cast(gateway_runtime.GatewayRuntimeDependencies, captured["deps"])
assert deps.stream_response is runtime_bridge.stream_response_gateway
assert deps.handle_slash_command is runtime_bridge.handle_gateway_slash_command
@pytest.mark.asyncio
async def test_run_concurrent_repl_defaults_to_native_bridge(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
monkeypatch.delenv("OPENSQUILLA_TUI_BACKEND", raising=False)
calls: list[dict[str, Any]] = []
async def fake_native_repl(**kwargs: Any) -> None:
calls.append(kwargs)
async def fake_dispatch(_value: str) -> bool:
return True
scope: dict[str, Any] = {}
monkeypatch.setattr(runtime_bridge._native_bridge, "run_concurrent_repl", fake_native_repl)
await runtime_bridge.run_concurrent_repl(
surface=Surface.CLI_GATEWAY,
scope=scope,
dispatch=fake_dispatch,
)
assert calls == [
{
"surface": Surface.CLI_GATEWAY,
"scope": scope,
"dispatch": fake_dispatch,
"queue_max_size": runtime_bridge.PENDING_QUEUE_MAX_SIZE,
"abort_active_turn": None,
}
]
@pytest.mark.parametrize("backend_id", REMOVED_BACKEND_IDS)
def test_validate_tui_backend_selection_rejects_removed_backends(
monkeypatch: pytest.MonkeyPatch,
backend_id: str,
) -> None:
from opensquilla.cli.repl import runtime_bridge
monkeypatch.setenv("OPENSQUILLA_TUI_BACKEND", backend_id)
with pytest.raises(ValueError) as exc_info:
runtime_bridge.validate_tui_backend_selection()
assert "Unsupported TUI backend" in str(exc_info.value)
assert backend_id in str(exc_info.value)
@pytest.mark.asyncio
async def test_run_concurrent_repl_uses_opentui_bridge_when_selected(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
monkeypatch.setenv("OPENSQUILLA_TUI_BACKEND", "opentui")
calls: list[dict[str, Any]] = []
async def fake_opentui_repl(**kwargs: Any) -> None:
calls.append(kwargs)
async def fake_dispatch(_value: str) -> bool:
return True
scope: dict[str, Any] = {}
monkeypatch.setattr(
runtime_bridge,
"validate_tui_backend_selection",
lambda env=None: "opentui",
)
monkeypatch.setattr(runtime_bridge._opentui_bridge, "run_concurrent_repl", fake_opentui_repl)
await runtime_bridge.run_concurrent_repl(
surface=Surface.CLI_GATEWAY,
scope=scope,
dispatch=fake_dispatch,
queue_max_size=5,
)
assert calls == [
{
"surface": Surface.CLI_GATEWAY,
"scope": scope,
"dispatch": fake_dispatch,
"queue_max_size": 5,
"abort_active_turn": None,
}
]
def test_turn_stream_dependencies_use_native_renderer_by_default(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
from opensquilla.cli.tui.native.renderer import NativeStreamRenderer
monkeypatch.delenv("OPENSQUILLA_TUI_BACKEND", raising=False)
deps = runtime_bridge._turn_stream_dependencies()
assert deps.renderer_factory is NativeStreamRenderer
def test_turn_stream_dependencies_use_opentui_renderer_when_selected(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
from opensquilla.cli.tui.opentui.renderer import OpenTuiStreamRenderer
monkeypatch.setattr(
runtime_bridge,
"validate_tui_backend_selection",
lambda env=None: "opentui",
)
deps = runtime_bridge._turn_stream_dependencies()
assert deps.renderer_factory is OpenTuiStreamRenderer
@pytest.mark.asyncio
async def test_opentui_chat_runtime_exposes_launch_scoped_plugin_manager(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.tui.backend.plugins import TuiPluginManager
from opensquilla.cli.tui.opentui import runtime as opentui_runtime
from opensquilla.cli.tui.plugins.router_hud import RouterHudPlugin
scope: dict[str, object] = {}
captured: dict[str, object] = {}
fake_surface = _FakeTuiSurface()
@asynccontextmanager
async def fake_open_opentui_surface(**_kwargs: object):
yield fake_surface
async def fake_run_tui_runtime(**kwargs: object):
hooks = kwargs["hooks"]
assert not opentui_runtime.get_tui_output(scope)
hooks.expose_surface(fake_surface)
output = opentui_runtime.get_tui_output(scope)
captured["output"] = output
captured["manager"] = getattr(output, "plugin_manager", None)
hooks.clear_exposed_surface()
return SimpleNamespace()
monkeypatch.setattr(
opentui_runtime,
"open_opentui_surface",
fake_open_opentui_surface,
)
monkeypatch.setattr(opentui_runtime, "run_tui_runtime", fake_run_tui_runtime)
async def fake_dispatch(_value: str) -> bool:
return True
await opentui_runtime.run_opentui_chat_runtime(
surface=Surface.CLI_GATEWAY,
scope=scope,
dispatch=fake_dispatch,
queue_max_size=8,
)
assert opentui_runtime.get_tui_output(scope) is None
manager = captured["manager"]
assert isinstance(manager, TuiPluginManager)
assert any(isinstance(plugin, RouterHudPlugin) for plugin in manager.plugins)
@pytest.mark.asyncio
async def test_gateway_runtime_bridge_threads_stream_override_to_default_slash(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
captured: dict[str, Any] = {}
slash_captured: dict[str, Any] = {}
output_console = Console(file=None, force_terminal=False)
async def fake_run_gateway_chat(**kwargs: Any) -> None:
captured.update(kwargs)
async def fake_handle_gateway_slash_command(*args: Any, **kwargs: Any) -> bool:
slash_captured.update({"args": args, **kwargs})
return True
monkeypatch.setattr(gateway_runtime, "run_gateway_chat", fake_run_gateway_chat)
monkeypatch.setattr(
runtime_bridge._slash_bridge,
"handle_gateway_slash_command",
fake_handle_gateway_slash_command,
)
await runtime_bridge.run_gateway_chat(
model=None,
session_id=None,
stream_response=_fake_gateway_stream,
output_console=output_console,
error_panel_factory=_fake_error_panel,
)
deps = cast(gateway_runtime.GatewayRuntimeDependencies, captured["deps"])
handled = await deps.handle_slash_command(
"/path report.md summarize",
ChatSessionState(session_key="agent:main:test"),
cast(gateway_runtime.GatewayClientLike, object()),
{"mode": None},
tui_output=None,
)
assert handled is True
assert slash_captured["stream_response"] is _fake_gateway_stream
assert slash_captured["output_console"] is output_console
assert slash_captured["error_panel_factory"] is _fake_error_panel
@pytest.mark.asyncio
async def test_standalone_runtime_bridge_assembles_standalone_dependencies(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
captured: dict[str, Any] = {}
output_console = Console(file=None, force_terminal=False)
async def fake_run_standalone_chat(**kwargs: Any) -> None:
captured.update(kwargs)
monkeypatch.setattr(standalone_runtime, "run_standalone_chat", fake_run_standalone_chat)
await runtime_bridge.run_standalone_chat(
model="openai/test",
session_id="agent:main:test",
workspace="repo",
workspace_strict=True,
timeout=7.25,
stream_response=_fake_standalone_stream,
image_command_handler=_fake_standalone_stream,
output_console=output_console,
error_panel_factory=_fake_error_panel,
)
deps = cast(standalone_runtime.StandaloneRuntimeDependencies, captured["deps"])
assert captured["model"] == "openai/test"
assert captured["session_id"] == "agent:main:test"
assert captured["workspace"] == "repo"
assert captured["workspace_strict"] is True
assert captured["timeout"] == 7.25
assert deps.stream_response is _fake_standalone_stream
assert deps.image_command_handler is _fake_standalone_stream
assert deps.run_concurrent_repl is runtime_bridge.run_concurrent_repl
assert deps.slash_services_factory is runtime_bridge.standalone_slash_services_from_runtime
assert deps.get_tui_output is runtime_bridge.get_tui_output
assert deps.output_console is output_console
@pytest.mark.asyncio
async def test_standalone_runtime_bridge_resolves_default_repl_runner_at_call_time(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
captured: dict[str, Any] = {}
async def fake_run_standalone_chat(**kwargs: Any) -> None:
captured.update(kwargs)
async def replacement_run_concurrent_repl(**kwargs: Any) -> None:
return None
monkeypatch.setattr(standalone_runtime, "run_standalone_chat", fake_run_standalone_chat)
monkeypatch.setattr(runtime_bridge, "run_concurrent_repl", replacement_run_concurrent_repl)
await runtime_bridge.run_standalone_chat(
model=None,
session_id=None,
stream_response=_fake_standalone_stream,
image_command_handler=_fake_standalone_stream,
)
deps = cast(standalone_runtime.StandaloneRuntimeDependencies, captured["deps"])
assert deps.run_concurrent_repl is replacement_run_concurrent_repl
@pytest.mark.asyncio
async def test_standalone_runtime_bridge_owns_default_turn_callbacks(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import runtime_bridge
captured: dict[str, Any] = {}
async def fake_run_standalone_chat(**kwargs: Any) -> None:
captured.update(kwargs)
monkeypatch.setattr(standalone_runtime, "run_standalone_chat", fake_run_standalone_chat)
await runtime_bridge.run_standalone_chat(model=None, session_id=None)
deps = cast(standalone_runtime.StandaloneRuntimeDependencies, captured["deps"])
assert deps.stream_response is runtime_bridge.stream_response_turnrunner
assert deps.image_command_handler is runtime_bridge.handle_image_command_turnrunner
+205
View File
@@ -0,0 +1,205 @@
from __future__ import annotations
from collections.abc import AsyncIterator
from pathlib import Path
from types import SimpleNamespace
from typing import Any
import pytest
from opensquilla.cli.repl.session_state import ChatSessionState
from opensquilla.cli.repl.stream import TurnResult
from opensquilla.cli.tui.contracts import TuiOutputHandle
class _FakeGatewayClient:
def __init__(self) -> None:
self.reset_calls: list[str] = []
async def create_session(
self,
agent_id: str = "main",
model: str | None = None,
display_name: str | None = None,
) -> str:
raise AssertionError("create_session is not used by these tests")
async def list_sessions(self, limit: int = 50) -> dict[str, Any]:
raise AssertionError("list_sessions is not used by these tests")
async def resolve_session(self, key: str) -> dict[str, Any]:
raise AssertionError("resolve_session is not used by these tests")
async def delete_sessions(self, keys: list[str]) -> dict[str, Any]:
raise AssertionError("delete_sessions is not used by these tests")
async def reset_session(self, session_key: str) -> dict[str, object]:
self.reset_calls.append(session_key)
return {"reset": True, "key": session_key}
async def compact_session(self, key: str) -> dict[str, Any]:
raise AssertionError("compact_session is not used by these tests")
async def list_models(
self,
provider: str | None = None,
capabilities: list[str] | None = None,
) -> list[dict[str, Any]]:
raise AssertionError("list_models is not used by these tests")
async def patch_session(self, key: str, **fields: Any) -> dict[str, Any]:
raise AssertionError("patch_session is not used by these tests")
async def usage_status(self) -> dict[str, Any]:
raise AssertionError("usage_status is not used by these tests")
async def upload_file(self, path: Path, mime: str, name: str) -> str:
raise AssertionError("upload_file is not used by these tests")
def send_message(
self,
session_key: str,
message: str,
attachments: list[dict] | None = None,
elevated: str | None = None,
) -> AsyncIterator[dict[str, Any]]:
async def _unused_events() -> AsyncIterator[dict[str, Any]]:
raise AssertionError("send_message is not used by these tests")
yield {}
return _unused_events()
async def resolve_approval(
self,
approval_id: str,
approved: bool,
*,
choice: str | None = None,
) -> Any:
del choice
raise AssertionError("resolve_approval is not used by these tests")
async def abort_session(self, key: str) -> dict[str, Any]:
raise AssertionError("abort_session is not used by these tests")
class _RecordingOutputHandle:
@property
def approval_surface(self) -> object:
return "approval-surface"
async def write_through(self, payload: str) -> None:
return None
def stream_output(self):
raise AssertionError("stream_output is only consumed by the renderer")
def test_gateway_slash_adapter_exposes_typed_context() -> None:
from opensquilla.cli.repl.slash_adapter import GatewaySlashContext
tui_output = _RecordingOutputHandle()
context = GatewaySlashContext(
state=ChatSessionState(session_key="agent:main:test", model="openai/test"),
client=_FakeGatewayClient(),
elevated_state={"mode": None},
tui_output=tui_output,
)
assert isinstance(context.tui_output, TuiOutputHandle)
@pytest.mark.asyncio
async def test_gateway_slash_adapter_handles_clear_without_chat_command_state() -> None:
from opensquilla.cli.repl.slash_adapter import (
GatewaySlashContext,
handle_gateway_slash_command,
)
state = ChatSessionState(session_key="agent:main:test", model="openai/test")
state.transcript.add("user", "hello")
client = _FakeGatewayClient()
handled = await handle_gateway_slash_command(
"/clear",
GatewaySlashContext(
state=state,
client=client,
elevated_state={"mode": None},
),
)
assert handled is True
assert client.reset_calls == ["agent:main:test"]
assert state.transcript.turns == []
@pytest.mark.asyncio
async def test_gateway_slash_adapter_threads_tui_output_to_streaming_slash_command(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import slash_adapter
from opensquilla.cli.repl.slash_adapter import (
GatewaySlashContext,
handle_gateway_slash_command,
)
state = ChatSessionState(session_key="agent:main:test", model="openai/test")
client = SimpleNamespace(is_local_gateway=True)
tui_output = _RecordingOutputHandle()
captured: dict[str, Any] = {}
def fake_path_prompt_and_attachments(command: str) -> tuple[str, list[dict[str, Any]]]:
captured["path_command"] = command
return "inspect /repo", []
async def fake_stream_response_gateway(
gateway_client: object,
session_key: str,
message: str,
elevated_state: dict[str, str | None],
*,
attachments: list[dict[str, Any]] | None = None,
tui_output: TuiOutputHandle | None = None,
) -> TurnResult:
captured.update(
{
"client": gateway_client,
"session_key": session_key,
"message": message,
"elevated_state": elevated_state,
"attachments": attachments,
"tui_output": tui_output,
}
)
return TurnResult(text="done")
monkeypatch.setattr(
slash_adapter,
"path_prompt_and_attachments",
fake_path_prompt_and_attachments,
)
monkeypatch.setattr(
slash_adapter,
"stream_response_gateway",
fake_stream_response_gateway,
)
handled = await handle_gateway_slash_command(
"/path /repo inspect",
GatewaySlashContext(
state=state,
client=client,
elevated_state={"mode": "on"},
tui_output=tui_output,
),
)
assert handled is True
assert captured["path_command"] == "/path /repo inspect"
assert captured["session_key"] == "agent:main:test"
assert captured["message"] == "inspect /repo"
assert captured["elevated_state"] == {"mode": "on"}
assert captured["attachments"] == []
assert captured["tui_output"] is tui_output
assert state.transcript.to_markdown()
+60
View File
@@ -0,0 +1,60 @@
from __future__ import annotations
from typing import Any, cast
import pytest
from rich.console import Console
from rich.panel import Panel
from opensquilla.cli.repl.session_state import ChatSessionState
from opensquilla.cli.repl.stream import TurnResult
@pytest.mark.asyncio
async def test_gateway_slash_bridge_syncs_io_and_builds_context(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import slash_adapter, slash_bridge
from opensquilla.cli.repl.slash_adapter import GatewaySlashContext
output_console = Console(file=None, force_terminal=False)
observed: dict[str, Any] = {}
async def fake_handle(cmd: str, context: GatewaySlashContext) -> bool:
observed["cmd"] = cmd
observed["context"] = context
observed["adapter_console"] = slash_adapter.console
observed["adapter_error_panel"] = slash_adapter.error_panel
return True
async def fake_stream(*args: Any, **kwargs: Any) -> TurnResult:
return TurnResult(text="streamed")
def fake_error_panel(message: str, *, title: str = "Error") -> Panel:
return Panel(message, title=title)
monkeypatch.setattr(slash_adapter, "handle_gateway_slash_command", fake_handle)
state = ChatSessionState(session_key="agent:main:test", model="openai/test")
client = cast(slash_bridge.GatewayClientLike, object())
elevated_state: dict[str, str | None] = {"mode": "enabled"}
handled = await slash_bridge.handle_gateway_slash_command(
"/status",
state,
client,
elevated_state,
stream_response=fake_stream,
output_console=output_console,
error_panel_factory=fake_error_panel,
)
context = observed["context"]
assert handled is True
assert observed["cmd"] == "/status"
assert context.state is state
assert context.client is client
assert context.elevated_state is elevated_state
assert context.stream_response is fake_stream
assert observed["adapter_console"] is output_console
assert observed["adapter_error_panel"] is fake_error_panel
+288
View File
@@ -0,0 +1,288 @@
"""Slash-command classification table.
The concurrent REPL spawns each user input as a child turn task while the
input task keeps accepting keystrokes. When new input arrives mid-turn,
the policy split routes the command by category:
* ``DESTRUCTIVE`` (``/clear`` / ``/reset`` / ``/compact``) — purge the
pending queue, cancel the active turn, then run synchronously.
* ``EXIT`` (``/exit`` / ``/quit``) — drain the pending queue then exit
the loop (mirroring Ctrl-D semantics).
* ``PURE_INFO`` / ``STATE_MUTATION`` — both enqueue identically.
* ``NON_SLASH`` — runs as a normal turn.
These tests pin the classification surface so the runtime split in
``chat_cmd._run_concurrent_repl`` can rely on it.
"""
from __future__ import annotations
import pytest
from opensquilla.cli.repl.slash_policy import (
DESTRUCTIVE_SLASH_WORDS,
EXIT_SLASH_WORDS,
SlashCategory,
classify,
)
# --------------------------------------------------------------------------- #
# Destructive set #
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize(
"command",
[
"/clear",
"/reset",
"/compact",
"/cmp",
"/clear ",
],
)
def test_classify_destructive(command: str) -> None:
"""Exact bare destructive words return DESTRUCTIVE.
Only the exact bare lowercase word qualifies: the slash handlers match
exact strings, so anything else must not purge queued work and then
fall through to "Unknown command".
"""
assert classify(command) is SlashCategory.DESTRUCTIVE
@pytest.mark.parametrize(
"command",
[
"/reset trailing-junk",
"/compact extra args",
"/CLEAR",
"/Clear",
],
)
def test_classify_destructive_requires_exact_bare_word(command: str) -> None:
"""Case slips and stray arguments never classify as DESTRUCTIVE.
Destructive routing purges the pending queue and cancels the in-flight
turn BEFORE dispatch, while the handlers only match the exact bare
lowercase word — so these variants must enqueue instead, letting the
handler chain surface "Unknown command" without destroying work.
"""
assert classify(command) is not SlashCategory.DESTRUCTIVE
def test_destructive_set_matches_plan_lock() -> None:
"""The destructive set is locked to exactly these commands.
The destructive set is closed; any future addition needs a
plan amendment. This test pins the frozenset contents so a silent
expansion fails loudly. ``/cmp`` is the ``/compact`` alias and shares its
context-rewriting (and therefore destructive) semantics.
"""
assert DESTRUCTIVE_SLASH_WORDS == frozenset({"/clear", "/reset", "/compact", "/cmp"})
# --------------------------------------------------------------------------- #
# Exit set #
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize(
"command",
[
"/exit",
"/quit",
"/exit ",
],
)
def test_classify_exit(command: str) -> None:
"""Exact bare exit words return EXIT.
``/exit`` and ``/quit`` are NOT destructive — they drain the pending
queue first so queued user work still runs.
"""
assert classify(command) is SlashCategory.EXIT
@pytest.mark.parametrize("command", ["/quit now", "/Exit", "/EXIT stuff"])
def test_classify_exit_requires_exact_bare_word(command: str) -> None:
"""Case slips and stray arguments never classify as EXIT.
EXIT drains the queue and terminates the loop before dispatch; a
variant the handlers would reject must enqueue instead.
"""
assert classify(command) is not SlashCategory.EXIT
def test_exit_set_matches_plan_lock() -> None:
"""The exit set is locked to exactly ``/exit`` and ``/quit``."""
assert EXIT_SLASH_WORDS == frozenset({"/exit", "/quit"})
# --------------------------------------------------------------------------- #
# Enqueue set (pure-info and state-mutation — both enqueue identically) #
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize(
"command",
[
"/help",
"/version",
"/cost",
"/usage",
"/save",
"/approvals",
"/permissions",
"/forget",
"/sessions",
"/resume some-id",
"/delete other-id",
"/file /tmp/path.txt",
"/new",
"/model gpt-5",
"/image /tmp/pic.png",
"/path /tmp/file.md",
"/models",
"/status",
"/session",
],
)
def test_classify_pure_info_or_state_mutation(command: str) -> None:
"""Pure-info and state-mutation commands both enqueue.
The two enqueue subcategories share the same runtime behavior (append
to pending FIFO, run after current turn finishes). The classifier
reports the more specific category so callers that want telemetry
differentiation can subset, but the dispatch loop never branches on
this distinction.
"""
category = classify(command)
assert category in {SlashCategory.PURE_INFO, SlashCategory.STATE_MUTATION}
# Sanity: must NOT be destructive / exit / non-slash for this set.
assert category is not SlashCategory.DESTRUCTIVE
assert category is not SlashCategory.EXIT
assert category is not SlashCategory.NON_SLASH
# --------------------------------------------------------------------------- #
# Non-slash and edge cases #
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize(
"command",
[
"hello world",
"what is the capital of France?",
" multi-word user prompt ",
"/", # bare slash with no command word — not a slash command yet
],
)
def test_classify_non_slash(command: str) -> None:
"""Non-slash inputs return NON_SLASH and run as a normal turn.
A bare ``/`` with no following character is not a slash command — the
head word is just ``/`` which is not in any of the explicit sets and
does not start with a recognized slash word; it falls through to the
enqueue path. (This is an internal detail; see the docstring of
``test_classify_unknown_slash_is_enqueue`` for the unknown-slash
contract.)
"""
# The bare `/` case actually starts with `/` so it'll be treated as an
# unknown slash word (enqueue) under the locked policy. Skip it from the
# strict NON_SLASH assertion and assert on the others.
if command.strip() == "/":
category = classify(command)
assert category is not SlashCategory.DESTRUCTIVE
assert category is not SlashCategory.EXIT
return
assert classify(command) is SlashCategory.NON_SLASH
def test_classify_empty_input_is_non_slash() -> None:
"""Empty input maps to NON_SLASH; the dispatch loop ignores it."""
assert classify("") is SlashCategory.NON_SLASH
assert classify(" ") is SlashCategory.NON_SLASH
@pytest.mark.parametrize(
"command",
[
" /clear ",
"\t/reset",
],
)
def test_classify_handles_leading_whitespace(command: str) -> None:
"""Surrounding whitespace must not change classification.
Users typing into the REPL may have trailing or leading spaces from a
history edit; the classifier strips before matching the bare word.
"""
assert classify(command) is SlashCategory.DESTRUCTIVE
def test_classify_destructive_and_exit_are_case_sensitive() -> None:
"""DESTRUCTIVE/EXIT match only the exact lowercase spelling.
The handlers match exact lowercase strings, so ``/CLEAR`` must not
purge the queue and cancel the turn only to land on "Unknown
command". Enqueue categories keep matching case-insensitively via the
lowercased head word.
"""
assert classify("/CLEAR") is not SlashCategory.DESTRUCTIVE
assert classify("/Exit") is not SlashCategory.EXIT
assert classify("/Help") in {SlashCategory.PURE_INFO, SlashCategory.STATE_MUTATION}
def test_classify_unknown_slash_is_enqueue() -> None:
"""Unknown slash words fall through to an enqueue category.
The destructive set is explicitly closed (``/clear``,
``/reset``, ``/compact`` only); anything else starting with ``/`` and
not in the exit set MUST NOT cancel the active turn. The chosen
behavior is to route through the enqueue path so the existing slash-
handler chain surfaces the canonical
``"Unknown command. Use /help."`` notice without disturbing the
in-flight turn. This locks the safe default.
"""
category = classify("/foobar")
assert category is not SlashCategory.DESTRUCTIVE
assert category is not SlashCategory.EXIT
assert category is not SlashCategory.NON_SLASH
# Documented choice: route through the enqueue path.
assert category in {SlashCategory.PURE_INFO, SlashCategory.STATE_MUTATION}
# --------------------------------------------------------------------------- #
# Local (host-only UI) set #
# --------------------------------------------------------------------------- #
@pytest.mark.parametrize("command", ["/theme", "/theme midnight", "/theme ", "/THEME"])
def test_classify_local_theme(command: str) -> None:
"""/theme is a host-only UI command -> LOCAL.
LOCAL commands run immediately (inline on the runtime loop), are never echoed
as a prompt block, and are never queued behind an in-flight turn.
"""
assert classify(command) is SlashCategory.LOCAL
def test_local_set_is_narrow_and_disjoint() -> None:
from opensquilla.cli.repl.slash_policy import (
LOCAL_SLASH_WORDS,
PURE_INFO_SLASH_WORDS,
STATE_MUTATION_SLASH_WORDS,
)
# Keep LOCAL narrow: only side-effect-free host commands belong here today.
assert LOCAL_SLASH_WORDS == {"/theme"}
# LOCAL must not overlap any queue/cancel/exit category.
for other in (
DESTRUCTIVE_SLASH_WORDS,
EXIT_SLASH_WORDS,
PURE_INFO_SLASH_WORDS,
STATE_MUTATION_SLASH_WORDS,
):
assert not (LOCAL_SLASH_WORDS & other)
@@ -0,0 +1,249 @@
from __future__ import annotations
import importlib
import inspect
import sys
from types import SimpleNamespace
from typing import Any, cast
import pytest
from opensquilla.cli.repl import standalone_slash_adapter
from opensquilla.cli.repl.session_state import ChatSessionState
from opensquilla.cli.repl.stream import TurnResult, UsageSummary
from opensquilla.cli.tui.contracts import TuiOutputHandle
from opensquilla.engine.commands import Surface
def test_standalone_runtime_has_no_raw_prompt_application_dependency(monkeypatch) -> None:
monkeypatch.delitem(
sys.modules,
"opensquilla.cli.repl.standalone_runtime",
raising=False,
)
original_import = __import__
blocked_module = "prompt" + "_toolkit"
def _guarded_import(name, globals=None, locals=None, fromlist=(), level=0): # noqa: ANN001
if name == blocked_module or name.startswith(f"{blocked_module}."):
raise AssertionError(f"standalone runtime imported {blocked_module} via {name}")
return original_import(name, globals, locals, fromlist, level)
monkeypatch.setattr("builtins.__import__", _guarded_import)
module = importlib.import_module("opensquilla.cli.repl.standalone_runtime")
source = inspect.getsource(module)
assert "ChatApplication" not in source
def test_standalone_session_context_mirrors_state_to_legacy_scope() -> None:
from opensquilla.cli.repl.standalone_runtime import StandaloneSessionContext
original_tool_ctx = object()
replacement_tool_ctx = object()
state = ChatSessionState(
session_key="agent:main:standalone:original",
model="standalone/original",
)
replacement_state = ChatSessionState(
session_key="agent:main:standalone:replacement",
model="standalone/replacement",
)
context = StandaloneSessionContext.create(state=state, tool_ctx=original_tool_ctx)
assert context.scope["session_key"] == "agent:main:standalone:original"
assert context.scope["tool_ctx"] is original_tool_ctx
assert context.scope["state"] is state
assert context.scope["model"] == "standalone/original"
context.replace_session(
session_key="agent:main:standalone:replacement",
tool_ctx=replacement_tool_ctx,
state=replacement_state,
model="standalone/replacement",
)
assert context.session_key == "agent:main:standalone:replacement"
assert context.tool_ctx is replacement_tool_ctx
assert context.model == "standalone/replacement"
assert context.scope["session_key"] == "agent:main:standalone:replacement"
assert context.scope["tool_ctx"] is replacement_tool_ctx
assert context.scope["state"] is replacement_state
assert context.scope["model"] == "standalone/replacement"
@pytest.mark.asyncio
async def test_standalone_runtime_mirrors_turn_model_update_to_legacy_scope(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import standalone_runtime
class _FakeSessionManager:
async def get_or_create(self, session_key: str, agent_id: str = "main") -> object:
return SimpleNamespace(session_key=session_key, agent_id=agent_id)
class _FakeServices:
def __init__(self) -> None:
self.config = None
self.session_manager = _FakeSessionManager()
async def close(self) -> None:
return None
services = _FakeServices()
turn_runner = object()
output = cast(TuiOutputHandle, object())
pending_provider = SimpleNamespace(drain_pending=lambda: [])
captured: dict[str, Any] = {}
async def fake_build_services() -> _FakeServices:
return services
def fake_build_turn_runner_from_services(_services: object) -> object:
return turn_runner
async def fake_stream_response(
active_turn_runner: object,
session_key: str,
tool_ctx: object,
message: str,
model: str | None = None,
svc: object = None,
timeout: float | None = None,
*,
tui_output: TuiOutputHandle | None = None,
pending_input_provider: object | None = None,
) -> TurnResult:
captured["stream"] = {
"turn_runner": active_turn_runner,
"session_key": session_key,
"tool_ctx": tool_ctx,
"message": message,
"model": model,
"svc": svc,
"timeout": timeout,
"tui_output": tui_output,
"pending_input_provider": pending_input_provider,
}
return TurnResult(
text="assistant reply",
usage=UsageSummary(input_tokens=5, output_tokens=8),
model_after="standalone/after",
)
async def fake_run_concurrent_repl(
*,
surface: Surface,
scope: standalone_runtime.StandaloneRuntimeScope,
dispatch,
) -> None:
captured["surface"] = surface
captured["initial_scope"] = dict(scope)
scope["pending_input_provider"] = pending_provider
assert await dispatch("hello") is True
captured["scope_after_message"] = dict(scope)
captured["state_after_message"] = scope["state"]
monkeypatch.setattr("opensquilla.gateway.build_services", fake_build_services)
monkeypatch.setattr(
"opensquilla.gateway.build_turn_runner_from_services",
fake_build_turn_runner_from_services,
)
deps = standalone_runtime.StandaloneRuntimeDependencies(
stream_response=fake_stream_response,
image_command_handler=fake_stream_response,
run_concurrent_repl=fake_run_concurrent_repl,
slash_services_factory=lambda _svc: standalone_slash_adapter.StandaloneSlashServices(),
sync_slash_adapter_io=lambda: None,
get_tui_output=lambda _scope: output,
)
await standalone_runtime.run_standalone_chat(
model="openrouter/test",
session_id="agent:main:standalone:test",
deps=deps,
timeout=7.25,
)
assert captured["surface"] is Surface.CLI_STANDALONE
assert captured["initial_scope"]["session_key"] == "agent:main:standalone:test"
assert captured["initial_scope"]["model"] == "openrouter/test"
assert captured["stream"]["turn_runner"] is turn_runner
assert captured["stream"]["session_key"] == "agent:main:standalone:test"
assert captured["stream"]["message"] == "hello"
assert captured["stream"]["model"] == "openrouter/test"
assert captured["stream"]["svc"] is services
assert captured["stream"]["timeout"] == 7.25
assert captured["stream"]["tui_output"] is output
assert captured["stream"]["pending_input_provider"] is pending_provider
assert captured["scope_after_message"]["model"] == "standalone/after"
assert captured["state_after_message"].model == "standalone/after"
assert captured["state_after_message"].usage.input_tokens == 5
assert captured["state_after_message"].usage.output_tokens == 8
@pytest.mark.asyncio
async def test_standalone_runtime_matches_exit_with_standalone_surface(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import standalone_runtime
class _FakeSessionManager:
async def get_or_create(self, session_key: str, agent_id: str = "main") -> object:
return SimpleNamespace(session_key=session_key, agent_id=agent_id)
class _FakeServices:
def __init__(self) -> None:
self.config = None
self.session_manager = _FakeSessionManager()
async def close(self) -> None:
return None
surfaces: list[Surface] = []
def fake_is_exit_command(value: str, surface: Surface) -> bool:
surfaces.append(surface)
return value == "/exit"
async def fake_build_services() -> _FakeServices:
return _FakeServices()
async def fake_run_concurrent_repl(
*,
surface: Surface,
scope: standalone_runtime.StandaloneRuntimeScope,
dispatch,
) -> None:
assert surface is Surface.CLI_STANDALONE
assert await dispatch("/exit") is False
monkeypatch.setattr("opensquilla.gateway.build_services", fake_build_services)
monkeypatch.setattr(
"opensquilla.gateway.build_turn_runner_from_services",
lambda _services: object(),
)
monkeypatch.setattr(standalone_runtime, "is_exit_command", fake_is_exit_command)
deps = standalone_runtime.StandaloneRuntimeDependencies(
stream_response=lambda *args, **kwargs: None, # type: ignore[arg-type]
image_command_handler=lambda *args, **kwargs: None, # type: ignore[arg-type]
run_concurrent_repl=fake_run_concurrent_repl,
slash_services_factory=lambda _svc: standalone_slash_adapter.StandaloneSlashServices(),
sync_slash_adapter_io=lambda: None,
get_tui_output=lambda _scope: None,
)
await standalone_runtime.run_standalone_chat(
model=None,
session_id="agent:main:standalone:test",
deps=deps,
)
assert surfaces == [Surface.CLI_STANDALONE]
@@ -0,0 +1,323 @@
from __future__ import annotations
from types import SimpleNamespace
from typing import Any, cast
import pytest
from opensquilla.cli.repl.session_state import ChatSessionState
from opensquilla.cli.repl.stream import TurnResult
from opensquilla.cli.tui.contracts import TuiOutputHandle
class _StandaloneSlashHarness:
def __init__(self) -> None:
self.create_calls: list[dict[str, str]] = []
self.truncate_calls: list[tuple[str, int]] = []
self.compact_calls: list[tuple[str, int, object | None]] = []
self.flush_calls: list[dict[str, object]] = []
self.transcripts: dict[str, list[object]] = {}
async def create_session(self, session_key: str, *, agent_id: str = "main") -> object:
self.create_calls.append({"session_key": session_key, "agent_id": agent_id})
return SimpleNamespace(session_key=session_key, agent_id=agent_id)
async def read_transcript(self, session_key: str) -> list[object]:
return list(self.transcripts.get(session_key, []))
async def truncate_session(self, session_key: str, *, max_messages: int = 0) -> None:
self.truncate_calls.append((session_key, max_messages))
self.transcripts[session_key] = []
async def compact_session(
self,
session_key: str,
context_window_tokens: int,
config: object | None = None,
) -> str:
self.compact_calls.append((session_key, context_window_tokens, config))
return "summary"
async def flush_transcript(
self,
transcript: object,
session_key: str,
**kwargs: object,
) -> object:
self.flush_calls.append(
{"transcript": transcript, "session_key": session_key, "kwargs": kwargs}
)
return SimpleNamespace(
mode="llm",
error=None,
indexed_chunk_count=1,
integrity_status="ok",
output_coverage_status="ok",
invalid_candidate_count=0,
candidate_missing_ids=[],
obligation_status="ok",
obligation_missing_ids=[],
)
def _slash_services(harness: _StandaloneSlashHarness):
from opensquilla.cli.repl.standalone_slash_adapter import StandaloneSlashServices
return StandaloneSlashServices(
create_session=harness.create_session,
read_transcript=harness.read_transcript,
truncate_session=harness.truncate_session,
compact_session=harness.compact_session,
flush_transcript=harness.flush_transcript,
)
@pytest.mark.asyncio
async def test_standalone_slash_adapter_leaves_exit_to_the_runtime_loop(
capsys: pytest.CaptureFixture[str],
) -> None:
"""Exit commands are owned by the runtime loop, not the slash handler.
The handler has no exit branch: an ``/exit`` that reaches it anyway is
answered with the unknown-command notice and ``True`` (handled), so
the dispatch loop keeps running instead of terminating from inside
slash dispatch.
"""
from opensquilla.cli.repl.standalone_slash_adapter import (
StandaloneSlashContext,
handle_standalone_slash_command,
)
state = ChatSessionState(session_key="agent:main:standalone:test", model="openai/test")
context = StandaloneSlashContext(
state=state,
session_key=state.session_key,
model=state.model,
tool_ctx=object(),
slash_services=_slash_services(_StandaloneSlashHarness()),
turn_runner=object(),
build_tool_ctx=lambda _session_key: object(),
replace_session=lambda **_updates: None,
)
handled = await handle_standalone_slash_command("/exit", context)
assert handled is True
assert "Unknown command." in capsys.readouterr().out
@pytest.mark.asyncio
async def test_standalone_slash_adapter_updates_model_without_chat_cmd_loop() -> None:
from opensquilla.cli.repl.standalone_slash_adapter import (
StandaloneSlashContext,
handle_standalone_slash_command,
)
state = ChatSessionState(session_key="agent:main:standalone:test", model="old/model")
context = StandaloneSlashContext(
state=state,
session_key=state.session_key,
model=state.model,
tool_ctx=object(),
slash_services=_slash_services(_StandaloneSlashHarness()),
turn_runner=object(),
build_tool_ctx=lambda _session_key: object(),
replace_session=lambda **_updates: None,
)
handled = await handle_standalone_slash_command("/model new/model", context)
assert handled is True
assert state.model == "new/model"
assert context.model == "new/model"
@pytest.mark.asyncio
async def test_standalone_slash_adapter_streams_path_without_chat_cmd_loop(
tmp_path,
) -> None:
from opensquilla.cli.repl.standalone_slash_adapter import (
StandaloneSlashContext,
handle_standalone_slash_command,
)
target = tmp_path / "notes.md"
target.write_text("hello\n", encoding="utf-8")
state = ChatSessionState(session_key="agent:main:standalone:test", model="openai/test")
tool_ctx = object()
stream_calls: list[dict[str, Any]] = []
async def stream_response(
turn_runner: object,
session_key: str,
tool_context: object,
message: str,
*,
model: str | None = None,
services: object = None,
timeout: float | None = None,
tui_output: TuiOutputHandle | None = None,
) -> TurnResult:
stream_calls.append(
{
"turn_runner": turn_runner,
"session_key": session_key,
"tool_context": tool_context,
"message": message,
"model": model,
"services": services,
"timeout": timeout,
"tui_output": tui_output,
}
)
return TurnResult(text="done")
runtime_services = SimpleNamespace()
turn_runner = object()
tui_output = cast(TuiOutputHandle, object())
context = StandaloneSlashContext(
state=state,
session_key=state.session_key,
model=state.model,
tool_ctx=tool_ctx,
slash_services=_slash_services(_StandaloneSlashHarness()),
runtime_services=runtime_services,
turn_runner=turn_runner,
timeout=7.25,
tui_output=tui_output,
build_tool_ctx=lambda _session_key: object(),
replace_session=lambda **_updates: None,
stream_response=stream_response,
)
handled = await handle_standalone_slash_command(f"/path {target} inspect", context)
assert handled is True
assert len(stream_calls) == 1
assert stream_calls[0]["turn_runner"] is turn_runner
assert stream_calls[0]["session_key"] == "agent:main:standalone:test"
assert stream_calls[0]["tool_context"] is tool_ctx
assert stream_calls[0]["model"] == "openai/test"
assert stream_calls[0]["services"] is runtime_services
assert stream_calls[0]["timeout"] == 7.25
assert stream_calls[0]["tui_output"] is tui_output
assert "inspect" in stream_calls[0]["message"]
assert str(target.resolve(strict=False)) in stream_calls[0]["message"]
assert state.transcript.to_markdown()
@pytest.mark.asyncio
async def test_standalone_slash_adapter_new_session_uses_typed_create_handle() -> None:
from opensquilla.cli.repl.standalone_slash_adapter import (
StandaloneSlashContext,
handle_standalone_slash_command,
)
harness = _StandaloneSlashHarness()
replacement_calls: list[dict[str, object]] = []
state = ChatSessionState(session_key="agent:main:standalone:old", model="openai/test")
context = StandaloneSlashContext(
state=state,
session_key=state.session_key,
model=state.model,
tool_ctx=object(),
slash_services=_slash_services(harness),
turn_runner=object(),
build_tool_ctx=lambda session_key: {"session_key": session_key},
replace_session=lambda **updates: replacement_calls.append(updates),
)
handled = await handle_standalone_slash_command("/new scratch", context)
assert handled is True
assert len(harness.create_calls) == 1
new_session_key = harness.create_calls[0]["session_key"]
assert new_session_key.startswith("agent:main:standalone:")
assert harness.create_calls[0]["agent_id"] == "main"
assert context.session_key == new_session_key
assert context.state.session_key == new_session_key
assert context.tool_ctx == {"session_key": new_session_key}
assert replacement_calls[0]["session_key"] == new_session_key
@pytest.mark.asyncio
async def test_standalone_slash_adapter_reset_uses_typed_flush_and_truncate_handles() -> None:
from opensquilla.cli.repl.standalone_slash_adapter import (
StandaloneSlashContext,
handle_standalone_slash_command,
)
harness = _StandaloneSlashHarness()
session_key = "agent:main:standalone:test"
harness.transcripts[session_key] = [SimpleNamespace(role="user", content="persisted")]
state = ChatSessionState(session_key=session_key, model="openai/test")
state.transcript.add("user", "local")
context = StandaloneSlashContext(
state=state,
session_key=session_key,
model=state.model,
tool_ctx=object(),
slash_services=_slash_services(harness),
turn_runner=object(),
build_tool_ctx=lambda _session_key: object(),
replace_session=lambda **_updates: None,
)
handled = await handle_standalone_slash_command("/reset", context)
assert handled is True
assert len(harness.flush_calls) == 1
assert harness.flush_calls[0]["session_key"] == session_key
assert harness.flush_calls[0]["kwargs"] == {
"agent_id": "main",
"timeout": 30.0,
"message_window": 0,
"segment_mode": "auto",
}
assert harness.truncate_calls == [(session_key, 0)]
assert not state.transcript.to_markdown()
@pytest.mark.asyncio
async def test_standalone_slash_adapter_compact_uses_typed_compact_handles() -> None:
from opensquilla.cli.repl.standalone_slash_adapter import (
StandaloneSlashContext,
StandaloneSlashServices,
handle_standalone_slash_command,
)
harness = _StandaloneSlashHarness()
session_key = "agent:main:standalone:test"
harness.transcripts[session_key] = [SimpleNamespace(role="user", content="persisted")]
config = SimpleNamespace(
context_budget_tokens=4321,
compaction=SimpleNamespace(enabled=True, model=None, timeout_seconds=12.5),
)
state = ChatSessionState(session_key=session_key, model="openai/test")
context = StandaloneSlashContext(
state=state,
session_key=session_key,
model=state.model,
tool_ctx=object(),
slash_services=StandaloneSlashServices(
create_session=harness.create_session,
read_transcript=harness.read_transcript,
compact_session=harness.compact_session,
flush_transcript=harness.flush_transcript,
config=config,
provider_selector=None,
),
turn_runner=object(),
build_tool_ctx=lambda _session_key: object(),
replace_session=lambda **_updates: None,
)
handled = await handle_standalone_slash_command("/compact", context)
assert handled is True
assert len(harness.flush_calls) == 1
assert len(harness.compact_calls) == 1
compact_session_key, context_window, compaction_config = harness.compact_calls[0]
assert compact_session_key == session_key
assert context_window == 4321
assert compaction_config is not None
@@ -0,0 +1,400 @@
from __future__ import annotations
import inspect
from types import SimpleNamespace
from typing import Any, cast
import pytest
from opensquilla.cli import chat_cmd
from opensquilla.cli.repl.stream import TurnResult
from opensquilla.cli.tui.contracts import TuiOutputHandle
from opensquilla.engine.commands import Surface
class _RecordingOutputHandle:
def __init__(self) -> None:
self._approval_surface = object()
@property
def approval_surface(self) -> object:
return self._approval_surface
async def write_through(self, payload: str) -> None:
return None
def stream_output(self):
raise AssertionError("stream_output is only consumed by renderers")
class _FalseyCallable:
def __call__(self, *args: Any, **kwargs: Any) -> Any:
return None
def __bool__(self) -> bool:
return False
class _FalseyConsole:
def __bool__(self) -> bool:
return False
class _ApprovalRenderer:
buffer = ""
def __init__(self, **_kwargs: Any) -> None:
return None
def __enter__(self) -> _ApprovalRenderer:
return self
def __exit__(self, *_args: Any) -> bool:
return False
async def aappend_text(self, _delta: str) -> None:
return None
def tool_start(self, *_args: Any, **_kwargs: Any) -> None:
return None
def tool_finished(self, *_args: Any, **_kwargs: Any) -> None:
return None
def pulse(self) -> None:
return None
def status(self, *_args: Any, **_kwargs: Any) -> None:
return None
def error(self, *_args: Any, **_kwargs: Any) -> None:
return None
def finalize(self, *_args: Any, **_kwargs: Any) -> None:
return None
class _ApprovalClient:
async def send_message(self, *_args: Any, **_kwargs: Any):
yield {
"event": "session.event.tool_result",
"result": {
"status": "approval_required",
"approval_id": "approval-1",
"command": "touch file",
},
"tool_use_id": "tool-1",
}
yield {"event": "session.event.done", "reason": "stop"}
async def resolve_approval(self, *_args: Any, **_kwargs: Any) -> None:
return None
async def abort_session(self, *_args: Any, **_kwargs: Any) -> None:
return None
class _SessionManager:
def __init__(self) -> None:
self.node = SimpleNamespace(
session_key="agent:main:webchat:abc",
agent_id="main",
origin=None,
)
async def get_session(self, session_key: str) -> object | None:
return self.node if session_key == self.node.session_key else None
async def update(self, session_key: str, **fields: Any) -> object:
for key, value in fields.items():
setattr(self.node, key, value)
return self.node
def _sandbox_config() -> SimpleNamespace:
return SimpleNamespace(
sandbox=SimpleNamespace(run_mode="standard", sandbox=True, security_grading=True),
permissions=SimpleNamespace(default_mode="off"),
)
def test_default_turn_stream_dependencies_preserves_explicit_falsey_overrides() -> None:
from opensquilla.cli.repl import turn_stream
renderer_factory = _FalseyCallable()
output_console = _FalseyConsole()
deps = turn_stream.default_turn_stream_dependencies(
renderer_factory=renderer_factory,
output_console=output_console,
)
assert deps.renderer_factory is renderer_factory
assert deps.output_console is output_console
def test_turn_stream_dependencies_preserve_frontend_owned_approval_surfaces() -> None:
from opensquilla.cli.repl import turn_stream
gateway_surface = object()
standalone_surface = object()
deps = turn_stream.default_turn_stream_dependencies(
gateway_approval_surface=gateway_surface,
standalone_approval_surface=standalone_surface,
)
assert deps.gateway_approval_surface is gateway_surface
assert deps.standalone_approval_surface is standalone_surface
def test_turn_stream_approval_surface_uses_structural_output_handle_value() -> None:
from opensquilla.cli.repl import turn_stream
output = _RecordingOutputHandle()
default_surface = object()
assert (
turn_stream.approval_surface_for_tui_output(output, default_surface)
is output.approval_surface
)
def test_tui_turn_bridge_supplies_cli_approval_surface_defaults() -> None:
from opensquilla.cli.repl import turn_bridge
deps = turn_bridge.default_turn_stream_dependencies()
assert deps.gateway_approval_surface is Surface.CLI_GATEWAY
assert deps.standalone_approval_surface is Surface.CLI_STANDALONE
def test_tui_turn_bridge_rejects_non_surface_output_approval_value() -> None:
from opensquilla.cli.repl import turn_bridge
assert (
turn_bridge.approval_surface_for_tui_output(
_RecordingOutputHandle(),
Surface.CLI_STANDALONE,
)
is Surface.CLI_STANDALONE
)
@pytest.mark.asyncio
async def test_tui_gateway_stream_coerces_invalid_output_approval_surface(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import turn_bridge
from opensquilla.cli.tui import turn_stream_defaults
captured: dict[str, Any] = {}
async def fake_approval_handler(*_args: Any, **kwargs: Any) -> None:
captured.update(kwargs)
# The default approval handler is built lazily per dependency set; patch
# its factory so the coerced surface kwarg can be observed.
monkeypatch.setattr(
turn_stream_defaults,
"tui_approval_handler",
lambda: fake_approval_handler,
)
deps = turn_bridge.default_turn_stream_dependencies(
renderer_factory=_ApprovalRenderer,
)
await turn_bridge.stream_response_gateway(
_ApprovalClient(),
"agent:main:test",
"hello",
tui_output=_RecordingOutputHandle(),
deps=deps,
)
assert captured["surface"] is Surface.CLI_GATEWAY
@pytest.mark.asyncio
async def test_local_approval_resolver_threads_sandbox_choice(
tmp_path,
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import turn_stream
from opensquilla.gateway.approval_queue import get_approval_queue, reset_approval_queue
from opensquilla.sandbox.escalation import build_path_approval_params
from opensquilla.sandbox.path_validation import MountDecision
reset_approval_queue()
manager = _SessionManager()
workspace = tmp_path / "workspace"
outside = tmp_path / "outside"
workspace.mkdir()
outside.mkdir()
params = build_path_approval_params(
MountDecision(
status="request",
normalized_path=str(outside.resolve(strict=False)),
access="ro",
reason="outside_sandbox_mounts",
),
session_key=manager.node.session_key,
workspace=str(workspace),
)
assert params is not None
approval_id = get_approval_queue().request(namespace="exec", params=params)
captured: dict[str, Any] = {}
async def fake_apply_sandbox_approval_choice(
params: dict[str, Any] | None,
*,
choice: str | None,
approved: bool,
session_manager: object,
config: object,
) -> None:
captured.update(
{
"params": params,
"choice": choice,
"approved": approved,
"session_manager": session_manager,
"config": config,
}
)
monkeypatch.setattr(
"opensquilla.gateway.rpc_approvals.apply_sandbox_approval_choice",
fake_apply_sandbox_approval_choice,
)
resolver = turn_stream.local_approval_resolver(
session_manager=manager,
config=_sandbox_config(),
)
await resolver(approval_id, True, choice="allow_same_type")
pending = get_approval_queue().get(approval_id)
assert pending.resolved is True
assert pending.approved is True
assert pending.claim_token is None
assert captured["choice"] == "allow_same_type"
assert captured["approved"] is True
assert captured["params"] == params
assert captured["session_manager"] is manager
reset_approval_queue()
@pytest.mark.asyncio
async def test_turn_stream_adapter_threads_gateway_tui_output_without_chat_cmd(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import turn_stream
captured: dict[str, Any] = {}
async def fake_stream_response_gateway(
client: object,
session_key: str,
message: str,
elevated_state: dict[str, str | None] | None = None,
attachments: list[dict[str, Any]] | None = None,
*,
tui_output: TuiOutputHandle | None = None,
) -> TurnResult:
captured.update(
{
"client": client,
"session_key": session_key,
"message": message,
"elevated_state": elevated_state,
"attachments": attachments,
"tui_output": tui_output,
}
)
return TurnResult(text="ok")
monkeypatch.setattr(turn_stream, "stream_response_gateway", fake_stream_response_gateway)
output = _RecordingOutputHandle()
result = await turn_stream.dispatch_gateway_stream(
cast(turn_stream.GatewayStreamingClient, object()),
"agent:main:test",
"hello",
{"mode": "bypass"},
attachments=[{"id": "file-1"}],
tui_output=output,
)
assert result.text == "ok"
assert captured == {
"client": captured["client"],
"session_key": "agent:main:test",
"message": "hello",
"elevated_state": {"mode": "bypass"},
"attachments": [{"id": "file-1"}],
"tui_output": output,
}
def test_turn_stream_adapter_has_no_raw_prompt_application_dependency() -> None:
from opensquilla.cli.repl import turn_stream
source = inspect.getsource(turn_stream)
blocked_module = "prompt" + "_toolkit"
assert blocked_module not in source
assert "ChatApplication" not in source
@pytest.mark.asyncio
async def test_chat_cmd_stream_wrapper_uses_bridge_owned_renderer_dependency(
monkeypatch: pytest.MonkeyPatch,
) -> None:
from opensquilla.cli.repl import turn_stream
from opensquilla.cli.tui.adapters import runtime_bridge
from opensquilla.cli.tui.opentui import renderer as opentui_renderer
captured: dict[str, Any] = {}
class _TemporaryOpenTuiRenderer:
pass
async def fake_stream_response_gateway(
client: object,
session_key: str,
message: str,
elevated_state: dict[str, str | None] | None = None,
attachments: list[dict[str, Any]] | None = None,
*,
tui_output: TuiOutputHandle | None = None,
deps: turn_stream.TurnStreamDependencies | None = None,
) -> TurnResult:
captured["renderer_dependency"] = None if deps is None else deps.renderer_factory
return TurnResult(text="ok")
# The bridge resolves the renderer from the selected backend, sourcing the
# class from the opentui renderer module — pin both halves of that wiring.
monkeypatch.setattr(
runtime_bridge,
"validate_tui_backend_selection",
lambda env=None: "opentui",
)
monkeypatch.setattr(
opentui_renderer,
"OpenTuiStreamRenderer",
_TemporaryOpenTuiRenderer,
)
monkeypatch.setattr(turn_stream, "stream_response_gateway", fake_stream_response_gateway)
result = await chat_cmd._stream_response_gateway(
cast(Any, object()),
"agent:main:test",
"hello",
{"mode": "bypass"},
)
assert result.text == "ok"
assert captured == {"renderer_dependency": _TemporaryOpenTuiRenderer}
@@ -0,0 +1,347 @@
from __future__ import annotations
import ast
import importlib
from pathlib import Path
PROJECT_ROOT = Path(__file__).resolve().parents[4]
CHAT_CMD = PROJECT_ROOT / "src/opensquilla/cli/chat_cmd.py"
TURN_STREAM = PROJECT_ROOT / "src/opensquilla/cli/chat/turn_stream.py"
TURN_BRIDGE = PROJECT_ROOT / "src/opensquilla/cli/tui/turn_bridge.py"
TURN_STREAM_DEFAULTS = (
PROJECT_ROOT / "src/opensquilla/cli/tui/adapters/turn_stream_defaults.py"
)
GATEWAY_SLASH_ADAPTER = (
PROJECT_ROOT / "src/opensquilla/cli/tui/adapters/slash_gateway.py"
)
STANDALONE_SLASH_ADAPTER = (
PROJECT_ROOT / "src/opensquilla/cli/tui/adapters/slash_standalone.py"
)
OPENTUI_RENDERER = "opensquilla.cli.tui.opentui.renderer"
REMOVED_TERMINAL_BRIDGE = "terminal" + "_bridge"
def _imports_name_from_module(path: Path, module: str, name: str) -> bool:
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
if not isinstance(node, ast.ImportFrom):
continue
if node.module == module and any(alias.name == name for alias in node.names):
return True
return False
def _imports_from_module(path: Path, module: str) -> bool:
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom) and node.module == module:
return True
if isinstance(node, ast.Import) and any(alias.name == module for alias in node.names):
return True
return False
def _imports_module(path: Path, module: str) -> bool:
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
if isinstance(node, ast.Import) and any(alias.name == module for alias in node.names):
return True
if isinstance(node, ast.ImportFrom) and node.module == module:
return True
return False
def _imports_module_from_package(path: Path, package: str, module_name: str) -> bool:
tree = ast.parse(path.read_text())
for node in ast.walk(tree):
if isinstance(node, ast.ImportFrom):
if node.module == package and any(alias.name == module_name for alias in node.names):
return True
continue
if isinstance(node, ast.Import):
module = f"{package}.{module_name}"
if any(alias.name == module for alias in node.names):
return True
return False
def _defined_function_names(path: Path) -> set[str]:
tree = ast.parse(path.read_text())
return {
node.name
for node in tree.body
if isinstance(node, ast.FunctionDef | ast.AsyncFunctionDef)
}
def _module_aliases(path: Path, module_alias: str) -> dict[str, str]:
tree = ast.parse(path.read_text())
aliases: dict[str, str] = {}
for node in tree.body:
if not isinstance(node, ast.Assign):
continue
if len(node.targets) != 1 or not isinstance(node.targets[0], ast.Name):
continue
value = node.value
if (
isinstance(value, ast.Attribute)
and isinstance(value.value, ast.Name)
and value.value.id == module_alias
):
aliases[node.targets[0].id] = value.attr
return aliases
def test_chat_cmd_does_not_import_legacy_renderer_or_approval_handler() -> None:
assert not _imports_name_from_module(
CHAT_CMD,
"opensquilla.cli.repl.stream",
"StreamingRenderer",
)
assert not _imports_name_from_module(
CHAT_CMD,
"opensquilla.cli.repl.approval",
"maybe_handle_approval",
)
def test_chat_cmd_does_not_import_raw_slash_adapters_or_context() -> None:
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
"slash_adapter",
)
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
"standalone_slash_adapter",
)
assert not _imports_from_module(
CHAT_CMD,
"opensquilla.cli.repl.slash_adapter",
)
def test_chat_cmd_does_not_import_raw_runtime_or_removed_bridges() -> None:
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
"gateway_runtime",
)
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
"standalone_runtime",
)
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
REMOVED_TERMINAL_BRIDGE,
)
def test_chat_cmd_does_not_import_raw_input_assets() -> None:
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
"input_assets",
)
assert not _imports_from_module(
CHAT_CMD,
"opensquilla.cli.repl.input_assets",
)
def test_chat_cmd_does_not_import_backend_helper_bridges() -> None:
for module_name in ("input_bridge", "slash_bridge", "turn_bridge"):
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
module_name,
)
assert not _imports_from_module(
CHAT_CMD,
f"opensquilla.cli.repl.{module_name}",
)
def test_chat_cmd_private_compat_surface_is_dynamic_legacy_exports() -> None:
tree = ast.parse(CHAT_CMD.read_text())
assigned_names = {
target.id
for node in tree.body
if isinstance(node, ast.Assign)
for target in node.targets
if isinstance(target, ast.Name)
}
assert _defined_function_names(CHAT_CMD) == {"__dir__", "__getattr__", "run_chat"}
assert not any(
name.startswith("_") and not name.startswith("__")
for name in assigned_names
)
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
"chat_compat",
)
assert not _imports_from_module(
CHAT_CMD,
"opensquilla.cli.repl.chat_compat",
)
def test_chat_cmd_uses_typed_launch_request_instead_of_private_runner_names() -> None:
source = CHAT_CMD.read_text()
assert "_ChatCommandRequest" in source
assert "_ChatCommandLaunchOverrides" in source
assert "_run_chat_request" in source
assert "legacy_overrides" not in source
assert "_launch_bridge" not in source
assert "_standalone_repl" not in source
assert "_gateway_chat" not in source
def test_chat_cmd_star_import_keeps_legacy_public_names_visible() -> None:
chat_cmd = importlib.import_module("opensquilla.cli.chat_cmd")
assert "run_chat" in chat_cmd.__all__
assert "GATEWAY_SLASH_HANDLER_WORDS" in chat_cmd.__all__
assert "TurnResult" in chat_cmd.__all__
assert "_file_prompt_and_attachments" not in chat_cmd.__all__
assert "GATEWAY_SLASH_HANDLER_WORDS" in dir(chat_cmd)
assert "_file_prompt_and_attachments" in dir(chat_cmd)
namespace: dict[str, object] = {}
exec("from opensquilla.cli.chat_cmd import *", namespace)
assert "run_chat" in namespace
assert "GATEWAY_SLASH_HANDLER_WORDS" in namespace
assert "_file_prompt_and_attachments" not in namespace
def test_chat_cmd_only_defines_typer_entrypoint_and_export_hooks() -> None:
assert _defined_function_names(CHAT_CMD) == {"__dir__", "__getattr__", "run_chat"}
def test_chat_cmd_does_not_import_launch_presentation_details() -> None:
assert not _imports_module(CHAT_CMD, "asyncio")
assert not _imports_module(CHAT_CMD, "os")
assert not _imports_module(CHAT_CMD, "sys")
assert not _imports_name_from_module(CHAT_CMD, "rich.panel", "Panel")
assert not _imports_name_from_module(CHAT_CMD, "opensquilla.cli.ui", "ACCENT")
def test_chat_cmd_does_not_import_cli_presentation_defaults() -> None:
assert not _imports_from_module(CHAT_CMD, "opensquilla.cli.ui")
def test_chat_cmd_does_not_import_raw_turn_stream_facade() -> None:
assert not _imports_module_from_package(
CHAT_CMD,
"opensquilla.cli.repl",
"turn_stream",
)
assert not _imports_from_module(
CHAT_CMD,
"opensquilla.cli.repl.turn_stream",
)
assert not _imports_from_module(
CHAT_CMD,
"opensquilla.cli.repl.stream",
)
def test_turn_stream_does_not_import_raw_input_assets() -> None:
assert not _imports_module_from_package(
TURN_STREAM,
"opensquilla.cli.repl",
"input_assets",
)
assert not _imports_from_module(
TURN_STREAM,
"opensquilla.cli.repl.input_assets",
)
def test_turn_stream_does_not_import_frontend_default_dependencies() -> None:
assert not _imports_module_from_package(
TURN_STREAM,
"opensquilla.cli.repl",
"input_bridge",
)
assert not _imports_from_module(
TURN_STREAM,
"opensquilla.cli.repl.approval",
)
assert not _imports_name_from_module(
TURN_STREAM,
"opensquilla.cli.repl.stream",
"StreamingRenderer",
)
assert not _imports_from_module(
TURN_STREAM,
f"opensquilla.cli.repl.{REMOVED_TERMINAL_BRIDGE}",
)
assert not _imports_from_module(
TURN_STREAM,
"opensquilla.cli.ui",
)
assert not _imports_from_module(
TURN_STREAM,
"opensquilla.cli.repl.slash_adapter",
)
assert not _imports_from_module(
TURN_STREAM,
"opensquilla.cli.tui.contracts",
)
assert not _imports_from_module(
TURN_STREAM,
"opensquilla.engine.commands",
)
def test_turn_bridge_does_not_import_concrete_streaming_renderer() -> None:
assert not _imports_name_from_module(
TURN_BRIDGE,
"opensquilla.cli.repl.stream",
"StreamingRenderer",
)
def test_turn_stream_defaults_uses_opentui_renderer_default() -> None:
assert _imports_name_from_module(
TURN_STREAM_DEFAULTS,
OPENTUI_RENDERER,
"OpenTuiStreamRenderer",
)
assert not _imports_name_from_module(
TURN_STREAM_DEFAULTS,
"opensquilla.cli.repl.approval",
"maybe_handle_approval",
)
def test_turn_bridge_delegates_tui_approval_defaults() -> None:
assert _imports_from_module(
TURN_BRIDGE,
"opensquilla.cli.tui.adapters.turn_stream_defaults",
)
assert not _imports_name_from_module(
TURN_BRIDGE,
OPENTUI_RENDERER,
"OpenTuiStreamRenderer",
)
def test_slash_adapters_do_not_import_raw_input_assets() -> None:
for path in (GATEWAY_SLASH_ADAPTER, STANDALONE_SLASH_ADAPTER):
assert not _imports_module_from_package(
path,
"opensquilla.cli.repl",
"input_assets",
)
assert not _imports_from_module(
path,
"opensquilla.cli.repl.input_assets",
)