droidrun 0.3.3__py3-none-any.whl → 0.3.5__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/agent/codeact/codeact_agent.py +13 -8
- droidrun/agent/context/task_manager.py +18 -3
- droidrun/agent/droid/droid_agent.py +35 -10
- droidrun/agent/droid/events.py +1 -0
- droidrun/agent/planner/planner_agent.py +21 -15
- droidrun/agent/utils/chat_utils.py +24 -22
- droidrun/agent/utils/executer.py +10 -2
- droidrun/cli/main.py +49 -22
- droidrun/macro/cli.py +1 -1
- droidrun/portal.py +23 -7
- droidrun/telemetry/events.py +1 -1
- droidrun/tools/adb.py +86 -98
- droidrun/tools/tools.py +28 -0
- {droidrun-0.3.3.dist-info → droidrun-0.3.5.dist-info}/METADATA +1 -2
- {droidrun-0.3.3.dist-info → droidrun-0.3.5.dist-info}/RECORD +18 -18
- {droidrun-0.3.3.dist-info → droidrun-0.3.5.dist-info}/WHEEL +0 -0
- {droidrun-0.3.3.dist-info → droidrun-0.3.5.dist-info}/entry_points.txt +0 -0
- {droidrun-0.3.3.dist-info → droidrun-0.3.5.dist-info}/licenses/LICENSE +0 -0
@@ -165,16 +165,18 @@ class CodeActAgent(Workflow):
|
|
165
165
|
chat_history = await chat_utils.add_memory_block(self.remembered_info, chat_history)
|
166
166
|
|
167
167
|
for context in self.required_context:
|
168
|
-
if
|
169
|
-
|
170
|
-
"[yellow]DeepSeek doesnt support images. Disabling screenshots[/]"
|
171
|
-
)
|
172
|
-
elif self.vision == True and context == "screenshot":
|
168
|
+
if context == "screenshot":
|
169
|
+
# if vision is disabled, screenshot should save to trajectory
|
173
170
|
screenshot = (self.tools.take_screenshot())[1]
|
174
171
|
ctx.write_event_to_stream(ScreenshotEvent(screenshot=screenshot))
|
175
172
|
|
176
173
|
await ctx.set("screenshot", screenshot)
|
177
|
-
|
174
|
+
if model == "DeepSeek":
|
175
|
+
logger.warning(
|
176
|
+
"[yellow]DeepSeek doesnt support images. Disabling screenshots[/]"
|
177
|
+
)
|
178
|
+
elif self.vision == True: # if vision is enabled, add screenshot to chat history
|
179
|
+
chat_history = await chat_utils.add_screenshot_image_block(screenshot, chat_history)
|
178
180
|
|
179
181
|
if context == "ui_state":
|
180
182
|
try:
|
@@ -248,7 +250,10 @@ class CodeActAgent(Workflow):
|
|
248
250
|
try:
|
249
251
|
self.code_exec_counter += 1
|
250
252
|
result = await self.executor.execute(ctx, code)
|
251
|
-
logger.info(f"💡 Code execution successful. Result: {result}")
|
253
|
+
logger.info(f"💡 Code execution successful. Result: {result['output']}")
|
254
|
+
screenshots = result['screenshots']
|
255
|
+
for screenshot in screenshots[:-1]: # the last screenshot will be captured by next step
|
256
|
+
ctx.write_event_to_stream(ScreenshotEvent(screenshot=screenshot))
|
252
257
|
|
253
258
|
if self.tools.finished == True:
|
254
259
|
logger.debug(" - Task completed.")
|
@@ -260,7 +265,7 @@ class CodeActAgent(Workflow):
|
|
260
265
|
|
261
266
|
self.remembered_info = self.tools.memory
|
262
267
|
|
263
|
-
event = TaskExecutionResultEvent(output=str(result))
|
268
|
+
event = TaskExecutionResultEvent(output=str(result['output']))
|
264
269
|
ctx.write_event_to_stream(event)
|
265
270
|
return event
|
266
271
|
|
@@ -1,5 +1,5 @@
|
|
1
1
|
import os
|
2
|
-
from typing import List, Dict
|
2
|
+
from typing import List, Dict, Optional
|
3
3
|
from dataclasses import dataclass
|
4
4
|
import copy
|
5
5
|
|
@@ -11,6 +11,9 @@ class Task:
|
|
11
11
|
description: str
|
12
12
|
status: str
|
13
13
|
agent_type: str
|
14
|
+
# Optional fields to carry success/failure context back to the planner
|
15
|
+
message: Optional[str] = None
|
16
|
+
failure_reason: Optional[str] = None
|
14
17
|
|
15
18
|
|
16
19
|
class TaskManager:
|
@@ -40,14 +43,23 @@ class TaskManager:
|
|
40
43
|
def get_task_history(self):
|
41
44
|
return self.task_history
|
42
45
|
|
43
|
-
def
|
46
|
+
def get_current_task(self) -> Optional[Task]:
|
47
|
+
"""Return the first task with status "pending" from the task list."""
|
48
|
+
for task in self.tasks:
|
49
|
+
if task.status == self.STATUS_PENDING:
|
50
|
+
return task
|
51
|
+
return None
|
52
|
+
|
53
|
+
def complete_task(self, task: Task, message: Optional[str] = None):
|
44
54
|
task = copy.deepcopy(task)
|
45
55
|
task.status = self.STATUS_COMPLETED
|
56
|
+
task.message = message
|
46
57
|
self.task_history.append(task)
|
47
58
|
|
48
|
-
def fail_task(self, task: Task):
|
59
|
+
def fail_task(self, task: Task, failure_reason: Optional[str] = None):
|
49
60
|
task = copy.deepcopy(task)
|
50
61
|
task.status = self.STATUS_FAILED
|
62
|
+
task.failure_reason = failure_reason
|
51
63
|
self.task_history.append(task)
|
52
64
|
|
53
65
|
def get_completed_tasks(self) -> list[dict]:
|
@@ -55,6 +67,9 @@ class TaskManager:
|
|
55
67
|
|
56
68
|
def get_failed_tasks(self) -> list[dict]:
|
57
69
|
return [task for task in self.task_history if task.status == self.STATUS_FAILED]
|
70
|
+
|
71
|
+
def get_task_history(self) -> list[dict]:
|
72
|
+
return self.task_history
|
58
73
|
|
59
74
|
|
60
75
|
def save_to_file(self):
|
@@ -68,7 +68,7 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
68
68
|
reflection: bool = False,
|
69
69
|
enable_tracing: bool = False,
|
70
70
|
debug: bool = False,
|
71
|
-
save_trajectories:
|
71
|
+
save_trajectories: str = "none",
|
72
72
|
excluded_tools: List[str] = None,
|
73
73
|
*args,
|
74
74
|
**kwargs
|
@@ -86,6 +86,10 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
86
86
|
reflection: Whether to reflect on steps the CodeActAgent did to give the PlannerAgent advice
|
87
87
|
enable_tracing: Whether to enable Arize Phoenix tracing
|
88
88
|
debug: Whether to enable verbose debug logging
|
89
|
+
save_trajectories: Trajectory saving level. Can be:
|
90
|
+
- "none" (no saving)
|
91
|
+
- "step" (save per step)
|
92
|
+
- "action" (save per action)
|
89
93
|
**kwargs: Additional keyword arguments to pass to the agents
|
90
94
|
"""
|
91
95
|
self.user_id = kwargs.pop("user_id", None)
|
@@ -114,7 +118,17 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
114
118
|
self.debug = debug
|
115
119
|
|
116
120
|
self.event_counter = 0
|
117
|
-
|
121
|
+
# Handle backward compatibility: bool -> str mapping
|
122
|
+
if isinstance(save_trajectories, bool):
|
123
|
+
self.save_trajectories = "step" if save_trajectories else "none"
|
124
|
+
else:
|
125
|
+
# Validate string values
|
126
|
+
valid_values = ["none", "step", "action"]
|
127
|
+
if save_trajectories not in valid_values:
|
128
|
+
logger.warning(f"Invalid save_trajectories value: {save_trajectories}. Using 'none' instead.")
|
129
|
+
self.save_trajectories = "none"
|
130
|
+
else:
|
131
|
+
self.save_trajectories = save_trajectories
|
118
132
|
|
119
133
|
self.trajectory = Trajectory(goal=goal)
|
120
134
|
self.task_manager = TaskManager()
|
@@ -125,9 +139,12 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
125
139
|
self.current_episodic_memory = None
|
126
140
|
|
127
141
|
logger.info("🤖 Initializing DroidAgent...")
|
142
|
+
logger.info(f"💾 Trajectory saving level: {self.save_trajectories}")
|
128
143
|
|
129
144
|
self.tool_list = describe_tools(tools, excluded_tools)
|
130
145
|
self.tools_instance = tools
|
146
|
+
|
147
|
+
self.tools_instance.save_trajectories = self.save_trajectories
|
131
148
|
|
132
149
|
|
133
150
|
if self.reasoning:
|
@@ -173,11 +190,11 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
173
190
|
|
174
191
|
logger.info("✅ DroidAgent initialized successfully.")
|
175
192
|
|
176
|
-
def run(self) -> WorkflowHandler:
|
193
|
+
def run(self, *args, **kwargs) -> WorkflowHandler:
|
177
194
|
"""
|
178
195
|
Run the DroidAgent workflow.
|
179
196
|
"""
|
180
|
-
return super().run()
|
197
|
+
return super().run(*args, **kwargs)
|
181
198
|
|
182
199
|
@step
|
183
200
|
async def execute_task(
|
@@ -237,16 +254,24 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
237
254
|
return CodeActResultEvent(success=False, reason=f"Error: {str(e)}", task=task, steps=[])
|
238
255
|
|
239
256
|
@step
|
240
|
-
async def handle_codeact_execute(self, ctx: Context, ev: CodeActResultEvent) -> FinalizeEvent | ReflectionEvent:
|
257
|
+
async def handle_codeact_execute(self, ctx: Context, ev: CodeActResultEvent) -> FinalizeEvent | ReflectionEvent | ReasoningLogicEvent:
|
241
258
|
try:
|
242
259
|
task = ev.task
|
243
260
|
if not self.reasoning:
|
244
261
|
return FinalizeEvent(success=ev.success, reason=ev.reason, output=ev.reason, task=[task], tasks=[task], steps=ev.steps)
|
245
262
|
|
246
|
-
if self.reflection:
|
263
|
+
if self.reflection and ev.success:
|
247
264
|
return ReflectionEvent(task=task)
|
248
|
-
|
249
|
-
|
265
|
+
|
266
|
+
# Reasoning is enabled but reflection is disabled.
|
267
|
+
# Success: mark complete and proceed to next step in reasoning loop.
|
268
|
+
# Failure: mark failed and trigger planner immediately without advancing to the next queued task.
|
269
|
+
if ev.success:
|
270
|
+
self.task_manager.complete_task(task, message=ev.reason)
|
271
|
+
return ReasoningLogicEvent()
|
272
|
+
else:
|
273
|
+
self.task_manager.fail_task(task, failure_reason=ev.reason)
|
274
|
+
return ReasoningLogicEvent(force_planning=True)
|
250
275
|
|
251
276
|
except Exception as e:
|
252
277
|
logger.error(f"❌ Error during DroidAgent execution: {e}")
|
@@ -298,7 +323,7 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
298
323
|
if ev.reflection:
|
299
324
|
handler = planner_agent.run(remembered_info=self.tools_instance.memory, reflection=ev.reflection)
|
300
325
|
else:
|
301
|
-
if self.task_iter:
|
326
|
+
if not ev.force_planning and self.task_iter:
|
302
327
|
try:
|
303
328
|
task = next(self.task_iter)
|
304
329
|
return CodeActExecuteEvent(task=task, reflection=None)
|
@@ -387,7 +412,7 @@ A wrapper class that coordinates between PlannerAgent (creates plans) and
|
|
387
412
|
"steps": ev.steps,
|
388
413
|
}
|
389
414
|
|
390
|
-
if self.trajectory and self.save_trajectories:
|
415
|
+
if self.trajectory and self.save_trajectories != "none":
|
391
416
|
self.trajectory.save_trajectory()
|
392
417
|
|
393
418
|
return StopEvent(result)
|
droidrun/agent/droid/events.py
CHANGED
@@ -130,9 +130,10 @@ class PlannerAgent(Workflow):
|
|
130
130
|
self.steps_counter += 1
|
131
131
|
logger.info(f"🧠 Thinking about how to plan the goal...")
|
132
132
|
|
133
|
+
# if vision is disabled, screenshot should save to trajectory
|
134
|
+
screenshot = (self.tools_instance.take_screenshot())[1]
|
135
|
+
ctx.write_event_to_stream(ScreenshotEvent(screenshot=screenshot))
|
133
136
|
if self.vision:
|
134
|
-
screenshot = (self.tools_instance.take_screenshot())[1]
|
135
|
-
ctx.write_event_to_stream(ScreenshotEvent(screenshot=screenshot))
|
136
137
|
await ctx.set("screenshot", screenshot)
|
137
138
|
|
138
139
|
try:
|
@@ -168,11 +169,15 @@ class PlannerAgent(Workflow):
|
|
168
169
|
try:
|
169
170
|
result = await self.executer.execute(ctx, code)
|
170
171
|
logger.info(f"📝 Planning complete")
|
171
|
-
logger.debug(f" - Planning code executed. Result: {result}")
|
172
|
+
logger.debug(f" - Planning code executed. Result: {result['output']}")
|
173
|
+
|
174
|
+
screenshots = result['screenshots']
|
175
|
+
for screenshot in screenshots[:-1]: # the last screenshot will be captured by next step
|
176
|
+
ctx.write_event_to_stream(ScreenshotEvent(screenshot=screenshot))
|
172
177
|
|
173
178
|
await self.chat_memory.aput(
|
174
179
|
ChatMessage(
|
175
|
-
role="user", content=f"Execution Result:\n```\n{result}\n```"
|
180
|
+
role="user", content=f"Execution Result:\n```\n{result['output']}\n```"
|
176
181
|
)
|
177
182
|
)
|
178
183
|
|
@@ -241,21 +246,22 @@ wrap your code inside this:
|
|
241
246
|
logger.debug(f" - Sending {len(chat_history)} messages to LLM.")
|
242
247
|
|
243
248
|
model = self.llm.class_name()
|
244
|
-
if
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
249
|
+
if self.vision == True:
|
250
|
+
if model == "DeepSeek":
|
251
|
+
logger.warning(
|
252
|
+
"[yellow]DeepSeek doesnt support images. Disabling screenshots[/]"
|
253
|
+
)
|
254
|
+
else:
|
255
|
+
chat_history = await chat_utils.add_screenshot_image_block(
|
256
|
+
await ctx.get("screenshot"), chat_history
|
257
|
+
)
|
253
258
|
|
254
259
|
|
255
260
|
|
256
261
|
chat_history = await chat_utils.add_task_history_block(
|
257
|
-
self.task_manager.get_completed_tasks(),
|
258
|
-
self.task_manager.get_failed_tasks(),
|
262
|
+
#self.task_manager.get_completed_tasks(),
|
263
|
+
#self.task_manager.get_failed_tasks(),
|
264
|
+
self.task_manager.get_task_history(),
|
259
265
|
chat_history,
|
260
266
|
)
|
261
267
|
|
@@ -120,7 +120,7 @@ async def add_ui_text_block(ui_state: str, chat_history: List[ChatMessage], copy
|
|
120
120
|
|
121
121
|
async def add_screenshot_image_block(screenshot, chat_history: List[ChatMessage], copy = True) -> None:
|
122
122
|
if screenshot:
|
123
|
-
image_block = ImageBlock(image=
|
123
|
+
image_block = ImageBlock(image=screenshot)
|
124
124
|
if copy:
|
125
125
|
chat_history = chat_history.copy() # Create a copy of chat history to avoid modifying the original
|
126
126
|
chat_history[-1] = message_copy(chat_history[-1])
|
@@ -201,29 +201,31 @@ async def get_reflection_block(reflections: List[Reflection]) -> ChatMessage:
|
|
201
201
|
|
202
202
|
return ChatMessage(role="user", content=reflection_block)
|
203
203
|
|
204
|
-
async def add_task_history_block(
|
205
|
-
|
204
|
+
async def add_task_history_block(all_tasks: list[dict], chat_history: List[ChatMessage]) -> List[ChatMessage]:
|
205
|
+
"""Experimental task history with all previous tasks."""
|
206
|
+
if not all_tasks:
|
207
|
+
return chat_history
|
208
|
+
|
209
|
+
lines = ["### Task Execution History (chronological):"]
|
210
|
+
for index, task in enumerate(all_tasks, 1):
|
211
|
+
description: str
|
212
|
+
status_value: str
|
213
|
+
|
214
|
+
if hasattr(task, "description") and hasattr(task, "status"):
|
215
|
+
description = getattr(task, "description")
|
216
|
+
status_value = getattr(task, "status") or "unknown"
|
217
|
+
elif isinstance(task, dict):
|
218
|
+
description = str(task.get("description", task))
|
219
|
+
status_value = str(task.get("status", "unknown"))
|
220
|
+
else:
|
221
|
+
description = str(task)
|
222
|
+
status_value = "unknown"
|
206
223
|
|
207
|
-
|
208
|
-
all_tasks = completed_tasks + failed_tasks
|
209
|
-
|
210
|
-
if all_tasks:
|
211
|
-
task_history += "Task History (chronological order):\n"
|
212
|
-
for i, task in enumerate(all_tasks, 1):
|
213
|
-
if hasattr(task, 'description'):
|
214
|
-
status_indicator = "[success]" if hasattr(task, 'status') and task.status == "completed" else "[failed]"
|
215
|
-
task_history += f"{i}. {status_indicator} {task.description}\n"
|
216
|
-
elif isinstance(task, dict):
|
217
|
-
# For backward compatibility with dict format
|
218
|
-
task_description = task.get('description', str(task))
|
219
|
-
status_indicator = "[success]" if task in completed_tasks else "[failed]"
|
220
|
-
task_history += f"{i}. {status_indicator} {task_description}\n"
|
221
|
-
else:
|
222
|
-
status_indicator = "[success]" if task in completed_tasks else "[failed]"
|
223
|
-
task_history += f"{i}. {status_indicator} {task}\n"
|
224
|
+
indicator = f"[{status_value}]"
|
224
225
|
|
225
|
-
|
226
|
-
|
226
|
+
lines.append(f"{index}. {indicator} {description}")
|
227
|
+
|
228
|
+
task_block = TextBlock(text="\n".join(lines))
|
227
229
|
|
228
230
|
chat_history = chat_history.copy()
|
229
231
|
chat_history[-1] = message_copy(chat_history[-1])
|
droidrun/agent/utils/executer.py
CHANGED
@@ -99,10 +99,13 @@ class SimpleCodeExecutor:
|
|
99
99
|
"""
|
100
100
|
# Update UI elements before execution
|
101
101
|
self.globals['ui_state'] = await ctx.get("ui_state", None)
|
102
|
-
|
102
|
+
self.globals['step_screenshots'] = []
|
103
|
+
self.globals['step_ui_states'] = []
|
104
|
+
|
103
105
|
if self.tools_instance and isinstance(self.tools_instance, AdbTools):
|
104
106
|
self.tools_instance._set_context(ctx)
|
105
107
|
|
108
|
+
# Capture stdout and stderr
|
106
109
|
stdout = io.StringIO()
|
107
110
|
stderr = io.StringIO()
|
108
111
|
|
@@ -137,4 +140,9 @@ class SimpleCodeExecutor:
|
|
137
140
|
output = f"Error: {type(e).__name__}: {str(e)}\n"
|
138
141
|
output += traceback.format_exc()
|
139
142
|
|
140
|
-
|
143
|
+
result = {
|
144
|
+
'output': output,
|
145
|
+
'screenshots': self.globals['step_screenshots'],
|
146
|
+
'ui_states': self.globals['step_ui_states']
|
147
|
+
}
|
148
|
+
return result
|
droidrun/cli/main.py
CHANGED
@@ -22,6 +22,8 @@ from droidrun.portal import (
|
|
22
22
|
enable_portal_accessibility,
|
23
23
|
PORTAL_PACKAGE_NAME,
|
24
24
|
ping_portal,
|
25
|
+
ping_portal_tcp,
|
26
|
+
ping_portal_content,
|
25
27
|
)
|
26
28
|
from droidrun.macro.cli import macro_cli
|
27
29
|
|
@@ -78,9 +80,9 @@ async def run_command(
|
|
78
80
|
reasoning: bool,
|
79
81
|
reflection: bool,
|
80
82
|
tracing: bool,
|
81
|
-
debug: bool,
|
83
|
+
debug: bool,
|
82
84
|
use_tcp: bool,
|
83
|
-
save_trajectory:
|
85
|
+
save_trajectory: str = "none",
|
84
86
|
ios: bool = False,
|
85
87
|
allow_drag: bool = False,
|
86
88
|
**kwargs,
|
@@ -104,7 +106,7 @@ async def run_command(
|
|
104
106
|
# Device setup
|
105
107
|
if device is None and not ios:
|
106
108
|
logger.info("🔍 Finding connected device...")
|
107
|
-
|
109
|
+
|
108
110
|
devices = adb.list()
|
109
111
|
if not devices:
|
110
112
|
raise ValueError("No connected devices found.")
|
@@ -117,10 +119,14 @@ async def run_command(
|
|
117
119
|
else:
|
118
120
|
logger.info(f"📱 Using device: {device}")
|
119
121
|
|
120
|
-
tools =
|
122
|
+
tools = (
|
123
|
+
AdbTools(serial=device, use_tcp=use_tcp)
|
124
|
+
if not ios
|
125
|
+
else IOSTools(url=device)
|
126
|
+
)
|
121
127
|
# Set excluded tools based on CLI flags
|
122
128
|
excluded_tools = [] if allow_drag else ["drag"]
|
123
|
-
|
129
|
+
|
124
130
|
# Select personas based on --drag flag
|
125
131
|
personas = [BIG_AGENT] if allow_drag else [DEFAULT]
|
126
132
|
|
@@ -144,7 +150,6 @@ async def run_command(
|
|
144
150
|
if tracing:
|
145
151
|
logger.info("🔍 Tracing enabled")
|
146
152
|
|
147
|
-
|
148
153
|
droid_agent = DroidAgent(
|
149
154
|
goal=command,
|
150
155
|
llm=llm,
|
@@ -254,13 +259,16 @@ class DroidRunCLI(click.Group):
|
|
254
259
|
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
255
260
|
)
|
256
261
|
@click.option(
|
257
|
-
"--use-tcp",
|
262
|
+
"--use-tcp",
|
263
|
+
is_flag=True,
|
264
|
+
help="Use TCP communication for device control",
|
265
|
+
default=False,
|
258
266
|
)
|
259
267
|
@click.option(
|
260
268
|
"--save-trajectory",
|
261
|
-
|
262
|
-
help="
|
263
|
-
default=
|
269
|
+
type=click.Choice(["none", "step", "action"]),
|
270
|
+
help="Trajectory saving level: none (no saving), step (save per step), action (save per action)",
|
271
|
+
default="none",
|
264
272
|
)
|
265
273
|
@click.group(cls=DroidRunCLI)
|
266
274
|
def cli(
|
@@ -277,7 +285,7 @@ def cli(
|
|
277
285
|
tracing: bool,
|
278
286
|
debug: bool,
|
279
287
|
use_tcp: bool,
|
280
|
-
save_trajectory:
|
288
|
+
save_trajectory: str,
|
281
289
|
):
|
282
290
|
"""DroidRun - Control your Android device through LLM agents."""
|
283
291
|
pass
|
@@ -333,13 +341,16 @@ def cli(
|
|
333
341
|
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
334
342
|
)
|
335
343
|
@click.option(
|
336
|
-
"--use-tcp",
|
344
|
+
"--use-tcp",
|
345
|
+
is_flag=True,
|
346
|
+
help="Use TCP communication for device control",
|
347
|
+
default=False,
|
337
348
|
)
|
338
349
|
@click.option(
|
339
350
|
"--save-trajectory",
|
340
|
-
|
341
|
-
help="
|
342
|
-
default=
|
351
|
+
type=click.Choice(["none", "step", "action"]),
|
352
|
+
help="Trajectory saving level: none (no saving), step (save per step), action (save per action)",
|
353
|
+
default="none",
|
343
354
|
)
|
344
355
|
@click.option(
|
345
356
|
"--drag",
|
@@ -364,7 +375,7 @@ def run(
|
|
364
375
|
tracing: bool,
|
365
376
|
debug: bool,
|
366
377
|
use_tcp: bool,
|
367
|
-
save_trajectory:
|
378
|
+
save_trajectory: str,
|
368
379
|
allow_drag: bool,
|
369
380
|
ios: bool,
|
370
381
|
):
|
@@ -437,7 +448,11 @@ def disconnect(serial: str):
|
|
437
448
|
|
438
449
|
@cli.command()
|
439
450
|
@click.option("--device", "-d", help="Device serial number or IP address", default=None)
|
440
|
-
@click.option(
|
451
|
+
@click.option(
|
452
|
+
"--path",
|
453
|
+
help="Path to the Droidrun Portal APK to install on the device. If not provided, the latest portal apk version will be downloaded and installed.",
|
454
|
+
default=None,
|
455
|
+
)
|
441
456
|
@click.option(
|
442
457
|
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
443
458
|
)
|
@@ -474,7 +489,9 @@ def setup(path: str | None, device: str | None, debug: bool):
|
|
474
489
|
|
475
490
|
console.print(f"[bold blue]Step 1/2: Installing APK:[/] {apk_path}")
|
476
491
|
try:
|
477
|
-
device_obj.install(
|
492
|
+
device_obj.install(
|
493
|
+
apk_path, uninstall=True, flags=["-g"], silent=not debug
|
494
|
+
)
|
478
495
|
except Exception as e:
|
479
496
|
console.print(f"[bold red]Installation failed:[/] {e}")
|
480
497
|
return
|
@@ -499,9 +516,7 @@ def setup(path: str | None, device: str | None, debug: bool):
|
|
499
516
|
"[yellow]Opening accessibility settings for manual configuration...[/]"
|
500
517
|
)
|
501
518
|
|
502
|
-
device_obj.shell(
|
503
|
-
"am start -a android.settings.ACCESSIBILITY_SETTINGS"
|
504
|
-
)
|
519
|
+
device_obj.shell("am start -a android.settings.ACCESSIBILITY_SETTINGS")
|
505
520
|
|
506
521
|
console.print(
|
507
522
|
"\n[yellow]Please complete the following steps on your device:[/]"
|
@@ -530,10 +545,16 @@ def setup(path: str | None, device: str | None, debug: bool):
|
|
530
545
|
|
531
546
|
@cli.command()
|
532
547
|
@click.option("--device", "-d", help="Device serial number or IP address", default=None)
|
548
|
+
@click.option(
|
549
|
+
"--use-tcp",
|
550
|
+
is_flag=True,
|
551
|
+
help="Use TCP communication for device control",
|
552
|
+
default=False,
|
553
|
+
)
|
533
554
|
@click.option(
|
534
555
|
"--debug", is_flag=True, help="Enable verbose debug logging", default=False
|
535
556
|
)
|
536
|
-
def ping(device: str | None, debug: bool):
|
557
|
+
def ping(device: str | None, use_tcp: bool, debug: bool):
|
537
558
|
"""Ping a device to check if it is ready and accessible."""
|
538
559
|
try:
|
539
560
|
device_obj = adb.device(device)
|
@@ -542,6 +563,12 @@ def ping(device: str | None, debug: bool):
|
|
542
563
|
return
|
543
564
|
|
544
565
|
ping_portal(device_obj, debug)
|
566
|
+
|
567
|
+
if use_tcp:
|
568
|
+
ping_portal_tcp(device_obj, debug)
|
569
|
+
else:
|
570
|
+
ping_portal_content(device_obj, debug)
|
571
|
+
|
545
572
|
console.print(
|
546
573
|
"[bold green]Portal is installed and accessible. You're good to go![/]"
|
547
574
|
)
|
droidrun/macro/cli.py
CHANGED
@@ -215,7 +215,7 @@ def list(directory: str, debug: bool):
|
|
215
215
|
|
216
216
|
# Still use console for table display as it's structured data
|
217
217
|
console.print(table)
|
218
|
-
logger.info(f"💡 Use 'droidrun
|
218
|
+
logger.info(f"💡 Use 'droidrun macro replay {directory}/<folder>' to replay a trajectory")
|
219
219
|
|
220
220
|
except Exception as e:
|
221
221
|
logger.error(f"💥 Error: {e}")
|
droidrun/portal.py
CHANGED
@@ -3,6 +3,8 @@ import tempfile
|
|
3
3
|
import os
|
4
4
|
import contextlib
|
5
5
|
from adbutils import adb, AdbDevice
|
6
|
+
from droidrun.tools import AdbTools
|
7
|
+
from rich.console import Console
|
6
8
|
|
7
9
|
REPO = "droidrun/droidrun-portal"
|
8
10
|
ASSET_NAME = "droidrun-portal"
|
@@ -36,8 +38,10 @@ def get_latest_release_assets(debug: bool = False):
|
|
36
38
|
|
37
39
|
@contextlib.contextmanager
|
38
40
|
def download_portal_apk(debug: bool = False):
|
41
|
+
console = Console()
|
39
42
|
assets = get_latest_release_assets(debug)
|
40
43
|
|
44
|
+
asset_version = None
|
41
45
|
asset_url = None
|
42
46
|
for asset in assets:
|
43
47
|
if (
|
@@ -46,11 +50,15 @@ def download_portal_apk(debug: bool = False):
|
|
46
50
|
and asset["name"].startswith(ASSET_NAME)
|
47
51
|
):
|
48
52
|
asset_url = asset["browser_download_url"]
|
53
|
+
asset_version = asset["name"].split("-")[-1]
|
54
|
+
asset_version = asset_version.removesuffix(".apk")
|
49
55
|
break
|
50
56
|
elif "downloadUrl" in asset and os.path.basename(
|
51
57
|
asset["downloadUrl"]
|
52
58
|
).startswith(ASSET_NAME):
|
53
59
|
asset_url = asset["downloadUrl"]
|
60
|
+
asset_version: str = asset["name"].split("-")[-1]
|
61
|
+
asset_version = asset_version.removesuffix(".apk")
|
54
62
|
break
|
55
63
|
else:
|
56
64
|
if debug:
|
@@ -59,6 +67,10 @@ def download_portal_apk(debug: bool = False):
|
|
59
67
|
if not asset_url:
|
60
68
|
raise Exception(f"Asset named '{ASSET_NAME}' not found in the latest release.")
|
61
69
|
|
70
|
+
console.print(f"Found Portal APK [bold]{asset_version}[/bold]")
|
71
|
+
if debug:
|
72
|
+
console.print(f"Asset URL: {asset_url}")
|
73
|
+
|
62
74
|
tmp = tempfile.NamedTemporaryFile(delete=False, suffix=".apk")
|
63
75
|
try:
|
64
76
|
r = requests.get(asset_url, stream=True)
|
@@ -81,9 +93,7 @@ def enable_portal_accessibility(device: AdbDevice):
|
|
81
93
|
|
82
94
|
|
83
95
|
def check_portal_accessibility(device: AdbDevice, debug: bool = False) -> bool:
|
84
|
-
a11y_services = device.shell(
|
85
|
-
"settings get secure enabled_accessibility_services"
|
86
|
-
)
|
96
|
+
a11y_services = device.shell("settings get secure enabled_accessibility_services")
|
87
97
|
if not A11Y_SERVICE_NAME in a11y_services:
|
88
98
|
if debug:
|
89
99
|
print(a11y_services)
|
@@ -118,17 +128,23 @@ def ping_portal(device: AdbDevice, debug: bool = False):
|
|
118
128
|
"Droidrun Portal is not enabled as an accessibility service on the device"
|
119
129
|
)
|
120
130
|
|
131
|
+
|
132
|
+
def ping_portal_content(device: AdbDevice, debug: bool = False):
|
121
133
|
try:
|
122
|
-
state = device.shell(
|
123
|
-
"content query --uri content://com.droidrun.portal/state"
|
124
|
-
)
|
134
|
+
state = device.shell("content query --uri content://com.droidrun.portal/state")
|
125
135
|
if not "Row: 0 result=" in state:
|
126
136
|
raise Exception("Failed to get state from Droidrun Portal")
|
127
|
-
|
128
137
|
except Exception as e:
|
129
138
|
raise Exception(f"Droidrun Portal is not reachable: {e}")
|
130
139
|
|
131
140
|
|
141
|
+
def ping_portal_tcp(device: AdbDevice, debug: bool = False):
|
142
|
+
try:
|
143
|
+
tools = AdbTools(serial=device.serial, use_tcp=True)
|
144
|
+
except Exception as e:
|
145
|
+
raise Exception(f"Failed to setup TCP forwarding: {e}")
|
146
|
+
|
147
|
+
|
132
148
|
def test():
|
133
149
|
device = adb.device()
|
134
150
|
ping_portal(device, debug=False)
|
droidrun/telemetry/events.py
CHANGED
droidrun/tools/adb.py
CHANGED
@@ -8,7 +8,7 @@ import json
|
|
8
8
|
import time
|
9
9
|
import logging
|
10
10
|
from llama_index.core.workflow import Context
|
11
|
-
from
|
11
|
+
from typing import Optional, Dict, Tuple, List, Any
|
12
12
|
from droidrun.agent.common.events import (
|
13
13
|
InputTextActionEvent,
|
14
14
|
KeyPressActionEvent,
|
@@ -23,13 +23,17 @@ import requests
|
|
23
23
|
import base64
|
24
24
|
|
25
25
|
logger = logging.getLogger("droidrun-tools")
|
26
|
+
PORTAL_DEFAULT_TCP_PORT = 8080
|
26
27
|
|
27
28
|
|
28
29
|
class AdbTools(Tools):
|
29
30
|
"""Core UI interaction tools for Android device control."""
|
30
31
|
|
31
32
|
def __init__(
|
32
|
-
self,
|
33
|
+
self,
|
34
|
+
serial: str | None = None,
|
35
|
+
use_tcp: bool = False,
|
36
|
+
remote_tcp_port: int = PORTAL_DEFAULT_TCP_PORT,
|
33
37
|
) -> None:
|
34
38
|
"""Initialize the AdbTools instance.
|
35
39
|
|
@@ -40,8 +44,7 @@ class AdbTools(Tools):
|
|
40
44
|
"""
|
41
45
|
self.device = adb.device(serial=serial)
|
42
46
|
self.use_tcp = use_tcp
|
43
|
-
self.
|
44
|
-
self.tcp_base_url = f"http://localhost:{tcp_port}"
|
47
|
+
self.remote_tcp_port = remote_tcp_port
|
45
48
|
self.tcp_forwarded = False
|
46
49
|
|
47
50
|
self._ctx = None
|
@@ -55,6 +58,8 @@ class AdbTools(Tools):
|
|
55
58
|
self.memory: List[str] = []
|
56
59
|
# Store all screenshots with timestamps
|
57
60
|
self.screenshots: List[Dict[str, Any]] = []
|
61
|
+
# Trajectory saving level
|
62
|
+
self.save_trajectories = "none"
|
58
63
|
|
59
64
|
# Set up TCP forwarding if requested
|
60
65
|
if self.use_tcp:
|
@@ -69,18 +74,21 @@ class AdbTools(Tools):
|
|
69
74
|
"""
|
70
75
|
try:
|
71
76
|
logger.debug(
|
72
|
-
f"Setting up TCP port forwarding
|
77
|
+
f"Setting up TCP port forwarding for port tcp:{self.remote_tcp_port} on device {self.device.serial}"
|
73
78
|
)
|
74
79
|
# Use adb forward command to set up port forwarding
|
75
|
-
|
76
|
-
self.
|
77
|
-
logger.debug(
|
80
|
+
self.local_tcp_port = self.device.forward_port(self.remote_tcp_port)
|
81
|
+
self.tcp_base_url = f"http://localhost:{self.local_tcp_port}"
|
82
|
+
logger.debug(
|
83
|
+
f"TCP port forwarding set up successfully to {self.tcp_base_url}"
|
84
|
+
)
|
78
85
|
|
79
86
|
# Test the connection with a ping
|
80
87
|
try:
|
81
88
|
response = requests.get(f"{self.tcp_base_url}/ping", timeout=5)
|
82
89
|
if response.status_code == 200:
|
83
90
|
logger.debug("TCP connection test successful")
|
91
|
+
self.tcp_forwarded = True
|
84
92
|
return True
|
85
93
|
else:
|
86
94
|
logger.warning(
|
@@ -105,10 +113,17 @@ class AdbTools(Tools):
|
|
105
113
|
"""
|
106
114
|
try:
|
107
115
|
if self.tcp_forwarded:
|
108
|
-
logger.debug(
|
109
|
-
|
116
|
+
logger.debug(
|
117
|
+
f"Removing TCP port forwarding for port {self.local_tcp_port}"
|
118
|
+
)
|
119
|
+
# remove forwarding
|
120
|
+
cmd = f"killforward:tcp:{self.local_tcp_port}"
|
121
|
+
logger.debug(f"Removing TCP port forwarding: {cmd}")
|
122
|
+
c = self.device.open_transport(cmd)
|
123
|
+
c.close()
|
124
|
+
|
110
125
|
self.tcp_forwarded = False
|
111
|
-
logger.debug(f"TCP port forwarding removed
|
126
|
+
logger.debug(f"TCP port forwarding removed")
|
112
127
|
return True
|
113
128
|
return True
|
114
129
|
except Exception as e:
|
@@ -170,6 +185,7 @@ class AdbTools(Tools):
|
|
170
185
|
except json.JSONDecodeError:
|
171
186
|
return None
|
172
187
|
|
188
|
+
@Tools.ui_action
|
173
189
|
def tap_by_index(self, index: int) -> str:
|
174
190
|
"""
|
175
191
|
Tap on a UI element by its index.
|
@@ -328,6 +344,7 @@ class AdbTools(Tools):
|
|
328
344
|
"""
|
329
345
|
return self.tap_by_index(index)
|
330
346
|
|
347
|
+
@Tools.ui_action
|
331
348
|
def swipe(
|
332
349
|
self,
|
333
350
|
start_x: int,
|
@@ -372,6 +389,7 @@ class AdbTools(Tools):
|
|
372
389
|
print(f"Error: {str(e)}")
|
373
390
|
return False
|
374
391
|
|
392
|
+
@Tools.ui_action
|
375
393
|
def drag(
|
376
394
|
self, start_x: int, start_y: int, end_x: int, end_y: int, duration: float = 3
|
377
395
|
) -> bool:
|
@@ -413,6 +431,7 @@ class AdbTools(Tools):
|
|
413
431
|
print(f"Error: {str(e)}")
|
414
432
|
return False
|
415
433
|
|
434
|
+
@Tools.ui_action
|
416
435
|
def input_text(self, text: str) -> str:
|
417
436
|
"""
|
418
437
|
Input text on the device.
|
@@ -427,59 +446,59 @@ class AdbTools(Tools):
|
|
427
446
|
try:
|
428
447
|
logger.debug(f"Inputting text: {text}")
|
429
448
|
|
430
|
-
if self.use_tcp and self.tcp_forwarded:
|
431
|
-
|
432
|
-
|
433
|
-
|
434
|
-
|
435
|
-
|
436
|
-
|
437
|
-
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
442
|
-
|
443
|
-
|
444
|
-
|
445
|
-
|
446
|
-
|
447
|
-
|
448
|
-
|
449
|
-
else:
|
450
|
-
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
455
|
-
|
449
|
+
# if self.use_tcp and self.tcp_forwarded:
|
450
|
+
# # Use TCP communication
|
451
|
+
# encoded_text = base64.b64encode(text.encode()).decode()
|
452
|
+
|
453
|
+
# payload = {"base64_text": encoded_text}
|
454
|
+
# response = requests.post(
|
455
|
+
# f"{self.tcp_base_url}/keyboard/input",
|
456
|
+
# json=payload,
|
457
|
+
# headers={"Content-Type": "application/json"},
|
458
|
+
# timeout=10,
|
459
|
+
# )
|
460
|
+
|
461
|
+
# logger.debug(
|
462
|
+
# f"Keyboard input TCP response: {response.status_code}, {response.text}"
|
463
|
+
# )
|
464
|
+
|
465
|
+
# if response.status_code != 200:
|
466
|
+
# return f"Error: HTTP request failed with status {response.status_code}: {response.text}"
|
467
|
+
|
468
|
+
# else:
|
469
|
+
# Fallback to content provider method
|
470
|
+
# Save the current keyboard
|
471
|
+
original_ime = self.device.shell(
|
472
|
+
"settings get secure default_input_method"
|
473
|
+
)
|
474
|
+
original_ime = original_ime.strip()
|
456
475
|
|
457
|
-
|
458
|
-
|
476
|
+
# Enable the Droidrun keyboard
|
477
|
+
self.device.shell("ime enable com.droidrun.portal/.DroidrunKeyboardIME")
|
459
478
|
|
460
|
-
|
461
|
-
|
479
|
+
# Set the Droidrun keyboard as the default
|
480
|
+
self.device.shell("ime set com.droidrun.portal/.DroidrunKeyboardIME")
|
462
481
|
|
463
|
-
|
464
|
-
|
482
|
+
# Wait for keyboard to change
|
483
|
+
time.sleep(1)
|
465
484
|
|
466
|
-
|
467
|
-
|
485
|
+
# Encode the text to Base64
|
486
|
+
encoded_text = base64.b64encode(text.encode()).decode()
|
468
487
|
|
469
|
-
|
470
|
-
|
488
|
+
cmd = f'content insert --uri "content://com.droidrun.portal/keyboard/input" --bind base64_text:s:"{encoded_text}"'
|
489
|
+
self.device.shell(cmd)
|
471
490
|
|
472
|
-
|
473
|
-
|
491
|
+
# Wait for text input to complete
|
492
|
+
time.sleep(0.5)
|
474
493
|
|
475
|
-
|
476
|
-
|
477
|
-
|
494
|
+
# Restore the original keyboard
|
495
|
+
if original_ime and "com.droidrun.portal" not in original_ime:
|
496
|
+
self.device.shell(f"ime set {original_ime}")
|
478
497
|
|
479
|
-
|
480
|
-
|
481
|
-
|
482
|
-
|
498
|
+
logger.debug(
|
499
|
+
f"Text input completed: {text[:50]}{'...' if len(text) > 50 else ''}"
|
500
|
+
)
|
501
|
+
return f"Text input completed: {text[:50]}{'...' if len(text) > 50 else ''}"
|
483
502
|
|
484
503
|
if self._ctx:
|
485
504
|
input_event = InputTextActionEvent(
|
@@ -501,6 +520,7 @@ class AdbTools(Tools):
|
|
501
520
|
except Exception as e:
|
502
521
|
return f"Error sending text input: {str(e)}"
|
503
522
|
|
523
|
+
@Tools.ui_action
|
504
524
|
def back(self) -> str:
|
505
525
|
"""
|
506
526
|
Go back on the current view.
|
@@ -523,6 +543,7 @@ class AdbTools(Tools):
|
|
523
543
|
except ValueError as e:
|
524
544
|
return f"Error: {str(e)}"
|
525
545
|
|
546
|
+
@Tools.ui_action
|
526
547
|
def press_key(self, keycode: int) -> str:
|
527
548
|
"""
|
528
549
|
Press a key on the Android device.
|
@@ -561,6 +582,7 @@ class AdbTools(Tools):
|
|
561
582
|
except ValueError as e:
|
562
583
|
return f"Error: {str(e)}"
|
563
584
|
|
585
|
+
@Tools.ui_action
|
564
586
|
def start_app(self, package: str, activity: str | None = None) -> str:
|
565
587
|
"""
|
566
588
|
Start an app on the device.
|
@@ -634,48 +656,13 @@ class AdbTools(Tools):
|
|
634
656
|
try:
|
635
657
|
logger.debug("Taking screenshot")
|
636
658
|
|
637
|
-
|
638
|
-
|
639
|
-
|
640
|
-
|
641
|
-
|
642
|
-
|
643
|
-
|
644
|
-
# Check if response has the expected format with data field
|
645
|
-
if isinstance(tcp_response, dict) and "data" in tcp_response:
|
646
|
-
base64_data = tcp_response["data"]
|
647
|
-
try:
|
648
|
-
# Decode base64 to get image bytes
|
649
|
-
image_bytes = base64.b64decode(base64_data)
|
650
|
-
img_format = "PNG" # Assuming PNG format from TCP endpoint
|
651
|
-
logger.debug("Screenshot taken via TCP")
|
652
|
-
except Exception as e:
|
653
|
-
raise ValueError(
|
654
|
-
f"Failed to decode base64 screenshot data: {str(e)}"
|
655
|
-
)
|
656
|
-
else:
|
657
|
-
# Fallback: assume direct base64 format
|
658
|
-
try:
|
659
|
-
image_bytes = base64.b64decode(tcp_response)
|
660
|
-
img_format = "PNG"
|
661
|
-
logger.debug("Screenshot taken via TCP (direct base64)")
|
662
|
-
except Exception as e:
|
663
|
-
raise ValueError(
|
664
|
-
f"Failed to decode screenshot response: {str(e)}"
|
665
|
-
)
|
666
|
-
else:
|
667
|
-
raise ValueError(
|
668
|
-
f"HTTP request failed with status {response.status_code}: {response.text}"
|
669
|
-
)
|
670
|
-
|
671
|
-
else:
|
672
|
-
# Fallback to ADB screenshot method
|
673
|
-
img = self.device.screenshot()
|
674
|
-
img_buf = io.BytesIO()
|
675
|
-
img_format = "PNG"
|
676
|
-
img.save(img_buf, format=img_format)
|
677
|
-
image_bytes = img_buf.getvalue()
|
678
|
-
logger.debug("Screenshot taken via ADB")
|
659
|
+
# Fallback to ADB screenshot method
|
660
|
+
img = self.device.screenshot()
|
661
|
+
img_buf = io.BytesIO()
|
662
|
+
img_format = "PNG"
|
663
|
+
img.save(img_buf, format=img_format)
|
664
|
+
image_bytes = img_buf.getvalue()
|
665
|
+
logger.debug("Screenshot taken via ADB")
|
679
666
|
|
680
667
|
# Store screenshot with timestamp
|
681
668
|
self.screenshots.append(
|
@@ -710,6 +697,7 @@ class AdbTools(Tools):
|
|
710
697
|
except ValueError as e:
|
711
698
|
raise ValueError(f"Error listing packages: {str(e)}")
|
712
699
|
|
700
|
+
@Tools.ui_action
|
713
701
|
def complete(self, success: bool, reason: str = ""):
|
714
702
|
"""
|
715
703
|
Mark the task as finished.
|
droidrun/tools/tools.py
CHANGED
@@ -2,6 +2,8 @@ from abc import ABC, abstractmethod
|
|
2
2
|
from typing import List, Optional, Dict, Any
|
3
3
|
import logging
|
4
4
|
from typing import Tuple, Dict, Callable, Any, Optional
|
5
|
+
from functools import wraps
|
6
|
+
import sys
|
5
7
|
|
6
8
|
# Get a logger for this module
|
7
9
|
logger = logging.getLogger(__name__)
|
@@ -13,6 +15,32 @@ class Tools(ABC):
|
|
13
15
|
This class provides a common interface for all tools to implement.
|
14
16
|
"""
|
15
17
|
|
18
|
+
@staticmethod
|
19
|
+
def ui_action(func):
|
20
|
+
""""
|
21
|
+
Decorator to capture screenshots and UI states for actions that modify the UI.
|
22
|
+
"""
|
23
|
+
@wraps(func)
|
24
|
+
def wrapper(*args, **kwargs):
|
25
|
+
self = args[0]
|
26
|
+
result = func(*args, **kwargs)
|
27
|
+
|
28
|
+
# Check if save_trajectories attribute exists and is set to "action"
|
29
|
+
if hasattr(self, 'save_trajectories') and self.save_trajectories == "action":
|
30
|
+
frame = sys._getframe(1)
|
31
|
+
caller_globals = frame.f_globals
|
32
|
+
|
33
|
+
step_screenshots = caller_globals.get('step_screenshots')
|
34
|
+
step_ui_states = caller_globals.get('step_ui_states')
|
35
|
+
|
36
|
+
if step_screenshots is not None:
|
37
|
+
step_screenshots.append(self.take_screenshot()[1])
|
38
|
+
if step_ui_states is not None:
|
39
|
+
step_ui_states.append(self.get_state())
|
40
|
+
|
41
|
+
return result
|
42
|
+
return wrapper
|
43
|
+
|
16
44
|
@abstractmethod
|
17
45
|
def get_state(self) -> Dict[str, Any]:
|
18
46
|
"""
|
@@ -1,6 +1,6 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: droidrun
|
3
|
-
Version: 0.3.
|
3
|
+
Version: 0.3.5
|
4
4
|
Summary: A framework for controlling Android devices through LLM agents
|
5
5
|
Project-URL: Homepage, https://github.com/droidrun/droidrun
|
6
6
|
Project-URL: Bug Tracker, https://github.com/droidrun/droidrun/issues
|
@@ -46,7 +46,6 @@ Requires-Dist: posthog==6.0.2
|
|
46
46
|
Requires-Dist: pydantic>=2.0.0
|
47
47
|
Requires-Dist: python-dotenv>=1.0.0
|
48
48
|
Requires-Dist: rich>=13.0.0
|
49
|
-
Requires-Dist: typing-extensions
|
50
49
|
Provides-Extra: dev
|
51
50
|
Requires-Dist: bandit>=1.7.0; extra == 'dev'
|
52
51
|
Requires-Dist: black>=23.0.0; extra == 'dev'
|
@@ -1,9 +1,9 @@
|
|
1
1
|
droidrun/__init__.py,sha256=Cqt4NXZ-753220dlRZXglMkFT2ygcXSErMmqupGsi9A,622
|
2
2
|
droidrun/__main__.py,sha256=78o1Wr_Z-NrZy9yLWmEfNfRRhAiJGBr4Xi3lmbgkx3w,105
|
3
|
-
droidrun/portal.py,sha256=
|
3
|
+
droidrun/portal.py,sha256=Bg3kVoNLJ2bTdk-4VuUr_i77yWJT2ZNLfT5WMumnZV8,4679
|
4
4
|
droidrun/agent/__init__.py,sha256=4SqTJeGDvr_wT8rtN9J8hnN6P-pae663mkYr-JmzH4w,208
|
5
5
|
droidrun/agent/codeact/__init__.py,sha256=ZLDGT_lTTzyNm7pcBzdyRIGHJ2ZgbInJdhXZRbJLhSQ,278
|
6
|
-
droidrun/agent/codeact/codeact_agent.py,sha256=
|
6
|
+
droidrun/agent/codeact/codeact_agent.py,sha256=6Hltg6J4mdb5pOr5sFQVY-6SMWIcxWp6RZjct5nkiCc,16506
|
7
7
|
droidrun/agent/codeact/events.py,sha256=skCfZ-5SR0YhhzZVxx8_VkUjfILk8rCv47k9pHNYhdc,634
|
8
8
|
droidrun/agent/codeact/prompts.py,sha256=28HflWMNkC1ky0hGCzAxhJftjU2IIU1ZRUfya3S7M6I,1006
|
9
9
|
droidrun/agent/common/default.py,sha256=P07el-PrHsoqQMYsYxSSln6mFl-QY75vzwp1Dll_XmY,259
|
@@ -13,42 +13,42 @@ droidrun/agent/context/agent_persona.py,sha256=Mxd4HTyirWD-aqNlka1hQBdS-0I-lXJr2
|
|
13
13
|
droidrun/agent/context/context_injection_manager.py,sha256=sA33q2KPtX_4Yap8wM11T6ewlZC_0FIbKPEc400SHrE,2188
|
14
14
|
droidrun/agent/context/episodic_memory.py,sha256=1ImeR3jAWOpKwkQt3bMlXVOBiQbIli5fBIlBq2waREQ,394
|
15
15
|
droidrun/agent/context/reflection.py,sha256=0hJluOz0hTlHHhReKpIJ9HU5aJbaJsvrjMfraQ84D-M,652
|
16
|
-
droidrun/agent/context/task_manager.py,sha256=
|
16
|
+
droidrun/agent/context/task_manager.py,sha256=9CDax1G48DRcqPm1JYy9UiLI7wPsF34eqAM0dRDCC1k,4992
|
17
17
|
droidrun/agent/context/personas/__init__.py,sha256=oSRa8g_xngX7JPIRPu7fLO33m3r7fdEQzIuORuqcw5M,232
|
18
18
|
droidrun/agent/context/personas/app_starter.py,sha256=dHeknznxGEPJ7S6VPyEG_MB-HvAvQwUOnRWaShaV8Xo,1585
|
19
19
|
droidrun/agent/context/personas/big_agent.py,sha256=Gl_y4ykz3apGc203-KG2UbSOwf7gDUiWh7GOVyiLn-Y,5091
|
20
20
|
droidrun/agent/context/personas/default.py,sha256=Xm07YCWoKjvlHAbQRtzE3vn7BVcz6wYcSVeg4FiojJQ,5060
|
21
21
|
droidrun/agent/context/personas/ui_expert.py,sha256=j0OKfN1jQSrREHcVeomMTDPCWLsZVX4aeuWN4Y-x3z0,4739
|
22
22
|
droidrun/agent/droid/__init__.py,sha256=3BfUVZiUQ8ATAJ_JmqQZQx53WoERRpQ4AyHW5WOgbRI,297
|
23
|
-
droidrun/agent/droid/droid_agent.py,sha256=
|
24
|
-
droidrun/agent/droid/events.py,sha256=
|
23
|
+
droidrun/agent/droid/droid_agent.py,sha256=nKr8gV6aV_S5T4llhKokSBqL2uKF9ldxcnE29FOGTL8,16737
|
24
|
+
droidrun/agent/droid/events.py,sha256=hjYpWcSffqP83rNv_GyOEc3CNSrdvlVPdUkaRU6QDJc,722
|
25
25
|
droidrun/agent/oneflows/reflector.py,sha256=I_tE0PBjvwWbS6SA8Qd41etxJglFgn8oScuKUxc9LEE,11621
|
26
26
|
droidrun/agent/planner/__init__.py,sha256=Fu0Ewtd-dIRLgHIL1DB_9EEKvQS_f1vjB8jgO5TbJXg,364
|
27
27
|
droidrun/agent/planner/events.py,sha256=oyt2FNrA2uVyUeVT65-N0AC6sWBFxSnwNEqWtnRYoFM,390
|
28
|
-
droidrun/agent/planner/planner_agent.py,sha256=
|
28
|
+
droidrun/agent/planner/planner_agent.py,sha256=KELaoNOcoyB0He0B_A4iCi-hFyOTVO-s4-UclbQ7YjM,10910
|
29
29
|
droidrun/agent/planner/prompts.py,sha256=Ci7Oeu3J4TAhx-tKGPZ9l6Wb3a81FSqC8cWW4jW73HI,6046
|
30
30
|
droidrun/agent/utils/__init__.py,sha256=JK6ygRjw7gzcQSG0HBEYLoVGH54QQAxJJ7HpIS5mgyc,44
|
31
31
|
droidrun/agent/utils/async_utils.py,sha256=IQBcWPwevm89B7R_UdMXk0unWeNCBA232b5kQGqoxNI,336
|
32
|
-
droidrun/agent/utils/chat_utils.py,sha256=
|
33
|
-
droidrun/agent/utils/executer.py,sha256
|
32
|
+
droidrun/agent/utils/chat_utils.py,sha256=KzmiNasXb9d0qH12ylRsWooKV4_9LXy_QifaD_xf_O0,12726
|
33
|
+
droidrun/agent/utils/executer.py,sha256=-iwdZpmpF0w116D7A_eDgeV0ZNXSuotVgBkM5lc0BNI,5202
|
34
34
|
droidrun/agent/utils/llm_picker.py,sha256=16tNkNhEM9gD_uivzxLvuaa6s0Tz7Igu-3fxMP2lAtY,5968
|
35
35
|
droidrun/agent/utils/trajectory.py,sha256=PU3nI3Zru580_bK0TvqUaf-5kiWvj6hFoedXkDgTqdc,17047
|
36
36
|
droidrun/cli/__init__.py,sha256=DuwSRtZ8WILPd-nf-fZ7BaBsRgtofoInOF3JtJ9wag0,167
|
37
37
|
droidrun/cli/logs.py,sha256=PsT_VbnOa_sOLXK4KkEJk4AsYCpscqrVoryMmLVwPG0,9714
|
38
|
-
droidrun/cli/main.py,sha256=
|
38
|
+
droidrun/cli/main.py,sha256=3RWvY19zY9ewzcasndxpul3BUPpMq4yhrtnuAIBkufM,18311
|
39
39
|
droidrun/macro/__init__.py,sha256=333sMt19mA8sfQa4qPKbbCmr8Ej3nvpdxGXUZtVTEqM,344
|
40
40
|
droidrun/macro/__main__.py,sha256=-zj42Bj7309oLPZbNsxZeNwIDaEe7Th1I4zF8yAHasw,193
|
41
|
-
droidrun/macro/cli.py,sha256=
|
41
|
+
droidrun/macro/cli.py,sha256=GaL1wVWVE_AT0OxHgOJyW-q_kVu4PQ76KZKeYMQdoEk,9057
|
42
42
|
droidrun/macro/replay.py,sha256=q_3ZcHVjvsdDfS2xyt_vuuwXGt9_1t38JD1cPsjzIfU,10764
|
43
43
|
droidrun/telemetry/__init__.py,sha256=D4Mp02iGJH2Tjpv42Bzyo6_WC3NWj9Qy9hQPWFaCkhA,234
|
44
|
-
droidrun/telemetry/events.py,sha256=
|
44
|
+
droidrun/telemetry/events.py,sha256=FtPMMDAhxvOiCssJLCCr5FX93ulnPCbiFMhkPh8NLw4,517
|
45
45
|
droidrun/telemetry/tracker.py,sha256=Ljue6zivX8KnadXI9DivrayuWxAnUwbJKvCvNtY1Y4Y,2717
|
46
46
|
droidrun/tools/__init__.py,sha256=9ReauavtSKDQG9ya9_Fr9O0TQnDFixgOPaP5n82_iEk,271
|
47
|
-
droidrun/tools/adb.py,sha256=
|
47
|
+
droidrun/tools/adb.py,sha256=zFnVydQi42GZ1vh-4HT79dCQVBeCS6VObywyaIUUCmw,40832
|
48
48
|
droidrun/tools/ios.py,sha256=imzojiS6gqz4IKexUEz1ga7-flSOaC5QRpHIJTwcgSQ,21807
|
49
|
-
droidrun/tools/tools.py,sha256=
|
50
|
-
droidrun-0.3.
|
51
|
-
droidrun-0.3.
|
52
|
-
droidrun-0.3.
|
53
|
-
droidrun-0.3.
|
54
|
-
droidrun-0.3.
|
49
|
+
droidrun/tools/tools.py,sha256=rqDe2gRyR45HVM15SwsW1aCTVZo5eleTxlh2hGqCHys,4785
|
50
|
+
droidrun-0.3.5.dist-info/METADATA,sha256=k4-cv0Vud2ol8yqc1qkKWWxp2OrGxL12yeS-YUpc4K4,6685
|
51
|
+
droidrun-0.3.5.dist-info/WHEEL,sha256=qtCwoSJWgHk21S1Kb4ihdzI2rlJ1ZKaIurTj_ngOhyQ,87
|
52
|
+
droidrun-0.3.5.dist-info/entry_points.txt,sha256=o259U66js8TIybQ7zs814Oe_LQ_GpZsp6a9Cr-xm5zE,51
|
53
|
+
droidrun-0.3.5.dist-info/licenses/LICENSE,sha256=s-uxn9qChu-kFdRXUp6v_0HhsaJ_5OANmfNOFVm2zdk,1069
|
54
|
+
droidrun-0.3.5.dist-info/RECORD,,
|
File without changes
|
File without changes
|
File without changes
|