feat(runtime): add planner reflection history with rationale and thinking
Tests / Test failed: 2, passed: 849

- ToolCallDecision captures thinking blocks and pre-tool text output
- AnthropicToolCallingClient supports optional extended thinking (budget_tokens + beta header)
- PlannedStep carries rationale and thinking from each LLM decision
- WorldEvent replaces scene_summary with rationale/thinking/page fields (backward-compatible)
- AI planner system prompt instructs reflection before each tool call
- _history_summary() emits compact {page, rationale, action, success} dicts
- Cloud DB migration 0011 adds nullable rationale/thinking columns to planner_decision_log
- OpenAI client extracts reasoning_content into thinking field
This commit is contained in:
2026-07-15 12:43:22 +08:00
parent 96e403ee47
commit a5aeb8889c
26 changed files with 903 additions and 25 deletions
+32 -4
View File
@@ -27,9 +27,9 @@ def test_world_event_and_state_to_dict() -> None:
widgets=[SemanticWidget(element_id="send", purpose="send message")],
)
event = WorldEvent(
scene_summary=semantic_scene,
action="tap",
success=True,
scene_summary=semantic_scene,
)
state = WorldState.with_history_bound(2)
state.current_app = "com.example.chat"
@@ -51,10 +51,38 @@ def test_world_event_and_state_to_dict() -> None:
def test_world_state_history_evicts_oldest_entry_at_bound() -> None:
state = WorldState.with_history_bound(2)
state.history.append(WorldEvent(_scene(), "first", True))
state.history.append(WorldEvent(_scene(), "second", True))
state.history.append(WorldEvent(_scene(), "third", True))
state.history.append(WorldEvent(action="first", success=True))
state.history.append(WorldEvent(action="second", success=True))
state.history.append(WorldEvent(action="third", success=True))
assert len(state.history) == 2
assert [event.action for event in state.history] == ["second", "third"]
assert state.history.maxlen == 2
def test_world_event_with_rationale_thinking_page() -> None:
event = WorldEvent(
action="tap",
success=True,
rationale="Previous step opened settings. Now tapping account.",
thinking="I should navigate to account settings.",
page="Settings",
)
data = event.to_dict()
assert data["rationale"] == "Previous step opened settings. Now tapping account."
assert data["thinking"] == "I should navigate to account settings."
assert data["page"] == "Settings"
assert data["scene_summary"] is None
def test_world_event_all_optional_fields_none() -> None:
event = WorldEvent(action="swipe", success=False)
data = event.to_dict()
assert data["rationale"] is None
assert data["thinking"] is None
assert data["page"] is None
assert data["scene_summary"] is None
assert data["action"] == "swipe"
assert data["success"] is False