Files
wehub-resource-sync 4b6817381b
CI (OpenClaw E2E) / openclaw test (push) Has been cancelled
CI / coverage-report (push) Has been cancelled
CI / test-kubernetes (push) Has been cancelled
CI / should-run-thorough (push) Has been cancelled
CI / test-thorough (cloudwatch-demo) (push) Has been cancelled
CI / test-thorough (flink-ecs) (push) Has been cancelled
CI / test-thorough (upstream-lambda) (push) Has been cancelled
CI / test-thorough (prefect-ecs-fargate) (push) Has been cancelled
Release / build-binaries (zip, opensre.exe, onefile, windows-latest, windows-x64) (push) Has been cancelled
Benchmark image — build + push to ECR (any adapter) / build + push (push) Has been cancelled
CI / quality (ubuntu-latest) (push) Has been cancelled
CI / test (tools-runtime) (push) Has been cancelled
CI / test (e2e-general) (push) Has been cancelled
CI / test (cli-runtime) (push) Has been cancelled
CI / test (e2e-provider-and-openclaw) (push) Has been cancelled
CI / test (integrations-and-misc) (push) Has been cancelled
Release / verify (push) Has been cancelled
Release / build-python-dist (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-15-intel, darwin-x64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, macos-latest, darwin-arm64) (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04, linux-x64) (push) Has been cancelled
Release / publish-release (push) Has been cancelled
Release / publish-main-release (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-checks (no-LLM) (push) Has been cancelled
CodeQL / Analyze (python) (push) Has been cancelled
Interactive Shell Live (PR + post-merge) / turn-live shard ${{ matrix.shard_index }} (push) Has been cancelled
Release / prepare (push) Has been cancelled
Release / build-binaries (tar.gz, opensre, onedir, ubuntu-22.04-arm, linux-arm64) (push) Has been cancelled
Synthetic Deterministic Tests / Synthetic offline (deterministic) (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:10:45 +08:00

111 lines
4.2 KiB
Python

"""Tests for gateway turn handler wiring."""
from __future__ import annotations
import logging
from typing import Any
from unittest.mock import MagicMock
from rich.console import Console
from core.agent_harness.session import SessionCore
from core.agent_harness.session.persistence.memory import InMemorySessionStorage
from core.agent_harness.turns.turn_results import ShellTurnResult, ToolCallingTurnResult
from gateway.turn_handler import GatewayTurnHandler
def _patch_headless_agent(monkeypatch: Any, result: ShellTurnResult) -> MagicMock:
"""Patch the gateway's ``HeadlessAgent`` so construction is inert and dispatch returns ``result``.
Returns the patched class mock; ``mock.call_args.kwargs`` exposes the constructor
ports (e.g. ``tools``) the gateway wired for the turn.
"""
agent_cls = MagicMock()
agent_cls.return_value.dispatch.return_value = result
monkeypatch.setattr("gateway.turn_handler.HeadlessAgent", agent_cls)
return agent_cls
def test_turn_handler_resolves_action_tools_from_live_session(monkeypatch: Any) -> None:
"""Per-chat session integrations must drive the action tool list each turn.
Precomputing tools at gateway boot (from an empty boot session) left the
action agent with no integration-scoped tools, so ``run_turn`` fell through
to the answer CLI agent on Telegram while the shell worked.
"""
recorded: list[dict[str, Any] | None] = []
def _fake_get_tools(
_ctx: Any,
*,
resolved_integrations: dict[str, Any] | None = None,
) -> list[Any]:
recorded.append(resolved_integrations)
return [MagicMock(name="slack_send_message")]
monkeypatch.setattr(
"core.agent_harness.tools.tool_provider.get_action_tools_from_integrations_context",
_fake_get_tools,
)
agent_cls = _patch_headless_agent(
monkeypatch,
ShellTurnResult(
final_intent="cli_agent_handled",
action_result=ToolCallingTurnResult(
planned_count=1,
executed_count=1,
executed_success_count=1,
has_unhandled_clause=False,
handled=True,
),
),
)
session = SessionCore(storage=InMemorySessionStorage())
chat_integrations = {"slack": {"webhook_url": "https://hooks.example/test"}}
session.resolved_integrations_cache = chat_integrations
handler = GatewayTurnHandler(console=Console(force_terminal=False))
handler("send slack update", session, MagicMock(), logging.getLogger("test.turn_handler"))
tool_provider = agent_cls.call_args.kwargs["tools"]
tools = tool_provider.action_tools(confirm_fn=None, is_tty=False)
assert len(tools) == 1
assert recorded == [chat_integrations]
def _empty_turn_result(*, llm_run: Any = None) -> ShellTurnResult:
return ShellTurnResult(
final_intent="cli_agent_handled",
action_result=ToolCallingTurnResult(
planned_count=0,
executed_count=0,
executed_success_count=0,
has_unhandled_clause=False,
handled=True,
response_text="",
),
assistant_response_text="",
llm_run=llm_run,
)
def test_turn_handler_finalizes_fallback_on_empty_response(monkeypatch: Any) -> None:
"""An empty, non-answered turn still finalizes so the placeholder status can't hang."""
_patch_headless_agent(monkeypatch, _empty_turn_result())
sink = MagicMock()
handler = GatewayTurnHandler(console=Console(force_terminal=False))
handler("/", SessionCore(storage=InMemorySessionStorage()), sink, logging.getLogger("test"))
sink.finalize.assert_called_once_with("I didn't have anything to add for that.")
def test_turn_handler_skips_finalize_when_answer_was_streamed(monkeypatch: Any) -> None:
"""A streamed answer (llm_run set) already resolved the status; do not re-finalize."""
result = _empty_turn_result(llm_run=MagicMock()) # answered=True
_patch_headless_agent(monkeypatch, result)
sink = MagicMock()
handler = GatewayTurnHandler(console=Console(force_terminal=False))
handler("hi", SessionCore(storage=InMemorySessionStorage()), sink, logging.getLogger("test"))
sink.finalize.assert_not_called()