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.
- AutoGLM_GUI/__main__.py +0 -4
- AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
- AutoGLM_GUI/agents/__init__.py +20 -0
- AutoGLM_GUI/agents/factory.py +160 -0
- AutoGLM_GUI/agents/mai_adapter.py +627 -0
- AutoGLM_GUI/agents/protocols.py +23 -0
- AutoGLM_GUI/api/__init__.py +48 -7
- AutoGLM_GUI/api/agents.py +61 -17
- AutoGLM_GUI/api/devices.py +12 -18
- AutoGLM_GUI/api/dual_model.py +15 -9
- AutoGLM_GUI/api/health.py +13 -0
- AutoGLM_GUI/api/layered_agent.py +239 -166
- AutoGLM_GUI/api/mcp.py +11 -10
- AutoGLM_GUI/api/version.py +23 -10
- AutoGLM_GUI/api/workflows.py +2 -1
- AutoGLM_GUI/config_manager.py +55 -1
- AutoGLM_GUI/device_adapter.py +263 -0
- AutoGLM_GUI/device_protocol.py +266 -0
- AutoGLM_GUI/devices/__init__.py +49 -0
- AutoGLM_GUI/devices/adb_device.py +205 -0
- AutoGLM_GUI/devices/mock_device.py +183 -0
- AutoGLM_GUI/devices/remote_device.py +172 -0
- AutoGLM_GUI/dual_model/decision_model.py +4 -4
- AutoGLM_GUI/exceptions.py +3 -3
- AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +2 -2
- AutoGLM_GUI/metrics.py +13 -20
- AutoGLM_GUI/phone_agent_manager.py +219 -134
- AutoGLM_GUI/phone_agent_patches.py +2 -1
- AutoGLM_GUI/platform_utils.py +5 -2
- AutoGLM_GUI/schemas.py +47 -0
- AutoGLM_GUI/scrcpy_stream.py +17 -13
- AutoGLM_GUI/server.py +3 -1
- AutoGLM_GUI/socketio_server.py +16 -4
- AutoGLM_GUI/state.py +10 -30
- AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-_XNhzQZX.js} +1 -1
- AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +126 -0
- AutoGLM_GUI/static/assets/{dialog-BfdcBs1x.js → dialog-B3uW4T8V.js} +3 -3
- AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +1 -0
- AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-Cy8TmmHV.js} +1 -1
- AutoGLM_GUI/static/assets/{index-DHF1NZh0.js → index-UYYauTly.js} +6 -6
- AutoGLM_GUI/static/assets/{workflows-xiplap-r.js → workflows-Du_de-dt.js} +1 -1
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/types.py +125 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/METADATA +83 -4
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/RECORD +54 -37
- 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/config.py +0 -23
- AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
- AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.4.1.dist-info}/entry_points.txt +0 -0
- {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
|
-
) ->
|
|
21
|
+
) -> ModelResponse:
|
|
21
22
|
"""
|
|
22
23
|
Patched version of ModelClient.request that supports streaming thinking chunks.
|
|
23
24
|
|
AutoGLM_GUI/platform_utils.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import platform
|
|
5
5
|
import subprocess
|
|
6
|
-
from
|
|
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(
|
|
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:
|
AutoGLM_GUI/scrcpy_stream.py
CHANGED
|
@@ -7,7 +7,7 @@ import subprocess
|
|
|
7
7
|
import sys
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from pathlib import Path
|
|
10
|
-
from
|
|
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:
|
|
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
|
-
|
|
87
|
-
|
|
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
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
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.
|
|
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(
|
|
8
|
+
app = ASGIApp(
|
|
9
|
+
other_asgi_app=fastapi_app, socketio_server=sio, socketio_path="/socket.io"
|
|
10
|
+
)
|
|
9
11
|
|
|
10
12
|
__all__ = ["app"]
|
AutoGLM_GUI/socketio_server.py
CHANGED
|
@@ -4,7 +4,9 @@ from __future__ import annotations
|
|
|
4
4
|
|
|
5
5
|
import asyncio
|
|
6
6
|
import time
|
|
7
|
-
from typing import
|
|
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) ->
|
|
68
|
-
payload:
|
|
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-
|
|
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};
|