autoglm-gui 1.3.1__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 +50 -7
- AutoGLM_GUI/api/agents.py +61 -19
- AutoGLM_GUI/api/devices.py +12 -18
- AutoGLM_GUI/api/dual_model.py +24 -17
- AutoGLM_GUI/api/health.py +13 -0
- AutoGLM_GUI/api/layered_agent.py +659 -0
- 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 +56 -24
- 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/dual_model/protocols.py +3 -3
- AutoGLM_GUI/exceptions.py +3 -3
- AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +291 -0
- 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/prompts.py +6 -1
- AutoGLM_GUI/schemas.py +45 -14
- 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-Cj6QXqMf.js → about-_XNhzQZX.js} +1 -1
- AutoGLM_GUI/static/assets/chat-DwJpiAWf.js +126 -0
- AutoGLM_GUI/static/assets/{dialog-CxJlnjzH.js → dialog-B3uW4T8V.js} +3 -3
- AutoGLM_GUI/static/assets/index-Cpv2gSF1.css +1 -0
- AutoGLM_GUI/static/assets/{index-C_B-Arvf.js → index-Cy8TmmHV.js} +1 -1
- AutoGLM_GUI/static/assets/{index-CxJQuE4y.js → index-UYYauTly.js} +6 -6
- AutoGLM_GUI/static/assets/{workflows-BTiGCNI0.js → workflows-Du_de-dt.js} +1 -1
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/types.py +125 -0
- {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/METADATA +147 -65
- {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/RECORD +58 -39
- 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
- phone_agent/config/prompts.py +6 -1
- phone_agent/config/prompts_zh.py +6 -1
- AutoGLM_GUI/config.py +0 -23
- AutoGLM_GUI/static/assets/chat-BJeomZgh.js +0 -124
- AutoGLM_GUI/static/assets/index-Z0uYCPOO.css +0 -1
- {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.3.1.dist-info → autoglm_gui-1.4.1.dist-info}/licenses/LICENSE +0 -0
|
@@ -9,6 +9,8 @@ from dataclasses import dataclass
|
|
|
9
9
|
from enum import Enum
|
|
10
10
|
from typing import TYPE_CHECKING, Callable, Optional
|
|
11
11
|
|
|
12
|
+
from AutoGLM_GUI.agents.protocols import BaseAgent
|
|
13
|
+
from AutoGLM_GUI.types import AgentSpecificConfig
|
|
12
14
|
from AutoGLM_GUI.exceptions import (
|
|
13
15
|
AgentInitializationError,
|
|
14
16
|
AgentNotInitializedError,
|
|
@@ -17,7 +19,6 @@ from AutoGLM_GUI.exceptions import (
|
|
|
17
19
|
from AutoGLM_GUI.logger import logger
|
|
18
20
|
|
|
19
21
|
if TYPE_CHECKING:
|
|
20
|
-
from phone_agent import PhoneAgent
|
|
21
22
|
from phone_agent.agent import AgentConfig
|
|
22
23
|
from phone_agent.model import ModelConfig
|
|
23
24
|
|
|
@@ -46,10 +47,8 @@ class AgentMetadata:
|
|
|
46
47
|
|
|
47
48
|
@dataclass
|
|
48
49
|
class StreamingAgentContext:
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
streaming_agent: "PhoneAgent"
|
|
52
|
-
original_agent: "PhoneAgent"
|
|
50
|
+
streaming_agent: BaseAgent
|
|
51
|
+
original_agent: BaseAgent
|
|
53
52
|
stop_event: threading.Event
|
|
54
53
|
|
|
55
54
|
|
|
@@ -95,11 +94,9 @@ class PhoneAgentManager:
|
|
|
95
94
|
self._device_locks_lock = threading.Lock()
|
|
96
95
|
|
|
97
96
|
# Agent metadata (indexed by device_id)
|
|
97
|
+
# State is stored in AgentMetadata.state (single source of truth)
|
|
98
98
|
self._metadata: dict[str, AgentMetadata] = {}
|
|
99
99
|
|
|
100
|
-
# State tracking
|
|
101
|
-
self._states: dict[str, AgentState] = {}
|
|
102
|
-
|
|
103
100
|
# Streaming agent state (device_id -> StreamingAgentContext)
|
|
104
101
|
self._streaming_contexts: dict[str, StreamingAgentContext] = {}
|
|
105
102
|
self._streaming_contexts_lock = threading.Lock()
|
|
@@ -107,6 +104,10 @@ class PhoneAgentManager:
|
|
|
107
104
|
# Abort events (device_id -> threading.Event)
|
|
108
105
|
self._abort_events: dict[str, threading.Event] = {}
|
|
109
106
|
|
|
107
|
+
# Agent storage (transition from global state to instance state)
|
|
108
|
+
self._agents: dict[str, BaseAgent] = {}
|
|
109
|
+
self._agent_configs: dict[str, tuple["ModelConfig", "AgentConfig"]] = {}
|
|
110
|
+
|
|
110
111
|
@classmethod
|
|
111
112
|
def get_instance(cls) -> PhoneAgentManager:
|
|
112
113
|
"""Get singleton instance (thread-safe, double-checked locking)."""
|
|
@@ -122,11 +123,11 @@ class PhoneAgentManager:
|
|
|
122
123
|
def initialize_agent(
|
|
123
124
|
self,
|
|
124
125
|
device_id: str,
|
|
125
|
-
model_config: ModelConfig,
|
|
126
|
-
agent_config: AgentConfig,
|
|
126
|
+
model_config: "ModelConfig",
|
|
127
|
+
agent_config: "AgentConfig",
|
|
127
128
|
takeover_callback: Optional[Callable] = None,
|
|
128
129
|
force: bool = False,
|
|
129
|
-
) ->
|
|
130
|
+
) -> BaseAgent:
|
|
130
131
|
"""
|
|
131
132
|
Initialize PhoneAgent for a device (thread-safe, idempotent).
|
|
132
133
|
|
|
@@ -148,15 +149,14 @@ class PhoneAgentManager:
|
|
|
148
149
|
- On failure, state is rolled back
|
|
149
150
|
- state.agents and state.agent_configs remain consistent
|
|
150
151
|
"""
|
|
152
|
+
from AutoGLM_GUI.state import non_blocking_takeover
|
|
151
153
|
from phone_agent import PhoneAgent
|
|
152
154
|
|
|
153
|
-
from AutoGLM_GUI.state import agent_configs, agents, non_blocking_takeover
|
|
154
|
-
|
|
155
155
|
with self._manager_lock:
|
|
156
156
|
# Check if already initialized
|
|
157
|
-
if device_id in
|
|
157
|
+
if device_id in self._agents and not force:
|
|
158
158
|
logger.debug(f"Agent already initialized for {device_id}")
|
|
159
|
-
return
|
|
159
|
+
return self._agents[device_id]
|
|
160
160
|
|
|
161
161
|
# Check device availability (non-blocking check)
|
|
162
162
|
device_lock = self._get_device_lock(device_id)
|
|
@@ -165,8 +165,15 @@ class PhoneAgentManager:
|
|
|
165
165
|
f"Device {device_id} is currently processing a request"
|
|
166
166
|
)
|
|
167
167
|
|
|
168
|
-
#
|
|
169
|
-
self.
|
|
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
|
+
)
|
|
170
177
|
|
|
171
178
|
try:
|
|
172
179
|
# Create agent
|
|
@@ -177,29 +184,119 @@ class PhoneAgentManager:
|
|
|
177
184
|
)
|
|
178
185
|
|
|
179
186
|
# Store in state (transactional)
|
|
180
|
-
|
|
181
|
-
|
|
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
|
+
def initialize_agent_with_factory(
|
|
209
|
+
self,
|
|
210
|
+
device_id: str,
|
|
211
|
+
agent_type: str,
|
|
212
|
+
model_config: ModelConfig,
|
|
213
|
+
agent_config: AgentConfig,
|
|
214
|
+
agent_specific_config: AgentSpecificConfig,
|
|
215
|
+
takeover_callback: Optional[Callable] = None,
|
|
216
|
+
confirmation_callback: Optional[Callable] = None,
|
|
217
|
+
force: bool = False,
|
|
218
|
+
) -> "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
|
+
from AutoGLM_GUI.agents import create_agent
|
|
247
|
+
|
|
248
|
+
with self._manager_lock:
|
|
249
|
+
# Check if already initialized
|
|
250
|
+
if device_id in self._agents and not force:
|
|
251
|
+
logger.debug(f"Agent already initialized for {device_id}")
|
|
252
|
+
return self._agents[device_id]
|
|
253
|
+
|
|
254
|
+
# Check device availability (non-blocking check)
|
|
255
|
+
device_lock = self._get_device_lock(device_id)
|
|
256
|
+
if device_lock.locked():
|
|
257
|
+
raise DeviceBusyError(
|
|
258
|
+
f"Device {device_id} is currently processing a request"
|
|
259
|
+
)
|
|
260
|
+
|
|
261
|
+
# Create metadata first with INITIALIZING state
|
|
262
|
+
self._metadata[device_id] = AgentMetadata(
|
|
263
|
+
device_id=device_id,
|
|
264
|
+
state=AgentState.INITIALIZING,
|
|
265
|
+
model_config=model_config,
|
|
266
|
+
agent_config=agent_config,
|
|
267
|
+
created_at=time.time(),
|
|
268
|
+
last_used=time.time(),
|
|
269
|
+
)
|
|
182
270
|
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
271
|
+
try:
|
|
272
|
+
# Create agent using factory
|
|
273
|
+
agent = create_agent(
|
|
274
|
+
agent_type=agent_type,
|
|
187
275
|
model_config=model_config,
|
|
188
276
|
agent_config=agent_config,
|
|
189
|
-
|
|
190
|
-
|
|
277
|
+
agent_specific_config=agent_specific_config,
|
|
278
|
+
takeover_callback=takeover_callback,
|
|
279
|
+
confirmation_callback=confirmation_callback,
|
|
191
280
|
)
|
|
192
|
-
self._states[device_id] = AgentState.IDLE
|
|
193
281
|
|
|
194
|
-
|
|
282
|
+
# Store in state (transactional)
|
|
283
|
+
self._agents[device_id] = agent
|
|
284
|
+
self._agent_configs[device_id] = (model_config, agent_config)
|
|
285
|
+
|
|
286
|
+
# Update state to IDLE on success
|
|
287
|
+
self._metadata[device_id].state = AgentState.IDLE
|
|
288
|
+
|
|
289
|
+
logger.info(
|
|
290
|
+
f"Agent of type '{agent_type}' initialized for device {device_id}"
|
|
291
|
+
)
|
|
195
292
|
return agent
|
|
196
293
|
|
|
197
294
|
except Exception as e:
|
|
198
295
|
# Rollback on error
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
self._metadata.
|
|
202
|
-
self.
|
|
296
|
+
self._agents.pop(device_id, None)
|
|
297
|
+
self._agent_configs.pop(device_id, None)
|
|
298
|
+
self._metadata[device_id].state = AgentState.ERROR
|
|
299
|
+
self._metadata[device_id].error_message = str(e)
|
|
203
300
|
|
|
204
301
|
logger.error(f"Failed to initialize agent for {device_id}: {e}")
|
|
205
302
|
raise AgentInitializationError(
|
|
@@ -208,41 +305,61 @@ class PhoneAgentManager:
|
|
|
208
305
|
|
|
209
306
|
def _create_streaming_agent(
|
|
210
307
|
self,
|
|
211
|
-
|
|
212
|
-
|
|
308
|
+
device_id: str,
|
|
309
|
+
original_agent: "BaseAgent",
|
|
213
310
|
on_thinking_chunk: Callable[[str], None],
|
|
214
|
-
) -> "
|
|
311
|
+
) -> "BaseAgent":
|
|
215
312
|
"""
|
|
216
|
-
创建支持流式输出的
|
|
313
|
+
创建支持流式输出的 agent(复制原始 agent).
|
|
217
314
|
|
|
218
315
|
Args:
|
|
219
|
-
|
|
220
|
-
|
|
316
|
+
device_id: 设备标识符
|
|
317
|
+
original_agent: 原始 agent 实例
|
|
221
318
|
on_thinking_chunk: 思考块回调函数
|
|
222
319
|
|
|
223
320
|
Returns:
|
|
224
|
-
已 patch 的
|
|
321
|
+
已 patch 的 agent 实例
|
|
225
322
|
"""
|
|
323
|
+
from AutoGLM_GUI.agents.mai_adapter import MAIAgentAdapter
|
|
226
324
|
from phone_agent import PhoneAgent
|
|
227
325
|
|
|
228
|
-
|
|
326
|
+
model_config, agent_config = self.get_config(device_id)
|
|
229
327
|
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
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
|
+
)
|
|
236
349
|
|
|
237
|
-
|
|
238
|
-
original_request = agent.model_client.request
|
|
350
|
+
original_request = phone_streaming_agent.model_client.request
|
|
239
351
|
|
|
240
|
-
|
|
241
|
-
|
|
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]
|
|
242
354
|
|
|
243
|
-
|
|
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)}")
|
|
244
361
|
|
|
245
|
-
return
|
|
362
|
+
return streaming_agent
|
|
246
363
|
|
|
247
364
|
@contextmanager
|
|
248
365
|
def use_streaming_agent(
|
|
@@ -297,21 +414,16 @@ class PhoneAgentManager:
|
|
|
297
414
|
auto_initialize=auto_initialize,
|
|
298
415
|
)
|
|
299
416
|
|
|
300
|
-
# 获取原始 agent
|
|
417
|
+
# 获取原始 agent
|
|
301
418
|
original_agent = self.get_agent(device_id)
|
|
302
|
-
model_config, agent_config = self.get_config(device_id)
|
|
303
419
|
|
|
304
|
-
# 创建 streaming agent
|
|
420
|
+
# 创建 streaming agent(自动检测类型)
|
|
305
421
|
streaming_agent = self._create_streaming_agent(
|
|
306
|
-
|
|
307
|
-
|
|
422
|
+
device_id=device_id,
|
|
423
|
+
original_agent=original_agent,
|
|
308
424
|
on_thinking_chunk=on_thinking_chunk,
|
|
309
425
|
)
|
|
310
426
|
|
|
311
|
-
# 复制上下文(由于持有设备锁,线程安全)
|
|
312
|
-
streaming_agent._context = original_agent._context.copy()
|
|
313
|
-
streaming_agent._step_count = original_agent._step_count
|
|
314
|
-
|
|
315
427
|
# 注册 abort 事件
|
|
316
428
|
with self._streaming_contexts_lock:
|
|
317
429
|
self._abort_events[device_id] = stop_event
|
|
@@ -326,12 +438,28 @@ class PhoneAgentManager:
|
|
|
326
438
|
yield streaming_agent, stop_event
|
|
327
439
|
|
|
328
440
|
finally:
|
|
329
|
-
# 同步状态回原始 agent
|
|
330
441
|
if streaming_agent and not stop_event.is_set():
|
|
331
442
|
original_agent = self.get_agent_safe(device_id)
|
|
332
443
|
if original_agent:
|
|
333
|
-
|
|
334
|
-
|
|
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
|
+
|
|
335
463
|
logger.debug(
|
|
336
464
|
f"Synchronized context back to original agent for {device_id}"
|
|
337
465
|
)
|
|
@@ -355,18 +483,15 @@ class PhoneAgentManager:
|
|
|
355
483
|
Raises:
|
|
356
484
|
AgentInitializationError: 如果配置不完整或初始化失败
|
|
357
485
|
"""
|
|
486
|
+
from AutoGLM_GUI.config_manager import config_manager
|
|
358
487
|
from phone_agent.agent import AgentConfig
|
|
359
488
|
from phone_agent.model import ModelConfig
|
|
360
489
|
|
|
361
|
-
from AutoGLM_GUI.config import config
|
|
362
|
-
from AutoGLM_GUI.config_manager import config_manager
|
|
363
|
-
|
|
364
490
|
logger.info(f"Auto-initializing agent for device {device_id}...")
|
|
365
491
|
|
|
366
492
|
# 热重载配置
|
|
367
493
|
config_manager.load_file_config()
|
|
368
494
|
config_manager.sync_to_env()
|
|
369
|
-
config.refresh_from_env()
|
|
370
495
|
|
|
371
496
|
effective_config = config_manager.get_effective_config()
|
|
372
497
|
|
|
@@ -388,43 +513,15 @@ class PhoneAgentManager:
|
|
|
388
513
|
self.initialize_agent(device_id, model_config, agent_config)
|
|
389
514
|
logger.info(f"Agent auto-initialized for device {device_id}")
|
|
390
515
|
|
|
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
|
-
|
|
516
|
+
def get_agent(self, device_id: str) -> BaseAgent:
|
|
408
517
|
with self._manager_lock:
|
|
409
|
-
if device_id not in
|
|
410
|
-
# 自动初始化:使用全局配置
|
|
518
|
+
if device_id not in self._agents:
|
|
411
519
|
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
|
|
520
|
+
return self._agents[device_id]
|
|
425
521
|
|
|
522
|
+
def get_agent_safe(self, device_id: str) -> Optional[BaseAgent]:
|
|
426
523
|
with self._manager_lock:
|
|
427
|
-
return
|
|
524
|
+
return self._agents.get(device_id)
|
|
428
525
|
|
|
429
526
|
def reset_agent(self, device_id: str) -> None:
|
|
430
527
|
"""
|
|
@@ -436,28 +533,27 @@ class PhoneAgentManager:
|
|
|
436
533
|
Raises:
|
|
437
534
|
AgentNotInitializedError: If agent not initialized
|
|
438
535
|
"""
|
|
536
|
+
from AutoGLM_GUI.state import non_blocking_takeover
|
|
439
537
|
from phone_agent import PhoneAgent
|
|
440
538
|
|
|
441
|
-
from AutoGLM_GUI.state import agent_configs, agents, non_blocking_takeover
|
|
442
|
-
|
|
443
539
|
with self._manager_lock:
|
|
444
|
-
if device_id not in
|
|
540
|
+
if device_id not in self._agents:
|
|
445
541
|
raise AgentNotInitializedError(
|
|
446
542
|
f"Agent not initialized for device {device_id}"
|
|
447
543
|
)
|
|
448
544
|
|
|
449
545
|
# Get cached config
|
|
450
|
-
if device_id not in
|
|
546
|
+
if device_id not in self._agent_configs:
|
|
451
547
|
logger.warning(
|
|
452
548
|
f"No cached config for {device_id}, only resetting agent state"
|
|
453
549
|
)
|
|
454
|
-
|
|
550
|
+
self._agents[device_id].reset()
|
|
455
551
|
return
|
|
456
552
|
|
|
457
553
|
# Rebuild agent from cached config
|
|
458
|
-
model_config, agent_config =
|
|
554
|
+
model_config, agent_config = self._agent_configs[device_id]
|
|
459
555
|
|
|
460
|
-
|
|
556
|
+
self._agents[device_id] = PhoneAgent(
|
|
461
557
|
model_config=model_config,
|
|
462
558
|
agent_config=agent_config,
|
|
463
559
|
takeover_callback=non_blocking_takeover,
|
|
@@ -467,8 +563,7 @@ class PhoneAgentManager:
|
|
|
467
563
|
if device_id in self._metadata:
|
|
468
564
|
self._metadata[device_id].last_used = time.time()
|
|
469
565
|
self._metadata[device_id].error_message = None
|
|
470
|
-
|
|
471
|
-
self._states[device_id] = AgentState.IDLE
|
|
566
|
+
self._metadata[device_id].state = AgentState.IDLE
|
|
472
567
|
|
|
473
568
|
logger.info(f"Agent reset for device {device_id}")
|
|
474
569
|
|
|
@@ -479,11 +574,9 @@ class PhoneAgentManager:
|
|
|
479
574
|
Args:
|
|
480
575
|
device_id: Device identifier
|
|
481
576
|
"""
|
|
482
|
-
from AutoGLM_GUI.state import agent_configs, agents
|
|
483
|
-
|
|
484
577
|
with self._manager_lock:
|
|
485
578
|
# Remove agent
|
|
486
|
-
agent =
|
|
579
|
+
agent = self._agents.pop(device_id, None)
|
|
487
580
|
if agent:
|
|
488
581
|
try:
|
|
489
582
|
agent.reset() # Clean up agent state
|
|
@@ -491,20 +584,17 @@ class PhoneAgentManager:
|
|
|
491
584
|
logger.warning(f"Error resetting agent during destroy: {e}")
|
|
492
585
|
|
|
493
586
|
# Remove config
|
|
494
|
-
|
|
587
|
+
self._agent_configs.pop(device_id, None)
|
|
495
588
|
|
|
496
589
|
# Remove metadata
|
|
497
590
|
self._metadata.pop(device_id, None)
|
|
498
|
-
self._states.pop(device_id, None)
|
|
499
591
|
|
|
500
592
|
logger.info(f"Agent destroyed for device {device_id}")
|
|
501
593
|
|
|
502
594
|
def is_initialized(self, device_id: str) -> bool:
|
|
503
595
|
"""Check if agent is initialized for device."""
|
|
504
|
-
from AutoGLM_GUI.state import agents
|
|
505
|
-
|
|
506
596
|
with self._manager_lock:
|
|
507
|
-
return device_id in
|
|
597
|
+
return device_id in self._agents
|
|
508
598
|
|
|
509
599
|
# ==================== Concurrency Control ====================
|
|
510
600
|
|
|
@@ -582,8 +672,8 @@ class PhoneAgentManager:
|
|
|
582
672
|
if acquired:
|
|
583
673
|
# Update state
|
|
584
674
|
with self._manager_lock:
|
|
585
|
-
self._states[device_id] = AgentState.BUSY
|
|
586
675
|
if device_id in self._metadata:
|
|
676
|
+
self._metadata[device_id].state = AgentState.BUSY
|
|
587
677
|
self._metadata[device_id].last_used = time.time()
|
|
588
678
|
|
|
589
679
|
logger.debug(f"Device lock acquired for {device_id}")
|
|
@@ -610,7 +700,8 @@ class PhoneAgentManager:
|
|
|
610
700
|
|
|
611
701
|
# Update state
|
|
612
702
|
with self._manager_lock:
|
|
613
|
-
|
|
703
|
+
if device_id in self._metadata:
|
|
704
|
+
self._metadata[device_id].state = AgentState.IDLE
|
|
614
705
|
|
|
615
706
|
logger.debug(f"Device lock released for {device_id}")
|
|
616
707
|
|
|
@@ -671,13 +762,14 @@ class PhoneAgentManager:
|
|
|
671
762
|
def get_state(self, device_id: str) -> AgentState:
|
|
672
763
|
"""Get current agent state."""
|
|
673
764
|
with self._manager_lock:
|
|
674
|
-
|
|
765
|
+
metadata = self._metadata.get(device_id)
|
|
766
|
+
return metadata.state if metadata else AgentState.ERROR
|
|
675
767
|
|
|
676
768
|
def set_error_state(self, device_id: str, error_message: str) -> None:
|
|
677
769
|
"""Mark agent as errored."""
|
|
678
770
|
with self._manager_lock:
|
|
679
|
-
self._states[device_id] = AgentState.ERROR
|
|
680
771
|
if device_id in self._metadata:
|
|
772
|
+
self._metadata[device_id].state = AgentState.ERROR
|
|
681
773
|
self._metadata[device_id].error_message = error_message
|
|
682
774
|
|
|
683
775
|
logger.error(f"Agent error for {device_id}: {error_message}")
|
|
@@ -686,14 +778,12 @@ class PhoneAgentManager:
|
|
|
686
778
|
|
|
687
779
|
def get_config(self, device_id: str) -> tuple[ModelConfig, AgentConfig]:
|
|
688
780
|
"""Get cached configuration for device."""
|
|
689
|
-
from AutoGLM_GUI.state import agent_configs
|
|
690
|
-
|
|
691
781
|
with self._manager_lock:
|
|
692
|
-
if device_id not in
|
|
782
|
+
if device_id not in self._agent_configs:
|
|
693
783
|
raise AgentNotInitializedError(
|
|
694
784
|
f"No configuration found for device {device_id}"
|
|
695
785
|
)
|
|
696
|
-
return
|
|
786
|
+
return self._agent_configs[device_id]
|
|
697
787
|
|
|
698
788
|
def update_config(
|
|
699
789
|
self,
|
|
@@ -709,15 +799,13 @@ class PhoneAgentManager:
|
|
|
709
799
|
model_config: New model config (None = keep existing)
|
|
710
800
|
agent_config: New agent config (None = keep existing)
|
|
711
801
|
"""
|
|
712
|
-
from AutoGLM_GUI.state import agent_configs
|
|
713
|
-
|
|
714
802
|
with self._manager_lock:
|
|
715
|
-
if device_id not in
|
|
803
|
+
if device_id not in self._agent_configs:
|
|
716
804
|
raise AgentNotInitializedError(
|
|
717
805
|
f"No configuration found for device {device_id}"
|
|
718
806
|
)
|
|
719
807
|
|
|
720
|
-
old_model_config, old_agent_config =
|
|
808
|
+
old_model_config, old_agent_config = self._agent_configs[device_id]
|
|
721
809
|
|
|
722
810
|
new_model_config = model_config or old_model_config
|
|
723
811
|
new_agent_config = agent_config or old_agent_config
|
|
@@ -743,7 +831,6 @@ class PhoneAgentManager:
|
|
|
743
831
|
Optional[str]: device_id of initialized agent, or None
|
|
744
832
|
"""
|
|
745
833
|
from AutoGLM_GUI.device_manager import DeviceManager
|
|
746
|
-
from AutoGLM_GUI.state import agents
|
|
747
834
|
|
|
748
835
|
with self._manager_lock:
|
|
749
836
|
# Get device by serial from DeviceManager
|
|
@@ -755,7 +842,7 @@ class PhoneAgentManager:
|
|
|
755
842
|
|
|
756
843
|
# Check all connections for initialized agents
|
|
757
844
|
for conn in device.connections:
|
|
758
|
-
if conn.device_id in
|
|
845
|
+
if conn.device_id in self._agents:
|
|
759
846
|
return conn.device_id
|
|
760
847
|
|
|
761
848
|
return None
|
|
@@ -764,10 +851,8 @@ class PhoneAgentManager:
|
|
|
764
851
|
|
|
765
852
|
def list_agents(self) -> list[str]:
|
|
766
853
|
"""Get list of all initialized device IDs."""
|
|
767
|
-
from AutoGLM_GUI.state import agents
|
|
768
|
-
|
|
769
854
|
with self._manager_lock:
|
|
770
|
-
return list(
|
|
855
|
+
return list(self._agents.keys())
|
|
771
856
|
|
|
772
857
|
def get_metadata(self, device_id: str) -> Optional[AgentMetadata]:
|
|
773
858
|
"""Get agent metadata."""
|
|
@@ -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/prompts.py
CHANGED
|
@@ -11,7 +11,12 @@ from datetime import datetime
|
|
|
11
11
|
today = datetime.today()
|
|
12
12
|
weekday_names = ["星期一", "星期二", "星期三", "星期四", "星期五", "星期六", "星期日"]
|
|
13
13
|
weekday = weekday_names[today.weekday()]
|
|
14
|
-
|
|
14
|
+
# NOTE: Do NOT use strftime with Chinese characters in format string!
|
|
15
|
+
# On some Windows systems with non-UTF-8 locale (e.g., GBK/CP936),
|
|
16
|
+
# strftime("%Y年%m月%d日") raises UnicodeEncodeError because the C library's
|
|
17
|
+
# strftime uses locale encoding, not Python's UTF-8 mode.
|
|
18
|
+
# Use f-string instead to avoid this issue completely.
|
|
19
|
+
formatted_date = f"{today.year}年{today.month:02d}月{today.day:02d}日 {weekday}"
|
|
15
20
|
|
|
16
21
|
MCP_SYSTEM_PROMPT_ZH = f"""
|
|
17
22
|
# Context
|