File size: 4,776 Bytes
627c6cf
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
e2f048d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
import pytest

import platform

from tiny_computer_agent import (
    AgentLoop, DryRunExecutor, Element, FakeScreen, MockBrain, PlannerBrain,
    PyAutoGuiExecutor,
)
from tiny_computer_agent.desktop import (
    DEFAULT_KILL_SWITCH, Check, build_agent, display_available, doctor, run_checks,
)

FRAME = FakeScreen([(Element("Save"),)])


class StubDetector:
    name = "stub"

    def detect(self, image):
        return ()


def test_display_detection_follows_the_environment(monkeypatch):
    monkeypatch.delenv("DISPLAY", raising=False)
    monkeypatch.delenv("WAYLAND_DISPLAY", raising=False)
    ok, detail = display_available()
    if platform.system() in ("Linux", "FreeBSD"):
        assert ok is False and "DISPLAY" in detail
    monkeypatch.setenv("DISPLAY", ":0")
    assert display_available()[0] is True


def test_checks_cover_every_piece_the_harness_needs():
    names = [c.name for c in run_checks(brain="planner", detector="ocr")]
    for expected in ("display server", "pillow", "element detector (ocr)",
                     "execution mode", "kill switch"):
        assert expected in names


def test_pyautogui_is_only_required_for_real_input():
    dry = {c.name: c for c in run_checks(real_input=False)}
    live = {c.name: c for c in run_checks(real_input=True)}
    assert dry["input backend (pyautogui)"].required is False
    assert live["input backend (pyautogui)"].required is True
    assert live["execution mode"].detail.startswith("REAL")


def test_the_planner_needs_no_model_runtime():
    checks = {c.name: c for c in run_checks(brain="planner")}
    assert checks["model runtime"].ok
    assert "no weights" in checks["model runtime"].detail


def test_a_model_brain_reports_its_runtime():
    checks = {c.name: c for c in run_checks(brain="text-planner")}
    assert "model runtime" in checks
    assert "transformers" in checks["model runtime"].detail


def test_doctor_gives_a_verdict_and_a_fix(monkeypatch):
    monkeypatch.delenv("DISPLAY", raising=False)
    monkeypatch.delenv("WAYLAND_DISPLAY", raising=False)
    report = doctor(brain="planner")
    assert "preflight" in report
    assert "fix:" in report or "READY" in report


def test_a_check_renders_its_fix_hint():
    rendered = Check("display server", False, "no display", fix="run on the desktop").render()
    assert "[FAIL]" in rendered and "fix: run on the desktop" in rendered


def test_build_agent_defaults_to_dry_run_and_the_planner():
    loop = build_agent(screen=FRAME, detector=StubDetector(), brain="planner")
    assert isinstance(loop, AgentLoop)
    assert isinstance(loop.brain, PlannerBrain)
    assert isinstance(loop.executor, DryRunExecutor)
    assert loop.policy.dry_run is True
    assert loop.policy.step_budget == 10


def test_build_agent_real_input_swaps_the_executor():
    loop = build_agent(screen=FRAME, detector=StubDetector(), brain=MockBrain([]),
                       dry_run=False, max_steps=3)
    assert isinstance(loop.executor, PyAutoGuiExecutor)
    assert loop.policy.dry_run is False


def test_build_agent_wires_the_kill_switch(tmp_path):
    marker = tmp_path / "STOP"
    loop = build_agent(screen=FRAME, detector=StubDetector(), brain=MockBrain([]),
                       kill_switch_path=str(marker))
    assert loop.policy.kill_switch_path == str(marker)
    assert DEFAULT_KILL_SWITCH.endswith("tiny-computer-agent.STOP")


def test_the_one_call_builder_can_wire_learned_skills_and_a_gate():
    """2.0: one call wires the desktop, what it has learned, and the operator's gate."""
    from tiny_computer_agent import (AllowOnlyLabels, Element, FakeScreen, Policy, Skill,
                                     SkillBrain, SkillLibrary, SkillStep, Action, build_agent)

    library = SkillLibrary()
    library.add(Skill(name="save", goal="click 'Save' to save the document", verified=True,
                      steps=(SkillStep(Action("click", label="Save"), aimed_at="Save"),)))
    gate = AllowOnlyLabels(["Save"])

    loop = build_agent(brain="planner", screen=FakeScreen([(Element("Save", x=10, y=10, width=60, height=20),)]),
                       detector="ocr", dry_run=True, skills=library, gate=gate)

    assert isinstance(loop.brain, SkillBrain)
    assert loop.gate is gate
    episode = loop.run("click 'Save' to save the document")
    assert [s.action.label for s in episode.steps if s.action and s.action.kind == "click"] == ["Save"]


def test_without_skills_the_builder_leaves_the_brain_alone():
    from tiny_computer_agent import Element, FakeScreen, PlannerBrain, build_agent

    loop = build_agent(brain="planner", screen=FakeScreen([(Element("Save", x=1, y=1, width=9, height=9),)]),
                       detector="ocr")
    assert isinstance(loop.brain, PlannerBrain) and loop.gate is None