myagent-ai 1.2.3 → 1.3.1
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.
- package/agents/main_agent.py +7 -0
- package/communication/__init__.py +0 -0
- package/communication/channel.py +0 -0
- package/communication/crypto.py +0 -0
- package/communication/manager.py +0 -0
- package/communication/peer.py +0 -0
- package/core/config_broadcast.py +0 -0
- package/core/config_validator.py +0 -0
- package/core/context_manager.py +0 -0
- package/core/deps_checker.py +473 -0
- package/core/permissions.py +0 -0
- package/core/task_persistence.py +0 -0
- package/core/update_manager.py +0 -0
- package/core/version.py +1 -1
- package/departments/__init__.py +0 -0
- package/departments/manager.py +0 -0
- package/docs//351/205/215/347/275/256/344/275/277/347/224/250/350/257/264/346/230/216.md +0 -0
- package/groups/__init__.py +0 -0
- package/groups/manager.py +0 -0
- package/install/install.ps1 +88 -24
- package/install/install.sh +134 -16
- package/knowledge/__init__.py +0 -0
- package/knowledge/rag.py +0 -0
- package/main.py +34 -2
- package/organization/__init__.py +0 -0
- package/organization/manager.py +0 -0
- package/package.json +1 -1
- package/requirements.txt +22 -15
- package/setup.py +14 -3
- package/skills/browser_skill.py +704 -80
- package/skills/gui_skill.py +908 -0
- package/start.sh +22 -9
- package/web/__init__.py +0 -0
- package/web/api_server.py +0 -0
- package/web/tts_handler.py +0 -0
- package/web/ui/chat.html +0 -0
- package/web/ui/index.html +0 -0
package/agents/main_agent.py
CHANGED
|
@@ -428,6 +428,13 @@ class MainAgent(BaseAgent):
|
|
|
428
428
|
# 浏览器技能需要网络
|
|
429
429
|
if skill_name in ("browser_open", "browser_click", "browser_fill"):
|
|
430
430
|
return "network"
|
|
431
|
+
# 浏览器扩展技能 - 截图/JS执行/导航/关闭
|
|
432
|
+
if skill_name in ("browser_screenshot", "browser_eval", "browser_navigate", "browser_close"):
|
|
433
|
+
return "network"
|
|
434
|
+
# 桌面 GUI 自动化技能 - 涉及系统级执行权限
|
|
435
|
+
if skill_name in ("screenshot", "mouse_click", "mouse_drag", "type_text",
|
|
436
|
+
"hotkey", "window_list", "window_focus", "screen_element"):
|
|
437
|
+
return "execution"
|
|
431
438
|
# 其他技能不需要特殊权限检查
|
|
432
439
|
return None
|
|
433
440
|
|
|
File without changes
|
package/communication/channel.py
CHANGED
|
File without changes
|
package/communication/crypto.py
CHANGED
|
File without changes
|
package/communication/manager.py
CHANGED
|
File without changes
|
package/communication/peer.py
CHANGED
|
File without changes
|
package/core/config_broadcast.py
CHANGED
|
File without changes
|
package/core/config_validator.py
CHANGED
|
File without changes
|
package/core/context_manager.py
CHANGED
|
File without changes
|
|
@@ -0,0 +1,473 @@
|
|
|
1
|
+
"""
|
|
2
|
+
core/deps_checker.py - 自动依赖检测与安装
|
|
3
|
+
==========================================
|
|
4
|
+
开箱即用:启动时自动检测缺失依赖并安装,确保所有技能可直接使用。
|
|
5
|
+
|
|
6
|
+
设计原则:
|
|
7
|
+
- 轻量化:仅检测实际需要的模块,不做全量 pip check
|
|
8
|
+
- 高效率:使用 importlib.util.find_spec 快速检测,0网络开销
|
|
9
|
+
- 自动修复:缺失时自动 pip install,无需用户干预
|
|
10
|
+
- 幂等安全:已安装的模块直接跳过,重复调用无副作用
|
|
11
|
+
- 平台适配:自动区分 Windows/macOS/Linux,跳过不适用的依赖
|
|
12
|
+
|
|
13
|
+
依赖映射:
|
|
14
|
+
核心功能: openai, aiohttp, requests
|
|
15
|
+
搜索技能: duckduckgo_search, bs4, lxml
|
|
16
|
+
系统技能: psutil
|
|
17
|
+
托盘功能: pystray, PIL
|
|
18
|
+
语音合成: edge_tts
|
|
19
|
+
浏览器自动化: playwright (+ chromium 浏览器二进制)
|
|
20
|
+
桌面GUI自动化: mss, pynput, pygetwindow
|
|
21
|
+
"""
|
|
22
|
+
from __future__ import annotations
|
|
23
|
+
|
|
24
|
+
import importlib.util
|
|
25
|
+
import subprocess
|
|
26
|
+
import sys
|
|
27
|
+
import threading
|
|
28
|
+
from dataclasses import dataclass, field
|
|
29
|
+
from pathlib import Path
|
|
30
|
+
from typing import Dict, List, Optional, Set, Tuple
|
|
31
|
+
|
|
32
|
+
from core.logger import get_logger
|
|
33
|
+
|
|
34
|
+
logger = get_logger("myagent.deps")
|
|
35
|
+
|
|
36
|
+
# 平台检测
|
|
37
|
+
IS_MACOS = sys.platform == "darwin"
|
|
38
|
+
IS_WINDOWS = sys.platform == "win32"
|
|
39
|
+
IS_LINUX = sys.platform.startswith("linux")
|
|
40
|
+
|
|
41
|
+
|
|
42
|
+
# ── 依赖定义 ──────────────────────────────────────────────
|
|
43
|
+
|
|
44
|
+
@dataclass
|
|
45
|
+
class DepInfo:
|
|
46
|
+
"""单个依赖的信息"""
|
|
47
|
+
import_name: str # Python import 名
|
|
48
|
+
pip_name: str # pip install 名
|
|
49
|
+
min_version: str = "" # 最低版本(可选)
|
|
50
|
+
category: str = "core" # 分类:core/browser/gui/tts/tray/optional
|
|
51
|
+
platform: str = "all" # 适用平台:all/windows/macos/linux
|
|
52
|
+
note: str = "" # 安装说明(失败时展示给用户)
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
# 所有需要自动管理的依赖
|
|
56
|
+
DEPENDENCIES: List[DepInfo] = [
|
|
57
|
+
# ── 核心依赖 ──
|
|
58
|
+
DepInfo("openai", "openai", "1.12.0", "core", "all"),
|
|
59
|
+
DepInfo("aiohttp", "aiohttp", "3.9.0", "core", "all"),
|
|
60
|
+
DepInfo("requests", "requests", "2.31.0", "core", "all"),
|
|
61
|
+
|
|
62
|
+
# ── 搜索技能 ──
|
|
63
|
+
DepInfo("duckduckgo_search", "duckduckgo-search", "6.0.0", "search", "all"),
|
|
64
|
+
DepInfo("bs4", "beautifulsoup4", "4.12.0", "search", "all"),
|
|
65
|
+
DepInfo("lxml", "lxml", "5.0.0", "search", "all"),
|
|
66
|
+
|
|
67
|
+
# ── 系统技能 ──
|
|
68
|
+
DepInfo("psutil", "psutil", "5.9.0", "system", "all"),
|
|
69
|
+
|
|
70
|
+
# ── 系统托盘 ──
|
|
71
|
+
DepInfo("pystray", "pystray", "0.19.5", "tray", "all"),
|
|
72
|
+
DepInfo("PIL", "Pillow", "10.0.0", "tray", "all"),
|
|
73
|
+
|
|
74
|
+
# ── 语音合成 ──
|
|
75
|
+
DepInfo("edge_tts", "edge-tts", "6.1.0", "tts", "all"),
|
|
76
|
+
|
|
77
|
+
# ── 浏览器自动化 (Playwright) ──
|
|
78
|
+
DepInfo("playwright", "playwright", "1.41.0", "browser", "all",
|
|
79
|
+
note="安装后还需运行: playwright install chromium"),
|
|
80
|
+
|
|
81
|
+
# ── 桌面 GUI 自动化 ──
|
|
82
|
+
DepInfo("mss", "mss", "9.0.0", "gui", "all",
|
|
83
|
+
note="跨平台屏幕截图库 (~1MB)"),
|
|
84
|
+
DepInfo("pynput", "pynput", "1.7.6", "gui", "all",
|
|
85
|
+
note="鼠标/键盘控制 (~500KB)"),
|
|
86
|
+
DepInfo("pygetwindow", "pygetwindow", "0.0.9", "gui", "windows_macos",
|
|
87
|
+
note="窗口管理 (仅 Windows/macOS)"),
|
|
88
|
+
]
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
# ── 检测与安装 ────────────────────────────────────────────
|
|
92
|
+
|
|
93
|
+
def _is_platform_match(dep: DepInfo) -> bool:
|
|
94
|
+
"""检查依赖是否适用于当前平台"""
|
|
95
|
+
if dep.platform == "all":
|
|
96
|
+
return True
|
|
97
|
+
if dep.platform == "windows" and IS_WINDOWS:
|
|
98
|
+
return True
|
|
99
|
+
if dep.platform == "macos" and IS_MACOS:
|
|
100
|
+
return True
|
|
101
|
+
if dep.platform == "linux" and IS_LINUX:
|
|
102
|
+
return True
|
|
103
|
+
if dep.platform == "windows_macos" and (IS_WINDOWS or IS_MACOS):
|
|
104
|
+
return True
|
|
105
|
+
return False
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
def _is_module_available(import_name: str) -> bool:
|
|
109
|
+
"""快速检测 Python 模块是否可导入(不实际导入,无副作用)"""
|
|
110
|
+
spec = importlib.util.find_spec(import_name)
|
|
111
|
+
return spec is not None
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
def _get_python_executable() -> str:
|
|
115
|
+
"""获取当前 Python 解释器路径"""
|
|
116
|
+
return sys.executable
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def _pip_install(pip_names: List[str], category: str = "") -> Tuple[bool, str]:
|
|
120
|
+
"""
|
|
121
|
+
使用当前 Python 解释器执行 pip install。
|
|
122
|
+
|
|
123
|
+
Returns:
|
|
124
|
+
(success, message)
|
|
125
|
+
"""
|
|
126
|
+
python = _get_python_executable()
|
|
127
|
+
packages = " ".join(pip_names)
|
|
128
|
+
|
|
129
|
+
# 构建安装命令
|
|
130
|
+
cmd = [python, "-m", "pip", "install", "--quiet"] + pip_names
|
|
131
|
+
|
|
132
|
+
# 处理 PEP668 (externally-managed-environment)
|
|
133
|
+
# 尝试: 1) 直接安装 2) --break-system-packages 3) 用户空间 --user
|
|
134
|
+
install_attempts = [
|
|
135
|
+
[python, "-m", "pip", "install", "--quiet", "--disable-pip-version-check"] + pip_names,
|
|
136
|
+
[python, "-m", "pip", "install", "--quiet", "--disable-pip-version-check",
|
|
137
|
+
"--break-system-packages"] + pip_names,
|
|
138
|
+
[python, "-m", "pip", "install", "--quiet", "--disable-pip-version-check",
|
|
139
|
+
"--user"] + pip_names,
|
|
140
|
+
]
|
|
141
|
+
|
|
142
|
+
last_error = ""
|
|
143
|
+
for attempt_cmd in install_attempts:
|
|
144
|
+
try:
|
|
145
|
+
result = subprocess.run(
|
|
146
|
+
attempt_cmd,
|
|
147
|
+
capture_output=True,
|
|
148
|
+
text=True,
|
|
149
|
+
timeout=120, # 2分钟超时
|
|
150
|
+
)
|
|
151
|
+
if result.returncode == 0:
|
|
152
|
+
return True, f"已安装: {packages}"
|
|
153
|
+
last_error = result.stderr.strip()
|
|
154
|
+
# 如果已经安装(满足要求),也算成功
|
|
155
|
+
if "Requirement already satisfied" in result.stdout or \
|
|
156
|
+
"Requirement already satisfied" in result.stderr:
|
|
157
|
+
return True, f"已就绪: {packages}"
|
|
158
|
+
except subprocess.TimeoutExpired:
|
|
159
|
+
last_error = "安装超时(120秒)"
|
|
160
|
+
continue
|
|
161
|
+
except Exception as e:
|
|
162
|
+
last_error = str(e)
|
|
163
|
+
continue
|
|
164
|
+
|
|
165
|
+
return False, f"安装失败: {packages} - {last_error}"
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
def _install_playwright_browsers() -> Tuple[bool, str]:
|
|
169
|
+
"""
|
|
170
|
+
安装 Playwright 浏览器二进制文件(Chromium)。
|
|
171
|
+
这是一个独立的步骤,因为 pip install playwright 只安装 Python 包,
|
|
172
|
+
浏览器二进制需要单独下载。
|
|
173
|
+
"""
|
|
174
|
+
python = _get_python_executable()
|
|
175
|
+
try:
|
|
176
|
+
result = subprocess.run(
|
|
177
|
+
[python, "-m", "playwright", "install", "chromium"],
|
|
178
|
+
capture_output=True,
|
|
179
|
+
text=True,
|
|
180
|
+
timeout=300, # 5分钟超时(浏览器较大)
|
|
181
|
+
)
|
|
182
|
+
if result.returncode == 0:
|
|
183
|
+
return True, "Chromium 浏览器已安装"
|
|
184
|
+
# 检查是否已经安装
|
|
185
|
+
if "Chromium" in result.stdout and "already" in result.stdout.lower():
|
|
186
|
+
return True, "Chromium 浏览器已就绪"
|
|
187
|
+
return False, f"Chromium 安装失败: {result.stderr[:200]}"
|
|
188
|
+
except subprocess.TimeoutExpired:
|
|
189
|
+
return False, "Chromium 安装超时(5分钟),请手动运行: playwright install chromium"
|
|
190
|
+
except Exception as e:
|
|
191
|
+
return False, f"Chromium 安装异常: {e}"
|
|
192
|
+
|
|
193
|
+
|
|
194
|
+
def _check_version(import_name: str, min_version: str) -> bool:
|
|
195
|
+
"""检查模块版本是否满足最低要求"""
|
|
196
|
+
if not min_version:
|
|
197
|
+
return True
|
|
198
|
+
try:
|
|
199
|
+
spec = importlib.util.find_spec(import_name)
|
|
200
|
+
if spec is None:
|
|
201
|
+
return False
|
|
202
|
+
mod = importlib.import_module(import_name)
|
|
203
|
+
ver = getattr(mod, "__version__", "0.0.0")
|
|
204
|
+
# 简单版本比较
|
|
205
|
+
from packaging.version import parse as parse_ver
|
|
206
|
+
return parse_ver(ver) >= parse_ver(min_version)
|
|
207
|
+
except Exception:
|
|
208
|
+
return True # 无法获取版本时保守通过
|
|
209
|
+
|
|
210
|
+
|
|
211
|
+
# ── 主流程 ────────────────────────────────────────────────
|
|
212
|
+
|
|
213
|
+
def check_and_install_deps(
|
|
214
|
+
categories: Optional[Set[str]] = None,
|
|
215
|
+
auto_fix: bool = True,
|
|
216
|
+
silent: bool = False,
|
|
217
|
+
) -> Dict[str, any]:
|
|
218
|
+
"""
|
|
219
|
+
检测依赖并在缺失时自动安装。
|
|
220
|
+
|
|
221
|
+
Args:
|
|
222
|
+
categories: 要检查的依赖分类集合。
|
|
223
|
+
None = 检查所有分类。
|
|
224
|
+
例如: {"browser", "gui"} 只检查浏览器和GUI依赖。
|
|
225
|
+
auto_fix: 是否自动安装缺失的依赖(默认 True)。
|
|
226
|
+
silent: 是否静默模式,不打印日志(默认 False)。
|
|
227
|
+
|
|
228
|
+
Returns:
|
|
229
|
+
{
|
|
230
|
+
"checked": int, # 检查的依赖数量
|
|
231
|
+
"available": int, # 已安装的数量
|
|
232
|
+
"missing": int, # 缺失的数量
|
|
233
|
+
"installed": int, # 本次新安装的数量
|
|
234
|
+
"failed": int, # 安装失败的数量
|
|
235
|
+
"skipped_platform": int, # 因平台不匹配而跳过的数量
|
|
236
|
+
"details": {...}, # 每个依赖的状态
|
|
237
|
+
"playwright_browser": str, # Chromium 状态
|
|
238
|
+
}
|
|
239
|
+
"""
|
|
240
|
+
stats = {
|
|
241
|
+
"checked": 0,
|
|
242
|
+
"available": 0,
|
|
243
|
+
"missing": 0,
|
|
244
|
+
"installed": 0,
|
|
245
|
+
"failed": 0,
|
|
246
|
+
"skipped_platform": 0,
|
|
247
|
+
"details": {},
|
|
248
|
+
"playwright_browser": "not_checked",
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
# 按分类收集缺失的依赖,批量安装以减少 pip 调用次数
|
|
252
|
+
missing_by_category: Dict[str, List[str]] = {} # category -> [pip_name, ...]
|
|
253
|
+
dep_pip_map: Dict[str, DepInfo] = {} # pip_name -> DepInfo (用于记录安装状态)
|
|
254
|
+
|
|
255
|
+
# 第一遍:检测所有依赖
|
|
256
|
+
for dep in DEPENDENCIES:
|
|
257
|
+
# 平台过滤
|
|
258
|
+
if not _is_platform_match(dep):
|
|
259
|
+
stats["skipped_platform"] += 1
|
|
260
|
+
stats["details"][dep.import_name] = {
|
|
261
|
+
"status": "skipped",
|
|
262
|
+
"reason": f"平台不匹配 (需要: {dep.platform}, 当前: {sys.platform})",
|
|
263
|
+
}
|
|
264
|
+
continue
|
|
265
|
+
|
|
266
|
+
# 分类过滤
|
|
267
|
+
if categories and dep.category not in categories:
|
|
268
|
+
continue
|
|
269
|
+
|
|
270
|
+
stats["checked"] += 1
|
|
271
|
+
|
|
272
|
+
# 检测模块是否可用
|
|
273
|
+
if _is_module_available(dep.import_name):
|
|
274
|
+
stats["available"] += 1
|
|
275
|
+
stats["details"][dep.import_name] = {"status": "available"}
|
|
276
|
+
if not silent:
|
|
277
|
+
logger.debug(f"依赖已就绪: {dep.import_name}")
|
|
278
|
+
continue
|
|
279
|
+
|
|
280
|
+
# 缺失
|
|
281
|
+
stats["missing"] += 1
|
|
282
|
+
stats["details"][dep.import_name] = {
|
|
283
|
+
"status": "missing",
|
|
284
|
+
"pip_name": dep.pip_name,
|
|
285
|
+
"category": dep.category,
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
if dep.pip_name not in dep_pip_map:
|
|
289
|
+
dep_pip_map[dep.pip_name] = dep
|
|
290
|
+
missing_by_category.setdefault(dep.category, []).append(dep.pip_name)
|
|
291
|
+
|
|
292
|
+
if not silent:
|
|
293
|
+
logger.info(f"依赖缺失: {dep.import_name} (pip: {dep.pip_name}, 分类: {dep.category})")
|
|
294
|
+
|
|
295
|
+
if not auto_fix or stats["missing"] == 0:
|
|
296
|
+
return stats
|
|
297
|
+
|
|
298
|
+
# 第二遍:批量安装缺失的依赖(按分类分批)
|
|
299
|
+
if not silent:
|
|
300
|
+
logger.info(f"开始自动安装 {stats['missing']} 个缺失依赖...")
|
|
301
|
+
|
|
302
|
+
total_installed = 0
|
|
303
|
+
total_failed = 0
|
|
304
|
+
|
|
305
|
+
for category, pip_names in missing_by_category.items():
|
|
306
|
+
success, message = _pip_install(pip_names, category)
|
|
307
|
+
|
|
308
|
+
# 更新所有相关依赖的状态
|
|
309
|
+
for pip_name in pip_names:
|
|
310
|
+
dep = dep_pip_map[pip_name]
|
|
311
|
+
if success:
|
|
312
|
+
stats["installed"] += 1
|
|
313
|
+
stats["details"][dep.import_name]["status"] = "installed"
|
|
314
|
+
total_installed += 1
|
|
315
|
+
if not silent:
|
|
316
|
+
logger.info(f" ✓ 已安装: {dep.import_name} ({dep.category})")
|
|
317
|
+
else:
|
|
318
|
+
stats["failed"] += 1
|
|
319
|
+
stats["details"][dep.import_name]["status"] = "install_failed"
|
|
320
|
+
stats["details"][dep.import_name]["error"] = message
|
|
321
|
+
total_failed += 1
|
|
322
|
+
if not silent:
|
|
323
|
+
logger.warning(f" ✗ 安装失败: {dep.import_name} - {message}")
|
|
324
|
+
|
|
325
|
+
# 第三遍:如果 playwright 安装成功,还需要安装 Chromium 浏览器二进制
|
|
326
|
+
playwright_dep = next((d for d in DEPENDENCIES if d.import_name == "playwright"), None)
|
|
327
|
+
if playwright_dep and stats["details"].get("playwright", {}).get("status") == "installed":
|
|
328
|
+
if not silent:
|
|
329
|
+
logger.info("正在安装 Chromium 浏览器二进制...")
|
|
330
|
+
success, message = _install_playwright_browsers()
|
|
331
|
+
stats["playwright_browser"] = "installed" if success else "failed"
|
|
332
|
+
if not silent:
|
|
333
|
+
if success:
|
|
334
|
+
logger.info(f" ✓ {message}")
|
|
335
|
+
else:
|
|
336
|
+
logger.warning(f" ✗ {message}")
|
|
337
|
+
elif playwright_dep and stats["details"].get("playwright", {}).get("status") == "available":
|
|
338
|
+
# playwright 已安装,检查 chromium 是否已安装
|
|
339
|
+
try:
|
|
340
|
+
result = subprocess.run(
|
|
341
|
+
[_get_python_executable(), "-m", "playwright", "install", "--dry-run", "chromium"],
|
|
342
|
+
capture_output=True, text=True, timeout=10,
|
|
343
|
+
)
|
|
344
|
+
if result.returncode == 0:
|
|
345
|
+
stats["playwright_browser"] = "ready"
|
|
346
|
+
else:
|
|
347
|
+
# 尝试安装
|
|
348
|
+
if not silent:
|
|
349
|
+
logger.info("正在安装 Chromium 浏览器二进制...")
|
|
350
|
+
success, message = _install_playwright_browsers()
|
|
351
|
+
stats["playwright_browser"] = "installed" if success else "failed"
|
|
352
|
+
except Exception:
|
|
353
|
+
stats["playwright_browser"] = "unknown"
|
|
354
|
+
|
|
355
|
+
# 汇总日志
|
|
356
|
+
if not silent and (total_installed > 0 or total_failed > 0):
|
|
357
|
+
logger.info(
|
|
358
|
+
f"依赖检查完成: {stats['available']}/{stats['checked']} 已就绪, "
|
|
359
|
+
f"{total_installed} 新安装, {total_failed} 失败"
|
|
360
|
+
)
|
|
361
|
+
|
|
362
|
+
return stats
|
|
363
|
+
|
|
364
|
+
|
|
365
|
+
def ensure_skill_deps(skill_category: str) -> bool:
|
|
366
|
+
"""
|
|
367
|
+
确保指定技能分类的依赖已安装。
|
|
368
|
+
在技能执行前调用,提供懒加载安装能力。
|
|
369
|
+
|
|
370
|
+
Args:
|
|
371
|
+
skill_category: 技能分类名称
|
|
372
|
+
"browser" - 浏览器自动化 (playwright)
|
|
373
|
+
"gui" - 桌面GUI自动化 (mss, pynput, pygetwindow)
|
|
374
|
+
"search" - 搜索技能
|
|
375
|
+
"tts" - 语音合成
|
|
376
|
+
|
|
377
|
+
Returns:
|
|
378
|
+
True 如果所有依赖已就绪(包括刚安装的)
|
|
379
|
+
"""
|
|
380
|
+
category_map = {
|
|
381
|
+
"browser": {"browser"},
|
|
382
|
+
"gui": {"gui"},
|
|
383
|
+
"search": {"search"},
|
|
384
|
+
"tts": {"tts"},
|
|
385
|
+
"tray": {"tray"},
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
cats = category_map.get(skill_category, {skill_category})
|
|
389
|
+
result = check_and_install_deps(categories=cats, auto_fix=True, silent=True)
|
|
390
|
+
|
|
391
|
+
return result["failed"] == 0 and result["missing"] == result["installed"]
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
def get_deps_status() -> Dict[str, Dict]:
|
|
395
|
+
"""
|
|
396
|
+
获取所有依赖的当前状态(不自动安装)。
|
|
397
|
+
供 API 和 UI 展示使用。
|
|
398
|
+
|
|
399
|
+
Returns:
|
|
400
|
+
{import_name: {"status": "available"|"missing", "version": "...", "category": "..."}}
|
|
401
|
+
"""
|
|
402
|
+
status = {}
|
|
403
|
+
for dep in DEPENDENCIES:
|
|
404
|
+
if not _is_platform_match(dep):
|
|
405
|
+
continue
|
|
406
|
+
|
|
407
|
+
available = _is_module_available(dep.import_name)
|
|
408
|
+
version = ""
|
|
409
|
+
if available:
|
|
410
|
+
try:
|
|
411
|
+
mod = importlib.import_module(dep.import_name)
|
|
412
|
+
version = getattr(mod, "__version__", "")
|
|
413
|
+
except Exception:
|
|
414
|
+
pass
|
|
415
|
+
|
|
416
|
+
status[dep.import_name] = {
|
|
417
|
+
"status": "available" if available else "missing",
|
|
418
|
+
"version": version,
|
|
419
|
+
"category": dep.category,
|
|
420
|
+
"pip_name": dep.pip_name,
|
|
421
|
+
}
|
|
422
|
+
|
|
423
|
+
return status
|
|
424
|
+
|
|
425
|
+
|
|
426
|
+
# ── 后台预安装(非阻塞) ──────────────────────────────────
|
|
427
|
+
|
|
428
|
+
_background_check_done = False
|
|
429
|
+
_background_check_running = False
|
|
430
|
+
|
|
431
|
+
|
|
432
|
+
def start_background_check():
|
|
433
|
+
"""
|
|
434
|
+
在后台线程中执行依赖检查和安装(非阻塞)。
|
|
435
|
+
适用于启动时不想等待安装完成的场景。
|
|
436
|
+
"""
|
|
437
|
+
global _background_check_done, _background_check_running
|
|
438
|
+
|
|
439
|
+
if _background_check_done or _background_check_running:
|
|
440
|
+
return
|
|
441
|
+
|
|
442
|
+
_background_check_running = True
|
|
443
|
+
|
|
444
|
+
def _do_check():
|
|
445
|
+
global _background_check_done, _background_check_running
|
|
446
|
+
try:
|
|
447
|
+
check_and_install_deps(auto_fix=True, silent=True)
|
|
448
|
+
except Exception as e:
|
|
449
|
+
logger.warning(f"后台依赖检查失败: {e}")
|
|
450
|
+
finally:
|
|
451
|
+
_background_check_done = True
|
|
452
|
+
_background_check_running = False
|
|
453
|
+
|
|
454
|
+
t = threading.Thread(target=_do_check, daemon=True, name="deps-checker")
|
|
455
|
+
t.start()
|
|
456
|
+
logger.info("后台依赖检查已启动")
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def wait_background_check(timeout: float = 30.0) -> bool:
|
|
460
|
+
"""
|
|
461
|
+
等待后台依赖检查完成。
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
timeout: 最长等待时间(秒)
|
|
465
|
+
|
|
466
|
+
Returns:
|
|
467
|
+
True 如果检查已完成,False 如果超时
|
|
468
|
+
"""
|
|
469
|
+
import time
|
|
470
|
+
deadline = time.monotonic() + timeout
|
|
471
|
+
while _background_check_running and time.monotonic() < deadline:
|
|
472
|
+
time.sleep(0.5)
|
|
473
|
+
return _background_check_done
|
package/core/permissions.py
CHANGED
|
File without changes
|
package/core/task_persistence.py
CHANGED
|
File without changes
|
package/core/update_manager.py
CHANGED
|
File without changes
|
package/core/version.py
CHANGED
package/departments/__init__.py
CHANGED
|
File without changes
|
package/departments/manager.py
CHANGED
|
File without changes
|
|
File without changes
|
package/groups/__init__.py
CHANGED
|
File without changes
|
package/groups/manager.py
CHANGED
|
File without changes
|