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.
Files changed (120) hide show
  1. AutoGLM_GUI/__init__.py +11 -0
  2. AutoGLM_GUI/__main__.py +26 -8
  3. AutoGLM_GUI/actions/__init__.py +6 -0
  4. AutoGLM_GUI/actions/handler.py +196 -0
  5. AutoGLM_GUI/actions/types.py +15 -0
  6. AutoGLM_GUI/adb/__init__.py +53 -0
  7. AutoGLM_GUI/adb/apps.py +227 -0
  8. AutoGLM_GUI/adb/connection.py +323 -0
  9. AutoGLM_GUI/adb/device.py +171 -0
  10. AutoGLM_GUI/adb/input.py +67 -0
  11. AutoGLM_GUI/adb/screenshot.py +11 -0
  12. AutoGLM_GUI/adb/timing.py +167 -0
  13. AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
  14. AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
  15. AutoGLM_GUI/adb_plus/screenshot.py +22 -1
  16. AutoGLM_GUI/adb_plus/serial.py +38 -20
  17. AutoGLM_GUI/adb_plus/touch.py +4 -9
  18. AutoGLM_GUI/agents/__init__.py +51 -0
  19. AutoGLM_GUI/agents/events.py +19 -0
  20. AutoGLM_GUI/agents/factory.py +153 -0
  21. AutoGLM_GUI/agents/glm/__init__.py +7 -0
  22. AutoGLM_GUI/agents/glm/agent.py +292 -0
  23. AutoGLM_GUI/agents/glm/message_builder.py +81 -0
  24. AutoGLM_GUI/agents/glm/parser.py +110 -0
  25. AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
  26. AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
  27. AutoGLM_GUI/agents/mai/__init__.py +28 -0
  28. AutoGLM_GUI/agents/mai/agent.py +405 -0
  29. AutoGLM_GUI/agents/mai/parser.py +254 -0
  30. AutoGLM_GUI/agents/mai/prompts.py +103 -0
  31. AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
  32. AutoGLM_GUI/agents/protocols.py +27 -0
  33. AutoGLM_GUI/agents/stream_runner.py +188 -0
  34. AutoGLM_GUI/api/__init__.py +71 -11
  35. AutoGLM_GUI/api/agents.py +190 -229
  36. AutoGLM_GUI/api/control.py +9 -6
  37. AutoGLM_GUI/api/devices.py +112 -28
  38. AutoGLM_GUI/api/health.py +13 -0
  39. AutoGLM_GUI/api/history.py +78 -0
  40. AutoGLM_GUI/api/layered_agent.py +306 -181
  41. AutoGLM_GUI/api/mcp.py +11 -10
  42. AutoGLM_GUI/api/media.py +64 -1
  43. AutoGLM_GUI/api/scheduled_tasks.py +98 -0
  44. AutoGLM_GUI/api/version.py +23 -10
  45. AutoGLM_GUI/api/workflows.py +2 -1
  46. AutoGLM_GUI/config.py +72 -14
  47. AutoGLM_GUI/config_manager.py +98 -27
  48. AutoGLM_GUI/device_adapter.py +263 -0
  49. AutoGLM_GUI/device_manager.py +248 -29
  50. AutoGLM_GUI/device_protocol.py +266 -0
  51. AutoGLM_GUI/devices/__init__.py +49 -0
  52. AutoGLM_GUI/devices/adb_device.py +200 -0
  53. AutoGLM_GUI/devices/mock_device.py +185 -0
  54. AutoGLM_GUI/devices/remote_device.py +177 -0
  55. AutoGLM_GUI/exceptions.py +3 -3
  56. AutoGLM_GUI/history_manager.py +164 -0
  57. AutoGLM_GUI/i18n.py +81 -0
  58. AutoGLM_GUI/metrics.py +13 -20
  59. AutoGLM_GUI/model/__init__.py +5 -0
  60. AutoGLM_GUI/model/message_builder.py +69 -0
  61. AutoGLM_GUI/model/types.py +24 -0
  62. AutoGLM_GUI/models/__init__.py +10 -0
  63. AutoGLM_GUI/models/history.py +96 -0
  64. AutoGLM_GUI/models/scheduled_task.py +71 -0
  65. AutoGLM_GUI/parsers/__init__.py +22 -0
  66. AutoGLM_GUI/parsers/base.py +50 -0
  67. AutoGLM_GUI/parsers/phone_parser.py +58 -0
  68. AutoGLM_GUI/phone_agent_manager.py +118 -367
  69. AutoGLM_GUI/platform_utils.py +31 -2
  70. AutoGLM_GUI/prompt_config.py +15 -0
  71. AutoGLM_GUI/prompts/__init__.py +32 -0
  72. AutoGLM_GUI/scheduler_manager.py +304 -0
  73. AutoGLM_GUI/schemas.py +272 -63
  74. AutoGLM_GUI/scrcpy_stream.py +159 -37
  75. AutoGLM_GUI/server.py +3 -1
  76. AutoGLM_GUI/socketio_server.py +114 -29
  77. AutoGLM_GUI/state.py +10 -30
  78. AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-BQm96DAl.js} +1 -1
  79. AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
  80. AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
  81. AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
  82. AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
  83. AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
  84. AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
  85. AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-CmZSnDqc.js} +1 -1
  86. AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
  87. AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
  88. AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
  89. AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
  90. AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
  91. AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
  92. AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
  93. AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
  94. AutoGLM_GUI/static/index.html +2 -2
  95. AutoGLM_GUI/types.py +142 -0
  96. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +178 -92
  97. autoglm_gui-1.5.0.dist-info/RECORD +157 -0
  98. mai_agent/base.py +137 -0
  99. mai_agent/mai_grounding_agent.py +263 -0
  100. mai_agent/mai_naivigation_agent.py +526 -0
  101. mai_agent/prompt.py +148 -0
  102. mai_agent/unified_memory.py +67 -0
  103. mai_agent/utils.py +73 -0
  104. AutoGLM_GUI/api/dual_model.py +0 -311
  105. AutoGLM_GUI/dual_model/__init__.py +0 -53
  106. AutoGLM_GUI/dual_model/decision_model.py +0 -664
  107. AutoGLM_GUI/dual_model/dual_agent.py +0 -917
  108. AutoGLM_GUI/dual_model/protocols.py +0 -354
  109. AutoGLM_GUI/dual_model/vision_model.py +0 -442
  110. AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
  111. AutoGLM_GUI/phone_agent_patches.py +0 -146
  112. AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
  113. AutoGLM_GUI/static/assets/dialog-BfdcBs1x.js +0 -45
  114. AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
  115. AutoGLM_GUI/static/assets/index-DHF1NZh0.js +0 -12
  116. AutoGLM_GUI/static/assets/workflows-xiplap-r.js +0 -1
  117. autoglm_gui-1.4.0.dist-info/RECORD +0 -100
  118. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
  119. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
  120. {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
@@ -1,4 +1,4 @@
1
- """PhoneAgent lifecycle and concurrency manager (singleton)."""
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 TYPE_CHECKING, Callable, Optional
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 a PhoneAgent instance."""
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
- created_at: float
43
- last_used: float
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
- """Streaming agent 会话上下文."""
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 PhoneAgent lifecycle and concurrency control.
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
- >>> # Initialize agent
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
- # Abort events (device_id -> threading.Event)
108
- self._abort_events: dict[str, threading.Event] = {}
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 initialize_agent(
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
- ) -> PhoneAgent:
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
- # Check if already initialized
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 agents[device_id]
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
- # Set initializing state
169
- self._states[device_id] = AgentState.INITIALIZING
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
- # Create agent
173
- agent = PhoneAgent(
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
- takeover_callback=takeover_callback or non_blocking_takeover,
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
- # Store in state (transactional)
180
- agents[device_id] = agent
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
- # Update metadata
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(f"Agent initialized for device {device_id}")
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
- # Rollback on error
199
- agents.pop(device_id, None)
200
- agent_configs.pop(device_id, None)
201
- self._metadata.pop(device_id, None)
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 phone_agent.agent import AgentConfig
359
- from phone_agent.model import ModelConfig
205
+ from typing import cast
360
206
 
361
- from AutoGLM_GUI.config import config
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
- # 调用 initialize_agent(RLock 支持重入,不会死锁)
388
- self.initialize_agent(device_id, model_config, agent_config)
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) -> PhoneAgent:
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 agents:
410
- # 自动初始化:使用全局配置
249
+ if device_id not in self._agents:
411
250
  self._auto_initialize_agent(device_id)
412
- return agents[device_id]
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 agents.get(device_id)
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 and rebuild from cached config.
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 agents:
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
- # Get cached config
450
- if device_id not in agent_configs:
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 = agents.pop(device_id, None)
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
- agent_configs.pop(device_id, None)
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 agents
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
- self._states[device_id] = AgentState.IDLE
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 initialize_agent().
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
- PhoneAgent: Agent instance
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
- return self._states.get(device_id, AgentState.ERROR)
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 agent_configs:
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(agents.keys())
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].set()
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}")