droidrun 0.3.9__py3-none-any.whl → 0.3.10.dev2__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- droidrun/__init__.py +2 -3
- droidrun/__main__.py +1 -1
- droidrun/agent/__init__.py +1 -1
- droidrun/agent/codeact/__init__.py +1 -4
- droidrun/agent/codeact/codeact_agent.py +66 -40
- droidrun/agent/codeact/events.py +6 -3
- droidrun/agent/codeact/prompts.py +2 -2
- droidrun/agent/common/events.py +4 -2
- droidrun/agent/context/__init__.py +1 -3
- droidrun/agent/context/agent_persona.py +2 -1
- droidrun/agent/context/context_injection_manager.py +6 -6
- droidrun/agent/context/episodic_memory.py +5 -3
- droidrun/agent/context/personas/__init__.py +3 -3
- droidrun/agent/context/personas/app_starter.py +3 -3
- droidrun/agent/context/personas/big_agent.py +3 -3
- droidrun/agent/context/personas/default.py +3 -3
- droidrun/agent/context/personas/ui_expert.py +5 -5
- droidrun/agent/context/task_manager.py +15 -17
- droidrun/agent/droid/__init__.py +1 -1
- droidrun/agent/droid/droid_agent.py +327 -180
- droidrun/agent/droid/events.py +91 -9
- droidrun/agent/executor/__init__.py +13 -0
- droidrun/agent/executor/events.py +24 -0
- droidrun/agent/executor/executor_agent.py +327 -0
- droidrun/agent/executor/prompts.py +136 -0
- droidrun/agent/manager/__init__.py +18 -0
- droidrun/agent/manager/events.py +20 -0
- droidrun/agent/manager/manager_agent.py +459 -0
- droidrun/agent/manager/prompts.py +223 -0
- droidrun/agent/oneflows/app_starter_workflow.py +118 -0
- droidrun/agent/oneflows/text_manipulator.py +204 -0
- droidrun/agent/planner/__init__.py +3 -3
- droidrun/agent/planner/events.py +6 -3
- droidrun/agent/planner/planner_agent.py +27 -42
- droidrun/agent/planner/prompts.py +2 -2
- droidrun/agent/usage.py +11 -11
- droidrun/agent/utils/__init__.py +11 -1
- droidrun/agent/utils/async_utils.py +2 -1
- droidrun/agent/utils/chat_utils.py +48 -60
- droidrun/agent/utils/device_state_formatter.py +177 -0
- droidrun/agent/utils/executer.py +12 -11
- droidrun/agent/utils/inference.py +114 -0
- droidrun/agent/utils/llm_picker.py +2 -0
- droidrun/agent/utils/message_utils.py +85 -0
- droidrun/agent/utils/tools.py +220 -0
- droidrun/agent/utils/trajectory.py +8 -7
- droidrun/cli/__init__.py +1 -1
- droidrun/cli/logs.py +29 -28
- droidrun/cli/main.py +279 -143
- droidrun/config_manager/__init__.py +25 -0
- droidrun/config_manager/config_manager.py +583 -0
- droidrun/macro/__init__.py +2 -2
- droidrun/macro/__main__.py +1 -1
- droidrun/macro/cli.py +36 -34
- droidrun/macro/replay.py +7 -9
- droidrun/portal.py +1 -1
- droidrun/telemetry/__init__.py +2 -2
- droidrun/telemetry/events.py +3 -4
- droidrun/telemetry/phoenix.py +173 -0
- droidrun/telemetry/tracker.py +7 -5
- droidrun/tools/__init__.py +1 -1
- droidrun/tools/adb.py +210 -82
- droidrun/tools/ios.py +7 -5
- droidrun/tools/tools.py +25 -8
- {droidrun-0.3.9.dist-info → droidrun-0.3.10.dev2.dist-info}/METADATA +6 -3
- droidrun-0.3.10.dev2.dist-info/RECORD +70 -0
- droidrun/agent/common/default.py +0 -5
- droidrun/agent/context/reflection.py +0 -20
- droidrun/agent/oneflows/reflector.py +0 -265
- droidrun-0.3.9.dist-info/RECORD +0 -56
- {droidrun-0.3.9.dist-info → droidrun-0.3.10.dev2.dist-info}/WHEEL +0 -0
- {droidrun-0.3.9.dist-info → droidrun-0.3.10.dev2.dist-info}/entry_points.txt +0 -0
- {droidrun-0.3.9.dist-info → droidrun-0.3.10.dev2.dist-info}/licenses/LICENSE +0 -0
@@ -1,41 +1,63 @@
|
|
1
1
|
"""
|
2
2
|
DroidAgent - A wrapper class that coordinates the planning and execution of tasks
|
3
3
|
to achieve a user's goal on an Android device.
|
4
|
+
|
5
|
+
Architecture:
|
6
|
+
- When reasoning=False: Uses CodeActAgent directly
|
7
|
+
- When reasoning=True: Uses Manager (planning) + Executor (action) workflows
|
4
8
|
"""
|
5
9
|
|
6
10
|
import logging
|
7
11
|
from typing import List
|
8
12
|
|
13
|
+
import llama_index.core
|
9
14
|
from llama_index.core.llms.llm import LLM
|
10
|
-
from llama_index.core.workflow import
|
15
|
+
from llama_index.core.workflow import Context, StartEvent, StopEvent, Workflow, step
|
11
16
|
from llama_index.core.workflow.handler import WorkflowHandler
|
12
|
-
from
|
17
|
+
from workflows.events import Event
|
18
|
+
|
19
|
+
from droidrun.config_manager.config_manager import VisionConfig
|
20
|
+
|
13
21
|
from droidrun.agent.codeact import CodeActAgent
|
14
22
|
from droidrun.agent.codeact.events import EpisodicMemoryEvent
|
15
|
-
from droidrun.agent.
|
16
|
-
from droidrun.agent.context.task_manager import TaskManager
|
17
|
-
from droidrun.agent.utils.trajectory import Trajectory
|
18
|
-
from droidrun.tools import Tools, describe_tools
|
19
|
-
from droidrun.agent.common.events import ScreenshotEvent, MacroEvent, RecordUIStateEvent
|
20
|
-
from droidrun.agent.common.default import MockWorkflow
|
23
|
+
from droidrun.agent.common.events import MacroEvent, RecordUIStateEvent, ScreenshotEvent
|
21
24
|
from droidrun.agent.context import ContextInjectionManager
|
22
25
|
from droidrun.agent.context.agent_persona import AgentPersona
|
23
26
|
from droidrun.agent.context.personas import DEFAULT
|
24
|
-
from droidrun.agent.
|
27
|
+
from droidrun.agent.context.task_manager import Task, TaskManager
|
28
|
+
from droidrun.agent.droid.events import (
|
29
|
+
CodeActExecuteEvent,
|
30
|
+
CodeActResultEvent,
|
31
|
+
DroidAgentState,
|
32
|
+
ExecutorInputEvent,
|
33
|
+
ExecutorResultEvent,
|
34
|
+
FinalizeEvent,
|
35
|
+
ManagerInputEvent,
|
36
|
+
ManagerPlanEvent,
|
37
|
+
)
|
38
|
+
from droidrun.agent.executor import ExecutorAgent
|
39
|
+
from droidrun.agent.manager import ManagerAgent
|
40
|
+
from droidrun.agent.utils.tools import ATOMIC_ACTION_SIGNATURES
|
41
|
+
from droidrun.agent.utils.trajectory import Trajectory
|
25
42
|
from droidrun.telemetry import (
|
43
|
+
DroidAgentFinalizeEvent,
|
44
|
+
DroidAgentInitEvent,
|
26
45
|
capture,
|
27
46
|
flush,
|
28
|
-
DroidAgentInitEvent,
|
29
|
-
DroidAgentFinalizeEvent,
|
30
47
|
)
|
48
|
+
from droidrun.telemetry.phoenix import arize_phoenix_callback_handler
|
49
|
+
from droidrun.tools import Tools
|
31
50
|
|
32
51
|
logger = logging.getLogger("droidrun")
|
33
52
|
|
34
53
|
|
35
54
|
class DroidAgent(Workflow):
|
36
55
|
"""
|
37
|
-
A wrapper class that coordinates between
|
38
|
-
|
56
|
+
A wrapper class that coordinates between agents to achieve a user's goal.
|
57
|
+
|
58
|
+
Reasoning modes:
|
59
|
+
- reasoning=False: Uses CodeActAgent directly for immediate execution
|
60
|
+
- reasoning=True: Uses ManagerAgent (planning) + ExecutorAgent (actions)
|
39
61
|
"""
|
40
62
|
|
41
63
|
@staticmethod
|
@@ -63,18 +85,18 @@ class DroidAgent(Workflow):
|
|
63
85
|
def __init__(
|
64
86
|
self,
|
65
87
|
goal: str,
|
66
|
-
|
88
|
+
llms: dict[str, LLM] | LLM,
|
67
89
|
tools: Tools,
|
68
|
-
personas: List[AgentPersona] = [DEFAULT],
|
90
|
+
personas: List[AgentPersona] = [DEFAULT], # noqa: B006
|
69
91
|
max_steps: int = 15,
|
70
92
|
timeout: int = 1000,
|
71
|
-
vision: bool = False,
|
93
|
+
vision: "VisionConfig | dict | bool" = False,
|
72
94
|
reasoning: bool = False,
|
73
|
-
reflection: bool = False,
|
74
95
|
enable_tracing: bool = False,
|
75
96
|
debug: bool = False,
|
76
97
|
save_trajectories: str = "none",
|
77
98
|
excluded_tools: List[str] = None,
|
99
|
+
custom_tools: dict = None,
|
78
100
|
*args,
|
79
101
|
**kwargs,
|
80
102
|
):
|
@@ -86,42 +108,97 @@ class DroidAgent(Workflow):
|
|
86
108
|
llm: The language model to use for both agents
|
87
109
|
max_steps: Maximum number of steps for both agents
|
88
110
|
timeout: Timeout for agent execution in seconds
|
89
|
-
reasoning: Whether to use
|
111
|
+
reasoning: Whether to use Manager+Executor for complex reasoning (True)
|
90
112
|
or send tasks directly to CodeActAgent (False)
|
91
|
-
reflection: Whether to reflect on steps the CodeActAgent did to give the PlannerAgent advice
|
92
113
|
enable_tracing: Whether to enable Arize Phoenix tracing
|
93
114
|
debug: Whether to enable verbose debug logging
|
94
115
|
save_trajectories: Trajectory saving level. Can be:
|
95
116
|
- "none" (no saving)
|
96
117
|
- "step" (save per step)
|
97
118
|
- "action" (save per action)
|
119
|
+
custom_tools: Dictionary of custom tools in ATOMIC_ACTION_SIGNATURES format:
|
120
|
+
{
|
121
|
+
"tool_name": {
|
122
|
+
"arguments": ["arg1", "arg2"],
|
123
|
+
"description": "Tool description with usage example",
|
124
|
+
"function": callable
|
125
|
+
}
|
126
|
+
}
|
98
127
|
**kwargs: Additional keyword arguments to pass to the agents
|
99
128
|
"""
|
100
129
|
self.user_id = kwargs.pop("user_id", None)
|
101
|
-
super().__init__(timeout=timeout, *args, **kwargs)
|
130
|
+
super().__init__(timeout=timeout, *args, **kwargs) # noqa: B026
|
102
131
|
# Configure default logging if not already configured
|
103
132
|
self._configure_default_logging(debug=debug)
|
104
133
|
|
105
134
|
# Setup global tracing first if enabled
|
106
135
|
if enable_tracing:
|
107
136
|
try:
|
108
|
-
|
109
|
-
|
110
|
-
set_global_handler("arize_phoenix")
|
137
|
+
handler = arize_phoenix_callback_handler()
|
138
|
+
llama_index.core.global_handler = handler
|
111
139
|
logger.info("🔍 Arize Phoenix tracing enabled globally")
|
112
140
|
except ImportError:
|
113
|
-
logger.warning(
|
141
|
+
logger.warning(
|
142
|
+
"⚠️ Arize Phoenix is not installed.\n"
|
143
|
+
" To enable Phoenix integration, install with:\n"
|
144
|
+
" • If installed via tool: `uv tool install droidrun[phoenix]`"
|
145
|
+
" • If installed via pip: `uv pip install droidrun[phoenix]`\n"
|
146
|
+
)
|
114
147
|
enable_tracing = False
|
115
148
|
|
116
149
|
self.goal = goal
|
117
|
-
self.llm = llm
|
118
|
-
self.vision = vision
|
119
150
|
self.max_steps = max_steps
|
120
151
|
self.max_codeact_steps = max_steps
|
121
152
|
self.timeout = timeout
|
122
153
|
self.reasoning = reasoning
|
123
|
-
self.reflection = reflection
|
124
154
|
self.debug = debug
|
155
|
+
self.custom_tools = custom_tools or {}
|
156
|
+
|
157
|
+
# ====================================================================
|
158
|
+
# Handle LLM parameter - support both dict and single LLM
|
159
|
+
# ====================================================================
|
160
|
+
if isinstance(llms, dict):
|
161
|
+
self.manager_llm = llms.get('manager')
|
162
|
+
self.executor_llm = llms.get('executor')
|
163
|
+
self.codeact_llm = llms.get('codeact')
|
164
|
+
self.text_manipulator_llm = llms.get('text_manipulator')
|
165
|
+
self.app_opener_llm = llms.get('app_opener')
|
166
|
+
|
167
|
+
# Validate required LLMs are present
|
168
|
+
if reasoning and (not self.manager_llm or not self.executor_llm):
|
169
|
+
raise ValueError("When reasoning=True, 'manager' and 'executor' LLMs must be provided in llms dict")
|
170
|
+
if not self.codeact_llm:
|
171
|
+
raise ValueError("'codeact' LLM must be provided in llms dict")
|
172
|
+
|
173
|
+
logger.info("📚 Using agent-specific LLMs from dictionary")
|
174
|
+
else:
|
175
|
+
# single LLM for all agents
|
176
|
+
logger.info("📚 Using single LLM for all agents (backward compatibility mode)")
|
177
|
+
self.manager_llm = llms
|
178
|
+
self.executor_llm = llms
|
179
|
+
self.codeact_llm = llms
|
180
|
+
self.text_manipulator_llm = llms
|
181
|
+
self.app_opener_llm = llms
|
182
|
+
|
183
|
+
# ====================================================================
|
184
|
+
# Handle vision parameter - support VisionConfig, dict, or bool
|
185
|
+
# ====================================================================
|
186
|
+
if isinstance(vision, VisionConfig):
|
187
|
+
self.vision_config = vision
|
188
|
+
elif isinstance(vision, dict):
|
189
|
+
self.vision_config = VisionConfig.from_dict(vision)
|
190
|
+
elif isinstance(vision, bool):
|
191
|
+
# Backward compatibility: single bool for all agents
|
192
|
+
logger.info(f"👁️ Using vision={vision} for all agents (backward compatibility mode)")
|
193
|
+
self.vision_config = VisionConfig(manager=vision, executor=vision, codeact=vision)
|
194
|
+
else:
|
195
|
+
raise TypeError(f"vision must be VisionConfig, dict, or bool, got {type(vision)}")
|
196
|
+
|
197
|
+
# Store individual vision flags for easy access
|
198
|
+
self.manager_vision = self.vision_config.manager
|
199
|
+
self.executor_vision = self.vision_config.executor
|
200
|
+
self.codeact_vision = self.vision_config.codeact
|
201
|
+
|
125
202
|
|
126
203
|
self.event_counter = 0
|
127
204
|
# Handle backward compatibility: bool -> str mapping
|
@@ -148,43 +225,64 @@ class DroidAgent(Workflow):
|
|
148
225
|
logger.info("🤖 Initializing DroidAgent...")
|
149
226
|
logger.info(f"💾 Trajectory saving level: {self.save_trajectories}")
|
150
227
|
|
151
|
-
self.tool_list = describe_tools(tools, excluded_tools)
|
152
228
|
self.tools_instance = tools
|
153
229
|
|
154
230
|
self.tools_instance.save_trajectories = self.save_trajectories
|
155
231
|
|
232
|
+
# Create shared state instance for Manager/Executor workflows
|
233
|
+
self.shared_state = DroidAgentState(
|
234
|
+
instruction=goal,
|
235
|
+
err_to_manager_thresh=2
|
236
|
+
)
|
237
|
+
|
156
238
|
if self.reasoning:
|
157
|
-
logger.info("📝 Initializing
|
158
|
-
self.
|
159
|
-
|
160
|
-
|
161
|
-
vision=vision,
|
239
|
+
logger.info("📝 Initializing Manager and Executor Agents...")
|
240
|
+
self.manager_agent = ManagerAgent(
|
241
|
+
llm=self.manager_llm,
|
242
|
+
vision=self.manager_vision,
|
162
243
|
personas=personas,
|
163
|
-
task_manager=self.task_manager,
|
164
244
|
tools_instance=tools,
|
245
|
+
shared_state=self.shared_state,
|
246
|
+
custom_tools=self.custom_tools,
|
247
|
+
timeout=timeout,
|
248
|
+
debug=debug,
|
249
|
+
)
|
250
|
+
self.executor_agent = ExecutorAgent(
|
251
|
+
llm=self.executor_llm,
|
252
|
+
vision=self.executor_vision,
|
253
|
+
tools_instance=tools,
|
254
|
+
shared_state=self.shared_state,
|
255
|
+
persona=None, # Need to figure this out
|
256
|
+
custom_tools=self.custom_tools,
|
165
257
|
timeout=timeout,
|
166
258
|
debug=debug,
|
167
259
|
)
|
168
260
|
self.max_codeact_steps = 5
|
169
261
|
|
170
|
-
|
171
|
-
|
262
|
+
|
263
|
+
# Keep planner_agent for backward compatibility (can be removed later)
|
264
|
+
self.planner_agent = None
|
172
265
|
|
173
266
|
else:
|
174
|
-
logger.debug("🚫
|
267
|
+
logger.debug("🚫 Reasoning disabled - will execute tasks directly with CodeActAgent")
|
268
|
+
self.manager_agent = None
|
269
|
+
self.executor_agent = None
|
175
270
|
self.planner_agent = None
|
176
271
|
|
272
|
+
# Get tool names from ATOMIC_ACTION_SIGNATURES for telemetry
|
273
|
+
atomic_tools = list(ATOMIC_ACTION_SIGNATURES.keys())
|
274
|
+
|
177
275
|
capture(
|
276
|
+
# TODO: do proper telemetry instead of this ductaped crap
|
178
277
|
DroidAgentInitEvent(
|
179
278
|
goal=goal,
|
180
|
-
llm=llm.class_name(),
|
181
|
-
tools=",".join(
|
279
|
+
llm=self.llm.class_name(),
|
280
|
+
tools=",".join(atomic_tools + ["remember", "complete"]),
|
182
281
|
personas=",".join([p.name for p in personas]),
|
183
282
|
max_steps=max_steps,
|
184
283
|
timeout=timeout,
|
185
|
-
vision=vision,
|
284
|
+
vision=self.vision,
|
186
285
|
reasoning=reasoning,
|
187
|
-
reflection=reflection,
|
188
286
|
enable_tracing=enable_tracing,
|
189
287
|
debug=debug,
|
190
288
|
save_trajectories=save_trajectories,
|
@@ -200,6 +298,35 @@ class DroidAgent(Workflow):
|
|
200
298
|
"""
|
201
299
|
return super().run(*args, **kwargs)
|
202
300
|
|
301
|
+
def _create_finalize_event(
|
302
|
+
self,
|
303
|
+
success: bool,
|
304
|
+
reason: str,
|
305
|
+
output: str
|
306
|
+
) -> FinalizeEvent:
|
307
|
+
"""
|
308
|
+
Single source of truth for creating FinalizeEvent.
|
309
|
+
|
310
|
+
This helper ensures all FinalizeEvent creation is consistent
|
311
|
+
across the workflow.
|
312
|
+
|
313
|
+
Args:
|
314
|
+
success: Whether the task succeeded
|
315
|
+
reason: Reason for completion (deprecated, use output)
|
316
|
+
output: Output message
|
317
|
+
|
318
|
+
Returns:
|
319
|
+
FinalizeEvent ready to be returned
|
320
|
+
"""
|
321
|
+
return FinalizeEvent(
|
322
|
+
success=success,
|
323
|
+
reason=reason,
|
324
|
+
output=output,
|
325
|
+
task=[], # TODO: use the final plan as the tasks and the goal as task
|
326
|
+
tasks=[],
|
327
|
+
steps=self.step_counter
|
328
|
+
)
|
329
|
+
|
203
330
|
@step
|
204
331
|
async def execute_task(self, ctx: Context, ev: CodeActExecuteEvent) -> CodeActResultEvent:
|
205
332
|
"""
|
@@ -212,19 +339,18 @@ class DroidAgent(Workflow):
|
|
212
339
|
Tuple of (success, reason)
|
213
340
|
"""
|
214
341
|
task: Task = ev.task
|
215
|
-
reflection = ev.reflection if ev.reflection is not None else None
|
216
342
|
persona = self.cim.get_persona(task.agent_type)
|
217
343
|
|
218
344
|
logger.info(f"🔧 Executing task: {task.description}")
|
219
345
|
|
220
346
|
try:
|
221
347
|
codeact_agent = CodeActAgent(
|
222
|
-
llm=self.
|
348
|
+
llm=self.codeact_llm,
|
223
349
|
persona=persona,
|
224
|
-
vision=self.
|
350
|
+
vision=self.codeact_vision,
|
225
351
|
max_steps=self.max_codeact_steps,
|
226
|
-
all_tools_list=self.tool_list,
|
227
352
|
tools_instance=self.tools_instance,
|
353
|
+
custom_tools=self.custom_tools,
|
228
354
|
debug=self.debug,
|
229
355
|
timeout=self.timeout,
|
230
356
|
)
|
@@ -232,7 +358,6 @@ class DroidAgent(Workflow):
|
|
232
358
|
handler = codeact_agent.run(
|
233
359
|
input=task.description,
|
234
360
|
remembered_info=self.tools_instance.memory,
|
235
|
-
reflection=reflection,
|
236
361
|
)
|
237
362
|
|
238
363
|
async for nested_ev in handler.stream_events():
|
@@ -266,32 +391,17 @@ class DroidAgent(Workflow):
|
|
266
391
|
@step
|
267
392
|
async def handle_codeact_execute(
|
268
393
|
self, ctx: Context, ev: CodeActResultEvent
|
269
|
-
) -> FinalizeEvent
|
394
|
+
) -> FinalizeEvent:
|
270
395
|
try:
|
271
396
|
task = ev.task
|
272
|
-
|
273
|
-
|
274
|
-
|
275
|
-
|
276
|
-
|
277
|
-
|
278
|
-
|
279
|
-
|
280
|
-
)
|
281
|
-
|
282
|
-
if self.reflection and ev.success:
|
283
|
-
return ReflectionEvent(task=task)
|
284
|
-
|
285
|
-
# Reasoning is enabled but reflection is disabled.
|
286
|
-
# Success: mark complete and proceed to next step in reasoning loop.
|
287
|
-
# Failure: mark failed and trigger planner immediately without advancing to the next queued task.
|
288
|
-
if ev.success:
|
289
|
-
self.task_manager.complete_task(task, message=ev.reason)
|
290
|
-
return ReasoningLogicEvent()
|
291
|
-
else:
|
292
|
-
self.task_manager.fail_task(task, failure_reason=ev.reason)
|
293
|
-
return ReasoningLogicEvent(force_planning=True)
|
294
|
-
|
397
|
+
return FinalizeEvent(
|
398
|
+
success=ev.success,
|
399
|
+
reason=ev.reason,
|
400
|
+
output=ev.reason,
|
401
|
+
task=[task],
|
402
|
+
tasks=[task],
|
403
|
+
steps=ev.steps,
|
404
|
+
)
|
295
405
|
except Exception as e:
|
296
406
|
logger.error(f"❌ Error during DroidAgent execution: {e}")
|
297
407
|
if self.debug:
|
@@ -309,141 +419,178 @@ class DroidAgent(Workflow):
|
|
309
419
|
)
|
310
420
|
|
311
421
|
@step
|
312
|
-
async def
|
313
|
-
self, ctx: Context, ev:
|
314
|
-
) ->
|
315
|
-
|
316
|
-
|
317
|
-
self.task_manager.complete_task(task)
|
318
|
-
return ReasoningLogicEvent()
|
319
|
-
|
320
|
-
reflection = await self.reflector.reflect_on_episodic_memory(
|
321
|
-
episodic_memory=self.current_episodic_memory, goal=task.description
|
322
|
-
)
|
422
|
+
async def start_handler(
|
423
|
+
self, ctx: Context, ev: StartEvent
|
424
|
+
) -> CodeActExecuteEvent | ManagerInputEvent:
|
425
|
+
"""
|
426
|
+
Main execution loop that coordinates between planning and execution.
|
323
427
|
|
324
|
-
|
325
|
-
|
326
|
-
|
428
|
+
Returns:
|
429
|
+
Event to trigger next step based on reasoning mode
|
430
|
+
"""
|
431
|
+
logger.info(f"🚀 Running DroidAgent to achieve goal: {self.goal}")
|
432
|
+
ctx.write_event_to_stream(ev)
|
327
433
|
|
328
|
-
|
329
|
-
|
330
|
-
|
434
|
+
self.step_counter = 0
|
435
|
+
self.retry_counter = 0
|
436
|
+
|
437
|
+
if not self.reasoning:
|
438
|
+
logger.info(f"🔄 Direct execution mode - executing goal: {self.goal}")
|
439
|
+
task = Task(
|
440
|
+
description=self.goal,
|
441
|
+
status=self.task_manager.STATUS_PENDING,
|
442
|
+
agent_type="Default",
|
443
|
+
)
|
444
|
+
|
445
|
+
return CodeActExecuteEvent(task=task)
|
446
|
+
|
447
|
+
# Reasoning mode - state already initialized in __init__, start with Manager
|
448
|
+
logger.info("🧠 Reasoning mode - initializing Manager/Executor workflow")
|
449
|
+
return ManagerInputEvent()
|
450
|
+
|
451
|
+
# ========================================================================
|
452
|
+
# Manager/Executor Workflow Steps
|
453
|
+
# ========================================================================
|
331
454
|
|
332
455
|
@step
|
333
|
-
async def
|
456
|
+
async def run_manager(
|
334
457
|
self,
|
335
458
|
ctx: Context,
|
336
|
-
ev:
|
337
|
-
) ->
|
338
|
-
|
339
|
-
|
340
|
-
output = f"Reached maximum number of steps ({self.max_steps})"
|
341
|
-
tasks = self.task_manager.get_task_history()
|
342
|
-
return FinalizeEvent(
|
343
|
-
success=False,
|
344
|
-
reason=output,
|
345
|
-
output=output,
|
346
|
-
task=tasks,
|
347
|
-
tasks=tasks,
|
348
|
-
steps=self.step_counter,
|
349
|
-
)
|
350
|
-
self.step_counter += 1
|
459
|
+
ev: ManagerInputEvent
|
460
|
+
) -> ManagerPlanEvent | FinalizeEvent:
|
461
|
+
"""
|
462
|
+
Run Manager planning phase.
|
351
463
|
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
464
|
+
Pre-flight checks for termination before running manager.
|
465
|
+
The Manager analyzes current state and creates a plan with subgoals.
|
466
|
+
"""
|
467
|
+
# Check if we've reached the maximum number of steps
|
468
|
+
if self.step_counter >= self.max_steps:
|
469
|
+
logger.warning(f"⚠️ Reached maximum steps ({self.max_steps})")
|
470
|
+
return self._create_finalize_event(
|
471
|
+
success=False,
|
472
|
+
reason=f"Reached maximum steps ({self.max_steps})",
|
473
|
+
output=f"Reached maximum steps ({self.max_steps})"
|
474
|
+
)
|
363
475
|
|
364
|
-
|
476
|
+
# Continue with Manager execution
|
477
|
+
logger.info(f"📋 Running Manager for planning... (step {self.step_counter}/{self.max_steps})")
|
365
478
|
|
366
|
-
|
367
|
-
|
368
|
-
)
|
479
|
+
# Run Manager workflow
|
480
|
+
handler = self.manager_agent.run()
|
369
481
|
|
370
|
-
|
371
|
-
|
482
|
+
# Stream nested events
|
483
|
+
async for nested_ev in handler.stream_events():
|
484
|
+
self.handle_stream_event(nested_ev, ctx)
|
372
485
|
|
373
|
-
|
486
|
+
result = await handler
|
374
487
|
|
375
|
-
|
376
|
-
|
488
|
+
# Manager already updated shared_state, just return event with results
|
489
|
+
return ManagerPlanEvent(
|
490
|
+
plan=result["plan"],
|
491
|
+
current_subgoal=result["current_subgoal"],
|
492
|
+
completed_plan=result["completed_plan"],
|
493
|
+
thought=result["thought"],
|
494
|
+
manager_answer=result.get("manager_answer", "")
|
495
|
+
)
|
377
496
|
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
tasks=tasks,
|
387
|
-
steps=self.step_counter,
|
388
|
-
)
|
389
|
-
if not self.tasks:
|
390
|
-
logger.warning("No tasks generated by planner")
|
391
|
-
output = "Planner did not generate any tasks"
|
392
|
-
tasks = self.task_manager.get_task_history()
|
393
|
-
return FinalizeEvent(
|
394
|
-
success=False,
|
395
|
-
reason=output,
|
396
|
-
output=output,
|
397
|
-
task=tasks,
|
398
|
-
tasks=tasks,
|
399
|
-
steps=self.step_counter,
|
400
|
-
)
|
497
|
+
@step
|
498
|
+
async def handle_manager_plan(
|
499
|
+
self,
|
500
|
+
ctx: Context,
|
501
|
+
ev: ManagerPlanEvent
|
502
|
+
) -> ExecutorInputEvent | FinalizeEvent:
|
503
|
+
"""
|
504
|
+
Process Manager output and decide next step.
|
401
505
|
|
402
|
-
|
506
|
+
Checks if task is complete or if Executor should take action.
|
507
|
+
"""
|
508
|
+
# Check for answer-type termination
|
509
|
+
if ev.manager_answer.strip():
|
510
|
+
logger.info(f"💬 Manager provided answer: {ev.manager_answer}")
|
511
|
+
self.shared_state.progress_status = f"Answer: {ev.manager_answer}"
|
512
|
+
|
513
|
+
return self._create_finalize_event(
|
514
|
+
success=True,
|
515
|
+
reason=ev.manager_answer,
|
516
|
+
output=ev.manager_answer
|
517
|
+
)
|
403
518
|
|
404
|
-
|
405
|
-
|
406
|
-
|
407
|
-
import traceback
|
519
|
+
# Continue to Executor with current subgoal
|
520
|
+
logger.info(f"▶️ Proceeding to Executor with subgoal: {ev.current_subgoal}")
|
521
|
+
return ExecutorInputEvent(current_subgoal=ev.current_subgoal)
|
408
522
|
|
409
|
-
|
410
|
-
|
411
|
-
|
412
|
-
|
413
|
-
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
|
523
|
+
@step
|
524
|
+
async def run_executor(
|
525
|
+
self,
|
526
|
+
ctx: Context,
|
527
|
+
ev: ExecutorInputEvent
|
528
|
+
) -> ExecutorResultEvent:
|
529
|
+
"""
|
530
|
+
Run Executor action phase.
|
531
|
+
|
532
|
+
The Executor selects and executes a specific action for the current subgoal.
|
533
|
+
"""
|
534
|
+
logger.info("⚡ Running Executor for action...")
|
535
|
+
|
536
|
+
# Run Executor workflow (Executor will update shared_state directly)
|
537
|
+
handler = self.executor_agent.run(subgoal=ev.current_subgoal)
|
538
|
+
|
539
|
+
# Stream nested events
|
540
|
+
async for nested_ev in handler.stream_events():
|
541
|
+
self.handle_stream_event(nested_ev, ctx)
|
542
|
+
|
543
|
+
result = await handler
|
544
|
+
|
545
|
+
# Update coordination state after execution
|
546
|
+
self.shared_state.action_history.append(result["action"])
|
547
|
+
self.shared_state.summary_history.append(result["summary"])
|
548
|
+
self.shared_state.action_outcomes.append(result["outcome"])
|
549
|
+
self.shared_state.error_descriptions.append(result["error"])
|
550
|
+
self.shared_state.last_action = result["action"]
|
551
|
+
self.shared_state.last_summary = result["summary"]
|
552
|
+
self.shared_state.last_action_thought = result.get("thought", "")
|
553
|
+
self.shared_state.action_pool.append(result["action_json"])
|
554
|
+
self.shared_state.progress_status = self.shared_state.completed_plan
|
555
|
+
|
556
|
+
return ExecutorResultEvent(
|
557
|
+
action=result["action"],
|
558
|
+
outcome=result["outcome"],
|
559
|
+
error=result["error"],
|
560
|
+
summary=result["summary"]
|
561
|
+
)
|
419
562
|
|
420
563
|
@step
|
421
|
-
async def
|
422
|
-
self,
|
423
|
-
|
564
|
+
async def handle_executor_result(
|
565
|
+
self,
|
566
|
+
ctx: Context,
|
567
|
+
ev: ExecutorResultEvent
|
568
|
+
) -> ManagerInputEvent:
|
424
569
|
"""
|
425
|
-
|
570
|
+
Process Executor result and continue.
|
426
571
|
|
427
|
-
|
428
|
-
|
572
|
+
Checks for error escalation and loops back to Manager.
|
573
|
+
Note: Max steps check is now done in run_manager pre-flight.
|
429
574
|
"""
|
430
|
-
|
431
|
-
|
575
|
+
# Check error escalation
|
576
|
+
err_thresh = self.shared_state.err_to_manager_thresh
|
432
577
|
|
433
|
-
self.
|
434
|
-
|
578
|
+
if len(self.shared_state.action_outcomes) >= err_thresh:
|
579
|
+
latest = self.shared_state.action_outcomes[-err_thresh:]
|
580
|
+
error_count = sum(1 for o in latest if not o)
|
581
|
+
if error_count == err_thresh:
|
582
|
+
logger.warning(f"⚠️ Error escalation: {err_thresh} consecutive errors")
|
583
|
+
self.shared_state.error_flag_plan = True
|
435
584
|
|
436
|
-
|
437
|
-
|
438
|
-
task = Task(
|
439
|
-
description=self.goal,
|
440
|
-
status=self.task_manager.STATUS_PENDING,
|
441
|
-
agent_type="Default",
|
442
|
-
)
|
585
|
+
self.step_counter += 1
|
586
|
+
logger.info(f"🔄 Step {self.step_counter}/{self.max_steps} complete, looping to Manager")
|
443
587
|
|
444
|
-
|
588
|
+
# Always loop back to Manager (it will check max steps in pre-flight)
|
589
|
+
return ManagerInputEvent()
|
445
590
|
|
446
|
-
|
591
|
+
# ========================================================================
|
592
|
+
# End Manager/Executor Workflow Steps
|
593
|
+
# ========================================================================
|
447
594
|
|
448
595
|
@step
|
449
596
|
async def finalize(self, ctx: Context, ev: FinalizeEvent) -> StopEvent:
|