autoglm-gui 1.4.1__py3-none-any.whl → 1.5.1__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 (135) hide show
  1. AutoGLM_GUI/__init__.py +11 -0
  2. AutoGLM_GUI/__main__.py +26 -4
  3. AutoGLM_GUI/actions/__init__.py +6 -0
  4. phone_agent/actions/handler_ios.py → AutoGLM_GUI/actions/handler.py +30 -112
  5. AutoGLM_GUI/actions/types.py +15 -0
  6. {phone_agent → AutoGLM_GUI}/adb/__init__.py +25 -23
  7. {phone_agent → AutoGLM_GUI}/adb/connection.py +5 -40
  8. {phone_agent → AutoGLM_GUI}/adb/device.py +12 -94
  9. {phone_agent → AutoGLM_GUI}/adb/input.py +6 -47
  10. AutoGLM_GUI/adb/screenshot.py +11 -0
  11. {phone_agent/config → AutoGLM_GUI/adb}/timing.py +1 -1
  12. AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
  13. AutoGLM_GUI/adb_plus/screenshot.py +22 -1
  14. AutoGLM_GUI/adb_plus/serial.py +38 -20
  15. AutoGLM_GUI/adb_plus/touch.py +4 -9
  16. AutoGLM_GUI/agents/__init__.py +43 -12
  17. AutoGLM_GUI/agents/events.py +19 -0
  18. AutoGLM_GUI/agents/factory.py +31 -38
  19. AutoGLM_GUI/agents/glm/__init__.py +7 -0
  20. AutoGLM_GUI/agents/glm/agent.py +297 -0
  21. AutoGLM_GUI/agents/glm/message_builder.py +81 -0
  22. AutoGLM_GUI/agents/glm/parser.py +110 -0
  23. {phone_agent/config → AutoGLM_GUI/agents/glm}/prompts_en.py +7 -9
  24. {phone_agent/config → AutoGLM_GUI/agents/glm}/prompts_zh.py +18 -25
  25. AutoGLM_GUI/agents/mai/__init__.py +28 -0
  26. AutoGLM_GUI/agents/mai/agent.py +408 -0
  27. AutoGLM_GUI/agents/mai/parser.py +254 -0
  28. AutoGLM_GUI/agents/mai/prompts.py +103 -0
  29. AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
  30. AutoGLM_GUI/agents/protocols.py +12 -8
  31. AutoGLM_GUI/agents/stream_runner.py +193 -0
  32. AutoGLM_GUI/api/__init__.py +40 -21
  33. AutoGLM_GUI/api/agents.py +181 -239
  34. AutoGLM_GUI/api/control.py +9 -6
  35. AutoGLM_GUI/api/devices.py +102 -12
  36. AutoGLM_GUI/api/history.py +104 -0
  37. AutoGLM_GUI/api/layered_agent.py +67 -15
  38. AutoGLM_GUI/api/media.py +64 -1
  39. AutoGLM_GUI/api/scheduled_tasks.py +98 -0
  40. AutoGLM_GUI/config.py +81 -0
  41. AutoGLM_GUI/config_manager.py +68 -51
  42. AutoGLM_GUI/device_manager.py +248 -29
  43. AutoGLM_GUI/device_protocol.py +1 -1
  44. AutoGLM_GUI/devices/adb_device.py +5 -10
  45. AutoGLM_GUI/devices/mock_device.py +4 -2
  46. AutoGLM_GUI/devices/remote_device.py +8 -3
  47. AutoGLM_GUI/history_manager.py +164 -0
  48. AutoGLM_GUI/model/__init__.py +5 -0
  49. AutoGLM_GUI/model/message_builder.py +69 -0
  50. AutoGLM_GUI/model/types.py +24 -0
  51. AutoGLM_GUI/models/__init__.py +10 -0
  52. AutoGLM_GUI/models/history.py +140 -0
  53. AutoGLM_GUI/models/scheduled_task.py +71 -0
  54. AutoGLM_GUI/parsers/__init__.py +22 -0
  55. AutoGLM_GUI/parsers/base.py +50 -0
  56. AutoGLM_GUI/parsers/phone_parser.py +58 -0
  57. AutoGLM_GUI/phone_agent_manager.py +62 -396
  58. AutoGLM_GUI/platform_utils.py +26 -0
  59. AutoGLM_GUI/prompt_config.py +15 -0
  60. AutoGLM_GUI/prompts/__init__.py +32 -0
  61. AutoGLM_GUI/scheduler_manager.py +350 -0
  62. AutoGLM_GUI/schemas.py +246 -72
  63. AutoGLM_GUI/scrcpy_stream.py +142 -24
  64. AutoGLM_GUI/socketio_server.py +100 -27
  65. AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-CfwX1Cmc.js} +1 -1
  66. AutoGLM_GUI/static/assets/alert-dialog-CtGlN2IJ.js +1 -0
  67. AutoGLM_GUI/static/assets/chat-BYa-foUI.js +129 -0
  68. AutoGLM_GUI/static/assets/circle-alert-t08bEMPO.js +1 -0
  69. AutoGLM_GUI/static/assets/dialog-FNwZJFwk.js +45 -0
  70. AutoGLM_GUI/static/assets/eye-D0UPWCWC.js +1 -0
  71. AutoGLM_GUI/static/assets/history-CRo95B7i.js +1 -0
  72. AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-BaLMSqd3.js} +1 -1
  73. AutoGLM_GUI/static/assets/index-CTHbFvKl.js +11 -0
  74. AutoGLM_GUI/static/assets/index-CV7jGxGm.css +1 -0
  75. AutoGLM_GUI/static/assets/label-DJFevVmr.js +1 -0
  76. AutoGLM_GUI/static/assets/logs-RW09DyYY.js +1 -0
  77. AutoGLM_GUI/static/assets/popover--JTJrE5v.js +1 -0
  78. AutoGLM_GUI/static/assets/scheduled-tasks-DTRKsQXF.js +1 -0
  79. AutoGLM_GUI/static/assets/square-pen-CPK_K680.js +1 -0
  80. AutoGLM_GUI/static/assets/textarea-PRmVnWq5.js +1 -0
  81. AutoGLM_GUI/static/assets/workflows-CdcsAoaT.js +1 -0
  82. AutoGLM_GUI/static/index.html +2 -2
  83. AutoGLM_GUI/types.py +17 -0
  84. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/METADATA +179 -130
  85. autoglm_gui-1.5.1.dist-info/RECORD +118 -0
  86. AutoGLM_GUI/agents/mai_adapter.py +0 -627
  87. AutoGLM_GUI/api/dual_model.py +0 -317
  88. AutoGLM_GUI/device_adapter.py +0 -263
  89. AutoGLM_GUI/dual_model/__init__.py +0 -53
  90. AutoGLM_GUI/dual_model/decision_model.py +0 -664
  91. AutoGLM_GUI/dual_model/dual_agent.py +0 -917
  92. AutoGLM_GUI/dual_model/protocols.py +0 -354
  93. AutoGLM_GUI/dual_model/vision_model.py +0 -442
  94. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
  95. AutoGLM_GUI/phone_agent_patches.py +0 -147
  96. AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +0 -126
  97. AutoGLM_GUI/static/assets/dialog-B3uW4T8V.js +0 -45
  98. AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +0 -1
  99. AutoGLM_GUI/static/assets/index-UYYauTly.js +0 -12
  100. AutoGLM_GUI/static/assets/workflows-Du_de-dt.js +0 -1
  101. autoglm_gui-1.4.1.dist-info/RECORD +0 -117
  102. mai_agent/base.py +0 -137
  103. mai_agent/mai_grounding_agent.py +0 -263
  104. mai_agent/mai_naivigation_agent.py +0 -526
  105. mai_agent/prompt.py +0 -148
  106. mai_agent/unified_memory.py +0 -67
  107. mai_agent/utils.py +0 -73
  108. phone_agent/__init__.py +0 -12
  109. phone_agent/actions/__init__.py +0 -5
  110. phone_agent/actions/handler.py +0 -400
  111. phone_agent/adb/screenshot.py +0 -108
  112. phone_agent/agent.py +0 -253
  113. phone_agent/agent_ios.py +0 -277
  114. phone_agent/config/__init__.py +0 -53
  115. phone_agent/config/apps_harmonyos.py +0 -256
  116. phone_agent/config/apps_ios.py +0 -339
  117. phone_agent/config/prompts.py +0 -80
  118. phone_agent/device_factory.py +0 -166
  119. phone_agent/hdc/__init__.py +0 -53
  120. phone_agent/hdc/connection.py +0 -384
  121. phone_agent/hdc/device.py +0 -269
  122. phone_agent/hdc/input.py +0 -145
  123. phone_agent/hdc/screenshot.py +0 -127
  124. phone_agent/model/__init__.py +0 -5
  125. phone_agent/model/client.py +0 -290
  126. phone_agent/xctest/__init__.py +0 -47
  127. phone_agent/xctest/connection.py +0 -379
  128. phone_agent/xctest/device.py +0 -472
  129. phone_agent/xctest/input.py +0 -311
  130. phone_agent/xctest/screenshot.py +0 -226
  131. {phone_agent/config → AutoGLM_GUI/adb}/apps.py +0 -0
  132. {phone_agent/config → AutoGLM_GUI}/i18n.py +0 -0
  133. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/WHEEL +0 -0
  134. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/entry_points.txt +0 -0
  135. {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.1.dist-info}/licenses/LICENSE +0 -0
@@ -3,21 +3,13 @@
3
3
  import subprocess
4
4
  import time
5
5
 
6
- from phone_agent.config.apps import APP_PACKAGES
7
- from phone_agent.config.timing import TIMING_CONFIG
6
+ from AutoGLM_GUI.adb.apps import APP_PACKAGES
7
+ from AutoGLM_GUI.adb.timing import TIMING_CONFIG
8
+ from AutoGLM_GUI.platform_utils import build_adb_command
8
9
 
9
10
 
10
11
  def get_current_app(device_id: str | None = None) -> str:
11
- """
12
- Get the currently focused app name.
13
-
14
- Args:
15
- device_id: Optional ADB device ID for multi-device setups.
16
-
17
- Returns:
18
- The app name if recognized, otherwise "System Home".
19
- """
20
- adb_prefix = _get_adb_prefix(device_id)
12
+ adb_prefix = build_adb_command(device_id)
21
13
 
22
14
  result = subprocess.run(
23
15
  adb_prefix + ["shell", "dumpsys", "window"],
@@ -29,7 +21,6 @@ def get_current_app(device_id: str | None = None) -> str:
29
21
  if not output:
30
22
  raise ValueError("No output from dumpsys window")
31
23
 
32
- # Parse window focus info
33
24
  for line in output.split("\n"):
34
25
  if "mCurrentFocus" in line or "mFocusedApp" in line:
35
26
  for app_name, package in APP_PACKAGES.items():
@@ -42,19 +33,10 @@ def get_current_app(device_id: str | None = None) -> str:
42
33
  def tap(
43
34
  x: int, y: int, device_id: str | None = None, delay: float | None = None
44
35
  ) -> None:
45
- """
46
- Tap at the specified coordinates.
47
-
48
- Args:
49
- x: X coordinate.
50
- y: Y coordinate.
51
- device_id: Optional ADB device ID.
52
- delay: Delay in seconds after tap. If None, uses configured default.
53
- """
54
36
  if delay is None:
55
37
  delay = TIMING_CONFIG.device.default_tap_delay
56
38
 
57
- adb_prefix = _get_adb_prefix(device_id)
39
+ adb_prefix = build_adb_command(device_id)
58
40
 
59
41
  subprocess.run(
60
42
  adb_prefix + ["shell", "input", "tap", str(x), str(y)], capture_output=True
@@ -65,19 +47,10 @@ def tap(
65
47
  def double_tap(
66
48
  x: int, y: int, device_id: str | None = None, delay: float | None = None
67
49
  ) -> None:
68
- """
69
- Double tap at the specified coordinates.
70
-
71
- Args:
72
- x: X coordinate.
73
- y: Y coordinate.
74
- device_id: Optional ADB device ID.
75
- delay: Delay in seconds after double tap. If None, uses configured default.
76
- """
77
50
  if delay is None:
78
51
  delay = TIMING_CONFIG.device.default_double_tap_delay
79
52
 
80
- adb_prefix = _get_adb_prefix(device_id)
53
+ adb_prefix = build_adb_command(device_id)
81
54
 
82
55
  subprocess.run(
83
56
  adb_prefix + ["shell", "input", "tap", str(x), str(y)], capture_output=True
@@ -96,20 +69,10 @@ def long_press(
96
69
  device_id: str | None = None,
97
70
  delay: float | None = None,
98
71
  ) -> None:
99
- """
100
- Long press at the specified coordinates.
101
-
102
- Args:
103
- x: X coordinate.
104
- y: Y coordinate.
105
- duration_ms: Duration of press in milliseconds.
106
- device_id: Optional ADB device ID.
107
- delay: Delay in seconds after long press. If None, uses configured default.
108
- """
109
72
  if delay is None:
110
73
  delay = TIMING_CONFIG.device.default_long_press_delay
111
74
 
112
- adb_prefix = _get_adb_prefix(device_id)
75
+ adb_prefix = build_adb_command(device_id)
113
76
 
114
77
  subprocess.run(
115
78
  adb_prefix
@@ -128,28 +91,15 @@ def swipe(
128
91
  device_id: str | None = None,
129
92
  delay: float | None = None,
130
93
  ) -> None:
131
- """
132
- Swipe from start to end coordinates.
133
-
134
- Args:
135
- start_x: Starting X coordinate.
136
- start_y: Starting Y coordinate.
137
- end_x: Ending X coordinate.
138
- end_y: Ending Y coordinate.
139
- duration_ms: Duration of swipe in milliseconds (auto-calculated if None).
140
- device_id: Optional ADB device ID.
141
- delay: Delay in seconds after swipe. If None, uses configured default.
142
- """
143
94
  if delay is None:
144
95
  delay = TIMING_CONFIG.device.default_swipe_delay
145
96
 
146
- adb_prefix = _get_adb_prefix(device_id)
97
+ adb_prefix = build_adb_command(device_id)
147
98
 
148
99
  if duration_ms is None:
149
- # Calculate duration based on distance
150
100
  dist_sq = (start_x - end_x) ** 2 + (start_y - end_y) ** 2
151
101
  duration_ms = int(dist_sq / 1000)
152
- duration_ms = max(1000, min(duration_ms, 2000)) # Clamp between 1000-2000ms
102
+ duration_ms = max(1000, min(duration_ms, 2000))
153
103
 
154
104
  subprocess.run(
155
105
  adb_prefix
@@ -169,17 +119,10 @@ def swipe(
169
119
 
170
120
 
171
121
  def back(device_id: str | None = None, delay: float | None = None) -> None:
172
- """
173
- Press the back button.
174
-
175
- Args:
176
- device_id: Optional ADB device ID.
177
- delay: Delay in seconds after pressing back. If None, uses configured default.
178
- """
179
122
  if delay is None:
180
123
  delay = TIMING_CONFIG.device.default_back_delay
181
124
 
182
- adb_prefix = _get_adb_prefix(device_id)
125
+ adb_prefix = build_adb_command(device_id)
183
126
 
184
127
  subprocess.run(
185
128
  adb_prefix + ["shell", "input", "keyevent", "4"], capture_output=True
@@ -188,17 +131,10 @@ def back(device_id: str | None = None, delay: float | None = None) -> None:
188
131
 
189
132
 
190
133
  def home(device_id: str | None = None, delay: float | None = None) -> None:
191
- """
192
- Press the home button.
193
-
194
- Args:
195
- device_id: Optional ADB device ID.
196
- delay: Delay in seconds after pressing home. If None, uses configured default.
197
- """
198
134
  if delay is None:
199
135
  delay = TIMING_CONFIG.device.default_home_delay
200
136
 
201
- adb_prefix = _get_adb_prefix(device_id)
137
+ adb_prefix = build_adb_command(device_id)
202
138
 
203
139
  subprocess.run(
204
140
  adb_prefix + ["shell", "input", "keyevent", "KEYCODE_HOME"], capture_output=True
@@ -209,24 +145,13 @@ def home(device_id: str | None = None, delay: float | None = None) -> None:
209
145
  def launch_app(
210
146
  app_name: str, device_id: str | None = None, delay: float | None = None
211
147
  ) -> bool:
212
- """
213
- Launch an app by name.
214
-
215
- Args:
216
- app_name: The app name (must be in APP_PACKAGES).
217
- device_id: Optional ADB device ID.
218
- delay: Delay in seconds after launching. If None, uses configured default.
219
-
220
- Returns:
221
- True if app was launched, False if app not found.
222
- """
223
148
  if delay is None:
224
149
  delay = TIMING_CONFIG.device.default_launch_delay
225
150
 
226
151
  if app_name not in APP_PACKAGES:
227
152
  return False
228
153
 
229
- adb_prefix = _get_adb_prefix(device_id)
154
+ adb_prefix = build_adb_command(device_id)
230
155
  package = APP_PACKAGES[app_name]
231
156
 
232
157
  subprocess.run(
@@ -244,10 +169,3 @@ def launch_app(
244
169
  )
245
170
  time.sleep(delay)
246
171
  return True
247
-
248
-
249
- def _get_adb_prefix(device_id: str | None) -> list:
250
- """Get ADB command prefix with optional device specifier."""
251
- if device_id:
252
- return ["adb", "-s", device_id]
253
- return ["adb"]
@@ -3,20 +3,11 @@
3
3
  import base64
4
4
  import subprocess
5
5
 
6
+ from AutoGLM_GUI.platform_utils import build_adb_command
7
+
6
8
 
7
9
  def type_text(text: str, device_id: str | None = None) -> None:
8
- """
9
- Type text into the currently focused input field using ADB Keyboard.
10
-
11
- Args:
12
- text: The text to type.
13
- device_id: Optional ADB device ID for multi-device setups.
14
-
15
- Note:
16
- Requires ADB Keyboard to be installed on the device.
17
- See: https://github.com/nicnocquee/AdbKeyboard
18
- """
19
- adb_prefix = _get_adb_prefix(device_id)
10
+ adb_prefix = build_adb_command(device_id)
20
11
  encoded_text = base64.b64encode(text.encode("utf-8")).decode("utf-8")
21
12
 
22
13
  subprocess.run(
@@ -37,13 +28,7 @@ def type_text(text: str, device_id: str | None = None) -> None:
37
28
 
38
29
 
39
30
  def clear_text(device_id: str | None = None) -> None:
40
- """
41
- Clear text in the currently focused input field.
42
-
43
- Args:
44
- device_id: Optional ADB device ID for multi-device setups.
45
- """
46
- adb_prefix = _get_adb_prefix(device_id)
31
+ adb_prefix = build_adb_command(device_id)
47
32
 
48
33
  subprocess.run(
49
34
  adb_prefix + ["shell", "am", "broadcast", "-a", "ADB_CLEAR_TEXT"],
@@ -53,18 +38,8 @@ def clear_text(device_id: str | None = None) -> None:
53
38
 
54
39
 
55
40
  def detect_and_set_adb_keyboard(device_id: str | None = None) -> str:
56
- """
57
- Detect current keyboard and switch to ADB Keyboard if needed.
58
-
59
- Args:
60
- device_id: Optional ADB device ID for multi-device setups.
61
-
62
- Returns:
63
- The original keyboard IME identifier for later restoration.
64
- """
65
- adb_prefix = _get_adb_prefix(device_id)
41
+ adb_prefix = build_adb_command(device_id)
66
42
 
67
- # Get current IME
68
43
  result = subprocess.run(
69
44
  adb_prefix + ["shell", "settings", "get", "secure", "default_input_method"],
70
45
  capture_output=True,
@@ -72,7 +47,6 @@ def detect_and_set_adb_keyboard(device_id: str | None = None) -> str:
72
47
  )
73
48
  current_ime = (result.stdout + result.stderr).strip()
74
49
 
75
- # Switch to ADB Keyboard if not already set
76
50
  if "com.android.adbkeyboard/.AdbIME" not in current_ime:
77
51
  subprocess.run(
78
52
  adb_prefix + ["shell", "ime", "set", "com.android.adbkeyboard/.AdbIME"],
@@ -80,29 +54,14 @@ def detect_and_set_adb_keyboard(device_id: str | None = None) -> str:
80
54
  text=True,
81
55
  )
82
56
 
83
- # Warm up the keyboard
84
57
  type_text("", device_id)
85
58
 
86
59
  return current_ime
87
60
 
88
61
 
89
62
  def restore_keyboard(ime: str, device_id: str | None = None) -> None:
90
- """
91
- Restore the original keyboard IME.
92
-
93
- Args:
94
- ime: The IME identifier to restore.
95
- device_id: Optional ADB device ID for multi-device setups.
96
- """
97
- adb_prefix = _get_adb_prefix(device_id)
63
+ adb_prefix = build_adb_command(device_id)
98
64
 
99
65
  subprocess.run(
100
66
  adb_prefix + ["shell", "ime", "set", ime], capture_output=True, text=True
101
67
  )
102
-
103
-
104
- def _get_adb_prefix(device_id: str | None) -> list:
105
- """Get ADB command prefix with optional device specifier."""
106
- if device_id:
107
- return ["adb", "-s", device_id]
108
- return ["adb"]
@@ -0,0 +1,11 @@
1
+ """Screenshot utilities for capturing Android device screen.
2
+
3
+ DEPRECATED: This module now delegates to adb_plus.screenshot for the actual implementation.
4
+ Use adb_plus.screenshot directly for new code.
5
+ """
6
+
7
+ from AutoGLM_GUI.adb_plus.screenshot import Screenshot, capture_screenshot
8
+
9
+
10
+ def get_screenshot(device_id: str | None = None, timeout: int = 10) -> Screenshot:
11
+ return capture_screenshot(device_id=device_id, timeout=timeout)
@@ -140,7 +140,7 @@ def update_timing_config(
140
140
  connection: New connection timing configuration.
141
141
 
142
142
  Example:
143
- >>> from phone_agent.config.timing import update_timing_config, ActionTimingConfig
143
+ >>> from AutoGLM_GUI.adb.timing import update_timing_config, ActionTimingConfig
144
144
  >>> custom_action = ActionTimingConfig(
145
145
  ... keyboard_switch_delay=0.5,
146
146
  ... text_input_delay=0.5
@@ -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"):
@@ -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)],
@@ -1,20 +1,51 @@
1
- """Agent adapters and factory for different agent implementations."""
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)
2
44
 
3
- from .factory import (
4
- create_agent,
5
- is_agent_type_registered,
6
- list_agent_types,
7
- register_agent,
8
- )
9
- from .mai_adapter import MAIAgentAdapter, MAIAgentConfig
10
45
 
11
46
  __all__ = [
12
- # Factory
13
47
  "create_agent",
14
48
  "register_agent",
15
49
  "list_agent_types",
16
50
  "is_agent_type_registered",
17
- # Adapters
18
- "MAIAgentAdapter",
19
- "MAIAgentConfig",
20
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]