droidrun 0.2.0__py3-none-any.whl → 0.3.1__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.
Files changed (55) hide show
  1. droidrun/__init__.py +16 -11
  2. droidrun/__main__.py +1 -1
  3. droidrun/adb/__init__.py +3 -3
  4. droidrun/adb/device.py +1 -1
  5. droidrun/adb/manager.py +2 -2
  6. droidrun/agent/__init__.py +6 -0
  7. droidrun/agent/codeact/__init__.py +2 -4
  8. droidrun/agent/codeact/codeact_agent.py +330 -235
  9. droidrun/agent/codeact/events.py +12 -20
  10. droidrun/agent/codeact/prompts.py +0 -52
  11. droidrun/agent/common/default.py +5 -0
  12. droidrun/agent/common/events.py +4 -0
  13. droidrun/agent/context/__init__.py +23 -0
  14. droidrun/agent/context/agent_persona.py +15 -0
  15. droidrun/agent/context/context_injection_manager.py +66 -0
  16. droidrun/agent/context/episodic_memory.py +15 -0
  17. droidrun/agent/context/personas/__init__.py +11 -0
  18. droidrun/agent/context/personas/app_starter.py +44 -0
  19. droidrun/agent/context/personas/default.py +95 -0
  20. droidrun/agent/context/personas/extractor.py +52 -0
  21. droidrun/agent/context/personas/ui_expert.py +107 -0
  22. droidrun/agent/context/reflection.py +20 -0
  23. droidrun/agent/context/task_manager.py +124 -0
  24. droidrun/agent/droid/__init__.py +2 -2
  25. droidrun/agent/droid/droid_agent.py +269 -325
  26. droidrun/agent/droid/events.py +28 -0
  27. droidrun/agent/oneflows/reflector.py +265 -0
  28. droidrun/agent/planner/__init__.py +2 -4
  29. droidrun/agent/planner/events.py +9 -13
  30. droidrun/agent/planner/planner_agent.py +288 -0
  31. droidrun/agent/planner/prompts.py +33 -53
  32. droidrun/agent/utils/__init__.py +3 -0
  33. droidrun/agent/utils/async_utils.py +1 -40
  34. droidrun/agent/utils/chat_utils.py +265 -48
  35. droidrun/agent/utils/executer.py +49 -14
  36. droidrun/agent/utils/llm_picker.py +14 -10
  37. droidrun/agent/utils/trajectory.py +184 -0
  38. droidrun/cli/__init__.py +1 -1
  39. droidrun/cli/logs.py +283 -0
  40. droidrun/cli/main.py +364 -441
  41. droidrun/tools/__init__.py +5 -10
  42. droidrun/tools/{actions.py → adb.py} +381 -412
  43. droidrun/tools/ios.py +596 -0
  44. droidrun/tools/tools.py +95 -0
  45. droidrun-0.3.1.dist-info/METADATA +150 -0
  46. droidrun-0.3.1.dist-info/RECORD +50 -0
  47. droidrun/agent/planner/task_manager.py +0 -355
  48. droidrun/agent/planner/workflow.py +0 -371
  49. droidrun/tools/device.py +0 -29
  50. droidrun/tools/loader.py +0 -60
  51. droidrun-0.2.0.dist-info/METADATA +0 -373
  52. droidrun-0.2.0.dist-info/RECORD +0 -32
  53. {droidrun-0.2.0.dist-info → droidrun-0.3.1.dist-info}/WHEEL +0 -0
  54. {droidrun-0.2.0.dist-info → droidrun-0.3.1.dist-info}/entry_points.txt +0 -0
  55. {droidrun-0.2.0.dist-info → droidrun-0.3.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,40 +3,71 @@ DroidAgent - A wrapper class that coordinates the planning and execution of task
3
3
  to achieve a user's goal on an Android device.
4
4
  """
5
5
 
6
- import asyncio
7
6
  import logging
8
- from typing import Dict, Any, List, Tuple
7
+ from typing import List
9
8
 
10
- from llama_index.core.base.llms.types import ChatMessage
11
9
  from llama_index.core.llms.llm import LLM
12
- from llama_index.core.memory import ChatMemoryBuffer
13
- from ..codeact import CodeActAgent
14
- from ..planner import PlannerAgent, TaskManager
15
- from ..utils.executer import SimpleCodeExecutor
16
- from ...tools import Tools
10
+ from llama_index.core.workflow import step, StartEvent, StopEvent, Workflow, Context
11
+ from droidrun.agent.droid.events import *
12
+ from droidrun.agent.codeact import CodeActAgent
13
+ from droidrun.agent.codeact.events import EpisodicMemoryEvent
14
+ from droidrun.agent.planner import PlannerAgent
15
+ from droidrun.agent.context.task_manager import TaskManager
16
+ from droidrun.agent.utils.trajectory import Trajectory
17
+ from droidrun.tools import Tools, describe_tools
18
+ from droidrun.agent.common.events import ScreenshotEvent
19
+ from droidrun.agent.common.default import MockWorkflow
20
+ from droidrun.agent.context import ContextInjectionManager
21
+ from droidrun.agent.context.agent_persona import AgentPersona
22
+ from droidrun.agent.context.personas import DEFAULT
23
+ from droidrun.agent.oneflows.reflector import Reflector
24
+
17
25
 
18
26
  logger = logging.getLogger("droidrun")
19
27
 
20
- class DroidAgent:
28
+ class DroidAgent(Workflow):
21
29
  """
22
- A wrapper class that coordinates between PlannerAgent (creates plans) and
30
+ A wrapper class that coordinates between PlannerAgent (creates plans) and
23
31
  CodeActAgent (executes tasks) to achieve a user's goal.
24
32
  """
25
33
 
34
+ @staticmethod
35
+ def _configure_default_logging(debug: bool = False):
36
+ """
37
+ Configure default logging for DroidAgent if no handlers are present.
38
+ This ensures logs are visible when using DroidAgent directly.
39
+ """
40
+ # Only configure if no handlers exist (avoid duplicate configuration)
41
+ if not logger.handlers:
42
+ # Create a console handler
43
+ handler = logging.StreamHandler()
44
+
45
+ # Set format
46
+ if debug:
47
+ formatter = logging.Formatter("%(asctime)s %(levelname)s: %(message)s", "%H:%M:%S")
48
+ else:
49
+ formatter = logging.Formatter("%(message)s")
50
+
51
+ handler.setFormatter(formatter)
52
+ logger.addHandler(handler)
53
+ logger.setLevel(logging.DEBUG if debug else logging.INFO)
54
+ logger.propagate = False
55
+
26
56
  def __init__(
27
57
  self,
28
58
  goal: str,
29
59
  llm: LLM,
30
- tools_instance: 'Tools' = None,
31
- tool_list: Dict[str, Any] = None,
60
+ tools: Tools,
61
+ personas: List[AgentPersona] = [DEFAULT],
32
62
  max_steps: int = 15,
33
- vision: bool = False,
34
63
  timeout: int = 1000,
35
- max_retries: int = 3,
36
- reasoning: bool = True,
64
+ vision: bool = False,
65
+ reasoning: bool = False,
66
+ reflection: bool = False,
37
67
  enable_tracing: bool = False,
38
68
  debug: bool = False,
39
- device_serial: str = None,
69
+ save_trajectories: bool = False,
70
+ *args,
40
71
  **kwargs
41
72
  ):
42
73
  """
@@ -45,184 +76,85 @@ class DroidAgent:
45
76
  Args:
46
77
  goal: The user's goal or command to execute
47
78
  llm: The language model to use for both agents
48
- tools_instance: An instance of the Tools class
49
- tool_list: Dictionary of available tools
50
79
  max_steps: Maximum number of steps for both agents
51
- vision: Whether to enable vision capabilities
52
80
  timeout: Timeout for agent execution in seconds
53
- max_retries: Maximum number of retries for failed tasks
54
81
  reasoning: Whether to use the PlannerAgent for complex reasoning (True)
55
82
  or send tasks directly to CodeActAgent (False)
83
+ reflection: Whether to reflect on steps the CodeActAgent did to give the PlannerAgent advice
56
84
  enable_tracing: Whether to enable Arize Phoenix tracing
57
85
  debug: Whether to enable verbose debug logging
58
- device_serial: Target Android device serial number
59
86
  **kwargs: Additional keyword arguments to pass to the agents
60
87
  """
88
+ super().__init__(timeout=timeout ,*args,**kwargs)
89
+
90
+ # Configure default logging if not already configured
91
+ self._configure_default_logging(debug=debug)
92
+
93
+ # Setup global tracing first if enabled
94
+ if enable_tracing:
95
+ try:
96
+ from llama_index.core import set_global_handler
97
+ set_global_handler("arize_phoenix")
98
+ logger.info("🔍 Arize Phoenix tracing enabled globally")
99
+ except ImportError:
100
+ logger.warning("⚠️ Arize Phoenix package not found, tracing disabled")
101
+ enable_tracing = False
102
+
61
103
  self.goal = goal
62
104
  self.llm = llm
63
- self.max_steps = max_steps
64
105
  self.vision = vision
106
+ self.max_steps = max_steps
107
+ self.max_codeact_steps = max_steps
65
108
  self.timeout = timeout
66
- self.max_retries = max_retries
67
- self.task_manager = TaskManager()
68
109
  self.reasoning = reasoning
110
+ self.reflection = reflection
69
111
  self.debug = debug
70
- self.device_serial = device_serial
71
-
72
- logger.info("🤖 Initializing DroidAgent wrapper...")
73
-
74
- self.tools_instance = tools_instance
75
- self.tool_list = tool_list
76
-
77
- # Ensure remember tool is in the tool_list if available
78
- if hasattr(tools_instance, 'remember') and 'remember' not in tool_list:
79
- logger.debug("📝 Adding 'remember' tool to the available tools")
80
- tool_list['remember'] = tools_instance.remember
81
-
82
- # Create code executor
83
- logger.debug("🔧 Initializing Code Executor...")
84
- loop = asyncio.get_event_loop()
85
- self.executor = SimpleCodeExecutor(
86
- loop=loop,
87
- locals={},
88
- tools=tool_list,
89
- globals={"__builtins__": __builtins__}
90
- )
91
-
92
- # Create memory buffer for the planning agent if reasoning is enabled
93
- if self.reasoning:
94
- self.planning_memory = ChatMemoryBuffer.from_defaults(llm=llm)
112
+
113
+ self.event_counter = 0
114
+ self.save_trajectories = save_trajectories
95
115
 
96
- # Create CodeActAgent
97
- logger.info("🧠 Initializing CodeAct Agent...")
98
- self.codeact_agent = CodeActAgent(
99
- llm=llm,
100
- code_execute_fn=self.executor.execute,
101
- available_tools=tool_list.values(),
102
- tools=tools_instance,
103
- max_steps=999999,
104
- vision=vision,
105
- debug=debug,
106
- timeout=timeout
107
- )
116
+ self.trajectory = Trajectory()
117
+ self.task_manager = TaskManager()
118
+ self.task_iter = None
119
+ self.cim = ContextInjectionManager(personas=personas)
120
+ self.current_episodic_memory = None
121
+
122
+ logger.info("🤖 Initializing DroidAgent...")
108
123
 
124
+ self.tool_list = describe_tools(tools)
125
+ self.tools_instance = tools
126
+
127
+
109
128
  if self.reasoning:
110
129
  logger.info("📝 Initializing Planner Agent...")
111
130
  self.planner_agent = PlannerAgent(
112
131
  goal=goal,
113
132
  llm=llm,
114
- agent=self.codeact_agent,
115
- tools_instance=tools_instance,
133
+ vision=vision,
134
+ personas=personas,
135
+ task_manager=self.task_manager,
136
+ tools_instance=tools,
116
137
  timeout=timeout,
117
- max_retries=max_retries,
118
- enable_tracing=enable_tracing,
119
138
  debug=debug
120
139
  )
140
+ self.add_workflows(planner_agent=self.planner_agent)
141
+ self.max_codeact_steps = 5
142
+
143
+ if self.reflection:
144
+ self.reflector = Reflector(llm=llm, debug=debug)
121
145
 
122
- # Give task manager to the planner
123
- self.planner_agent.task_manager = self.task_manager
124
146
  else:
125
147
  logger.debug("🚫 Planning disabled - will execute tasks directly with CodeActAgent")
126
148
  self.planner_agent = None
127
149
 
128
150
  logger.info("✅ DroidAgent initialized successfully.")
129
151
 
130
- async def _get_plan_from_planner(self) -> List[Dict]:
131
- """
132
- Get a plan (list of tasks) from the PlannerAgent.
133
-
134
- Returns:
135
- List of task dictionaries
136
- """
137
- logger.info("📋 Planning steps to accomplish the goal...")
138
-
139
- # Create system and user messages
140
- system_msg = ChatMessage(role="system", content=self.planner_agent.system_prompt)
141
- user_msg = ChatMessage(role="user", content=self.planner_agent.user_prompt)
142
-
143
- # Check if we have task history to add to the prompt
144
- task_history = ""
145
- # Use the persistent task history methods to get ALL completed and failed tasks
146
- completed_tasks = self.task_manager.get_all_completed_tasks()
147
- failed_tasks = self.task_manager.get_all_failed_tasks()
148
-
149
- # Show any remembered information in task history
150
- remembered_info = ""
151
- if hasattr(self.tools_instance, 'memory') and self.tools_instance.memory:
152
- remembered_info = "\n### Remembered Information:\n"
153
- for idx, item in enumerate(self.tools_instance.memory, 1):
154
- remembered_info += f"{idx}. {item}\n"
155
-
156
- if completed_tasks or failed_tasks or remembered_info:
157
- task_history = "### Task Execution History:\n"
158
-
159
- if completed_tasks:
160
- task_history += "✅ Completed Tasks:\n"
161
- for task in completed_tasks:
162
- task_history += f"- {task['description']}\n"
163
-
164
- if failed_tasks:
165
- task_history += "\n❌ Failed Tasks:\n"
166
- for task in failed_tasks:
167
- failure_reason = task.get('failure_reason', 'Unknown reason')
168
- task_history += f"- {task['description']} (Failed: {failure_reason})\n"
169
-
170
- if remembered_info:
171
- task_history += remembered_info
172
-
173
- # Add a reminder to use this information
174
- task_history += "\n⚠️ Please use the above information in your planning. For example, if specific dates or locations were found, include them explicitly in your next tasks instead of just referring to 'the dates' or 'the location'.\n"
175
-
176
- # Append task history to user prompt
177
- user_msg = ChatMessage(
178
- role="user",
179
- content=f"{self.planner_agent.user_prompt}\n\n{task_history}\n\nPlease consider the above task history and discovered information when creating your next plan. Incorporate specific data (dates, locations, etc.) directly into tasks rather than referring to them generally. Remember that previously completed or failed tasks will not be repeated."
180
- )
181
-
182
- # Create message list
183
- messages = [system_msg, user_msg]
184
- if self.debug:
185
- logger.debug(f"Sending {len(messages)} messages to planner: {[msg.role for msg in messages]}")
186
-
187
- # Get response from LLM
188
- llm_response = await self.planner_agent._get_llm_response(messages)
189
- code, thoughts = self.planner_agent._extract_code_and_thought(llm_response.message.content)
190
-
191
- # Execute the planning code (which should call set_tasks)
192
- if code:
193
- try:
194
- planning_tools = {
195
- "set_tasks": self.task_manager.set_tasks,
196
- "add_task": self.task_manager.add_task,
197
- "get_all_tasks": self.task_manager.get_all_tasks,
198
- "clear_tasks": self.task_manager.clear_tasks,
199
- "complete_goal": self.task_manager.complete_goal
200
- }
201
- planning_executor = SimpleCodeExecutor(
202
- loop=asyncio.get_event_loop(),
203
- globals={},
204
- locals={},
205
- tools=planning_tools
206
- )
207
- await planning_executor.execute(code)
208
- except Exception as e:
209
- logger.error(f"Error executing planning code: {e}")
210
- # If there's an error, create a simple default task
211
- self.task_manager.set_tasks([f"Achieve the goal: {self.goal}"])
212
-
213
- # Get and display the tasks
214
- tasks = self.task_manager.get_all_tasks()
215
- if tasks:
216
- logger.info("📝 Plan created:")
217
- for i, task in enumerate(tasks, 1):
218
- if task["status"] == self.task_manager.STATUS_PENDING:
219
- logger.info(f" {i}. {task['description']}")
220
- else:
221
- logger.warning("No tasks were generated in the plan")
222
-
223
- return tasks
224
-
225
- async def _execute_task_with_codeact(self, task: Dict) -> Tuple[bool, str]:
152
+ @step
153
+ async def execute_task(
154
+ self,
155
+ ctx: Context,
156
+ ev: CodeActExecuteEvent
157
+ ) -> CodeActResultEvent:
226
158
  """
227
159
  Execute a single task using the CodeActAgent.
228
160
 
@@ -232,59 +164,145 @@ class DroidAgent:
232
164
  Returns:
233
165
  Tuple of (success, reason)
234
166
  """
235
- task_description = task["description"]
236
- logger.info(f"🔧 Executing task: {task_description}")
237
-
238
- # Update task status
239
- task["status"] = self.task_manager.STATUS_ATTEMPTING
240
-
241
- # Run the CodeActAgent
167
+ task: Task = ev.task
168
+ reflection = ev.reflection if ev.reflection is not None else None
169
+ persona = self.cim.get_persona(task.agent_type)
170
+
171
+ logger.info(f"🔧 Executing task: {task.description}")
172
+
242
173
  try:
243
- # Reset the tools finished flag before execution
244
- self.tools_instance.finished = False
245
- self.tools_instance.success = None
246
- self.tools_instance.reason = None
247
-
248
- # Execute the CodeActAgent with the task description as input
249
- # Pass input as a keyword argument, not as a dictionary
250
- result = await self.codeact_agent.run(input=task_description)
174
+ codeact_agent = CodeActAgent(
175
+ llm=self.llm,
176
+ persona=persona,
177
+ vision=self.vision,
178
+ max_steps=self.max_codeact_steps,
179
+ all_tools_list=self.tool_list,
180
+ tools_instance=self.tools_instance,
181
+ debug=self.debug,
182
+ timeout=self.timeout,
183
+ )
184
+
185
+ handler = codeact_agent.run(
186
+ input=task.description,
187
+ remembered_info=self.tools_instance.memory,
188
+ reflection=reflection
189
+ )
251
190
 
252
- # Check if the tools instance was marked as finished by the 'complete' function
253
- if self.tools_instance.finished:
254
- if self.tools_instance.success:
255
- task["status"] = self.task_manager.STATUS_COMPLETED
256
- if self.debug:
257
- logger.debug(f"Task completed successfully: {self.tools_instance.reason}")
258
- return True, self.tools_instance.reason or "Task completed successfully"
259
- else:
260
- task["status"] = self.task_manager.STATUS_FAILED
261
- task["failure_reason"] = self.tools_instance.reason or "Task failed without specific reason"
262
- logger.warning(f"Task failed: {task['failure_reason']}")
263
- return False, self.tools_instance.reason or "Task failed without specific reason"
191
+ async for nested_ev in handler.stream_events():
192
+ self.handle_stream_event(nested_ev, ctx)
193
+
194
+ result = await handler
195
+
264
196
 
265
- # If tools instance wasn't marked as finished, check the result directly
266
- if result and isinstance(result, dict) and "success" in result and result["success"]:
267
- task["status"] = self.task_manager.STATUS_COMPLETED
268
- if self.debug:
269
- logger.debug(f"Task completed with result: {result}")
270
- return True, result.get("reason", "Task completed successfully")
197
+ if "success" in result and result["success"]:
198
+ return CodeActResultEvent(success=True, reason=result["reason"], task=task, steps=result["codeact_steps"])
271
199
  else:
272
- failure_reason = result.get("reason", "Unknown failure") if isinstance(result, dict) else "Task execution failed"
273
- task["status"] = self.task_manager.STATUS_FAILED
274
- task["failure_reason"] = failure_reason
275
- logger.warning(f"Task failed: {failure_reason}")
276
- return False, failure_reason
200
+ return CodeActResultEvent(success=False, reason=result["reason"], task=task, steps=result["codeact_steps"])
277
201
 
278
202
  except Exception as e:
279
203
  logger.error(f"Error during task execution: {e}")
280
204
  if self.debug:
281
205
  import traceback
282
206
  logger.error(traceback.format_exc())
283
- task["status"] = self.task_manager.STATUS_FAILED
284
- task["failure_reason"] = f"Error: {str(e)}"
285
- return False, f"Error: {str(e)}"
207
+ return CodeActResultEvent(success=False, reason=f"Error: {str(e)}", task=task, steps=[])
208
+
209
+ @step
210
+ async def handle_codeact_execute(self, ctx: Context, ev: CodeActResultEvent) -> FinalizeEvent | ReflectionEvent:
211
+ try:
212
+ task = ev.task
213
+ if not self.reasoning:
214
+ return FinalizeEvent(success=ev.success, reason=ev.reason, task=[task], steps=ev.steps)
215
+
216
+ if self.reflection:
217
+ return ReflectionEvent(task=task)
218
+
219
+ return ReasoningLogicEvent()
286
220
 
287
- async def run(self) -> Dict[str, Any]:
221
+ except Exception as e:
222
+ logger.error(f"❌ Error during DroidAgent execution: {e}")
223
+ if self.debug:
224
+ import traceback
225
+ logger.error(traceback.format_exc())
226
+ return FinalizeEvent(success=False, reason=str(e), task=self.task_manager.get_task_history(), steps=self.step_counter)
227
+
228
+
229
+ @step
230
+ async def reflect(
231
+ self,
232
+ ctx: Context,
233
+ ev: ReflectionEvent
234
+ ) -> ReasoningLogicEvent | CodeActExecuteEvent:
235
+
236
+
237
+ task = ev.task
238
+ if ev.task.agent_type == "AppStarterExpert":
239
+ self.task_manager.complete_task(task)
240
+ return ReasoningLogicEvent()
241
+
242
+ reflection = await self.reflector.reflect_on_episodic_memory(episodic_memory=self.current_episodic_memory, goal=task.description)
243
+
244
+ if reflection.goal_achieved:
245
+ self.task_manager.complete_task(task)
246
+ return ReasoningLogicEvent()
247
+
248
+ else:
249
+ self.task_manager.fail_task(task)
250
+ return ReasoningLogicEvent(reflection=reflection)
251
+
252
+
253
+ @step
254
+ async def handle_reasoning_logic(
255
+ self,
256
+ ctx: Context,
257
+ ev: ReasoningLogicEvent,
258
+ planner_agent: Workflow = MockWorkflow()
259
+ ) -> FinalizeEvent | CodeActExecuteEvent:
260
+ try:
261
+ if self.step_counter >= self.max_steps:
262
+ return FinalizeEvent(success=False, reason=f"Reached maximum number of steps ({self.max_steps})", task=self.task_manager.get_task_history(), steps=self.step_counter)
263
+ self.step_counter += 1
264
+
265
+ if ev.reflection:
266
+ handler = planner_agent.run(remembered_info=self.tools_instance.memory, reflection=ev.reflection)
267
+ else:
268
+ if self.task_iter:
269
+ try:
270
+ task = next(self.task_iter)
271
+ return CodeActExecuteEvent(task=task, reflection=None)
272
+ except StopIteration as e:
273
+ logger.info("Planning next steps...")
274
+
275
+ logger.debug(f"Planning step {self.step_counter}/{self.max_steps}")
276
+
277
+ handler = planner_agent.run(remembered_info=self.tools_instance.memory, reflection=None)
278
+
279
+ async for nested_ev in handler.stream_events():
280
+ self.handle_stream_event(nested_ev, ctx)
281
+
282
+ result = await handler
283
+
284
+ self.tasks = self.task_manager.get_all_tasks()
285
+ self.task_iter = iter(self.tasks)
286
+
287
+ if self.task_manager.goal_completed:
288
+ logger.info(f"✅ Goal completed: {self.task_manager.message}")
289
+ return FinalizeEvent(success=True, reason=self.task_manager.message, task=self.task_manager.get_task_history(), steps=self.step_counter)
290
+ if not self.tasks:
291
+ logger.warning("No tasks generated by planner")
292
+ return FinalizeEvent(success=False, reason="Planner did not generate any tasks", task=self.task_manager.get_task_history(), steps=self.step_counter)
293
+
294
+ return CodeActExecuteEvent(task=next(self.task_iter), reflection=None)
295
+
296
+ except Exception as e:
297
+ logger.error(f"❌ Error during DroidAgent execution: {e}")
298
+ if self.debug:
299
+ import traceback
300
+ logger.error(traceback.format_exc())
301
+ return FinalizeEvent(success=False, reason=str(e), task=self.task_manager.get_task_history(), steps=self.step_counter)
302
+
303
+
304
+ @step
305
+ async def start_handler(self, ctx: Context, ev: StartEvent) -> CodeActExecuteEvent | ReasoningLogicEvent:
288
306
  """
289
307
  Main execution loop that coordinates between planning and execution.
290
308
 
@@ -292,127 +310,53 @@ class DroidAgent:
292
310
  Dict containing the execution result
293
311
  """
294
312
  logger.info(f"🚀 Running DroidAgent to achieve goal: {self.goal}")
313
+ ctx.write_event_to_stream(ev)
295
314
 
296
- step_counter = 0
297
- retry_counter = 0
298
- overall_success = False
299
- final_message = ""
300
-
301
- try:
302
- # If reasoning is disabled, directly execute the goal as a single task in CodeActAgent
303
- if not self.reasoning:
304
- logger.info(f"🔄 Direct execution mode - executing goal: {self.goal}")
305
- # Create a simple task for the goal
306
- task = {
307
- "description": self.goal,
308
- "status": self.task_manager.STATUS_PENDING
309
- }
310
-
311
- # Execute the task directly with CodeActAgent
312
- success, reason = await self._execute_task_with_codeact(task)
313
-
314
- return {
315
- "success": success,
316
- "reason": reason,
317
- "steps": 1,
318
- "task_history": [task] # Single task history
319
- }
315
+ self.step_counter = 0
316
+ self.retry_counter = 0
317
+
318
+ if not self.reasoning:
319
+ logger.info(f"🔄 Direct execution mode - executing goal: {self.goal}")
320
+ task = Task(
321
+ description=self.goal,
322
+ status=self.task_manager.STATUS_PENDING,
323
+ agent_type="Default"
324
+ )
320
325
 
321
- # Standard reasoning mode with planning
322
- while step_counter < self.max_steps:
323
- step_counter += 1
324
- if self.debug:
325
- logger.debug(f"Planning step {step_counter}/{self.max_steps}")
326
-
327
- # 1. Get a plan from the planner
328
- tasks = await self._get_plan_from_planner()
329
-
330
- if self.task_manager.task_completed:
331
- # Task is marked as complete by the planner
332
- logger.info(f"✅ Goal completed: {self.task_manager.message}")
333
- overall_success = True
334
- final_message = self.task_manager.message
335
- break
336
-
337
- if not tasks:
338
- logger.warning("No tasks generated by planner")
339
- final_message = "Planner did not generate any tasks"
340
- break
341
-
342
- # 2. Execute each task in the plan sequentially
343
- for task in tasks:
344
- if task["status"] == self.task_manager.STATUS_PENDING:
345
- # Reset the CodeActAgent's step counter for this task
346
- self.codeact_agent.steps_counter = 0
347
-
348
- # Execute the task
349
- success, reason = await self._execute_task_with_codeact(task)
350
-
351
- # Update task info with detailed result for the planner
352
- task_idx = tasks.index(task)
353
- result_info = {
354
- "execution_details": reason,
355
- "step_executed": step_counter,
356
- "codeact_steps": self.codeact_agent.steps_counter
357
- }
358
-
359
- # Only update if not already updated in _execute_task_with_codeact
360
- if success:
361
- self.task_manager.update_status(
362
- task_idx,
363
- self.task_manager.STATUS_COMPLETED,
364
- result_info
365
- )
366
- logger.info(f"✅ Task completed: {task['description']}")
367
-
368
- if not success:
369
- # Store detailed failure information if not already set
370
- if "failure_reason" not in task:
371
- self.task_manager.update_status(
372
- task_idx,
373
- self.task_manager.STATUS_FAILED,
374
- {"failure_reason": reason, **result_info}
375
- )
376
-
377
- # Handle retries
378
- if retry_counter < self.max_retries:
379
- retry_counter += 1
380
- logger.info(f"Retrying... ({retry_counter}/{self.max_retries})")
381
- # Next iteration will generate a new plan based on current state
382
- break
383
- else:
384
- logger.error(f"Max retries exceeded for task")
385
- final_message = f"Failed after {self.max_retries} retries. Reason: {reason}"
386
- return {"success": False, "reason": final_message}
387
-
388
- # Reset retry counter for new task sequence
389
- retry_counter = 0
326
+ return CodeActExecuteEvent(task=task, reflection=None)
327
+
328
+ return ReasoningLogicEvent()
390
329
 
391
- # Check if all tasks are completed
392
- all_completed = all(task["status"] == self.task_manager.STATUS_COMPLETED for task in tasks)
393
- if all_completed:
394
- # Get a new plan (the planner might decide we're done)
395
- continue
330
+
331
+ @step
332
+ async def finalize(self, ctx: Context, ev: FinalizeEvent) -> StopEvent:
333
+ ctx.write_event_to_stream(ev)
334
+
335
+ result = {
336
+ "success": ev.success,
337
+ "reason": ev.reason,
338
+ "steps": ev.steps,
339
+ }
340
+
341
+ if self.trajectory and self.save_trajectories:
342
+ self.trajectory.save_trajectory()
343
+
344
+ return StopEvent(result)
345
+
346
+ def handle_stream_event(self, ev: Event, ctx: Context):
347
+
348
+ if isinstance(ev, EpisodicMemoryEvent):
349
+ self.current_episodic_memory = ev.episodic_memory
350
+ return
351
+
352
+ if not isinstance(ev, StopEvent):
353
+ ctx.write_event_to_stream(ev)
396
354
 
397
- # Check if we exited due to max steps
398
- if step_counter >= self.max_steps and not overall_success:
399
- final_message = f"Reached maximum number of steps ({self.max_steps})"
400
- overall_success = False
401
-
402
- return {
403
- "success": overall_success,
404
- "reason": final_message,
405
- "steps": step_counter,
406
- "task_history": self.task_manager.get_task_history()
407
- }
408
-
409
- except Exception as e:
410
- logger.error(f"❌ Error during DroidAgent execution: {e}")
411
- if self.debug:
412
- import traceback
413
- logger.error(traceback.format_exc())
414
- return {
415
- "success": False,
416
- "reason": str(e),
417
- "task_history": self.task_manager.get_task_history()
418
- }
355
+ if isinstance(ev, ScreenshotEvent):
356
+ self.trajectory.screenshots.append(ev.screenshot)
357
+
358
+ else:
359
+ self.trajectory.events.append(ev)
360
+
361
+
362
+