feat(planner): persist reusable action semantics
Tests / Test passed: 879

This commit is contained in:
2026-07-15 18:14:28 +08:00
parent 361dada276
commit d69be48f96
41 changed files with 733 additions and 116 deletions
+36 -4
View File
@@ -57,7 +57,12 @@ def _context() -> TaskContext:
def test_ai_planner_returns_single_planned_step_for_action_decision() -> None:
client = FakeToolCallingClient(
ToolCallDecision(tool_name="tap", arguments={"x": 1, "y": 2})
ToolCallDecision(
tool_name="tap",
arguments={"x": 1, "y": 2},
purpose="Open the send control.",
expected_outcome="The message composer is focused.",
)
)
planner = AIPlanner(client=client)
@@ -66,8 +71,11 @@ def test_ai_planner_returns_single_planned_step_for_action_decision() -> None:
assert len(steps) == 1
step = steps[0]
assert step.action == "tap"
assert step.description == "AI planner: tap({'x': 1, 'y': 2})"
assert step.description == "AI planner: Open the send control."
assert step.args == {"x": 1, "y": 2}
assert step.purpose == "Open the send control."
assert step.expected_outcome == "The message composer is focused."
assert step.expected_text == "The message composer is focused."
def test_ai_planner_finish_task_success_returns_empty_plan() -> None:
@@ -204,6 +212,8 @@ def test_ai_planner_propagates_rationale_and_thinking_to_planned_step() -> None:
arguments={"x": 1, "y": 2},
text_output="Previous step opened settings. Now tapping account.",
thinking="I need to navigate deeper.",
purpose="Open account settings.",
expected_outcome="The account settings page is visible.",
)
)
planner = AIPlanner(client=client)
@@ -212,6 +222,8 @@ def test_ai_planner_propagates_rationale_and_thinking_to_planned_step() -> None:
assert steps[0].rationale == "Previous step opened settings. Now tapping account."
assert steps[0].thinking == "I need to navigate deeper."
assert steps[0].purpose == "Open account settings."
assert steps[0].expected_outcome == "The account settings page is visible."
def test_history_summary_returns_compact_format() -> None:
@@ -226,12 +238,16 @@ def test_history_summary_returns_compact_format() -> None:
action="tap",
success=True,
rationale="Opened settings.",
arguments={"x": 1, "y": 2},
purpose="Open settings.",
expected_outcome="Settings is visible.",
page="Home",
),
WorldEvent(
action="swipe",
success=False,
rationale=None,
arguments={"start_y": 700, "end_y": 200},
page="Settings",
),
]
@@ -241,8 +257,24 @@ def test_history_summary_returns_compact_format() -> None:
summary = _history_summary(state)
assert summary == [
{"page": "Home", "action": "tap", "rationale": "Opened settings.", "success": True},
{"page": "Settings", "action": "swipe", "rationale": None, "success": False},
{
"page": "Home",
"action": "tap",
"arguments": {"x": 1, "y": 2},
"rationale": "Opened settings.",
"purpose": "Open settings.",
"expected_outcome": "Settings is visible.",
"success": True,
},
{
"page": "Settings",
"action": "swipe",
"arguments": {"start_y": 700, "end_y": 200},
"rationale": None,
"purpose": None,
"expected_outcome": None,
"success": False,
},
]
# Must not contain scene element data
for entry in summary: