"""ActionRecord + ActionFlow models.""" from __future__ import annotations import uuid from dataclasses import dataclass, field from datetime import datetime from typing import Any, Optional SCHEMA_ACTION = "openclaw.ui_inspector.action.v1" SCHEMA_ACTION_FLOW = "openclaw.ui_inspector.action_flow.v1" # Full vocabulary — capability flags live elsewhere for unimplemented types. ACTION_TYPES: tuple[str, ...] = ( "click", "double_click", "right_click", "fill", "type", "clear", "press_key", "hotkey", "select", "toggle", "hover", "scroll", "drag_drop", "focus", "window_switch", "navigation", "checkpoint", "wait", "verify", "unknown", ) # Capability status for this Inspector release. ACTION_CAPABILITY: dict[str, str] = { "click": "implemented", "double_click": "schema_only", "right_click": "schema_only", "fill": "implemented", "type": "implemented", # aggregated into fill/type "clear": "schema_only", "press_key": "implemented", "hotkey": "schema_only", "select": "schema_only", "toggle": "schema_only", "hover": "schema_only", "scroll": "schema_only", "drag_drop": "schema_only", "focus": "schema_only", "window_switch": "schema_only", "navigation": "partial", "checkpoint": "implemented", "wait": "schema_only", "verify": "schema_only", "unknown": "fallback", } def _now_iso() -> str: return datetime.now().astimezone().isoformat() def _new_id(prefix: str = "act") -> str: return f"{prefix}_{uuid.uuid4().hex[:12]}" @dataclass class ActionRecord: schema: str = SCHEMA_ACTION id: str = field(default_factory=lambda: _new_id("act")) sequence: int = 0 action_type: str = "unknown" started_at: str = field(default_factory=_now_iso) ended_at: Optional[str] = None surface: str = "windows" target_element_id: Optional[str] = None target_snapshot: dict[str, Any] = field(default_factory=dict) input: dict[str, Any] = field(default_factory=dict) sensitive: bool = False before_state_id: Optional[str] = None after_state_id: Optional[str] = None before_observation_id: Optional[str] = None after_observation_id: Optional[str] = None observed_changes: list[dict[str, Any]] = field(default_factory=list) proposed_postconditions: list[dict[str, Any]] = field(default_factory=list) status: str = "ok" # ok | partial | failed | skipped confidence: float = 0.0 evidence: dict[str, Any] = field(default_factory=dict) raw_event: dict[str, Any] = field(default_factory=dict) raw_event_ids: list[str] = field(default_factory=list) capability: str = "" def __post_init__(self) -> None: if self.action_type not in ACTION_TYPES: self.action_type = "unknown" if not self.capability: self.capability = ACTION_CAPABILITY.get(self.action_type, "schema_only") @dataclass class ActionFlow: schema: str = SCHEMA_ACTION_FLOW flow_id: str = "" flow_name: str = "" created_at: str = field(default_factory=_now_iso) ended_at: Optional[str] = None states: list[str] = field(default_factory=list) # state ids in order seen observations: list[str] = field(default_factory=list) # every concrete capture actions: list[str] = field(default_factory=list) # action ids in sequence elements: list[str] = field(default_factory=list) # element ids entry_state_id: Optional[str] = None final_state_id: Optional[str] = None metadata: dict[str, Any] = field(default_factory=dict) recording_mode: str = "auto" # auto | guided | checkpoint | legacy_interval surface: str = "" status: str = "open"