droidrun 0.3.10.dev11__tar.gz → 0.3.10.dev13__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 (133) hide show
  1. droidrun-0.3.10.dev13/.github/black.yml +19 -0
  2. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/PKG-INFO +1 -1
  3. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/codeact/codeact_agent.py +15 -2
  4. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/droid/droid_agent.py +144 -16
  5. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/droid/events.py +26 -0
  6. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/executor/executor_agent.py +8 -3
  7. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/manager/manager_agent.py +20 -1
  8. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/manager/prompts.py +20 -11
  9. droidrun-0.3.10.dev13/droidrun/agent/scripter/__init__.py +7 -0
  10. droidrun-0.3.10.dev13/droidrun/agent/scripter/events.py +36 -0
  11. droidrun-0.3.10.dev13/droidrun/agent/scripter/scripter_agent.py +281 -0
  12. droidrun-0.3.10.dev13/droidrun/agent/utils/async_utils.py +42 -0
  13. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/executer.py +40 -5
  14. droidrun-0.3.10.dev13/droidrun/agent/utils/llm_loader.py +180 -0
  15. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/tools.py +43 -2
  16. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/cli/main.py +137 -298
  17. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/prompts/executor/system.jinja2 +1 -1
  18. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/prompts/manager/rev1.jinja2 +55 -0
  19. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/prompts/manager/system.jinja2 +51 -0
  20. droidrun-0.3.10.dev13/droidrun/config/prompts/scripter/system.jinja2 +86 -0
  21. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config_example.yaml +79 -0
  22. droidrun-0.3.10.dev13/droidrun/config_manager/__init__.py +52 -0
  23. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config_manager/config_manager.py +123 -1
  24. droidrun-0.3.10.dev13/droidrun/config_manager/safe_execution.py +226 -0
  25. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/pyproject.toml +1 -1
  26. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/uv.lock +1 -1
  27. droidrun-0.3.10.dev11/droidrun/agent/utils/async_utils.py +0 -18
  28. droidrun-0.3.10.dev11/droidrun/config_manager/__init__.py +0 -25
  29. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/.github/workflows/bounty.yml +0 -0
  30. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/.github/workflows/publish.yml +0 -0
  31. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/.gitignore +0 -0
  32. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/.python-version +0 -0
  33. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/CHANGELOG.md +0 -0
  34. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/CONTRIBUTING.md +0 -0
  35. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/LICENSE +0 -0
  36. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/MANIFEST.in +0 -0
  37. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/README.md +0 -0
  38. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/.generated-files.txt +0 -0
  39. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/docs.json +0 -0
  40. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/favicon.png +0 -0
  41. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/logo/dark.svg +0 -0
  42. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/logo/light.svg +0 -0
  43. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v1/concepts/agent.mdx +0 -0
  44. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v1/concepts/android-control.mdx +0 -0
  45. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v1/concepts/portal-app.mdx +0 -0
  46. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v1/overview.mdx +0 -0
  47. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v1/quickstart.mdx +0 -0
  48. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v2/concepts/agent.mdx +0 -0
  49. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v2/concepts/android-control.mdx +0 -0
  50. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v2/concepts/planning.mdx +0 -0
  51. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v2/concepts/portal-app.mdx +0 -0
  52. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v2/concepts/tracing.mdx +0 -0
  53. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v2/overview.mdx +0 -0
  54. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v2/quickstart.mdx +0 -0
  55. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/concepts/agent.mdx +0 -0
  56. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/concepts/android-tools.mdx +0 -0
  57. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/concepts/models.mdx +0 -0
  58. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/concepts/portal-app.mdx +0 -0
  59. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/guides/cli.mdx +0 -0
  60. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/guides/gemini.mdx +0 -0
  61. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/guides/ollama.mdx +0 -0
  62. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/guides/openailike.mdx +0 -0
  63. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/guides/overview.mdx +0 -0
  64. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/guides/telemetry.mdx +0 -0
  65. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/images/portal_apk.png +0 -0
  66. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/overview.mdx +0 -0
  67. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/quickstart.mdx +0 -0
  68. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/sdk/adb-tools.mdx +0 -0
  69. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/sdk/base-tools.mdx +0 -0
  70. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/sdk/droid-agent.mdx +0 -0
  71. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/docs/v3/sdk/ios-tools.mdx +0 -0
  72. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/__init__.py +0 -0
  73. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/__main__.py +0 -0
  74. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/__init__.py +0 -0
  75. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/codeact/__init__.py +0 -0
  76. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/codeact/events.py +0 -0
  77. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/common/__init__.py +0 -0
  78. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/common/constants.py +0 -0
  79. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/common/events.py +0 -0
  80. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/context/__init__.py +0 -0
  81. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/context/episodic_memory.py +0 -0
  82. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/context/task_manager.py +0 -0
  83. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/droid/__init__.py +0 -0
  84. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/executor/__init__.py +0 -0
  85. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/executor/events.py +0 -0
  86. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/executor/prompts.py +0 -0
  87. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/manager/__init__.py +0 -0
  88. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/manager/events.py +0 -0
  89. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/oneflows/__init__.py +0 -0
  90. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/oneflows/app_starter_workflow.py +0 -0
  91. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/oneflows/text_manipulator.py +0 -0
  92. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/usage.py +0 -0
  93. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/__init__.py +0 -0
  94. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/chat_utils.py +0 -0
  95. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/device_state_formatter.py +0 -0
  96. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/inference.py +0 -0
  97. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/llm_picker.py +0 -0
  98. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/message_utils.py +0 -0
  99. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/agent/utils/trajectory.py +0 -0
  100. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/app_cards/__init__.py +0 -0
  101. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/app_cards/app_card_provider.py +0 -0
  102. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/app_cards/providers/__init__.py +0 -0
  103. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/app_cards/providers/composite_provider.py +0 -0
  104. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/app_cards/providers/local_provider.py +0 -0
  105. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/app_cards/providers/server_provider.py +0 -0
  106. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/cli/__init__.py +0 -0
  107. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/cli/logs.py +0 -0
  108. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/app_cards/README.md +0 -0
  109. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/app_cards/app_cards.json +0 -0
  110. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/app_cards/gmail.md +0 -0
  111. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/prompts/codeact/system.jinja2 +0 -0
  112. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/prompts/codeact/user.jinja2 +0 -0
  113. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config/prompts/executor/rev1.jinja2 +0 -0
  114. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config_manager/path_resolver.py +0 -0
  115. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/config_manager/prompt_loader.py +0 -0
  116. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/macro/__init__.py +0 -0
  117. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/macro/__main__.py +0 -0
  118. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/macro/cli.py +0 -0
  119. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/macro/replay.py +0 -0
  120. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/portal.py +0 -0
  121. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/telemetry/__init__.py +0 -0
  122. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/telemetry/events.py +0 -0
  123. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/telemetry/phoenix.py +0 -0
  124. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/telemetry/tracker.py +0 -0
  125. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/tools/__init__.py +0 -0
  126. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/tools/adb.py +0 -0
  127. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/tools/ios.py +0 -0
  128. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/tools/portal_client.py +0 -0
  129. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/droidrun/tools/tools.py +0 -0
  130. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/gen-docs-sdk-ref.sh +0 -0
  131. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/setup.py +0 -0
  132. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/static/droidrun-dark.png +0 -0
  133. {droidrun-0.3.10.dev11 → droidrun-0.3.10.dev13}/static/droidrun.png +0 -0
@@ -0,0 +1,19 @@
1
+ name: Lint
2
+
3
+ on: [push, pull_request]
4
+
5
+ jobs:
6
+ lint:
7
+ runs-on: ubuntu-latest
8
+ steps:
9
+ - uses: actions/checkout@v4
10
+
11
+ - uses: actions/setup-python@v5
12
+ with:
13
+ python-version: "3.13"
14
+
15
+ - uses: psf/black@stable
16
+ with:
17
+ options: "--check --diff --verbose"
18
+ src: "."
19
+ version: "25.9.0"
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: droidrun
3
- Version: 0.3.10.dev11
3
+ Version: 0.3.10.dev13
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
@@ -53,8 +53,10 @@ class CodeActAgent(Workflow):
53
53
  agent_config: AgentConfig,
54
54
  tools_instance: "Tools",
55
55
  custom_tools: dict = None,
56
+ atomic_tools: dict = None,
56
57
  debug: bool = False,
57
58
  shared_state: Optional["DroidAgentState"] = None,
59
+ safe_execution_config=None,
58
60
  *args,
59
61
  **kwargs,
60
62
  ):
@@ -77,8 +79,10 @@ class CodeActAgent(Workflow):
77
79
  self.goal = None
78
80
  self.code_exec_counter = 0
79
81
 
80
- # Build tool list
81
- merged_signatures = {**ATOMIC_ACTION_SIGNATURES, **(custom_tools or {})}
82
+ if atomic_tools is None:
83
+ atomic_tools = ATOMIC_ACTION_SIGNATURES
84
+
85
+ merged_signatures = {**atomic_tools, **(custom_tools or {})}
82
86
 
83
87
  self.tool_list = {}
84
88
  for action_name, signature in merged_signatures.items():
@@ -104,11 +108,20 @@ class CodeActAgent(Workflow):
104
108
  )
105
109
  self.system_prompt = ChatMessage(role="system", content=system_prompt_text)
106
110
 
111
+ # Get safety settings
112
+ safe_mode = self.config.safe_execution
113
+ safe_config = safe_execution_config
114
+
107
115
  self.executor = SimpleCodeExecutor(
108
116
  loop=asyncio.get_event_loop(),
109
117
  locals={},
110
118
  tools=self.tool_list,
111
119
  globals={"__builtins__": __builtins__},
120
+ safe_mode=safe_mode,
121
+ allowed_modules=safe_config.get_allowed_modules() if safe_config and safe_mode else None,
122
+ blocked_modules=safe_config.get_blocked_modules() if safe_config and safe_mode else None,
123
+ allowed_builtins=safe_config.get_allowed_builtins() if safe_config and safe_mode else None,
124
+ blocked_builtins=safe_config.get_blocked_builtins() if safe_config and safe_mode else None,
112
125
  )
113
126
 
114
127
  logger.info("✅ CodeActAgent initialized successfully.")
@@ -7,9 +7,10 @@ Architecture:
7
7
  - When reasoning=True: Uses Manager (planning) + Executor (action) workflows
8
8
  """
9
9
 
10
+ import asyncio
10
11
  import logging
11
12
  from typing import List
12
-
13
+ from droidrun.agent.utils.tools import create_tools_from_config
13
14
  import llama_index.core
14
15
  from llama_index.core.llms.llm import LLM
15
16
  from llama_index.core.workflow import Context, StartEvent, StopEvent, Workflow, step
@@ -29,9 +30,13 @@ from droidrun.agent.droid.events import (
29
30
  FinalizeEvent,
30
31
  ManagerInputEvent,
31
32
  ManagerPlanEvent,
33
+ ScripterExecutorInputEvent,
34
+ ScripterExecutorResultEvent,
32
35
  )
33
36
  from droidrun.agent.executor import ExecutorAgent
34
37
  from droidrun.agent.manager import ManagerAgent
38
+ from droidrun.agent.scripter import ScripterAgent
39
+ from droidrun.agent.utils.async_utils import wrap_async_tools
35
40
  from droidrun.agent.utils.tools import ATOMIC_ACTION_SIGNATURES, open_app
36
41
  from droidrun.agent.utils.trajectory import Trajectory
37
42
  from droidrun.config_manager.config_manager import (
@@ -51,6 +56,7 @@ from droidrun.telemetry import (
51
56
  )
52
57
  from droidrun.telemetry.phoenix import arize_phoenix_callback_handler
53
58
  from droidrun.tools import Tools
59
+ from droidrun.agent.utils.llm_loader import load_agent_llms, validate_llm_dict
54
60
 
55
61
  logger = logging.getLogger("droidrun")
56
62
 
@@ -89,9 +95,8 @@ class DroidAgent(Workflow):
89
95
  def __init__(
90
96
  self,
91
97
  goal: str,
92
- llms: dict[str, LLM] | LLM,
93
- tools: Tools,
94
98
  config: DroidRunConfig | None = None,
99
+ llms: dict[str, LLM] | LLM | None = None,
95
100
  agent_config: AgentConfig | None = None,
96
101
  device_config: DeviceConfig | None = None,
97
102
  tools_config: ToolsConfig | None = None,
@@ -109,9 +114,9 @@ class DroidAgent(Workflow):
109
114
 
110
115
  Args:
111
116
  goal: User's goal or command
112
- llms: Dict of agent-specific LLMs or single LLM for all
113
- tools: Tools instance (AdbTools or IOSTools)
114
- config: Full config override (optional)
117
+ config: Full config (required if llms not provided)
118
+ llms: Optional dict of agent-specific LLMs or single LLM for all.
119
+ If not provided, LLMs will be loaded from config profiles.
115
120
  agent_config: Agent config override (optional)
116
121
  device_config: Device config override (optional)
117
122
  tools_config: Tools config override (optional)
@@ -141,10 +146,31 @@ class DroidAgent(Workflow):
141
146
  llm_profiles=base_config.llm_profiles,
142
147
  )
143
148
 
149
+ # Create tools from config
150
+ tools = create_tools_from_config(self.config.device)
151
+
144
152
  super().__init__(*args, timeout=timeout, **kwargs)
145
153
 
146
154
  self._configure_default_logging(debug=self.config.logging.debug)
147
155
 
156
+ # Load LLMs if not provided
157
+ if llms is None:
158
+ if config is None:
159
+ raise ValueError(
160
+ "Either 'llms' or 'config' must be provided. "
161
+ "If llms is not provided, config is required to load LLMs from profiles."
162
+ )
163
+
164
+
165
+ logger.info("🔄 Loading LLMs from config (llms not provided)...")
166
+
167
+
168
+ llms = load_agent_llms(
169
+ config=self.config,
170
+ **kwargs
171
+ )
172
+ validate_llm_dict(self.config, llms)
173
+
148
174
  if self.config.tracing.enabled:
149
175
  try:
150
176
  handler = arize_phoenix_callback_handler()
@@ -167,11 +193,7 @@ class DroidAgent(Workflow):
167
193
  self.codeact_llm = llms.get('codeact')
168
194
  self.text_manipulator_llm = llms.get('text_manipulator')
169
195
  self.app_opener_llm = llms.get('app_opener')
170
-
171
- if self.config.agent.reasoning and (not self.manager_llm or not self.executor_llm):
172
- raise ValueError("When reasoning=True, 'manager' and 'executor' LLMs must be provided in llms dict")
173
- if not self.codeact_llm:
174
- raise ValueError("'codeact' LLM must be provided in llms dict")
196
+ self.scripter_llm = llms.get('scripter', self.codeact_llm)
175
197
 
176
198
  logger.info("📚 Using agent-specific LLMs from dictionary")
177
199
  else:
@@ -181,6 +203,7 @@ class DroidAgent(Workflow):
181
203
  self.codeact_llm = llms
182
204
  self.text_manipulator_llm = llms
183
205
  self.app_opener_llm = llms
206
+ self.scripter_llm = llms
184
207
 
185
208
 
186
209
  self.trajectory = Trajectory(goal=self.shared_state.instruction)
@@ -188,12 +211,13 @@ class DroidAgent(Workflow):
188
211
  self.task_iter = None
189
212
  self.current_episodic_memory = None
190
213
 
214
+ self.atomic_tools = ATOMIC_ACTION_SIGNATURES.copy()
215
+
191
216
  open_app_tool = {
192
217
  "arguments": ["text"],
193
218
  "description": "Open an app by name. Usage example: {\"action\": \"open_app\", \"text\": \"the name of app\"}",
194
219
  "function": open_app,
195
220
  }
196
- # Merge with user-provided custom tools
197
221
  self.custom_tools = {**self.custom_tools, "open_app": open_app_tool}
198
222
 
199
223
  logger.info("🤖 Initializing DroidAgent...")
@@ -201,8 +225,9 @@ class DroidAgent(Workflow):
201
225
 
202
226
  self.tools_instance = tools
203
227
  self.tools_instance.save_trajectories = self.config.logging.save_trajectory
204
- # Set app_opener_llm on tools instance for open_app custom tool
228
+ # Set LLMs on tools instance for helper tools
205
229
  self.tools_instance.app_opener_llm = self.app_opener_llm
230
+ self.tools_instance.text_manipulator_llm = self.text_manipulator_llm
206
231
 
207
232
 
208
233
  if self.config.agent.reasoning:
@@ -289,8 +314,10 @@ class DroidAgent(Workflow):
289
314
  agent_config=self.config.agent,
290
315
  tools_instance=self.tools_instance,
291
316
  custom_tools=self.custom_tools,
317
+ atomic_tools=self.atomic_tools,
292
318
  debug=self.config.logging.debug,
293
319
  shared_state=self.shared_state,
320
+ safe_execution_config=self.config.safe_execution,
294
321
  timeout=self.timeout,
295
322
  )
296
323
 
@@ -356,6 +383,15 @@ class DroidAgent(Workflow):
356
383
  logger.info(f"🚀 Running DroidAgent to achieve goal: {self.shared_state.instruction}")
357
384
  ctx.write_event_to_stream(ev)
358
385
 
386
+ self.tools_instance._set_context(ctx)
387
+
388
+ if not hasattr(self, '_tools_wrapped') and not self.config.agent.reasoning:
389
+
390
+ self.atomic_tools = wrap_async_tools(self.atomic_tools)
391
+ self.custom_tools = wrap_async_tools(self.custom_tools)
392
+
393
+ self._tools_wrapped = True
394
+ logger.debug("✅ Async tools wrapped for synchronous execution contexts")
359
395
 
360
396
  if not self.config.agent.reasoning:
361
397
  logger.info(f"🔄 Direct execution mode - executing goal: {self.shared_state.instruction}")
@@ -416,11 +452,11 @@ class DroidAgent(Workflow):
416
452
  self,
417
453
  ctx: Context,
418
454
  ev: ManagerPlanEvent
419
- ) -> ExecutorInputEvent | FinalizeEvent:
455
+ ) -> ExecutorInputEvent | ScripterExecutorInputEvent | FinalizeEvent:
420
456
  """
421
457
  Process Manager output and decide next step.
422
458
 
423
- Checks if task is complete or if Executor should take action.
459
+ Checks if task is complete, if ScripterAgent should run, or if Executor should take action.
424
460
  """
425
461
  # Check for answer-type termination
426
462
  if ev.manager_answer.strip():
@@ -432,6 +468,21 @@ class DroidAgent(Workflow):
432
468
  reason=ev.manager_answer
433
469
  )
434
470
 
471
+ # Check for <script> tag in current_subgoal, then extract from full plan
472
+ if '<script>' in ev.current_subgoal:
473
+ # Found script tag in subgoal - now search the entire plan
474
+ start_idx = ev.plan.find('<script>')
475
+ end_idx = ev.plan.find('</script>')
476
+
477
+ if start_idx != -1 and end_idx != -1 and end_idx > start_idx:
478
+ # Extract content between first <script> and first </script> in plan
479
+ task = ev.plan[start_idx + len('<script>'):end_idx].strip()
480
+ logger.info(f"🐍 Routing to ScripterAgent: {task[:80]}...")
481
+ return ScripterExecutorInputEvent(task=task)
482
+ else:
483
+ # <script> found in subgoal but not properly closed in plan - log warning
484
+ logger.warning("⚠️ Found <script> in subgoal but not properly closed in plan, treating as regular subgoal")
485
+
435
486
  # Continue to Executor with current subgoal
436
487
  logger.info(f"▶️ Proceeding to Executor with subgoal: {ev.current_subgoal}")
437
488
  return ExecutorInputEvent(current_subgoal=ev.current_subgoal)
@@ -507,7 +558,82 @@ class DroidAgent(Workflow):
507
558
  return ManagerInputEvent()
508
559
 
509
560
  # ========================================================================
510
- # End Manager/Executor Workflow Steps
561
+ # Script Executor Workflow Steps
562
+ # ========================================================================
563
+
564
+ @step
565
+ async def run_scripter(
566
+ self,
567
+ ctx: Context,
568
+ ev: ScripterExecutorInputEvent
569
+ ) -> ScripterExecutorResultEvent:
570
+ """
571
+ Instantiate and run ScripterAgent for off-device operations.
572
+ """
573
+ logger.info(f"🐍 Starting ScripterAgent for task: {ev.task[:2000]}...")
574
+
575
+ # Create fresh ScripterAgent instance for this task
576
+ scripter_agent = ScripterAgent(
577
+ llm=self.scripter_llm,
578
+ agent_config=self.config.agent,
579
+ shared_state=self.shared_state,
580
+ task=ev.task,
581
+ safe_execution_config=self.config.safe_execution,
582
+ timeout=self.timeout
583
+ )
584
+
585
+ # Run ScripterAgent workflow
586
+ handler = scripter_agent.run()
587
+
588
+ # Stream nested events
589
+ async for nested_ev in handler.stream_events():
590
+ ctx.write_event_to_stream(nested_ev)
591
+
592
+ result = await handler
593
+
594
+ # Store in shared state
595
+ script_record = {
596
+ 'task': ev.task,
597
+ 'message': result['message'],
598
+ 'success': result['success'],
599
+ 'code_executions': result.get('code_executions', 0)
600
+ }
601
+ self.shared_state.scripter_history.append(script_record)
602
+ self.shared_state.last_scripter_message = result['message']
603
+ self.shared_state.last_scripter_success = result['success']
604
+
605
+ logger.info(f"🐍 ScripterAgent finished: {result['message'][:2000]}...")
606
+
607
+ return ScripterExecutorResultEvent(
608
+ task=ev.task,
609
+ message=result['message'],
610
+ success=result['success'],
611
+ code_executions=result.get('code_executions', 0)
612
+ )
613
+
614
+ @step
615
+ async def handle_scripter_result(
616
+ self,
617
+ ctx: Context,
618
+ ev: ScripterExecutorResultEvent
619
+ ) -> ManagerInputEvent:
620
+ """
621
+ Process ScripterAgent result and loop back to Manager.
622
+ """
623
+ if ev.success:
624
+ logger.info(f"✅ Script completed successfully in {ev.code_executions} steps")
625
+ else:
626
+ logger.warning(f"⚠️ Script failed or reached max steps: {ev.message}")
627
+
628
+ # Increment DroidAgent step counter
629
+ self.shared_state.step_number += 1
630
+ logger.info(f"🔄 Step {self.shared_state.step_number}/{self.config.agent.max_steps} complete, looping to Manager")
631
+
632
+ # Loop back to Manager (script result in shared_state)
633
+ return ManagerInputEvent()
634
+
635
+ # ========================================================================
636
+ # End Manager/Executor/Script Workflow Steps
511
637
  # ========================================================================
512
638
 
513
639
  @step
@@ -534,6 +660,8 @@ class DroidAgent(Workflow):
534
660
  if self.trajectory and self.config.logging.save_trajectory != "none":
535
661
  self.trajectory.save_trajectory()
536
662
 
663
+ self.tools_instance._set_context(None)
664
+
537
665
  return StopEvent(result)
538
666
 
539
667
  def handle_stream_event(self, ev: Event, ctx: Context):
@@ -103,6 +103,11 @@ class DroidAgentState(BaseModel):
103
103
  error_flag_plan: bool = False
104
104
  err_to_manager_thresh: int = 2
105
105
 
106
+ # Script execution tracking
107
+ scripter_history: List[Dict] = Field(default_factory=list)
108
+ last_scripter_message: str = ""
109
+ last_scripter_success: bool = True
110
+
106
111
  # Output
107
112
  output_dir: str = ""
108
113
 
@@ -179,3 +184,24 @@ class ExecutorResultEvent(Event):
179
184
  outcome: bool
180
185
  error: str
181
186
  summary: str
187
+
188
+
189
+ # ============================================================================
190
+ # Script executor coordination events
191
+ # ============================================================================
192
+
193
+ class ScripterExecutorInputEvent(Event):
194
+ """Trigger ScripterAgent workflow for off-device operations"""
195
+ task: str
196
+
197
+
198
+ class ScripterExecutorResultEvent(Event):
199
+ """
200
+ Coordination event from ScripterAgent to DroidAgent.
201
+
202
+ Used for workflow step routing only (NOT streamed to frontend).
203
+ """
204
+ task: str
205
+ message: str # Response from response() function
206
+ success: bool
207
+ code_executions: int
@@ -67,7 +67,12 @@ class ExecutorAgent(Workflow):
67
67
  self.vision = agent_config.executor.vision
68
68
  self.tools_instance = tools_instance
69
69
  self.shared_state = shared_state
70
- self.custom_tools = custom_tools or {}
70
+
71
+ # Merge custom_tools with atomic actions (same as CodeActAgent)
72
+ atomic_tools = ATOMIC_ACTION_SIGNATURES
73
+ merged_signatures = {**atomic_tools, **(custom_tools or {})}
74
+ self.all_actions = merged_signatures # Store merged dict for prompt
75
+ self.custom_tools = custom_tools or {} # Keep for execution lookup
71
76
 
72
77
  logger.info("✅ ExecutorAgent initialized successfully.")
73
78
 
@@ -120,7 +125,7 @@ class ExecutorAgent(Workflow):
120
125
  "plan": self.shared_state.plan,
121
126
  "subgoal": subgoal,
122
127
  "progress_status": self.shared_state.progress_status,
123
- "atomic_actions": ATOMIC_ACTION_SIGNATURES,
128
+ "atomic_actions": self.all_actions, # Now includes custom tools!
124
129
  "action_history": action_history
125
130
  }
126
131
  )
@@ -296,7 +301,7 @@ class ExecutorAgent(Workflow):
296
301
  if text is None:
297
302
  return False, "Missing 'text' parameter", "Failed: open_app requires text"
298
303
 
299
- result = open_app(self.tools_instance, text)
304
+ result = await open_app(self.tools_instance, text)
300
305
  return True, "None", f"Opened app: {text}"
301
306
 
302
307
  else:
@@ -161,7 +161,9 @@ class ManagerAgent(Workflow):
161
161
  "important_notes": "", # TODO: implement
162
162
  "error_history": error_history,
163
163
  "text_manipulation_enabled": has_text_to_modify,
164
- "custom_tools_descriptions": build_custom_tool_descriptions(self.custom_tools)
164
+ "custom_tools_descriptions": build_custom_tool_descriptions(self.custom_tools),
165
+ "scripter_execution_enabled": self.agent_config.scripter.enabled,
166
+ "scripter_max_steps": self.agent_config.scripter.max_steps,
165
167
  }
166
168
  )
167
169
 
@@ -219,6 +221,23 @@ class ManagerAgent(Workflow):
219
221
  if screenshot and self.vision:
220
222
  messages[last_user_idx]['content'].append({"image": screenshot})
221
223
 
224
+ # Add script result if available
225
+ if self.shared_state.last_scripter_message:
226
+ status = "SUCCESS" if self.shared_state.last_scripter_success else "FAILED"
227
+ script_context = (
228
+ f"\n<script_result status=\"{status}\">\n"
229
+ f"{self.shared_state.last_scripter_message}\n"
230
+ f"</script_result>\n"
231
+ )
232
+
233
+ if messages[last_user_idx]['content'] and 'text' in messages[last_user_idx]['content'][0]:
234
+ messages[last_user_idx]['content'][0]['text'] += script_context
235
+ else:
236
+ messages[last_user_idx]['content'].insert(0, {"text": script_context})
237
+
238
+ # Clear after injection (avoid duplicate injection)
239
+ self.shared_state.last_scripter_message = ""
240
+
222
241
  # Add PREVIOUS device state to SECOND-TO-LAST user message (if exists)
223
242
  if len(user_indices) >= 2:
224
243
  second_last_user_idx = user_indices[-2]
@@ -17,6 +17,7 @@ def parse_manager_response(response: str) -> dict:
17
17
 
18
18
  Also derives:
19
19
  - current_subgoal: first line of plan (with list markers removed)
20
+ - If first item is <script> tag, extract script content as current_subgoal
20
21
 
21
22
  Args:
22
23
  response: Raw LLM response text
@@ -26,7 +27,7 @@ def parse_manager_response(response: str) -> dict:
26
27
  - thought: str
27
28
  - memory: str
28
29
  - plan: str
29
- - current_subgoal: str (first line of plan, cleaned)
30
+ - current_subgoal: str (first line of plan, cleaned, or script content)
30
31
  - request_accomplished: str (from request_accomplished tag)
31
32
  """
32
33
  def extract(tag: str) -> str:
@@ -42,19 +43,27 @@ def parse_manager_response(response: str) -> dict:
42
43
 
43
44
  # Parse current subgoal from first line of plan
44
45
  current_goal_text = plan
45
- # Prefer newline-separated plans; take the first non-empty line
46
- plan_lines = [line.strip() for line in current_goal_text.splitlines() if line.strip()]
47
- if plan_lines:
48
- first_line = plan_lines[0]
46
+
47
+ # Check if first item is a <script> tag
48
+ script_match = re.search(r'^\s*<script>(.*?)</script>', current_goal_text, re.DOTALL)
49
+
50
+ if script_match:
51
+ # Script is first task - extract script content with tag
52
+ current_subgoal = f"<script>{script_match.group(1).strip()}</script>"
49
53
  else:
50
- first_line = current_goal_text.strip()
54
+ # Regular subgoal - use existing logic
55
+ plan_lines = [line.strip() for line in current_goal_text.splitlines() if line.strip()]
56
+ if plan_lines:
57
+ first_line = plan_lines[0]
58
+ else:
59
+ first_line = current_goal_text.strip()
51
60
 
52
- # Remove common list markers like "1.", "-", "*", or bullet characters
53
- first_line = re.sub(r"^\s*\d+\.\s*", "", first_line) # Remove "1. ", "2. ", etc.
54
- first_line = re.sub(r"^\s*[-*]\s*", "", first_line) # Remove "- " or "* "
55
- first_line = re.sub(r"^\s*•\s*", "", first_line) # Remove bullet "• "
61
+ # Remove common list markers like "1.", "-", "*", or bullet characters
62
+ first_line = re.sub(r"^\s*\d+\.\s*", "", first_line) # Remove "1. ", "2. ", etc.
63
+ first_line = re.sub(r"^\s*[-*]\s*", "", first_line) # Remove "- " or "* "
64
+ first_line = re.sub(r"^\s*•\s*", "", first_line) # Remove bullet "• "
56
65
 
57
- current_subgoal = first_line.strip()
66
+ current_subgoal = first_line.strip()
58
67
 
59
68
  return {
60
69
  "thought": thought,
@@ -0,0 +1,7 @@
1
+ """
2
+ ScripterAgent - ReAct agent for executing Python scripts (off-device operations).
3
+ """
4
+
5
+ from droidrun.agent.scripter.scripter_agent import ScripterAgent
6
+
7
+ __all__ = ["ScripterAgent"]
@@ -0,0 +1,36 @@
1
+ """
2
+ Events for ScripterAgent workflow.
3
+ """
4
+
5
+ from typing import List, Optional
6
+
7
+ from llama_index.core.workflow import Event
8
+
9
+
10
+ class ScripterInputEvent(Event):
11
+ """Input to LLM (chat history)."""
12
+ input: List # List of ChatMessages
13
+
14
+
15
+ class ScripterThinkingEvent(Event):
16
+ """LLM generated thought + code."""
17
+ thoughts: str
18
+ code: Optional[str] = None
19
+ full_response: str = "" # Full LLM response (for fallback when no code)
20
+
21
+
22
+ class ScripterExecutionEvent(Event):
23
+ """Trigger code execution."""
24
+ code: str
25
+
26
+
27
+ class ScripterExecutionResultEvent(Event):
28
+ """Code execution result."""
29
+ output: str
30
+
31
+
32
+ class ScripterEndEvent(Event):
33
+ """Script agent finished."""
34
+ message: str # Message to Manager
35
+ success: bool # True if response() called, False if max_steps
36
+ code_executions: int = 0