droidrun 0.3.10.dev5__py3-none-any.whl → 0.3.10.dev7__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/agent/codeact/codeact_agent.py +21 -29
- droidrun/agent/context/task_manager.py +0 -1
- droidrun/agent/droid/droid_agent.py +1 -3
- droidrun/agent/droid/events.py +6 -3
- droidrun/agent/executor/executor_agent.py +24 -38
- droidrun/agent/executor/prompts.py +0 -108
- droidrun/agent/manager/__init__.py +1 -1
- droidrun/agent/manager/manager_agent.py +104 -87
- droidrun/agent/utils/executer.py +11 -10
- droidrun/agent/utils/llm_picker.py +63 -1
- droidrun/agent/utils/tools.py +30 -1
- droidrun/app_cards/app_card_provider.py +26 -0
- droidrun/app_cards/providers/__init__.py +7 -0
- droidrun/app_cards/providers/composite_provider.py +97 -0
- droidrun/app_cards/providers/local_provider.py +115 -0
- droidrun/app_cards/providers/server_provider.py +126 -0
- droidrun/cli/logs.py +4 -4
- droidrun/cli/main.py +244 -34
- droidrun/config_manager/__init__.py +0 -2
- droidrun/config_manager/config_manager.py +45 -102
- droidrun/config_manager/path_resolver.py +1 -1
- droidrun/config_manager/prompt_loader.py +48 -51
- droidrun/macro/cli.py +0 -1
- droidrun/portal.py +17 -0
- droidrun/tools/adb.py +13 -34
- {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/METADATA +2 -9
- {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/RECORD +30 -26
- droidrun/config_manager/app_card_loader.py +0 -148
- {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/WHEEL +0 -0
- {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/entry_points.txt +0 -0
- {droidrun-0.3.10.dev5.dist-info → droidrun-0.3.10.dev7.dist-info}/licenses/LICENSE +0 -0
droidrun/agent/utils/executer.py
CHANGED
@@ -1,9 +1,10 @@
|
|
1
|
-
import io
|
2
1
|
import contextlib
|
3
|
-
import
|
2
|
+
import io
|
4
3
|
import logging
|
5
|
-
|
4
|
+
import traceback
|
6
5
|
from asyncio import AbstractEventLoop
|
6
|
+
from typing import Any, Dict, Optional
|
7
|
+
|
7
8
|
from pydantic import BaseModel
|
8
9
|
|
9
10
|
logger = logging.getLogger("droidrun")
|
@@ -11,7 +12,7 @@ logger = logging.getLogger("droidrun")
|
|
11
12
|
class ExecuterState(BaseModel):
|
12
13
|
"""State object for the code executor."""
|
13
14
|
ui_state: Optional[Any] = None
|
14
|
-
|
15
|
+
|
15
16
|
class Config:
|
16
17
|
arbitrary_types_allowed = True
|
17
18
|
|
@@ -70,7 +71,7 @@ class SimpleCodeExecutor:
|
|
70
71
|
self.locals = locals
|
71
72
|
self.loop = loop
|
72
73
|
self.use_same_scope = use_same_scope
|
73
|
-
|
74
|
+
|
74
75
|
if self.use_same_scope:
|
75
76
|
# If using the same scope, merge globals and locals
|
76
77
|
self.globals = self.locals = {
|
@@ -85,7 +86,7 @@ class SimpleCodeExecutor:
|
|
85
86
|
"""
|
86
87
|
# Update UI state
|
87
88
|
self.globals['ui_state'] = ui_state
|
88
|
-
|
89
|
+
|
89
90
|
# Capture stdout and stderr
|
90
91
|
stdout = io.StringIO()
|
91
92
|
stderr = io.StringIO()
|
@@ -111,7 +112,7 @@ class SimpleCodeExecutor:
|
|
111
112
|
async def execute(self, state: ExecuterState, code: str) -> str:
|
112
113
|
"""
|
113
114
|
Execute Python code and capture output and return values.
|
114
|
-
|
115
|
+
|
115
116
|
Runs the code in a separate thread to prevent blocking.
|
116
117
|
|
117
118
|
Args:
|
@@ -123,7 +124,7 @@ class SimpleCodeExecutor:
|
|
123
124
|
"""
|
124
125
|
# Get UI state from the state object
|
125
126
|
ui_state = state.ui_state
|
126
|
-
|
127
|
+
|
127
128
|
# Run the execution in a thread pool executor
|
128
129
|
output = await self.loop.run_in_executor(
|
129
130
|
None,
|
@@ -131,5 +132,5 @@ class SimpleCodeExecutor:
|
|
131
132
|
code,
|
132
133
|
ui_state
|
133
134
|
)
|
134
|
-
|
135
|
-
return output
|
135
|
+
|
136
|
+
return output
|
@@ -1,11 +1,14 @@
|
|
1
1
|
import importlib
|
2
2
|
import logging
|
3
|
-
from typing import Any
|
3
|
+
from typing import TYPE_CHECKING, Any
|
4
4
|
|
5
5
|
from llama_index.core.llms.llm import LLM
|
6
6
|
|
7
7
|
from droidrun.agent.usage import track_usage
|
8
8
|
|
9
|
+
if TYPE_CHECKING:
|
10
|
+
from droidrun.config_manager.config_manager import LLMProfile
|
11
|
+
|
9
12
|
# Configure logging
|
10
13
|
logger = logging.getLogger("droidrun")
|
11
14
|
|
@@ -104,6 +107,65 @@ def load_llm(provider_name: str, **kwargs: Any) -> LLM:
|
|
104
107
|
raise e
|
105
108
|
|
106
109
|
|
110
|
+
def load_llms_from_profiles(
|
111
|
+
profiles: dict[str, "LLMProfile"],
|
112
|
+
profile_names: list[str] | None = None,
|
113
|
+
**override_kwargs_per_profile
|
114
|
+
) -> dict[str, LLM]:
|
115
|
+
"""
|
116
|
+
Load multiple LLMs from LLMProfile objects.
|
117
|
+
|
118
|
+
Args:
|
119
|
+
profiles: Dict of profile_name -> LLMProfile objects
|
120
|
+
profile_names: List of profile names to load. If None, loads all profiles
|
121
|
+
**override_kwargs_per_profile: Dict of profile-specific overrides
|
122
|
+
Example: manager={'temperature': 0.1}, executor={'max_tokens': 8000}
|
123
|
+
|
124
|
+
Returns:
|
125
|
+
Dict mapping profile names to initialized LLM instances
|
126
|
+
|
127
|
+
Example:
|
128
|
+
>>> config = ConfigManager()
|
129
|
+
>>> llms = load_llms_from_profiles(config.llm_profiles)
|
130
|
+
>>> manager_llm = llms['manager']
|
131
|
+
|
132
|
+
>>> # Load specific profiles with overrides
|
133
|
+
>>> llms = load_llms_from_profiles(
|
134
|
+
... config.llm_profiles,
|
135
|
+
... profile_names=['manager', 'executor'],
|
136
|
+
... manager={'temperature': 0.1}
|
137
|
+
... )
|
138
|
+
"""
|
139
|
+
if profile_names is None:
|
140
|
+
profile_names = list(profiles.keys())
|
141
|
+
|
142
|
+
llms = {}
|
143
|
+
for profile_name in profile_names:
|
144
|
+
logger.debug(f"Loading LLM for profile: {profile_name}")
|
145
|
+
|
146
|
+
if profile_name not in profiles:
|
147
|
+
raise KeyError(
|
148
|
+
f"Profile '{profile_name}' not found. "
|
149
|
+
f"Available profiles: {list(profiles.keys())}"
|
150
|
+
)
|
151
|
+
|
152
|
+
profile = profiles[profile_name]
|
153
|
+
|
154
|
+
# Get base kwargs from profile
|
155
|
+
kwargs = profile.to_load_llm_kwargs()
|
156
|
+
|
157
|
+
# Apply profile-specific overrides if provided
|
158
|
+
if profile_name in override_kwargs_per_profile:
|
159
|
+
logger.debug(f"Applying overrides for {profile_name}: {override_kwargs_per_profile[profile_name]}")
|
160
|
+
kwargs.update(override_kwargs_per_profile[profile_name])
|
161
|
+
|
162
|
+
# Load the LLM
|
163
|
+
llms[profile_name] = load_llm(provider_name=profile.provider, **kwargs)
|
164
|
+
logger.debug(f"Successfully loaded {profile_name} LLM: {profile.provider}/{profile.model}")
|
165
|
+
|
166
|
+
return llms
|
167
|
+
|
168
|
+
|
107
169
|
# --- Example Usage ---
|
108
170
|
if __name__ == "__main__":
|
109
171
|
# Install the specific LLM integrations you want to test:
|
droidrun/agent/utils/tools.py
CHANGED
@@ -1,5 +1,5 @@
|
|
1
|
-
from typing import TYPE_CHECKING, List
|
2
1
|
import time
|
2
|
+
from typing import TYPE_CHECKING, List
|
3
3
|
|
4
4
|
if TYPE_CHECKING:
|
5
5
|
from droidrun.tools import Tools
|
@@ -127,6 +127,35 @@ def open_app(tool_instance: "Tools", text: str) -> str:
|
|
127
127
|
return result
|
128
128
|
|
129
129
|
|
130
|
+
def remember(tool_instance: "Tools", information: str) -> str:
|
131
|
+
"""
|
132
|
+
Remember important information for later use.
|
133
|
+
|
134
|
+
Args:
|
135
|
+
tool_instance: The Tools instance
|
136
|
+
information: The information to remember
|
137
|
+
|
138
|
+
Returns:
|
139
|
+
Confirmation message
|
140
|
+
"""
|
141
|
+
return tool_instance.remember(information)
|
142
|
+
|
143
|
+
|
144
|
+
def complete(tool_instance: "Tools", success: bool, reason: str = "") -> None:
|
145
|
+
"""
|
146
|
+
Mark the task as complete.
|
147
|
+
|
148
|
+
Args:
|
149
|
+
tool_instance: The Tools instance
|
150
|
+
success: Whether the task was completed successfully
|
151
|
+
reason: Explanation for success or failure
|
152
|
+
|
153
|
+
Returns:
|
154
|
+
None
|
155
|
+
"""
|
156
|
+
tool_instance.complete(success, reason)
|
157
|
+
|
158
|
+
|
130
159
|
# =============================================================================
|
131
160
|
# ATOMIC ACTION SIGNATURES - Single source of truth for both Executor and CodeAct
|
132
161
|
# =============================================================================
|
@@ -0,0 +1,26 @@
|
|
1
|
+
"""
|
2
|
+
Abstract base class for app card providers.
|
3
|
+
|
4
|
+
Providers load app-specific instruction cards based on package names.
|
5
|
+
Supports multiple backends: local files, remote servers, or composite strategies.
|
6
|
+
"""
|
7
|
+
|
8
|
+
from abc import ABC, abstractmethod
|
9
|
+
|
10
|
+
|
11
|
+
class AppCardProvider(ABC):
|
12
|
+
"""Abstract interface for loading app-specific instruction cards."""
|
13
|
+
|
14
|
+
@abstractmethod
|
15
|
+
async def load_app_card(self, package_name: str, instruction: str = "") -> str:
|
16
|
+
"""
|
17
|
+
Load app card for a given package asynchronously.
|
18
|
+
|
19
|
+
Args:
|
20
|
+
package_name: Android package name (e.g., "com.google.android.gm")
|
21
|
+
instruction: User's instruction/goal (optional context for server providers)
|
22
|
+
|
23
|
+
Returns:
|
24
|
+
App card content as string, or empty string if not found or on error
|
25
|
+
"""
|
26
|
+
pass
|
@@ -0,0 +1,7 @@
|
|
1
|
+
"""App card provider implementations."""
|
2
|
+
|
3
|
+
from droidrun.app_cards.providers.composite_provider import CompositeAppCardProvider
|
4
|
+
from droidrun.app_cards.providers.local_provider import LocalAppCardProvider
|
5
|
+
from droidrun.app_cards.providers.server_provider import ServerAppCardProvider
|
6
|
+
|
7
|
+
__all__ = ["LocalAppCardProvider", "ServerAppCardProvider", "CompositeAppCardProvider"]
|
@@ -0,0 +1,97 @@
|
|
1
|
+
"""
|
2
|
+
Composite app card provider.
|
3
|
+
|
4
|
+
Tries server first, falls back to local if server fails or returns empty.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Dict
|
9
|
+
|
10
|
+
from droidrun.app_cards.app_card_provider import AppCardProvider
|
11
|
+
from droidrun.app_cards.providers.local_provider import LocalAppCardProvider
|
12
|
+
from droidrun.app_cards.providers.server_provider import ServerAppCardProvider
|
13
|
+
|
14
|
+
logger = logging.getLogger("droidrun")
|
15
|
+
|
16
|
+
|
17
|
+
class CompositeAppCardProvider(AppCardProvider):
|
18
|
+
"""
|
19
|
+
Load app cards from server with local fallback.
|
20
|
+
|
21
|
+
Strategy:
|
22
|
+
1. Try server first
|
23
|
+
2. If server fails or returns empty, try local
|
24
|
+
3. Return first non-empty result, or empty if both fail
|
25
|
+
"""
|
26
|
+
|
27
|
+
def __init__(
|
28
|
+
self,
|
29
|
+
server_url: str,
|
30
|
+
app_cards_dir: str = "config/app_cards",
|
31
|
+
server_timeout: float = 2.0,
|
32
|
+
server_max_retries: int = 2
|
33
|
+
):
|
34
|
+
"""
|
35
|
+
Initialize composite provider.
|
36
|
+
|
37
|
+
Args:
|
38
|
+
server_url: Base URL of the app card server
|
39
|
+
app_cards_dir: Directory containing local app_cards.json
|
40
|
+
server_timeout: Server request timeout in seconds
|
41
|
+
server_max_retries: Number of server retry attempts
|
42
|
+
"""
|
43
|
+
self.server_provider = ServerAppCardProvider(
|
44
|
+
server_url=server_url,
|
45
|
+
timeout=server_timeout,
|
46
|
+
max_retries=server_max_retries
|
47
|
+
)
|
48
|
+
self.local_provider = LocalAppCardProvider(app_cards_dir=app_cards_dir)
|
49
|
+
|
50
|
+
async def load_app_card(self, package_name: str, instruction: str = "") -> str:
|
51
|
+
"""
|
52
|
+
Load app card with server-first, local-fallback strategy.
|
53
|
+
|
54
|
+
Args:
|
55
|
+
package_name: Android package name (e.g., "com.google.android.gm")
|
56
|
+
instruction: User instruction/goal
|
57
|
+
|
58
|
+
Returns:
|
59
|
+
App card content from server or local, or empty string if both fail
|
60
|
+
"""
|
61
|
+
if not package_name:
|
62
|
+
return ""
|
63
|
+
|
64
|
+
# Try server first
|
65
|
+
server_result = await self.server_provider.load_app_card(package_name, instruction)
|
66
|
+
|
67
|
+
if server_result:
|
68
|
+
return server_result
|
69
|
+
|
70
|
+
# Server failed or returned empty, try local
|
71
|
+
logger.debug(f"Composite provider: falling back to local for {package_name}")
|
72
|
+
local_result = await self.local_provider.load_app_card(package_name, instruction)
|
73
|
+
|
74
|
+
if local_result:
|
75
|
+
logger.info(f"Composite provider: using local fallback for {package_name}")
|
76
|
+
else:
|
77
|
+
logger.debug(f"Composite provider: no app card found for {package_name}")
|
78
|
+
|
79
|
+
return local_result
|
80
|
+
|
81
|
+
def clear_cache(self) -> None:
|
82
|
+
"""Clear caches in both providers."""
|
83
|
+
self.server_provider.clear_cache()
|
84
|
+
self.local_provider.clear_cache()
|
85
|
+
logger.debug("Composite app card cache cleared")
|
86
|
+
|
87
|
+
def get_cache_stats(self) -> Dict[str, any]:
|
88
|
+
"""
|
89
|
+
Get cache statistics from both providers.
|
90
|
+
|
91
|
+
Returns:
|
92
|
+
Dict with cache stats from server and local providers
|
93
|
+
"""
|
94
|
+
return {
|
95
|
+
"server": self.server_provider.get_cache_stats(),
|
96
|
+
"local": self.local_provider.get_cache_stats(),
|
97
|
+
}
|
@@ -0,0 +1,115 @@
|
|
1
|
+
"""
|
2
|
+
Local file-based app card provider.
|
3
|
+
|
4
|
+
Loads app cards from local filesystem using app_cards.json mapping.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import json
|
8
|
+
import logging
|
9
|
+
from typing import Dict
|
10
|
+
|
11
|
+
from droidrun.app_cards.app_card_provider import AppCardProvider
|
12
|
+
from droidrun.config_manager.path_resolver import PathResolver
|
13
|
+
|
14
|
+
logger = logging.getLogger("droidrun")
|
15
|
+
|
16
|
+
|
17
|
+
class LocalAppCardProvider(AppCardProvider):
|
18
|
+
"""Load app cards from local filesystem with in-memory caching."""
|
19
|
+
|
20
|
+
def __init__(self, app_cards_dir: str = "config/app_cards"):
|
21
|
+
"""
|
22
|
+
Initialize local provider.
|
23
|
+
|
24
|
+
Args:
|
25
|
+
app_cards_dir: Directory containing app_cards.json and markdown files
|
26
|
+
"""
|
27
|
+
# Resolve app_cards.json path once
|
28
|
+
mapping_path = PathResolver.resolve(f"{app_cards_dir}/app_cards.json")
|
29
|
+
self.app_cards_dir = mapping_path.parent
|
30
|
+
|
31
|
+
# Load mapping immediately
|
32
|
+
try:
|
33
|
+
if mapping_path.exists():
|
34
|
+
with open(mapping_path, "r", encoding="utf-8") as f:
|
35
|
+
self.mapping = json.load(f)
|
36
|
+
logger.debug(f"Loaded app_cards.json with {len(self.mapping)} entries")
|
37
|
+
else:
|
38
|
+
logger.warning(f"app_cards.json not found at {mapping_path}")
|
39
|
+
self.mapping = {}
|
40
|
+
except Exception as e:
|
41
|
+
logger.warning(f"Failed to load app_cards.json: {e}")
|
42
|
+
self.mapping = {}
|
43
|
+
|
44
|
+
# Content cache: (package_name, instruction) -> content
|
45
|
+
self._content_cache: Dict[tuple[str, str], str] = {}
|
46
|
+
|
47
|
+
async def load_app_card(self, package_name: str, instruction: str = "") -> str:
|
48
|
+
"""
|
49
|
+
Load app card for a package name from local files.
|
50
|
+
|
51
|
+
Args:
|
52
|
+
package_name: Android package name (e.g., "com.google.android.gm")
|
53
|
+
instruction: User instruction (for cache key consistency, not used in loading)
|
54
|
+
|
55
|
+
Returns:
|
56
|
+
App card content or empty string if not found
|
57
|
+
"""
|
58
|
+
if not package_name:
|
59
|
+
return ""
|
60
|
+
|
61
|
+
# Check content cache first
|
62
|
+
cache_key = (package_name, instruction)
|
63
|
+
if cache_key in self._content_cache:
|
64
|
+
logger.debug(f"App card cache hit: {package_name}")
|
65
|
+
return self._content_cache[cache_key]
|
66
|
+
|
67
|
+
# Check if package exists in mapping
|
68
|
+
if package_name not in self.mapping:
|
69
|
+
self._content_cache[cache_key] = ""
|
70
|
+
return ""
|
71
|
+
|
72
|
+
# Get app card file path (relative to app_cards_dir)
|
73
|
+
filename = self.mapping[package_name]
|
74
|
+
app_card_path = self.app_cards_dir / filename
|
75
|
+
|
76
|
+
# Read file
|
77
|
+
try:
|
78
|
+
if not app_card_path.exists():
|
79
|
+
self._content_cache[cache_key] = ""
|
80
|
+
logger.debug(f"App card not found: {app_card_path}")
|
81
|
+
return ""
|
82
|
+
|
83
|
+
# Async file read
|
84
|
+
import asyncio
|
85
|
+
loop = asyncio.get_event_loop()
|
86
|
+
content = await loop.run_in_executor(
|
87
|
+
None, app_card_path.read_text, "utf-8"
|
88
|
+
)
|
89
|
+
|
90
|
+
# Cache and return
|
91
|
+
self._content_cache[cache_key] = content
|
92
|
+
logger.info(f"Loaded app card for {package_name} from {app_card_path}")
|
93
|
+
return content
|
94
|
+
|
95
|
+
except Exception as e:
|
96
|
+
logger.warning(f"Failed to load app card for {package_name}: {e}")
|
97
|
+
self._content_cache[cache_key] = ""
|
98
|
+
return ""
|
99
|
+
|
100
|
+
def clear_cache(self) -> None:
|
101
|
+
"""Clear content cache (useful for testing or runtime reloading)."""
|
102
|
+
self._content_cache.clear()
|
103
|
+
logger.debug("Local app card cache cleared")
|
104
|
+
|
105
|
+
def get_cache_stats(self) -> Dict[str, int]:
|
106
|
+
"""
|
107
|
+
Get cache statistics.
|
108
|
+
|
109
|
+
Returns:
|
110
|
+
Dict with cache stats (useful for debugging)
|
111
|
+
"""
|
112
|
+
return {
|
113
|
+
"content_entries": len(self._content_cache),
|
114
|
+
"mapping_entries": len(self.mapping),
|
115
|
+
}
|
@@ -0,0 +1,126 @@
|
|
1
|
+
"""
|
2
|
+
Server-based app card provider.
|
3
|
+
|
4
|
+
Fetches app cards from a remote HTTP server.
|
5
|
+
"""
|
6
|
+
|
7
|
+
import logging
|
8
|
+
from typing import Dict
|
9
|
+
|
10
|
+
import httpx
|
11
|
+
|
12
|
+
from droidrun.app_cards.app_card_provider import AppCardProvider
|
13
|
+
|
14
|
+
logger = logging.getLogger("droidrun")
|
15
|
+
|
16
|
+
|
17
|
+
class ServerAppCardProvider(AppCardProvider):
|
18
|
+
"""Load app cards from remote server with in-memory caching."""
|
19
|
+
|
20
|
+
def __init__(
|
21
|
+
self,
|
22
|
+
server_url: str,
|
23
|
+
timeout: float = 2.0,
|
24
|
+
max_retries: int = 2
|
25
|
+
):
|
26
|
+
"""
|
27
|
+
Initialize server provider.
|
28
|
+
|
29
|
+
Args:
|
30
|
+
server_url: Base URL of the app card server (e.g., "https://api.example.com")
|
31
|
+
timeout: Request timeout in seconds
|
32
|
+
max_retries: Number of retry attempts on failure
|
33
|
+
"""
|
34
|
+
self.server_url = server_url.rstrip("/")
|
35
|
+
self.timeout = timeout
|
36
|
+
self.max_retries = max_retries
|
37
|
+
self._content_cache: Dict[tuple[str, str], str] = {}
|
38
|
+
|
39
|
+
async def load_app_card(self, package_name: str, instruction: str = "") -> str:
|
40
|
+
"""
|
41
|
+
Load app card from remote server.
|
42
|
+
|
43
|
+
Args:
|
44
|
+
package_name: Android package name (e.g., "com.google.android.gm")
|
45
|
+
instruction: User instruction/goal (sent to server for context)
|
46
|
+
|
47
|
+
Returns:
|
48
|
+
App card content or empty string if not found or on error
|
49
|
+
"""
|
50
|
+
if not package_name:
|
51
|
+
return ""
|
52
|
+
|
53
|
+
# Check content cache first (key: package_name, instruction)
|
54
|
+
cache_key = (package_name, instruction)
|
55
|
+
if cache_key in self._content_cache:
|
56
|
+
return self._content_cache[cache_key]
|
57
|
+
|
58
|
+
# Make HTTP request with retries
|
59
|
+
endpoint = f"{self.server_url}/app-cards"
|
60
|
+
payload = {
|
61
|
+
"package_name": package_name,
|
62
|
+
"instruction": instruction
|
63
|
+
}
|
64
|
+
|
65
|
+
for attempt in range(1, self.max_retries + 1):
|
66
|
+
try:
|
67
|
+
async with httpx.AsyncClient(timeout=self.timeout) as client:
|
68
|
+
response = await client.post(endpoint, json=payload)
|
69
|
+
|
70
|
+
if response.status_code == 200:
|
71
|
+
data = response.json()
|
72
|
+
app_card = data.get("app_card", "")
|
73
|
+
|
74
|
+
# Cache the result (even if empty)
|
75
|
+
self._content_cache[cache_key] = app_card
|
76
|
+
return app_card
|
77
|
+
|
78
|
+
elif response.status_code == 404:
|
79
|
+
# Not found is expected, cache empty result
|
80
|
+
self._content_cache[cache_key] = ""
|
81
|
+
return ""
|
82
|
+
|
83
|
+
else:
|
84
|
+
logger.warning(
|
85
|
+
f"Server returned status {response.status_code} for {package_name} "
|
86
|
+
f"(attempt {attempt}/{self.max_retries})"
|
87
|
+
)
|
88
|
+
|
89
|
+
except httpx.TimeoutException:
|
90
|
+
logger.warning(
|
91
|
+
f"Server request timeout for {package_name} "
|
92
|
+
f"(attempt {attempt}/{self.max_retries})"
|
93
|
+
)
|
94
|
+
|
95
|
+
except httpx.RequestError as e:
|
96
|
+
logger.warning(
|
97
|
+
f"Server request failed for {package_name}: {e} "
|
98
|
+
f"(attempt {attempt}/{self.max_retries})"
|
99
|
+
)
|
100
|
+
|
101
|
+
except Exception as e:
|
102
|
+
logger.warning(
|
103
|
+
f"Unexpected error loading app card from server: {e} "
|
104
|
+
f"(attempt {attempt}/{self.max_retries})"
|
105
|
+
)
|
106
|
+
|
107
|
+
# All retries failed, cache empty result
|
108
|
+
logger.warning(f"Failed to load app card from server after {self.max_retries} attempts")
|
109
|
+
self._content_cache[cache_key] = ""
|
110
|
+
return ""
|
111
|
+
|
112
|
+
def clear_cache(self) -> None:
|
113
|
+
"""Clear content cache."""
|
114
|
+
self._content_cache.clear()
|
115
|
+
logger.debug("Server app card cache cleared")
|
116
|
+
|
117
|
+
def get_cache_stats(self) -> Dict[str, int]:
|
118
|
+
"""
|
119
|
+
Get cache statistics.
|
120
|
+
|
121
|
+
Returns:
|
122
|
+
Dict with cache stats (useful for debugging)
|
123
|
+
"""
|
124
|
+
return {
|
125
|
+
"content_entries": len(self._content_cache),
|
126
|
+
}
|
droidrun/cli/logs.py
CHANGED
@@ -21,14 +21,14 @@ from droidrun.agent.droid.events import (
|
|
21
21
|
FinalizeEvent,
|
22
22
|
TaskRunnerEvent,
|
23
23
|
)
|
24
|
-
from droidrun.agent.manager.events import (
|
25
|
-
ManagerInternalPlanEvent,
|
26
|
-
ManagerThinkingEvent,
|
27
|
-
)
|
28
24
|
from droidrun.agent.executor.events import (
|
29
25
|
ExecutorInternalActionEvent,
|
30
26
|
ExecutorInternalResultEvent,
|
31
27
|
)
|
28
|
+
from droidrun.agent.manager.events import (
|
29
|
+
ManagerInternalPlanEvent,
|
30
|
+
ManagerThinkingEvent,
|
31
|
+
)
|
32
32
|
|
33
33
|
|
34
34
|
class LogHandler(logging.Handler):
|