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
@@ -4,9 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import time
7
- from typing import NotRequired
8
7
 
9
- from typing_extensions import TypedDict
8
+ from typing_extensions import NotRequired, TypedDict
10
9
 
11
10
  import socketio
12
11
 
@@ -31,6 +30,9 @@ sio = socketio.AsyncServer(
31
30
 
32
31
  _socket_streamers: dict[str, ScrcpyStreamer] = {}
33
32
  _stream_tasks: dict[str, asyncio.Task] = {}
33
+ _device_locks: dict[
34
+ str, asyncio.Lock
35
+ ] = {} # Lock per device to prevent concurrent connections
34
36
 
35
37
 
36
38
  async def _stop_stream_for_sid(sid: str) -> None:
@@ -43,6 +45,46 @@ async def _stop_stream_for_sid(sid: str) -> None:
43
45
  streamer.stop()
44
46
 
45
47
 
48
+ def _classify_error(exc: Exception) -> dict:
49
+ """Classify error and return user-friendly message."""
50
+ error_str = str(exc)
51
+
52
+ if "Address already in use" in error_str or (
53
+ "Port" in error_str and "occupied" in error_str
54
+ ):
55
+ return {
56
+ "message": "端口冲突,视频流端口仍被占用。通常会自动解决,如果持续出现请重启应用。",
57
+ "type": "port_conflict",
58
+ "technical_details": error_str,
59
+ }
60
+ elif "Device" in error_str and (
61
+ "not available" in error_str or "not found" in error_str
62
+ ):
63
+ return {
64
+ "message": "设备无响应,请检查 USB/WiFi 连接。",
65
+ "type": "device_offline",
66
+ "technical_details": error_str,
67
+ }
68
+ elif "timeout" in error_str.lower() or "timed out" in error_str.lower():
69
+ return {
70
+ "message": "连接超时,请检查设备连接后重试。",
71
+ "type": "timeout",
72
+ "technical_details": error_str,
73
+ }
74
+ elif "Failed to connect" in error_str:
75
+ return {
76
+ "message": "无法连接到 scrcpy 服务器,请检查设备连接。",
77
+ "type": "connection_failed",
78
+ "technical_details": error_str,
79
+ }
80
+ else:
81
+ return {
82
+ "message": error_str,
83
+ "type": "unknown",
84
+ "technical_details": error_str,
85
+ }
86
+
87
+
46
88
  def stop_streamers(device_id: str | None = None) -> None:
47
89
  """Stop active scrcpy streamers (all or by device)."""
48
90
  sids = list(_socket_streamers.keys())
@@ -103,35 +145,66 @@ async def disconnect(sid: str) -> None:
103
145
  async def connect_device(sid: str, data: dict | None) -> None:
104
146
  payload = data or {}
105
147
  device_id = payload.get("device_id") or payload.get("deviceId")
148
+ if not device_id:
149
+ await sio.emit(
150
+ "error",
151
+ {"message": "Device ID is required", "type": "invalid_request"},
152
+ to=sid,
153
+ )
154
+ return
155
+
106
156
  max_size = int(payload.get("maxSize") or 1280)
107
157
  bit_rate = int(payload.get("bitRate") or 4_000_000)
108
158
 
159
+ # Stop any existing stream for this sid
109
160
  await _stop_stream_for_sid(sid)
110
161
 
111
- streamer = ScrcpyStreamer(
112
- device_id=device_id,
113
- max_size=max_size,
114
- bit_rate=bit_rate,
115
- )
116
-
117
- try:
118
- await streamer.start()
119
- metadata = await streamer.read_video_metadata()
120
- await sio.emit(
121
- "video-metadata",
122
- {
123
- "deviceName": metadata.device_name,
124
- "width": metadata.width,
125
- "height": metadata.height,
126
- "codec": metadata.codec,
127
- },
128
- to=sid,
162
+ # Get or create a lock for this device
163
+ if device_id not in _device_locks:
164
+ _device_locks[device_id] = asyncio.Lock()
165
+
166
+ device_lock = _device_locks[device_id]
167
+
168
+ # Acquire lock to prevent concurrent connections to the same device
169
+ async with device_lock:
170
+ logger.debug(f"Acquired device lock for {device_id}, sid: {sid}")
171
+
172
+ # Stop any existing streams for the same device (from other sids)
173
+ sids_to_stop = [
174
+ s
175
+ for s, streamer in _socket_streamers.items()
176
+ if s != sid and streamer.device_id == device_id
177
+ ]
178
+ for s in sids_to_stop:
179
+ logger.info(f"Stopping existing stream for device {device_id} from sid {s}")
180
+ await _stop_stream_for_sid(s)
181
+
182
+ streamer = ScrcpyStreamer(
183
+ device_id=device_id,
184
+ max_size=max_size,
185
+ bit_rate=bit_rate,
129
186
  )
130
- except Exception as exc:
131
- streamer.stop()
132
- logger.exception("Failed to start scrcpy stream: %s", exc)
133
- await sio.emit("error", {"message": str(exc)}, to=sid)
134
- return
135
187
 
136
- _socket_streamers[sid] = streamer
137
- _stream_tasks[sid] = asyncio.create_task(_stream_packets(sid, streamer))
188
+ try:
189
+ await streamer.start() # ScrcpyStreamer has built-in retry logic
190
+ metadata = await streamer.read_video_metadata()
191
+ await sio.emit(
192
+ "video-metadata",
193
+ {
194
+ "deviceName": metadata.device_name,
195
+ "width": metadata.width,
196
+ "height": metadata.height,
197
+ "codec": metadata.codec,
198
+ },
199
+ to=sid,
200
+ )
201
+
202
+ _socket_streamers[sid] = streamer
203
+ _stream_tasks[sid] = asyncio.create_task(_stream_packets(sid, streamer))
204
+
205
+ except Exception as exc:
206
+ streamer.stop()
207
+ logger.exception("Failed to start scrcpy stream: %s", exc)
208
+ # Use unified error classification
209
+ error_info = _classify_error(exc)
210
+ await sio.emit("error", error_info, to=sid)
@@ -1 +1 @@
1
- import{j as o}from"./index-UYYauTly.js";function t(){return o.jsx("div",{className:"p-2",children:o.jsx("h3",{children:"About"})})}export{t as component};
1
+ import{j as o}from"./index-CTHbFvKl.js";function t(){return o.jsx("div",{className:"p-2",children:o.jsx("h3",{children:"About"})})}export{t as component};
@@ -0,0 +1 @@
1
+ import{o as u,r as o,j as a,b as r,B as d}from"./index-CTHbFvKl.js";import{P as g,c as x,b as f,d as m}from"./popover--JTJrE5v.js";import{D as p,d as h,e as w,f as j,g as D}from"./dialog-FNwZJFwk.js";const N=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],b=u("check",N),c=o.createContext(void 0),P=({value:t="",onValueChange:e,children:s})=>{const[n,l]=o.useState(!1);return a.jsx(c.Provider,{value:{value:t,onValueChange:e||(()=>{}),open:n,setOpen:l},children:a.jsx(g,{open:n,onOpenChange:l,children:s})})},C=o.forwardRef(({className:t,children:e,...s},n)=>{if(!o.useContext(c))throw new Error("SelectTrigger must be used within Select");return a.jsx(x,{asChild:!0,children:a.jsxs("button",{ref:n,className:r("flex h-10 w-full items-center justify-between rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus:ring-slate-300",t),...s,children:[e,a.jsx(f,{className:"h-4 w-4 opacity-50"})]})})});C.displayName="SelectTrigger";const V=({placeholder:t})=>{const e=o.useContext(c);if(!e)throw new Error("SelectValue must be used within Select");return a.jsx("span",{className:e.value?"":"text-slate-500",children:e.value||t})},I=({children:t,className:e})=>a.jsx(m,{className:r("w-[var(--radix-popover-trigger-width)] p-1",e),children:t}),O=({value:t,children:e,disabled:s,className:n})=>{const l=o.useContext(c);if(!l)throw new Error("SelectItem must be used within Select");const i=l.value===t;return a.jsxs("div",{role:"option","aria-selected":i,className:r("relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-slate-100 focus:bg-slate-100 dark:hover:bg-slate-800 dark:focus:bg-slate-800",s&&"pointer-events-none opacity-50",n),onClick:()=>{s||(l.onValueChange(t),l.setOpen(!1))},children:[a.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:i&&a.jsx(b,{className:"h-4 w-4"})}),e]})},B=({open:t,onOpenChange:e,children:s})=>a.jsx(p,{open:t,onOpenChange:e,children:s}),v=o.forwardRef(({className:t,...e},s)=>a.jsx(h,{ref:s,className:r("sm:max-w-[425px]",t),...e}));v.displayName="AlertDialogContent";const F=({className:t,...e})=>a.jsx(w,{className:r(t),...e}),H=({className:t,...e})=>a.jsx(D,{className:r(t),...e}),A=o.forwardRef(({className:t,...e},s)=>a.jsx(j,{ref:s,className:r(t),...e}));A.displayName="AlertDialogTitle";const S=o.forwardRef(({className:t,...e},s)=>a.jsx("p",{ref:s,className:r("text-sm text-slate-500 dark:text-slate-400",t),...e}));S.displayName="AlertDialogDescription";const y=o.forwardRef(({className:t,...e},s)=>a.jsx(d,{ref:s,className:r(t),...e}));y.displayName="AlertDialogAction";const k=o.forwardRef(({className:t,...e},s)=>a.jsx(d,{ref:s,variant:"outline",className:r(t),...e}));k.displayName="AlertDialogCancel";export{B as A,P as S,C as a,V as b,I as c,O as d,v as e,F as f,A as g,S as h,H as i,k as j,y as k};