harmonyrun 0.0.29__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- droidrun/__init__.py +92 -0
- droidrun/__main__.py +8 -0
- droidrun/agent/__init__.py +3 -0
- droidrun/agent/action_context.py +41 -0
- droidrun/agent/action_result.py +16 -0
- droidrun/agent/common/__init__.py +0 -0
- droidrun/agent/common/constants.py +11 -0
- droidrun/agent/common/events.py +139 -0
- droidrun/agent/common/ui_state_snapshot.py +188 -0
- droidrun/agent/droid/__init__.py +10 -0
- droidrun/agent/droid/droid_agent.py +1404 -0
- droidrun/agent/droid/events.py +110 -0
- droidrun/agent/droid/state.py +243 -0
- droidrun/agent/executor/__init__.py +22 -0
- droidrun/agent/executor/events.py +47 -0
- droidrun/agent/executor/executor_agent.py +303 -0
- droidrun/agent/executor/prompts.py +51 -0
- droidrun/agent/fast_agent/__init__.py +3 -0
- droidrun/agent/fast_agent/events.py +52 -0
- droidrun/agent/fast_agent/fast_agent.py +679 -0
- droidrun/agent/fast_agent/xml_parser.py +191 -0
- droidrun/agent/manager/__init__.py +28 -0
- droidrun/agent/manager/events.py +40 -0
- droidrun/agent/manager/manager_agent.py +599 -0
- droidrun/agent/manager/prompts.py +106 -0
- droidrun/agent/manager/stateless_manager_agent.py +399 -0
- droidrun/agent/oneflows/__init__.py +0 -0
- droidrun/agent/oneflows/app_starter_workflow.py +141 -0
- droidrun/agent/oneflows/structured_output_agent.py +87 -0
- droidrun/agent/tool_registry.py +336 -0
- droidrun/agent/trajectory/__init__.py +7 -0
- droidrun/agent/trajectory/llm_interaction_log.py +206 -0
- droidrun/agent/trajectory/writer.py +498 -0
- droidrun/agent/usage.py +252 -0
- droidrun/agent/utils/__init__.py +33 -0
- droidrun/agent/utils/actions.py +468 -0
- droidrun/agent/utils/chat_utils.py +90 -0
- droidrun/agent/utils/inference.py +499 -0
- droidrun/agent/utils/llm_loader.py +256 -0
- droidrun/agent/utils/llm_picker.py +263 -0
- droidrun/agent/utils/prompt_resolver.py +66 -0
- droidrun/agent/utils/signatures.py +205 -0
- droidrun/agent/utils/trajectory.py +287 -0
- droidrun/app_cards/__init__.py +0 -0
- droidrun/app_cards/app_card_provider.py +26 -0
- droidrun/app_cards/loading.py +57 -0
- droidrun/app_cards/provider_factory.py +54 -0
- droidrun/app_cards/providers/__init__.py +7 -0
- droidrun/app_cards/providers/composite_provider.py +101 -0
- droidrun/app_cards/providers/local_provider.py +114 -0
- droidrun/app_cards/providers/server_provider.py +120 -0
- droidrun/app_cards/suite_resolve.py +112 -0
- droidrun/batch/__init__.py +41 -0
- droidrun/batch/levels.py +23 -0
- droidrun/batch/loader.py +232 -0
- droidrun/batch/models.py +94 -0
- droidrun/batch/prompt_templates.py +21 -0
- droidrun/batch/report.py +617 -0
- droidrun/batch/resource_manager.py +189 -0
- droidrun/batch/runner.py +558 -0
- droidrun/batch/scheduler.py +105 -0
- droidrun/batch/schema_validation.py +58 -0
- droidrun/batch/schemas/suite_report.schema.json +121 -0
- droidrun/batch/schemas/test_suite.schema.json +103 -0
- droidrun/batch/suite_runner.py +451 -0
- droidrun/cli/__init__.py +9 -0
- droidrun/cli/doctor.py +663 -0
- droidrun/cli/event_handler.py +178 -0
- droidrun/cli/logs.py +9 -0
- droidrun/cli/main.py +585 -0
- droidrun/config/app_cards/README.md +139 -0
- droidrun/config/app_cards/app_cards.json +3 -0
- droidrun/config/app_cards/gmail.md +35 -0
- droidrun/config/config_example.yaml +202 -0
- droidrun/config/credentials_example.yaml +106 -0
- droidrun/config/prompts/batch/agent_goal.jinja2 +27 -0
- droidrun/config/prompts/batch/postcondition_goal.jinja2 +8 -0
- droidrun/config/prompts/batch/precondition_goal.jinja2 +8 -0
- droidrun/config/prompts/batch/suite_report.jinja2 +62 -0
- droidrun/config/prompts/batch/test_execution_goal.jinja2 +13 -0
- droidrun/config/prompts/executor/rev1.jinja2 +103 -0
- droidrun/config/prompts/executor/system.jinja2 +173 -0
- droidrun/config/prompts/fast_agent/system.jinja2 +214 -0
- droidrun/config/prompts/fast_agent/user.jinja2 +7 -0
- droidrun/config/prompts/manager/rev1.jinja2 +131 -0
- droidrun/config/prompts/manager/stateless.jinja2 +181 -0
- droidrun/config/prompts/manager/system.jinja2 +179 -0
- droidrun/config/prompts/manager/trained.jinja2 +67 -0
- droidrun/config/test_cases_example.json +51 -0
- droidrun/config_manager/__init__.py +37 -0
- droidrun/config_manager/config_manager.py +345 -0
- droidrun/config_manager/env_keys.py +51 -0
- droidrun/config_manager/loader.py +78 -0
- droidrun/config_manager/path_resolver.py +112 -0
- droidrun/config_manager/prompt_loader.py +94 -0
- droidrun/credential_manager/__init__.py +13 -0
- droidrun/credential_manager/credential_manager.py +38 -0
- droidrun/credential_manager/file_credential_manager.py +137 -0
- droidrun/hdcutils/__init__.py +34 -0
- droidrun/hdcutils/_agent_so.py +420 -0
- droidrun/hdcutils/_device.py +1417 -0
- droidrun/hdcutils/_hdc.py +97 -0
- droidrun/hdcutils/_hilog.py +217 -0
- droidrun/hdcutils/_proto.py +43 -0
- droidrun/hdcutils/_recording.py +93 -0
- droidrun/hdcutils/_uitest_rpc.py +1100 -0
- droidrun/hdcutils/_utils.py +223 -0
- droidrun/hdcutils/_version.py +6 -0
- droidrun/hdcutils/agents/uitest_agent_v1.1.10.so +0 -0
- droidrun/hdcutils/agents/uitest_agent_v1.1.3.so +0 -0
- droidrun/hdcutils/agents/uitest_agent_v1.1.5.so +0 -0
- droidrun/hdcutils/agents/uitest_agent_v1.1.9.x86_64_so +0 -0
- droidrun/hdcutils/agents/uitest_agent_v1.2.2.so +0 -0
- droidrun/hdcutils/errors.py +28 -0
- droidrun/hdcutils/harmony_wake.py +314 -0
- droidrun/hdcutils/keycode.py +361 -0
- droidrun/hypium/__init__.py +11 -0
- droidrun/hypium/action_mapper.py +180 -0
- droidrun/hypium/assertion_builder.py +279 -0
- droidrun/hypium/cli.py +148 -0
- droidrun/hypium/code_polisher.py +207 -0
- droidrun/hypium/component_resolver.py +221 -0
- droidrun/hypium/generator.py +559 -0
- droidrun/hypium/template.py +157 -0
- droidrun/log_handlers.py +53 -0
- droidrun/telemetry/__init__.py +15 -0
- droidrun/telemetry/events.py +53 -0
- droidrun/telemetry/tracker.py +151 -0
- droidrun/tools/__init__.py +18 -0
- droidrun/tools/driver/__init__.py +14 -0
- droidrun/tools/driver/base.py +157 -0
- droidrun/tools/driver/harmony.py +1071 -0
- droidrun/tools/driver/recording.py +376 -0
- droidrun/tools/driver/stealth.py +211 -0
- droidrun/tools/filters/__init__.py +31 -0
- droidrun/tools/filters/base.py +42 -0
- droidrun/tools/filters/concise_filter.py +120 -0
- droidrun/tools/filters/detailed_filter.py +168 -0
- droidrun/tools/filters/passthrough_filter.py +23 -0
- droidrun/tools/formatters/__init__.py +7 -0
- droidrun/tools/formatters/base.py +27 -0
- droidrun/tools/formatters/indexed_formatter.py +663 -0
- droidrun/tools/helpers/__init__.py +18 -0
- droidrun/tools/helpers/coordinate.py +25 -0
- droidrun/tools/helpers/element_search.py +635 -0
- droidrun/tools/helpers/geometry.py +49 -0
- droidrun/tools/hilog_monitor.py +284 -0
- droidrun/tools/screen_recorder.py +246 -0
- droidrun/tools/ui/__init__.py +13 -0
- droidrun/tools/ui/provider.py +239 -0
- droidrun/tools/ui/screenshot_annotator.py +196 -0
- droidrun/tools/ui/state.py +168 -0
- droidrun/tools/ui/stealth_state.py +104 -0
- harmonyrun-0.0.29.dist-info/METADATA +142 -0
- harmonyrun-0.0.29.dist-info/RECORD +158 -0
- harmonyrun-0.0.29.dist-info/WHEEL +4 -0
- harmonyrun-0.0.29.dist-info/entry_points.txt +3 -0
- harmonyrun-0.0.29.dist-info/licenses/LICENSE +21 -0
droidrun/__init__.py
ADDED
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
"""
|
|
2
|
+
HarmonyRun - A framework for automating HarmonyOS devices through LLM agents.
|
|
3
|
+
"""
|
|
4
|
+
|
|
5
|
+
from __future__ import annotations
|
|
6
|
+
|
|
7
|
+
import logging
|
|
8
|
+
from importlib import import_module
|
|
9
|
+
from importlib.metadata import PackageNotFoundError, version
|
|
10
|
+
|
|
11
|
+
try:
|
|
12
|
+
__version__ = version("harmonyrun")
|
|
13
|
+
except PackageNotFoundError:
|
|
14
|
+
# Running from a checkout without installing the distribution (e.g. scripts).
|
|
15
|
+
__version__ = "0.0.0+unknown"
|
|
16
|
+
|
|
17
|
+
# Attach a default CLILogHandler so that every consumer (CLI, SDK, tools-only)
|
|
18
|
+
# gets visible output without explicit setup. CLI may replace this via
|
|
19
|
+
# ``configure_logging()``.
|
|
20
|
+
_logger = logging.getLogger("droidrun")
|
|
21
|
+
try:
|
|
22
|
+
from droidrun.log_handlers import CLILogHandler
|
|
23
|
+
|
|
24
|
+
_logger.addHandler(CLILogHandler())
|
|
25
|
+
_logger.setLevel(logging.INFO)
|
|
26
|
+
_logger.propagate = False
|
|
27
|
+
except ImportError:
|
|
28
|
+
# e.g. ``rich`` not installed — skip; subpackages like ``hdcutils`` still import.
|
|
29
|
+
pass
|
|
30
|
+
|
|
31
|
+
_LAZY_EXPORTS: dict[str, tuple[str, str]] = {
|
|
32
|
+
"ResultEvent": ("droidrun.agent", "ResultEvent"),
|
|
33
|
+
"DroidAgent": ("droidrun.agent.droid", "DroidAgent"),
|
|
34
|
+
"load_llm": ("droidrun.agent.utils.llm_picker", "load_llm"),
|
|
35
|
+
"AgentConfig": ("droidrun.config_manager", "AgentConfig"),
|
|
36
|
+
"AppCardConfig": ("droidrun.config_manager", "AppCardConfig"),
|
|
37
|
+
"FastAgentConfig": ("droidrun.config_manager", "FastAgentConfig"),
|
|
38
|
+
"CredentialsConfig": ("droidrun.config_manager", "CredentialsConfig"),
|
|
39
|
+
"DeviceConfig": ("droidrun.config_manager", "DeviceConfig"),
|
|
40
|
+
"DroidrunConfig": ("droidrun.config_manager", "DroidrunConfig"),
|
|
41
|
+
"ExecutorConfig": ("droidrun.config_manager", "ExecutorConfig"),
|
|
42
|
+
"LLMProfile": ("droidrun.config_manager", "LLMProfile"),
|
|
43
|
+
"LoggingConfig": ("droidrun.config_manager", "LoggingConfig"),
|
|
44
|
+
"ManagerConfig": ("droidrun.config_manager", "ManagerConfig"),
|
|
45
|
+
"TelemetryConfig": ("droidrun.config_manager", "TelemetryConfig"),
|
|
46
|
+
"ToolsConfig": ("droidrun.config_manager", "ToolsConfig"),
|
|
47
|
+
"TracingConfig": ("droidrun.config_manager", "TracingConfig"),
|
|
48
|
+
"DeviceDriver": ("droidrun.tools", "DeviceDriver"),
|
|
49
|
+
"TreeStateProvider": ("droidrun.tools", "TreeStateProvider"),
|
|
50
|
+
"HarmonyDriver": ("droidrun.tools", "HarmonyDriver"),
|
|
51
|
+
"RecordingDriver": ("droidrun.tools", "RecordingDriver"),
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def __getattr__(name: str) -> object:
|
|
56
|
+
spec = _LAZY_EXPORTS.get(name)
|
|
57
|
+
if spec is None:
|
|
58
|
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
|
59
|
+
mod_name, attr = spec
|
|
60
|
+
return getattr(import_module(mod_name), attr)
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def __dir__() -> list[str]:
|
|
64
|
+
return sorted({*globals().keys(), *_LAZY_EXPORTS.keys(), "__version__"})
|
|
65
|
+
|
|
66
|
+
|
|
67
|
+
# Make main components available at package level
|
|
68
|
+
__all__ = [
|
|
69
|
+
# Agent
|
|
70
|
+
"DroidAgent",
|
|
71
|
+
"load_llm",
|
|
72
|
+
"ResultEvent",
|
|
73
|
+
# Tools / Drivers
|
|
74
|
+
"DeviceDriver",
|
|
75
|
+
"TreeStateProvider",
|
|
76
|
+
"HarmonyDriver",
|
|
77
|
+
"RecordingDriver",
|
|
78
|
+
# Configuration
|
|
79
|
+
"DroidrunConfig",
|
|
80
|
+
"AgentConfig",
|
|
81
|
+
"FastAgentConfig",
|
|
82
|
+
"ManagerConfig",
|
|
83
|
+
"ExecutorConfig",
|
|
84
|
+
"AppCardConfig",
|
|
85
|
+
"DeviceConfig",
|
|
86
|
+
"LoggingConfig",
|
|
87
|
+
"TracingConfig",
|
|
88
|
+
"TelemetryConfig",
|
|
89
|
+
"ToolsConfig",
|
|
90
|
+
"CredentialsConfig",
|
|
91
|
+
"LLMProfile",
|
|
92
|
+
]
|
droidrun/__main__.py
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
"""ActionContext — composed bag of dependencies for action functions.
|
|
2
|
+
|
|
3
|
+
Replaces the ``tools=tools_instance`` parameter that action functions
|
|
4
|
+
previously received.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import TYPE_CHECKING, Optional
|
|
10
|
+
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from droidrun.agent.droid.state import DroidAgentState
|
|
13
|
+
from droidrun.credential_manager import CredentialManager
|
|
14
|
+
from droidrun.tools.driver.base import DeviceDriver
|
|
15
|
+
from droidrun.tools.ui.provider import StateProvider
|
|
16
|
+
from droidrun.tools.ui.state import UIState
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
class ActionContext:
|
|
20
|
+
"""Everything an action function needs to interact with the device."""
|
|
21
|
+
|
|
22
|
+
def __init__(
|
|
23
|
+
self,
|
|
24
|
+
driver: "DeviceDriver",
|
|
25
|
+
ui: "Optional[UIState]",
|
|
26
|
+
shared_state: "DroidAgentState",
|
|
27
|
+
state_provider: "StateProvider",
|
|
28
|
+
app_opener_llm=None,
|
|
29
|
+
credential_manager: "Optional[CredentialManager]" = None,
|
|
30
|
+
streaming: bool = False,
|
|
31
|
+
swipe_fast_default: bool = False,
|
|
32
|
+
) -> None:
|
|
33
|
+
self.driver = driver
|
|
34
|
+
self.ui = ui # refreshed each step before tool execution
|
|
35
|
+
self.shared_state = shared_state
|
|
36
|
+
self.state_provider = state_provider
|
|
37
|
+
self.app_opener_llm = app_opener_llm
|
|
38
|
+
self.credential_manager = credential_manager
|
|
39
|
+
self.streaming = streaming
|
|
40
|
+
#: When the ``swipe`` action omits ``fast``, use this (from ``tools.swipe_fast_default``).
|
|
41
|
+
self.swipe_fast_default = swipe_fast_default
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
"""ActionResult — structured return type from action functions."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
from dataclasses import dataclass
|
|
6
|
+
|
|
7
|
+
|
|
8
|
+
@dataclass
|
|
9
|
+
class ActionResult:
|
|
10
|
+
"""What the agent sees after an action runs."""
|
|
11
|
+
|
|
12
|
+
success: bool
|
|
13
|
+
summary: str
|
|
14
|
+
|
|
15
|
+
def __str__(self) -> str:
|
|
16
|
+
return self.summary
|
|
File without changes
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
"""Shared constants used across agent and batch packages."""
|
|
2
|
+
|
|
3
|
+
LLM_HISTORY_LIMIT = 100
|
|
4
|
+
"""Max number of recent conversation steps to include in LLM prompt."""
|
|
5
|
+
|
|
6
|
+
# Canonical suite-managed resource names.
|
|
7
|
+
# Used by TestCaseResourceManager (batch) and DroidAgent (agent) to coordinate
|
|
8
|
+
# which resources are managed at suite level vs. per-phase level.
|
|
9
|
+
RESOURCE_SCREEN_RECORDING = "screen_recording"
|
|
10
|
+
RESOURCE_HILOG_MONITOR = "hilog_monitor"
|
|
11
|
+
RESOURCE_DEVICE_DRIVER = "device_driver"
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
from typing import Any, Dict, Optional
|
|
2
|
+
|
|
3
|
+
from llama_index.core.workflow import Event
|
|
4
|
+
from pydantic import Field
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
class TimedEvent(Event):
|
|
8
|
+
"""Base event with optional per-event timestamps.
|
|
9
|
+
|
|
10
|
+
Every event that flows through DroidAgent gets ``ts_unix`` and
|
|
11
|
+
``ts_iso`` stamped by ``DroidAgent.handle_stream_event`` right before
|
|
12
|
+
it is appended to the trajectory. Keeping the fields declared as
|
|
13
|
+
real pydantic fields ensures they end up in ``__dict__`` and are
|
|
14
|
+
therefore picked up by the trajectory serializer.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
ts_unix: Optional[float] = None
|
|
18
|
+
ts_iso: Optional[str] = None
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class ScreenshotEvent(TimedEvent):
|
|
22
|
+
screenshot: bytes
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class RecordUIStateEvent(TimedEvent):
|
|
26
|
+
"""Trajectory UI snapshot.
|
|
27
|
+
|
|
28
|
+
Carries the fully-formatted device-state string that was surfaced to
|
|
29
|
+
the model (phone state + indexed UI tree). Previously this event
|
|
30
|
+
also transported the raw element list (``ui_state``); that field was
|
|
31
|
+
removed because the raw snapshot isn't used for debugging and it
|
|
32
|
+
bloated trajectory disk output. The flattened element tree still
|
|
33
|
+
lives in memory on ``UIState.elements`` for the duration of a step.
|
|
34
|
+
"""
|
|
35
|
+
|
|
36
|
+
#: Same string as ``UIState.formatted_text`` / ``shared_state.formatted_device_state``
|
|
37
|
+
#: (phone state + indexed UI tree) — what goes into manager/executor prompts.
|
|
38
|
+
formatted_device_state: str = ""
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
class ToolExecutionEvent(TimedEvent):
|
|
42
|
+
"""Emitted after every tool call dispatched through ToolRegistry."""
|
|
43
|
+
|
|
44
|
+
tool_name: str
|
|
45
|
+
tool_args: Dict[str, Any]
|
|
46
|
+
success: bool
|
|
47
|
+
summary: str
|
|
48
|
+
# Latency in milliseconds; None if not measured.
|
|
49
|
+
latency_ms: Optional[float] = None
|
|
50
|
+
# Repr of the exception when ``success`` is False.
|
|
51
|
+
error: Optional[str] = None
|
|
52
|
+
# Fast-agent turn index this tool call belongs to (1-based). Lets us
|
|
53
|
+
# group tool calls by LLM turn when post-analyzing trajectories.
|
|
54
|
+
step_index: Optional[int] = None
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
class DeviceActionEvent(TimedEvent):
|
|
58
|
+
"""A single mutating or read operation routed through RecordingDriver.
|
|
59
|
+
|
|
60
|
+
Device operations are embedded directly in ``trajectory.jsonl`` so one
|
|
61
|
+
event stream can reconstruct both agent decisions and device I/O.
|
|
62
|
+
"""
|
|
63
|
+
|
|
64
|
+
op: str
|
|
65
|
+
args: Dict[str, Any] = Field(default_factory=dict)
|
|
66
|
+
success: bool = True
|
|
67
|
+
error: Optional[str] = None
|
|
68
|
+
duration_ms: Optional[float] = None
|
|
69
|
+
# True for pure observation operations (screenshot, get_ui_tree, …).
|
|
70
|
+
readonly: bool = False
|
|
71
|
+
# Compact summaries for large read-only results such as screenshots/UI trees.
|
|
72
|
+
result_summary: Optional[Dict[str, Any]] = None
|
|
73
|
+
|
|
74
|
+
|
|
75
|
+
_DEVICE_ENTRY_ARG_FIELDS: tuple[str, ...] = (
|
|
76
|
+
"subcmd",
|
|
77
|
+
"cmd",
|
|
78
|
+
"serial",
|
|
79
|
+
"returncode",
|
|
80
|
+
"stdout_preview",
|
|
81
|
+
"stderr_preview",
|
|
82
|
+
"timed_out",
|
|
83
|
+
"layer",
|
|
84
|
+
)
|
|
85
|
+
|
|
86
|
+
|
|
87
|
+
def device_action_event_from_entry(entry: Dict[str, Any]) -> DeviceActionEvent:
|
|
88
|
+
"""Build the compact trajectory event for a recorded device operation.
|
|
89
|
+
|
|
90
|
+
The old ``device_ops.jsonl`` record carried some raw-hdc fields at top level.
|
|
91
|
+
To keep trajectory rows compact and consistent, those details are nested
|
|
92
|
+
under ``args`` only for operations that actually provide them.
|
|
93
|
+
"""
|
|
94
|
+
args = dict(entry.get("args") or {})
|
|
95
|
+
for key in _DEVICE_ENTRY_ARG_FIELDS:
|
|
96
|
+
if key in entry and entry[key] is not None:
|
|
97
|
+
args.setdefault(key, entry[key])
|
|
98
|
+
|
|
99
|
+
return DeviceActionEvent(
|
|
100
|
+
op=entry.get("op", "unknown"),
|
|
101
|
+
args=args,
|
|
102
|
+
success=bool(entry.get("success", True)),
|
|
103
|
+
error=entry.get("error"),
|
|
104
|
+
duration_ms=entry.get("duration_ms"),
|
|
105
|
+
readonly=bool(entry.get("readonly", False)),
|
|
106
|
+
result_summary=entry.get("result_summary"),
|
|
107
|
+
ts_unix=entry.get("ts_unix"),
|
|
108
|
+
ts_iso=entry.get("ts_iso"),
|
|
109
|
+
)
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
class ErrorEvent(TimedEvent):
|
|
113
|
+
"""Top-level exception captured at driver/tool/workflow boundaries.
|
|
114
|
+
|
|
115
|
+
Use this to record unexpected failures so you can later correlate
|
|
116
|
+
them with the surrounding step / tool call. The message/traceback
|
|
117
|
+
are raw strings — keep them succinct enough to be readable in
|
|
118
|
+
``trajectory.jsonl``.
|
|
119
|
+
"""
|
|
120
|
+
|
|
121
|
+
where: str
|
|
122
|
+
exception_type: str
|
|
123
|
+
message: str
|
|
124
|
+
traceback: Optional[str] = None
|
|
125
|
+
step_index: Optional[int] = None
|
|
126
|
+
|
|
127
|
+
|
|
128
|
+
class PhaseStartEvent(TimedEvent):
|
|
129
|
+
"""Marks the beginning of a test phase (setup/preconditions/test_execution/postconditions/teardown)."""
|
|
130
|
+
|
|
131
|
+
phase: str
|
|
132
|
+
|
|
133
|
+
|
|
134
|
+
class PhaseEndEvent(TimedEvent):
|
|
135
|
+
"""Marks the end of a test phase."""
|
|
136
|
+
|
|
137
|
+
phase: str
|
|
138
|
+
success: bool
|
|
139
|
+
reason: str = ""
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
"""Build UI trajectory snapshots aligned with LLM-facing context.
|
|
2
|
+
|
|
3
|
+
All blocks use a consistent Markdown-inside-XML-anchor shape so the model
|
|
4
|
+
sees the same structure across fast_agent / manager / executor: each block
|
|
5
|
+
is wrapped in an XML tag (``<device_state>``, ``<previous_device_state>``,
|
|
6
|
+
etc.) for machine parseability, and the body is standard Markdown with
|
|
7
|
+
``### Phone`` / ``### UI Tree`` sub-sections produced by the formatter.
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from __future__ import annotations
|
|
11
|
+
|
|
12
|
+
from typing import Any, Dict, List
|
|
13
|
+
|
|
14
|
+
MANAGER_DEVICE_STATE_OPEN = "\n<device_state>\n## Device State\n\n"
|
|
15
|
+
MANAGER_DEVICE_STATE_CLOSE = "\n</device_state>\n"
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
def manager_device_state_block(formatted_inner: str) -> str:
|
|
19
|
+
inner = (formatted_inner or "").strip()
|
|
20
|
+
if not inner:
|
|
21
|
+
return ""
|
|
22
|
+
return MANAGER_DEVICE_STATE_OPEN + inner + MANAGER_DEVICE_STATE_CLOSE
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
def manager_memory_block(manager_memory: str) -> str:
|
|
26
|
+
m = (manager_memory or "").strip()
|
|
27
|
+
if not m:
|
|
28
|
+
return ""
|
|
29
|
+
return f"\n<memory>\n{m}\n</memory>\n"
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def manager_previous_device_state_block(previous_formatted: str) -> str:
|
|
33
|
+
prev = (previous_formatted or "").strip()
|
|
34
|
+
if not prev:
|
|
35
|
+
return ""
|
|
36
|
+
return (
|
|
37
|
+
"\n<previous_device_state>\n"
|
|
38
|
+
"## Previous Device State\n\n"
|
|
39
|
+
f"{prev}\n"
|
|
40
|
+
"</previous_device_state>\n"
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def fast_agent_ui_sections(shared_state: Any, step: int, max_steps: int) -> Dict[str, str]:
|
|
45
|
+
"""Fragments FastAgent appends to chat (excluding the image block).
|
|
46
|
+
|
|
47
|
+
Must stay in sync with the injection order in ``fast_agent.py`` so that
|
|
48
|
+
what we snapshot into the trajectory matches what the LLM actually saw.
|
|
49
|
+
|
|
50
|
+
Does not drain ``device_events``; caller drains after appending ``device_events``.
|
|
51
|
+
"""
|
|
52
|
+
out: Dict[str, str] = {}
|
|
53
|
+
remaining = max_steps - step
|
|
54
|
+
out["step_progress"] = (
|
|
55
|
+
f"\n> **Progress:** step {step} / {max_steps} · {remaining} remaining\n"
|
|
56
|
+
)
|
|
57
|
+
|
|
58
|
+
events_text = shared_state.format_device_events()
|
|
59
|
+
if events_text:
|
|
60
|
+
out["device_events"] = (
|
|
61
|
+
"\n<device_events>\n"
|
|
62
|
+
"### Device Events\n"
|
|
63
|
+
"_Transient UI events captured via system-level monitoring. "
|
|
64
|
+
"Use this as the source of truth for toast verification._\n\n"
|
|
65
|
+
f"{events_text}\n"
|
|
66
|
+
"</device_events>\n"
|
|
67
|
+
)
|
|
68
|
+
|
|
69
|
+
app_card_text = (shared_state.app_card or "").strip()
|
|
70
|
+
if app_card_text:
|
|
71
|
+
out["app_card"] = f"\n<app_card>\n{app_card_text}\n</app_card>\n"
|
|
72
|
+
|
|
73
|
+
current_state = (shared_state.formatted_device_state or "").strip()
|
|
74
|
+
if current_state:
|
|
75
|
+
out["device_state"] = (
|
|
76
|
+
"\n<device_state>\n"
|
|
77
|
+
"## Device State\n\n"
|
|
78
|
+
f"{current_state}\n"
|
|
79
|
+
"</device_state>\n"
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
prev = (shared_state.previous_formatted_device_state or "").strip()
|
|
83
|
+
if prev:
|
|
84
|
+
out["previous_device_state"] = manager_previous_device_state_block(prev)
|
|
85
|
+
|
|
86
|
+
return out
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
def build_fast_agent_ui_snapshot(
|
|
90
|
+
shared_state: Any,
|
|
91
|
+
step: int,
|
|
92
|
+
max_steps: int,
|
|
93
|
+
elements: List[Dict[str, Any]],
|
|
94
|
+
phone_state: Dict[str, Any],
|
|
95
|
+
focused_text: str,
|
|
96
|
+
) -> Dict[str, Any]:
|
|
97
|
+
sections = fast_agent_ui_sections(shared_state, step, max_steps)
|
|
98
|
+
# Must match injection order in ``fast_agent.handle_llm_input``:
|
|
99
|
+
# progress → events → app_card → device_state (freshest closest to model head).
|
|
100
|
+
last_user = "".join(
|
|
101
|
+
sections.get(k, "")
|
|
102
|
+
for k in ("step_progress", "device_events", "app_card", "device_state")
|
|
103
|
+
)
|
|
104
|
+
return {
|
|
105
|
+
"schema_version": 2,
|
|
106
|
+
"agent": "fast_agent",
|
|
107
|
+
"elements": elements,
|
|
108
|
+
"phone_state": dict(phone_state) if phone_state else {},
|
|
109
|
+
"focused_text": focused_text or "",
|
|
110
|
+
"formatted_device_state": (shared_state.formatted_device_state or "").strip(),
|
|
111
|
+
"llm_text_sections": sections,
|
|
112
|
+
"llm_last_user_message_text": last_user,
|
|
113
|
+
"llm_second_to_last_user_message_text": sections.get(
|
|
114
|
+
"previous_device_state", ""
|
|
115
|
+
),
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def build_manager_ui_snapshot(
|
|
120
|
+
shared_state: Any,
|
|
121
|
+
elements: List[Dict[str, Any]],
|
|
122
|
+
phone_state: Dict[str, Any],
|
|
123
|
+
focused_text: str,
|
|
124
|
+
manager_user_message_content: str,
|
|
125
|
+
user_message_count_before_append: int,
|
|
126
|
+
vision: bool,
|
|
127
|
+
) -> Dict[str, Any]:
|
|
128
|
+
current = (shared_state.formatted_device_state or "").strip()
|
|
129
|
+
prev_plain = (shared_state.previous_formatted_device_state or "").strip()
|
|
130
|
+
|
|
131
|
+
memory_block = manager_memory_block(shared_state.manager_memory or "")
|
|
132
|
+
device_block = manager_device_state_block(shared_state.formatted_device_state or "")
|
|
133
|
+
|
|
134
|
+
second_augment = ""
|
|
135
|
+
if user_message_count_before_append >= 1 and prev_plain:
|
|
136
|
+
second_augment = manager_previous_device_state_block(
|
|
137
|
+
shared_state.previous_formatted_device_state or ""
|
|
138
|
+
)
|
|
139
|
+
|
|
140
|
+
return {
|
|
141
|
+
"schema_version": 2,
|
|
142
|
+
"agent": "manager",
|
|
143
|
+
"elements": elements,
|
|
144
|
+
"phone_state": dict(phone_state) if phone_state else {},
|
|
145
|
+
"focused_text": focused_text or "",
|
|
146
|
+
"formatted_device_state": current,
|
|
147
|
+
"previous_formatted_device_state": prev_plain,
|
|
148
|
+
"app_card": (shared_state.app_card or "").strip(),
|
|
149
|
+
"manager_user_message_content": manager_user_message_content,
|
|
150
|
+
"llm_last_user_text_appendix": memory_block + device_block,
|
|
151
|
+
"llm_second_to_last_user_text_appendix": second_augment,
|
|
152
|
+
"vision_includes_current_screenshot": bool(vision),
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
|
|
156
|
+
def build_stateless_manager_ui_snapshot(
|
|
157
|
+
shared_state: Any,
|
|
158
|
+
full_user_message_text: str,
|
|
159
|
+
) -> Dict[str, Any]:
|
|
160
|
+
return {
|
|
161
|
+
"schema_version": 2,
|
|
162
|
+
"agent": "stateless_manager",
|
|
163
|
+
"elements": list(shared_state.a11y_tree),
|
|
164
|
+
"phone_state": dict(shared_state.phone_state) if shared_state.phone_state else {},
|
|
165
|
+
"focused_text": shared_state.focused_text or "",
|
|
166
|
+
"formatted_device_state": (shared_state.formatted_device_state or "").strip(),
|
|
167
|
+
"previous_formatted_device_state": (
|
|
168
|
+
shared_state.previous_formatted_device_state or ""
|
|
169
|
+
).strip(),
|
|
170
|
+
"app_card": (shared_state.app_card or "").strip(),
|
|
171
|
+
"full_user_message_text": full_user_message_text,
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
|
|
175
|
+
def build_final_ui_snapshot(
|
|
176
|
+
elements: List[Dict[str, Any]],
|
|
177
|
+
phone_state: Dict[str, Any],
|
|
178
|
+
focused_text: str,
|
|
179
|
+
formatted_text: str,
|
|
180
|
+
) -> Dict[str, Any]:
|
|
181
|
+
return {
|
|
182
|
+
"schema_version": 2,
|
|
183
|
+
"agent": "capture_final",
|
|
184
|
+
"elements": elements,
|
|
185
|
+
"phone_state": dict(phone_state) if phone_state else {},
|
|
186
|
+
"focused_text": focused_text or "",
|
|
187
|
+
"formatted_device_state": (formatted_text or "").strip(),
|
|
188
|
+
}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""
|
|
2
|
+
Droidrun Agent Module.
|
|
3
|
+
|
|
4
|
+
This module provides a ReAct agent for automating HarmonyOS devices using reasoning and acting.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from droidrun.agent.droid.droid_agent import DroidAgent
|
|
8
|
+
from droidrun.agent.droid.state import DroidAgentState
|
|
9
|
+
|
|
10
|
+
__all__ = ["DroidAgent", "DroidAgentState"]
|