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.
- AutoGLM_GUI/__init__.py +11 -0
- AutoGLM_GUI/__main__.py +26 -8
- AutoGLM_GUI/actions/__init__.py +6 -0
- AutoGLM_GUI/actions/handler.py +196 -0
- AutoGLM_GUI/actions/types.py +15 -0
- AutoGLM_GUI/adb/__init__.py +53 -0
- AutoGLM_GUI/adb/apps.py +227 -0
- AutoGLM_GUI/adb/connection.py +323 -0
- AutoGLM_GUI/adb/device.py +171 -0
- AutoGLM_GUI/adb/input.py +67 -0
- AutoGLM_GUI/adb/screenshot.py +11 -0
- AutoGLM_GUI/adb/timing.py +167 -0
- AutoGLM_GUI/adb_plus/keyboard_installer.py +4 -2
- AutoGLM_GUI/adb_plus/qr_pair.py +8 -8
- AutoGLM_GUI/adb_plus/screenshot.py +22 -1
- AutoGLM_GUI/adb_plus/serial.py +38 -20
- AutoGLM_GUI/adb_plus/touch.py +4 -9
- AutoGLM_GUI/agents/__init__.py +51 -0
- AutoGLM_GUI/agents/events.py +19 -0
- AutoGLM_GUI/agents/factory.py +153 -0
- AutoGLM_GUI/agents/glm/__init__.py +7 -0
- AutoGLM_GUI/agents/glm/agent.py +292 -0
- AutoGLM_GUI/agents/glm/message_builder.py +81 -0
- AutoGLM_GUI/agents/glm/parser.py +110 -0
- AutoGLM_GUI/agents/glm/prompts_en.py +77 -0
- AutoGLM_GUI/agents/glm/prompts_zh.py +75 -0
- AutoGLM_GUI/agents/mai/__init__.py +28 -0
- AutoGLM_GUI/agents/mai/agent.py +405 -0
- AutoGLM_GUI/agents/mai/parser.py +254 -0
- AutoGLM_GUI/agents/mai/prompts.py +103 -0
- AutoGLM_GUI/agents/mai/traj_memory.py +91 -0
- AutoGLM_GUI/agents/protocols.py +27 -0
- AutoGLM_GUI/agents/stream_runner.py +188 -0
- AutoGLM_GUI/api/__init__.py +71 -11
- AutoGLM_GUI/api/agents.py +190 -229
- AutoGLM_GUI/api/control.py +9 -6
- AutoGLM_GUI/api/devices.py +112 -28
- AutoGLM_GUI/api/health.py +13 -0
- AutoGLM_GUI/api/history.py +78 -0
- AutoGLM_GUI/api/layered_agent.py +306 -181
- AutoGLM_GUI/api/mcp.py +11 -10
- AutoGLM_GUI/api/media.py +64 -1
- AutoGLM_GUI/api/scheduled_tasks.py +98 -0
- AutoGLM_GUI/api/version.py +23 -10
- AutoGLM_GUI/api/workflows.py +2 -1
- AutoGLM_GUI/config.py +72 -14
- AutoGLM_GUI/config_manager.py +98 -27
- AutoGLM_GUI/device_adapter.py +263 -0
- AutoGLM_GUI/device_manager.py +248 -29
- AutoGLM_GUI/device_protocol.py +266 -0
- AutoGLM_GUI/devices/__init__.py +49 -0
- AutoGLM_GUI/devices/adb_device.py +200 -0
- AutoGLM_GUI/devices/mock_device.py +185 -0
- AutoGLM_GUI/devices/remote_device.py +177 -0
- AutoGLM_GUI/exceptions.py +3 -3
- AutoGLM_GUI/history_manager.py +164 -0
- AutoGLM_GUI/i18n.py +81 -0
- AutoGLM_GUI/metrics.py +13 -20
- AutoGLM_GUI/model/__init__.py +5 -0
- AutoGLM_GUI/model/message_builder.py +69 -0
- AutoGLM_GUI/model/types.py +24 -0
- AutoGLM_GUI/models/__init__.py +10 -0
- AutoGLM_GUI/models/history.py +96 -0
- AutoGLM_GUI/models/scheduled_task.py +71 -0
- AutoGLM_GUI/parsers/__init__.py +22 -0
- AutoGLM_GUI/parsers/base.py +50 -0
- AutoGLM_GUI/parsers/phone_parser.py +58 -0
- AutoGLM_GUI/phone_agent_manager.py +118 -367
- AutoGLM_GUI/platform_utils.py +31 -2
- AutoGLM_GUI/prompt_config.py +15 -0
- AutoGLM_GUI/prompts/__init__.py +32 -0
- AutoGLM_GUI/scheduler_manager.py +304 -0
- AutoGLM_GUI/schemas.py +272 -63
- AutoGLM_GUI/scrcpy_stream.py +159 -37
- AutoGLM_GUI/server.py +3 -1
- AutoGLM_GUI/socketio_server.py +114 -29
- AutoGLM_GUI/state.py +10 -30
- AutoGLM_GUI/static/assets/{about-DeclntHg.js → about-BQm96DAl.js} +1 -1
- AutoGLM_GUI/static/assets/alert-dialog-B42XxGPR.js +1 -0
- AutoGLM_GUI/static/assets/chat-C0L2gQYG.js +129 -0
- AutoGLM_GUI/static/assets/circle-alert-D4rSJh37.js +1 -0
- AutoGLM_GUI/static/assets/dialog-DZ78cEcj.js +45 -0
- AutoGLM_GUI/static/assets/history-DFBv7TGc.js +1 -0
- AutoGLM_GUI/static/assets/index-Bzyv2yQ2.css +1 -0
- AutoGLM_GUI/static/assets/{index-zQ4KKDHt.js → index-CmZSnDqc.js} +1 -1
- AutoGLM_GUI/static/assets/index-CssG-3TH.js +11 -0
- AutoGLM_GUI/static/assets/label-BCUzE_nm.js +1 -0
- AutoGLM_GUI/static/assets/logs-eoFxn5of.js +1 -0
- AutoGLM_GUI/static/assets/popover-DLsuV5Sx.js +1 -0
- AutoGLM_GUI/static/assets/scheduled-tasks-MyqGJvy_.js +1 -0
- AutoGLM_GUI/static/assets/square-pen-zGWYrdfj.js +1 -0
- AutoGLM_GUI/static/assets/textarea-BX6y7uM5.js +1 -0
- AutoGLM_GUI/static/assets/workflows-CYFs6ssC.js +1 -0
- AutoGLM_GUI/static/index.html +2 -2
- AutoGLM_GUI/types.py +142 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/METADATA +178 -92
- autoglm_gui-1.5.0.dist-info/RECORD +157 -0
- mai_agent/base.py +137 -0
- mai_agent/mai_grounding_agent.py +263 -0
- mai_agent/mai_naivigation_agent.py +526 -0
- mai_agent/prompt.py +148 -0
- mai_agent/unified_memory.py +67 -0
- mai_agent/utils.py +73 -0
- AutoGLM_GUI/api/dual_model.py +0 -311
- AutoGLM_GUI/dual_model/__init__.py +0 -53
- AutoGLM_GUI/dual_model/decision_model.py +0 -664
- AutoGLM_GUI/dual_model/dual_agent.py +0 -917
- AutoGLM_GUI/dual_model/protocols.py +0 -354
- AutoGLM_GUI/dual_model/vision_model.py +0 -442
- AutoGLM_GUI/mai_ui_adapter/agent_wrapper.py +0 -291
- AutoGLM_GUI/phone_agent_patches.py +0 -146
- AutoGLM_GUI/static/assets/chat-Iut2yhSw.js +0 -125
- AutoGLM_GUI/static/assets/dialog-BfdcBs1x.js +0 -45
- AutoGLM_GUI/static/assets/index-5hCCwHA7.css +0 -1
- AutoGLM_GUI/static/assets/index-DHF1NZh0.js +0 -12
- AutoGLM_GUI/static/assets/workflows-xiplap-r.js +0 -1
- autoglm_gui-1.4.0.dist-info/RECORD +0 -100
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/WHEEL +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/entry_points.txt +0 -0
- {autoglm_gui-1.4.0.dist-info → autoglm_gui-1.5.0.dist-info}/licenses/LICENSE +0 -0
AutoGLM_GUI/platform_utils.py
CHANGED
|
@@ -3,7 +3,8 @@
|
|
|
3
3
|
import asyncio
|
|
4
4
|
import platform
|
|
5
5
|
import subprocess
|
|
6
|
-
from
|
|
6
|
+
from asyncio.subprocess import Process as AsyncProcess
|
|
7
|
+
from typing import Sequence
|
|
7
8
|
|
|
8
9
|
|
|
9
10
|
def is_windows() -> bool:
|
|
@@ -51,7 +52,9 @@ async def run_cmd_silently(cmd: Sequence[str]) -> subprocess.CompletedProcess:
|
|
|
51
52
|
return subprocess.CompletedProcess(cmd, return_code, stdout_str, stderr_str)
|
|
52
53
|
|
|
53
54
|
|
|
54
|
-
async def spawn_process(
|
|
55
|
+
async def spawn_process(
|
|
56
|
+
cmd: Sequence[str], *, capture_output: bool = False
|
|
57
|
+
) -> subprocess.Popen[bytes] | AsyncProcess:
|
|
55
58
|
"""Start a long-running process with optional stdio capture."""
|
|
56
59
|
stdout = subprocess.PIPE if capture_output else None
|
|
57
60
|
stderr = subprocess.PIPE if capture_output else None
|
|
@@ -60,3 +63,29 @@ async def spawn_process(cmd: Sequence[str], *, capture_output: bool = False) ->
|
|
|
60
63
|
return subprocess.Popen(cmd, stdout=stdout, stderr=stderr)
|
|
61
64
|
|
|
62
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
|
+
]
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
"""Prompt templates for agents."""
|
|
2
|
+
|
|
3
|
+
from pathlib import Path
|
|
4
|
+
|
|
5
|
+
# Import MAI prompt from new location
|
|
6
|
+
from AutoGLM_GUI.agents.mai.prompts import MAI_MOBILE_SYSTEM_PROMPT
|
|
7
|
+
|
|
8
|
+
# Import from parent-level prompts.py file
|
|
9
|
+
# When prompts/ directory exists, Python prioritizes it over prompts.py
|
|
10
|
+
# We need to import from sibling prompts.py file
|
|
11
|
+
parent_dir = Path(__file__).parent.parent
|
|
12
|
+
prompts_file = parent_dir / "prompts.py"
|
|
13
|
+
|
|
14
|
+
if prompts_file.exists():
|
|
15
|
+
import importlib.util
|
|
16
|
+
|
|
17
|
+
spec = importlib.util.spec_from_file_location("_prompts_legacy", prompts_file)
|
|
18
|
+
if spec and spec.loader:
|
|
19
|
+
_prompts_legacy = importlib.util.module_from_spec(spec)
|
|
20
|
+
spec.loader.exec_module(_prompts_legacy)
|
|
21
|
+
MCP_SYSTEM_PROMPT_ZH = getattr(_prompts_legacy, "MCP_SYSTEM_PROMPT_ZH", "")
|
|
22
|
+
MCP_SYSTEM_PROMPT_EN = getattr(_prompts_legacy, "MCP_SYSTEM_PROMPT_EN", "")
|
|
23
|
+
else:
|
|
24
|
+
# Fallback if file doesn't exist
|
|
25
|
+
MCP_SYSTEM_PROMPT_ZH = ""
|
|
26
|
+
MCP_SYSTEM_PROMPT_EN = ""
|
|
27
|
+
|
|
28
|
+
__all__ = [
|
|
29
|
+
"MAI_MOBILE_SYSTEM_PROMPT",
|
|
30
|
+
"MCP_SYSTEM_PROMPT_ZH",
|
|
31
|
+
"MCP_SYSTEM_PROMPT_EN",
|
|
32
|
+
]
|
|
@@ -0,0 +1,304 @@
|
|
|
1
|
+
"""Scheduled task manager with APScheduler."""
|
|
2
|
+
|
|
3
|
+
import json
|
|
4
|
+
from datetime import datetime
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Optional
|
|
7
|
+
|
|
8
|
+
from apscheduler.schedulers.background import BackgroundScheduler
|
|
9
|
+
from apscheduler.triggers.cron import CronTrigger
|
|
10
|
+
|
|
11
|
+
from AutoGLM_GUI.logger import logger
|
|
12
|
+
from AutoGLM_GUI.models.scheduled_task import ScheduledTask
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
class SchedulerManager:
|
|
16
|
+
_instance: Optional["SchedulerManager"] = None
|
|
17
|
+
|
|
18
|
+
def __new__(cls):
|
|
19
|
+
if cls._instance is None:
|
|
20
|
+
cls._instance = super().__new__(cls)
|
|
21
|
+
return cls._instance
|
|
22
|
+
|
|
23
|
+
def __init__(self):
|
|
24
|
+
if hasattr(self, "_initialized"):
|
|
25
|
+
return
|
|
26
|
+
self._initialized = True
|
|
27
|
+
self._tasks_path = Path.home() / ".config" / "autoglm" / "scheduled_tasks.json"
|
|
28
|
+
self._scheduler = BackgroundScheduler()
|
|
29
|
+
self._tasks: dict[str, ScheduledTask] = {}
|
|
30
|
+
self._file_mtime: Optional[float] = None
|
|
31
|
+
|
|
32
|
+
def start(self) -> None:
|
|
33
|
+
self._load_tasks()
|
|
34
|
+
for task in self._tasks.values():
|
|
35
|
+
if task.enabled:
|
|
36
|
+
self._add_job(task)
|
|
37
|
+
self._scheduler.start()
|
|
38
|
+
logger.info(f"SchedulerManager started with {len(self._tasks)} task(s)")
|
|
39
|
+
|
|
40
|
+
def shutdown(self) -> None:
|
|
41
|
+
self._scheduler.shutdown(wait=False)
|
|
42
|
+
logger.info("SchedulerManager shutdown")
|
|
43
|
+
|
|
44
|
+
def create_task(
|
|
45
|
+
self,
|
|
46
|
+
name: str,
|
|
47
|
+
workflow_uuid: str,
|
|
48
|
+
device_serialno: str,
|
|
49
|
+
cron_expression: str,
|
|
50
|
+
enabled: bool = True,
|
|
51
|
+
) -> ScheduledTask:
|
|
52
|
+
task = ScheduledTask(
|
|
53
|
+
name=name,
|
|
54
|
+
workflow_uuid=workflow_uuid,
|
|
55
|
+
device_serialno=device_serialno,
|
|
56
|
+
cron_expression=cron_expression,
|
|
57
|
+
enabled=enabled,
|
|
58
|
+
)
|
|
59
|
+
self._tasks[task.id] = task
|
|
60
|
+
self._save_tasks()
|
|
61
|
+
|
|
62
|
+
if enabled:
|
|
63
|
+
self._add_job(task)
|
|
64
|
+
|
|
65
|
+
logger.info(f"Created scheduled task: {name} (id={task.id})")
|
|
66
|
+
return task
|
|
67
|
+
|
|
68
|
+
def update_task(self, task_id: str, **kwargs) -> Optional[ScheduledTask]:
|
|
69
|
+
task = self._tasks.get(task_id)
|
|
70
|
+
if not task:
|
|
71
|
+
return None
|
|
72
|
+
|
|
73
|
+
old_enabled = task.enabled
|
|
74
|
+
old_cron = task.cron_expression
|
|
75
|
+
|
|
76
|
+
for key, value in kwargs.items():
|
|
77
|
+
if value is not None and hasattr(task, key):
|
|
78
|
+
setattr(task, key, value)
|
|
79
|
+
|
|
80
|
+
task.updated_at = datetime.now()
|
|
81
|
+
self._save_tasks()
|
|
82
|
+
|
|
83
|
+
if old_enabled and not task.enabled:
|
|
84
|
+
self._remove_job(task_id)
|
|
85
|
+
elif not old_enabled and task.enabled:
|
|
86
|
+
self._add_job(task)
|
|
87
|
+
elif task.enabled and old_cron != task.cron_expression:
|
|
88
|
+
self._remove_job(task_id)
|
|
89
|
+
self._add_job(task)
|
|
90
|
+
|
|
91
|
+
logger.info(f"Updated scheduled task: {task.name} (id={task_id})")
|
|
92
|
+
return task
|
|
93
|
+
|
|
94
|
+
def delete_task(self, task_id: str) -> bool:
|
|
95
|
+
task = self._tasks.pop(task_id, None)
|
|
96
|
+
if not task:
|
|
97
|
+
return False
|
|
98
|
+
|
|
99
|
+
self._remove_job(task_id)
|
|
100
|
+
self._save_tasks()
|
|
101
|
+
logger.info(f"Deleted scheduled task: {task.name} (id={task_id})")
|
|
102
|
+
return True
|
|
103
|
+
|
|
104
|
+
def list_tasks(self) -> list[ScheduledTask]:
|
|
105
|
+
return list(self._tasks.values())
|
|
106
|
+
|
|
107
|
+
def get_task(self, task_id: str) -> Optional[ScheduledTask]:
|
|
108
|
+
return self._tasks.get(task_id)
|
|
109
|
+
|
|
110
|
+
def set_enabled(self, task_id: str, enabled: bool) -> bool:
|
|
111
|
+
task = self._tasks.get(task_id)
|
|
112
|
+
if not task:
|
|
113
|
+
return False
|
|
114
|
+
|
|
115
|
+
if task.enabled == enabled:
|
|
116
|
+
return True
|
|
117
|
+
|
|
118
|
+
task.enabled = enabled
|
|
119
|
+
task.updated_at = datetime.now()
|
|
120
|
+
self._save_tasks()
|
|
121
|
+
|
|
122
|
+
if enabled:
|
|
123
|
+
self._add_job(task)
|
|
124
|
+
else:
|
|
125
|
+
self._remove_job(task_id)
|
|
126
|
+
|
|
127
|
+
logger.info(f"{'Enabled' if enabled else 'Disabled'} task: {task.name}")
|
|
128
|
+
return True
|
|
129
|
+
|
|
130
|
+
def get_next_run_time(self, task_id: str) -> Optional[datetime]:
|
|
131
|
+
job = self._scheduler.get_job(task_id)
|
|
132
|
+
if job and job.next_run_time:
|
|
133
|
+
return job.next_run_time.replace(tzinfo=None)
|
|
134
|
+
return None
|
|
135
|
+
|
|
136
|
+
def _add_job(self, task: ScheduledTask) -> None:
|
|
137
|
+
try:
|
|
138
|
+
parts = task.cron_expression.split()
|
|
139
|
+
if len(parts) != 5:
|
|
140
|
+
logger.error(f"Invalid cron expression: {task.cron_expression}")
|
|
141
|
+
return
|
|
142
|
+
|
|
143
|
+
trigger = CronTrigger(
|
|
144
|
+
minute=parts[0],
|
|
145
|
+
hour=parts[1],
|
|
146
|
+
day=parts[2],
|
|
147
|
+
month=parts[3],
|
|
148
|
+
day_of_week=parts[4],
|
|
149
|
+
)
|
|
150
|
+
|
|
151
|
+
self._scheduler.add_job(
|
|
152
|
+
self._execute_task,
|
|
153
|
+
trigger=trigger,
|
|
154
|
+
id=task.id,
|
|
155
|
+
args=[task.id],
|
|
156
|
+
replace_existing=True,
|
|
157
|
+
)
|
|
158
|
+
logger.debug(f"Added job for task: {task.name}")
|
|
159
|
+
except Exception as e:
|
|
160
|
+
logger.error(f"Failed to add job for task {task.name}: {e}")
|
|
161
|
+
|
|
162
|
+
def _remove_job(self, task_id: str) -> None:
|
|
163
|
+
try:
|
|
164
|
+
if self._scheduler.get_job(task_id):
|
|
165
|
+
self._scheduler.remove_job(task_id)
|
|
166
|
+
logger.debug(f"Removed job: {task_id}")
|
|
167
|
+
except Exception as e:
|
|
168
|
+
logger.warning(f"Failed to remove job {task_id}: {e}")
|
|
169
|
+
|
|
170
|
+
def _execute_task(self, task_id: str) -> None:
|
|
171
|
+
task = self._tasks.get(task_id)
|
|
172
|
+
if not task:
|
|
173
|
+
logger.warning(f"Task {task_id} not found for execution")
|
|
174
|
+
return
|
|
175
|
+
|
|
176
|
+
logger.info(f"Executing scheduled task: {task.name}")
|
|
177
|
+
|
|
178
|
+
from AutoGLM_GUI.device_manager import DeviceManager
|
|
179
|
+
from AutoGLM_GUI.history_manager import history_manager
|
|
180
|
+
from AutoGLM_GUI.models.history import ConversationRecord
|
|
181
|
+
from AutoGLM_GUI.phone_agent_manager import PhoneAgentManager
|
|
182
|
+
from AutoGLM_GUI.workflow_manager import workflow_manager
|
|
183
|
+
|
|
184
|
+
workflow = workflow_manager.get_workflow(task.workflow_uuid)
|
|
185
|
+
if not workflow:
|
|
186
|
+
self._record_failure(task, "Workflow not found")
|
|
187
|
+
return
|
|
188
|
+
|
|
189
|
+
device_manager = DeviceManager.get_instance()
|
|
190
|
+
device = None
|
|
191
|
+
for d in device_manager.get_devices():
|
|
192
|
+
if d.serial == task.device_serialno and d.state.value == "online":
|
|
193
|
+
device = d
|
|
194
|
+
break
|
|
195
|
+
|
|
196
|
+
if not device:
|
|
197
|
+
self._record_failure(task, "Device offline")
|
|
198
|
+
return
|
|
199
|
+
|
|
200
|
+
manager = PhoneAgentManager.get_instance()
|
|
201
|
+
acquired = manager.acquire_device(
|
|
202
|
+
device.primary_device_id,
|
|
203
|
+
timeout=0,
|
|
204
|
+
raise_on_timeout=False,
|
|
205
|
+
auto_initialize=True,
|
|
206
|
+
)
|
|
207
|
+
|
|
208
|
+
if not acquired:
|
|
209
|
+
self._record_failure(task, "Device busy")
|
|
210
|
+
return
|
|
211
|
+
|
|
212
|
+
start_time = datetime.now()
|
|
213
|
+
try:
|
|
214
|
+
agent = manager.get_agent(device.primary_device_id)
|
|
215
|
+
agent.reset()
|
|
216
|
+
result = agent.run(workflow["text"])
|
|
217
|
+
steps = agent.step_count
|
|
218
|
+
|
|
219
|
+
end_time = datetime.now()
|
|
220
|
+
record = ConversationRecord(
|
|
221
|
+
task_text=workflow["text"],
|
|
222
|
+
final_message=result,
|
|
223
|
+
success=True,
|
|
224
|
+
steps=steps,
|
|
225
|
+
start_time=start_time,
|
|
226
|
+
end_time=end_time,
|
|
227
|
+
duration_ms=int((end_time - start_time).total_seconds() * 1000),
|
|
228
|
+
source="scheduled",
|
|
229
|
+
source_detail=task.name,
|
|
230
|
+
)
|
|
231
|
+
history_manager.add_record(task.device_serialno, record)
|
|
232
|
+
|
|
233
|
+
self._record_success(task, result)
|
|
234
|
+
|
|
235
|
+
except Exception as e:
|
|
236
|
+
end_time = datetime.now()
|
|
237
|
+
error_msg = str(e)
|
|
238
|
+
logger.error(f"Scheduled task failed: {task.name} - {error_msg}")
|
|
239
|
+
|
|
240
|
+
record = ConversationRecord(
|
|
241
|
+
task_text=workflow["text"],
|
|
242
|
+
final_message=error_msg,
|
|
243
|
+
success=False,
|
|
244
|
+
steps=0,
|
|
245
|
+
start_time=start_time,
|
|
246
|
+
end_time=end_time,
|
|
247
|
+
duration_ms=int((end_time - start_time).total_seconds() * 1000),
|
|
248
|
+
source="scheduled",
|
|
249
|
+
source_detail=task.name,
|
|
250
|
+
error_message=error_msg,
|
|
251
|
+
)
|
|
252
|
+
history_manager.add_record(task.device_serialno, record)
|
|
253
|
+
|
|
254
|
+
self._record_failure(task, error_msg)
|
|
255
|
+
|
|
256
|
+
finally:
|
|
257
|
+
manager.release_device(device.primary_device_id)
|
|
258
|
+
|
|
259
|
+
def _record_success(self, task: ScheduledTask, message: str) -> None:
|
|
260
|
+
task.last_run_time = datetime.now()
|
|
261
|
+
task.last_run_success = True
|
|
262
|
+
task.last_run_message = message[:500] if message else ""
|
|
263
|
+
self._save_tasks()
|
|
264
|
+
logger.info(f"Scheduled task completed: {task.name}")
|
|
265
|
+
|
|
266
|
+
def _record_failure(self, task: ScheduledTask, error: str) -> None:
|
|
267
|
+
task.last_run_time = datetime.now()
|
|
268
|
+
task.last_run_success = False
|
|
269
|
+
task.last_run_message = error[:500] if error else ""
|
|
270
|
+
self._save_tasks()
|
|
271
|
+
logger.warning(f"Scheduled task failed: {task.name} - {error}")
|
|
272
|
+
|
|
273
|
+
def _load_tasks(self) -> None:
|
|
274
|
+
if not self._tasks_path.exists():
|
|
275
|
+
return
|
|
276
|
+
|
|
277
|
+
try:
|
|
278
|
+
with open(self._tasks_path, encoding="utf-8") as f:
|
|
279
|
+
data = json.load(f)
|
|
280
|
+
tasks_data = data.get("tasks", [])
|
|
281
|
+
self._tasks = {t["id"]: ScheduledTask.from_dict(t) for t in tasks_data}
|
|
282
|
+
self._file_mtime = self._tasks_path.stat().st_mtime
|
|
283
|
+
logger.debug(f"Loaded {len(self._tasks)} scheduled tasks")
|
|
284
|
+
except Exception as e:
|
|
285
|
+
logger.warning(f"Failed to load scheduled tasks: {e}")
|
|
286
|
+
|
|
287
|
+
def _save_tasks(self) -> None:
|
|
288
|
+
self._tasks_path.parent.mkdir(parents=True, exist_ok=True)
|
|
289
|
+
temp_path = self._tasks_path.with_suffix(".tmp")
|
|
290
|
+
|
|
291
|
+
try:
|
|
292
|
+
data = {"tasks": [t.to_dict() for t in self._tasks.values()]}
|
|
293
|
+
with open(temp_path, "w", encoding="utf-8") as f:
|
|
294
|
+
json.dump(data, f, indent=2, ensure_ascii=False)
|
|
295
|
+
temp_path.replace(self._tasks_path)
|
|
296
|
+
self._file_mtime = self._tasks_path.stat().st_mtime
|
|
297
|
+
logger.debug(f"Saved {len(self._tasks)} scheduled tasks")
|
|
298
|
+
except Exception as e:
|
|
299
|
+
logger.error(f"Failed to save scheduled tasks: {e}")
|
|
300
|
+
if temp_path.exists():
|
|
301
|
+
temp_path.unlink()
|
|
302
|
+
|
|
303
|
+
|
|
304
|
+
scheduler_manager = SchedulerManager()
|