- Allow agents with no config section (use DEFAULT_CONFIG or env vars)
- Validate run() is async, reject sync functions with clear error
- Wrap list_agents() at CLI import to prevent crashes on scan failure
- Simplify migration to only remove example entries
- Add README.md with full contract, AdbDevice API, and setup guide
External agents now receive a raw async_adbutils.AdbDevice instead of
internal DroidRun tools. This makes them fully self-contained — zero
imports from droidrun, just copy-paste a file or folder and it works.
- Remove mai_ui and autoglm agents (depended on internals)
- Pass raw AdbDevice directly, skip Portal/driver/registry setup
- Add --agent CLI flag with dynamic discovery from external/ dir
- Add list_agents() for runtime agent enumeration
- Add config migration v005 to clean up legacy agent entries
- Update config_example.yaml to remove old agent references
BREAKING CHANGE: external agent run() signature changed from
(tools, instruction, config, max_steps) to
(device, instruction, config, max_steps) where device is an
async_adbutils.AdbDevice
Remove three deprecated agents and all associated dead code:
- CodeActAgent (Python code-gen + exec)
- ScripterAgent (off-device Python execution)
- TextManipulatorAgent (text field editing via code-gen)
Only FastAgent (XML tool-calling) remains as the direct-execution
agent. Rename agent/codeact/ to agent/fast_agent/ and
config/prompts/codeact/ to config/prompts/fast_agent/.
Also removes: SimpleCodeExecutor, SafeExecutionConfig,
extract_code_and_thought, code_checker, and all related config
fields (codeact, scripter, text_manipulator LLM profiles).
Adds v004 config migration to strip removed keys and update
prompt paths in existing user configs.
BREAKING CHANGE: `ScripterConfig`, `SafeExecutionConfig`, and
`codeact` config field are removed. The `text_manipulator` and
`scripter` LLM profiles are no longer used. Users with custom
imports of these classes or configs referencing removed fields
will need to update their code.
- Set _RECOVERY_AFTER_ATTEMPT to 5 so recovery fires after ~11s as intended
- Use Optional[List[float]] consistently instead of mixing old/new style hints
- Simplify nested f-string in retry warning log
- Update docstring to reflect actual fallback behavior for short delay lists
Restart Portal's TCP socket server alongside the accessibility service
during mid-retry recovery. This handles the case where the TCP server
crashes mid-run, causing state requests to fail on both TCP and content
provider paths.
Extract retry logic into fetch_state_with_retry() helper with 7 attempts,
increasing backoff (1-10s, ~29s total), and mid-retry accessibility service
restart after ~11s of failures. Fixes intermittent "Portal returned error:
Unknown error" by logging the full Portal response dict.
CloudDriver.__init__ accepts stealth=True which passes
{"stealth": true} via extra_body on tap, swipe, and input_text
calls to devices-api. No stealth logic added — purely passthrough.
- Manager and StatelessManager now emit ScreenshotEvent before
RecordUIStateEvent, matching FastAgent/CodeAct ordering
- DroidAgent finalize step now emits RecordUIStateEvent after the
final screenshot so every screenshot has a paired UI state
WorkflowValidationError: llama-index validates that all consumed events
are produced by some step. ExternalUserMessageEvent is external-only,
so the @step approach fails validation.
Replace with DroidAgent.send_user_message(text) -> QueuedUserMessage.
Directly queues on shared state, no event routing needed. Drain points
in FastAgent and Manager still emit Applied/Dropped stream events.
- QueuedUserMessage dataclass with uuid4 ID and queued_at_step
- Events now carry message IDs for end-to-end tracking
- ExternalUserMessageDroppedEvent for max-steps scenario
- FastAgent complete() with pending messages: reset and continue loop
so LLM sees its own complete() result alongside the user's message
- Manager request_accomplished with pending messages: loop back to
ManagerInputEvent instead of finalizing
- Max steps: drain queue, emit DroppedEvent, finalize as normal
- workflow_completed flag on shared state, guarding queue_user_message
- Fix device-state injection bug: merge external messages into tool
results user message instead of appending separate ChatMessages
Allow callers to inject user messages into a running DroidAgent via
handler.send_event(ExternalUserMessageEvent(message="...")).
Messages are queued in shared state and drained at safe checkpoints:
- FastAgent: after tool results, before next LLM call
- ManagerAgent: merged into synthesized user turn in prepare_context
Adds ExternalUserMessageEvent, QueuedEvent, and AppliedEvent for
observability. Updates FastAgent and Manager system prompts to
instruct the LLM to honor mid-execution user corrections.
Stop mutating message_history with stale device state and screenshots.
Instead, deepcopy the history before each LLM call and inject current
+ previous device state and screenshot into the disposable copy.
- FastAgent/CodeActAgent: add state rotation, deepcopy, and 2-state
injection matching the manager's pattern
- ManagerAgent: rename previous state tag to <previous_device_state>
Currently the formatted tree removed all information about the checked/enabled state of switches/buttons/toggles.
This causes droidrun (without vision) to incorrectly toggle switches as it cannot know if they are already checked.
This PR adds an isChecked=True/False field for checkable nodes.
Signed-off-by: Akhil Kedia <16665267+akhilkedia@users.noreply.github.com>
Add DROIDRUN_STREAM_SCREENSHOTS env var (disabled by default) to enable
ScreenshotEvent emission without trajectory disk I/O. Screenshots can
now be streamed via the event system independently of TrajectoryWriter
serialization, PNG writes, and GIF generation.
- Wrap response.read() in screenshot() to catch SDK disconnect errors
that occur after the initial request succeeds but during body read
- Remove try/except around get_date() in start_handler — if the device
can't respond at startup, fail immediately instead of silently degrading
Add DeviceDisconnectedError to the driver layer so cloud device
disconnects (409/timeout/connection errors) propagate cleanly instead
of crashing the workflow or being silently swallowed.
- CloudDriver: add max_retries=4 (~7.5s backoff), wrap all SDK calls
via _call() helper that translates ConflictError/APIConnectionError/
APITimeoutError into DeviceDisconnectedError, remove 7 silent
try/except blocks that returned fake values on failure
- StateProvider: let DeviceDisconnectedError bypass retry loop; fix
pre-existing bug where `from last_error` used str instead of exception
- DroidAgent: catch DeviceDisconnectedError in run_manager and
execute_task, finalize with success=False and clear reason
- FastAgent/CodeActAgent: re-raise DeviceDisconnectedError from
screenshot and get_state try/except blocks
- ManagerAgent/StatelessManagerAgent: re-raise DeviceDisconnectedError
from screenshot; use shared_state.device_date (fetched once at
startup) instead of calling get_date() every step
Allow custom UIState subclasses to be injected via ui_cls param,
enabling external stealth implementations without subclassing
the provider. Bump version to 0.5.0.dev4.