Treat bare desktop canvas as ready

Allow the Linux Desktop state collector to report a healthy canvas when XFCE has no active application window, as long as the display, visible windows, and screenshots are available. Document the readiness rule in the linux-desktop skill and add regression coverage for the bare-desktop active_window=null case.

Alessandro committed May 10, 2026 at 00:07 UTC 854a96e88046ceeb4565aa9b0448aec76f71401f
3 files changed +57 -1
plugins/_desktop/helpers/desktop_state.py
-1
@@ -372,7 +372,6 @@ def collect_active_window(env: dict[str, str], capabilities: dict[str, str], err
372 return None
373 result = run([capabilities["xdotool"], "getactivewindow"], env=env, timeout=3)
374 if result.returncode != 0:
375 - errors.append(command_output(result) or "xdotool could not read the active window.")
375 return None
376 window_id = result.stdout.strip().splitlines()[0] if result.stdout.strip() else ""
377 if not window_id:
plugins/_desktop/skills/linux-desktop/SKILL.md
+2
@@ -60,6 +60,8 @@ $DESKTOP key ctrl+s
60
61 The script targets the persistent `agent-zero-desktop` X display, sets `DISPLAY`, `XAUTHORITY`, and `HOME` to the XFCE profile, then uses `xdotool` for input. Startup normally prepares this session. If `check` fails during explicit Desktop work, report that the Desktop runtime is not ready instead of installing packages ad hoc.
62
63 +If `observe --json --screenshot` shows a reachable display, visible Desktop/window entries, and a fresh screenshot, the Desktop is usable even when `active_window` is `null`; a bare XFCE desktop can have no active application window. Treat missing screenshots, missing display, or unavailable `xdotool`/`xwd` as blockers and stop with the specific readiness message instead of repeating clicks or inventing a fallback.
64 +
65 For direct app launches without coordinates:
66
67 ```bash
tests/test_office_desktop_state.py
+55
@@ -92,6 +92,61 @@ def test_desktop_state_collects_x11_state_from_mocked_tools(tmp_path, monkeypatc
92 assert [window["title"] for window in state["windows"]] == ["LibreOffice Calc", "Terminal"]
93
94
95 +def test_desktop_state_allows_missing_active_window_when_display_is_reachable(tmp_path, monkeypatch):
96 + session_dir = tmp_path / "sessions"
97 + profile_dir = tmp_path / "profiles" / desktop_state.SESSION_ID
98 + session_dir.mkdir(parents=True)
99 + profile_dir.mkdir(parents=True)
100 + (session_dir / f"{desktop_state.SESSION_ID}.json").write_text(
101 + '{"display": 120, "profile_dir": "%s"}' % profile_dir,
102 + encoding="utf-8",
103 + )
104 +
105 + monkeypatch.setattr(desktop_state, "SESSION_DIR", session_dir)
106 + monkeypatch.setattr(desktop_state, "PROFILE_DIR", tmp_path / "profiles")
107 + monkeypatch.setattr(desktop_state.shutil, "which", lambda name: f"/usr/bin/{name}")
108 +
109 + def fake_run(command, **kwargs):
110 + del kwargs
111 + name = Path(command[0]).name
112 + if name == "xrandr":
113 + return _completed(command, stdout="Screen 0: current 543 x 792, maximum 1920 x 1080\n")
114 + if name == "xdotool" and command[1:3] == ["getmouselocation", "--shell"]:
115 + return _completed(command, stdout="X=43\nY=244\nSCREEN=0\nWINDOW=18874412\n")
116 + if name == "xdotool" and command[1] == "getactivewindow":
117 + return _completed(
118 + command,
119 + returncode=1,
120 + stderr="XGetWindowProperty[_NET_ACTIVE_WINDOW] failed (code=1)\n",
121 + )
122 + if name == "xdotool" and command[1] == "search":
123 + return _completed(command, stdout="18874412\n")
124 + if name == "xdotool" and command[1] == "getwindowname":
125 + return _completed(command, stdout="Desktop\n")
126 + if name == "xwininfo":
127 + return _completed(
128 + command,
129 + stdout=(
130 + " Absolute upper-left X: 0\n"
131 + " Absolute upper-left Y: 0\n"
132 + " Width: 543\n"
133 + " Height: 792\n"
134 + ),
135 + )
136 + if name == "xprop":
137 + return _completed(command, stdout='WM_CLASS(STRING) = "xfdesktop", "Xfdesktop"\n')
138 + raise AssertionError(f"unexpected command: {command}")
139 +
140 + monkeypatch.setattr(desktop_state.subprocess, "run", fake_run)
141 +
142 + state = desktop_state.collect_state()
143 +
144 + assert state["ok"] is True
145 + assert state["active_window"] is None
146 + assert state["errors"] == []
147 + assert [window["title"] for window in state["windows"]] == ["Desktop"]
148 +
149 +
150 def test_desktop_state_screenshot_capture_uses_xwd_and_pillow_when_available(tmp_path, monkeypatch):
151 monkeypatch.setattr(desktop_state, "SCREENSHOT_DIR", tmp_path)
152 capabilities = {"xwd": "/usr/bin/xwd"}