droidrun 0.3.9__py3-none-any.whl → 0.3.10.dev3__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.dev3.dist-info}/METADATA +5 -3
- droidrun-0.3.10.dev3.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.dev3.dist-info}/WHEEL +0 -0
- {droidrun-0.3.9.dist-info → droidrun-0.3.10.dev3.dist-info}/entry_points.txt +0 -0
- {droidrun-0.3.9.dist-info → droidrun-0.3.10.dev3.dist-info}/licenses/LICENSE +0 -0
droidrun/macro/cli.py
CHANGED
@@ -3,15 +3,17 @@ Command-line interface for DroidRun macro replay.
|
|
3
3
|
"""
|
4
4
|
|
5
5
|
import asyncio
|
6
|
-
import click
|
7
6
|
import logging
|
8
7
|
import os
|
9
8
|
from typing import Optional
|
9
|
+
|
10
|
+
import click
|
11
|
+
from adbutils import adb
|
10
12
|
from rich.console import Console
|
11
13
|
from rich.table import Table
|
12
|
-
|
14
|
+
|
13
15
|
from droidrun.agent.utils.trajectory import Trajectory
|
14
|
-
from
|
16
|
+
from droidrun.macro.replay import MacroPlayer
|
15
17
|
|
16
18
|
console = Console()
|
17
19
|
|
@@ -20,21 +22,21 @@ def configure_logging(debug: bool = False):
|
|
20
22
|
"""Configure logging for the macro CLI."""
|
21
23
|
logger = logging.getLogger("droidrun-macro")
|
22
24
|
logger.handlers = []
|
23
|
-
|
25
|
+
|
24
26
|
handler = logging.StreamHandler()
|
25
|
-
|
27
|
+
|
26
28
|
if debug:
|
27
29
|
level = logging.DEBUG
|
28
30
|
formatter = logging.Formatter("%(levelname)s %(name)s %(message)s", "%H:%M:%S")
|
29
31
|
else:
|
30
32
|
level = logging.INFO
|
31
33
|
formatter = logging.Formatter("%(message)s", "%H:%M:%S")
|
32
|
-
|
34
|
+
|
33
35
|
handler.setFormatter(formatter)
|
34
36
|
logger.addHandler(handler)
|
35
37
|
logger.setLevel(level)
|
36
38
|
logger.propagate = False
|
37
|
-
|
39
|
+
|
38
40
|
return logger
|
39
41
|
|
40
42
|
|
@@ -55,12 +57,12 @@ def macro_cli():
|
|
55
57
|
def replay(path: str, device: Optional[str], delay: float, start_from: int, max_steps: Optional[int], debug: bool, dry_run: bool):
|
56
58
|
"""Replay a macro from a file or trajectory folder."""
|
57
59
|
logger = configure_logging(debug)
|
58
|
-
|
60
|
+
|
59
61
|
logger.info("🎬 DroidRun Macro Replay")
|
60
|
-
|
62
|
+
|
61
63
|
# Convert start_from from 1-based to 0-based
|
62
64
|
start_from_zero = max(0, start_from - 1)
|
63
|
-
|
65
|
+
|
64
66
|
if device is None:
|
65
67
|
logger.info("🔍 Finding connected device...")
|
66
68
|
devices = adb.list()
|
@@ -70,7 +72,7 @@ def replay(path: str, device: Optional[str], delay: float, start_from: int, max_
|
|
70
72
|
logger.info(f"📱 Using device: {device}")
|
71
73
|
else:
|
72
74
|
logger.info(f"📱 Using device: {device}")
|
73
|
-
|
75
|
+
|
74
76
|
asyncio.run(_replay_async(path, device, delay, start_from_zero, max_steps, dry_run, logger))
|
75
77
|
|
76
78
|
|
@@ -88,40 +90,40 @@ async def _replay_async(path: str, device: str, delay: float, start_from: int, m
|
|
88
90
|
else:
|
89
91
|
logger.error(f"❌ Invalid path: {path}")
|
90
92
|
return
|
91
|
-
|
93
|
+
|
92
94
|
if not macro_data:
|
93
95
|
logger.error("❌ Failed to load macro data")
|
94
96
|
return
|
95
|
-
|
97
|
+
|
96
98
|
# Show macro information
|
97
99
|
description = macro_data.get("description", "No description")
|
98
100
|
total_actions = macro_data.get("total_actions", 0)
|
99
101
|
version = macro_data.get("version", "unknown")
|
100
|
-
|
102
|
+
|
101
103
|
logger.info("📋 Macro Information:")
|
102
104
|
logger.info(f" Description: {description}")
|
103
105
|
logger.info(f" Version: {version}")
|
104
106
|
logger.info(f" Total actions: {total_actions}")
|
105
107
|
logger.info(f" Device: {device}")
|
106
108
|
logger.info(f" Delay between actions: {delay}s")
|
107
|
-
|
109
|
+
|
108
110
|
if start_from > 0:
|
109
111
|
logger.info(f" Starting from step: {start_from + 1}")
|
110
112
|
if max_steps:
|
111
113
|
logger.info(f" Maximum steps: {max_steps}")
|
112
|
-
|
114
|
+
|
113
115
|
if dry_run:
|
114
116
|
logger.info("🔍 DRY RUN MODE - Actions will be shown but not executed")
|
115
117
|
await _show_dry_run(macro_data, start_from, max_steps, logger)
|
116
118
|
else:
|
117
119
|
logger.info("▶️ Starting macro replay...")
|
118
120
|
success = await player.replay_macro(macro_data, start_from_step=start_from, max_steps=max_steps)
|
119
|
-
|
121
|
+
|
120
122
|
if success:
|
121
123
|
logger.info("🎉 Macro replay completed successfully!")
|
122
124
|
else:
|
123
125
|
logger.error("💥 Macro replay completed with errors")
|
124
|
-
|
126
|
+
|
125
127
|
except Exception as e:
|
126
128
|
logger.error(f"💥 Error: {e}")
|
127
129
|
if logger.isEnabledFor(logging.DEBUG):
|
@@ -132,25 +134,25 @@ async def _replay_async(path: str, device: str, delay: float, start_from: int, m
|
|
132
134
|
async def _show_dry_run(macro_data: dict, start_from: int, max_steps: Optional[int], logger: logging.Logger):
|
133
135
|
"""Show what actions would be executed in dry run mode."""
|
134
136
|
actions = macro_data.get("actions", [])
|
135
|
-
|
137
|
+
|
136
138
|
# Apply filters
|
137
139
|
if start_from > 0:
|
138
140
|
actions = actions[start_from:]
|
139
141
|
if max_steps:
|
140
142
|
actions = actions[:max_steps]
|
141
|
-
|
143
|
+
|
142
144
|
logger.info(f"📋 Found {len(actions)} actions to execute:")
|
143
|
-
|
145
|
+
|
144
146
|
table = Table(title="Actions to Execute")
|
145
147
|
table.add_column("Step", style="cyan")
|
146
148
|
table.add_column("Type", style="green")
|
147
149
|
table.add_column("Details", style="white")
|
148
150
|
table.add_column("Description", style="yellow")
|
149
|
-
|
151
|
+
|
150
152
|
for i, action in enumerate(actions, start=start_from + 1):
|
151
153
|
action_type = action.get("action_type", action.get("type", "unknown"))
|
152
154
|
details = ""
|
153
|
-
|
155
|
+
|
154
156
|
if action_type == "tap":
|
155
157
|
x, y = action.get("x", 0), action.get("y", 0)
|
156
158
|
element_text = action.get("element_text", "")
|
@@ -165,10 +167,10 @@ async def _show_dry_run(macro_data: dict, start_from: int, max_steps: Optional[i
|
|
165
167
|
elif action_type == "key_press":
|
166
168
|
key_name = action.get("key_name", "UNKNOWN")
|
167
169
|
details = f"{key_name}"
|
168
|
-
|
170
|
+
|
169
171
|
description = action.get("description", "")
|
170
172
|
table.add_row(str(i), action_type, details, description[:50] + "..." if len(description) > 50 else description)
|
171
|
-
|
173
|
+
|
172
174
|
# Still use console for table display as it's structured data
|
173
175
|
console.print(table)
|
174
176
|
|
@@ -179,9 +181,9 @@ async def _show_dry_run(macro_data: dict, start_from: int, max_steps: Optional[i
|
|
179
181
|
def list(directory: str, debug: bool):
|
180
182
|
"""List available trajectory folders in a directory."""
|
181
183
|
logger = configure_logging(debug)
|
182
|
-
|
184
|
+
|
183
185
|
logger.info(f"📁 Scanning directory: {directory}")
|
184
|
-
|
186
|
+
|
185
187
|
try:
|
186
188
|
folders = []
|
187
189
|
for item in os.listdir(directory):
|
@@ -198,25 +200,25 @@ def list(directory: str, debug: bool):
|
|
198
200
|
except Exception as e:
|
199
201
|
logger.debug(f"Error loading macro from {item}: {e}")
|
200
202
|
folders.append((item, "Error loading", 0))
|
201
|
-
|
203
|
+
|
202
204
|
if not folders:
|
203
205
|
logger.info("📭 No trajectory folders found")
|
204
206
|
return
|
205
|
-
|
207
|
+
|
206
208
|
logger.info(f"🎯 Found {len(folders)} trajectory(s):")
|
207
|
-
|
209
|
+
|
208
210
|
table = Table(title=f"Available Trajectories in {directory}")
|
209
211
|
table.add_column("Folder", style="cyan")
|
210
212
|
table.add_column("Description", style="white")
|
211
213
|
table.add_column("Actions", style="green")
|
212
|
-
|
214
|
+
|
213
215
|
for folder, description, actions in sorted(folders):
|
214
216
|
table.add_row(folder, description[:80] + "..." if len(description) > 80 else description, str(actions))
|
215
|
-
|
217
|
+
|
216
218
|
# Still use console for table display as it's structured data
|
217
219
|
console.print(table)
|
218
220
|
logger.info(f"💡 Use 'droidrun macro replay {directory}/<folder>' to replay a trajectory")
|
219
|
-
|
221
|
+
|
220
222
|
except Exception as e:
|
221
223
|
logger.error(f"💥 Error: {e}")
|
222
224
|
if logger.isEnabledFor(logging.DEBUG):
|
@@ -225,4 +227,4 @@ def list(directory: str, debug: bool):
|
|
225
227
|
|
226
228
|
|
227
229
|
if __name__ == "__main__":
|
228
|
-
macro_cli()
|
230
|
+
macro_cli()
|
droidrun/macro/replay.py
CHANGED
@@ -5,14 +5,12 @@ This module provides functionality to load and replay macro JSON files
|
|
5
5
|
that were generated during DroidAgent trajectory recording.
|
6
6
|
"""
|
7
7
|
|
8
|
-
import json
|
9
8
|
import asyncio
|
10
9
|
import logging
|
11
|
-
import
|
12
|
-
|
13
|
-
from typing import Dict, List, Any, Optional
|
14
|
-
from droidrun.tools.adb import AdbTools
|
10
|
+
from typing import Any, Dict, Optional
|
11
|
+
|
15
12
|
from droidrun.agent.utils.trajectory import Trajectory
|
13
|
+
from droidrun.tools.adb import AdbTools
|
16
14
|
|
17
15
|
logger = logging.getLogger("droidrun-macro")
|
18
16
|
|
@@ -145,7 +143,7 @@ class MacroPlayer:
|
|
145
143
|
return True
|
146
144
|
|
147
145
|
elif action_type == "back":
|
148
|
-
logger.info(
|
146
|
+
logger.info("⬅️ Pressing back button")
|
149
147
|
result = tools.back()
|
150
148
|
logger.debug(f" Result: {result}")
|
151
149
|
return True
|
@@ -211,10 +209,10 @@ class MacroPlayer:
|
|
211
209
|
|
212
210
|
if success:
|
213
211
|
success_count += 1
|
214
|
-
logger.info(
|
212
|
+
logger.info(" ✅ Action completed successfully")
|
215
213
|
else:
|
216
214
|
failed_count += 1
|
217
|
-
logger.error(
|
215
|
+
logger.error(" ❌ Action failed")
|
218
216
|
|
219
217
|
# Wait between actions (except for the last one)
|
220
218
|
if i < len(actions):
|
@@ -227,7 +225,7 @@ class MacroPlayer:
|
|
227
225
|
(success_count / total_executed * 100) if total_executed > 0 else 0
|
228
226
|
)
|
229
227
|
|
230
|
-
logger.info(
|
228
|
+
logger.info("\n🎉 Macro replay completed!")
|
231
229
|
logger.info(
|
232
230
|
f"📊 Success: {success_count}/{total_executed} ({success_rate:.1f}%)"
|
233
231
|
)
|
droidrun/portal.py
CHANGED
@@ -144,7 +144,7 @@ def ping_portal_content(device: AdbDevice, debug: bool = False):
|
|
144
144
|
|
145
145
|
def ping_portal_tcp(device: AdbDevice, debug: bool = False):
|
146
146
|
try:
|
147
|
-
|
147
|
+
AdbTools(serial=device.serial, use_tcp=True)
|
148
148
|
except Exception as e:
|
149
149
|
raise Exception("Failed to setup TCP forwarding") from e
|
150
150
|
|
droidrun/telemetry/__init__.py
CHANGED
@@ -1,4 +1,4 @@
|
|
1
|
+
from .events import DroidAgentFinalizeEvent, DroidAgentInitEvent
|
1
2
|
from .tracker import capture, flush, print_telemetry_message
|
2
|
-
from .events import DroidAgentInitEvent, DroidAgentFinalizeEvent
|
3
3
|
|
4
|
-
__all__ = ["capture", "flush", "DroidAgentInitEvent", "DroidAgentFinalizeEvent", "print_telemetry_message"]
|
4
|
+
__all__ = ["capture", "flush", "DroidAgentInitEvent", "DroidAgentFinalizeEvent", "print_telemetry_message"]
|
droidrun/telemetry/events.py
CHANGED
@@ -1,7 +1,7 @@
|
|
1
|
-
|
2
|
-
from droidrun.agent.context import Task
|
1
|
+
|
3
2
|
from pydantic import BaseModel
|
4
3
|
|
4
|
+
|
5
5
|
class TelemetryEvent(BaseModel):
|
6
6
|
pass
|
7
7
|
|
@@ -14,10 +14,9 @@ class DroidAgentInitEvent(TelemetryEvent):
|
|
14
14
|
timeout: int
|
15
15
|
vision: bool
|
16
16
|
reasoning: bool
|
17
|
-
reflection: bool
|
18
17
|
enable_tracing: bool
|
19
18
|
debug: bool
|
20
|
-
save_trajectories: str = "none",
|
19
|
+
save_trajectories: str = "none",
|
21
20
|
|
22
21
|
|
23
22
|
class DroidAgentFinalizeEvent(TelemetryEvent):
|
@@ -0,0 +1,173 @@
|
|
1
|
+
import asyncio
|
2
|
+
import functools
|
3
|
+
import inspect
|
4
|
+
import os
|
5
|
+
import uuid
|
6
|
+
from contextvars import Token, copy_context
|
7
|
+
from typing import Any, Callable
|
8
|
+
|
9
|
+
from llama_index.core.callbacks.base_handler import BaseCallbackHandler
|
10
|
+
from llama_index_instrumentation import get_dispatcher
|
11
|
+
from openinference.instrumentation import TraceConfig
|
12
|
+
|
13
|
+
dispatcher = get_dispatcher()
|
14
|
+
|
15
|
+
def arize_phoenix_callback_handler(**kwargs: Any) -> BaseCallbackHandler:
|
16
|
+
# newer versions of arize, v2.x
|
17
|
+
from openinference.instrumentation.llama_index import LlamaIndexInstrumentor
|
18
|
+
from openinference.semconv.resource import ResourceAttributes
|
19
|
+
from opentelemetry.exporter.otlp.proto.http.trace_exporter import (
|
20
|
+
OTLPSpanExporter,
|
21
|
+
)
|
22
|
+
from opentelemetry.sdk import trace as trace_sdk
|
23
|
+
from opentelemetry.sdk.resources import Resource
|
24
|
+
from opentelemetry.sdk.trace.export import SimpleSpanProcessor
|
25
|
+
|
26
|
+
endpoint = kwargs.get("endpoint", os.getenv("phoenix_url", "http://127.0.0.1:6006")) + "/v1/traces"
|
27
|
+
|
28
|
+
resource_attributes = {}
|
29
|
+
phoenix_project_name = os.getenv("phoenix_project_name", "")
|
30
|
+
if phoenix_project_name.strip():
|
31
|
+
resource_attributes[ResourceAttributes.PROJECT_NAME] = phoenix_project_name
|
32
|
+
resource = Resource(attributes=resource_attributes)
|
33
|
+
|
34
|
+
tracer_provider = trace_sdk.TracerProvider(resource=resource)
|
35
|
+
tracer_provider.add_span_processor(
|
36
|
+
SimpleSpanProcessor(OTLPSpanExporter(endpoint))
|
37
|
+
)
|
38
|
+
config = TraceConfig(
|
39
|
+
base64_image_max_length=64000000
|
40
|
+
)
|
41
|
+
|
42
|
+
return LlamaIndexInstrumentor().instrument(
|
43
|
+
tracer_provider=kwargs.get("tracer_provider", tracer_provider),
|
44
|
+
separate_trace_from_runtime_context=kwargs.get(
|
45
|
+
"separate_trace_from_runtime_context"
|
46
|
+
),
|
47
|
+
config=config
|
48
|
+
)
|
49
|
+
|
50
|
+
|
51
|
+
def clean_span(span_name: str):
|
52
|
+
"""
|
53
|
+
Create a span with a clean name (without class prefix).
|
54
|
+
|
55
|
+
This function returns a decorator that creates spans with custom names
|
56
|
+
instead of the default class.method format.
|
57
|
+
|
58
|
+
It preserves parent-child relationships by using the same active span
|
59
|
+
context variable as the built-in dispatcher decorator does.
|
60
|
+
|
61
|
+
Args:
|
62
|
+
span_name: The desired name for the span
|
63
|
+
|
64
|
+
Returns:
|
65
|
+
A decorator function
|
66
|
+
"""
|
67
|
+
def decorator(func: Callable) -> Callable:
|
68
|
+
# Support both sync and async callables
|
69
|
+
if inspect.iscoroutinefunction(func):
|
70
|
+
@functools.wraps(func)
|
71
|
+
async def async_wrapper(*args, **kwargs):
|
72
|
+
# Import here to avoid circular imports
|
73
|
+
from llama_index_instrumentation.dispatcher import active_instrument_tags
|
74
|
+
from llama_index_instrumentation.span import active_span_id
|
75
|
+
|
76
|
+
|
77
|
+
span_id = f"{span_name}-{uuid.uuid4()}"
|
78
|
+
bound_args = inspect.signature(func).bind(*args, **kwargs)
|
79
|
+
# Treat as method only if qualname indicates Class.method
|
80
|
+
is_method = "." in getattr(func, "__qualname__", "")
|
81
|
+
instance = args[0] if (args and is_method) else None
|
82
|
+
|
83
|
+
tags = active_instrument_tags.get()
|
84
|
+
token = active_span_id.set(span_id)
|
85
|
+
parent_id = None if token.old_value is Token.MISSING else token.old_value
|
86
|
+
|
87
|
+
dispatcher.span_enter(
|
88
|
+
id_=span_id,
|
89
|
+
bound_args=bound_args,
|
90
|
+
instance=instance,
|
91
|
+
parent_id=parent_id,
|
92
|
+
tags=tags,
|
93
|
+
)
|
94
|
+
try:
|
95
|
+
result = await func(*args, **kwargs)
|
96
|
+
except Exception as e:
|
97
|
+
dispatcher.span_drop(id_=span_id, bound_args=bound_args, instance=instance, err=e)
|
98
|
+
raise
|
99
|
+
else:
|
100
|
+
dispatcher.span_exit(id_=span_id, bound_args=bound_args, instance=instance, result=result)
|
101
|
+
return result
|
102
|
+
finally:
|
103
|
+
active_span_id.reset(token)
|
104
|
+
|
105
|
+
return async_wrapper
|
106
|
+
else:
|
107
|
+
@functools.wraps(func)
|
108
|
+
def wrapper(*args, **kwargs):
|
109
|
+
# Import here to avoid circular imports
|
110
|
+
from llama_index_instrumentation.dispatcher import active_instrument_tags
|
111
|
+
from llama_index_instrumentation.span import active_span_id
|
112
|
+
|
113
|
+
|
114
|
+
span_id = f"{span_name}-{uuid.uuid4()}"
|
115
|
+
bound_args = inspect.signature(func).bind(*args, **kwargs)
|
116
|
+
# Treat as method only if qualname indicates Class.method
|
117
|
+
is_method = "." in getattr(func, "__qualname__", "")
|
118
|
+
instance = args[0] if (args and is_method) else None
|
119
|
+
|
120
|
+
tags = active_instrument_tags.get()
|
121
|
+
context = copy_context()
|
122
|
+
token = active_span_id.set(span_id)
|
123
|
+
parent_id = None if token.old_value is Token.MISSING else token.old_value
|
124
|
+
|
125
|
+
dispatcher.span_enter(
|
126
|
+
id_=span_id,
|
127
|
+
bound_args=bound_args,
|
128
|
+
instance=instance,
|
129
|
+
parent_id=parent_id,
|
130
|
+
tags=tags,
|
131
|
+
)
|
132
|
+
try:
|
133
|
+
result = func(*args, **kwargs)
|
134
|
+
if isinstance(result, asyncio.Future):
|
135
|
+
new_future = asyncio.ensure_future(result)
|
136
|
+
|
137
|
+
def _on_done(fut: asyncio.Future) -> None:
|
138
|
+
try:
|
139
|
+
fut_result = None if fut.exception() else fut.result()
|
140
|
+
dispatcher.span_exit(
|
141
|
+
id_=span_id,
|
142
|
+
bound_args=bound_args,
|
143
|
+
instance=instance,
|
144
|
+
result=fut_result,
|
145
|
+
)
|
146
|
+
except Exception as e2:
|
147
|
+
dispatcher.span_drop(
|
148
|
+
id_=span_id,
|
149
|
+
bound_args=bound_args,
|
150
|
+
instance=instance,
|
151
|
+
err=e2,
|
152
|
+
)
|
153
|
+
raise
|
154
|
+
finally:
|
155
|
+
try:
|
156
|
+
context.run(active_span_id.reset, token)
|
157
|
+
except ValueError:
|
158
|
+
pass
|
159
|
+
|
160
|
+
new_future.add_done_callback(_on_done)
|
161
|
+
return new_future
|
162
|
+
except Exception as e:
|
163
|
+
dispatcher.span_drop(id_=span_id, bound_args=bound_args, instance=instance, err=e)
|
164
|
+
raise
|
165
|
+
else:
|
166
|
+
dispatcher.span_exit(id_=span_id, bound_args=bound_args, instance=instance, result=result)
|
167
|
+
return result
|
168
|
+
finally:
|
169
|
+
if not isinstance(locals().get("result"), asyncio.Future):
|
170
|
+
active_span_id.reset(token)
|
171
|
+
|
172
|
+
return wrapper
|
173
|
+
return decorator
|
droidrun/telemetry/tracker.py
CHANGED
@@ -1,8 +1,10 @@
|
|
1
|
-
|
1
|
+
import logging
|
2
|
+
import os
|
2
3
|
from pathlib import Path
|
3
4
|
from uuid import uuid4
|
4
|
-
|
5
|
-
import
|
5
|
+
|
6
|
+
from posthog import Posthog
|
7
|
+
|
6
8
|
from .events import TelemetryEvent
|
7
9
|
|
8
10
|
logger = logging.getLogger("droidrun-telemetry")
|
@@ -76,9 +78,9 @@ def capture(event: TelemetryEvent, user_id: str | None = None):
|
|
76
78
|
def flush():
|
77
79
|
try:
|
78
80
|
if not is_telemetry_enabled():
|
79
|
-
logger.debug(
|
81
|
+
logger.debug("Telemetry disabled, skipping flush")
|
80
82
|
return
|
81
83
|
posthog.flush()
|
82
|
-
logger.debug(
|
84
|
+
logger.debug("Flushed telemetry data")
|
83
85
|
except Exception as e:
|
84
86
|
logger.error(f"Error flushing telemetry data: {e}")
|
droidrun/tools/__init__.py
CHANGED
@@ -2,8 +2,8 @@
|
|
2
2
|
DroidRun Tools - Core functionality for Android device control.
|
3
3
|
"""
|
4
4
|
|
5
|
-
from droidrun.tools.tools import Tools, describe_tools
|
6
5
|
from droidrun.tools.adb import AdbTools
|
7
6
|
from droidrun.tools.ios import IOSTools
|
7
|
+
from droidrun.tools.tools import Tools, describe_tools
|
8
8
|
|
9
9
|
__all__ = ["Tools", "describe_tools", "AdbTools", "IOSTools"]
|