autoglm-gui 1.4.0__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 -8
- 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/qr_pair.py +8 -8
- 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 +51 -0
- AutoGLM_GUI/agents/events.py +19 -0
- AutoGLM_GUI/agents/factory.py +153 -0
- 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 +27 -0
- AutoGLM_GUI/agents/stream_runner.py +188 -0
- AutoGLM_GUI/api/__init__.py +71 -11
- AutoGLM_GUI/api/agents.py +190 -229
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +112 -28
- AutoGLM_GUI/api/health.py +13 -0
- AutoGLM_GUI/api/history.py +78 -0
- AutoGLM_GUI/api/layered_agent.py +306 -181
- AutoGLM_GUI/api/mcp.py +11 -10
- AutoGLM_GUI/api/media.py +64 -1
- AutoGLM_GUI/api/scheduled_tasks.py +98 -0
- AutoGLM_GUI/api/version.py +23 -10
- AutoGLM_GUI/api/workflows.py +2 -1
- AutoGLM_GUI/config.py +72 -14
- AutoGLM_GUI/config_manager.py +98 -27
- AutoGLM_GUI/device_adapter.py +263 -0
- AutoGLM_GUI/device_manager.py +248 -29
- AutoGLM_GUI/device_protocol.py +266 -0
- AutoGLM_GUI/devices/__init__.py +49 -0
- AutoGLM_GUI/devices/adb_device.py +200 -0
- AutoGLM_GUI/devices/mock_device.py +185 -0
- AutoGLM_GUI/devices/remote_device.py +177 -0
- AutoGLM_GUI/exceptions.py +3 -3
- AutoGLM_GUI/history_manager.py +164 -0
- AutoGLM_GUI/i18n.py +81 -0
- AutoGLM_GUI/metrics.py +13 -20
- 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 +118 -367
- AutoGLM_GUI/platform_utils.py +31 -2
- 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 +272 -63
- AutoGLM_GUI/scrcpy_stream.py +159 -37
- AutoGLM_GUI/server.py +3 -1
- AutoGLM_GUI/socketio_server.py +114 -29
- AutoGLM_GUI/state.py +10 -30
- AutoGLM_GUI/static/assets/{about-DeclntHg.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-zQ4KKDHt.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 +142 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +178 -92
- autoglm_gui-1.5.0.dist-info/RECORD +157 -0
- 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/api/dual_model.py +0 -311
- 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 -146
- AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
- AutoGLM_GUI/static/assets/dialog-BfdcBs1x.js +0 -45
- AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
- AutoGLM_GUI/static/assets/index-DHF1NZh0.js +0 -12
- AutoGLM_GUI/static/assets/workflows-xiplap-r.js +0 -1
- autoglm_gui-1.4.0.dist-info/RECORD +0 -100
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.0.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,19 +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
|
+
from AutoGLM_GUI.agents.protocols import BaseAgent
|
|
13
|
+
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
12
14
|
from AutoGLM_GUI.exceptions import (
|
|
13
15
|
AgentInitializationError,
|
|
14
16
|
AgentNotInitializedError,
|
|
15
17
|
DeviceBusyError,
|
|
16
18
|
)
|
|
17
19
|
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
|
|
20
|
+
from AutoGLM_GUI.types import AgentSpecificConfig
|
|
23
21
|
|
|
24
22
|
|
|
25
23
|
class AgentState(str, Enum):
|
|
@@ -33,29 +31,28 @@ class AgentState(str, Enum):
|
|
|
33
31
|
|
|
34
32
|
@dataclass
|
|
35
33
|
class AgentMetadata:
|
|
36
|
-
"""Metadata for
|
|
34
|
+
"""Metadata for an agent instance."""
|
|
37
35
|
|
|
38
36
|
device_id: str
|
|
39
37
|
state: AgentState
|
|
40
38
|
model_config: ModelConfig
|
|
41
39
|
agent_config: AgentConfig
|
|
42
|
-
|
|
43
|
-
|
|
40
|
+
agent_type: str = "glm"
|
|
41
|
+
created_at: float = 0.0
|
|
42
|
+
last_used: float = 0.0
|
|
44
43
|
error_message: Optional[str] = None
|
|
45
44
|
|
|
46
45
|
|
|
47
46
|
@dataclass
|
|
48
47
|
class StreamingAgentContext:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
streaming_agent: "PhoneAgent"
|
|
52
|
-
original_agent: "PhoneAgent"
|
|
48
|
+
streaming_agent: BaseAgent
|
|
49
|
+
original_agent: BaseAgent
|
|
53
50
|
stop_event: threading.Event
|
|
54
51
|
|
|
55
52
|
|
|
56
53
|
class PhoneAgentManager:
|
|
57
54
|
"""
|
|
58
|
-
Singleton manager for
|
|
55
|
+
Singleton manager for agent lifecycle and concurrency control.
|
|
59
56
|
|
|
60
57
|
Features:
|
|
61
58
|
- Thread-safe agent creation/destruction
|
|
@@ -74,10 +71,7 @@ class PhoneAgentManager:
|
|
|
74
71
|
Example:
|
|
75
72
|
>>> manager = PhoneAgentManager.get_instance()
|
|
76
73
|
>>>
|
|
77
|
-
>>> #
|
|
78
|
-
>>> agent = manager.initialize_agent(device_id, model_config, agent_config)
|
|
79
|
-
>>>
|
|
80
|
-
>>> # Use agent with automatic locking
|
|
74
|
+
>>> # Use agent with automatic locking (auto-initializes if needed)
|
|
81
75
|
>>> with manager.use_agent(device_id) as agent:
|
|
82
76
|
>>> result = agent.run("Open WeChat")
|
|
83
77
|
"""
|
|
@@ -95,17 +89,18 @@ class PhoneAgentManager:
|
|
|
95
89
|
self._device_locks_lock = threading.Lock()
|
|
96
90
|
|
|
97
91
|
# Agent metadata (indexed by device_id)
|
|
92
|
+
# State is stored in AgentMetadata.state (single source of truth)
|
|
98
93
|
self._metadata: dict[str, AgentMetadata] = {}
|
|
99
94
|
|
|
100
|
-
# State tracking
|
|
101
|
-
self._states: dict[str, AgentState] = {}
|
|
102
|
-
|
|
103
95
|
# Streaming agent state (device_id -> StreamingAgentContext)
|
|
104
96
|
self._streaming_contexts: dict[str, StreamingAgentContext] = {}
|
|
105
97
|
self._streaming_contexts_lock = threading.Lock()
|
|
106
98
|
|
|
107
|
-
|
|
108
|
-
|
|
99
|
+
self._abort_events: dict[str, threading.Event | Callable[[], None]] = {}
|
|
100
|
+
|
|
101
|
+
# Agent storage (transition from global state to instance state)
|
|
102
|
+
self._agents: dict[str, BaseAgent] = {}
|
|
103
|
+
self._agent_configs: dict[str, tuple[ModelConfig, AgentConfig]] = {}
|
|
109
104
|
|
|
110
105
|
@classmethod
|
|
111
106
|
def get_instance(cls) -> PhoneAgentManager:
|
|
@@ -119,254 +114,105 @@ class PhoneAgentManager:
|
|
|
119
114
|
|
|
120
115
|
# ==================== Agent Lifecycle ====================
|
|
121
116
|
|
|
122
|
-
def
|
|
117
|
+
def initialize_agent_with_factory(
|
|
123
118
|
self,
|
|
124
119
|
device_id: str,
|
|
120
|
+
agent_type: str,
|
|
125
121
|
model_config: ModelConfig,
|
|
126
122
|
agent_config: AgentConfig,
|
|
123
|
+
agent_specific_config: AgentSpecificConfig,
|
|
127
124
|
takeover_callback: Optional[Callable] = None,
|
|
125
|
+
confirmation_callback: Optional[Callable] = None,
|
|
128
126
|
force: bool = False,
|
|
129
|
-
) ->
|
|
130
|
-
|
|
131
|
-
Initialize PhoneAgent for a device (thread-safe, idempotent).
|
|
132
|
-
|
|
133
|
-
Args:
|
|
134
|
-
device_id: Device identifier (USB serial / IP:port)
|
|
135
|
-
model_config: Model configuration
|
|
136
|
-
agent_config: Agent configuration
|
|
137
|
-
takeover_callback: Optional takeover callback
|
|
138
|
-
force: Force re-initialization even if agent exists
|
|
139
|
-
|
|
140
|
-
Returns:
|
|
141
|
-
PhoneAgent: Initialized agent instance
|
|
142
|
-
|
|
143
|
-
Raises:
|
|
144
|
-
AgentInitializationError: If initialization fails
|
|
145
|
-
DeviceBusyError: If device is currently processing
|
|
146
|
-
|
|
147
|
-
Transactional Guarantee:
|
|
148
|
-
- On failure, state is rolled back
|
|
149
|
-
- state.agents and state.agent_configs remain consistent
|
|
150
|
-
"""
|
|
151
|
-
from phone_agent import PhoneAgent
|
|
152
|
-
|
|
153
|
-
from AutoGLM_GUI.state import agent_configs, agents, non_blocking_takeover
|
|
127
|
+
) -> "BaseAgent":
|
|
128
|
+
from AutoGLM_GUI.agents import create_agent
|
|
154
129
|
|
|
155
130
|
with self._manager_lock:
|
|
156
|
-
|
|
157
|
-
if device_id in agents and not force:
|
|
131
|
+
if device_id in self._agents and not force:
|
|
158
132
|
logger.debug(f"Agent already initialized for {device_id}")
|
|
159
|
-
return
|
|
133
|
+
return self._agents[device_id]
|
|
160
134
|
|
|
161
|
-
# Check device availability (non-blocking check)
|
|
162
135
|
device_lock = self._get_device_lock(device_id)
|
|
163
136
|
if device_lock.locked():
|
|
164
137
|
raise DeviceBusyError(
|
|
165
138
|
f"Device {device_id} is currently processing a request"
|
|
166
139
|
)
|
|
167
140
|
|
|
168
|
-
|
|
169
|
-
|
|
141
|
+
self._metadata[device_id] = AgentMetadata(
|
|
142
|
+
device_id=device_id,
|
|
143
|
+
state=AgentState.INITIALIZING,
|
|
144
|
+
model_config=model_config,
|
|
145
|
+
agent_config=agent_config,
|
|
146
|
+
agent_type=agent_type,
|
|
147
|
+
created_at=time.time(),
|
|
148
|
+
last_used=time.time(),
|
|
149
|
+
)
|
|
170
150
|
|
|
171
151
|
try:
|
|
172
|
-
|
|
173
|
-
|
|
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
|
+
|
|
162
|
+
agent = create_agent(
|
|
163
|
+
agent_type=agent_type,
|
|
174
164
|
model_config=model_config,
|
|
175
165
|
agent_config=agent_config,
|
|
176
|
-
|
|
166
|
+
agent_specific_config=agent_specific_config,
|
|
167
|
+
device=device,
|
|
168
|
+
takeover_callback=takeover_callback,
|
|
169
|
+
confirmation_callback=confirmation_callback,
|
|
177
170
|
)
|
|
178
171
|
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
agent_configs[device_id] = (model_config, agent_config)
|
|
172
|
+
self._agents[device_id] = agent
|
|
173
|
+
self._agent_configs[device_id] = (model_config, agent_config)
|
|
182
174
|
|
|
183
|
-
|
|
184
|
-
self._metadata[device_id] = AgentMetadata(
|
|
185
|
-
device_id=device_id,
|
|
186
|
-
state=AgentState.IDLE,
|
|
187
|
-
model_config=model_config,
|
|
188
|
-
agent_config=agent_config,
|
|
189
|
-
created_at=time.time(),
|
|
190
|
-
last_used=time.time(),
|
|
191
|
-
)
|
|
192
|
-
self._states[device_id] = AgentState.IDLE
|
|
175
|
+
self._metadata[device_id].state = AgentState.IDLE
|
|
193
176
|
|
|
194
|
-
logger.info(
|
|
177
|
+
logger.info(
|
|
178
|
+
f"Agent of type '{agent_type}' initialized for device {device_id}"
|
|
179
|
+
)
|
|
195
180
|
return agent
|
|
196
181
|
|
|
197
182
|
except Exception as e:
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
self._metadata.
|
|
202
|
-
self._states[device_id] = AgentState.ERROR
|
|
183
|
+
self._agents.pop(device_id, None)
|
|
184
|
+
self._agent_configs.pop(device_id, None)
|
|
185
|
+
self._metadata[device_id].state = AgentState.ERROR
|
|
186
|
+
self._metadata[device_id].error_message = str(e)
|
|
203
187
|
|
|
204
188
|
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
205
189
|
raise AgentInitializationError(
|
|
206
190
|
f"Failed to initialize agent: {str(e)}"
|
|
207
191
|
) from e
|
|
208
192
|
|
|
209
|
-
def _create_streaming_agent(
|
|
210
|
-
self,
|
|
211
|
-
model_config: "ModelConfig",
|
|
212
|
-
agent_config: "AgentConfig",
|
|
213
|
-
on_thinking_chunk: Callable[[str], None],
|
|
214
|
-
) -> "PhoneAgent":
|
|
215
|
-
"""
|
|
216
|
-
创建支持流式输出的 PhoneAgent(monkey-patched model_client).
|
|
217
|
-
|
|
218
|
-
Args:
|
|
219
|
-
model_config: 模型配置
|
|
220
|
-
agent_config: Agent 配置
|
|
221
|
-
on_thinking_chunk: 思考块回调函数
|
|
222
|
-
|
|
223
|
-
Returns:
|
|
224
|
-
已 patch 的 PhoneAgent 实例
|
|
225
|
-
"""
|
|
226
|
-
from phone_agent import PhoneAgent
|
|
227
|
-
|
|
228
|
-
from AutoGLM_GUI.state import non_blocking_takeover
|
|
229
|
-
|
|
230
|
-
# 创建 agent
|
|
231
|
-
agent = PhoneAgent(
|
|
232
|
-
model_config=model_config,
|
|
233
|
-
agent_config=agent_config,
|
|
234
|
-
takeover_callback=non_blocking_takeover,
|
|
235
|
-
)
|
|
236
|
-
|
|
237
|
-
# Monkey-patch model_client.request 以支持流式回调
|
|
238
|
-
original_request = agent.model_client.request
|
|
239
|
-
|
|
240
|
-
def patched_request(messages, **kwargs):
|
|
241
|
-
return original_request(messages, on_thinking_chunk=on_thinking_chunk)
|
|
242
|
-
|
|
243
|
-
agent.model_client.request = patched_request
|
|
244
|
-
|
|
245
|
-
return agent
|
|
246
|
-
|
|
247
|
-
@contextmanager
|
|
248
|
-
def use_streaming_agent(
|
|
249
|
-
self,
|
|
250
|
-
device_id: str,
|
|
251
|
-
on_thinking_chunk: Callable[[str], None],
|
|
252
|
-
timeout: Optional[float] = None,
|
|
253
|
-
auto_initialize: bool = True,
|
|
254
|
-
):
|
|
255
|
-
"""
|
|
256
|
-
Context manager for streaming-enabled agent with automatic:
|
|
257
|
-
- 设备锁获取/释放
|
|
258
|
-
- Streaming agent 创建(带 monkey-patch)
|
|
259
|
-
- 上下文隔离和同步
|
|
260
|
-
- Abort 事件注册/清理
|
|
261
|
-
|
|
262
|
-
By default, automatically initializes the agent using global configuration
|
|
263
|
-
if not already initialized. Set auto_initialize=False to require explicit
|
|
264
|
-
initialization via initialize_agent().
|
|
265
|
-
|
|
266
|
-
Args:
|
|
267
|
-
device_id: 设备标识符
|
|
268
|
-
on_thinking_chunk: 流式思考块回调函数
|
|
269
|
-
timeout: 锁获取超时时间(None=阻塞,0=非阻塞)
|
|
270
|
-
auto_initialize: 自动初始化(默认: True)
|
|
271
|
-
|
|
272
|
-
Yields:
|
|
273
|
-
tuple[PhoneAgent, threading.Event]: (streaming_agent, stop_event)
|
|
274
|
-
|
|
275
|
-
Raises:
|
|
276
|
-
DeviceBusyError: 设备忙
|
|
277
|
-
AgentNotInitializedError: Agent 未初始化且 auto_initialize=False
|
|
278
|
-
AgentInitializationError: auto_initialize=True 且初始化失败
|
|
279
|
-
|
|
280
|
-
Example:
|
|
281
|
-
>>> def on_chunk(chunk: str):
|
|
282
|
-
>>> print(chunk, end='', flush=True)
|
|
283
|
-
>>>
|
|
284
|
-
>>> with manager.use_streaming_agent("device_123", on_chunk) as (agent, stop_event):
|
|
285
|
-
>>> result = agent.step("Open WeChat")
|
|
286
|
-
"""
|
|
287
|
-
acquired = False
|
|
288
|
-
streaming_agent = None
|
|
289
|
-
stop_event = threading.Event()
|
|
290
|
-
|
|
291
|
-
try:
|
|
292
|
-
# 获取设备锁(默认非阻塞)
|
|
293
|
-
acquired = self.acquire_device(
|
|
294
|
-
device_id,
|
|
295
|
-
timeout=timeout if timeout is not None else 0,
|
|
296
|
-
raise_on_timeout=True,
|
|
297
|
-
auto_initialize=auto_initialize,
|
|
298
|
-
)
|
|
299
|
-
|
|
300
|
-
# 获取原始 agent 和配置
|
|
301
|
-
original_agent = self.get_agent(device_id)
|
|
302
|
-
model_config, agent_config = self.get_config(device_id)
|
|
303
|
-
|
|
304
|
-
# 创建 streaming agent
|
|
305
|
-
streaming_agent = self._create_streaming_agent(
|
|
306
|
-
model_config=model_config,
|
|
307
|
-
agent_config=agent_config,
|
|
308
|
-
on_thinking_chunk=on_thinking_chunk,
|
|
309
|
-
)
|
|
310
|
-
|
|
311
|
-
# 复制上下文(由于持有设备锁,线程安全)
|
|
312
|
-
streaming_agent._context = original_agent._context.copy()
|
|
313
|
-
streaming_agent._step_count = original_agent._step_count
|
|
314
|
-
|
|
315
|
-
# 注册 abort 事件
|
|
316
|
-
with self._streaming_contexts_lock:
|
|
317
|
-
self._abort_events[device_id] = stop_event
|
|
318
|
-
self._streaming_contexts[device_id] = StreamingAgentContext(
|
|
319
|
-
streaming_agent=streaming_agent,
|
|
320
|
-
original_agent=original_agent,
|
|
321
|
-
stop_event=stop_event,
|
|
322
|
-
)
|
|
323
|
-
|
|
324
|
-
logger.debug(f"Streaming agent created for {device_id}")
|
|
325
|
-
|
|
326
|
-
yield streaming_agent, stop_event
|
|
327
|
-
|
|
328
|
-
finally:
|
|
329
|
-
# 同步状态回原始 agent
|
|
330
|
-
if streaming_agent and not stop_event.is_set():
|
|
331
|
-
original_agent = self.get_agent_safe(device_id)
|
|
332
|
-
if original_agent:
|
|
333
|
-
original_agent._context = streaming_agent._context
|
|
334
|
-
original_agent._step_count = streaming_agent._step_count
|
|
335
|
-
logger.debug(
|
|
336
|
-
f"Synchronized context back to original agent for {device_id}"
|
|
337
|
-
)
|
|
338
|
-
|
|
339
|
-
# 清理 abort 事件注册
|
|
340
|
-
with self._streaming_contexts_lock:
|
|
341
|
-
self._abort_events.pop(device_id, None)
|
|
342
|
-
self._streaming_contexts.pop(device_id, None)
|
|
343
|
-
|
|
344
|
-
# 释放设备锁
|
|
345
|
-
if acquired:
|
|
346
|
-
self.release_device(device_id)
|
|
347
|
-
|
|
348
193
|
def _auto_initialize_agent(self, device_id: str) -> None:
|
|
349
194
|
"""
|
|
350
195
|
使用全局配置自动初始化 agent(内部方法,需在 manager_lock 内调用).
|
|
351
196
|
|
|
197
|
+
使用 factory 模式创建 agent,避免直接依赖 phone_agent.PhoneAgent。
|
|
198
|
+
|
|
352
199
|
Args:
|
|
353
200
|
device_id: 设备标识符
|
|
354
201
|
|
|
355
202
|
Raises:
|
|
356
203
|
AgentInitializationError: 如果配置不完整或初始化失败
|
|
357
204
|
"""
|
|
358
|
-
from
|
|
359
|
-
from phone_agent.model import ModelConfig
|
|
205
|
+
from typing import cast
|
|
360
206
|
|
|
361
|
-
from AutoGLM_GUI.config import
|
|
207
|
+
from AutoGLM_GUI.config import AgentConfig, ModelConfig
|
|
362
208
|
from AutoGLM_GUI.config_manager import config_manager
|
|
209
|
+
from AutoGLM_GUI.types import AgentSpecificConfig
|
|
363
210
|
|
|
364
211
|
logger.info(f"Auto-initializing agent for device {device_id}...")
|
|
365
212
|
|
|
366
213
|
# 热重载配置
|
|
367
214
|
config_manager.load_file_config()
|
|
368
215
|
config_manager.sync_to_env()
|
|
369
|
-
config.refresh_from_env()
|
|
370
216
|
|
|
371
217
|
effective_config = config_manager.get_effective_config()
|
|
372
218
|
|
|
@@ -376,6 +222,7 @@ class PhoneAgentManager:
|
|
|
376
222
|
f"Please configure base_url via /api/config or call /api/init explicitly."
|
|
377
223
|
)
|
|
378
224
|
|
|
225
|
+
# 使用本地配置类型
|
|
379
226
|
model_config = ModelConfig(
|
|
380
227
|
base_url=effective_config.base_url,
|
|
381
228
|
api_key=effective_config.api_key,
|
|
@@ -384,51 +231,32 @@ class PhoneAgentManager:
|
|
|
384
231
|
|
|
385
232
|
agent_config = AgentConfig(device_id=device_id)
|
|
386
233
|
|
|
387
|
-
# 调用
|
|
388
|
-
|
|
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
|
+
)
|
|
389
245
|
logger.info(f"Agent auto-initialized for device {device_id}")
|
|
390
246
|
|
|
391
|
-
def get_agent(self, device_id: str) ->
|
|
392
|
-
"""
|
|
393
|
-
Get initialized agent for a device.
|
|
394
|
-
|
|
395
|
-
Auto-initializes the agent using global config if not already initialized.
|
|
396
|
-
|
|
397
|
-
Args:
|
|
398
|
-
device_id: Device identifier
|
|
399
|
-
|
|
400
|
-
Returns:
|
|
401
|
-
PhoneAgent: Agent instance
|
|
402
|
-
|
|
403
|
-
Raises:
|
|
404
|
-
AgentInitializationError: If agent not initialized and auto-init fails
|
|
405
|
-
"""
|
|
406
|
-
from AutoGLM_GUI.state import agents
|
|
407
|
-
|
|
247
|
+
def get_agent(self, device_id: str) -> BaseAgent:
|
|
408
248
|
with self._manager_lock:
|
|
409
|
-
if device_id not in
|
|
410
|
-
# 自动初始化:使用全局配置
|
|
249
|
+
if device_id not in self._agents:
|
|
411
250
|
self._auto_initialize_agent(device_id)
|
|
412
|
-
return
|
|
413
|
-
|
|
414
|
-
def get_agent_safe(self, device_id: str) -> Optional[PhoneAgent]:
|
|
415
|
-
"""
|
|
416
|
-
Get initialized agent for a device (safe version, no exception).
|
|
417
|
-
|
|
418
|
-
Args:
|
|
419
|
-
device_id: Device identifier
|
|
420
|
-
|
|
421
|
-
Returns:
|
|
422
|
-
PhoneAgent or None: Agent instance or None if not initialized
|
|
423
|
-
"""
|
|
424
|
-
from AutoGLM_GUI.state import agents
|
|
251
|
+
return self._agents[device_id]
|
|
425
252
|
|
|
253
|
+
def get_agent_safe(self, device_id: str) -> Optional[BaseAgent]:
|
|
426
254
|
with self._manager_lock:
|
|
427
|
-
return
|
|
255
|
+
return self._agents.get(device_id)
|
|
428
256
|
|
|
429
257
|
def reset_agent(self, device_id: str) -> None:
|
|
430
258
|
"""
|
|
431
|
-
Reset agent state
|
|
259
|
+
Reset agent state by calling the agent's reset() method.
|
|
432
260
|
|
|
433
261
|
Args:
|
|
434
262
|
device_id: Device identifier
|
|
@@ -436,39 +264,20 @@ class PhoneAgentManager:
|
|
|
436
264
|
Raises:
|
|
437
265
|
AgentNotInitializedError: If agent not initialized
|
|
438
266
|
"""
|
|
439
|
-
from phone_agent import PhoneAgent
|
|
440
|
-
|
|
441
|
-
from AutoGLM_GUI.state import agent_configs, agents, non_blocking_takeover
|
|
442
|
-
|
|
443
267
|
with self._manager_lock:
|
|
444
|
-
if device_id not in
|
|
268
|
+
if device_id not in self._agents:
|
|
445
269
|
raise AgentNotInitializedError(
|
|
446
270
|
f"Agent not initialized for device {device_id}"
|
|
447
271
|
)
|
|
448
272
|
|
|
449
|
-
#
|
|
450
|
-
|
|
451
|
-
logger.warning(
|
|
452
|
-
f"No cached config for {device_id}, only resetting agent state"
|
|
453
|
-
)
|
|
454
|
-
agents[device_id].reset()
|
|
455
|
-
return
|
|
456
|
-
|
|
457
|
-
# Rebuild agent from cached config
|
|
458
|
-
model_config, agent_config = agent_configs[device_id]
|
|
459
|
-
|
|
460
|
-
agents[device_id] = PhoneAgent(
|
|
461
|
-
model_config=model_config,
|
|
462
|
-
agent_config=agent_config,
|
|
463
|
-
takeover_callback=non_blocking_takeover,
|
|
464
|
-
)
|
|
273
|
+
# Reset agent state using its reset() method
|
|
274
|
+
self._agents[device_id].reset()
|
|
465
275
|
|
|
466
276
|
# Update metadata
|
|
467
277
|
if device_id in self._metadata:
|
|
468
278
|
self._metadata[device_id].last_used = time.time()
|
|
469
279
|
self._metadata[device_id].error_message = None
|
|
470
|
-
|
|
471
|
-
self._states[device_id] = AgentState.IDLE
|
|
280
|
+
self._metadata[device_id].state = AgentState.IDLE
|
|
472
281
|
|
|
473
282
|
logger.info(f"Agent reset for device {device_id}")
|
|
474
283
|
|
|
@@ -479,11 +288,9 @@ class PhoneAgentManager:
|
|
|
479
288
|
Args:
|
|
480
289
|
device_id: Device identifier
|
|
481
290
|
"""
|
|
482
|
-
from AutoGLM_GUI.state import agent_configs, agents
|
|
483
|
-
|
|
484
291
|
with self._manager_lock:
|
|
485
292
|
# Remove agent
|
|
486
|
-
agent =
|
|
293
|
+
agent = self._agents.pop(device_id, None)
|
|
487
294
|
if agent:
|
|
488
295
|
try:
|
|
489
296
|
agent.reset() # Clean up agent state
|
|
@@ -491,20 +298,17 @@ class PhoneAgentManager:
|
|
|
491
298
|
logger.warning(f"Error resetting agent during destroy: {e}")
|
|
492
299
|
|
|
493
300
|
# Remove config
|
|
494
|
-
|
|
301
|
+
self._agent_configs.pop(device_id, None)
|
|
495
302
|
|
|
496
303
|
# Remove metadata
|
|
497
304
|
self._metadata.pop(device_id, None)
|
|
498
|
-
self._states.pop(device_id, None)
|
|
499
305
|
|
|
500
306
|
logger.info(f"Agent destroyed for device {device_id}")
|
|
501
307
|
|
|
502
308
|
def is_initialized(self, device_id: str) -> bool:
|
|
503
309
|
"""Check if agent is initialized for device."""
|
|
504
|
-
from AutoGLM_GUI.state import agents
|
|
505
|
-
|
|
506
310
|
with self._manager_lock:
|
|
507
|
-
return device_id in
|
|
311
|
+
return device_id in self._agents
|
|
508
312
|
|
|
509
313
|
# ==================== Concurrency Control ====================
|
|
510
314
|
|
|
@@ -582,8 +386,8 @@ class PhoneAgentManager:
|
|
|
582
386
|
if acquired:
|
|
583
387
|
# Update state
|
|
584
388
|
with self._manager_lock:
|
|
585
|
-
self._states[device_id] = AgentState.BUSY
|
|
586
389
|
if device_id in self._metadata:
|
|
390
|
+
self._metadata[device_id].state = AgentState.BUSY
|
|
587
391
|
self._metadata[device_id].last_used = time.time()
|
|
588
392
|
|
|
589
393
|
logger.debug(f"Device lock acquired for {device_id}")
|
|
@@ -610,7 +414,8 @@ class PhoneAgentManager:
|
|
|
610
414
|
|
|
611
415
|
# Update state
|
|
612
416
|
with self._manager_lock:
|
|
613
|
-
|
|
417
|
+
if device_id in self._metadata:
|
|
418
|
+
self._metadata[device_id].state = AgentState.IDLE
|
|
614
419
|
|
|
615
420
|
logger.debug(f"Device lock released for {device_id}")
|
|
616
421
|
|
|
@@ -626,7 +431,7 @@ class PhoneAgentManager:
|
|
|
626
431
|
|
|
627
432
|
By default, automatically initializes the agent using global configuration
|
|
628
433
|
if not already initialized. Set auto_initialize=False to require explicit
|
|
629
|
-
initialization via
|
|
434
|
+
initialization via initialize_agent_with_factory().
|
|
630
435
|
|
|
631
436
|
Args:
|
|
632
437
|
device_id: Device identifier
|
|
@@ -634,7 +439,7 @@ class PhoneAgentManager:
|
|
|
634
439
|
auto_initialize: Auto-initialize if not already initialized (default: True)
|
|
635
440
|
|
|
636
441
|
Yields:
|
|
637
|
-
|
|
442
|
+
BaseAgent: Agent instance
|
|
638
443
|
|
|
639
444
|
Raises:
|
|
640
445
|
DeviceBusyError: If device is busy
|
|
@@ -671,13 +476,14 @@ class PhoneAgentManager:
|
|
|
671
476
|
def get_state(self, device_id: str) -> AgentState:
|
|
672
477
|
"""Get current agent state."""
|
|
673
478
|
with self._manager_lock:
|
|
674
|
-
|
|
479
|
+
metadata = self._metadata.get(device_id)
|
|
480
|
+
return metadata.state if metadata else AgentState.ERROR
|
|
675
481
|
|
|
676
482
|
def set_error_state(self, device_id: str, error_message: str) -> None:
|
|
677
483
|
"""Mark agent as errored."""
|
|
678
484
|
with self._manager_lock:
|
|
679
|
-
self._states[device_id] = AgentState.ERROR
|
|
680
485
|
if device_id in self._metadata:
|
|
486
|
+
self._metadata[device_id].state = AgentState.ERROR
|
|
681
487
|
self._metadata[device_id].error_message = error_message
|
|
682
488
|
|
|
683
489
|
logger.error(f"Agent error for {device_id}: {error_message}")
|
|
@@ -686,94 +492,35 @@ class PhoneAgentManager:
|
|
|
686
492
|
|
|
687
493
|
def get_config(self, device_id: str) -> tuple[ModelConfig, AgentConfig]:
|
|
688
494
|
"""Get cached configuration for device."""
|
|
689
|
-
from AutoGLM_GUI.state import agent_configs
|
|
690
|
-
|
|
691
|
-
with self._manager_lock:
|
|
692
|
-
if device_id not in agent_configs:
|
|
693
|
-
raise AgentNotInitializedError(
|
|
694
|
-
f"No configuration found for device {device_id}"
|
|
695
|
-
)
|
|
696
|
-
return agent_configs[device_id]
|
|
697
|
-
|
|
698
|
-
def update_config(
|
|
699
|
-
self,
|
|
700
|
-
device_id: str,
|
|
701
|
-
model_config: Optional[ModelConfig] = None,
|
|
702
|
-
agent_config: Optional[AgentConfig] = None,
|
|
703
|
-
) -> None:
|
|
704
|
-
"""
|
|
705
|
-
Update agent configuration (requires reinitialization).
|
|
706
|
-
|
|
707
|
-
Args:
|
|
708
|
-
device_id: Device identifier
|
|
709
|
-
model_config: New model config (None = keep existing)
|
|
710
|
-
agent_config: New agent config (None = keep existing)
|
|
711
|
-
"""
|
|
712
|
-
from AutoGLM_GUI.state import agent_configs
|
|
713
|
-
|
|
714
495
|
with self._manager_lock:
|
|
715
|
-
if device_id not in
|
|
496
|
+
if device_id not in self._agent_configs:
|
|
716
497
|
raise AgentNotInitializedError(
|
|
717
498
|
f"No configuration found for device {device_id}"
|
|
718
499
|
)
|
|
719
|
-
|
|
720
|
-
old_model_config, old_agent_config = agent_configs[device_id]
|
|
721
|
-
|
|
722
|
-
new_model_config = model_config or old_model_config
|
|
723
|
-
new_agent_config = agent_config or old_agent_config
|
|
724
|
-
|
|
725
|
-
# Reinitialize with new config
|
|
726
|
-
self.initialize_agent(
|
|
727
|
-
device_id,
|
|
728
|
-
new_model_config,
|
|
729
|
-
new_agent_config,
|
|
730
|
-
force=True,
|
|
731
|
-
)
|
|
732
|
-
|
|
733
|
-
# ==================== DeviceManager Integration ====================
|
|
734
|
-
|
|
735
|
-
def find_agent_by_serial(self, serial: str) -> Optional[str]:
|
|
736
|
-
"""
|
|
737
|
-
Find agent device_id by hardware serial (connection switching support).
|
|
738
|
-
|
|
739
|
-
Args:
|
|
740
|
-
serial: Hardware serial number
|
|
741
|
-
|
|
742
|
-
Returns:
|
|
743
|
-
Optional[str]: device_id of initialized agent, or None
|
|
744
|
-
"""
|
|
745
|
-
from AutoGLM_GUI.device_manager import DeviceManager
|
|
746
|
-
from AutoGLM_GUI.state import agents
|
|
747
|
-
|
|
748
|
-
with self._manager_lock:
|
|
749
|
-
# Get device by serial from DeviceManager
|
|
750
|
-
device_manager = DeviceManager.get_instance()
|
|
751
|
-
device = device_manager._devices.get(serial)
|
|
752
|
-
|
|
753
|
-
if not device:
|
|
754
|
-
return None
|
|
755
|
-
|
|
756
|
-
# Check all connections for initialized agents
|
|
757
|
-
for conn in device.connections:
|
|
758
|
-
if conn.device_id in agents:
|
|
759
|
-
return conn.device_id
|
|
760
|
-
|
|
761
|
-
return None
|
|
500
|
+
return self._agent_configs[device_id]
|
|
762
501
|
|
|
763
502
|
# ==================== Introspection ====================
|
|
764
503
|
|
|
765
504
|
def list_agents(self) -> list[str]:
|
|
766
505
|
"""Get list of all initialized device IDs."""
|
|
767
|
-
from AutoGLM_GUI.state import agents
|
|
768
|
-
|
|
769
506
|
with self._manager_lock:
|
|
770
|
-
return list(
|
|
507
|
+
return list(self._agents.keys())
|
|
771
508
|
|
|
772
509
|
def get_metadata(self, device_id: str) -> Optional[AgentMetadata]:
|
|
773
510
|
"""Get agent metadata."""
|
|
774
511
|
with self._manager_lock:
|
|
775
512
|
return self._metadata.get(device_id)
|
|
776
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
|
+
|
|
777
524
|
def abort_streaming_chat(self, device_id: str) -> bool:
|
|
778
525
|
"""
|
|
779
526
|
中止正在进行的流式对话.
|
|
@@ -787,7 +534,11 @@ class PhoneAgentManager:
|
|
|
787
534
|
with self._streaming_contexts_lock:
|
|
788
535
|
if device_id in self._abort_events:
|
|
789
536
|
logger.info(f"Aborting streaming chat for device {device_id}")
|
|
790
|
-
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()
|
|
791
542
|
return True
|
|
792
543
|
else:
|
|
793
544
|
logger.warning(f"No active streaming chat for device {device_id}")
|