autoglm-gui 1.5.0__py3-none-any.whl → 1.5.2__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 (97) hide show
  1. AutoGLM_GUI/__init__.py +1 -1
  2. AutoGLM_GUI/__main__.py +11 -2
  3. AutoGLM_GUI/adb_plus/qr_pair.py +3 -3
  4. AutoGLM_GUI/agents/__init__.py +7 -2
  5. AutoGLM_GUI/agents/factory.py +46 -6
  6. AutoGLM_GUI/agents/glm/agent.py +8 -3
  7. AutoGLM_GUI/agents/glm/async_agent.py +515 -0
  8. AutoGLM_GUI/agents/glm/parser.py +4 -2
  9. AutoGLM_GUI/agents/mai/agent.py +3 -0
  10. AutoGLM_GUI/agents/protocols.py +111 -1
  11. AutoGLM_GUI/agents/stream_runner.py +11 -7
  12. AutoGLM_GUI/api/__init__.py +3 -1
  13. AutoGLM_GUI/api/agents.py +103 -37
  14. AutoGLM_GUI/api/devices.py +72 -0
  15. AutoGLM_GUI/api/history.py +27 -1
  16. AutoGLM_GUI/api/layered_agent.py +9 -8
  17. AutoGLM_GUI/api/mcp.py +6 -4
  18. AutoGLM_GUI/config_manager.py +38 -1
  19. AutoGLM_GUI/device_manager.py +28 -4
  20. AutoGLM_GUI/device_metadata_manager.py +174 -0
  21. AutoGLM_GUI/devices/mock_device.py +8 -1
  22. AutoGLM_GUI/models/history.py +45 -1
  23. AutoGLM_GUI/phone_agent_manager.py +145 -32
  24. AutoGLM_GUI/scheduler_manager.py +52 -6
  25. AutoGLM_GUI/schemas.py +101 -0
  26. AutoGLM_GUI/scrcpy_stream.py +2 -1
  27. AutoGLM_GUI/static/assets/{about-BQm96DAl.js → about-D7r9gCvG.js} +1 -1
  28. AutoGLM_GUI/static/assets/{alert-dialog-B42XxGPR.js → alert-dialog-BKM-yRiQ.js} +1 -1
  29. AutoGLM_GUI/static/assets/chat-k6TTD7PW.js +129 -0
  30. AutoGLM_GUI/static/assets/{circle-alert-D4rSJh37.js → circle-alert-sohSDLhl.js} +1 -1
  31. AutoGLM_GUI/static/assets/{dialog-DZ78cEcj.js → dialog-BgtPh0d5.js} +1 -1
  32. AutoGLM_GUI/static/assets/eye-DLqKbQmg.js +1 -0
  33. AutoGLM_GUI/static/assets/history-Bv1lfGUU.js +1 -0
  34. AutoGLM_GUI/static/assets/index-CV7jGxGm.css +1 -0
  35. AutoGLM_GUI/static/assets/index-CxWwh1VO.js +1 -0
  36. AutoGLM_GUI/static/assets/{index-CssG-3TH.js → index-SysdKciY.js} +5 -5
  37. AutoGLM_GUI/static/assets/label-DTUnzN4B.js +1 -0
  38. AutoGLM_GUI/static/assets/{logs-eoFxn5of.js → logs-BIhnDizW.js} +1 -1
  39. AutoGLM_GUI/static/assets/{popover-DLsuV5Sx.js → popover-CikYqu2P.js} +1 -1
  40. AutoGLM_GUI/static/assets/scheduled-tasks-B-KBsGbl.js +1 -0
  41. AutoGLM_GUI/static/assets/{textarea-BX6y7uM5.js → textarea-knJZrz77.js} +1 -1
  42. AutoGLM_GUI/static/assets/workflows-DzcSYwLZ.js +1 -0
  43. AutoGLM_GUI/static/index.html +2 -2
  44. {autoglm_gui-1.5.0.dist-info → autoglm_gui-1.5.2.dist-info}/METADATA +58 -7
  45. autoglm_gui-1.5.2.dist-info/RECORD +119 -0
  46. AutoGLM_GUI/device_adapter.py +0 -263
  47. AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +0 -129
  48. AutoGLM_GUI/static/assets/history-DFBv7TGc.js +0 -1
  49. AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +0 -1
  50. AutoGLM_GUI/static/assets/index-CmZSnDqc.js +0 -1
  51. AutoGLM_GUI/static/assets/label-BCUzE_nm.js +0 -1
  52. AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +0 -1
  53. AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +0 -1
  54. AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +0 -1
  55. autoglm_gui-1.5.0.dist-info/RECORD +0 -157
  56. mai_agent/base.py +0 -137
  57. mai_agent/mai_grounding_agent.py +0 -263
  58. mai_agent/mai_naivigation_agent.py +0 -526
  59. mai_agent/prompt.py +0 -148
  60. mai_agent/unified_memory.py +0 -67
  61. mai_agent/utils.py +0 -73
  62. phone_agent/__init__.py +0 -12
  63. phone_agent/actions/__init__.py +0 -5
  64. phone_agent/actions/handler.py +0 -400
  65. phone_agent/actions/handler_ios.py +0 -278
  66. phone_agent/adb/__init__.py +0 -51
  67. phone_agent/adb/connection.py +0 -358
  68. phone_agent/adb/device.py +0 -253
  69. phone_agent/adb/input.py +0 -108
  70. phone_agent/adb/screenshot.py +0 -108
  71. phone_agent/agent.py +0 -253
  72. phone_agent/agent_ios.py +0 -277
  73. phone_agent/config/__init__.py +0 -53
  74. phone_agent/config/apps.py +0 -227
  75. phone_agent/config/apps_harmonyos.py +0 -256
  76. phone_agent/config/apps_ios.py +0 -339
  77. phone_agent/config/i18n.py +0 -81
  78. phone_agent/config/prompts.py +0 -80
  79. phone_agent/config/prompts_en.py +0 -79
  80. phone_agent/config/prompts_zh.py +0 -82
  81. phone_agent/config/timing.py +0 -167
  82. phone_agent/device_factory.py +0 -166
  83. phone_agent/hdc/__init__.py +0 -53
  84. phone_agent/hdc/connection.py +0 -384
  85. phone_agent/hdc/device.py +0 -269
  86. phone_agent/hdc/input.py +0 -145
  87. phone_agent/hdc/screenshot.py +0 -127
  88. phone_agent/model/__init__.py +0 -5
  89. phone_agent/model/client.py +0 -290
  90. phone_agent/xctest/__init__.py +0 -47
  91. phone_agent/xctest/connection.py +0 -379
  92. phone_agent/xctest/device.py +0 -472
  93. phone_agent/xctest/input.py +0 -311
  94. phone_agent/xctest/screenshot.py +0 -226
  95. {autoglm_gui-1.5.0.dist-info → autoglm_gui-1.5.2.dist-info}/WHEEL +0 -0
  96. {autoglm_gui-1.5.0.dist-info → autoglm_gui-1.5.2.dist-info}/entry_points.txt +0 -0
  97. {autoglm_gui-1.5.0.dist-info → autoglm_gui-1.5.2.dist-info}/licenses/LICENSE +0 -0
@@ -2,14 +2,15 @@
2
2
 
3
3
  from __future__ import annotations
4
4
 
5
+ import asyncio
5
6
  import threading
6
7
  import time
7
8
  from contextlib import contextmanager
8
9
  from dataclasses import dataclass
9
10
  from enum import Enum
10
- from typing import Callable, Optional
11
+ from typing import Awaitable, Callable, Optional
11
12
 
12
- from AutoGLM_GUI.agents.protocols import BaseAgent
13
+ from AutoGLM_GUI.agents.protocols import AsyncAgent, BaseAgent
13
14
  from AutoGLM_GUI.config import AgentConfig, ModelConfig
14
15
  from AutoGLM_GUI.exceptions import (
15
16
  AgentInitializationError,
@@ -96,10 +97,13 @@ class PhoneAgentManager:
96
97
  self._streaming_contexts: dict[str, StreamingAgentContext] = {}
97
98
  self._streaming_contexts_lock = threading.Lock()
98
99
 
99
- self._abort_events: dict[str, threading.Event | Callable[[], None]] = {}
100
+ self._abort_events: dict[
101
+ str, threading.Event | Callable[[], None] | Callable[[], Awaitable[None]]
102
+ ] = {}
100
103
 
101
104
  # Agent storage (transition from global state to instance state)
102
- self._agents: dict[str, BaseAgent] = {}
105
+ # Agents can be either AsyncAgent or BaseAgent depending on agent_type
106
+ self._agents: dict[str, AsyncAgent | BaseAgent] = {}
103
107
  self._agent_configs: dict[str, tuple[ModelConfig, AgentConfig]] = {}
104
108
 
105
109
  @classmethod
@@ -124,7 +128,7 @@ class PhoneAgentManager:
124
128
  takeover_callback: Optional[Callable] = None,
125
129
  confirmation_callback: Optional[Callable] = None,
126
130
  force: bool = False,
127
- ) -> "BaseAgent":
131
+ ) -> AsyncAgent | BaseAgent:
128
132
  from AutoGLM_GUI.agents import create_agent
129
133
 
130
134
  with self._manager_lock:
@@ -152,12 +156,19 @@ class PhoneAgentManager:
152
156
  from AutoGLM_GUI.device_manager import DeviceManager
153
157
 
154
158
  device_manager = DeviceManager.get_instance()
159
+ # Use agent_config.device_id (actual device ID) instead of device_id (storage key)
160
+ # to get device protocol, as device_id may be a composite key like "device_id:context"
161
+ actual_device_id = agent_config.device_id
162
+ if not actual_device_id:
163
+ raise AgentInitializationError(
164
+ "agent_config.device_id is required but was None"
165
+ )
155
166
  try:
156
- device = device_manager.get_device_protocol(device_id)
167
+ device = device_manager.get_device_protocol(actual_device_id)
157
168
  except ValueError:
158
169
  # Ensure cold starts refresh device cache before failing.
159
170
  device_manager.force_refresh()
160
- device = device_manager.get_device_protocol(device_id)
171
+ device = device_manager.get_device_protocol(actual_device_id)
161
172
 
162
173
  agent = create_agent(
163
174
  agent_type=agent_type,
@@ -190,14 +201,18 @@ class PhoneAgentManager:
190
201
  f"Failed to initialize agent: {str(e)}"
191
202
  ) from e
192
203
 
193
- def _auto_initialize_agent(self, device_id: str) -> None:
204
+ def _auto_initialize_agent(
205
+ self, agent_key: str, actual_device_id: str, agent_type: str | None = None
206
+ ) -> None:
194
207
  """
195
208
  使用全局配置自动初始化 agent(内部方法,需在 manager_lock 内调用).
196
209
 
197
210
  使用 factory 模式创建 agent,避免直接依赖 phone_agent.PhoneAgent。
198
211
 
199
212
  Args:
200
- device_id: 设备标识符
213
+ agent_key: Agent 存储键(可能是 device_id 或 device_id:context)
214
+ actual_device_id: 实际设备标识符(用于设备操作)
215
+ agent_type: 可选的 agent 类型覆盖
201
216
 
202
217
  Raises:
203
218
  AgentInitializationError: 如果配置不完整或初始化失败
@@ -208,7 +223,9 @@ class PhoneAgentManager:
208
223
  from AutoGLM_GUI.config_manager import config_manager
209
224
  from AutoGLM_GUI.types import AgentSpecificConfig
210
225
 
211
- logger.info(f"Auto-initializing agent for device {device_id}...")
226
+ logger.info(
227
+ f"Auto-initializing agent for key {agent_key} (device: {actual_device_id})..."
228
+ )
212
229
 
213
230
  # 热重载配置
214
231
  config_manager.load_file_config()
@@ -218,7 +235,7 @@ class PhoneAgentManager:
218
235
 
219
236
  if not effective_config.base_url:
220
237
  raise AgentInitializationError(
221
- f"Cannot auto-initialize agent for {device_id}: base_url not configured. "
238
+ f"Cannot auto-initialize agent for {agent_key}: base_url not configured. "
222
239
  f"Please configure base_url via /api/config or call /api/init explicitly."
223
240
  )
224
241
 
@@ -229,28 +246,54 @@ class PhoneAgentManager:
229
246
  model_name=effective_config.model_name,
230
247
  )
231
248
 
232
- agent_config = AgentConfig(device_id=device_id)
249
+ # 使用实际的 device_id 创建 AgentConfig
250
+ agent_config = AgentConfig(device_id=actual_device_id)
233
251
 
234
252
  # 调用 factory 方法创建 agent(避免直接依赖 phone_agent)
235
253
  agent_specific_config = cast(
236
254
  AgentSpecificConfig, effective_config.agent_config_params or {}
237
255
  )
256
+ # 使用提供的 agent_type 或从配置中获取
257
+ effective_agent_type = agent_type or effective_config.agent_type
238
258
  self.initialize_agent_with_factory(
239
- device_id=device_id,
240
- agent_type=effective_config.agent_type,
259
+ device_id=agent_key,
260
+ agent_type=effective_agent_type,
241
261
  model_config=model_config,
242
262
  agent_config=agent_config,
243
263
  agent_specific_config=agent_specific_config,
244
264
  )
245
- logger.info(f"Agent auto-initialized for device {device_id}")
265
+ logger.info(f"Agent auto-initialized for key {agent_key}")
266
+
267
+ def get_agent(self, device_id: str) -> AsyncAgent | BaseAgent:
268
+ """Get agent using default context (backward compatible)."""
269
+ return self.get_agent_with_context(device_id, context="default")
270
+
271
+ def get_agent_with_context(
272
+ self,
273
+ device_id: str,
274
+ context: str = "default",
275
+ agent_type: str | None = None,
276
+ ) -> AsyncAgent | BaseAgent:
277
+ """Get or create agent for specific context.
246
278
 
247
- def get_agent(self, device_id: str) -> BaseAgent:
279
+ Args:
280
+ device_id: Device identifier
281
+ context: Context identifier (e.g., "chat", "default")
282
+ agent_type: Optional agent type override
283
+
284
+ Returns:
285
+ Agent instance for this device+context combination
286
+ """
248
287
  with self._manager_lock:
249
- if device_id not in self._agents:
250
- self._auto_initialize_agent(device_id)
251
- return self._agents[device_id]
288
+ # Use composite key for context isolation (except for default)
289
+ agent_key = device_id if context == "default" else f"{device_id}:{context}"
252
290
 
253
- def get_agent_safe(self, device_id: str) -> Optional[BaseAgent]:
291
+ if agent_key not in self._agents:
292
+ self._auto_initialize_agent(agent_key, device_id, agent_type=agent_type)
293
+
294
+ return self._agents[agent_key]
295
+
296
+ def get_agent_safe(self, device_id: str) -> AsyncAgent | BaseAgent | None:
254
297
  with self._manager_lock:
255
298
  return self._agents.get(device_id)
256
299
 
@@ -363,7 +406,7 @@ class PhoneAgentManager:
363
406
  # Double-check locking pattern for thread safety
364
407
  with self._manager_lock:
365
408
  if not self.is_initialized(device_id):
366
- self._auto_initialize_agent(device_id)
409
+ self._auto_initialize_agent(device_id, device_id)
367
410
  else:
368
411
  raise AgentNotInitializedError(
369
412
  f"Agent not initialized for device {device_id}. "
@@ -512,18 +555,62 @@ class PhoneAgentManager:
512
555
  return self._metadata.get(device_id)
513
556
 
514
557
  def register_abort_handler(
515
- self, device_id: str, abort_handler: threading.Event | Callable[[], None]
558
+ self,
559
+ device_id: str,
560
+ abort_handler: threading.Event
561
+ | Callable[[], None]
562
+ | Callable[[], Awaitable[None]],
516
563
  ) -> None:
564
+ """注册取消处理器 (支持同步和异步处理器)。
565
+
566
+ Args:
567
+ device_id: 设备标识符
568
+ abort_handler: 取消处理器 (Event / 同步函数 / 异步函数)
569
+ """
517
570
  with self._streaming_contexts_lock:
518
571
  self._abort_events[device_id] = abort_handler
519
572
 
520
573
  def unregister_abort_handler(self, device_id: str) -> None:
574
+ """注销取消处理器。
575
+
576
+ Args:
577
+ device_id: 设备标识符
578
+ """
521
579
  with self._streaming_contexts_lock:
522
580
  self._abort_events.pop(device_id, None)
523
581
 
524
- def abort_streaming_chat(self, device_id: str) -> bool:
582
+ async def abort_streaming_chat_async(self, device_id: str) -> bool:
583
+ """异步中止流式对话 (支持 AsyncAgent)。
584
+
585
+ Args:
586
+ device_id: 设备标识符
587
+
588
+ Returns:
589
+ bool: True 表示发送了中止信号,False 表示没有活跃会话
525
590
  """
526
- 中止正在进行的流式对话.
591
+ with self._streaming_contexts_lock:
592
+ if device_id not in self._abort_events:
593
+ logger.warning(f"No active streaming chat for device {device_id}")
594
+ return False
595
+
596
+ logger.info(f"Aborting async streaming chat for device {device_id}")
597
+ handler = self._abort_events[device_id]
598
+
599
+ # 执行取消 (根据类型选择方式)
600
+ if isinstance(handler, threading.Event):
601
+ handler.set()
602
+ elif asyncio.iscoroutinefunction(handler):
603
+ await handler()
604
+ elif callable(handler):
605
+ handler()
606
+ else:
607
+ logger.warning(f"Unknown abort handler type: {type(handler)}")
608
+ return False
609
+
610
+ return True
611
+
612
+ def abort_streaming_chat(self, device_id: str) -> bool:
613
+ """同步中止流式对话 (向后兼容)。
527
614
 
528
615
  Args:
529
616
  device_id: 设备标识符
@@ -532,16 +619,42 @@ class PhoneAgentManager:
532
619
  bool: True 表示发送了中止信号,False 表示没有活跃会话
533
620
  """
534
621
  with self._streaming_contexts_lock:
535
- if device_id in self._abort_events:
536
- logger.info(f"Aborting streaming chat for device {device_id}")
537
- handler = self._abort_events[device_id]
538
- if isinstance(handler, threading.Event):
539
- handler.set()
540
- elif callable(handler):
541
- handler()
622
+ if device_id not in self._abort_events:
623
+ logger.warning(f"No active streaming chat for device {device_id}")
624
+ return False
625
+
626
+ logger.info(f"Aborting streaming chat for device {device_id}")
627
+ handler = self._abort_events[device_id]
628
+
629
+ if isinstance(handler, threading.Event):
630
+ handler.set()
631
+ return True
632
+ elif asyncio.iscoroutinefunction(handler):
633
+ logger.warning(
634
+ f"Detected async handler for {device_id}, "
635
+ f"but called sync abort. Use abort_streaming_chat_async instead."
636
+ )
637
+ # 尝试在当前线程的 event loop 中运行
638
+ try:
639
+ loop = asyncio.get_event_loop()
640
+ if loop.is_running():
641
+ # 不能在运行中的 loop 中调用 run_until_complete
642
+ # 创建一个 task
643
+ asyncio.create_task(self.abort_streaming_chat_async(device_id))
644
+ return True
645
+ else:
646
+ loop.run_until_complete(
647
+ self.abort_streaming_chat_async(device_id)
648
+ )
649
+ return True
650
+ except RuntimeError:
651
+ logger.error("Cannot abort async agent from sync context")
652
+ return False
653
+ elif callable(handler):
654
+ handler()
542
655
  return True
543
656
  else:
544
- logger.warning(f"No active streaming chat for device {device_id}")
657
+ logger.warning(f"Unknown abort handler type: {type(handler)}")
545
658
  return False
546
659
 
547
660
  def is_streaming_active(self, device_id: str) -> bool:
@@ -177,7 +177,7 @@ class SchedulerManager:
177
177
 
178
178
  from AutoGLM_GUI.device_manager import DeviceManager
179
179
  from AutoGLM_GUI.history_manager import history_manager
180
- from AutoGLM_GUI.models.history import ConversationRecord
180
+ from AutoGLM_GUI.models.history import ConversationRecord, MessageRecord
181
181
  from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
182
182
  from AutoGLM_GUI.workflow_manager import workflow_manager
183
183
 
@@ -210,27 +210,72 @@ class SchedulerManager:
210
210
  return
211
211
 
212
212
  start_time = datetime.now()
213
+
214
+ # 收集完整对话消息
215
+ messages: list[MessageRecord] = []
216
+ messages.append(
217
+ MessageRecord(
218
+ role="user",
219
+ content=workflow["text"],
220
+ timestamp=start_time,
221
+ )
222
+ )
223
+
213
224
  try:
214
225
  agent = manager.get_agent(device.primary_device_id)
215
226
  agent.reset()
216
- result = agent.run(workflow["text"])
217
- steps = agent.step_count
218
227
 
228
+ # 使用 step 循环执行,收集每步信息
229
+ is_first = True
230
+ result_message = ""
231
+ task_success = False
232
+
233
+ while agent.step_count < agent.agent_config.max_steps:
234
+ step_result = agent.step(workflow["text"] if is_first else None) # type: ignore[misc]
235
+ is_first = False
236
+
237
+ # 收集每个 step 的消息
238
+ messages.append(
239
+ MessageRecord(
240
+ role="assistant",
241
+ content="",
242
+ timestamp=datetime.now(),
243
+ thinking=step_result.thinking, # type: ignore[union-attr]
244
+ action=step_result.action, # type: ignore[union-attr]
245
+ step=agent.step_count,
246
+ )
247
+ )
248
+
249
+ if step_result.finished: # type: ignore[union-attr]
250
+ result_message = step_result.message or "Task completed" # type: ignore[union-attr]
251
+ task_success = step_result.success # type: ignore[union-attr]
252
+ break
253
+ else:
254
+ result_message = "Max steps reached"
255
+ task_success = False
256
+
257
+ steps = agent.step_count
219
258
  end_time = datetime.now()
259
+
220
260
  record = ConversationRecord(
221
261
  task_text=workflow["text"],
222
- final_message=result,
223
- success=True,
262
+ final_message=result_message,
263
+ success=task_success,
224
264
  steps=steps,
225
265
  start_time=start_time,
226
266
  end_time=end_time,
227
267
  duration_ms=int((end_time - start_time).total_seconds() * 1000),
228
268
  source="scheduled",
229
269
  source_detail=task.name,
270
+ error_message=None if task_success else result_message,
271
+ messages=messages,
230
272
  )
231
273
  history_manager.add_record(task.device_serialno, record)
232
274
 
233
- self._record_success(task, result)
275
+ if task_success:
276
+ self._record_success(task, result_message)
277
+ else:
278
+ self._record_failure(task, result_message)
234
279
 
235
280
  except Exception as e:
236
281
  end_time = datetime.now()
@@ -248,6 +293,7 @@ class SchedulerManager:
248
293
  source="scheduled",
249
294
  source_detail=task.name,
250
295
  error_message=error_msg,
296
+ messages=messages,
251
297
  )
252
298
  history_manager.add_record(task.device_serialno, record)
253
299
 
AutoGLM_GUI/schemas.py CHANGED
@@ -4,6 +4,8 @@ import re
4
4
 
5
5
  from pydantic import BaseModel, field_validator
6
6
 
7
+ from AutoGLM_GUI.device_metadata_manager import DISPLAY_NAME_MAX_LENGTH
8
+
7
9
 
8
10
  class InitRequest(BaseModel):
9
11
  device_id: str # Device ID (required)
@@ -274,6 +276,7 @@ class DeviceResponse(BaseModel):
274
276
  connection_type: str
275
277
  state: str
276
278
  is_available_only: bool
279
+ display_name: str | None = None
277
280
  agent: AgentStatusResponse | None = None
278
281
 
279
282
 
@@ -296,6 +299,9 @@ class ConfigResponse(BaseModel):
296
299
  # Agent 执行配置
297
300
  default_max_steps: int = 100 # 单次任务最大执行步数
298
301
 
302
+ # 分层代理配置
303
+ layered_max_turns: int = 50 # 分层代理模式的最大轮次
304
+
299
305
  # 决策模型配置(用于分层代理)
300
306
  decision_base_url: str | None = None
301
307
  decision_model_name: str | None = None
@@ -318,6 +324,9 @@ class ConfigSaveRequest(BaseModel):
318
324
  # Agent 执行配置
319
325
  default_max_steps: int | None = None # 单次任务最大执行步数
320
326
 
327
+ # 分层代理配置
328
+ layered_max_turns: int | None = None # 分层代理模式的最大轮次
329
+
321
330
  # 决策模型配置(用于分层代理)
322
331
  decision_base_url: str | None = None
323
332
  decision_model_name: str | None = None
@@ -335,6 +344,15 @@ class ConfigSaveRequest(BaseModel):
335
344
  raise ValueError("default_max_steps must be <= 1000")
336
345
  return v
337
346
 
347
+ @field_validator("layered_max_turns")
348
+ @classmethod
349
+ def validate_layered_max_turns(cls, v: int | None) -> int | None:
350
+ if v is None:
351
+ return v
352
+ if v < 1:
353
+ raise ValueError("layered_max_turns must be >= 1")
354
+ return v
355
+
338
356
  @field_validator("base_url")
339
357
  @classmethod
340
358
  def validate_base_url(cls, v: str) -> str:
@@ -702,6 +720,17 @@ class ReinitAllAgentsResponse(BaseModel):
702
720
  # History Models
703
721
 
704
722
 
723
+ class MessageRecordResponse(BaseModel):
724
+ """对话消息响应."""
725
+
726
+ role: str # "user" | "assistant"
727
+ content: str
728
+ timestamp: str
729
+ thinking: str | None = None
730
+ action: dict | None = None
731
+ step: int | None = None
732
+
733
+
705
734
  class HistoryRecordResponse(BaseModel):
706
735
  """历史记录条目响应."""
707
736
 
@@ -716,6 +745,7 @@ class HistoryRecordResponse(BaseModel):
716
745
  source: str
717
746
  source_detail: str
718
747
  error_message: str | None
748
+ messages: list[MessageRecordResponse] = []
719
749
 
720
750
 
721
751
  class HistoryListResponse(BaseModel):
@@ -804,3 +834,74 @@ class ScheduledTaskListResponse(BaseModel):
804
834
  """定时任务列表响应."""
805
835
 
806
836
  tasks: list[ScheduledTaskResponse]
837
+
838
+
839
+ class DeleteResponse(BaseModel):
840
+ success: bool
841
+ message: str
842
+
843
+
844
+ class ResetResponse(BaseModel):
845
+ success: bool
846
+ message: str
847
+ device_id: str
848
+
849
+
850
+ class ConfigSaveResponse(BaseModel):
851
+ success: bool
852
+ message: str
853
+ warnings: list[str] | None = None
854
+ destroyed_agents: int
855
+
856
+
857
+ class InitResponse(BaseModel):
858
+ success: bool
859
+ message: str
860
+ device_id: str
861
+ agent_type: str
862
+
863
+
864
+ class StreamResetResponse(BaseModel):
865
+ success: bool
866
+ message: str
867
+ device_id: str | None = None
868
+
869
+
870
+ class EnableDisableResponse(BaseModel):
871
+ success: bool
872
+ message: str
873
+ task_id: str
874
+ enabled: bool
875
+
876
+
877
+ # Device Name Models
878
+
879
+
880
+ class DeviceNameUpdateRequest(BaseModel):
881
+ """更新设备显示名称请求."""
882
+
883
+ display_name: str | None
884
+
885
+ @field_validator("display_name")
886
+ @classmethod
887
+ def validate_display_name(cls, v: str | None) -> str | None:
888
+ """验证 display_name."""
889
+ if v is None:
890
+ return v
891
+ v = v.strip()
892
+ if not v:
893
+ return None
894
+ if len(v) > DISPLAY_NAME_MAX_LENGTH:
895
+ raise ValueError(
896
+ f"display_name too long (max {DISPLAY_NAME_MAX_LENGTH} characters)"
897
+ )
898
+ return v
899
+
900
+
901
+ class DeviceNameResponse(BaseModel):
902
+ """设备显示名称响应."""
903
+
904
+ success: bool
905
+ serial: str
906
+ display_name: str | None = None
907
+ error: str | None = None
@@ -9,6 +9,7 @@ import time
9
9
  from dataclasses import dataclass
10
10
  from pathlib import Path
11
11
  from asyncio.subprocess import Process as AsyncProcess
12
+ from typing import AsyncGenerator
12
13
 
13
14
  from AutoGLM_GUI.adb_plus import check_device_available
14
15
  from AutoGLM_GUI.logger import logger
@@ -535,7 +536,7 @@ class ScrcpyStreamer:
535
536
  pts=pts,
536
537
  )
537
538
 
538
- async def iter_packets(self):
539
+ async def iter_packets(self) -> AsyncGenerator[ScrcpyMediaStreamPacket, None]:
539
540
  """Yield packets continuously from the scrcpy stream."""
540
541
  while True:
541
542
  yield await self.read_media_packet()
@@ -1 +1 @@
1
- import{j as o}from"./index-CssG-3TH.js";function t(){return o.jsx("div",{className:"p-2",children:o.jsx("h3",{children:"About"})})}export{t as component};
1
+ import{j as o}from"./index-SysdKciY.js";function t(){return o.jsx("div",{className:"p-2",children:o.jsx("h3",{children:"About"})})}export{t as component};
@@ -1 +1 @@
1
- import{o as u,r as o,j as a,b as r,B as d}from"./index-CssG-3TH.js";import{P as g,b as x,c as f,d as m}from"./popover-DLsuV5Sx.js";import{D as p,d as h,e as w,f as j,g as D}from"./dialog-DZ78cEcj.js";const N=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],b=u("check",N),c=o.createContext(void 0),P=({value:t="",onValueChange:e,children:s})=>{const[n,l]=o.useState(!1);return a.jsx(c.Provider,{value:{value:t,onValueChange:e||(()=>{}),open:n,setOpen:l},children:a.jsx(g,{open:n,onOpenChange:l,children:s})})},C=o.forwardRef(({className:t,children:e,...s},n)=>{if(!o.useContext(c))throw new Error("SelectTrigger must be used within Select");return a.jsx(x,{asChild:!0,children:a.jsxs("button",{ref:n,className:r("flex h-10 w-full items-center justify-between rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus:ring-slate-300",t),...s,children:[e,a.jsx(f,{className:"h-4 w-4 opacity-50"})]})})});C.displayName="SelectTrigger";const V=({placeholder:t})=>{const e=o.useContext(c);if(!e)throw new Error("SelectValue must be used within Select");return a.jsx("span",{className:e.value?"":"text-slate-500",children:e.value||t})},I=({children:t,className:e})=>a.jsx(m,{className:r("w-[var(--radix-popover-trigger-width)] p-1",e),children:t}),O=({value:t,children:e,disabled:s,className:n})=>{const l=o.useContext(c);if(!l)throw new Error("SelectItem must be used within Select");const i=l.value===t;return a.jsxs("div",{role:"option","aria-selected":i,className:r("relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-slate-100 focus:bg-slate-100 dark:hover:bg-slate-800 dark:focus:bg-slate-800",s&&"pointer-events-none opacity-50",n),onClick:()=>{s||(l.onValueChange(t),l.setOpen(!1))},children:[a.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:i&&a.jsx(b,{className:"h-4 w-4"})}),e]})},B=({open:t,onOpenChange:e,children:s})=>a.jsx(p,{open:t,onOpenChange:e,children:s}),v=o.forwardRef(({className:t,...e},s)=>a.jsx(h,{ref:s,className:r("sm:max-w-[425px]",t),...e}));v.displayName="AlertDialogContent";const F=({className:t,...e})=>a.jsx(w,{className:r(t),...e}),H=({className:t,...e})=>a.jsx(D,{className:r(t),...e}),A=o.forwardRef(({className:t,...e},s)=>a.jsx(j,{ref:s,className:r(t),...e}));A.displayName="AlertDialogTitle";const S=o.forwardRef(({className:t,...e},s)=>a.jsx("p",{ref:s,className:r("text-sm text-slate-500 dark:text-slate-400",t),...e}));S.displayName="AlertDialogDescription";const y=o.forwardRef(({className:t,...e},s)=>a.jsx(d,{ref:s,className:r(t),...e}));y.displayName="AlertDialogAction";const k=o.forwardRef(({className:t,...e},s)=>a.jsx(d,{ref:s,variant:"outline",className:r(t),...e}));k.displayName="AlertDialogCancel";export{B as A,P as S,C as a,V as b,I as c,O as d,v as e,F as f,A as g,S as h,H as i,k as j,y as k};
1
+ import{o as u,r as o,j as a,b as r,B as d}from"./index-SysdKciY.js";import{P as g,c as x,b as f,d as m}from"./popover-CikYqu2P.js";import{D as p,d as h,e as w,f as j,g as D}from"./dialog-BgtPh0d5.js";const N=[["path",{d:"M20 6 9 17l-5-5",key:"1gmf2c"}]],b=u("check",N),c=o.createContext(void 0),P=({value:t="",onValueChange:e,children:s})=>{const[n,l]=o.useState(!1);return a.jsx(c.Provider,{value:{value:t,onValueChange:e||(()=>{}),open:n,setOpen:l},children:a.jsx(g,{open:n,onOpenChange:l,children:s})})},C=o.forwardRef(({className:t,children:e,...s},n)=>{if(!o.useContext(c))throw new Error("SelectTrigger must be used within Select");return a.jsx(x,{asChild:!0,children:a.jsxs("button",{ref:n,className:r("flex h-10 w-full items-center justify-between rounded-md border border-slate-200 bg-white px-3 py-2 text-sm ring-offset-white placeholder:text-slate-500 focus:outline-none focus:ring-2 focus:ring-slate-950 focus:ring-offset-2 disabled:cursor-not-allowed disabled:opacity-50 dark:border-slate-800 dark:bg-slate-950 dark:ring-offset-slate-950 dark:placeholder:text-slate-400 dark:focus:ring-slate-300",t),...s,children:[e,a.jsx(f,{className:"h-4 w-4 opacity-50"})]})})});C.displayName="SelectTrigger";const V=({placeholder:t})=>{const e=o.useContext(c);if(!e)throw new Error("SelectValue must be used within Select");return a.jsx("span",{className:e.value?"":"text-slate-500",children:e.value||t})},I=({children:t,className:e})=>a.jsx(m,{className:r("w-[var(--radix-popover-trigger-width)] p-1",e),children:t}),O=({value:t,children:e,disabled:s,className:n})=>{const l=o.useContext(c);if(!l)throw new Error("SelectItem must be used within Select");const i=l.value===t;return a.jsxs("div",{role:"option","aria-selected":i,className:r("relative flex w-full cursor-pointer select-none items-center rounded-sm py-1.5 pl-8 pr-2 text-sm outline-none hover:bg-slate-100 focus:bg-slate-100 dark:hover:bg-slate-800 dark:focus:bg-slate-800",s&&"pointer-events-none opacity-50",n),onClick:()=>{s||(l.onValueChange(t),l.setOpen(!1))},children:[a.jsx("span",{className:"absolute left-2 flex h-3.5 w-3.5 items-center justify-center",children:i&&a.jsx(b,{className:"h-4 w-4"})}),e]})},B=({open:t,onOpenChange:e,children:s})=>a.jsx(p,{open:t,onOpenChange:e,children:s}),v=o.forwardRef(({className:t,...e},s)=>a.jsx(h,{ref:s,className:r("sm:max-w-[425px]",t),...e}));v.displayName="AlertDialogContent";const F=({className:t,...e})=>a.jsx(w,{className:r(t),...e}),H=({className:t,...e})=>a.jsx(D,{className:r(t),...e}),A=o.forwardRef(({className:t,...e},s)=>a.jsx(j,{ref:s,className:r(t),...e}));A.displayName="AlertDialogTitle";const S=o.forwardRef(({className:t,...e},s)=>a.jsx("p",{ref:s,className:r("text-sm text-slate-500 dark:text-slate-400",t),...e}));S.displayName="AlertDialogDescription";const y=o.forwardRef(({className:t,...e},s)=>a.jsx(d,{ref:s,className:r(t),...e}));y.displayName="AlertDialogAction";const k=o.forwardRef(({className:t,...e},s)=>a.jsx(d,{ref:s,variant:"outline",className:r(t),...e}));k.displayName="AlertDialogCancel";export{B as A,P as S,C as a,V as b,I as c,O as d,v as e,F as f,A as g,S as h,H as i,k as j,y as k};