autoglm-gui 1.4.1__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.
- AutoGLM_GUI/__init__.py +11 -0
- AutoGLM_GUI/__main__.py +26 -4
- AutoGLM_GUI/actions/__init__.py +6 -0
- AutoGLM_GUI/actions/handler.py +196 -0
- AutoGLM_GUI/actions/types.py +15 -0
- AutoGLM_GUI/adb/__init__.py +53 -0
- AutoGLM_GUI/adb/apps.py +227 -0
- AutoGLM_GUI/adb/connection.py +323 -0
- AutoGLM_GUI/adb/device.py +171 -0
- AutoGLM_GUI/adb/input.py +67 -0
- AutoGLM_GUI/adb/screenshot.py +11 -0
- AutoGLM_GUI/adb/timing.py +167 -0
- AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
- AutoGLM_GUI/adb_plus/screenshot.py +22 -1
- AutoGLM_GUI/adb_plus/serial.py +38 -20
- AutoGLM_GUI/adb_plus/touch.py +4 -9
- AutoGLM_GUI/agents/__init__.py +43 -12
- AutoGLM_GUI/agents/events.py +19 -0
- AutoGLM_GUI/agents/factory.py +31 -38
- AutoGLM_GUI/agents/glm/__init__.py +7 -0
- AutoGLM_GUI/agents/glm/agent.py +292 -0
- AutoGLM_GUI/agents/glm/message_builder.py +81 -0
- AutoGLM_GUI/agents/glm/parser.py +110 -0
- AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
- AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
- AutoGLM_GUI/agents/mai/__init__.py +28 -0
- AutoGLM_GUI/agents/mai/agent.py +405 -0
- AutoGLM_GUI/agents/mai/parser.py +254 -0
- AutoGLM_GUI/agents/mai/prompts.py +103 -0
- AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
- AutoGLM_GUI/agents/protocols.py +12 -8
- AutoGLM_GUI/agents/stream_runner.py +188 -0
- AutoGLM_GUI/api/__init__.py +40 -21
- AutoGLM_GUI/api/agents.py +157 -240
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +102 -12
- AutoGLM_GUI/api/history.py +78 -0
- AutoGLM_GUI/api/layered_agent.py +67 -15
- AutoGLM_GUI/api/media.py +64 -1
- AutoGLM_GUI/api/scheduled_tasks.py +98 -0
- AutoGLM_GUI/config.py +81 -0
- AutoGLM_GUI/config_manager.py +68 -51
- AutoGLM_GUI/device_manager.py +248 -29
- AutoGLM_GUI/device_protocol.py +1 -1
- AutoGLM_GUI/devices/adb_device.py +5 -10
- AutoGLM_GUI/devices/mock_device.py +4 -2
- AutoGLM_GUI/devices/remote_device.py +8 -3
- AutoGLM_GUI/history_manager.py +164 -0
- AutoGLM_GUI/i18n.py +81 -0
- AutoGLM_GUI/model/__init__.py +5 -0
- AutoGLM_GUI/model/message_builder.py +69 -0
- AutoGLM_GUI/model/types.py +24 -0
- AutoGLM_GUI/models/__init__.py +10 -0
- AutoGLM_GUI/models/history.py +96 -0
- AutoGLM_GUI/models/scheduled_task.py +71 -0
- AutoGLM_GUI/parsers/__init__.py +22 -0
- AutoGLM_GUI/parsers/base.py +50 -0
- AutoGLM_GUI/parsers/phone_parser.py +58 -0
- AutoGLM_GUI/phone_agent_manager.py +62 -396
- AutoGLM_GUI/platform_utils.py +26 -0
- AutoGLM_GUI/prompt_config.py +15 -0
- AutoGLM_GUI/prompts/__init__.py +32 -0
- AutoGLM_GUI/scheduler_manager.py +304 -0
- AutoGLM_GUI/schemas.py +234 -72
- AutoGLM_GUI/scrcpy_stream.py +142 -24
- AutoGLM_GUI/socketio_server.py +100 -27
- AutoGLM_GUI/static/assets/{about-_XNhzQZX.js → about-BQm96DAl.js} +1 -1
- AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
- AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
- AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
- AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
- AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
- AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
- AutoGLM_GUI/static/assets/{index-Cy8TmmHV.js → index-CmZSnDqc.js} +1 -1
- AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
- AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
- AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
- AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
- AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
- AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
- AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
- AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/types.py +17 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +137 -130
- autoglm_gui-1.5.0.dist-info/RECORD +157 -0
- AutoGLM_GUI/agents/mai_adapter.py +0 -627
- AutoGLM_GUI/api/dual_model.py +0 -317
- AutoGLM_GUI/dual_model/__init__.py +0 -53
- AutoGLM_GUI/dual_model/decision_model.py +0 -664
- AutoGLM_GUI/dual_model/dual_agent.py +0 -917
- AutoGLM_GUI/dual_model/protocols.py +0 -354
- AutoGLM_GUI/dual_model/vision_model.py +0 -442
- AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
- AutoGLM_GUI/phone_agent_patches.py +0 -147
- AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +0 -126
- AutoGLM_GUI/static/assets/dialog-B3uW4T8V.js +0 -45
- AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +0 -1
- AutoGLM_GUI/static/assets/index-UYYauTly.js +0 -12
- AutoGLM_GUI/static/assets/workflows-Du_de-dt.js +0 -1
- autoglm_gui-1.4.1.dist-info/RECORD +0 -117
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.1.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
"""
|
|
1
|
+
"""Agent lifecycle and concurrency manager (singleton)."""
|
|
2
2
|
|
|
3
3
|
from __future__ import annotations
|
|
4
4
|
|
|
@@ -7,20 +7,17 @@ import time
|
|
|
7
7
|
from contextlib import contextmanager
|
|
8
8
|
from dataclasses import dataclass
|
|
9
9
|
from enum import Enum
|
|
10
|
-
from typing import
|
|
10
|
+
from typing import Callable, Optional
|
|
11
11
|
|
|
12
12
|
from AutoGLM_GUI.agents.protocols import BaseAgent
|
|
13
|
-
from AutoGLM_GUI.
|
|
13
|
+
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
14
14
|
from AutoGLM_GUI.exceptions import (
|
|
15
15
|
AgentInitializationError,
|
|
16
16
|
AgentNotInitializedError,
|
|
17
17
|
DeviceBusyError,
|
|
18
18
|
)
|
|
19
19
|
from AutoGLM_GUI.logger import logger
|
|
20
|
-
|
|
21
|
-
if TYPE_CHECKING:
|
|
22
|
-
from phone_agent.agent import AgentConfig
|
|
23
|
-
from phone_agent.model import ModelConfig
|
|
20
|
+
from AutoGLM_GUI.types import AgentSpecificConfig
|
|
24
21
|
|
|
25
22
|
|
|
26
23
|
class AgentState(str, Enum):
|
|
@@ -34,14 +31,15 @@ class AgentState(str, Enum):
|
|
|
34
31
|
|
|
35
32
|
@dataclass
|
|
36
33
|
class AgentMetadata:
|
|
37
|
-
"""Metadata for
|
|
34
|
+
"""Metadata for an agent instance."""
|
|
38
35
|
|
|
39
36
|
device_id: str
|
|
40
37
|
state: AgentState
|
|
41
38
|
model_config: ModelConfig
|
|
42
39
|
agent_config: AgentConfig
|
|
43
|
-
|
|
44
|
-
|
|
40
|
+
agent_type: str = "glm"
|
|
41
|
+
created_at: float = 0.0
|
|
42
|
+
last_used: float = 0.0
|
|
45
43
|
error_message: Optional[str] = None
|
|
46
44
|
|
|
47
45
|
|
|
@@ -54,7 +52,7 @@ class StreamingAgentContext:
|
|
|
54
52
|
|
|
55
53
|
class PhoneAgentManager:
|
|
56
54
|
"""
|
|
57
|
-
Singleton manager for
|
|
55
|
+
Singleton manager for agent lifecycle and concurrency control.
|
|
58
56
|
|
|
59
57
|
Features:
|
|
60
58
|
- Thread-safe agent creation/destruction
|
|
@@ -73,10 +71,7 @@ class PhoneAgentManager:
|
|
|
73
71
|
Example:
|
|
74
72
|
>>> manager = PhoneAgentManager.get_instance()
|
|
75
73
|
>>>
|
|
76
|
-
>>> #
|
|
77
|
-
>>> agent = manager.initialize_agent(device_id, model_config, agent_config)
|
|
78
|
-
>>>
|
|
79
|
-
>>> # Use agent with automatic locking
|
|
74
|
+
>>> # Use agent with automatic locking (auto-initializes if needed)
|
|
80
75
|
>>> with manager.use_agent(device_id) as agent:
|
|
81
76
|
>>> result = agent.run("Open WeChat")
|
|
82
77
|
"""
|
|
@@ -101,12 +96,11 @@ class PhoneAgentManager:
|
|
|
101
96
|
self._streaming_contexts: dict[str, StreamingAgentContext] = {}
|
|
102
97
|
self._streaming_contexts_lock = threading.Lock()
|
|
103
98
|
|
|
104
|
-
|
|
105
|
-
self._abort_events: dict[str, threading.Event] = {}
|
|
99
|
+
self._abort_events: dict[str, threading.Event | Callable[[], None]] = {}
|
|
106
100
|
|
|
107
101
|
# Agent storage (transition from global state to instance state)
|
|
108
102
|
self._agents: dict[str, BaseAgent] = {}
|
|
109
|
-
self._agent_configs: dict[str, tuple[
|
|
103
|
+
self._agent_configs: dict[str, tuple[ModelConfig, AgentConfig]] = {}
|
|
110
104
|
|
|
111
105
|
@classmethod
|
|
112
106
|
def get_instance(cls) -> PhoneAgentManager:
|
|
@@ -120,91 +114,6 @@ class PhoneAgentManager:
|
|
|
120
114
|
|
|
121
115
|
# ==================== Agent Lifecycle ====================
|
|
122
116
|
|
|
123
|
-
def initialize_agent(
|
|
124
|
-
self,
|
|
125
|
-
device_id: str,
|
|
126
|
-
model_config: "ModelConfig",
|
|
127
|
-
agent_config: "AgentConfig",
|
|
128
|
-
takeover_callback: Optional[Callable] = None,
|
|
129
|
-
force: bool = False,
|
|
130
|
-
) -> BaseAgent:
|
|
131
|
-
"""
|
|
132
|
-
Initialize PhoneAgent for a device (thread-safe, idempotent).
|
|
133
|
-
|
|
134
|
-
Args:
|
|
135
|
-
device_id: Device identifier (USB serial / IP:port)
|
|
136
|
-
model_config: Model configuration
|
|
137
|
-
agent_config: Agent configuration
|
|
138
|
-
takeover_callback: Optional takeover callback
|
|
139
|
-
force: Force re-initialization even if agent exists
|
|
140
|
-
|
|
141
|
-
Returns:
|
|
142
|
-
PhoneAgent: Initialized agent instance
|
|
143
|
-
|
|
144
|
-
Raises:
|
|
145
|
-
AgentInitializationError: If initialization fails
|
|
146
|
-
DeviceBusyError: If device is currently processing
|
|
147
|
-
|
|
148
|
-
Transactional Guarantee:
|
|
149
|
-
- On failure, state is rolled back
|
|
150
|
-
- state.agents and state.agent_configs remain consistent
|
|
151
|
-
"""
|
|
152
|
-
from AutoGLM_GUI.state import non_blocking_takeover
|
|
153
|
-
from phone_agent import PhoneAgent
|
|
154
|
-
|
|
155
|
-
with self._manager_lock:
|
|
156
|
-
# Check if already initialized
|
|
157
|
-
if device_id in self._agents and not force:
|
|
158
|
-
logger.debug(f"Agent already initialized for {device_id}")
|
|
159
|
-
return self._agents[device_id]
|
|
160
|
-
|
|
161
|
-
# Check device availability (non-blocking check)
|
|
162
|
-
device_lock = self._get_device_lock(device_id)
|
|
163
|
-
if device_lock.locked():
|
|
164
|
-
raise DeviceBusyError(
|
|
165
|
-
f"Device {device_id} is currently processing a request"
|
|
166
|
-
)
|
|
167
|
-
|
|
168
|
-
# Create metadata first with INITIALIZING state
|
|
169
|
-
self._metadata[device_id] = AgentMetadata(
|
|
170
|
-
device_id=device_id,
|
|
171
|
-
state=AgentState.INITIALIZING,
|
|
172
|
-
model_config=model_config,
|
|
173
|
-
agent_config=agent_config,
|
|
174
|
-
created_at=time.time(),
|
|
175
|
-
last_used=time.time(),
|
|
176
|
-
)
|
|
177
|
-
|
|
178
|
-
try:
|
|
179
|
-
# Create agent
|
|
180
|
-
agent = PhoneAgent(
|
|
181
|
-
model_config=model_config,
|
|
182
|
-
agent_config=agent_config,
|
|
183
|
-
takeover_callback=takeover_callback or non_blocking_takeover,
|
|
184
|
-
)
|
|
185
|
-
|
|
186
|
-
# Store in state (transactional)
|
|
187
|
-
self._agents[device_id] = agent
|
|
188
|
-
self._agent_configs[device_id] = (model_config, agent_config)
|
|
189
|
-
|
|
190
|
-
# Update state to IDLE on success
|
|
191
|
-
self._metadata[device_id].state = AgentState.IDLE
|
|
192
|
-
|
|
193
|
-
logger.info(f"Agent initialized for device {device_id}")
|
|
194
|
-
return agent
|
|
195
|
-
|
|
196
|
-
except Exception as e:
|
|
197
|
-
# Rollback on error
|
|
198
|
-
self._agents.pop(device_id, None)
|
|
199
|
-
self._agent_configs.pop(device_id, None)
|
|
200
|
-
self._metadata[device_id].state = AgentState.ERROR
|
|
201
|
-
self._metadata[device_id].error_message = str(e)
|
|
202
|
-
|
|
203
|
-
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
204
|
-
raise AgentInitializationError(
|
|
205
|
-
f"Failed to initialize agent: {str(e)}"
|
|
206
|
-
) from e
|
|
207
|
-
|
|
208
117
|
def initialize_agent_with_factory(
|
|
209
118
|
self,
|
|
210
119
|
device_id: str,
|
|
@@ -216,74 +125,53 @@ class PhoneAgentManager:
|
|
|
216
125
|
confirmation_callback: Optional[Callable] = None,
|
|
217
126
|
force: bool = False,
|
|
218
127
|
) -> "BaseAgent":
|
|
219
|
-
"""
|
|
220
|
-
Initialize agent using factory pattern (thread-safe, idempotent).
|
|
221
|
-
|
|
222
|
-
This method uses the agent factory to create agents dynamically based on agent_type.
|
|
223
|
-
New agent types can be added without modifying this method by registering them.
|
|
224
|
-
|
|
225
|
-
Args:
|
|
226
|
-
device_id: Device identifier (USB serial / IP:port)
|
|
227
|
-
agent_type: Type of agent to create (e.g., "phone", "mai")
|
|
228
|
-
model_config: Model configuration
|
|
229
|
-
agent_config: Agent configuration
|
|
230
|
-
agent_specific_config: Agent-specific configuration dict
|
|
231
|
-
takeover_callback: Optional takeover callback
|
|
232
|
-
confirmation_callback: Optional confirmation callback
|
|
233
|
-
force: Force re-initialization even if agent exists
|
|
234
|
-
|
|
235
|
-
Returns:
|
|
236
|
-
BaseAgent: Initialized agent instance
|
|
237
|
-
|
|
238
|
-
Raises:
|
|
239
|
-
AgentInitializationError: If initialization fails
|
|
240
|
-
DeviceBusyError: If device is currently processing
|
|
241
|
-
|
|
242
|
-
Transactional Guarantee:
|
|
243
|
-
- On failure, state is rolled back
|
|
244
|
-
- state.agents and state.agent_configs remain consistent
|
|
245
|
-
"""
|
|
246
128
|
from AutoGLM_GUI.agents import create_agent
|
|
247
129
|
|
|
248
130
|
with self._manager_lock:
|
|
249
|
-
# Check if already initialized
|
|
250
131
|
if device_id in self._agents and not force:
|
|
251
132
|
logger.debug(f"Agent already initialized for {device_id}")
|
|
252
133
|
return self._agents[device_id]
|
|
253
134
|
|
|
254
|
-
# Check device availability (non-blocking check)
|
|
255
135
|
device_lock = self._get_device_lock(device_id)
|
|
256
136
|
if device_lock.locked():
|
|
257
137
|
raise DeviceBusyError(
|
|
258
138
|
f"Device {device_id} is currently processing a request"
|
|
259
139
|
)
|
|
260
140
|
|
|
261
|
-
# Create metadata first with INITIALIZING state
|
|
262
141
|
self._metadata[device_id] = AgentMetadata(
|
|
263
142
|
device_id=device_id,
|
|
264
143
|
state=AgentState.INITIALIZING,
|
|
265
144
|
model_config=model_config,
|
|
266
145
|
agent_config=agent_config,
|
|
146
|
+
agent_type=agent_type,
|
|
267
147
|
created_at=time.time(),
|
|
268
148
|
last_used=time.time(),
|
|
269
149
|
)
|
|
270
150
|
|
|
271
151
|
try:
|
|
272
|
-
|
|
152
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
153
|
+
|
|
154
|
+
device_manager = DeviceManager.get_instance()
|
|
155
|
+
try:
|
|
156
|
+
device = device_manager.get_device_protocol(device_id)
|
|
157
|
+
except ValueError:
|
|
158
|
+
# Ensure cold starts refresh device cache before failing.
|
|
159
|
+
device_manager.force_refresh()
|
|
160
|
+
device = device_manager.get_device_protocol(device_id)
|
|
161
|
+
|
|
273
162
|
agent = create_agent(
|
|
274
163
|
agent_type=agent_type,
|
|
275
164
|
model_config=model_config,
|
|
276
165
|
agent_config=agent_config,
|
|
277
166
|
agent_specific_config=agent_specific_config,
|
|
167
|
+
device=device,
|
|
278
168
|
takeover_callback=takeover_callback,
|
|
279
169
|
confirmation_callback=confirmation_callback,
|
|
280
170
|
)
|
|
281
171
|
|
|
282
|
-
# Store in state (transactional)
|
|
283
172
|
self._agents[device_id] = agent
|
|
284
173
|
self._agent_configs[device_id] = (model_config, agent_config)
|
|
285
174
|
|
|
286
|
-
# Update state to IDLE on success
|
|
287
175
|
self._metadata[device_id].state = AgentState.IDLE
|
|
288
176
|
|
|
289
177
|
logger.info(
|
|
@@ -292,7 +180,6 @@ class PhoneAgentManager:
|
|
|
292
180
|
return agent
|
|
293
181
|
|
|
294
182
|
except Exception as e:
|
|
295
|
-
# Rollback on error
|
|
296
183
|
self._agents.pop(device_id, None)
|
|
297
184
|
self._agent_configs.pop(device_id, None)
|
|
298
185
|
self._metadata[device_id].state = AgentState.ERROR
|
|
@@ -303,189 +190,23 @@ class PhoneAgentManager:
|
|
|
303
190
|
f"Failed to initialize agent: {str(e)}"
|
|
304
191
|
) from e
|
|
305
192
|
|
|
306
|
-
def _create_streaming_agent(
|
|
307
|
-
self,
|
|
308
|
-
device_id: str,
|
|
309
|
-
original_agent: "BaseAgent",
|
|
310
|
-
on_thinking_chunk: Callable[[str], None],
|
|
311
|
-
) -> "BaseAgent":
|
|
312
|
-
"""
|
|
313
|
-
创建支持流式输出的 agent(复制原始 agent).
|
|
314
|
-
|
|
315
|
-
Args:
|
|
316
|
-
device_id: 设备标识符
|
|
317
|
-
original_agent: 原始 agent 实例
|
|
318
|
-
on_thinking_chunk: 思考块回调函数
|
|
319
|
-
|
|
320
|
-
Returns:
|
|
321
|
-
已 patch 的 agent 实例
|
|
322
|
-
"""
|
|
323
|
-
from AutoGLM_GUI.agents.mai_adapter import MAIAgentAdapter
|
|
324
|
-
from phone_agent import PhoneAgent
|
|
325
|
-
|
|
326
|
-
model_config, agent_config = self.get_config(device_id)
|
|
327
|
-
|
|
328
|
-
streaming_agent: BaseAgent
|
|
329
|
-
if isinstance(original_agent, MAIAgentAdapter):
|
|
330
|
-
streaming_agent = MAIAgentAdapter(
|
|
331
|
-
model_config=model_config,
|
|
332
|
-
agent_config=agent_config,
|
|
333
|
-
mai_config=original_agent.mai_config,
|
|
334
|
-
takeover_callback=original_agent.action_handler.takeover_callback,
|
|
335
|
-
confirmation_callback=original_agent.action_handler.confirmation_callback,
|
|
336
|
-
on_thinking_chunk=on_thinking_chunk,
|
|
337
|
-
)
|
|
338
|
-
streaming_agent.mai_agent.traj_memory = original_agent.mai_agent.traj_memory
|
|
339
|
-
streaming_agent._step_count = original_agent._step_count
|
|
340
|
-
streaming_agent._current_instruction = original_agent._current_instruction
|
|
341
|
-
elif isinstance(original_agent, PhoneAgent):
|
|
342
|
-
from AutoGLM_GUI.state import non_blocking_takeover
|
|
343
|
-
|
|
344
|
-
phone_streaming_agent = PhoneAgent(
|
|
345
|
-
model_config=model_config,
|
|
346
|
-
agent_config=agent_config,
|
|
347
|
-
takeover_callback=non_blocking_takeover,
|
|
348
|
-
)
|
|
349
|
-
|
|
350
|
-
original_request = phone_streaming_agent.model_client.request
|
|
351
|
-
|
|
352
|
-
def patched_request(messages, **kwargs): # type: ignore[no-untyped-def]
|
|
353
|
-
return original_request(messages, on_thinking_chunk=on_thinking_chunk) # type: ignore[call-arg]
|
|
354
|
-
|
|
355
|
-
phone_streaming_agent.model_client.request = patched_request # type: ignore[method-assign]
|
|
356
|
-
phone_streaming_agent._context = original_agent._context.copy()
|
|
357
|
-
phone_streaming_agent._step_count = original_agent._step_count
|
|
358
|
-
streaming_agent = phone_streaming_agent
|
|
359
|
-
else:
|
|
360
|
-
raise ValueError(f"Unknown agent type: {type(original_agent)}")
|
|
361
|
-
|
|
362
|
-
return streaming_agent
|
|
363
|
-
|
|
364
|
-
@contextmanager
|
|
365
|
-
def use_streaming_agent(
|
|
366
|
-
self,
|
|
367
|
-
device_id: str,
|
|
368
|
-
on_thinking_chunk: Callable[[str], None],
|
|
369
|
-
timeout: Optional[float] = None,
|
|
370
|
-
auto_initialize: bool = True,
|
|
371
|
-
):
|
|
372
|
-
"""
|
|
373
|
-
Context manager for streaming-enabled agent with automatic:
|
|
374
|
-
- 设备锁获取/释放
|
|
375
|
-
- Streaming agent 创建(带 monkey-patch)
|
|
376
|
-
- 上下文隔离和同步
|
|
377
|
-
- Abort 事件注册/清理
|
|
378
|
-
|
|
379
|
-
By default, automatically initializes the agent using global configuration
|
|
380
|
-
if not already initialized. Set auto_initialize=False to require explicit
|
|
381
|
-
initialization via initialize_agent().
|
|
382
|
-
|
|
383
|
-
Args:
|
|
384
|
-
device_id: 设备标识符
|
|
385
|
-
on_thinking_chunk: 流式思考块回调函数
|
|
386
|
-
timeout: 锁获取超时时间(None=阻塞,0=非阻塞)
|
|
387
|
-
auto_initialize: 自动初始化(默认: True)
|
|
388
|
-
|
|
389
|
-
Yields:
|
|
390
|
-
tuple[PhoneAgent, threading.Event]: (streaming_agent, stop_event)
|
|
391
|
-
|
|
392
|
-
Raises:
|
|
393
|
-
DeviceBusyError: 设备忙
|
|
394
|
-
AgentNotInitializedError: Agent 未初始化且 auto_initialize=False
|
|
395
|
-
AgentInitializationError: auto_initialize=True 且初始化失败
|
|
396
|
-
|
|
397
|
-
Example:
|
|
398
|
-
>>> def on_chunk(chunk: str):
|
|
399
|
-
>>> print(chunk, end='', flush=True)
|
|
400
|
-
>>>
|
|
401
|
-
>>> with manager.use_streaming_agent("device_123", on_chunk) as (agent, stop_event):
|
|
402
|
-
>>> result = agent.step("Open WeChat")
|
|
403
|
-
"""
|
|
404
|
-
acquired = False
|
|
405
|
-
streaming_agent = None
|
|
406
|
-
stop_event = threading.Event()
|
|
407
|
-
|
|
408
|
-
try:
|
|
409
|
-
# 获取设备锁(默认非阻塞)
|
|
410
|
-
acquired = self.acquire_device(
|
|
411
|
-
device_id,
|
|
412
|
-
timeout=timeout if timeout is not None else 0,
|
|
413
|
-
raise_on_timeout=True,
|
|
414
|
-
auto_initialize=auto_initialize,
|
|
415
|
-
)
|
|
416
|
-
|
|
417
|
-
# 获取原始 agent
|
|
418
|
-
original_agent = self.get_agent(device_id)
|
|
419
|
-
|
|
420
|
-
# 创建 streaming agent(自动检测类型)
|
|
421
|
-
streaming_agent = self._create_streaming_agent(
|
|
422
|
-
device_id=device_id,
|
|
423
|
-
original_agent=original_agent,
|
|
424
|
-
on_thinking_chunk=on_thinking_chunk,
|
|
425
|
-
)
|
|
426
|
-
|
|
427
|
-
# 注册 abort 事件
|
|
428
|
-
with self._streaming_contexts_lock:
|
|
429
|
-
self._abort_events[device_id] = stop_event
|
|
430
|
-
self._streaming_contexts[device_id] = StreamingAgentContext(
|
|
431
|
-
streaming_agent=streaming_agent,
|
|
432
|
-
original_agent=original_agent,
|
|
433
|
-
stop_event=stop_event,
|
|
434
|
-
)
|
|
435
|
-
|
|
436
|
-
logger.debug(f"Streaming agent created for {device_id}")
|
|
437
|
-
|
|
438
|
-
yield streaming_agent, stop_event
|
|
439
|
-
|
|
440
|
-
finally:
|
|
441
|
-
if streaming_agent and not stop_event.is_set():
|
|
442
|
-
original_agent = self.get_agent_safe(device_id)
|
|
443
|
-
if original_agent:
|
|
444
|
-
from AutoGLM_GUI.agents.mai_adapter import MAIAgentAdapter
|
|
445
|
-
from phone_agent import PhoneAgent
|
|
446
|
-
|
|
447
|
-
if isinstance(original_agent, MAIAgentAdapter) and isinstance(
|
|
448
|
-
streaming_agent, MAIAgentAdapter
|
|
449
|
-
):
|
|
450
|
-
original_agent.mai_agent.traj_memory = (
|
|
451
|
-
streaming_agent.mai_agent.traj_memory
|
|
452
|
-
)
|
|
453
|
-
original_agent._step_count = streaming_agent._step_count
|
|
454
|
-
original_agent._current_instruction = (
|
|
455
|
-
streaming_agent._current_instruction
|
|
456
|
-
)
|
|
457
|
-
elif isinstance(original_agent, PhoneAgent) and isinstance(
|
|
458
|
-
streaming_agent, PhoneAgent
|
|
459
|
-
):
|
|
460
|
-
original_agent._context = streaming_agent._context
|
|
461
|
-
original_agent._step_count = streaming_agent._step_count
|
|
462
|
-
|
|
463
|
-
logger.debug(
|
|
464
|
-
f"Synchronized context back to original agent for {device_id}"
|
|
465
|
-
)
|
|
466
|
-
|
|
467
|
-
# 清理 abort 事件注册
|
|
468
|
-
with self._streaming_contexts_lock:
|
|
469
|
-
self._abort_events.pop(device_id, None)
|
|
470
|
-
self._streaming_contexts.pop(device_id, None)
|
|
471
|
-
|
|
472
|
-
# 释放设备锁
|
|
473
|
-
if acquired:
|
|
474
|
-
self.release_device(device_id)
|
|
475
|
-
|
|
476
193
|
def _auto_initialize_agent(self, device_id: str) -> None:
|
|
477
194
|
"""
|
|
478
195
|
使用全局配置自动初始化 agent(内部方法,需在 manager_lock 内调用).
|
|
479
196
|
|
|
197
|
+
使用 factory 模式创建 agent,避免直接依赖 phone_agent.PhoneAgent。
|
|
198
|
+
|
|
480
199
|
Args:
|
|
481
200
|
device_id: 设备标识符
|
|
482
201
|
|
|
483
202
|
Raises:
|
|
484
203
|
AgentInitializationError: 如果配置不完整或初始化失败
|
|
485
204
|
"""
|
|
205
|
+
from typing import cast
|
|
206
|
+
|
|
207
|
+
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
486
208
|
from AutoGLM_GUI.config_manager import config_manager
|
|
487
|
-
from
|
|
488
|
-
from phone_agent.model import ModelConfig
|
|
209
|
+
from AutoGLM_GUI.types import AgentSpecificConfig
|
|
489
210
|
|
|
490
211
|
logger.info(f"Auto-initializing agent for device {device_id}...")
|
|
491
212
|
|
|
@@ -501,6 +222,7 @@ class PhoneAgentManager:
|
|
|
501
222
|
f"Please configure base_url via /api/config or call /api/init explicitly."
|
|
502
223
|
)
|
|
503
224
|
|
|
225
|
+
# 使用本地配置类型
|
|
504
226
|
model_config = ModelConfig(
|
|
505
227
|
base_url=effective_config.base_url,
|
|
506
228
|
api_key=effective_config.api_key,
|
|
@@ -509,8 +231,17 @@ class PhoneAgentManager:
|
|
|
509
231
|
|
|
510
232
|
agent_config = AgentConfig(device_id=device_id)
|
|
511
233
|
|
|
512
|
-
# 调用
|
|
513
|
-
|
|
234
|
+
# 调用 factory 方法创建 agent(避免直接依赖 phone_agent)
|
|
235
|
+
agent_specific_config = cast(
|
|
236
|
+
AgentSpecificConfig, effective_config.agent_config_params or {}
|
|
237
|
+
)
|
|
238
|
+
self.initialize_agent_with_factory(
|
|
239
|
+
device_id=device_id,
|
|
240
|
+
agent_type=effective_config.agent_type,
|
|
241
|
+
model_config=model_config,
|
|
242
|
+
agent_config=agent_config,
|
|
243
|
+
agent_specific_config=agent_specific_config,
|
|
244
|
+
)
|
|
514
245
|
logger.info(f"Agent auto-initialized for device {device_id}")
|
|
515
246
|
|
|
516
247
|
def get_agent(self, device_id: str) -> BaseAgent:
|
|
@@ -525,7 +256,7 @@ class PhoneAgentManager:
|
|
|
525
256
|
|
|
526
257
|
def reset_agent(self, device_id: str) -> None:
|
|
527
258
|
"""
|
|
528
|
-
Reset agent state
|
|
259
|
+
Reset agent state by calling the agent's reset() method.
|
|
529
260
|
|
|
530
261
|
Args:
|
|
531
262
|
device_id: Device identifier
|
|
@@ -533,31 +264,14 @@ class PhoneAgentManager:
|
|
|
533
264
|
Raises:
|
|
534
265
|
AgentNotInitializedError: If agent not initialized
|
|
535
266
|
"""
|
|
536
|
-
from AutoGLM_GUI.state import non_blocking_takeover
|
|
537
|
-
from phone_agent import PhoneAgent
|
|
538
|
-
|
|
539
267
|
with self._manager_lock:
|
|
540
268
|
if device_id not in self._agents:
|
|
541
269
|
raise AgentNotInitializedError(
|
|
542
270
|
f"Agent not initialized for device {device_id}"
|
|
543
271
|
)
|
|
544
272
|
|
|
545
|
-
#
|
|
546
|
-
|
|
547
|
-
logger.warning(
|
|
548
|
-
f"No cached config for {device_id}, only resetting agent state"
|
|
549
|
-
)
|
|
550
|
-
self._agents[device_id].reset()
|
|
551
|
-
return
|
|
552
|
-
|
|
553
|
-
# Rebuild agent from cached config
|
|
554
|
-
model_config, agent_config = self._agent_configs[device_id]
|
|
555
|
-
|
|
556
|
-
self._agents[device_id] = PhoneAgent(
|
|
557
|
-
model_config=model_config,
|
|
558
|
-
agent_config=agent_config,
|
|
559
|
-
takeover_callback=non_blocking_takeover,
|
|
560
|
-
)
|
|
273
|
+
# Reset agent state using its reset() method
|
|
274
|
+
self._agents[device_id].reset()
|
|
561
275
|
|
|
562
276
|
# Update metadata
|
|
563
277
|
if device_id in self._metadata:
|
|
@@ -717,7 +431,7 @@ class PhoneAgentManager:
|
|
|
717
431
|
|
|
718
432
|
By default, automatically initializes the agent using global configuration
|
|
719
433
|
if not already initialized. Set auto_initialize=False to require explicit
|
|
720
|
-
initialization via
|
|
434
|
+
initialization via initialize_agent_with_factory().
|
|
721
435
|
|
|
722
436
|
Args:
|
|
723
437
|
device_id: Device identifier
|
|
@@ -725,7 +439,7 @@ class PhoneAgentManager:
|
|
|
725
439
|
auto_initialize: Auto-initialize if not already initialized (default: True)
|
|
726
440
|
|
|
727
441
|
Yields:
|
|
728
|
-
|
|
442
|
+
BaseAgent: Agent instance
|
|
729
443
|
|
|
730
444
|
Raises:
|
|
731
445
|
DeviceBusyError: If device is busy
|
|
@@ -785,68 +499,6 @@ class PhoneAgentManager:
|
|
|
785
499
|
)
|
|
786
500
|
return self._agent_configs[device_id]
|
|
787
501
|
|
|
788
|
-
def update_config(
|
|
789
|
-
self,
|
|
790
|
-
device_id: str,
|
|
791
|
-
model_config: Optional[ModelConfig] = None,
|
|
792
|
-
agent_config: Optional[AgentConfig] = None,
|
|
793
|
-
) -> None:
|
|
794
|
-
"""
|
|
795
|
-
Update agent configuration (requires reinitialization).
|
|
796
|
-
|
|
797
|
-
Args:
|
|
798
|
-
device_id: Device identifier
|
|
799
|
-
model_config: New model config (None = keep existing)
|
|
800
|
-
agent_config: New agent config (None = keep existing)
|
|
801
|
-
"""
|
|
802
|
-
with self._manager_lock:
|
|
803
|
-
if device_id not in self._agent_configs:
|
|
804
|
-
raise AgentNotInitializedError(
|
|
805
|
-
f"No configuration found for device {device_id}"
|
|
806
|
-
)
|
|
807
|
-
|
|
808
|
-
old_model_config, old_agent_config = self._agent_configs[device_id]
|
|
809
|
-
|
|
810
|
-
new_model_config = model_config or old_model_config
|
|
811
|
-
new_agent_config = agent_config or old_agent_config
|
|
812
|
-
|
|
813
|
-
# Reinitialize with new config
|
|
814
|
-
self.initialize_agent(
|
|
815
|
-
device_id,
|
|
816
|
-
new_model_config,
|
|
817
|
-
new_agent_config,
|
|
818
|
-
force=True,
|
|
819
|
-
)
|
|
820
|
-
|
|
821
|
-
# ==================== DeviceManager Integration ====================
|
|
822
|
-
|
|
823
|
-
def find_agent_by_serial(self, serial: str) -> Optional[str]:
|
|
824
|
-
"""
|
|
825
|
-
Find agent device_id by hardware serial (connection switching support).
|
|
826
|
-
|
|
827
|
-
Args:
|
|
828
|
-
serial: Hardware serial number
|
|
829
|
-
|
|
830
|
-
Returns:
|
|
831
|
-
Optional[str]: device_id of initialized agent, or None
|
|
832
|
-
"""
|
|
833
|
-
from AutoGLM_GUI.device_manager import DeviceManager
|
|
834
|
-
|
|
835
|
-
with self._manager_lock:
|
|
836
|
-
# Get device by serial from DeviceManager
|
|
837
|
-
device_manager = DeviceManager.get_instance()
|
|
838
|
-
device = device_manager._devices.get(serial)
|
|
839
|
-
|
|
840
|
-
if not device:
|
|
841
|
-
return None
|
|
842
|
-
|
|
843
|
-
# Check all connections for initialized agents
|
|
844
|
-
for conn in device.connections:
|
|
845
|
-
if conn.device_id in self._agents:
|
|
846
|
-
return conn.device_id
|
|
847
|
-
|
|
848
|
-
return None
|
|
849
|
-
|
|
850
502
|
# ==================== Introspection ====================
|
|
851
503
|
|
|
852
504
|
def list_agents(self) -> list[str]:
|
|
@@ -859,6 +511,16 @@ class PhoneAgentManager:
|
|
|
859
511
|
with self._manager_lock:
|
|
860
512
|
return self._metadata.get(device_id)
|
|
861
513
|
|
|
514
|
+
def register_abort_handler(
|
|
515
|
+
self, device_id: str, abort_handler: threading.Event | Callable[[], None]
|
|
516
|
+
) -> None:
|
|
517
|
+
with self._streaming_contexts_lock:
|
|
518
|
+
self._abort_events[device_id] = abort_handler
|
|
519
|
+
|
|
520
|
+
def unregister_abort_handler(self, device_id: str) -> None:
|
|
521
|
+
with self._streaming_contexts_lock:
|
|
522
|
+
self._abort_events.pop(device_id, None)
|
|
523
|
+
|
|
862
524
|
def abort_streaming_chat(self, device_id: str) -> bool:
|
|
863
525
|
"""
|
|
864
526
|
中止正在进行的流式对话.
|
|
@@ -872,7 +534,11 @@ class PhoneAgentManager:
|
|
|
872
534
|
with self._streaming_contexts_lock:
|
|
873
535
|
if device_id in self._abort_events:
|
|
874
536
|
logger.info(f"Aborting streaming chat for device {device_id}")
|
|
875
|
-
self._abort_events[device_id]
|
|
537
|
+
handler = self._abort_events[device_id]
|
|
538
|
+
if isinstance(handler, threading.Event):
|
|
539
|
+
handler.set()
|
|
540
|
+
elif callable(handler):
|
|
541
|
+
handler()
|
|
876
542
|
return True
|
|
877
543
|
else:
|
|
878
544
|
logger.warning(f"No active streaming chat for device {device_id}")
|
AutoGLM_GUI/platform_utils.py
CHANGED
|
@@ -63,3 +63,29 @@ async def spawn_process(
|
|
|
63
63
|
return subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
|
|
64
64
|
|
|
65
65
|
return await asyncio.create_subprocess_exec(*cmd, stdout=stdout, stderr=stderr)
|
|
66
|
+
|
|
67
|
+
|
|
68
|
+
def build_adb_command(device_id: str | None = None, adb_path: str = "adb") -> list[str]:
|
|
69
|
+
"""Build ADB command prefix with optional device specifier.
|
|
70
|
+
|
|
71
|
+
This centralizes the logic for constructing ADB commands across all modules.
|
|
72
|
+
|
|
73
|
+
Args:
|
|
74
|
+
device_id: Optional ADB device serial (e.g., "192.168.1.100:5555" or USB serial)
|
|
75
|
+
adb_path: Path to ADB executable (default: "adb")
|
|
76
|
+
|
|
77
|
+
Returns:
|
|
78
|
+
List of command parts to use with subprocess (e.g., ["adb", "-s", "device_id"])
|
|
79
|
+
|
|
80
|
+
Examples:
|
|
81
|
+
>>> build_adb_command()
|
|
82
|
+
['adb']
|
|
83
|
+
>>> build_adb_command(device_id="192.168.1.100:5555")
|
|
84
|
+
['adb', '-s', '192.168.1.100:5555']
|
|
85
|
+
>>> build_adb_command(device_id="emulator-5554", adb_path="/usr/local/bin/adb")
|
|
86
|
+
['/usr/local/bin/adb', '-s', 'emulator-5554']
|
|
87
|
+
"""
|
|
88
|
+
cmd = [adb_path]
|
|
89
|
+
if device_id:
|
|
90
|
+
cmd.extend(["-s", device_id])
|
|
91
|
+
return cmd
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
from AutoGLM_GUI.agents.glm import SYSTEM_PROMPT_EN, SYSTEM_PROMPT_ZH
|
|
2
|
+
from AutoGLM_GUI.i18n import get_message, get_messages
|
|
3
|
+
|
|
4
|
+
|
|
5
|
+
def get_system_prompt(lang: str = "cn") -> str:
|
|
6
|
+
if lang == "en":
|
|
7
|
+
return SYSTEM_PROMPT_EN
|
|
8
|
+
return SYSTEM_PROMPT_ZH
|
|
9
|
+
|
|
10
|
+
|
|
11
|
+
__all__ = [
|
|
12
|
+
"get_system_prompt",
|
|
13
|
+
"get_messages",
|
|
14
|
+
"get_message",
|
|
15
|
+
]
|