autoglm-gui 1.4.1__py3-none-any.whl → 1.5.0__py3-none-any.whl

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