autoglm-gui 1.4.0__py3-none-any.whl → 1.4.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 (57) hide show
  1. AutoGLM_GUI/__main__.py +0 -4
  2. AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
  3. AutoGLM_GUI/agents/__init__.py +20 -0
  4. AutoGLM_GUI/agents/factory.py +160 -0
  5. AutoGLM_GUI/agents/mai_adapter.py +627 -0
  6. AutoGLM_GUI/agents/protocols.py +23 -0
  7. AutoGLM_GUI/api/__init__.py +48 -7
  8. AutoGLM_GUI/api/agents.py +61 -17
  9. AutoGLM_GUI/api/devices.py +12 -18
  10. AutoGLM_GUI/api/dual_model.py +15 -9
  11. AutoGLM_GUI/api/health.py +13 -0
  12. AutoGLM_GUI/api/layered_agent.py +239 -166
  13. AutoGLM_GUI/api/mcp.py +11 -10
  14. AutoGLM_GUI/api/version.py +23 -10
  15. AutoGLM_GUI/api/workflows.py +2 -1
  16. AutoGLM_GUI/config_manager.py +55 -1
  17. AutoGLM_GUI/device_adapter.py +263 -0
  18. AutoGLM_GUI/device_protocol.py +266 -0
  19. AutoGLM_GUI/devices/__init__.py +49 -0
  20. AutoGLM_GUI/devices/adb_device.py +205 -0
  21. AutoGLM_GUI/devices/mock_device.py +183 -0
  22. AutoGLM_GUI/devices/remote_device.py +172 -0
  23. AutoGLM_GUI/dual_model/decision_model.py +4 -4
  24. AutoGLM_GUI/exceptions.py +3 -3
  25. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +2 -2
  26. AutoGLM_GUI/metrics.py +13 -20
  27. AutoGLM_GUI/phone_agent_manager.py +219 -134
  28. AutoGLM_GUI/phone_agent_patches.py +2 -1
  29. AutoGLM_GUI/platform_utils.py +5 -2
  30. AutoGLM_GUI/schemas.py +47 -0
  31. AutoGLM_GUI/scrcpy_stream.py +17 -13
  32. AutoGLM_GUI/server.py +3 -1
  33. AutoGLM_GUI/socketio_server.py +16 -4
  34. AutoGLM_GUI/state.py +10 -30
  35. AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-_XNhzQZX.js} +1 -1
  36. AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +126 -0
  37. AutoGLM_GUI/static/assets/{dialog-BfdcBs1x.js → dialog-B3uW4T8V.js} +3 -3
  38. AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +1 -0
  39. AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-Cy8TmmHV.js} +1 -1
  40. AutoGLM_GUI/static/assets/{index-DHF1NZh0.js → index-UYYauTly.js} +6 -6
  41. AutoGLM_GUI/static/assets/{workflows-xiplap-r.js → workflows-Du_de-dt.js} +1 -1
  42. AutoGLM_GUI/static/index.html +2 -2
  43. AutoGLM_GUI/types.py +125 -0
  44. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/METADATA +83 -4
  45. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/RECORD +54 -37
  46. mai_agent/base.py +137 -0
  47. mai_agent/mai_grounding_agent.py +263 -0
  48. mai_agent/mai_naivigation_agent.py +526 -0
  49. mai_agent/prompt.py +148 -0
  50. mai_agent/unified_memory.py +67 -0
  51. mai_agent/utils.py +73 -0
  52. AutoGLM_GUI/config.py +0 -23
  53. AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
  54. AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
  55. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/WHEEL +0 -0
  56. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/entry_points.txt +0 -0
  57. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/licenses/LICENSE +0 -0
@@ -7,6 +7,7 @@ This module patches the upstream phone_agent code without modifying the original
7
7
  from typing import Any, Callable
8
8
 
9
9
  from phone_agent.model import ModelClient
10
+ from phone_agent.model.client import ModelResponse
10
11
 
11
12
 
12
13
  # Store original methods
@@ -17,7 +18,7 @@ def _patched_model_request(
17
18
  self,
18
19
  messages: list[dict[str, Any]],
19
20
  on_thinking_chunk: Callable[[str], None] | None = None,
20
- ) -> Any:
21
+ ) -> ModelResponse:
21
22
  """
22
23
  Patched version of ModelClient.request that supports streaming thinking chunks.
23
24
 
@@ -3,7 +3,8 @@
3
3
  import asyncio
4
4
  import platform
5
5
  import subprocess
6
- from typing import Any, Sequence
6
+ from asyncio.subprocess import Process as AsyncProcess
7
+ from typing import Sequence
7
8
 
8
9
 
9
10
  def is_windows() -> bool:
@@ -51,7 +52,9 @@ async def run_cmd_silently(cmd: Sequence[str]) -> subprocess.CompletedProcess:
51
52
  return subprocess.CompletedProcess(cmd, return_code, stdout_str, stderr_str)
52
53
 
53
54
 
54
- async def spawn_process(cmd: Sequence[str], *, capture_output: bool = False) -> Any:
55
+ async def spawn_process(
56
+ cmd: Sequence[str], *, capture_output: bool = False
57
+ ) -> subprocess.Popen[bytes] | AsyncProcess:
55
58
  """Start a long-running process with optional stdio capture."""
56
59
  stdout = subprocess.PIPE if capture_output else None
57
60
  stderr = subprocess.PIPE if capture_output else None
AutoGLM_GUI/schemas.py CHANGED
@@ -60,6 +60,27 @@ class InitRequest(BaseModel):
60
60
  model: APIModelConfig | None = Field(default=None, alias="model_config")
61
61
  agent: APIAgentConfig | None = Field(default=None, alias="agent_config")
62
62
 
63
+ # Agent configuration (factory pattern)
64
+ agent_type: str = "glm" # Agent type to use (e.g., "glm", "mai")
65
+ agent_config_params: dict | None = None # Agent-specific configuration parameters
66
+
67
+ # Hot-reload support
68
+ force: bool = False # Force re-initialization even if agent already exists
69
+
70
+ @field_validator("agent_type")
71
+ @classmethod
72
+ def validate_agent_type(cls, v: str) -> str:
73
+ """验证 agent_type 有效性."""
74
+ # Don't import at module level to avoid circular imports
75
+ from AutoGLM_GUI.agents import is_agent_type_registered
76
+
77
+ if not is_agent_type_registered(v):
78
+ raise ValueError(
79
+ f"Unknown agent_type: '{v}'. "
80
+ f"Please register the agent type first or use a known type."
81
+ )
82
+ return v
83
+
63
84
 
64
85
  class ChatRequest(BaseModel):
65
86
  message: str
@@ -326,6 +347,13 @@ class ConfigResponse(BaseModel):
326
347
  decision_model_name: str = ""
327
348
  decision_api_key: str = ""
328
349
 
350
+ # Agent 类型配置
351
+ agent_type: str = "glm" # Agent type (e.g., "glm", "mai")
352
+ agent_config_params: dict | None = None # Agent-specific configuration
353
+
354
+ # Agent 执行配置
355
+ default_max_steps: int = 100 # 单次任务最大执行步数
356
+
329
357
  conflicts: list[dict] | None = None # 配置冲突信息(可选)
330
358
 
331
359
 
@@ -342,6 +370,25 @@ class ConfigSaveRequest(BaseModel):
342
370
  decision_model_name: str | None = None
343
371
  decision_api_key: str | None = None
344
372
 
373
+ # Agent 类型配置
374
+ agent_type: str = "glm" # Agent type to use (e.g., "glm", "mai")
375
+ agent_config_params: dict | None = None # Agent-specific configuration parameters
376
+
377
+ # Agent 执行配置
378
+ default_max_steps: int | None = None # 单次任务最大执行步数
379
+
380
+ @field_validator("default_max_steps")
381
+ @classmethod
382
+ def validate_default_max_steps(cls, v: int | None) -> int | None:
383
+ """验证 default_max_steps 范围."""
384
+ if v is None:
385
+ return v
386
+ if v <= 0:
387
+ raise ValueError("default_max_steps must be positive")
388
+ if v > 1000:
389
+ raise ValueError("default_max_steps must be <= 1000")
390
+ return v
391
+
345
392
  @field_validator("base_url")
346
393
  @classmethod
347
394
  def validate_base_url(cls, v: str) -> str:
@@ -7,7 +7,7 @@ import subprocess
7
7
  import sys
8
8
  from dataclasses import dataclass
9
9
  from pathlib import Path
10
- from typing import Any
10
+ from asyncio.subprocess import Process as AsyncProcess
11
11
 
12
12
  from AutoGLM_GUI.adb_plus import check_device_available
13
13
  from AutoGLM_GUI.logger import logger
@@ -69,7 +69,7 @@ class ScrcpyStreamer:
69
69
  self.idr_interval_s = idr_interval_s
70
70
  self.stream_options = stream_options or ScrcpyVideoStreamOptions()
71
71
 
72
- self.scrcpy_process: Any | None = None
72
+ self.scrcpy_process: subprocess.Popen[bytes] | AsyncProcess | None = None
73
73
  self.tcp_socket: socket.socket | None = None
74
74
  self.forward_cleanup_needed = False
75
75
 
@@ -83,8 +83,9 @@ class ScrcpyStreamer:
83
83
  def _find_scrcpy_server(self) -> str:
84
84
  """Find scrcpy-server binary path."""
85
85
  # Priority 1: PyInstaller bundled path (for packaged executable)
86
- if getattr(sys, "_MEIPASS", None):
87
- bundled_server = Path(sys._MEIPASS) / "scrcpy-server-v3.3.3"
86
+ meipass = getattr(sys, "_MEIPASS", None)
87
+ if meipass:
88
+ bundled_server = Path(meipass) / "scrcpy-server-v3.3.3"
88
89
  if bundled_server.exists():
89
90
  logger.info(f"Using bundled scrcpy-server: {bundled_server}")
90
91
  return str(bundled_server)
@@ -262,14 +263,16 @@ class ScrcpyStreamer:
262
263
 
263
264
  # Check if process is still running
264
265
  error_msg = None
265
- if is_windows():
266
- if self.scrcpy_process.poll() is not None:
267
- stdout, stderr = self.scrcpy_process.communicate()
268
- error_msg = stderr.decode() if stderr else stdout.decode()
269
- else:
270
- if self.scrcpy_process.returncode is not None:
271
- stdout, stderr = await self.scrcpy_process.communicate()
272
- error_msg = stderr.decode() if stderr else stdout.decode()
266
+ proc = self.scrcpy_process
267
+ if proc is not None:
268
+ if is_windows():
269
+ if proc.poll() is not None: # type: ignore[union-attr]
270
+ stdout, stderr = proc.communicate() # type: ignore[union-attr]
271
+ error_msg = stderr.decode() if stderr else stdout.decode()
272
+ else:
273
+ if proc.returncode is not None: # type: ignore[union-attr]
274
+ stdout, stderr = await proc.communicate() # type: ignore[union-attr]
275
+ error_msg = stderr.decode() if stderr else stdout.decode()
273
276
 
274
277
  if error_msg is not None:
275
278
  if "Address already in use" in error_msg:
@@ -431,7 +434,8 @@ class ScrcpyStreamer:
431
434
  if self.scrcpy_process:
432
435
  try:
433
436
  self.scrcpy_process.terminate()
434
- self.scrcpy_process.wait(timeout=2)
437
+ if isinstance(self.scrcpy_process, subprocess.Popen):
438
+ self.scrcpy_process.wait(timeout=2)
435
439
  except Exception:
436
440
  try:
437
441
  self.scrcpy_process.kill()
AutoGLM_GUI/server.py CHANGED
@@ -5,6 +5,8 @@ from socketio import ASGIApp
5
5
  from AutoGLM_GUI.api import app as fastapi_app
6
6
  from AutoGLM_GUI.socketio_server import sio
7
7
 
8
- app = ASGIApp(sio, other_asgi_app=fastapi_app)
8
+ app = ASGIApp(
9
+ other_asgi_app=fastapi_app, socketio_server=sio, socketio_path="/socket.io"
10
+ )
9
11
 
10
12
  __all__ = ["app"]
@@ -4,7 +4,9 @@ from __future__ import annotations
4
4
 
5
5
  import asyncio
6
6
  import time
7
- from typing import Any
7
+ from typing import NotRequired
8
+
9
+ from typing_extensions import TypedDict
8
10
 
9
11
  import socketio
10
12
 
@@ -12,9 +14,19 @@ from AutoGLM_GUI.logger import logger
12
14
  from AutoGLM_GUI.scrcpy_protocol import ScrcpyMediaStreamPacket
13
15
  from AutoGLM_GUI.scrcpy_stream import ScrcpyStreamer
14
16
 
17
+
18
+ class VideoPacketPayload(TypedDict):
19
+ type: str
20
+ data: bytes
21
+ timestamp: int
22
+ keyframe: NotRequired[bool | None]
23
+ pts: NotRequired[int | None]
24
+
25
+
15
26
  sio = socketio.AsyncServer(
16
27
  async_mode="asgi",
17
28
  cors_allowed_origins="*",
29
+ server_kwargs={"socketio_path": "/socket.io"},
18
30
  )
19
31
 
20
32
  _socket_streamers: dict[str, ScrcpyStreamer] = {}
@@ -64,8 +76,8 @@ async def _stream_packets(sid: str, streamer: ScrcpyStreamer) -> None:
64
76
  await _stop_stream_for_sid(sid)
65
77
 
66
78
 
67
- def _packet_to_payload(packet: ScrcpyMediaStreamPacket) -> dict[str, Any]:
68
- payload: dict[str, Any] = {
79
+ def _packet_to_payload(packet: ScrcpyMediaStreamPacket) -> VideoPacketPayload:
80
+ payload: VideoPacketPayload = {
69
81
  "type": packet.type,
70
82
  "data": packet.data,
71
83
  "timestamp": int(time.time() * 1000),
@@ -87,7 +99,7 @@ async def disconnect(sid: str) -> None:
87
99
  await _stop_stream_for_sid(sid)
88
100
 
89
101
 
90
- @sio.on("connect-device")
102
+ @sio.on("connect-device") # type: ignore[misc]
91
103
  async def connect_device(sid: str, data: dict | None) -> None:
92
104
  payload = data or {}
93
105
  device_id = payload.get("device_id") or payload.get("deviceId")
AutoGLM_GUI/state.py CHANGED
@@ -1,4 +1,13 @@
1
- """Shared runtime state for the AutoGLM-GUI API."""
1
+ """Shared runtime state for the AutoGLM-GUI API.
2
+
3
+ NOTE: Agent instances and configurations are now managed internally
4
+ by PhoneAgentManager singleton. This module only contains:
5
+ - scrcpy_streamers: Video streaming state per device
6
+ - scrcpy_locks: Async locks for stream management
7
+ - non_blocking_takeover: Takeover callback handler
8
+
9
+ See PhoneAgentManager (phone_agent_manager.py) for agent lifecycle management.
10
+ """
2
11
 
3
12
  from __future__ import annotations
4
13
 
@@ -6,38 +15,9 @@ import asyncio
6
15
  from typing import TYPE_CHECKING
7
16
 
8
17
  from AutoGLM_GUI.logger import logger
9
- from phone_agent.agent import AgentConfig
10
- from phone_agent.model import ModelConfig
11
18
 
12
19
  if TYPE_CHECKING:
13
20
  from AutoGLM_GUI.scrcpy_stream import ScrcpyStreamer
14
- from phone_agent import PhoneAgent
15
-
16
- # Agent instances keyed by device_id
17
- #
18
- # IMPORTANT: Managed by PhoneAgentManager (AutoGLM_GUI/phone_agent_manager.py)
19
- # - Do NOT directly modify these dictionaries
20
- # - Use PhoneAgentManager.get_instance() for all agent operations
21
- #
22
- # device_id changes when connection method changes
23
- # (e.g., USB "ABC123" → WiFi "192.168.1.100:5555")
24
- #
25
- # This means the same physical device may have different device_ids:
26
- # - USB connection: device_id = hardware serial (e.g., "ABC123DEF")
27
- # - WiFi connection: device_id = IP:port (e.g., "192.168.1.100:5555")
28
- # - mDNS connection: device_id = service name (e.g., "adb-ABC123._adb-tls-connect._tcp")
29
- #
30
- # DeviceManager tracks devices by hardware serial and maintains
31
- # device_id ↔ serial mapping. Use PhoneAgentManager.find_agent_by_serial()
32
- # to find agents when device_id changes.
33
- #
34
- # See CLAUDE.md "Device Identification" section for details.
35
- agents: dict[str, "PhoneAgent"] = {}
36
-
37
- # Cached configs to rebuild agents on reset
38
- # Keyed by device_id (same semantics as agents dict)
39
- # IMPORTANT: Managed by PhoneAgentManager - do NOT modify directly
40
- agent_configs: dict[str, tuple[ModelConfig, AgentConfig]] = {}
41
21
 
42
22
  # Scrcpy streaming per device
43
23
  scrcpy_streamers: dict[str, "ScrcpyStreamer"] = {}
@@ -1 +1 @@
1
- import{j as o}from"./index-DHF1NZh0.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-UYYauTly.js";function t(){return o.jsx("div",{className:"p-2",children:o.jsx("h3",{children:"About"})})}export{t as component};