From f0058d1057924e46307f21838f9f34d104a6f6bf Mon Sep 17 00:00:00 2001 From: Space-Banane Date: Sun, 31 May 2026 21:02:56 +0200 Subject: [PATCH] feat: add support for Windows-only tools and enhance platform checks --- src/agent.py | 14 +++++++++++--- tests/test_agent_tools.py | 17 +++++++++++++++++ 2 files changed, 28 insertions(+), 3 deletions(-) diff --git a/src/agent.py b/src/agent.py index e596a8a..176281d 100644 --- a/src/agent.py +++ b/src/agent.py @@ -6,6 +6,7 @@ import json import logging import os import re +import sys import subprocess import threading import time @@ -331,6 +332,7 @@ OBSERVATION_TOOL_NAMES = VISUAL_TOOL_NAMES | WINDOW_TOOL_NAMES | DIALOG_TOOL_NAM "clipboard_get", "get_cursor_position", } +WINDOWS_ONLY_TOOL_NAMES = WINDOW_TOOL_NAMES | DIALOG_TOOL_NAMES | UI_ELEMENT_TOOL_NAMES MAX_ACTION_SIGNATURE_ATTEMPTS = 3 MAX_STABLE_OBSERVATION_STEPS = 3 FINISH_LIKELY_OBSERVATION_TOOLS = {"see_screen", "enhance", "get_active_window", "detect_dialog"} @@ -1022,9 +1024,10 @@ class ScreenJobAgent: }, }, ] - optional_native_tools = WINDOW_TOOL_NAMES | DIALOG_TOOL_NAMES | UI_ELEMENT_TOOL_NAMES - if not self._native_control_tools_enabled(): + optional_native_tools = set(WINDOWS_ONLY_TOOL_NAMES) + if self._is_windows_host() and not self._native_control_tools_enabled(): optional_native_tools = optional_native_tools - {"get_active_window"} + if not self._is_windows_host() or not self._native_control_tools_enabled(): return [ tool for tool in all_tools @@ -1080,6 +1083,9 @@ class ScreenJobAgent: def _sorted_prohibited_key_combos(self) -> list[str]: return sorted(self.prohibited_key_combos) + def _is_windows_host(self) -> bool: + return sys.platform.startswith("win") + def _native_automation_mode(self) -> str: mode = str(self.options.native_automation_mode or "prefer").strip().lower() if mode not in {"off", "prefer", "require_fallback"}: @@ -1087,7 +1093,7 @@ class ScreenJobAgent: return mode def _native_control_tools_enabled(self) -> bool: - return self._native_automation_mode() != "off" + return self._is_windows_host() and self._native_automation_mode() != "off" def _active_app_identity(self, window: dict[str, Any] | None) -> str: if not isinstance(window, dict): @@ -3753,6 +3759,8 @@ class ScreenJobAgent: def _dispatch_tool(self, name: str, args: dict[str, Any]) -> dict[str, Any]: if name in self.disabled_tools: return {"ok": False, "error": f"Tool '{name}' is disabled for this job."} + if not self._is_windows_host() and name in WINDOWS_ONLY_TOOL_NAMES: + return {"ok": False, "error": f"Tool '{name}' is only available on Windows."} finish_likely_result = self._check_finish_likely_gate(name, args) if finish_likely_result is not None: return finish_likely_result diff --git a/tests/test_agent_tools.py b/tests/test_agent_tools.py index fd8db08..f40705b 100644 --- a/tests/test_agent_tools.py +++ b/tests/test_agent_tools.py @@ -752,6 +752,23 @@ def test_tool_schemas_hide_optional_native_tools_when_mode_off(tmp_path: Path, m assert "list_ui_elements" not in schemas +def test_tool_schemas_hide_windows_only_tools_on_non_windows_host(tmp_path: Path, monkeypatch) -> None: + agent = _build_agent(tmp_path, monkeypatch) + monkeypatch.setattr(agent_module.sys, "platform", "linux") + + schemas = {tool["name"]: tool for tool in agent._tool_schemas()} + + assert "get_active_window" not in schemas + assert "list_windows" not in schemas + assert "detect_dialog" not in schemas + assert "list_ui_elements" not in schemas + + result = agent._dispatch_tool("get_active_window", {}) + + assert result["ok"] is False + assert result["error"] == "Tool 'get_active_window' is only available on Windows." + + def test_list_windows_returns_structured_surface_metadata(tmp_path: Path, monkeypatch) -> None: agent = _build_agent(tmp_path, monkeypatch) monkeypatch.setattr(