Skip to content

Commit 81da598

Browse files
committed
fixes
1 parent 913603b commit 81da598

3 files changed

Lines changed: 46 additions & 7 deletions

File tree

hud/tools/computer/gemini.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -232,8 +232,10 @@ async def _finalize(
232232
requested_url: str | None = None,
233233
output: str | None = None,
234234
) -> list[ContentBlock]:
235-
if output is not None and not result.error:
235+
if output is not None and result.error is None:
236236
result.output = output
237+
elif result.error == "":
238+
result.error = "Tool execution failed with no error output"
237239
if result.base64_image and self.rescale_images:
238240
try:
239241
result.base64_image = await self._rescale_screenshot(result.base64_image)

hud/tools/computer/tests/test_computer.py

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -36,6 +36,11 @@ async def drag(self, path, pattern=None, hold_keys=None, take_screenshot=True):
3636
return await super().drag(path, pattern, hold_keys, take_screenshot=False)
3737

3838

39+
class EmptyErrorExecutor(BaseExecutor):
40+
async def click(self, *args, **kwargs):
41+
return ContentResult(error="")
42+
43+
3944
@pytest.mark.asyncio
4045
async def test_hud_computer_screenshot():
4146
comp = HudComputerTool()
@@ -100,6 +105,17 @@ async def test_gemini_computer_click_reports_model_coordinates():
100105
)
101106

102107

108+
@pytest.mark.asyncio
109+
async def test_gemini_computer_does_not_mask_empty_error():
110+
comp = GeminiComputerTool(executor=EmptyErrorExecutor())
111+
112+
blocks = await comp(action="click_at", x=214, y=420)
113+
text = "\n".join(content.text for content in blocks if isinstance(content, TextContent))
114+
115+
assert "Clicked at (214, 420)" not in text
116+
assert "Tool execution failed with no error output" in text
117+
118+
103119
@pytest.mark.asyncio
104120
async def test_anthropic_computer_zoom():
105121
"""Test zoom action on AnthropicComputerTool.
@@ -246,6 +262,19 @@ async def test_xdo_commands_use_execution_pixels_for_agent_coordinates():
246262
assert executor.commands[-1] == "mousemove 309 396 click 1"
247263

248264

265+
@pytest.mark.asyncio
266+
async def test_xdo_nonzero_empty_stderr_surfaces_error(monkeypatch):
267+
async def fake_run(command: str):
268+
return 1, "", ""
269+
270+
monkeypatch.setattr("hud.tools.executors.xdo.run", fake_run)
271+
executor = XDOExecutor()
272+
273+
result = await executor.execute("mousemove 1 2", take_screenshot=False)
274+
275+
assert result.error == "Command failed with exit code 1"
276+
277+
249278
class TestHudComputerToolExtended:
250279
"""Extended tests for HudComputerTool covering edge cases and platform logic."""
251280

hud/tools/executors/xdo.py

Lines changed: 14 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -5,11 +5,13 @@
55
import logging
66
import os
77
import shlex
8-
from pathlib import Path
8+
from contextlib import suppress
99
from tempfile import gettempdir
1010
from typing import Literal
1111
from uuid import uuid4
1212

13+
from anyio import Path
14+
1315
from hud.tools.types import ContentResult
1416
from hud.tools.utils import run
1517

@@ -141,9 +143,14 @@ async def execute(self, command: str, take_screenshot: bool = True) -> ContentRe
141143
# Execute command
142144
returncode, stdout, stderr = await run(full_command)
143145

146+
error = None
147+
if returncode != 0:
148+
error = stderr or f"Command failed with exit code {returncode}"
149+
144150
# Prepare result
145151
result = ContentResult(
146-
output=stdout if stdout else None, error=stderr if stderr or returncode != 0 else None
152+
output=stdout if stdout else None,
153+
error=error,
147154
)
148155

149156
# Take screenshot if requested
@@ -167,7 +174,7 @@ async def screenshot(self) -> str | None:
167174
# Real screenshot using scrot
168175
if OUTPUT_DIR:
169176
output_dir = Path(OUTPUT_DIR)
170-
output_dir.mkdir(parents=True, exist_ok=True)
177+
await output_dir.mkdir(parents=True, exist_ok=True)
171178
screenshot_path = output_dir / f"screenshot_{uuid4().hex}.png"
172179
else:
173180
# Generate a unique path in system temp dir without opening a file
@@ -177,12 +184,13 @@ async def screenshot(self) -> str | None:
177184

178185
returncode, _, _stderr = await run(screenshot_cmd)
179186

180-
if returncode == 0 and screenshot_path.exists():
187+
if returncode == 0 and await screenshot_path.exists():
181188
try:
182-
image_data = screenshot_path.read_bytes()
189+
image_data = await screenshot_path.read_bytes()
183190
# Remove the file unless user requested persistence via env var
184191
if not OUTPUT_DIR:
185-
screenshot_path.unlink(missing_ok=True)
192+
with suppress(FileNotFoundError):
193+
await screenshot_path.unlink()
186194
return base64.b64encode(image_data).decode()
187195
except Exception:
188196
return None

0 commit comments

Comments
 (0)