droidrun 0.2.0__py3-none-any.whl → 0.3.0__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 (57) 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 +321 -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/context/todo.txt +4 -0
  25. droidrun/agent/droid/__init__.py +2 -2
  26. droidrun/agent/droid/droid_agent.py +264 -325
  27. droidrun/agent/droid/events.py +28 -0
  28. droidrun/agent/oneflows/reflector.py +265 -0
  29. droidrun/agent/planner/__init__.py +2 -4
  30. droidrun/agent/planner/events.py +9 -13
  31. droidrun/agent/planner/planner_agent.py +268 -0
  32. droidrun/agent/planner/prompts.py +33 -53
  33. droidrun/agent/utils/__init__.py +3 -0
  34. droidrun/agent/utils/async_utils.py +1 -40
  35. droidrun/agent/utils/chat_utils.py +268 -48
  36. droidrun/agent/utils/executer.py +49 -14
  37. droidrun/agent/utils/llm_picker.py +14 -10
  38. droidrun/agent/utils/trajectory.py +184 -0
  39. droidrun/cli/__init__.py +1 -1
  40. droidrun/cli/logs.py +283 -0
  41. droidrun/cli/main.py +333 -439
  42. droidrun/run.py +105 -0
  43. droidrun/tools/__init__.py +5 -10
  44. droidrun/tools/{actions.py → adb.py} +279 -238
  45. droidrun/tools/ios.py +594 -0
  46. droidrun/tools/tools.py +99 -0
  47. droidrun-0.3.0.dist-info/METADATA +149 -0
  48. droidrun-0.3.0.dist-info/RECORD +52 -0
  49. droidrun/agent/planner/task_manager.py +0 -355
  50. droidrun/agent/planner/workflow.py +0 -371
  51. droidrun/tools/device.py +0 -29
  52. droidrun/tools/loader.py +0 -60
  53. droidrun-0.2.0.dist-info/METADATA +0 -373
  54. droidrun-0.2.0.dist-info/RECORD +0 -32
  55. {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/WHEEL +0 -0
  56. {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/entry_points.txt +0 -0
  57. {droidrun-0.2.0.dist-info → droidrun-0.3.0.dist-info}/licenses/LICENSE +0 -0
@@ -3,40 +3,70 @@ 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
+ reasoning: bool = False,
65
+ reflection: bool = False,
37
66
  enable_tracing: bool = False,
38
67
  debug: bool = False,
39
- device_serial: str = None,
68
+ save_trajectories: bool = False,
69
+ *args,
40
70
  **kwargs
41
71
  ):
42
72
  """
@@ -45,184 +75,83 @@ class DroidAgent:
45
75
  Args:
46
76
  goal: The user's goal or command to execute
47
77
  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
78
  max_steps: Maximum number of steps for both agents
51
- vision: Whether to enable vision capabilities
52
79
  timeout: Timeout for agent execution in seconds
53
- max_retries: Maximum number of retries for failed tasks
54
80
  reasoning: Whether to use the PlannerAgent for complex reasoning (True)
55
81
  or send tasks directly to CodeActAgent (False)
82
+ reflection: Whether to reflect on steps the CodeActAgent did to give the PlannerAgent advice
56
83
  enable_tracing: Whether to enable Arize Phoenix tracing
57
84
  debug: Whether to enable verbose debug logging
58
- device_serial: Target Android device serial number
59
85
  **kwargs: Additional keyword arguments to pass to the agents
60
86
  """
87
+ super().__init__(timeout=timeout ,*args,**kwargs)
88
+
89
+ # Configure default logging if not already configured
90
+ self._configure_default_logging(debug=debug)
91
+
92
+ # Setup global tracing first if enabled
93
+ if enable_tracing:
94
+ try:
95
+ from llama_index.core import set_global_handler
96
+ set_global_handler("arize_phoenix")
97
+ logger.info("🔍 Arize Phoenix tracing enabled globally")
98
+ except ImportError:
99
+ logger.warning("⚠️ Arize Phoenix package not found, tracing disabled")
100
+ enable_tracing = False
101
+
61
102
  self.goal = goal
62
103
  self.llm = llm
63
104
  self.max_steps = max_steps
64
- self.vision = vision
105
+ self.max_codeact_steps = max_steps
65
106
  self.timeout = timeout
66
- self.max_retries = max_retries
67
- self.task_manager = TaskManager()
68
107
  self.reasoning = reasoning
108
+ self.reflection = reflection
69
109
  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)
110
+
111
+ self.event_counter = 0
112
+ self.save_trajectories = save_trajectories
95
113
 
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
- )
114
+ self.trajectory = Trajectory()
115
+ self.task_manager = TaskManager()
116
+ self.task_iter = None
117
+ self.cim = ContextInjectionManager(personas=personas)
118
+ self.current_episodic_memory = None
119
+
120
+ logger.info("🤖 Initializing DroidAgent...")
108
121
 
122
+ self.tool_list = describe_tools(tools)
123
+ self.tools_instance = tools
124
+
125
+
109
126
  if self.reasoning:
110
127
  logger.info("📝 Initializing Planner Agent...")
111
128
  self.planner_agent = PlannerAgent(
112
129
  goal=goal,
113
130
  llm=llm,
114
- agent=self.codeact_agent,
115
- tools_instance=tools_instance,
131
+ personas=personas,
132
+ task_manager=self.task_manager,
133
+ tools_instance=tools,
116
134
  timeout=timeout,
117
- max_retries=max_retries,
118
- enable_tracing=enable_tracing,
119
135
  debug=debug
120
136
  )
137
+ self.add_workflows(planner_agent=self.planner_agent)
138
+ self.max_codeact_steps = 5
139
+
140
+ if self.reflection:
141
+ self.reflector = Reflector(llm=llm, debug=debug)
121
142
 
122
- # Give task manager to the planner
123
- self.planner_agent.task_manager = self.task_manager
124
143
  else:
125
144
  logger.debug("🚫 Planning disabled - will execute tasks directly with CodeActAgent")
126
145
  self.planner_agent = None
127
146
 
128
147
  logger.info("✅ DroidAgent initialized successfully.")
129
148
 
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]:
149
+ @step
150
+ async def execute_task(
151
+ self,
152
+ ctx: Context,
153
+ ev: CodeActExecuteEvent
154
+ ) -> CodeActResultEvent:
226
155
  """
227
156
  Execute a single task using the CodeActAgent.
228
157
 
@@ -232,59 +161,144 @@ class DroidAgent:
232
161
  Returns:
233
162
  Tuple of (success, reason)
234
163
  """
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
164
+ task: Task = ev.task
165
+ reflection = ev.reflection if ev.reflection is not None else None
166
+ persona = self.cim.get_persona(task.agent_type)
167
+
168
+ logger.info(f"🔧 Executing task: {task.description}")
169
+
242
170
  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)
171
+ codeact_agent = CodeActAgent(
172
+ llm=self.llm,
173
+ persona=persona,
174
+ max_steps=self.max_codeact_steps,
175
+ all_tools_list=self.tool_list,
176
+ tools_instance=self.tools_instance,
177
+ debug=self.debug,
178
+ timeout=self.timeout,
179
+ )
180
+
181
+ handler = codeact_agent.run(
182
+ input=task.description,
183
+ remembered_info=self.tools_instance.memory,
184
+ reflection=reflection
185
+ )
251
186
 
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"
187
+ async for nested_ev in handler.stream_events():
188
+ self.handle_stream_event(nested_ev, ctx)
189
+
190
+ result = await handler
191
+
264
192
 
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")
193
+ if "success" in result and result["success"]:
194
+ return CodeActResultEvent(success=True, reason=result["reason"], task=task, steps=result["codeact_steps"])
271
195
  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
196
+ return CodeActResultEvent(success=False, reason=result["reason"], task=task, steps=result["codeact_steps"])
277
197
 
278
198
  except Exception as e:
279
199
  logger.error(f"Error during task execution: {e}")
280
200
  if self.debug:
281
201
  import traceback
282
202
  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)}"
203
+ return CodeActResultEvent(success=False, reason=f"Error: {str(e)}", task=task, steps=result["codeact_steps"])
204
+
205
+ @step
206
+ async def handle_codeact_execute(self, ctx: Context, ev: CodeActResultEvent) -> FinalizeEvent | ReflectionEvent:
207
+ try:
208
+ task = ev.task
209
+ if not self.reasoning:
210
+ return FinalizeEvent(success=ev.success, reason=ev.reason, task=[task], steps=ev.steps)
211
+
212
+ if self.reflection:
213
+ return ReflectionEvent(task=task)
214
+
215
+ return ReasoningLogicEvent()
286
216
 
287
- async def run(self) -> Dict[str, Any]:
217
+ except Exception as e:
218
+ logger.error(f"❌ Error during DroidAgent execution: {e}")
219
+ if self.debug:
220
+ import traceback
221
+ logger.error(traceback.format_exc())
222
+ return FinalizeEvent(success=False, reason=str(e), task=self.task_manager.get_task_history(), steps=self.step_counter)
223
+
224
+
225
+ @step
226
+ async def reflect(
227
+ self,
228
+ ctx: Context,
229
+ ev: ReflectionEvent
230
+ ) -> ReasoningLogicEvent | CodeActExecuteEvent:
231
+
232
+
233
+ task = ev.task
234
+ if ev.task.agent_type == "AppStarterExpert":
235
+ self.task_manager.complete_task(task)
236
+ return ReasoningLogicEvent()
237
+
238
+ reflection = await self.reflector.reflect_on_episodic_memory(episodic_memory=self.current_episodic_memory, goal=task.description)
239
+
240
+ if reflection.goal_achieved:
241
+ self.task_manager.complete_task(task)
242
+ return ReasoningLogicEvent()
243
+
244
+ else:
245
+ self.task_manager.fail_task(task)
246
+ return ReasoningLogicEvent(reflection=reflection)
247
+
248
+
249
+ @step
250
+ async def handle_reasoning_logic(
251
+ self,
252
+ ctx: Context,
253
+ ev: ReasoningLogicEvent,
254
+ planner_agent: Workflow = MockWorkflow()
255
+ ) -> FinalizeEvent | CodeActExecuteEvent:
256
+ try:
257
+ if self.step_counter >= self.max_steps:
258
+ 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)
259
+ self.step_counter += 1
260
+
261
+ if ev.reflection:
262
+ handler = planner_agent.run(remembered_info=self.tools_instance.memory, reflection=ev.reflection)
263
+ else:
264
+ if self.task_iter:
265
+ try:
266
+ task = next(self.task_iter)
267
+ return CodeActExecuteEvent(task=task, reflection=None)
268
+ except StopIteration as e:
269
+ logger.info("Planning next steps...")
270
+
271
+ logger.debug(f"Planning step {self.step_counter}/{self.max_steps}")
272
+
273
+ handler = planner_agent.run(remembered_info=self.tools_instance.memory, reflection=None)
274
+
275
+ async for nested_ev in handler.stream_events():
276
+ self.handle_stream_event(nested_ev, ctx)
277
+
278
+ result = await handler
279
+
280
+ self.tasks = self.task_manager.get_all_tasks()
281
+ self.task_iter = iter(self.tasks)
282
+
283
+ if self.task_manager.goal_completed:
284
+ logger.info(f"✅ Goal completed: {self.task_manager.message}")
285
+ return FinalizeEvent(success=True, reason=self.task_manager.message, task=self.task_manager.get_task_history(), steps=self.step_counter)
286
+ if not self.tasks:
287
+ logger.warning("No tasks generated by planner")
288
+ return FinalizeEvent(success=False, reason="Planner did not generate any tasks", task=self.task_manager.get_task_history(), steps=self.step_counter)
289
+
290
+ return CodeActExecuteEvent(task=next(self.task_iter), reflection=None)
291
+
292
+ except Exception as e:
293
+ logger.error(f"❌ Error during DroidAgent execution: {e}")
294
+ if self.debug:
295
+ import traceback
296
+ logger.error(traceback.format_exc())
297
+ return FinalizeEvent(success=False, reason=str(e), task=self.task_manager.get_task_history(), steps=self.step_counter)
298
+
299
+
300
+ @step
301
+ async def start_handler(self, ctx: Context, ev: StartEvent) -> CodeActExecuteEvent | ReasoningLogicEvent:
288
302
  """
289
303
  Main execution loop that coordinates between planning and execution.
290
304
 
@@ -293,126 +307,51 @@ class DroidAgent:
293
307
  """
294
308
  logger.info(f"🚀 Running DroidAgent to achieve goal: {self.goal}")
295
309
 
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
- }
310
+ self.step_counter = 0
311
+ self.retry_counter = 0
312
+
313
+ if not self.reasoning:
314
+ logger.info(f"🔄 Direct execution mode - executing goal: {self.goal}")
315
+ task = Task(
316
+ description=self.goal,
317
+ status=self.task_manager.STATUS_PENDING,
318
+ agent_type="Default"
319
+ )
320
320
 
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
321
+ return CodeActExecuteEvent(task=task, reflection=None)
322
+
323
+ return ReasoningLogicEvent()
390
324
 
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
325
+
326
+ @step
327
+ async def finalize(self, ctx: Context, ev: FinalizeEvent) -> StopEvent:
328
+ ctx.write_event_to_stream(ev)
329
+
330
+ result = {
331
+ "success": ev.success,
332
+ "reason": ev.reason,
333
+ "steps": ev.steps,
334
+ }
335
+
336
+ if self.trajectory and self.save_trajectories:
337
+ self.trajectory.save_trajectory()
338
+
339
+ return StopEvent(result)
340
+
341
+ def handle_stream_event(self, ev: Event, ctx: Context):
342
+
343
+ if isinstance(ev, EpisodicMemoryEvent):
344
+ self.current_episodic_memory = ev.episodic_memory
345
+ return
346
+
347
+ if not isinstance(ev, StopEvent):
348
+ ctx.write_event_to_stream(ev)
396
349
 
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
- }
350
+ if isinstance(ev, ScreenshotEvent):
351
+ self.trajectory.screenshots.append(ev.screenshot)
352
+
353
+ else:
354
+ self.trajectory.events.append(ev)
355
+
356
+
357
+