myagent-ai 1.15.37 → 1.15.40
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 +8 -1
- package/core/deps_checker.py +21 -1
- package/main.py +3 -0
- package/package.json +2 -2
- package/skills/search_skill.py +44 -35
package/agents/main_agent.py
CHANGED
|
@@ -1080,6 +1080,10 @@ class MainAgent(BaseAgent):
|
|
|
1080
1080
|
return out
|
|
1081
1081
|
return tr.get("error", "")
|
|
1082
1082
|
|
|
1083
|
+
# 防御 tool_result 为 None(极端情况)
|
|
1084
|
+
if tool_result is None:
|
|
1085
|
+
tool_result = {"success": False, "error": "工具返回了空结果", "timed_out": False}
|
|
1086
|
+
|
|
1083
1087
|
tool_output_text = _extract_tool_output(tool_result)
|
|
1084
1088
|
|
|
1085
1089
|
await self._emit_v2_event(
|
|
@@ -1328,7 +1332,10 @@ class MainAgent(BaseAgent):
|
|
|
1328
1332
|
|
|
1329
1333
|
elif self.skills:
|
|
1330
1334
|
exec_result = await self.skills.execute(tool_name, **params)
|
|
1331
|
-
|
|
1335
|
+
if exec_result is None:
|
|
1336
|
+
result["error"] = f"技能 {tool_name} 返回了空结果"
|
|
1337
|
+
else:
|
|
1338
|
+
result = exec_result.to_dict()
|
|
1332
1339
|
else:
|
|
1333
1340
|
result["error"] = f"未知工具: {tool_name}"
|
|
1334
1341
|
|
package/core/deps_checker.py
CHANGED
|
@@ -140,6 +140,11 @@ def _get_pip_index_args() -> List[str]:
|
|
|
140
140
|
return []
|
|
141
141
|
|
|
142
142
|
|
|
143
|
+
def _is_skip_deps() -> bool:
|
|
144
|
+
"""检查是否设置了跳过依赖安装的环境变量"""
|
|
145
|
+
return os.environ.get("MYAGENT_SKIP_DEPS") == "1"
|
|
146
|
+
|
|
147
|
+
|
|
143
148
|
def _pip_install(pip_names: List[str], category: str = "") -> Tuple[bool, str]:
|
|
144
149
|
"""
|
|
145
150
|
使用当前 Python 解释器执行 pip install。
|
|
@@ -147,6 +152,11 @@ def _pip_install(pip_names: List[str], category: str = "") -> Tuple[bool, str]:
|
|
|
147
152
|
Returns:
|
|
148
153
|
(success, message)
|
|
149
154
|
"""
|
|
155
|
+
# --skip-deps 时完全跳过任何 pip 安装
|
|
156
|
+
if _is_skip_deps():
|
|
157
|
+
packages = " ".join(pip_names)
|
|
158
|
+
logger.debug(f"跳过依赖安装(--skip-deps): {packages}")
|
|
159
|
+
return True, f"已跳过: {packages}"
|
|
150
160
|
python = _get_python_executable()
|
|
151
161
|
packages = " ".join(pip_names)
|
|
152
162
|
|
|
@@ -267,6 +277,9 @@ def check_and_install_deps(
|
|
|
267
277
|
"browser": str, # ChromeDev MCP 状态
|
|
268
278
|
}
|
|
269
279
|
"""
|
|
280
|
+
# --skip-deps 安全网:即使调用方忘记检查,也不会执行安装
|
|
281
|
+
skip_deps = _is_skip_deps()
|
|
282
|
+
|
|
270
283
|
stats = {
|
|
271
284
|
"checked": 0,
|
|
272
285
|
"available": 0,
|
|
@@ -322,7 +335,9 @@ def check_and_install_deps(
|
|
|
322
335
|
if not silent:
|
|
323
336
|
logger.info(f"依赖缺失: {dep.import_name} (pip: {dep.pip_name}, 分类: {dep.category})")
|
|
324
337
|
|
|
325
|
-
if not auto_fix or stats["missing"] == 0:
|
|
338
|
+
if not auto_fix or stats["missing"] == 0 or skip_deps:
|
|
339
|
+
if skip_deps and stats["missing"] > 0 and not silent:
|
|
340
|
+
logger.info(f"跳过 {stats['missing']} 个缺失依赖的安装(--skip-deps)")
|
|
326
341
|
return stats
|
|
327
342
|
|
|
328
343
|
# 第二遍:批量安装缺失的依赖(按分类分批)
|
|
@@ -377,6 +392,11 @@ def ensure_skill_deps(skill_category: str) -> bool:
|
|
|
377
392
|
Returns:
|
|
378
393
|
True 如果所有依赖已就绪(包括刚安装的)
|
|
379
394
|
"""
|
|
395
|
+
# --skip-deps 时跳过懒加载依赖安装
|
|
396
|
+
if _is_skip_deps():
|
|
397
|
+
logger.debug(f"跳过技能依赖检查(--skip-deps): {skill_category}")
|
|
398
|
+
return True
|
|
399
|
+
|
|
380
400
|
category_map = {
|
|
381
401
|
"browser": {"browser"},
|
|
382
402
|
"gui": {"gui"},
|
package/main.py
CHANGED
|
@@ -692,6 +692,9 @@ def create_tray_icon(app: MyAgentApp, web_port: int = 8767):
|
|
|
692
692
|
from PIL import Image, ImageDraw, ImageFont
|
|
693
693
|
except Exception:
|
|
694
694
|
# pystray 可能因缺少 GUI 环境 (X11/Wayland) 或未安装而失败
|
|
695
|
+
if os.environ.get('MYAGENT_SKIP_DEPS') == '1':
|
|
696
|
+
logger.info("pystray/PIL 未安装,跳过自动安装(--skip-deps)")
|
|
697
|
+
return None
|
|
695
698
|
try:
|
|
696
699
|
from core.deps_checker import _pip_install
|
|
697
700
|
logger.info("pystray/PIL 未安装,正在自动安装...")
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "myagent-ai",
|
|
3
|
-
"version": "1.15.
|
|
3
|
+
"version": "1.15.40",
|
|
4
4
|
"description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
|
|
5
5
|
"main": "main.py",
|
|
6
6
|
"bin": {
|
|
@@ -65,4 +65,4 @@
|
|
|
65
65
|
"departments/",
|
|
66
66
|
"web/"
|
|
67
67
|
]
|
|
68
|
-
}
|
|
68
|
+
}
|
package/skills/search_skill.py
CHANGED
|
@@ -118,20 +118,22 @@ class WebSearchSkill(Skill):
|
|
|
118
118
|
import requests
|
|
119
119
|
from bs4 import BeautifulSoup
|
|
120
120
|
|
|
121
|
-
loop = asyncio.
|
|
121
|
+
loop = asyncio.get_running_loop()
|
|
122
122
|
|
|
123
123
|
def _fetch():
|
|
124
|
-
|
|
124
|
+
r = None
|
|
125
125
|
try:
|
|
126
|
-
r = requests.get(
|
|
126
|
+
r = requests.get("https://html.duckduckgo.com/html/", params={"q": query}, headers=_HEADERS, timeout=15)
|
|
127
127
|
r.raise_for_status()
|
|
128
128
|
except requests.exceptions.SSLError:
|
|
129
129
|
logger.warning("DuckDuckGo SSL 验证失败,跳过证书验证重试")
|
|
130
|
-
r = requests.get(
|
|
130
|
+
r = requests.get("https://html.duckduckgo.com/html/", params={"q": query}, headers=_HEADERS, timeout=15, verify=False)
|
|
131
131
|
r.raise_for_status()
|
|
132
|
-
|
|
132
|
+
if r is not None:
|
|
133
|
+
return r.text or ""
|
|
134
|
+
return ""
|
|
133
135
|
|
|
134
|
-
html = await loop.run_in_executor(None, _fetch)
|
|
136
|
+
html = await asyncio.wait_for(loop.run_in_executor(None, _fetch), timeout=30)
|
|
135
137
|
soup = BeautifulSoup(html, "html.parser")
|
|
136
138
|
|
|
137
139
|
results = []
|
|
@@ -170,31 +172,22 @@ class WebSearchSkill(Skill):
|
|
|
170
172
|
import requests
|
|
171
173
|
from bs4 import BeautifulSoup
|
|
172
174
|
|
|
173
|
-
loop = asyncio.
|
|
175
|
+
loop = asyncio.get_running_loop()
|
|
174
176
|
|
|
175
177
|
def _fetch():
|
|
176
|
-
|
|
178
|
+
r = None
|
|
177
179
|
try:
|
|
178
|
-
r = requests.get(
|
|
179
|
-
url,
|
|
180
|
-
params={"q": query, "count": num},
|
|
181
|
-
headers=_HEADERS,
|
|
182
|
-
timeout=15,
|
|
183
|
-
)
|
|
180
|
+
r = requests.get("https://www.bing.com/search", params={"q": query, "count": num}, headers=_HEADERS, timeout=15)
|
|
184
181
|
r.raise_for_status()
|
|
185
182
|
except requests.exceptions.SSLError:
|
|
186
183
|
logger.warning("Bing SSL 验证失败,跳过证书验证重试")
|
|
187
|
-
r = requests.get(
|
|
188
|
-
url,
|
|
189
|
-
params={"q": query, "count": num},
|
|
190
|
-
headers=_HEADERS,
|
|
191
|
-
timeout=15,
|
|
192
|
-
verify=False,
|
|
193
|
-
)
|
|
184
|
+
r = requests.get("https://www.bing.com/search", params={"q": query, "count": num}, headers=_HEADERS, timeout=15, verify=False)
|
|
194
185
|
r.raise_for_status()
|
|
195
|
-
|
|
186
|
+
if r is not None:
|
|
187
|
+
return r.text or ""
|
|
188
|
+
return ""
|
|
196
189
|
|
|
197
|
-
html = await loop.run_in_executor(None, _fetch)
|
|
190
|
+
html = await asyncio.wait_for(loop.run_in_executor(None, _fetch), timeout=30)
|
|
198
191
|
soup = BeautifulSoup(html, "html.parser")
|
|
199
192
|
|
|
200
193
|
results = []
|
|
@@ -250,22 +243,24 @@ class WebReadSkill(Skill):
|
|
|
250
243
|
]
|
|
251
244
|
|
|
252
245
|
async def execute(self, url: str = "", extract_text: bool = True, **kwargs) -> SkillResult:
|
|
246
|
+
if not url or not isinstance(url, str) or not url.startswith("http"):
|
|
247
|
+
return SkillResult(success=False, error=f"无效的URL: {url!r}")
|
|
248
|
+
|
|
253
249
|
# 确保搜索依赖已安装
|
|
254
250
|
try:
|
|
255
|
-
import requests
|
|
256
|
-
from bs4 import BeautifulSoup
|
|
251
|
+
import requests # noqa: F811
|
|
252
|
+
from bs4 import BeautifulSoup # noqa: F811
|
|
257
253
|
except ImportError as imp_err:
|
|
258
254
|
logger.warning(f"网页读取依赖缺失: {imp_err},尝试自动安装...")
|
|
259
255
|
from core.deps_checker import ensure_skill_deps
|
|
260
256
|
if not ensure_skill_deps("search"):
|
|
261
257
|
return SkillResult(success=False, error=f"依赖安装失败,请手动运行: pip install requests beautifulsoup4 lxml ({imp_err})")
|
|
262
|
-
try:
|
|
263
|
-
import requests # noqa: F811
|
|
264
|
-
from bs4 import BeautifulSoup # noqa: F811
|
|
265
258
|
|
|
266
|
-
|
|
259
|
+
try:
|
|
260
|
+
loop = asyncio.get_running_loop()
|
|
267
261
|
|
|
268
262
|
def _fetch():
|
|
263
|
+
r = None
|
|
269
264
|
try:
|
|
270
265
|
r = requests.get(url, headers=_HEADERS, timeout=30)
|
|
271
266
|
r.raise_for_status()
|
|
@@ -273,10 +268,17 @@ class WebReadSkill(Skill):
|
|
|
273
268
|
logger.warning(f"web_read SSL 验证失败 ({url}),跳过证书验证重试")
|
|
274
269
|
r = requests.get(url, headers=_HEADERS, timeout=30, verify=False)
|
|
275
270
|
r.raise_for_status()
|
|
276
|
-
r
|
|
277
|
-
|
|
271
|
+
if r is not None:
|
|
272
|
+
r.encoding = r.apparent_encoding
|
|
273
|
+
return r.text or ""
|
|
274
|
+
return ""
|
|
275
|
+
|
|
276
|
+
html = await asyncio.wait_for(
|
|
277
|
+
loop.run_in_executor(None, _fetch), timeout=60
|
|
278
|
+
)
|
|
278
279
|
|
|
279
|
-
html
|
|
280
|
+
if not html or not isinstance(html, str):
|
|
281
|
+
return SkillResult(success=False, error="网页内容为空")
|
|
280
282
|
|
|
281
283
|
soup = BeautifulSoup(html, "html.parser")
|
|
282
284
|
title = soup.title.get_text(strip=True) if soup.title else ""
|
|
@@ -318,6 +320,8 @@ class WebReadSkill(Skill):
|
|
|
318
320
|
},
|
|
319
321
|
message=f"已读取: {title} ({len(content)} 字符)",
|
|
320
322
|
)
|
|
323
|
+
except asyncio.TimeoutError:
|
|
324
|
+
return SkillResult(success=False, error=f"网页读取超时 (60s): {url}")
|
|
321
325
|
except Exception as e:
|
|
322
326
|
return SkillResult(success=False, error=f"网页读取失败: {e}")
|
|
323
327
|
|
|
@@ -337,6 +341,8 @@ class URLReadSkill(Skill):
|
|
|
337
341
|
|
|
338
342
|
async def execute(self, url: str = "", method: str = "GET",
|
|
339
343
|
headers: dict = None, body: str = "", **kwargs) -> SkillResult:
|
|
344
|
+
if not url:
|
|
345
|
+
return SkillResult(success=False, error="缺少 URL 参数")
|
|
340
346
|
# 确保依赖已安装
|
|
341
347
|
try:
|
|
342
348
|
import requests
|
|
@@ -349,9 +355,10 @@ class URLReadSkill(Skill):
|
|
|
349
355
|
import requests # noqa: F811
|
|
350
356
|
|
|
351
357
|
headers = headers or {}
|
|
352
|
-
loop = asyncio.
|
|
358
|
+
loop = asyncio.get_running_loop()
|
|
353
359
|
|
|
354
360
|
def _request():
|
|
361
|
+
r = None
|
|
355
362
|
try:
|
|
356
363
|
r = requests.request(
|
|
357
364
|
method=method,
|
|
@@ -370,9 +377,11 @@ class URLReadSkill(Skill):
|
|
|
370
377
|
timeout=30,
|
|
371
378
|
verify=False,
|
|
372
379
|
)
|
|
373
|
-
|
|
380
|
+
if r is not None:
|
|
381
|
+
return r
|
|
382
|
+
raise requests.exceptions.ConnectionError(f"请求失败: {url}")
|
|
374
383
|
|
|
375
|
-
response = await loop.run_in_executor(None, _request)
|
|
384
|
+
response = await asyncio.wait_for(loop.run_in_executor(None, _request), timeout=60)
|
|
376
385
|
|
|
377
386
|
try:
|
|
378
387
|
content = response.json()
|