droidrun 0.3.10.dev8__tar.gz → 0.3.10.dev10__tar.gz

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 (125) hide show
  1. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/PKG-INFO +1 -1
  2. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/codeact/codeact_agent.py +14 -15
  3. droidrun-0.3.10.dev10/droidrun/agent/common/__init__.py +0 -0
  4. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/droid/droid_agent.py +31 -73
  5. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/droid/events.py +52 -10
  6. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/executor/executor_agent.py +3 -5
  7. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/manager/manager_agent.py +31 -10
  8. droidrun-0.3.10.dev10/droidrun/agent/oneflows/__init__.py +0 -0
  9. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/executer.py +16 -10
  10. droidrun-0.3.10.dev10/droidrun/app_cards/__init__.py +0 -0
  11. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/cli/main.py +2 -2
  12. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/app_cards/README.md +5 -3
  13. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/prompts/codeact/system.jinja2 +0 -1
  14. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/config_manager/config_manager.py +2 -2
  15. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/config_manager/path_resolver.py +14 -14
  16. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/macro/cli.py +2 -2
  17. droidrun-0.3.10.dev10/droidrun/telemetry/__init__.py +15 -0
  18. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/telemetry/events.py +14 -5
  19. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/telemetry/tracker.py +36 -8
  20. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/tools/adb.py +2 -2
  21. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/pyproject.toml +1 -1
  22. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/uv.lock +1 -1
  23. droidrun-0.3.10.dev8/droidrun/telemetry/__init__.py +0 -4
  24. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/.github/workflows/bounty.yml +0 -0
  25. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/.github/workflows/publish.yml +0 -0
  26. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/.gitignore +0 -0
  27. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/.python-version +0 -0
  28. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/CHANGELOG.md +0 -0
  29. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/CONTRIBUTING.md +0 -0
  30. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/LICENSE +0 -0
  31. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/MANIFEST.in +0 -0
  32. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/README.md +0 -0
  33. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/.generated-files.txt +0 -0
  34. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/docs.json +0 -0
  35. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/favicon.png +0 -0
  36. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/logo/dark.svg +0 -0
  37. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/logo/light.svg +0 -0
  38. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v1/concepts/agent.mdx +0 -0
  39. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v1/concepts/android-control.mdx +0 -0
  40. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v1/concepts/portal-app.mdx +0 -0
  41. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v1/overview.mdx +0 -0
  42. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v1/quickstart.mdx +0 -0
  43. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v2/concepts/agent.mdx +0 -0
  44. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v2/concepts/android-control.mdx +0 -0
  45. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v2/concepts/planning.mdx +0 -0
  46. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v2/concepts/portal-app.mdx +0 -0
  47. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v2/concepts/tracing.mdx +0 -0
  48. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v2/overview.mdx +0 -0
  49. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v2/quickstart.mdx +0 -0
  50. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/concepts/agent.mdx +0 -0
  51. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/concepts/android-tools.mdx +0 -0
  52. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/concepts/models.mdx +0 -0
  53. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/concepts/portal-app.mdx +0 -0
  54. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/guides/cli.mdx +0 -0
  55. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/guides/gemini.mdx +0 -0
  56. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/guides/ollama.mdx +0 -0
  57. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/guides/openailike.mdx +0 -0
  58. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/guides/overview.mdx +0 -0
  59. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/guides/telemetry.mdx +0 -0
  60. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/images/portal_apk.png +0 -0
  61. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/overview.mdx +0 -0
  62. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/quickstart.mdx +0 -0
  63. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/sdk/adb-tools.mdx +0 -0
  64. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/sdk/base-tools.mdx +0 -0
  65. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/sdk/droid-agent.mdx +0 -0
  66. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/docs/v3/sdk/ios-tools.mdx +0 -0
  67. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/__init__.py +0 -0
  68. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/__main__.py +0 -0
  69. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/__init__.py +0 -0
  70. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/codeact/__init__.py +0 -0
  71. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/codeact/events.py +0 -0
  72. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/common/constants.py +0 -0
  73. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/common/events.py +0 -0
  74. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/context/__init__.py +0 -0
  75. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/context/episodic_memory.py +0 -0
  76. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/context/task_manager.py +0 -0
  77. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/droid/__init__.py +0 -0
  78. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/executor/__init__.py +0 -0
  79. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/executor/events.py +0 -0
  80. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/executor/prompts.py +0 -0
  81. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/manager/__init__.py +0 -0
  82. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/manager/events.py +0 -0
  83. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/manager/prompts.py +0 -0
  84. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/oneflows/app_starter_workflow.py +0 -0
  85. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/oneflows/text_manipulator.py +0 -0
  86. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/usage.py +0 -0
  87. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/__init__.py +0 -0
  88. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/async_utils.py +0 -0
  89. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/chat_utils.py +0 -0
  90. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/device_state_formatter.py +0 -0
  91. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/inference.py +0 -0
  92. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/llm_picker.py +0 -0
  93. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/message_utils.py +0 -0
  94. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/tools.py +0 -0
  95. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/agent/utils/trajectory.py +0 -0
  96. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/app_cards/app_card_provider.py +0 -0
  97. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/app_cards/providers/__init__.py +0 -0
  98. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/app_cards/providers/composite_provider.py +0 -0
  99. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/app_cards/providers/local_provider.py +0 -0
  100. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/app_cards/providers/server_provider.py +0 -0
  101. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/cli/__init__.py +0 -0
  102. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/cli/logs.py +0 -0
  103. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/app_cards/app_cards.json +0 -0
  104. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/app_cards/gmail.md +0 -0
  105. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/prompts/codeact/user.jinja2 +0 -0
  106. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/prompts/executor/rev1.jinja2 +0 -0
  107. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/prompts/executor/system.jinja2 +0 -0
  108. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/prompts/manager/rev1.jinja2 +0 -0
  109. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config/prompts/manager/system.jinja2 +0 -0
  110. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10/droidrun}/config_example.yaml +0 -0
  111. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/config_manager/__init__.py +0 -0
  112. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/config_manager/prompt_loader.py +0 -0
  113. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/macro/__init__.py +0 -0
  114. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/macro/__main__.py +0 -0
  115. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/macro/replay.py +0 -0
  116. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/portal.py +0 -0
  117. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/telemetry/phoenix.py +0 -0
  118. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/tools/__init__.py +0 -0
  119. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/tools/ios.py +0 -0
  120. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/tools/portal_client.py +0 -0
  121. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/droidrun/tools/tools.py +0 -0
  122. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/gen-docs-sdk-ref.sh +0 -0
  123. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/setup.py +0 -0
  124. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/static/droidrun-dark.png +0 -0
  125. {droidrun-0.3.10.dev8 → droidrun-0.3.10.dev10}/static/droidrun.png +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: droidrun
3
- Version: 0.3.10.dev8
3
+ Version: 0.3.10.dev10
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
@@ -75,7 +75,6 @@ class CodeActAgent(Workflow):
75
75
  self.remembered_info = None
76
76
 
77
77
  self.goal = None
78
- self.steps_counter = 0
79
78
  self.code_exec_counter = 0
80
79
 
81
80
  # Build tool list
@@ -164,7 +163,7 @@ Now, describe the next step you will take to address the original goal: {goal}""
164
163
  assert len(chat_history) > 0, "Chat history cannot be empty."
165
164
  ctx.write_event_to_stream(ev)
166
165
 
167
- if self.steps_counter >= self.max_steps:
166
+ if self.shared_state.step_number + 1 > self.max_steps:
168
167
  ev = TaskEndEvent(
169
168
  success=False,
170
169
  reason=f"Reached max step count of {self.max_steps} steps",
@@ -172,8 +171,7 @@ Now, describe the next step you will take to address the original goal: {goal}""
172
171
  ctx.write_event_to_stream(ev)
173
172
  return ev
174
173
 
175
- self.steps_counter += 1
176
- logger.info(f"🧠 Step {self.steps_counter}: Thinking...")
174
+ logger.info(f"🧠 Step {self.shared_state.step_number + 1}: Thinking...")
177
175
 
178
176
  model = self.llm.class_name()
179
177
 
@@ -200,15 +198,17 @@ Now, describe the next step you will take to address the original goal: {goal}""
200
198
  formatted_text, focused_text, a11y_tree, phone_state = format_device_state(raw_state)
201
199
 
202
200
  # Update shared_state if available
203
- if self.shared_state is not None:
204
- self.shared_state.formatted_device_state = formatted_text
205
- self.shared_state.focused_text = focused_text
206
- self.shared_state.a11y_tree = a11y_tree
207
- self.shared_state.phone_state = phone_state
208
-
209
- # Extract and store package/app name
210
- self.shared_state.current_package_name = phone_state.get('packageName', 'Unknown')
211
- self.shared_state.current_app_name = phone_state.get('currentApp', 'Unknown')
201
+ assert self.shared_state is not None, "Shared state is not set"
202
+ self.shared_state.formatted_device_state = formatted_text
203
+ self.shared_state.focused_text = focused_text
204
+ self.shared_state.a11y_tree = a11y_tree
205
+ self.shared_state.phone_state = phone_state
206
+
207
+ # Extract and store package/app name (using unified update method)
208
+ self.shared_state.update_current_app(
209
+ package_name=phone_state.get('packageName', 'Unknown'),
210
+ activity_name=phone_state.get('currentApp', 'Unknown')
211
+ )
212
212
 
213
213
  # Stream formatted state for trajectory
214
214
  ctx.write_event_to_stream(RecordUIStateEvent(ui_state=a11y_tree))
@@ -235,6 +235,7 @@ Now, describe the next step you will take to address the original goal: {goal}""
235
235
  usage = None
236
236
 
237
237
  await self.chat_memory.aput(response.message)
238
+ self.shared_state.step_number += 1
238
239
 
239
240
  code, thoughts = chat_utils.extract_code_and_thought(response.message.content)
240
241
 
@@ -359,8 +360,6 @@ Now, describe the next step you will take to address the original goal: {goal}""
359
360
  {
360
361
  "success": ev.success,
361
362
  "reason": ev.reason,
362
- "output": ev.reason,
363
- "codeact_steps": self.steps_counter,
364
363
  "code_executions": self.code_exec_counter,
365
364
  }
366
365
  )
@@ -124,7 +124,11 @@ class DroidAgent(Workflow):
124
124
  """
125
125
 
126
126
  self.user_id = kwargs.pop("user_id", None)
127
-
127
+ self.runtype = kwargs.pop("runtype", "developer")
128
+ self.shared_state = DroidAgentState(
129
+ instruction=goal,
130
+ err_to_manager_thresh=2
131
+ )
128
132
  base_config = config
129
133
 
130
134
  self.config = DroidRunConfig(
@@ -154,7 +158,6 @@ class DroidAgent(Workflow):
154
158
  " • If installed via pip: `uv pip install droidrun[phoenix]`\n"
155
159
  )
156
160
 
157
- self.goal = goal
158
161
  self.timeout = timeout
159
162
  self.custom_tools = custom_tools or {}
160
163
 
@@ -180,8 +183,7 @@ class DroidAgent(Workflow):
180
183
  self.app_opener_llm = llms
181
184
 
182
185
 
183
- self.event_counter = 0
184
- self.trajectory = Trajectory(goal=goal)
186
+ self.trajectory = Trajectory(goal=self.shared_state.instruction)
185
187
  self.task_manager = TaskManager()
186
188
  self.task_iter = None
187
189
  self.current_episodic_memory = None
@@ -202,10 +204,6 @@ class DroidAgent(Workflow):
202
204
  # Set app_opener_llm on tools instance for open_app custom tool
203
205
  self.tools_instance.app_opener_llm = self.app_opener_llm
204
206
 
205
- self.shared_state = DroidAgentState(
206
- instruction=goal,
207
- err_to_manager_thresh=2
208
- )
209
207
 
210
208
  if self.config.agent.reasoning:
211
209
  logger.info("📝 Initializing Manager and Executor Agents...")
@@ -236,7 +234,7 @@ class DroidAgent(Workflow):
236
234
 
237
235
  capture(
238
236
  DroidAgentInitEvent(
239
- goal=goal,
237
+ goal=self.shared_state.instruction,
240
238
  llms={
241
239
  "manager": self.manager_llm.class_name() if self.manager_llm else "None",
242
240
  "executor": self.executor_llm.class_name() if self.executor_llm else "None",
@@ -257,6 +255,7 @@ class DroidAgent(Workflow):
257
255
  enable_tracing=self.config.tracing.enabled,
258
256
  debug=self.config.logging.debug,
259
257
  save_trajectories=self.config.logging.save_trajectory,
258
+ runtype=self.runtype,
260
259
  ),
261
260
  self.user_id,
262
261
  )
@@ -269,35 +268,6 @@ class DroidAgent(Workflow):
269
268
  """
270
269
  return super().run(*args, **kwargs)
271
270
 
272
- def _create_finalize_event(
273
- self,
274
- success: bool,
275
- reason: str,
276
- output: str
277
- ) -> FinalizeEvent:
278
- """
279
- Single source of truth for creating FinalizeEvent.
280
-
281
- This helper ensures all FinalizeEvent creation is consistent
282
- across the workflow.
283
-
284
- Args:
285
- success: Whether the task succeeded
286
- reason: Reason for completion (deprecated, use output)
287
- output: Output message
288
-
289
- Returns:
290
- FinalizeEvent ready to be returned
291
- """
292
- return FinalizeEvent(
293
- success=success,
294
- reason=reason,
295
- output=output,
296
- task=[], # TODO: use the final plan as the tasks and the goal as task
297
- tasks=[],
298
- steps=self.step_counter
299
- )
300
-
301
271
  @step
302
272
  async def execute_task(self, ctx: Context, ev: CodeActExecuteEvent) -> CodeActResultEvent:
303
273
  """
@@ -339,14 +309,12 @@ class DroidAgent(Workflow):
339
309
  success=True,
340
310
  reason=result["reason"],
341
311
  task=task,
342
- steps=result["codeact_steps"],
343
312
  )
344
313
  else:
345
314
  return CodeActResultEvent(
346
315
  success=False,
347
316
  reason=result["reason"],
348
317
  task=task,
349
- steps=result["codeact_steps"],
350
318
  )
351
319
 
352
320
  except Exception as e:
@@ -354,35 +322,25 @@ class DroidAgent(Workflow):
354
322
  if self.config.logging.debug:
355
323
  import traceback
356
324
  logger.error(traceback.format_exc())
357
- return CodeActResultEvent(success=False, reason=f"Error: {str(e)}", task=task, steps=0)
325
+ return CodeActResultEvent(success=False, reason=f"Error: {str(e)}", task=task)
358
326
 
359
327
  @step
360
328
  async def handle_codeact_execute(
361
329
  self, ctx: Context, ev: CodeActResultEvent
362
330
  ) -> FinalizeEvent:
363
331
  try:
364
- task = ev.task
365
332
  return FinalizeEvent(
366
333
  success=ev.success,
367
- reason=ev.reason,
368
- output=ev.reason,
369
- task=[task],
370
- tasks=[task],
371
- steps=ev.steps,
334
+ reason=ev.reason
372
335
  )
373
336
  except Exception as e:
374
337
  logger.error(f"❌ Error during DroidAgent execution: {e}")
375
338
  if self.config.logging.debug:
376
339
  import traceback
377
340
  logger.error(traceback.format_exc())
378
- tasks = self.task_manager.get_task_history()
379
341
  return FinalizeEvent(
380
342
  success=False,
381
343
  reason=str(e),
382
- output=str(e),
383
- task=tasks,
384
- tasks=tasks,
385
- steps=self.step_counter,
386
344
  )
387
345
 
388
346
  @step
@@ -395,16 +353,14 @@ class DroidAgent(Workflow):
395
353
  Returns:
396
354
  Event to trigger next step based on reasoning mode
397
355
  """
398
- logger.info(f"🚀 Running DroidAgent to achieve goal: {self.goal}")
356
+ logger.info(f"🚀 Running DroidAgent to achieve goal: {self.shared_state.instruction}")
399
357
  ctx.write_event_to_stream(ev)
400
358
 
401
- self.step_counter = 0
402
- self.retry_counter = 0
403
359
 
404
360
  if not self.config.agent.reasoning:
405
- logger.info(f"🔄 Direct execution mode - executing goal: {self.goal}")
361
+ logger.info(f"🔄 Direct execution mode - executing goal: {self.shared_state.instruction}")
406
362
  task = Task(
407
- description=self.goal,
363
+ description=self.shared_state.instruction,
408
364
  status=self.task_manager.STATUS_PENDING,
409
365
  agent_type="Default",
410
366
  )
@@ -429,15 +385,14 @@ class DroidAgent(Workflow):
429
385
  Pre-flight checks for termination before running manager.
430
386
  The Manager analyzes current state and creates a plan with subgoals.
431
387
  """
432
- if self.step_counter >= self.config.agent.max_steps:
388
+ if self.shared_state.step_number >= self.config.agent.max_steps:
433
389
  logger.warning(f"⚠️ Reached maximum steps ({self.config.agent.max_steps})")
434
- return self._create_finalize_event(
390
+ return FinalizeEvent(
435
391
  success=False,
436
- reason=f"Reached maximum steps ({self.config.agent.max_steps})",
437
- output=f"Reached maximum steps ({self.config.agent.max_steps})"
392
+ reason=f"Reached maximum steps ({self.config.agent.max_steps})"
438
393
  )
439
394
 
440
- logger.info(f"📋 Running Manager for planning... (step {self.step_counter}/{self.config.agent.max_steps})")
395
+ logger.info(f"📋 Running Manager for planning... (step {self.shared_state.step_number}/{self.config.agent.max_steps})")
441
396
 
442
397
  # Run Manager workflow
443
398
  handler = self.manager_agent.run()
@@ -472,10 +427,9 @@ class DroidAgent(Workflow):
472
427
  logger.info(f"💬 Manager provided answer: {ev.manager_answer}")
473
428
  self.shared_state.progress_status = f"Answer: {ev.manager_answer}"
474
429
 
475
- return self._create_finalize_event(
430
+ return FinalizeEvent(
476
431
  success=True,
477
- reason=ev.manager_answer,
478
- output=ev.manager_answer
432
+ reason=ev.manager_answer
479
433
  )
480
434
 
481
435
  # Continue to Executor with current subgoal
@@ -533,7 +487,7 @@ class DroidAgent(Workflow):
533
487
  Checks for error escalation and loops back to Manager.
534
488
  Note: Max steps check is now done in run_manager pre-flight.
535
489
  """
536
- # Check error escalation
490
+ # Check error escalation and reset flag when errors are resolved
537
491
  err_thresh = self.shared_state.err_to_manager_thresh
538
492
 
539
493
  if len(self.shared_state.action_outcomes) >= err_thresh:
@@ -542,9 +496,13 @@ class DroidAgent(Workflow):
542
496
  if error_count == err_thresh:
543
497
  logger.warning(f"⚠️ Error escalation: {err_thresh} consecutive errors")
544
498
  self.shared_state.error_flag_plan = True
499
+ else:
500
+ if self.shared_state.error_flag_plan:
501
+ logger.info("✅ Error resolved - resetting error flag")
502
+ self.shared_state.error_flag_plan = False
545
503
 
546
- self.step_counter += 1
547
- logger.info(f"🔄 Step {self.step_counter}/{self.config.agent.max_steps} complete, looping to Manager")
504
+ self.shared_state.step_number += 1
505
+ logger.info(f"🔄 Step {self.shared_state.step_number}/{self.config.agent.max_steps} complete, looping to Manager")
548
506
 
549
507
  return ManagerInputEvent()
550
508
 
@@ -557,10 +515,11 @@ class DroidAgent(Workflow):
557
515
  ctx.write_event_to_stream(ev)
558
516
  capture(
559
517
  DroidAgentFinalizeEvent(
560
- tasks=",".join([f"{t.agent_type}:{t.description}" for t in ev.task]),
561
518
  success=ev.success,
562
- output=ev.output,
563
- steps=ev.steps,
519
+ reason=ev.reason,
520
+ steps=self.shared_state.step_number,
521
+ unique_packages_count=len(self.shared_state._visited_packages),
522
+ unique_activities_count=len(self.shared_state._visited_activities),
564
523
  ),
565
524
  self.user_id,
566
525
  )
@@ -569,8 +528,7 @@ class DroidAgent(Workflow):
569
528
  result = {
570
529
  "success": ev.success,
571
530
  "reason": ev.reason,
572
- "output": ev.output,
573
- "steps": ev.steps,
531
+ "steps": self.shared_state.step_number,
574
532
  }
575
533
 
576
534
  if self.trajectory and self.config.logging.save_trajectory != "none":
@@ -26,18 +26,11 @@ class CodeActResultEvent(Event):
26
26
  success: bool
27
27
  reason: str
28
28
  task: Task
29
- steps: int
30
29
 
31
30
 
32
31
  class FinalizeEvent(Event):
33
32
  success: bool
34
- # deprecated. use output instead.
35
33
  reason: str
36
- output: str
37
- # deprecated. use tasks instead.
38
- task: List[Task]
39
- tasks: List[Task]
40
- steps: int = 1
41
34
 
42
35
  class TaskRunnerEvent(Event):
43
36
  pass
@@ -50,9 +43,12 @@ class DroidAgentState(BaseModel):
50
43
  model_config = ConfigDict(arbitrary_types_allowed=True)
51
44
  # Task context
52
45
  instruction: str = ""
46
+ step_number: int = 0
53
47
  # App Cards
54
48
  app_card: str = ""
55
49
  app_card_loading_task: asyncio.Task[str] | None = None
50
+ _app_card_package: str = "" # Track which package the loading task is for
51
+ _app_card_instruction: str = "" # Track which instruction the loading task is for
56
52
  # Formatted device state for prompts (complete text)
57
53
  formatted_device_state: str = ""
58
54
 
@@ -63,9 +59,11 @@ class DroidAgentState(BaseModel):
63
59
  a11y_tree: List[Dict] = Field(default_factory=list)
64
60
  phone_state: Dict = Field(default_factory=dict)
65
61
 
66
- # Derived fields (extracted from phone_state)
67
- current_package_name: str = ""
68
- current_app_name: str = ""
62
+ # Private fields
63
+ _current_package_name: str = ""
64
+ _current_activity_name: str = ""
65
+ _visited_packages: set = Field(default_factory=set)
66
+ _visited_activities: set = Field(default_factory=set)
69
67
 
70
68
  # Previous device state (for before/after comparison in Manager)
71
69
  previous_formatted_device_state: str = ""
@@ -108,6 +106,50 @@ class DroidAgentState(BaseModel):
108
106
  # Output
109
107
  output_dir: str = ""
110
108
 
109
+ @property
110
+ def current_package_name(self) -> str:
111
+ """Get current package name"""
112
+ return self._current_package_name
113
+
114
+ @property
115
+ def current_activity_name(self) -> str:
116
+ """Get current activity name"""
117
+ return self._current_activity_name
118
+
119
+ def update_current_app(self, package_name: str, activity_name: str):
120
+ """
121
+ Update package and activity together, capturing telemetry event only once.
122
+
123
+ This prevents duplicate PackageVisitEvents when both package and activity change.
124
+ """
125
+ # Check if either changed
126
+ package_changed = package_name != self._current_package_name
127
+ activity_changed = activity_name != self._current_activity_name
128
+
129
+ if not (package_changed or activity_changed):
130
+ return # No change, nothing to do
131
+
132
+ # Update tracking sets
133
+ if package_changed and package_name:
134
+ self._visited_packages.add(package_name)
135
+ if activity_changed and activity_name:
136
+ self._visited_activities.add(activity_name)
137
+
138
+ # Update values
139
+ self._current_package_name = package_name
140
+ self._current_activity_name = activity_name
141
+
142
+ # Capture telemetry event for any change
143
+ # This ensures we track when apps close or transitions to empty state occur
144
+ from droidrun.telemetry import PackageVisitEvent, capture
145
+ capture(
146
+ PackageVisitEvent(
147
+ package_name=package_name or "Unknown",
148
+ activity_name=activity_name or "Unknown",
149
+ step_number=self.step_number,
150
+ )
151
+ )
152
+
111
153
 
112
154
  # ============================================================================
113
155
  # Manager/Executor coordination events
@@ -39,7 +39,7 @@ if TYPE_CHECKING:
39
39
  logger = logging.getLogger("droidrun")
40
40
 
41
41
 
42
- class ExecutorAgent(Workflow): # TODO: Fix a bug in bad prompt
42
+ class ExecutorAgent(Workflow):
43
43
  """
44
44
  Action execution agent that performs specific actions.
45
45
 
@@ -115,7 +115,7 @@ class ExecutorAgent(Workflow): # TODO: Fix a bug in bad prompt
115
115
  self.agent_config.get_executor_system_prompt_path(),
116
116
  {
117
117
  "instruction": self.shared_state.instruction,
118
- "app_card": "", # TODO: Implement app card loader
118
+ "app_card": "", # TODO: optionally implement app card loader
119
119
  "device_state": self.shared_state.formatted_device_state,
120
120
  "plan": self.shared_state.plan,
121
121
  "subgoal": subgoal,
@@ -196,9 +196,7 @@ class ExecutorAgent(Workflow): # TODO: Fix a bug in bad prompt
196
196
 
197
197
  outcome, error, summary = await self._execute_action(action_dict, ev.description)
198
198
 
199
- # TODO: Add sleep after action (should be in DroidAgent.handle_executor_result)
200
- # Available via: self.agent_config.after_sleep_action
201
- # await asyncio.sleep(self.agent_config.after_sleep_action)
199
+ await asyncio.sleep(self.agent_config.after_sleep_action)
202
200
 
203
201
  logger.info(f"{'✅' if outcome else '❌'} Execution complete: {summary}")
204
202
 
@@ -328,21 +328,38 @@ class ManagerAgent(Workflow):
328
328
  self.shared_state.phone_state = phone_state
329
329
 
330
330
  # Extract and store package/app name
331
- self.shared_state.current_package_name = phone_state.get('packageName', 'Unknown')
332
- self.shared_state.current_app_name = phone_state.get('currentApp', 'Unknown')
331
+ self.shared_state.update_current_app(
332
+ package_name=phone_state.get('packageName', 'Unknown'),
333
+ activity_name=phone_state.get('currentApp', 'Unknown')
334
+ )
333
335
 
334
336
  # ====================================================================
335
- # Step 1.5: Start loading app card in background
337
+ # Step 1.5: Start loading app card in background (only if package/instruction changed)
336
338
  # ====================================================================
337
339
  if self.app_card_config.enabled:
338
- loading_task = asyncio.create_task(
339
- self.app_card_provider.load_app_card(
340
- package_name=self.shared_state.current_package_name,
341
- instruction=self.shared_state.instruction
340
+ current_package = self.shared_state.current_package_name
341
+ current_instruction = self.shared_state.instruction
342
+
343
+ # Check if we need to start a new loading task
344
+ package_changed = current_package != self.shared_state._app_card_package
345
+ instruction_changed = current_instruction != self.shared_state._app_card_instruction
346
+
347
+ if package_changed or instruction_changed:
348
+ # Cancel old task if it exists and is still running (non-blocking)
349
+ if (self.shared_state.app_card_loading_task and
350
+ not self.shared_state.app_card_loading_task.done()):
351
+ self.shared_state.app_card_loading_task.cancel()
352
+
353
+ # Start new loading task
354
+ loading_task = asyncio.create_task(
355
+ self.app_card_provider.load_app_card(
356
+ package_name=current_package,
357
+ instruction=current_instruction
358
+ )
342
359
  )
343
- )
344
- self.shared_state.app_card_loading_task = loading_task
345
-
360
+ self.shared_state.app_card_loading_task = loading_task
361
+ self.shared_state._app_card_package = current_package
362
+ self.shared_state._app_card_instruction = current_instruction
346
363
  # ====================================================================
347
364
  # Step 2: Capture screenshot if vision enabled
348
365
  # ====================================================================
@@ -433,6 +450,10 @@ class ManagerAgent(Workflow):
433
450
  except asyncio.TimeoutError:
434
451
  # Task not ready yet, use empty string
435
452
  self.shared_state.app_card = ""
453
+ except asyncio.CancelledError:
454
+ # Task was cancelled (app/instruction changed), use empty string
455
+ logger.debug("App card task was cancelled")
456
+ self.shared_state.app_card = ""
436
457
  except Exception as e:
437
458
  logger.warning(f"Error getting app card: {e}")
438
459
  self.shared_state.app_card = ""
@@ -1,3 +1,4 @@
1
+ import asyncio
1
2
  import contextlib
2
3
  import io
3
4
  import logging
@@ -109,7 +110,7 @@ class SimpleCodeExecutor:
109
110
 
110
111
  return output
111
112
 
112
- async def execute(self, state: ExecuterState, code: str) -> str:
113
+ async def execute(self, state: ExecuterState, code: str, timeout: float = 10.0) -> str:
113
114
  """
114
115
  Execute Python code and capture output and return values.
115
116
 
@@ -118,6 +119,7 @@ class SimpleCodeExecutor:
118
119
  Args:
119
120
  state: ExecuterState containing ui_state and other execution context.
120
121
  code: Python code to execute
122
+ timeout: Maximum execution time in seconds (default: 30.0)
121
123
 
122
124
  Returns:
123
125
  str: Output from the execution, including print statements.
@@ -125,12 +127,16 @@ class SimpleCodeExecutor:
125
127
  # Get UI state from the state object
126
128
  ui_state = state.ui_state
127
129
 
128
- # Run the execution in a thread pool executor
129
- output = await self.loop.run_in_executor(
130
- None,
131
- self._execute_in_thread,
132
- code,
133
- ui_state
134
- )
135
-
136
- return output
130
+ try:
131
+ output = await asyncio.wait_for(
132
+ self.loop.run_in_executor(
133
+ None,
134
+ self._execute_in_thread,
135
+ code,
136
+ ui_state
137
+ ),
138
+ timeout=timeout
139
+ )
140
+ return output
141
+ except asyncio.TimeoutError:
142
+ return f"Error: Execution timed out after {timeout} seconds"
File without changes
@@ -300,6 +300,7 @@ async def run_command(
300
300
  tracing_config=tracing_cfg,
301
301
  excluded_tools=excluded_tools,
302
302
  timeout=1000,
303
+ runtype="cli"
303
304
  )
304
305
 
305
306
  # ================================================================
@@ -563,7 +564,6 @@ def run(
563
564
  device_obj.shell("ime disable com.droidrun.portal/.DroidrunKeyboardIME")
564
565
  except Exception:
565
566
  click.echo("Failed to disable DroidRun keyboard")
566
- pass
567
567
 
568
568
 
569
569
  @cli.command()
@@ -624,7 +624,7 @@ def setup(path: str | None, device: str | None, debug: bool):
624
624
  """Install and enable the DroidRun Portal on a device."""
625
625
  from droidrun.config_manager.path_resolver import PathResolver
626
626
 
627
- # Ensure config.yaml exists (check working dir, then project dir)
627
+ # Ensure config.yaml exists (check working dir, then package dir)
628
628
  try:
629
629
  config_path = PathResolver.resolve("config.yaml")
630
630
  console.print(f"[blue]Using existing config: {config_path}[/]")
@@ -60,13 +60,15 @@ App cards support three path types:
60
60
  ```json
61
61
  {"com.google.gm": "gmail.md"}
62
62
  ```
63
- Resolves to: `config/app_cards/gmail.md`
63
+ Resolves to: `config/app_cards/gmail.md` (in package)
64
64
 
65
- 2. **Relative to project root**:
65
+ 2. **Relative paths with PathResolver**:
66
66
  ```json
67
67
  {"com.google.gm": "config/custom_cards/gmail.md"}
68
68
  ```
69
- Resolves to: `config/custom_cards/gmail.md`
69
+ Checks working directory first, then package directory
70
+ - Working dir: `./config/custom_cards/gmail.md`
71
+ - Package dir: `<package>/config/custom_cards/gmail.md`
70
72
 
71
73
  3. **Absolute path**:
72
74
  ```json
@@ -45,7 +45,6 @@ click(1)
45
45
  click(5)
46
46
  complete(success=True, reason="Successfully navigated to Wi-Fi settings and initiated connection to HomeNetwork")
47
47
  ```
48
- ```
49
48
 
50
49
  ## Tools:
51
50
  In addition to the Python Standard Library and any functions you have already written, you can use the following functions:
@@ -431,7 +431,7 @@ class ConfigManager:
431
431
  # Resolution order:
432
432
  # 1) Explicit path arg
433
433
  # 2) DROIDRUN_CONFIG env var
434
- # 3) Default "config.yaml" (checks working dir, then project dir)
434
+ # 3) Default "config.yaml" (checks working dir, then package dir)
435
435
  if path:
436
436
  self.path = PathResolver.resolve(path)
437
437
  else:
@@ -439,7 +439,7 @@ class ConfigManager:
439
439
  if env:
440
440
  self.path = PathResolver.resolve(env)
441
441
  else:
442
- # Default: checks CWD first, then project dir
442
+ # Default: checks CWD first, then package dir
443
443
  self.path = PathResolver.resolve("config.yaml")
444
444
 
445
445
  # Initialize with default config