autoglm-gui 1.1.0__py3-none-any.whl → 1.2.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/adb_plus/__init__.py +5 -1
- AutoGLM_GUI/adb_plus/serial.py +61 -2
- AutoGLM_GUI/adb_plus/version.py +81 -0
- AutoGLM_GUI/api/__init__.py +8 -1
- AutoGLM_GUI/api/agents.py +329 -94
- AutoGLM_GUI/api/devices.py +145 -164
- AutoGLM_GUI/api/workflows.py +70 -0
- AutoGLM_GUI/device_manager.py +760 -0
- AutoGLM_GUI/exceptions.py +18 -0
- AutoGLM_GUI/phone_agent_manager.py +549 -0
- AutoGLM_GUI/phone_agent_patches.py +146 -0
- AutoGLM_GUI/schemas.py +310 -2
- AutoGLM_GUI/state.py +21 -0
- AutoGLM_GUI/static/assets/{about-Crpy4Xue.js → about-BtBH1xKN.js} +1 -1
- AutoGLM_GUI/static/assets/chat-DPzFNNGu.js +124 -0
- AutoGLM_GUI/static/assets/dialog-Dwuk2Hgl.js +45 -0
- AutoGLM_GUI/static/assets/index-B_AaKuOT.js +1 -0
- AutoGLM_GUI/static/assets/index-BjYIY--m.css +1 -0
- AutoGLM_GUI/static/assets/index-CvQkCi2d.js +11 -0
- AutoGLM_GUI/static/assets/logo-Cyfm06Ym.png +0 -0
- AutoGLM_GUI/static/assets/workflows-xX_QH-wI.js +1 -0
- AutoGLM_GUI/static/favicon.ico +0 -0
- AutoGLM_GUI/static/index.html +9 -2
- AutoGLM_GUI/static/logo-192.png +0 -0
- AutoGLM_GUI/static/logo-512.png +0 -0
- AutoGLM_GUI/workflow_manager.py +181 -0
- {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.1.dist-info}/METADATA +51 -6
- {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.1.dist-info}/RECORD +31 -19
- AutoGLM_GUI/static/assets/chat-DGFuSj6_.js +0 -149
- AutoGLM_GUI/static/assets/index-C1k5Ch1V.js +0 -10
- AutoGLM_GUI/static/assets/index-COYnSjzf.js +0 -1
- AutoGLM_GUI/static/assets/index-QX6oy21q.css +0 -1
- {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.1.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.1.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.1.0.dist-info → autoglm_gui-1.2.1.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/exceptions.py
CHANGED
|
@@ -5,3 +5,21 @@ class DeviceNotAvailableError(Exception):
|
|
|
5
5
|
"""Raised when device is not available (disconnected/offline)."""
|
|
6
6
|
|
|
7
7
|
pass
|
|
8
|
+
|
|
9
|
+
|
|
10
|
+
class AgentNotInitializedError(Exception):
|
|
11
|
+
"""Raised when attempting to access uninitialized agent."""
|
|
12
|
+
|
|
13
|
+
pass
|
|
14
|
+
|
|
15
|
+
|
|
16
|
+
class DeviceBusyError(Exception):
|
|
17
|
+
"""Raised when device is currently processing a request."""
|
|
18
|
+
|
|
19
|
+
pass
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
class AgentInitializationError(Exception):
|
|
23
|
+
"""Raised when agent initialization fails."""
|
|
24
|
+
|
|
25
|
+
pass
|
|
@@ -0,0 +1,549 @@
|
|
|
1
|
+
"""PhoneAgent lifecycle and concurrency manager (singleton)."""
|
|
2
|
+
|
|
3
|
+
from __future__ import annotations
|
|
4
|
+
|
|
5
|
+
import threading
|
|
6
|
+
import time
|
|
7
|
+
from contextlib import contextmanager
|
|
8
|
+
from dataclasses import dataclass
|
|
9
|
+
from enum import Enum
|
|
10
|
+
from typing import TYPE_CHECKING, Callable, Optional
|
|
11
|
+
|
|
12
|
+
from AutoGLM_GUI.exceptions import (
|
|
13
|
+
AgentInitializationError,
|
|
14
|
+
AgentNotInitializedError,
|
|
15
|
+
DeviceBusyError,
|
|
16
|
+
)
|
|
17
|
+
from AutoGLM_GUI.logger import logger
|
|
18
|
+
|
|
19
|
+
if TYPE_CHECKING:
|
|
20
|
+
from phone_agent import PhoneAgent
|
|
21
|
+
from phone_agent.agent import AgentConfig
|
|
22
|
+
from phone_agent.model import ModelConfig
|
|
23
|
+
|
|
24
|
+
|
|
25
|
+
class AgentState(str, Enum):
|
|
26
|
+
"""Agent runtime state."""
|
|
27
|
+
|
|
28
|
+
IDLE = "idle" # Agent initialized, not processing
|
|
29
|
+
BUSY = "busy" # Agent processing a request
|
|
30
|
+
ERROR = "error" # Agent encountered error
|
|
31
|
+
INITIALIZING = "initializing" # Agent being created
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
@dataclass
|
|
35
|
+
class AgentMetadata:
|
|
36
|
+
"""Metadata for a PhoneAgent instance."""
|
|
37
|
+
|
|
38
|
+
device_id: str
|
|
39
|
+
state: AgentState
|
|
40
|
+
model_config: ModelConfig
|
|
41
|
+
agent_config: AgentConfig
|
|
42
|
+
created_at: float
|
|
43
|
+
last_used: float
|
|
44
|
+
error_message: Optional[str] = None
|
|
45
|
+
|
|
46
|
+
|
|
47
|
+
class PhoneAgentManager:
|
|
48
|
+
"""
|
|
49
|
+
Singleton manager for PhoneAgent lifecycle and concurrency control.
|
|
50
|
+
|
|
51
|
+
Features:
|
|
52
|
+
- Thread-safe agent creation/destruction
|
|
53
|
+
- Per-device locking (device-level concurrency control)
|
|
54
|
+
- State management (IDLE/BUSY/ERROR/INITIALIZING)
|
|
55
|
+
- Integration with DeviceManager
|
|
56
|
+
- Configuration hot-reload support
|
|
57
|
+
- Connection switching detection
|
|
58
|
+
|
|
59
|
+
Design Principles:
|
|
60
|
+
- Uses state.agents and state.agent_configs as storage (backward compatible)
|
|
61
|
+
- Double-checked locking for device locks
|
|
62
|
+
- RLock for manager-level operations (supports reentrant calls)
|
|
63
|
+
- Context managers for automatic lock release
|
|
64
|
+
|
|
65
|
+
Example:
|
|
66
|
+
>>> manager = PhoneAgentManager.get_instance()
|
|
67
|
+
>>>
|
|
68
|
+
>>> # Initialize agent
|
|
69
|
+
>>> agent = manager.initialize_agent(device_id, model_config, agent_config)
|
|
70
|
+
>>>
|
|
71
|
+
>>> # Use agent with automatic locking
|
|
72
|
+
>>> with manager.use_agent(device_id) as agent:
|
|
73
|
+
>>> result = agent.run("Open WeChat")
|
|
74
|
+
"""
|
|
75
|
+
|
|
76
|
+
_instance: Optional[PhoneAgentManager] = None
|
|
77
|
+
_instance_lock = threading.Lock()
|
|
78
|
+
|
|
79
|
+
def __init__(self):
|
|
80
|
+
"""Private constructor. Use get_instance() instead."""
|
|
81
|
+
# Manager-level lock (protects internal state)
|
|
82
|
+
self._manager_lock = threading.RLock()
|
|
83
|
+
|
|
84
|
+
# Device-level locks (per-device concurrency control)
|
|
85
|
+
self._device_locks: dict[str, threading.Lock] = {}
|
|
86
|
+
self._device_locks_lock = threading.Lock()
|
|
87
|
+
|
|
88
|
+
# Agent metadata (indexed by device_id)
|
|
89
|
+
self._metadata: dict[str, AgentMetadata] = {}
|
|
90
|
+
|
|
91
|
+
# State tracking
|
|
92
|
+
self._states: dict[str, AgentState] = {}
|
|
93
|
+
|
|
94
|
+
@classmethod
|
|
95
|
+
def get_instance(cls) -> PhoneAgentManager:
|
|
96
|
+
"""Get singleton instance (thread-safe, double-checked locking)."""
|
|
97
|
+
if cls._instance is None:
|
|
98
|
+
with cls._instance_lock:
|
|
99
|
+
if cls._instance is None:
|
|
100
|
+
cls._instance = cls()
|
|
101
|
+
logger.info("PhoneAgentManager singleton created")
|
|
102
|
+
return cls._instance
|
|
103
|
+
|
|
104
|
+
# ==================== Agent Lifecycle ====================
|
|
105
|
+
|
|
106
|
+
def initialize_agent(
|
|
107
|
+
self,
|
|
108
|
+
device_id: str,
|
|
109
|
+
model_config: ModelConfig,
|
|
110
|
+
agent_config: AgentConfig,
|
|
111
|
+
takeover_callback: Optional[Callable] = None,
|
|
112
|
+
force: bool = False,
|
|
113
|
+
) -> PhoneAgent:
|
|
114
|
+
"""
|
|
115
|
+
Initialize PhoneAgent for a device (thread-safe, idempotent).
|
|
116
|
+
|
|
117
|
+
Args:
|
|
118
|
+
device_id: Device identifier (USB serial / IP:port)
|
|
119
|
+
model_config: Model configuration
|
|
120
|
+
agent_config: Agent configuration
|
|
121
|
+
takeover_callback: Optional takeover callback
|
|
122
|
+
force: Force re-initialization even if agent exists
|
|
123
|
+
|
|
124
|
+
Returns:
|
|
125
|
+
PhoneAgent: Initialized agent instance
|
|
126
|
+
|
|
127
|
+
Raises:
|
|
128
|
+
AgentInitializationError: If initialization fails
|
|
129
|
+
DeviceBusyError: If device is currently processing
|
|
130
|
+
|
|
131
|
+
Transactional Guarantee:
|
|
132
|
+
- On failure, state is rolled back
|
|
133
|
+
- state.agents and state.agent_configs remain consistent
|
|
134
|
+
"""
|
|
135
|
+
from phone_agent import PhoneAgent
|
|
136
|
+
|
|
137
|
+
from AutoGLM_GUI.state import agent_configs, agents, non_blocking_takeover
|
|
138
|
+
|
|
139
|
+
with self._manager_lock:
|
|
140
|
+
# Check if already initialized
|
|
141
|
+
if device_id in agents and not force:
|
|
142
|
+
logger.debug(f"Agent already initialized for {device_id}")
|
|
143
|
+
return agents[device_id]
|
|
144
|
+
|
|
145
|
+
# Check device availability (non-blocking check)
|
|
146
|
+
device_lock = self._get_device_lock(device_id)
|
|
147
|
+
if device_lock.locked():
|
|
148
|
+
raise DeviceBusyError(
|
|
149
|
+
f"Device {device_id} is currently processing a request"
|
|
150
|
+
)
|
|
151
|
+
|
|
152
|
+
# Set initializing state
|
|
153
|
+
self._states[device_id] = AgentState.INITIALIZING
|
|
154
|
+
|
|
155
|
+
try:
|
|
156
|
+
# Create agent
|
|
157
|
+
agent = PhoneAgent(
|
|
158
|
+
model_config=model_config,
|
|
159
|
+
agent_config=agent_config,
|
|
160
|
+
takeover_callback=takeover_callback or non_blocking_takeover,
|
|
161
|
+
)
|
|
162
|
+
|
|
163
|
+
# Store in state (transactional)
|
|
164
|
+
agents[device_id] = agent
|
|
165
|
+
agent_configs[device_id] = (model_config, agent_config)
|
|
166
|
+
|
|
167
|
+
# Update metadata
|
|
168
|
+
self._metadata[device_id] = AgentMetadata(
|
|
169
|
+
device_id=device_id,
|
|
170
|
+
state=AgentState.IDLE,
|
|
171
|
+
model_config=model_config,
|
|
172
|
+
agent_config=agent_config,
|
|
173
|
+
created_at=time.time(),
|
|
174
|
+
last_used=time.time(),
|
|
175
|
+
)
|
|
176
|
+
self._states[device_id] = AgentState.IDLE
|
|
177
|
+
|
|
178
|
+
logger.info(f"Agent initialized for device {device_id}")
|
|
179
|
+
return agent
|
|
180
|
+
|
|
181
|
+
except Exception as e:
|
|
182
|
+
# Rollback on error
|
|
183
|
+
agents.pop(device_id, None)
|
|
184
|
+
agent_configs.pop(device_id, None)
|
|
185
|
+
self._metadata.pop(device_id, None)
|
|
186
|
+
self._states[device_id] = AgentState.ERROR
|
|
187
|
+
|
|
188
|
+
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
189
|
+
raise AgentInitializationError(
|
|
190
|
+
f"Failed to initialize agent: {str(e)}"
|
|
191
|
+
) from e
|
|
192
|
+
|
|
193
|
+
def get_agent(self, device_id: str) -> PhoneAgent:
|
|
194
|
+
"""
|
|
195
|
+
Get initialized agent for a device.
|
|
196
|
+
|
|
197
|
+
Args:
|
|
198
|
+
device_id: Device identifier
|
|
199
|
+
|
|
200
|
+
Returns:
|
|
201
|
+
PhoneAgent: Agent instance
|
|
202
|
+
|
|
203
|
+
Raises:
|
|
204
|
+
AgentNotInitializedError: If agent not initialized
|
|
205
|
+
"""
|
|
206
|
+
from AutoGLM_GUI.state import agents
|
|
207
|
+
|
|
208
|
+
with self._manager_lock:
|
|
209
|
+
if device_id not in agents:
|
|
210
|
+
raise AgentNotInitializedError(
|
|
211
|
+
f"Agent not initialized for device {device_id}. "
|
|
212
|
+
f"Call /api/init first."
|
|
213
|
+
)
|
|
214
|
+
return agents[device_id]
|
|
215
|
+
|
|
216
|
+
def get_agent_safe(self, device_id: str) -> Optional[PhoneAgent]:
|
|
217
|
+
"""
|
|
218
|
+
Get initialized agent for a device (safe version, no exception).
|
|
219
|
+
|
|
220
|
+
Args:
|
|
221
|
+
device_id: Device identifier
|
|
222
|
+
|
|
223
|
+
Returns:
|
|
224
|
+
PhoneAgent or None: Agent instance or None if not initialized
|
|
225
|
+
"""
|
|
226
|
+
from AutoGLM_GUI.state import agents
|
|
227
|
+
|
|
228
|
+
with self._manager_lock:
|
|
229
|
+
return agents.get(device_id)
|
|
230
|
+
|
|
231
|
+
def reset_agent(self, device_id: str) -> None:
|
|
232
|
+
"""
|
|
233
|
+
Reset agent state and rebuild from cached config.
|
|
234
|
+
|
|
235
|
+
Args:
|
|
236
|
+
device_id: Device identifier
|
|
237
|
+
|
|
238
|
+
Raises:
|
|
239
|
+
AgentNotInitializedError: If agent not initialized
|
|
240
|
+
"""
|
|
241
|
+
from phone_agent import PhoneAgent
|
|
242
|
+
|
|
243
|
+
from AutoGLM_GUI.state import agent_configs, agents, non_blocking_takeover
|
|
244
|
+
|
|
245
|
+
with self._manager_lock:
|
|
246
|
+
if device_id not in agents:
|
|
247
|
+
raise AgentNotInitializedError(
|
|
248
|
+
f"Agent not initialized for device {device_id}"
|
|
249
|
+
)
|
|
250
|
+
|
|
251
|
+
# Get cached config
|
|
252
|
+
if device_id not in agent_configs:
|
|
253
|
+
logger.warning(
|
|
254
|
+
f"No cached config for {device_id}, only resetting agent state"
|
|
255
|
+
)
|
|
256
|
+
agents[device_id].reset()
|
|
257
|
+
return
|
|
258
|
+
|
|
259
|
+
# Rebuild agent from cached config
|
|
260
|
+
model_config, agent_config = agent_configs[device_id]
|
|
261
|
+
|
|
262
|
+
agents[device_id] = PhoneAgent(
|
|
263
|
+
model_config=model_config,
|
|
264
|
+
agent_config=agent_config,
|
|
265
|
+
takeover_callback=non_blocking_takeover,
|
|
266
|
+
)
|
|
267
|
+
|
|
268
|
+
# Update metadata
|
|
269
|
+
if device_id in self._metadata:
|
|
270
|
+
self._metadata[device_id].last_used = time.time()
|
|
271
|
+
self._metadata[device_id].error_message = None
|
|
272
|
+
|
|
273
|
+
self._states[device_id] = AgentState.IDLE
|
|
274
|
+
|
|
275
|
+
logger.info(f"Agent reset for device {device_id}")
|
|
276
|
+
|
|
277
|
+
def destroy_agent(self, device_id: str) -> None:
|
|
278
|
+
"""
|
|
279
|
+
Destroy agent and clean up resources.
|
|
280
|
+
|
|
281
|
+
Args:
|
|
282
|
+
device_id: Device identifier
|
|
283
|
+
"""
|
|
284
|
+
from AutoGLM_GUI.state import agent_configs, agents
|
|
285
|
+
|
|
286
|
+
with self._manager_lock:
|
|
287
|
+
# Remove agent
|
|
288
|
+
agent = agents.pop(device_id, None)
|
|
289
|
+
if agent:
|
|
290
|
+
try:
|
|
291
|
+
agent.reset() # Clean up agent state
|
|
292
|
+
except Exception as e:
|
|
293
|
+
logger.warning(f"Error resetting agent during destroy: {e}")
|
|
294
|
+
|
|
295
|
+
# Remove config
|
|
296
|
+
agent_configs.pop(device_id, None)
|
|
297
|
+
|
|
298
|
+
# Remove metadata
|
|
299
|
+
self._metadata.pop(device_id, None)
|
|
300
|
+
self._states.pop(device_id, None)
|
|
301
|
+
|
|
302
|
+
logger.info(f"Agent destroyed for device {device_id}")
|
|
303
|
+
|
|
304
|
+
def is_initialized(self, device_id: str) -> bool:
|
|
305
|
+
"""Check if agent is initialized for device."""
|
|
306
|
+
from AutoGLM_GUI.state import agents
|
|
307
|
+
|
|
308
|
+
with self._manager_lock:
|
|
309
|
+
return device_id in agents
|
|
310
|
+
|
|
311
|
+
# ==================== Concurrency Control ====================
|
|
312
|
+
|
|
313
|
+
def _get_device_lock(self, device_id: str) -> threading.Lock:
|
|
314
|
+
"""
|
|
315
|
+
Get or create device lock (double-checked locking pattern).
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
device_id: Device identifier
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
threading.Lock: Device-specific lock
|
|
322
|
+
"""
|
|
323
|
+
# Fast path: lock already exists
|
|
324
|
+
if device_id in self._device_locks:
|
|
325
|
+
return self._device_locks[device_id]
|
|
326
|
+
|
|
327
|
+
# Slow path: create lock
|
|
328
|
+
with self._device_locks_lock:
|
|
329
|
+
# Double-check inside lock
|
|
330
|
+
if device_id not in self._device_locks:
|
|
331
|
+
self._device_locks[device_id] = threading.Lock()
|
|
332
|
+
return self._device_locks[device_id]
|
|
333
|
+
|
|
334
|
+
def acquire_device(
|
|
335
|
+
self,
|
|
336
|
+
device_id: str,
|
|
337
|
+
timeout: Optional[float] = None,
|
|
338
|
+
raise_on_timeout: bool = True,
|
|
339
|
+
) -> bool:
|
|
340
|
+
"""
|
|
341
|
+
Acquire device lock for exclusive access.
|
|
342
|
+
|
|
343
|
+
Args:
|
|
344
|
+
device_id: Device identifier
|
|
345
|
+
timeout: Lock acquisition timeout (None = blocking, 0 = non-blocking)
|
|
346
|
+
raise_on_timeout: Raise DeviceBusyError on timeout
|
|
347
|
+
|
|
348
|
+
Returns:
|
|
349
|
+
bool: True if acquired, False if timeout (when raise_on_timeout=False)
|
|
350
|
+
|
|
351
|
+
Raises:
|
|
352
|
+
DeviceBusyError: If timeout and raise_on_timeout=True
|
|
353
|
+
AgentNotInitializedError: If agent not initialized
|
|
354
|
+
"""
|
|
355
|
+
# Verify agent exists
|
|
356
|
+
if not self.is_initialized(device_id):
|
|
357
|
+
raise AgentNotInitializedError(
|
|
358
|
+
f"Agent not initialized for device {device_id}"
|
|
359
|
+
)
|
|
360
|
+
|
|
361
|
+
lock = self._get_device_lock(device_id)
|
|
362
|
+
|
|
363
|
+
# Try to acquire with timeout
|
|
364
|
+
if timeout is None:
|
|
365
|
+
# Blocking mode
|
|
366
|
+
acquired = lock.acquire(blocking=True)
|
|
367
|
+
elif timeout == 0:
|
|
368
|
+
# Non-blocking mode
|
|
369
|
+
acquired = lock.acquire(blocking=False)
|
|
370
|
+
else:
|
|
371
|
+
# Timeout mode
|
|
372
|
+
acquired = lock.acquire(blocking=True, timeout=timeout)
|
|
373
|
+
|
|
374
|
+
if acquired:
|
|
375
|
+
# Update state
|
|
376
|
+
with self._manager_lock:
|
|
377
|
+
self._states[device_id] = AgentState.BUSY
|
|
378
|
+
if device_id in self._metadata:
|
|
379
|
+
self._metadata[device_id].last_used = time.time()
|
|
380
|
+
|
|
381
|
+
logger.debug(f"Device lock acquired for {device_id}")
|
|
382
|
+
return True
|
|
383
|
+
else:
|
|
384
|
+
if raise_on_timeout:
|
|
385
|
+
raise DeviceBusyError(
|
|
386
|
+
f"Device {device_id} is busy, could not acquire lock"
|
|
387
|
+
+ (f" within {timeout}s" if timeout else "")
|
|
388
|
+
)
|
|
389
|
+
return False
|
|
390
|
+
|
|
391
|
+
def release_device(self, device_id: str) -> None:
|
|
392
|
+
"""
|
|
393
|
+
Release device lock.
|
|
394
|
+
|
|
395
|
+
Args:
|
|
396
|
+
device_id: Device identifier
|
|
397
|
+
"""
|
|
398
|
+
lock = self._get_device_lock(device_id)
|
|
399
|
+
|
|
400
|
+
if lock.locked():
|
|
401
|
+
lock.release()
|
|
402
|
+
|
|
403
|
+
# Update state
|
|
404
|
+
with self._manager_lock:
|
|
405
|
+
self._states[device_id] = AgentState.IDLE
|
|
406
|
+
|
|
407
|
+
logger.debug(f"Device lock released for {device_id}")
|
|
408
|
+
|
|
409
|
+
@contextmanager
|
|
410
|
+
def use_agent(self, device_id: str, timeout: Optional[float] = None):
|
|
411
|
+
"""
|
|
412
|
+
Context manager for automatic lock acquisition/release.
|
|
413
|
+
|
|
414
|
+
Args:
|
|
415
|
+
device_id: Device identifier
|
|
416
|
+
timeout: Lock acquisition timeout
|
|
417
|
+
|
|
418
|
+
Yields:
|
|
419
|
+
PhoneAgent: Agent instance
|
|
420
|
+
|
|
421
|
+
Raises:
|
|
422
|
+
DeviceBusyError: If device is busy
|
|
423
|
+
AgentNotInitializedError: If agent not initialized
|
|
424
|
+
|
|
425
|
+
Example:
|
|
426
|
+
>>> manager = PhoneAgentManager.get_instance()
|
|
427
|
+
>>> with manager.use_agent("device_123") as agent:
|
|
428
|
+
>>> result = agent.run("Open WeChat")
|
|
429
|
+
"""
|
|
430
|
+
acquired = False
|
|
431
|
+
try:
|
|
432
|
+
acquired = self.acquire_device(device_id, timeout, raise_on_timeout=True)
|
|
433
|
+
agent = self.get_agent(device_id)
|
|
434
|
+
yield agent
|
|
435
|
+
except Exception as exc:
|
|
436
|
+
# Handle errors
|
|
437
|
+
self.set_error_state(device_id, str(exc))
|
|
438
|
+
raise
|
|
439
|
+
finally:
|
|
440
|
+
if acquired:
|
|
441
|
+
self.release_device(device_id)
|
|
442
|
+
|
|
443
|
+
# ==================== State Management ====================
|
|
444
|
+
|
|
445
|
+
def get_state(self, device_id: str) -> AgentState:
|
|
446
|
+
"""Get current agent state."""
|
|
447
|
+
with self._manager_lock:
|
|
448
|
+
return self._states.get(device_id, AgentState.ERROR)
|
|
449
|
+
|
|
450
|
+
def set_error_state(self, device_id: str, error_message: str) -> None:
|
|
451
|
+
"""Mark agent as errored."""
|
|
452
|
+
with self._manager_lock:
|
|
453
|
+
self._states[device_id] = AgentState.ERROR
|
|
454
|
+
if device_id in self._metadata:
|
|
455
|
+
self._metadata[device_id].error_message = error_message
|
|
456
|
+
|
|
457
|
+
logger.error(f"Agent error for {device_id}: {error_message}")
|
|
458
|
+
|
|
459
|
+
# ==================== Configuration Management ====================
|
|
460
|
+
|
|
461
|
+
def get_config(self, device_id: str) -> tuple[ModelConfig, AgentConfig]:
|
|
462
|
+
"""Get cached configuration for device."""
|
|
463
|
+
from AutoGLM_GUI.state import agent_configs
|
|
464
|
+
|
|
465
|
+
with self._manager_lock:
|
|
466
|
+
if device_id not in agent_configs:
|
|
467
|
+
raise AgentNotInitializedError(
|
|
468
|
+
f"No configuration found for device {device_id}"
|
|
469
|
+
)
|
|
470
|
+
return agent_configs[device_id]
|
|
471
|
+
|
|
472
|
+
def update_config(
|
|
473
|
+
self,
|
|
474
|
+
device_id: str,
|
|
475
|
+
model_config: Optional[ModelConfig] = None,
|
|
476
|
+
agent_config: Optional[AgentConfig] = None,
|
|
477
|
+
) -> None:
|
|
478
|
+
"""
|
|
479
|
+
Update agent configuration (requires reinitialization).
|
|
480
|
+
|
|
481
|
+
Args:
|
|
482
|
+
device_id: Device identifier
|
|
483
|
+
model_config: New model config (None = keep existing)
|
|
484
|
+
agent_config: New agent config (None = keep existing)
|
|
485
|
+
"""
|
|
486
|
+
from AutoGLM_GUI.state import agent_configs
|
|
487
|
+
|
|
488
|
+
with self._manager_lock:
|
|
489
|
+
if device_id not in agent_configs:
|
|
490
|
+
raise AgentNotInitializedError(
|
|
491
|
+
f"No configuration found for device {device_id}"
|
|
492
|
+
)
|
|
493
|
+
|
|
494
|
+
old_model_config, old_agent_config = agent_configs[device_id]
|
|
495
|
+
|
|
496
|
+
new_model_config = model_config or old_model_config
|
|
497
|
+
new_agent_config = agent_config or old_agent_config
|
|
498
|
+
|
|
499
|
+
# Reinitialize with new config
|
|
500
|
+
self.initialize_agent(
|
|
501
|
+
device_id,
|
|
502
|
+
new_model_config,
|
|
503
|
+
new_agent_config,
|
|
504
|
+
force=True,
|
|
505
|
+
)
|
|
506
|
+
|
|
507
|
+
# ==================== DeviceManager Integration ====================
|
|
508
|
+
|
|
509
|
+
def find_agent_by_serial(self, serial: str) -> Optional[str]:
|
|
510
|
+
"""
|
|
511
|
+
Find agent device_id by hardware serial (connection switching support).
|
|
512
|
+
|
|
513
|
+
Args:
|
|
514
|
+
serial: Hardware serial number
|
|
515
|
+
|
|
516
|
+
Returns:
|
|
517
|
+
Optional[str]: device_id of initialized agent, or None
|
|
518
|
+
"""
|
|
519
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
520
|
+
from AutoGLM_GUI.state import agents
|
|
521
|
+
|
|
522
|
+
with self._manager_lock:
|
|
523
|
+
# Get device by serial from DeviceManager
|
|
524
|
+
device_manager = DeviceManager.get_instance()
|
|
525
|
+
device = device_manager._devices.get(serial)
|
|
526
|
+
|
|
527
|
+
if not device:
|
|
528
|
+
return None
|
|
529
|
+
|
|
530
|
+
# Check all connections for initialized agents
|
|
531
|
+
for conn in device.connections:
|
|
532
|
+
if conn.device_id in agents:
|
|
533
|
+
return conn.device_id
|
|
534
|
+
|
|
535
|
+
return None
|
|
536
|
+
|
|
537
|
+
# ==================== Introspection ====================
|
|
538
|
+
|
|
539
|
+
def list_agents(self) -> list[str]:
|
|
540
|
+
"""Get list of all initialized device IDs."""
|
|
541
|
+
from AutoGLM_GUI.state import agents
|
|
542
|
+
|
|
543
|
+
with self._manager_lock:
|
|
544
|
+
return list(agents.keys())
|
|
545
|
+
|
|
546
|
+
def get_metadata(self, device_id: str) -> Optional[AgentMetadata]:
|
|
547
|
+
"""Get agent metadata."""
|
|
548
|
+
with self._manager_lock:
|
|
549
|
+
return self._metadata.get(device_id)
|