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.
- AutoGLM_GUI/__init__.py +1 -1
- AutoGLM_GUI/__main__.py +11 -2
- AutoGLM_GUI/adb_plus/qr_pair.py +3 -3
- AutoGLM_GUI/agents/__init__.py +7 -2
- AutoGLM_GUI/agents/factory.py +46 -6
- AutoGLM_GUI/agents/glm/agent.py +8 -3
- AutoGLM_GUI/agents/glm/async_agent.py +515 -0
- AutoGLM_GUI/agents/glm/parser.py +4 -2
- AutoGLM_GUI/agents/mai/agent.py +3 -0
- AutoGLM_GUI/agents/protocols.py +111 -1
- AutoGLM_GUI/agents/stream_runner.py +11 -7
- AutoGLM_GUI/api/__init__.py +3 -1
- AutoGLM_GUI/api/agents.py +103 -37
- AutoGLM_GUI/api/devices.py +72 -0
- AutoGLM_GUI/api/history.py +27 -1
- AutoGLM_GUI/api/layered_agent.py +9 -8
- AutoGLM_GUI/api/mcp.py +6 -4
- AutoGLM_GUI/config_manager.py +38 -1
- AutoGLM_GUI/device_manager.py +28 -4
- AutoGLM_GUI/device_metadata_manager.py +174 -0
- AutoGLM_GUI/devices/mock_device.py +8 -1
- AutoGLM_GUI/models/history.py +45 -1
- AutoGLM_GUI/phone_agent_manager.py +145 -32
- AutoGLM_GUI/scheduler_manager.py +52 -6
- AutoGLM_GUI/schemas.py +101 -0
- AutoGLM_GUI/scrcpy_stream.py +2 -1
- AutoGLM_GUI/static/assets/{about-BQm96DAl.js → about-D7r9gCvG.js} +1 -1
- AutoGLM_GUI/static/assets/{alert-dialog-B42XxGPR.js → alert-dialog-BKM-yRiQ.js} +1 -1
- AutoGLM_GUI/static/assets/chat-k6TTD7PW.js +129 -0
- AutoGLM_GUI/static/assets/{circle-alert-D4rSJh37.js → circle-alert-sohSDLhl.js} +1 -1
- AutoGLM_GUI/static/assets/{dialog-DZ78cEcj.js → dialog-BgtPh0d5.js} +1 -1
- AutoGLM_GUI/static/assets/eye-DLqKbQmg.js +1 -0
- AutoGLM_GUI/static/assets/history-Bv1lfGUU.js +1 -0
- AutoGLM_GUI/static/assets/index-CV7jGxGm.css +1 -0
- AutoGLM_GUI/static/assets/index-CxWwh1VO.js +1 -0
- AutoGLM_GUI/static/assets/{index-CssG-3TH.js → index-SysdKciY.js} +5 -5
- AutoGLM_GUI/static/assets/label-DTUnzN4B.js +1 -0
- AutoGLM_GUI/static/assets/{logs-eoFxn5of.js → logs-BIhnDizW.js} +1 -1
- AutoGLM_GUI/static/assets/{popover-DLsuV5Sx.js → popover-CikYqu2P.js} +1 -1
- AutoGLM_GUI/static/assets/scheduled-tasks-B-KBsGbl.js +1 -0
- AutoGLM_GUI/static/assets/{textarea-BX6y7uM5.js → textarea-knJZrz77.js} +1 -1
- AutoGLM_GUI/static/assets/workflows-DzcSYwLZ.js +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- {autoglm_gui-1.5.0.dist-info → autoglm_gui-1.5.2.dist-info}/METADATA +58 -7
- autoglm_gui-1.5.2.dist-info/RECORD +119 -0
- AutoGLM_GUI/device_adapter.py +0 -263
- AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +0 -129
- AutoGLM_GUI/static/assets/history-DFBv7TGc.js +0 -1
- AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +0 -1
- AutoGLM_GUI/static/assets/index-CmZSnDqc.js +0 -1
- AutoGLM_GUI/static/assets/label-BCUzE_nm.js +0 -1
- AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +0 -1
- AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +0 -1
- AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +0 -1
- autoglm_gui-1.5.0.dist-info/RECORD +0 -157
- mai_agent/base.py +0 -137
- mai_agent/mai_grounding_agent.py +0 -263
- mai_agent/mai_naivigation_agent.py +0 -526
- mai_agent/prompt.py +0 -148
- mai_agent/unified_memory.py +0 -67
- mai_agent/utils.py +0 -73
- phone_agent/__init__.py +0 -12
- phone_agent/actions/__init__.py +0 -5
- phone_agent/actions/handler.py +0 -400
- phone_agent/actions/handler_ios.py +0 -278
- phone_agent/adb/__init__.py +0 -51
- phone_agent/adb/connection.py +0 -358
- phone_agent/adb/device.py +0 -253
- phone_agent/adb/input.py +0 -108
- phone_agent/adb/screenshot.py +0 -108
- phone_agent/agent.py +0 -253
- phone_agent/agent_ios.py +0 -277
- phone_agent/config/__init__.py +0 -53
- phone_agent/config/apps.py +0 -227
- phone_agent/config/apps_harmonyos.py +0 -256
- phone_agent/config/apps_ios.py +0 -339
- phone_agent/config/i18n.py +0 -81
- phone_agent/config/prompts.py +0 -80
- phone_agent/config/prompts_en.py +0 -79
- phone_agent/config/prompts_zh.py +0 -82
- phone_agent/config/timing.py +0 -167
- phone_agent/device_factory.py +0 -166
- phone_agent/hdc/__init__.py +0 -53
- phone_agent/hdc/connection.py +0 -384
- phone_agent/hdc/device.py +0 -269
- phone_agent/hdc/input.py +0 -145
- phone_agent/hdc/screenshot.py +0 -127
- phone_agent/model/__init__.py +0 -5
- phone_agent/model/client.py +0 -290
- phone_agent/xctest/__init__.py +0 -47
- phone_agent/xctest/connection.py +0 -379
- phone_agent/xctest/device.py +0 -472
- phone_agent/xctest/input.py +0 -311
- phone_agent/xctest/screenshot.py +0 -226
- {autoglm_gui-1.5.0.dist-info → autoglm_gui-1.5.2.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.5.0.dist-info → autoglm_gui-1.5.2.dist-info}/entry_points.txt +0 -0
- {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[
|
|
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
|
-
|
|
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
|
-
) ->
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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 {
|
|
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
|
-
|
|
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=
|
|
240
|
-
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
|
|
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
|
-
|
|
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
|
-
|
|
250
|
-
|
|
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
|
-
|
|
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,
|
|
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
|
|
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.
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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"
|
|
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:
|
AutoGLM_GUI/scheduler_manager.py
CHANGED
|
@@ -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=
|
|
223
|
-
success=
|
|
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
|
-
|
|
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
|
AutoGLM_GUI/scrcpy_stream.py
CHANGED
|
@@ -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-
|
|
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-
|
|
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};
|