autoglm-gui 1.4.0__py3-none-any.whl → 1.5.0__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.
- AutoGLM_GUI/__init__.py +11 -0
- AutoGLM_GUI/__main__.py +26 -8
- AutoGLM_GUI/actions/__init__.py +6 -0
- AutoGLM_GUI/actions/handler.py +196 -0
- AutoGLM_GUI/actions/types.py +15 -0
- AutoGLM_GUI/adb/__init__.py +53 -0
- AutoGLM_GUI/adb/apps.py +227 -0
- AutoGLM_GUI/adb/connection.py +323 -0
- AutoGLM_GUI/adb/device.py +171 -0
- AutoGLM_GUI/adb/input.py +67 -0
- AutoGLM_GUI/adb/screenshot.py +11 -0
- AutoGLM_GUI/adb/timing.py +167 -0
- AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
- AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
- AutoGLM_GUI/adb_plus/screenshot.py +22 -1
- AutoGLM_GUI/adb_plus/serial.py +38 -20
- AutoGLM_GUI/adb_plus/touch.py +4 -9
- AutoGLM_GUI/agents/__init__.py +51 -0
- AutoGLM_GUI/agents/events.py +19 -0
- AutoGLM_GUI/agents/factory.py +153 -0
- AutoGLM_GUI/agents/glm/__init__.py +7 -0
- AutoGLM_GUI/agents/glm/agent.py +292 -0
- AutoGLM_GUI/agents/glm/message_builder.py +81 -0
- AutoGLM_GUI/agents/glm/parser.py +110 -0
- AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
- AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
- AutoGLM_GUI/agents/mai/__init__.py +28 -0
- AutoGLM_GUI/agents/mai/agent.py +405 -0
- AutoGLM_GUI/agents/mai/parser.py +254 -0
- AutoGLM_GUI/agents/mai/prompts.py +103 -0
- AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
- AutoGLM_GUI/agents/protocols.py +27 -0
- AutoGLM_GUI/agents/stream_runner.py +188 -0
- AutoGLM_GUI/api/__init__.py +71 -11
- AutoGLM_GUI/api/agents.py +190 -229
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +112 -28
- AutoGLM_GUI/api/health.py +13 -0
- AutoGLM_GUI/api/history.py +78 -0
- AutoGLM_GUI/api/layered_agent.py +306 -181
- AutoGLM_GUI/api/mcp.py +11 -10
- AutoGLM_GUI/api/media.py +64 -1
- AutoGLM_GUI/api/scheduled_tasks.py +98 -0
- AutoGLM_GUI/api/version.py +23 -10
- AutoGLM_GUI/api/workflows.py +2 -1
- AutoGLM_GUI/config.py +72 -14
- AutoGLM_GUI/config_manager.py +98 -27
- AutoGLM_GUI/device_adapter.py +263 -0
- AutoGLM_GUI/device_manager.py +248 -29
- AutoGLM_GUI/device_protocol.py +266 -0
- AutoGLM_GUI/devices/__init__.py +49 -0
- AutoGLM_GUI/devices/adb_device.py +200 -0
- AutoGLM_GUI/devices/mock_device.py +185 -0
- AutoGLM_GUI/devices/remote_device.py +177 -0
- AutoGLM_GUI/exceptions.py +3 -3
- AutoGLM_GUI/history_manager.py +164 -0
- AutoGLM_GUI/i18n.py +81 -0
- AutoGLM_GUI/metrics.py +13 -20
- AutoGLM_GUI/model/__init__.py +5 -0
- AutoGLM_GUI/model/message_builder.py +69 -0
- AutoGLM_GUI/model/types.py +24 -0
- AutoGLM_GUI/models/__init__.py +10 -0
- AutoGLM_GUI/models/history.py +96 -0
- AutoGLM_GUI/models/scheduled_task.py +71 -0
- AutoGLM_GUI/parsers/__init__.py +22 -0
- AutoGLM_GUI/parsers/base.py +50 -0
- AutoGLM_GUI/parsers/phone_parser.py +58 -0
- AutoGLM_GUI/phone_agent_manager.py +118 -367
- AutoGLM_GUI/platform_utils.py +31 -2
- AutoGLM_GUI/prompt_config.py +15 -0
- AutoGLM_GUI/prompts/__init__.py +32 -0
- AutoGLM_GUI/scheduler_manager.py +304 -0
- AutoGLM_GUI/schemas.py +272 -63
- AutoGLM_GUI/scrcpy_stream.py +159 -37
- AutoGLM_GUI/server.py +3 -1
- AutoGLM_GUI/socketio_server.py +114 -29
- AutoGLM_GUI/state.py +10 -30
- AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-BQm96DAl.js} +1 -1
- AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
- AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
- AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
- AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
- AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
- AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
- AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-CmZSnDqc.js} +1 -1
- AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
- AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
- AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
- AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
- AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
- AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
- AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
- AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/types.py +142 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +178 -92
- autoglm_gui-1.5.0.dist-info/RECORD +157 -0
- mai_agent/base.py +137 -0
- mai_agent/mai_grounding_agent.py +263 -0
- mai_agent/mai_naivigation_agent.py +526 -0
- mai_agent/prompt.py +148 -0
- mai_agent/unified_memory.py +67 -0
- mai_agent/utils.py +73 -0
- AutoGLM_GUI/api/dual_model.py +0 -311
- AutoGLM_GUI/dual_model/__init__.py +0 -53
- AutoGLM_GUI/dual_model/decision_model.py +0 -664
- AutoGLM_GUI/dual_model/dual_agent.py +0 -917
- AutoGLM_GUI/dual_model/protocols.py +0 -354
- AutoGLM_GUI/dual_model/vision_model.py +0 -442
- AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
- AutoGLM_GUI/phone_agent_patches.py +0 -146
- AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
- AutoGLM_GUI/static/assets/dialog-BfdcBs1x.js +0 -45
- AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
- AutoGLM_GUI/static/assets/index-DHF1NZh0.js +0 -12
- AutoGLM_GUI/static/assets/workflows-xiplap-r.js +0 -1
- autoglm_gui-1.4.0.dist-info/RECORD +0 -100
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
"""Timing configuration for Phone Agent.
|
|
2
|
+
|
|
3
|
+
This module defines all configurable waiting times used throughout the application.
|
|
4
|
+
Users can customize these values by modifying this file or by setting environment variables.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
import os
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
@dataclass
|
|
12
|
+
class ActionTimingConfig:
|
|
13
|
+
"""Configuration for action handler timing delays."""
|
|
14
|
+
|
|
15
|
+
# Text input related delays (in seconds)
|
|
16
|
+
keyboard_switch_delay: float = 1.0 # Delay after switching to ADB keyboard
|
|
17
|
+
text_clear_delay: float = 1.0 # Delay after clearing text
|
|
18
|
+
text_input_delay: float = 1.0 # Delay after typing text
|
|
19
|
+
keyboard_restore_delay: float = 1.0 # Delay after restoring original keyboard
|
|
20
|
+
|
|
21
|
+
def __post_init__(self):
|
|
22
|
+
"""Load values from environment variables if present."""
|
|
23
|
+
self.keyboard_switch_delay = float(
|
|
24
|
+
os.getenv("PHONE_AGENT_KEYBOARD_SWITCH_DELAY", self.keyboard_switch_delay)
|
|
25
|
+
)
|
|
26
|
+
self.text_clear_delay = float(
|
|
27
|
+
os.getenv("PHONE_AGENT_TEXT_CLEAR_DELAY", self.text_clear_delay)
|
|
28
|
+
)
|
|
29
|
+
self.text_input_delay = float(
|
|
30
|
+
os.getenv("PHONE_AGENT_TEXT_INPUT_DELAY", self.text_input_delay)
|
|
31
|
+
)
|
|
32
|
+
self.keyboard_restore_delay = float(
|
|
33
|
+
os.getenv("PHONE_AGENT_KEYBOARD_RESTORE_DELAY", self.keyboard_restore_delay)
|
|
34
|
+
)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
@dataclass
|
|
38
|
+
class DeviceTimingConfig:
|
|
39
|
+
"""Configuration for device operation timing delays."""
|
|
40
|
+
|
|
41
|
+
# Default delays for various device operations (in seconds)
|
|
42
|
+
default_tap_delay: float = 1.0 # Default delay after tap
|
|
43
|
+
default_double_tap_delay: float = 1.0 # Default delay after double tap
|
|
44
|
+
double_tap_interval: float = 0.1 # Interval between two taps in double tap
|
|
45
|
+
default_long_press_delay: float = 1.0 # Default delay after long press
|
|
46
|
+
default_swipe_delay: float = 1.0 # Default delay after swipe
|
|
47
|
+
default_back_delay: float = 1.0 # Default delay after back button
|
|
48
|
+
default_home_delay: float = 1.0 # Default delay after home button
|
|
49
|
+
default_launch_delay: float = 1.0 # Default delay after launching app
|
|
50
|
+
|
|
51
|
+
def __post_init__(self):
|
|
52
|
+
"""Load values from environment variables if present."""
|
|
53
|
+
self.default_tap_delay = float(
|
|
54
|
+
os.getenv("PHONE_AGENT_TAP_DELAY", self.default_tap_delay)
|
|
55
|
+
)
|
|
56
|
+
self.default_double_tap_delay = float(
|
|
57
|
+
os.getenv("PHONE_AGENT_DOUBLE_TAP_DELAY", self.default_double_tap_delay)
|
|
58
|
+
)
|
|
59
|
+
self.double_tap_interval = float(
|
|
60
|
+
os.getenv("PHONE_AGENT_DOUBLE_TAP_INTERVAL", self.double_tap_interval)
|
|
61
|
+
)
|
|
62
|
+
self.default_long_press_delay = float(
|
|
63
|
+
os.getenv("PHONE_AGENT_LONG_PRESS_DELAY", self.default_long_press_delay)
|
|
64
|
+
)
|
|
65
|
+
self.default_swipe_delay = float(
|
|
66
|
+
os.getenv("PHONE_AGENT_SWIPE_DELAY", self.default_swipe_delay)
|
|
67
|
+
)
|
|
68
|
+
self.default_back_delay = float(
|
|
69
|
+
os.getenv("PHONE_AGENT_BACK_DELAY", self.default_back_delay)
|
|
70
|
+
)
|
|
71
|
+
self.default_home_delay = float(
|
|
72
|
+
os.getenv("PHONE_AGENT_HOME_DELAY", self.default_home_delay)
|
|
73
|
+
)
|
|
74
|
+
self.default_launch_delay = float(
|
|
75
|
+
os.getenv("PHONE_AGENT_LAUNCH_DELAY", self.default_launch_delay)
|
|
76
|
+
)
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
@dataclass
|
|
80
|
+
class ConnectionTimingConfig:
|
|
81
|
+
"""Configuration for ADB connection timing delays."""
|
|
82
|
+
|
|
83
|
+
# ADB server and connection delays (in seconds)
|
|
84
|
+
adb_restart_delay: float = 2.0 # Wait time after enabling TCP/IP mode
|
|
85
|
+
server_restart_delay: float = (
|
|
86
|
+
1.0 # Wait time between killing and starting ADB server
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
def __post_init__(self):
|
|
90
|
+
"""Load values from environment variables if present."""
|
|
91
|
+
self.adb_restart_delay = float(
|
|
92
|
+
os.getenv("PHONE_AGENT_ADB_RESTART_DELAY", self.adb_restart_delay)
|
|
93
|
+
)
|
|
94
|
+
self.server_restart_delay = float(
|
|
95
|
+
os.getenv("PHONE_AGENT_SERVER_RESTART_DELAY", self.server_restart_delay)
|
|
96
|
+
)
|
|
97
|
+
|
|
98
|
+
|
|
99
|
+
@dataclass
|
|
100
|
+
class TimingConfig:
|
|
101
|
+
"""Master timing configuration combining all timing settings."""
|
|
102
|
+
|
|
103
|
+
action: ActionTimingConfig
|
|
104
|
+
device: DeviceTimingConfig
|
|
105
|
+
connection: ConnectionTimingConfig
|
|
106
|
+
|
|
107
|
+
def __init__(self):
|
|
108
|
+
"""Initialize all timing configurations."""
|
|
109
|
+
self.action = ActionTimingConfig()
|
|
110
|
+
self.device = DeviceTimingConfig()
|
|
111
|
+
self.connection = ConnectionTimingConfig()
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
# Global timing configuration instance
|
|
115
|
+
# Users can modify these values at runtime or through environment variables
|
|
116
|
+
TIMING_CONFIG = TimingConfig()
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def get_timing_config() -> TimingConfig:
|
|
120
|
+
"""
|
|
121
|
+
Get the global timing configuration.
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
The global TimingConfig instance.
|
|
125
|
+
"""
|
|
126
|
+
return TIMING_CONFIG
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def update_timing_config(
|
|
130
|
+
action: ActionTimingConfig | None = None,
|
|
131
|
+
device: DeviceTimingConfig | None = None,
|
|
132
|
+
connection: ConnectionTimingConfig | None = None,
|
|
133
|
+
) -> None:
|
|
134
|
+
"""
|
|
135
|
+
Update the global timing configuration.
|
|
136
|
+
|
|
137
|
+
Args:
|
|
138
|
+
action: New action timing configuration.
|
|
139
|
+
device: New device timing configuration.
|
|
140
|
+
connection: New connection timing configuration.
|
|
141
|
+
|
|
142
|
+
Example:
|
|
143
|
+
>>> from AutoGLM_GUI.adb.timing import update_timing_config, ActionTimingConfig
|
|
144
|
+
>>> custom_action = ActionTimingConfig(
|
|
145
|
+
... keyboard_switch_delay=0.5,
|
|
146
|
+
... text_input_delay=0.5
|
|
147
|
+
... )
|
|
148
|
+
>>> update_timing_config(action=custom_action)
|
|
149
|
+
"""
|
|
150
|
+
global TIMING_CONFIG
|
|
151
|
+
if action is not None:
|
|
152
|
+
TIMING_CONFIG.action = action
|
|
153
|
+
if device is not None:
|
|
154
|
+
TIMING_CONFIG.device = device
|
|
155
|
+
if connection is not None:
|
|
156
|
+
TIMING_CONFIG.connection = connection
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
__all__ = [
|
|
160
|
+
"ActionTimingConfig",
|
|
161
|
+
"DeviceTimingConfig",
|
|
162
|
+
"ConnectionTimingConfig",
|
|
163
|
+
"TimingConfig",
|
|
164
|
+
"TIMING_CONFIG",
|
|
165
|
+
"get_timing_config",
|
|
166
|
+
"update_timing_config",
|
|
167
|
+
]
|
|
@@ -104,8 +104,10 @@ class ADBKeyboardInstaller:
|
|
|
104
104
|
from importlib.resources import files
|
|
105
105
|
|
|
106
106
|
logger.debug("Searching for bundled APK in wheel package")
|
|
107
|
-
resource =
|
|
108
|
-
"
|
|
107
|
+
resource = (
|
|
108
|
+
files("AutoGLM_GUI")
|
|
109
|
+
.joinpath("resources/apks")
|
|
110
|
+
.joinpath(ADB_KEYBOARD_APK_FILENAME)
|
|
109
111
|
)
|
|
110
112
|
# Convert to Path
|
|
111
113
|
if hasattr(resource, "read_bytes"):
|
AutoGLM_GUI/adb_plus/qr_pair.py
CHANGED
|
@@ -124,9 +124,9 @@ class QRPairingListener(ServiceListener):
|
|
|
124
124
|
|
|
125
125
|
self.last_paired_host: Optional[str] = None
|
|
126
126
|
|
|
127
|
-
def add_service(self, zc: Zeroconf,
|
|
127
|
+
def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
|
|
128
128
|
"""Handle new service discovery."""
|
|
129
|
-
info = zc.get_service_info(
|
|
129
|
+
info = zc.get_service_info(type_, name, timeout=3000)
|
|
130
130
|
if not info:
|
|
131
131
|
logger.debug(f"[QR Pair] No info for service: {name}")
|
|
132
132
|
return
|
|
@@ -144,7 +144,7 @@ class QRPairingListener(ServiceListener):
|
|
|
144
144
|
key = (host, port)
|
|
145
145
|
|
|
146
146
|
# Handle pairing service
|
|
147
|
-
if
|
|
147
|
+
if type_ == PAIR_SERVICE_TYPE and not self.paired:
|
|
148
148
|
if key in self.attempted_pair:
|
|
149
149
|
logger.debug(f"[QR Pair] Already attempted pairing for {host}:{port}")
|
|
150
150
|
return
|
|
@@ -161,7 +161,7 @@ class QRPairingListener(ServiceListener):
|
|
|
161
161
|
logger.info("[QR Pair] Pairing OK. Waiting for connect service...")
|
|
162
162
|
|
|
163
163
|
# Handle connect service
|
|
164
|
-
if
|
|
164
|
+
if type_ == CONNECT_SERVICE_TYPE and self.paired and not self.connected:
|
|
165
165
|
# Prefer same host as paired if we have it
|
|
166
166
|
if self.last_paired_host and host != self.last_paired_host:
|
|
167
167
|
logger.debug(
|
|
@@ -186,13 +186,13 @@ class QRPairingListener(ServiceListener):
|
|
|
186
186
|
self.session.device_id = f"{host}:{port}"
|
|
187
187
|
logger.info(f"[QR Pair] Connected! Device ID: {self.session.device_id}")
|
|
188
188
|
|
|
189
|
-
def update_service(self, zc: Zeroconf,
|
|
189
|
+
def update_service(self, zc: Zeroconf, type_: str, name: str) -> None:
|
|
190
190
|
"""Handle service updates (treat as adds)."""
|
|
191
|
-
self.add_service(zc,
|
|
191
|
+
self.add_service(zc, type_, name)
|
|
192
192
|
|
|
193
|
-
def remove_service(self,
|
|
193
|
+
def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:
|
|
194
194
|
"""Handle service removal (no action needed)."""
|
|
195
|
-
|
|
195
|
+
_ = (zc, type_, name) # Mark as intentionally unused
|
|
196
196
|
|
|
197
197
|
|
|
198
198
|
class QRPairingManager:
|
|
@@ -13,6 +13,8 @@ from io import BytesIO
|
|
|
13
13
|
|
|
14
14
|
from PIL import Image
|
|
15
15
|
|
|
16
|
+
from AutoGLM_GUI.exceptions import DeviceNotAvailableError
|
|
17
|
+
|
|
16
18
|
|
|
17
19
|
PNG_SIGNATURE = b"\x89PNG\r\n\x1a\n"
|
|
18
20
|
|
|
@@ -44,9 +46,13 @@ def capture_screenshot(
|
|
|
44
46
|
|
|
45
47
|
Returns:
|
|
46
48
|
Screenshot object; falls back to a black image on failure.
|
|
49
|
+
|
|
50
|
+
Raises:
|
|
51
|
+
DeviceNotAvailableError: When device is not found or offline.
|
|
47
52
|
"""
|
|
48
53
|
attempts = max(1, retries + 1)
|
|
49
54
|
for _ in range(attempts):
|
|
55
|
+
# _try_capture may raise DeviceNotAvailableError, let it propagate
|
|
50
56
|
data = _try_capture(device_id=device_id, adb_path=adb_path, timeout=timeout)
|
|
51
57
|
if not data:
|
|
52
58
|
continue
|
|
@@ -72,7 +78,11 @@ def capture_screenshot(
|
|
|
72
78
|
|
|
73
79
|
|
|
74
80
|
def _try_capture(device_id: str | None, adb_path: str, timeout: int) -> bytes | None:
|
|
75
|
-
"""Run exec-out screencap and return raw bytes or None on failure.
|
|
81
|
+
"""Run exec-out screencap and return raw bytes or None on failure.
|
|
82
|
+
|
|
83
|
+
Raises:
|
|
84
|
+
DeviceNotAvailableError: When device is not found or offline.
|
|
85
|
+
"""
|
|
76
86
|
cmd: list[str | bytes] = [adb_path]
|
|
77
87
|
if device_id:
|
|
78
88
|
cmd.extend(["-s", device_id])
|
|
@@ -85,9 +95,20 @@ def _try_capture(device_id: str | None, adb_path: str, timeout: int) -> bytes |
|
|
|
85
95
|
timeout=timeout,
|
|
86
96
|
)
|
|
87
97
|
if result.returncode != 0:
|
|
98
|
+
# Check for device not found or offline errors
|
|
99
|
+
stderr = (
|
|
100
|
+
result.stderr.decode("utf-8", errors="ignore") if result.stderr else ""
|
|
101
|
+
)
|
|
102
|
+
stderr_lower = stderr.lower()
|
|
103
|
+
if "device not found" in stderr_lower or "offline" in stderr_lower:
|
|
104
|
+
raise DeviceNotAvailableError(
|
|
105
|
+
f"Device {device_id} not found or offline"
|
|
106
|
+
)
|
|
88
107
|
return None
|
|
89
108
|
# stdout should hold the PNG data
|
|
90
109
|
return result.stdout if isinstance(result.stdout, (bytes, bytearray)) else None
|
|
110
|
+
except DeviceNotAvailableError:
|
|
111
|
+
raise # Re-raise to caller
|
|
91
112
|
except Exception:
|
|
92
113
|
return None
|
|
93
114
|
|
AutoGLM_GUI/adb_plus/serial.py
CHANGED
|
@@ -49,12 +49,22 @@ def extract_serial_from_mdns(device_id: str) -> Optional[str]:
|
|
|
49
49
|
return None
|
|
50
50
|
|
|
51
51
|
|
|
52
|
-
|
|
52
|
+
# Serial number properties to try, in order of preference
|
|
53
|
+
_SERIAL_PROPS = [
|
|
54
|
+
"ro.serialno",
|
|
55
|
+
"ro.boot.serialno",
|
|
56
|
+
"ro.product.serial",
|
|
57
|
+
]
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def get_device_serial(device_id: str, adb_path: str = "adb") -> str:
|
|
53
61
|
"""
|
|
54
62
|
Get the real hardware serial number of a device.
|
|
55
63
|
|
|
56
64
|
For mDNS devices, attempts to extract serial from service name first.
|
|
57
65
|
Falls back to getprop for USB/WiFi devices or if extraction fails.
|
|
66
|
+
If all methods fail, returns device_id as fallback (for emulators or
|
|
67
|
+
restricted devices that don't expose serial number).
|
|
58
68
|
|
|
59
69
|
This works for both USB and WiFi connected devices,
|
|
60
70
|
returning the actual hardware serial number (ro.serialno).
|
|
@@ -64,7 +74,8 @@ def get_device_serial(device_id: str, adb_path: str = "adb") -> str | None:
|
|
|
64
74
|
adb_path: Path to adb executable (default: "adb")
|
|
65
75
|
|
|
66
76
|
Returns:
|
|
67
|
-
The device hardware serial number
|
|
77
|
+
The device hardware serial number. Always returns a value - uses
|
|
78
|
+
device_id as fallback if serial cannot be obtained.
|
|
68
79
|
"""
|
|
69
80
|
from AutoGLM_GUI.logger import logger
|
|
70
81
|
|
|
@@ -74,21 +85,28 @@ def get_device_serial(device_id: str, adb_path: str = "adb") -> str | None:
|
|
|
74
85
|
logger.debug(f"Extracted serial from mDNS name: {device_id} → {mdns_serial}")
|
|
75
86
|
return mdns_serial
|
|
76
87
|
|
|
77
|
-
#
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
# Try multiple serial properties (some emulators use different props)
|
|
89
|
+
for prop in _SERIAL_PROPS:
|
|
90
|
+
try:
|
|
91
|
+
result = run_cmd_silently_sync(
|
|
92
|
+
[adb_path, "-s", device_id, "shell", "getprop", prop],
|
|
93
|
+
timeout=5, # Increased timeout for network devices
|
|
94
|
+
)
|
|
95
|
+
if result.returncode == 0:
|
|
96
|
+
serial = result.stdout.strip()
|
|
97
|
+
# Filter out error messages and empty values
|
|
98
|
+
if serial and not serial.startswith("error:") and serial != "unknown":
|
|
99
|
+
logger.debug(f"Got serial via {prop}: {device_id} → {serial}")
|
|
100
|
+
return serial
|
|
101
|
+
except Exception as e:
|
|
102
|
+
logger.debug(f"Failed to get serial via {prop} for {device_id}: {e}")
|
|
103
|
+
continue
|
|
104
|
+
|
|
105
|
+
# Fallback: Use device_id itself as serial
|
|
106
|
+
# This handles emulators (MuMu, Nox, etc.) and restricted devices
|
|
107
|
+
# that don't expose serial number via getprop
|
|
108
|
+
logger.warning(
|
|
109
|
+
f"Could not get hardware serial for {device_id}, "
|
|
110
|
+
f"using device_id as serial (emulator/restricted device)"
|
|
111
|
+
)
|
|
112
|
+
return device_id
|
AutoGLM_GUI/adb_plus/touch.py
CHANGED
|
@@ -3,12 +3,7 @@
|
|
|
3
3
|
import subprocess
|
|
4
4
|
import time
|
|
5
5
|
|
|
6
|
-
|
|
7
|
-
def _get_adb_prefix(device_id: str | None, adb_path: str = "adb") -> list[str]:
|
|
8
|
-
"""Get ADB command prefix with optional device specifier."""
|
|
9
|
-
if device_id:
|
|
10
|
-
return [adb_path, "-s", device_id]
|
|
11
|
-
return [adb_path]
|
|
6
|
+
from AutoGLM_GUI.platform_utils import build_adb_command
|
|
12
7
|
|
|
13
8
|
|
|
14
9
|
def touch_down(
|
|
@@ -28,7 +23,7 @@ def touch_down(
|
|
|
28
23
|
delay: Delay in seconds after event (default: 0.0 for real-time).
|
|
29
24
|
adb_path: Path to adb binary.
|
|
30
25
|
"""
|
|
31
|
-
adb_prefix =
|
|
26
|
+
adb_prefix = build_adb_command(device_id, adb_path)
|
|
32
27
|
|
|
33
28
|
subprocess.run(
|
|
34
29
|
adb_prefix + ["shell", "input", "motionevent", "DOWN", str(x), str(y)],
|
|
@@ -55,7 +50,7 @@ def touch_move(
|
|
|
55
50
|
delay: Delay in seconds after event (default: 0.0 for real-time).
|
|
56
51
|
adb_path: Path to adb binary.
|
|
57
52
|
"""
|
|
58
|
-
adb_prefix =
|
|
53
|
+
adb_prefix = build_adb_command(device_id, adb_path)
|
|
59
54
|
|
|
60
55
|
subprocess.run(
|
|
61
56
|
adb_prefix + ["shell", "input", "motionevent", "MOVE", str(x), str(y)],
|
|
@@ -82,7 +77,7 @@ def touch_up(
|
|
|
82
77
|
delay: Delay in seconds after event (default: 0.0 for real-time).
|
|
83
78
|
adb_path: Path to adb binary.
|
|
84
79
|
"""
|
|
85
|
-
adb_prefix =
|
|
80
|
+
adb_prefix = build_adb_command(device_id, adb_path)
|
|
86
81
|
|
|
87
82
|
subprocess.run(
|
|
88
83
|
adb_prefix + ["shell", "input", "motionevent", "UP", str(x), str(y)],
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import Callable
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def register_agent(agent_type: str, creator: Callable) -> None:
|
|
7
|
+
from .factory import register_agent as _register_agent
|
|
8
|
+
|
|
9
|
+
_register_agent(agent_type=agent_type, creator=creator)
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def create_agent(
|
|
13
|
+
agent_type: str,
|
|
14
|
+
model_config,
|
|
15
|
+
agent_config,
|
|
16
|
+
agent_specific_config,
|
|
17
|
+
device,
|
|
18
|
+
takeover_callback: Callable | None = None,
|
|
19
|
+
confirmation_callback: Callable | None = None,
|
|
20
|
+
):
|
|
21
|
+
from .factory import create_agent as _create_agent
|
|
22
|
+
|
|
23
|
+
return _create_agent(
|
|
24
|
+
agent_type=agent_type,
|
|
25
|
+
model_config=model_config,
|
|
26
|
+
agent_config=agent_config,
|
|
27
|
+
agent_specific_config=agent_specific_config,
|
|
28
|
+
device=device,
|
|
29
|
+
takeover_callback=takeover_callback,
|
|
30
|
+
confirmation_callback=confirmation_callback,
|
|
31
|
+
)
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def list_agent_types() -> list[str]:
|
|
35
|
+
from .factory import list_agent_types as _list_agent_types
|
|
36
|
+
|
|
37
|
+
return _list_agent_types()
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def is_agent_type_registered(agent_type: str) -> bool:
|
|
41
|
+
from .factory import is_agent_type_registered as _is_agent_type_registered
|
|
42
|
+
|
|
43
|
+
return _is_agent_type_registered(agent_type)
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
__all__ = [
|
|
47
|
+
"create_agent",
|
|
48
|
+
"register_agent",
|
|
49
|
+
"list_agent_types",
|
|
50
|
+
"is_agent_type_registered",
|
|
51
|
+
]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
from enum import Enum
|
|
2
|
+
from typing import Any, TypedDict
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
class AgentEventType(str, Enum):
|
|
6
|
+
"""Agent 事件类型."""
|
|
7
|
+
|
|
8
|
+
THINKING = "thinking_chunk"
|
|
9
|
+
STEP = "step"
|
|
10
|
+
DONE = "done"
|
|
11
|
+
ERROR = "error"
|
|
12
|
+
ABORTED = "aborted"
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class AgentEvent(TypedDict):
|
|
16
|
+
"""Agent 事件(统一类型)."""
|
|
17
|
+
|
|
18
|
+
type: str # 使用字符串以兼容现有 SSE 类型
|
|
19
|
+
data: dict[str, Any]
|
|
@@ -0,0 +1,153 @@
|
|
|
1
|
+
"""Agent factory for creating different agent implementations.
|
|
2
|
+
|
|
3
|
+
This module provides a factory pattern + registry for creating agents,
|
|
4
|
+
making it easy to add new agent types without modifying existing code.
|
|
5
|
+
"""
|
|
6
|
+
|
|
7
|
+
from __future__ import annotations
|
|
8
|
+
|
|
9
|
+
from typing import Callable, Dict
|
|
10
|
+
|
|
11
|
+
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
12
|
+
from AutoGLM_GUI.logger import logger
|
|
13
|
+
from AutoGLM_GUI.types import AgentSpecificConfig
|
|
14
|
+
|
|
15
|
+
from .protocols import BaseAgent
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
# Agent registry: agent_type -> (creator_function, config_schema)
|
|
19
|
+
AGENT_REGISTRY: Dict[str, Callable] = {}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def register_agent(
|
|
23
|
+
agent_type: str,
|
|
24
|
+
creator: Callable,
|
|
25
|
+
) -> None:
|
|
26
|
+
"""
|
|
27
|
+
Register a new agent type.
|
|
28
|
+
|
|
29
|
+
Args:
|
|
30
|
+
agent_type: Unique identifier for the agent type (e.g., "glm", "mai")
|
|
31
|
+
creator: Function that creates the agent instance.
|
|
32
|
+
Signature: (model_config, agent_config, agent_specific_config, callbacks) -> BaseAgent
|
|
33
|
+
|
|
34
|
+
Example:
|
|
35
|
+
>>> def create_mai_agent(model_config, agent_config, mai_config, callbacks):
|
|
36
|
+
>>> return MAIAgentAdapter(...)
|
|
37
|
+
>>>
|
|
38
|
+
>>> register_agent("mai", create_mai_agent)
|
|
39
|
+
"""
|
|
40
|
+
if agent_type in AGENT_REGISTRY:
|
|
41
|
+
logger.warning(f"Agent type '{agent_type}' already registered, overwriting")
|
|
42
|
+
|
|
43
|
+
AGENT_REGISTRY[agent_type] = creator
|
|
44
|
+
logger.info(f"Registered agent type: {agent_type}")
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
def create_agent(
|
|
48
|
+
agent_type: str,
|
|
49
|
+
model_config: ModelConfig,
|
|
50
|
+
agent_config: AgentConfig,
|
|
51
|
+
agent_specific_config: AgentSpecificConfig,
|
|
52
|
+
device,
|
|
53
|
+
takeover_callback: Callable | None = None,
|
|
54
|
+
confirmation_callback: Callable | None = None,
|
|
55
|
+
) -> BaseAgent:
|
|
56
|
+
"""
|
|
57
|
+
Create an agent instance using the factory pattern.
|
|
58
|
+
|
|
59
|
+
Args:
|
|
60
|
+
agent_type: Type of agent to create (e.g., "glm", "mai")
|
|
61
|
+
model_config: Model configuration
|
|
62
|
+
agent_config: Agent configuration
|
|
63
|
+
agent_specific_config: Agent-specific configuration (e.g., MAIConfig fields)
|
|
64
|
+
device: DeviceProtocol instance (provided by PhoneAgentManager)
|
|
65
|
+
takeover_callback: Takeover callback
|
|
66
|
+
confirmation_callback: Confirmation callback
|
|
67
|
+
|
|
68
|
+
Returns:
|
|
69
|
+
Agent instance implementing BaseAgent interface
|
|
70
|
+
|
|
71
|
+
Raises:
|
|
72
|
+
ValueError: If agent_type is not registered
|
|
73
|
+
"""
|
|
74
|
+
if agent_type not in AGENT_REGISTRY:
|
|
75
|
+
available = ", ".join(AGENT_REGISTRY.keys())
|
|
76
|
+
raise ValueError(
|
|
77
|
+
f"Unknown agent type: '{agent_type}'. Available types: {available}"
|
|
78
|
+
)
|
|
79
|
+
|
|
80
|
+
creator = AGENT_REGISTRY[agent_type]
|
|
81
|
+
|
|
82
|
+
try:
|
|
83
|
+
agent = creator(
|
|
84
|
+
model_config=model_config,
|
|
85
|
+
agent_config=agent_config,
|
|
86
|
+
agent_specific_config=agent_specific_config,
|
|
87
|
+
device=device,
|
|
88
|
+
takeover_callback=takeover_callback,
|
|
89
|
+
confirmation_callback=confirmation_callback,
|
|
90
|
+
)
|
|
91
|
+
logger.debug(f"Created agent of type '{agent_type}'")
|
|
92
|
+
return agent
|
|
93
|
+
except Exception as e:
|
|
94
|
+
logger.error(f"Failed to create agent of type '{agent_type}': {e}")
|
|
95
|
+
raise
|
|
96
|
+
|
|
97
|
+
|
|
98
|
+
def list_agent_types() -> list[str]:
|
|
99
|
+
"""Get list of registered agent types."""
|
|
100
|
+
return list(AGENT_REGISTRY.keys())
|
|
101
|
+
|
|
102
|
+
|
|
103
|
+
def is_agent_type_registered(agent_type: str) -> bool:
|
|
104
|
+
"""Check if an agent type is registered."""
|
|
105
|
+
return agent_type in AGENT_REGISTRY
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
# ==================== Built-in Agent Creators ====================
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def _create_glm_agent_v2(
|
|
112
|
+
model_config: ModelConfig,
|
|
113
|
+
agent_config: AgentConfig,
|
|
114
|
+
agent_specific_config: AgentSpecificConfig,
|
|
115
|
+
device,
|
|
116
|
+
takeover_callback: Callable | None = None,
|
|
117
|
+
confirmation_callback: Callable | None = None,
|
|
118
|
+
) -> BaseAgent:
|
|
119
|
+
from .glm.agent import GLMAgent
|
|
120
|
+
|
|
121
|
+
return GLMAgent(
|
|
122
|
+
model_config=model_config,
|
|
123
|
+
agent_config=agent_config,
|
|
124
|
+
device=device,
|
|
125
|
+
confirmation_callback=confirmation_callback,
|
|
126
|
+
takeover_callback=takeover_callback,
|
|
127
|
+
)
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
def _create_internal_mai_agent(
|
|
131
|
+
model_config: ModelConfig,
|
|
132
|
+
agent_config: AgentConfig,
|
|
133
|
+
agent_specific_config: AgentSpecificConfig,
|
|
134
|
+
device,
|
|
135
|
+
takeover_callback: Callable | None = None,
|
|
136
|
+
confirmation_callback: Callable | None = None,
|
|
137
|
+
) -> BaseAgent:
|
|
138
|
+
from .mai.agent import InternalMAIAgent
|
|
139
|
+
|
|
140
|
+
history_n = agent_specific_config.get("history_n", 3)
|
|
141
|
+
|
|
142
|
+
return InternalMAIAgent(
|
|
143
|
+
model_config=model_config,
|
|
144
|
+
agent_config=agent_config,
|
|
145
|
+
device=device,
|
|
146
|
+
history_n=history_n,
|
|
147
|
+
confirmation_callback=confirmation_callback,
|
|
148
|
+
takeover_callback=takeover_callback,
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
|
|
152
|
+
register_agent("glm", _create_glm_agent_v2)
|
|
153
|
+
register_agent("mai", _create_internal_mai_agent)
|