myagent-ai 1.15.36 → 1.15.38

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/README.md CHANGED
@@ -125,7 +125,16 @@ curl -fsSL https://raw.githubusercontent.com/ctz168/myagent/main/install/install
125
125
 
126
126
  ### 一键重装(跳过依赖检查)
127
127
 
128
- > 已安装过 MyAgent 的环境下,快速升级/重装,不检查 Python/Node.js 和 pip 依赖。
128
+ > 已安装过 MyAgent 的环境下,快速升级/重装,安装和启动时均不检查 pip 依赖。
129
+
130
+ **npm 方式(推荐,最简单):**
131
+ ```bash
132
+ npx myagent-ai --skip-deps
133
+ ```
134
+
135
+ > `npx` 会自动安装/升级最新版 `myagent-ai`,`--skip-deps` 跳过安装和启动时的所有依赖检查。
136
+
137
+ **脚本方式:**
129
138
 
130
139
  **Windows(PowerShell):**
131
140
  ```powershell
@@ -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
- result = exec_result.to_dict()
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/main.py CHANGED
@@ -155,16 +155,20 @@ class MyAgentApp:
155
155
  self.logger.info("=" * 60)
156
156
 
157
157
  # 1.5 自动检测并安装缺失依赖(开箱即用)
158
- self.logger.info("检查依赖...")
159
- deps_result = check_and_install_deps(auto_fix=True, silent=False)
160
- if deps_result["installed"] > 0:
161
- self.logger.info(
162
- f"自动安装了 {deps_result['installed']} 个依赖"
163
- )
164
- if deps_result["failed"] > 0:
165
- self.logger.warning(
166
- f"{deps_result['failed']} 个依赖安装失败,相关功能可能不可用"
167
- )
158
+ # 可通过 --skip-deps 参数或 MYAGENT_SKIP_DEPS=1 环境变量跳过
159
+ if os.environ.get("MYAGENT_SKIP_DEPS") == "1":
160
+ self.logger.info("跳过依赖检查(--skip-deps)")
161
+ else:
162
+ self.logger.info("检查依赖...")
163
+ deps_result = check_and_install_deps(auto_fix=True, silent=False)
164
+ if deps_result["installed"] > 0:
165
+ self.logger.info(
166
+ f"自动安装了 {deps_result['installed']} 个依赖"
167
+ )
168
+ if deps_result["failed"] > 0:
169
+ self.logger.warning(
170
+ f"{deps_result['failed']} 个依赖安装失败,相关功能可能不可用"
171
+ )
168
172
 
169
173
  # 2. LLM 客户端
170
174
  llm_cfg = self.config.llm
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.15.36",
3
+ "version": "1.15.38",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
@@ -118,20 +118,22 @@ class WebSearchSkill(Skill):
118
118
  import requests
119
119
  from bs4 import BeautifulSoup
120
120
 
121
- loop = asyncio.get_event_loop()
121
+ loop = asyncio.get_running_loop()
122
122
 
123
123
  def _fetch():
124
- url = "https://html.duckduckgo.com/html/"
124
+ r = None
125
125
  try:
126
- r = requests.get(url, params={"q": query}, headers=_HEADERS, timeout=15)
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(url, params={"q": query}, headers=_HEADERS, timeout=15, verify=False)
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
- return r.text
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.get_event_loop()
175
+ loop = asyncio.get_running_loop()
174
176
 
175
177
  def _fetch():
176
- url = "https://www.bing.com/search"
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
- return r.text
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
- loop = asyncio.get_event_loop()
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.encoding = r.apparent_encoding
277
- return r.text
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 = await loop.run_in_executor(None, _fetch)
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.get_event_loop()
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
- return r
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()
package/start.js CHANGED
@@ -584,17 +584,22 @@ function main() {
584
584
  }
585
585
 
586
586
  // 使用 venv 的 Python 启动
587
+ const spawnEnv = {
588
+ ...process.env,
589
+ // 设置 VIRTUAL_ENV 环境变量,让 Python 程序知道自己运行在 venv 中
590
+ VIRTUAL_ENV: venvDir,
591
+ PATH: IS_WIN
592
+ ? `${path.join(venvDir, "Scripts")};${process.env.PATH}`
593
+ : `${path.join(venvDir, "bin")}:${process.env.PATH}`,
594
+ };
595
+ // 将 --skip-deps 信号传递给 Python,让 main.py 也跳过依赖检查
596
+ if (skipDeps) {
597
+ spawnEnv.MYAGENT_SKIP_DEPS = "1";
598
+ }
587
599
  const child = spawn(venvPython, pyArgs, {
588
600
  cwd: pkgDir,
589
601
  stdio: "inherit",
590
- env: {
591
- ...process.env,
592
- // 设置 VIRTUAL_ENV 环境变量,让 Python 程序知道自己运行在 venv 中
593
- VIRTUAL_ENV: venvDir,
594
- PATH: IS_WIN
595
- ? `${path.join(venvDir, "Scripts")};${process.env.PATH}`
596
- : `${path.join(venvDir, "bin")}:${process.env.PATH}`,
597
- },
602
+ env: spawnEnv,
598
603
  });
599
604
 
600
605
  child.on("error", (err) => {