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.
Files changed (120) hide show
  1. AutoGLM_GUI/__init__.py +11 -0
  2. AutoGLM_GUI/__main__.py +26 -8
  3. AutoGLM_GUI/actions/__init__.py +6 -0
  4. AutoGLM_GUI/actions/handler.py +196 -0
  5. AutoGLM_GUI/actions/types.py +15 -0
  6. AutoGLM_GUI/adb/__init__.py +53 -0
  7. AutoGLM_GUI/adb/apps.py +227 -0
  8. AutoGLM_GUI/adb/connection.py +323 -0
  9. AutoGLM_GUI/adb/device.py +171 -0
  10. AutoGLM_GUI/adb/input.py +67 -0
  11. AutoGLM_GUI/adb/screenshot.py +11 -0
  12. AutoGLM_GUI/adb/timing.py +167 -0
  13. AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
  14. AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
  15. AutoGLM_GUI/adb_plus/screenshot.py +22 -1
  16. AutoGLM_GUI/adb_plus/serial.py +38 -20
  17. AutoGLM_GUI/adb_plus/touch.py +4 -9
  18. AutoGLM_GUI/agents/__init__.py +51 -0
  19. AutoGLM_GUI/agents/events.py +19 -0
  20. AutoGLM_GUI/agents/factory.py +153 -0
  21. AutoGLM_GUI/agents/glm/__init__.py +7 -0
  22. AutoGLM_GUI/agents/glm/agent.py +292 -0
  23. AutoGLM_GUI/agents/glm/message_builder.py +81 -0
  24. AutoGLM_GUI/agents/glm/parser.py +110 -0
  25. AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
  26. AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
  27. AutoGLM_GUI/agents/mai/__init__.py +28 -0
  28. AutoGLM_GUI/agents/mai/agent.py +405 -0
  29. AutoGLM_GUI/agents/mai/parser.py +254 -0
  30. AutoGLM_GUI/agents/mai/prompts.py +103 -0
  31. AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
  32. AutoGLM_GUI/agents/protocols.py +27 -0
  33. AutoGLM_GUI/agents/stream_runner.py +188 -0
  34. AutoGLM_GUI/api/__init__.py +71 -11
  35. AutoGLM_GUI/api/agents.py +190 -229
  36. AutoGLM_GUI/api/control.py +9 -6
  37. AutoGLM_GUI/api/devices.py +112 -28
  38. AutoGLM_GUI/api/health.py +13 -0
  39. AutoGLM_GUI/api/history.py +78 -0
  40. AutoGLM_GUI/api/layered_agent.py +306 -181
  41. AutoGLM_GUI/api/mcp.py +11 -10
  42. AutoGLM_GUI/api/media.py +64 -1
  43. AutoGLM_GUI/api/scheduled_tasks.py +98 -0
  44. AutoGLM_GUI/api/version.py +23 -10
  45. AutoGLM_GUI/api/workflows.py +2 -1
  46. AutoGLM_GUI/config.py +72 -14
  47. AutoGLM_GUI/config_manager.py +98 -27
  48. AutoGLM_GUI/device_adapter.py +263 -0
  49. AutoGLM_GUI/device_manager.py +248 -29
  50. AutoGLM_GUI/device_protocol.py +266 -0
  51. AutoGLM_GUI/devices/__init__.py +49 -0
  52. AutoGLM_GUI/devices/adb_device.py +200 -0
  53. AutoGLM_GUI/devices/mock_device.py +185 -0
  54. AutoGLM_GUI/devices/remote_device.py +177 -0
  55. AutoGLM_GUI/exceptions.py +3 -3
  56. AutoGLM_GUI/history_manager.py +164 -0
  57. AutoGLM_GUI/i18n.py +81 -0
  58. AutoGLM_GUI/metrics.py +13 -20
  59. AutoGLM_GUI/model/__init__.py +5 -0
  60. AutoGLM_GUI/model/message_builder.py +69 -0
  61. AutoGLM_GUI/model/types.py +24 -0
  62. AutoGLM_GUI/models/__init__.py +10 -0
  63. AutoGLM_GUI/models/history.py +96 -0
  64. AutoGLM_GUI/models/scheduled_task.py +71 -0
  65. AutoGLM_GUI/parsers/__init__.py +22 -0
  66. AutoGLM_GUI/parsers/base.py +50 -0
  67. AutoGLM_GUI/parsers/phone_parser.py +58 -0
  68. AutoGLM_GUI/phone_agent_manager.py +118 -367
  69. AutoGLM_GUI/platform_utils.py +31 -2
  70. AutoGLM_GUI/prompt_config.py +15 -0
  71. AutoGLM_GUI/prompts/__init__.py +32 -0
  72. AutoGLM_GUI/scheduler_manager.py +304 -0
  73. AutoGLM_GUI/schemas.py +272 -63
  74. AutoGLM_GUI/scrcpy_stream.py +159 -37
  75. AutoGLM_GUI/server.py +3 -1
  76. AutoGLM_GUI/socketio_server.py +114 -29
  77. AutoGLM_GUI/state.py +10 -30
  78. AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-BQm96DAl.js} +1 -1
  79. AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
  80. AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
  81. AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
  82. AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
  83. AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
  84. AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
  85. AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-CmZSnDqc.js} +1 -1
  86. AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
  87. AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
  88. AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
  89. AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
  90. AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
  91. AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
  92. AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
  93. AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
  94. AutoGLM_GUI/static/index.html +2 -2
  95. AutoGLM_GUI/types.py +142 -0
  96. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +178 -92
  97. autoglm_gui-1.5.0.dist-info/RECORD +157 -0
  98. mai_agent/base.py +137 -0
  99. mai_agent/mai_grounding_agent.py +263 -0
  100. mai_agent/mai_naivigation_agent.py +526 -0
  101. mai_agent/prompt.py +148 -0
  102. mai_agent/unified_memory.py +67 -0
  103. mai_agent/utils.py +73 -0
  104. AutoGLM_GUI/api/dual_model.py +0 -311
  105. AutoGLM_GUI/dual_model/__init__.py +0 -53
  106. AutoGLM_GUI/dual_model/decision_model.py +0 -664
  107. AutoGLM_GUI/dual_model/dual_agent.py +0 -917
  108. AutoGLM_GUI/dual_model/protocols.py +0 -354
  109. AutoGLM_GUI/dual_model/vision_model.py +0 -442
  110. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
  111. AutoGLM_GUI/phone_agent_patches.py +0 -146
  112. AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
  113. AutoGLM_GUI/static/assets/dialog-BfdcBs1x.js +0 -45
  114. AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
  115. AutoGLM_GUI/static/assets/index-DHF1NZh0.js +0 -12
  116. AutoGLM_GUI/static/assets/workflows-xiplap-r.js +0 -1
  117. autoglm_gui-1.4.0.dist-info/RECORD +0 -100
  118. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
  119. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
  120. {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 = files("AutoGLM_GUI").joinpath(
108
- "resources/apks", ADB_KEYBOARD_APK_FILENAME
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"):
@@ -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, service_type: str, name: str) -> None:
127
+ def add_service(self, zc: Zeroconf, type_: str, name: str) -> None:
128
128
  """Handle new service discovery."""
129
- info = zc.get_service_info(service_type, name, timeout=3000)
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 service_type == PAIR_SERVICE_TYPE and not self.paired:
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 service_type == CONNECT_SERVICE_TYPE and self.paired and not self.connected:
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, service_type: str, name: str) -> None:
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, service_type, name)
191
+ self.add_service(zc, type_, name)
192
192
 
193
- def remove_service(self, _zc: Zeroconf, _service_type: str, _name: str) -> None:
193
+ def remove_service(self, zc: Zeroconf, type_: str, name: str) -> None:
194
194
  """Handle service removal (no action needed)."""
195
- pass
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
 
@@ -49,12 +49,22 @@ def extract_serial_from_mdns(device_id: str) -> Optional[str]:
49
49
  return None
50
50
 
51
51
 
52
- def get_device_serial(device_id: str, adb_path: str = "adb") -> str | None:
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, or None if failed
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
- # Fallback: Use getprop (original behavior)
78
- try:
79
- # Use getprop to get the actual hardware serial number
80
- # This works for both USB and WiFi connections
81
- result = run_cmd_silently_sync(
82
- [adb_path, "-s", device_id, "shell", "getprop", "ro.serialno"],
83
- timeout=3,
84
- )
85
- if result.returncode == 0:
86
- serial = result.stdout.strip()
87
- # Filter out error messages and empty values
88
- if serial and not serial.startswith("error:") and serial != "unknown":
89
- logger.debug(f"Got serial via getprop: {device_id} → {serial}")
90
- return serial
91
- except Exception as e:
92
- logger.debug(f"Failed to get serial via getprop for {device_id}: {e}")
93
-
94
- return None
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
@@ -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 = _get_adb_prefix(device_id, adb_path)
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 = _get_adb_prefix(device_id, adb_path)
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 = _get_adb_prefix(device_id, adb_path)
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)
@@ -0,0 +1,7 @@
1
+ from .prompts_en import SYSTEM_PROMPT as SYSTEM_PROMPT_EN
2
+ from .prompts_zh import SYSTEM_PROMPT as SYSTEM_PROMPT_ZH
3
+
4
+ __all__ = [
5
+ "SYSTEM_PROMPT_EN",
6
+ "SYSTEM_PROMPT_ZH",
7
+ ]