myagent-ai 1.31.2 → 1.31.3

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "myagent-ai",
3
- "version": "1.31.2",
3
+ "version": "1.31.3",
4
4
  "description": "本地桌面端执行型AI助手 - Open Interpreter 风格 | Local Desktop Execution-Oriented AI Assistant",
5
5
  "main": "main.py",
6
6
  "bin": {
package/web/api_server.py CHANGED
@@ -505,6 +505,8 @@ class ApiServer:
505
505
  r.add_post("/api/sites/{name}/init-profile", self.handle_init_site_profile)
506
506
  r.add_get("/api/sites/profiles", self.handle_list_profiles)
507
507
  r.add_delete("/api/sites/profiles/{name}", self.handle_delete_profile)
508
+ r.add_post("/api/sites/{name}/open-browser", self.handle_open_site_browser)
509
+ r.add_post("/api/sites/{name}/close-browser", self.handle_close_site_browser)
508
510
  # ── 任务持久化 ──
509
511
  r.add_get("/api/tasks", self.handle_list_tasks)
510
512
  r.add_post("/api/tasks/{task_id}/retry", self.handle_retry_task)
@@ -4957,10 +4959,14 @@ window.addEventListener('beforeunload', function() {{
4957
4959
  return web.json_response({"builtin_tools": [], "cli_commands": [], "python_skills": [], "skill_guides": []})
4958
4960
 
4959
4961
  view = request.query.get("view", "")
4960
- if view == "categorized":
4961
- return web.json_response(self.core.skill_registry.list_skills_by_category())
4962
- # 默认:向后兼容的扁平列表
4963
- return web.json_response(self.core.skill_registry.list_skills_info())
4962
+ try:
4963
+ if view == "categorized":
4964
+ return web.json_response(self.core.skill_registry.list_skills_by_category())
4965
+ # 默认:向后兼容的扁平列表
4966
+ return web.json_response(self.core.skill_registry.list_skills_info())
4967
+ except Exception as e:
4968
+ logger.error(f"list_skills 失败: {e}", exc_info=True)
4969
+ return web.json_response({"error": f"获取技能列表失败: {e}"}, status=500)
4964
4970
 
4965
4971
  async def handle_get_skill(self, request):
4966
4972
  """GET /api/skills/{name} - 获取单个技能/工具详情
@@ -4969,44 +4975,48 @@ window.addEventListener('beforeunload', function() {{
4969
4975
  """
4970
4976
  name = request.match_info["name"]
4971
4977
 
4972
- # 1. 从 SkillRegistry 查找(Python 技能 + Markdown 技能指南 + 旧版存根)
4973
- if self.core.skill_registry:
4974
- s = self.core.skill_registry._skills.get(name)
4975
- if s:
4976
- info = s.to_openclaw_format()
4977
- info["disabled"] = self.core.skill_registry._is_disabled(name)
4978
- return web.json_response(info)
4979
-
4980
- # 2. 从 BUILTIN_TOOLS 元数据查找(内置平台工具)
4981
- from aiskills.registry import BUILTIN_TOOLS, CLI_COMMANDS
4982
- for bt in BUILTIN_TOOLS:
4983
- if bt["name"] == name:
4984
- return web.json_response({
4985
- "name": bt["name"],
4986
- "description": bt["description"],
4987
- "category": bt["category"],
4988
- "parameters": bt["parameters"],
4989
- "dangerous": False,
4990
- "disabled": False,
4991
- "skill_type": "builtin_platform",
4992
- "note": f"由 {bt['handler']} 处理,不可禁用",
4993
- })
4978
+ try:
4979
+ # 1. 从 SkillRegistry 查找(Python 技能 + Markdown 技能指南 + 旧版存根)
4980
+ if self.core.skill_registry:
4981
+ s = self.core.skill_registry._skills.get(name)
4982
+ if s:
4983
+ info = s.to_openclaw_format()
4984
+ info["disabled"] = self.core.skill_registry._is_disabled(name)
4985
+ return web.json_response(info)
4986
+
4987
+ # 2. BUILTIN_TOOLS 元数据查找(内置平台工具)
4988
+ from aiskills.registry import BUILTIN_TOOLS, CLI_COMMANDS
4989
+ for bt in BUILTIN_TOOLS:
4990
+ if bt["name"] == name:
4991
+ return web.json_response({
4992
+ "name": bt["name"],
4993
+ "description": bt["description"],
4994
+ "category": bt["category"],
4995
+ "parameters": bt["parameters"],
4996
+ "dangerous": False,
4997
+ "disabled": False,
4998
+ "skill_type": "builtin_platform",
4999
+ "note": f"由 {bt['handler']} 处理,不可禁用",
5000
+ })
4994
5001
 
4995
- # 3. [v1.23.0] 从 CLI_COMMANDS 查找
4996
- for cmd in CLI_COMMANDS:
4997
- if cmd["name"] == name or name in cmd.get("aliases", []):
4998
- return web.json_response({
4999
- "name": cmd["name"],
5000
- "description": cmd["description"],
5001
- "category": cmd["category"],
5002
- "cli": cmd.get("cli", ""),
5003
- "aliases": cmd.get("aliases", []),
5004
- "disabled": False,
5005
- "skill_type": "cli_command",
5006
- "note": "通过 command 工具调用: myagent-ai <name>",
5007
- })
5002
+ # 3. [v1.23.0] 从 CLI_COMMANDS 查找
5003
+ for cmd in CLI_COMMANDS:
5004
+ if cmd["name"] == name or name in cmd.get("aliases", []):
5005
+ return web.json_response({
5006
+ "name": cmd["name"],
5007
+ "description": cmd["description"],
5008
+ "category": cmd["category"],
5009
+ "cli": cmd.get("cli", ""),
5010
+ "aliases": cmd.get("aliases", []),
5011
+ "disabled": False,
5012
+ "skill_type": "cli_command",
5013
+ "note": "通过 command 工具调用: myagent-ai <name>",
5014
+ })
5008
5015
 
5009
- return web.json_response({"error": "not found"}, status=404)
5016
+ return web.json_response({"error": "not found"}, status=404)
5017
+ except Exception as e:
5018
+ logger.error(f"get_skill 失败 ({name}): {e}", exc_info=True)
5019
+ return web.json_response({"error": f"获取技能 '{name}' 失败: {e}"}, status=500)
5010
5020
 
5011
5021
  async def handle_toggle_skill(self, request):
5012
5022
  """POST /api/skills/{name}/toggle - 启用/禁用技能
@@ -5015,25 +5025,29 @@ window.addEventListener('beforeunload', function() {{
5015
5025
  """
5016
5026
  name = request.match_info["name"]
5017
5027
  try:
5018
- data = await request.json()
5019
- except Exception:
5020
- data = {}
5021
- enabled = data.get("enabled", True)
5022
-
5023
- # [v1.23.0] 内置平台工具和 CLI 命令不可禁用
5024
- from aiskills.registry import BUILTIN_TOOLS, CLI_COMMANDS
5025
- builtin_names = {bt["name"] for bt in BUILTIN_TOOLS}
5026
- cli_names = {c["name"] for c in CLI_COMMANDS}
5027
- if name in builtin_names:
5028
- return web.json_response({"error": f"内置平台工具 '{name}' 不可禁用"}, status=400)
5029
- if name in cli_names:
5030
- return web.json_response({"error": f"CLI 命令 '{name}' 不可禁用(由 command 工具统一管理)"}, status=400)
5031
-
5032
- if self.core.skill_registry:
5033
- self.core.skill_registry.toggle(name, enabled)
5034
- # 持久化到配置文件
5035
- self._save_disabled_skills()
5036
- return web.json_response({"ok": True, "name": name, "enabled": enabled})
5028
+ try:
5029
+ data = await request.json()
5030
+ except Exception:
5031
+ data = {}
5032
+ enabled = data.get("enabled", True)
5033
+
5034
+ # [v1.23.0] 内置平台工具和 CLI 命令不可禁用
5035
+ from aiskills.registry import BUILTIN_TOOLS, CLI_COMMANDS
5036
+ builtin_names = {bt["name"] for bt in BUILTIN_TOOLS}
5037
+ cli_names = {c["name"] for c in CLI_COMMANDS}
5038
+ if name in builtin_names:
5039
+ return web.json_response({"error": f"内置平台工具 '{name}' 不可禁用"}, status=400)
5040
+ if name in cli_names:
5041
+ return web.json_response({"error": f"CLI 命令 '{name}' 不可禁用(由 command 工具统一管理)"}, status=400)
5042
+
5043
+ if self.core.skill_registry:
5044
+ self.core.skill_registry.toggle(name, enabled)
5045
+ # 持久化到配置文件
5046
+ self._save_disabled_skills()
5047
+ return web.json_response({"ok": True, "name": name, "enabled": enabled})
5048
+ except Exception as e:
5049
+ logger.error(f"toggle_skill 失败 ({name}): {e}", exc_info=True)
5050
+ return web.json_response({"error": f"切换技能 '{name}' 状态失败: {e}"}, status=500)
5037
5051
 
5038
5052
  def _save_disabled_skills(self):
5039
5053
  """将禁用技能列表持久化到配置文件"""
@@ -5200,6 +5214,58 @@ window.addEventListener('beforeunload', function() {{
5200
5214
  return web.json_response({"error": f"Profile '{name}' 不存在或删除失败"}, status=404)
5201
5215
  return web.json_response({"ok": True})
5202
5216
 
5217
+ async def handle_open_site_browser(self, request):
5218
+ """POST /api/sites/{name}/open-browser - 打开浏览器登录网站"""
5219
+ name = request.match_info["name"]
5220
+ reg = self._get_site_registry()
5221
+ site = reg.get_site(name)
5222
+ if not site:
5223
+ return web.json_response({"error": f"网站 '{name}' 不存在"}, status=404)
5224
+
5225
+ # Ensure profile is initialized
5226
+ from core.browser_profile import get_browser_profile_manager
5227
+ mgr = get_browser_profile_manager()
5228
+ profile = mgr.get_profile(name)
5229
+ if not profile.is_initialized():
5230
+ profile.ensure_dirs()
5231
+
5232
+ login_url = site.get("login_url", "")
5233
+
5234
+ try:
5235
+ from aiskills.browser_stealth import get_stealth_browser
5236
+ browser = get_stealth_browser(profile_name=name, headless=False)
5237
+ result = await browser.start()
5238
+ if not result.success:
5239
+ return web.json_response({"error": f"启动浏览器失败: {result.error}"}, status=500)
5240
+
5241
+ # Navigate to login URL
5242
+ if login_url:
5243
+ page = browser._page
5244
+ if page:
5245
+ page.get(login_url)
5246
+
5247
+ return web.json_response({
5248
+ "ok": True,
5249
+ "site": name,
5250
+ "login_url": login_url,
5251
+ "message": f"浏览器已打开,请在浏览器中完成 {site.get('display_name', name)} 的登录",
5252
+ })
5253
+ except Exception as e:
5254
+ logger.error(f"打开浏览器失败 ({name}): {e}")
5255
+ return web.json_response({"error": f"打开浏览器失败: {e}"}, status=500)
5256
+
5257
+ async def handle_close_site_browser(self, request):
5258
+ """POST /api/sites/{name}/close-browser - 关闭网站浏览器"""
5259
+ name = request.match_info["name"]
5260
+
5261
+ try:
5262
+ from aiskills.browser_stealth import close_stealth_browser
5263
+ close_stealth_browser(profile_name=name)
5264
+ return web.json_response({"ok": True, "site": name, "message": f"浏览器已关闭 ({name})"})
5265
+ except Exception as e:
5266
+ logger.error(f"关闭浏览器失败 ({name}): {e}")
5267
+ return web.json_response({"error": f"关闭浏览器失败: {e}"}, status=500)
5268
+
5203
5269
  # --- Workdir ---
5204
5270
  async def handle_get_workdir(self, request):
5205
5271
  return web.json_response({"path": str(self.core.config_mgr.data_dir / "workspace")})
@@ -85,7 +85,14 @@ function siteRowHtml(s) {
85
85
  actions += '<button class="btn btn-sm btn-ghost" onclick="editSiteModal(\'' + name + '\')">编辑</button>';
86
86
  actions += '<button class="btn btn-sm btn-ghost" style="color:var(--danger)" onclick="removeSite(\'' + name + '\')">删除</button>';
87
87
  }
88
+ // 登录按钮 - 始终显示(有 login_url 时突出显示)
89
+ if (s.login_url) {
90
+ actions += '<button class="btn btn-sm btn-primary" onclick="openSiteBrowser(\'' + name + '\')">登录</button>';
91
+ } else {
92
+ actions += '<button class="btn btn-sm btn-ghost" onclick="openSiteBrowser(\'' + name + '\')">登录</button>';
93
+ }
88
94
  if (hasProfile) {
95
+ actions += '<button class="btn btn-sm btn-ghost" style="color:var(--warn)" onclick="closeSiteBrowser(\'' + name + '\')">关闭浏览器</button>';
89
96
  actions += '<button class="btn btn-sm btn-ghost" style="color:var(--warn)" onclick="deleteSiteProfile(\'' + name + '\')">清除登录</button>';
90
97
  } else {
91
98
  actions += '<button class="btn btn-sm btn-ghost" onclick="initSiteProfile(\'' + name + '\')">Profile</button>';
@@ -312,6 +319,23 @@ async function deleteSiteProfile(name) {
312
319
  });
313
320
  }
314
321
 
322
+ // ── Browser Login ──
323
+ async function openSiteBrowser(name) {
324
+ showToast('正在启动浏览器...', 'info');
325
+ var r = await api('/api/sites/' + encodeURIComponent(name) + '/open-browser', { method: 'POST' });
326
+ if (r.error) { showToast(r.error, 'danger'); return; }
327
+ showToast(r.message || '浏览器已打开', 'success', 5000);
328
+ // Refresh to update profile status
329
+ setTimeout(function() { renderSites(); }, 2000);
330
+ }
331
+
332
+ async function closeSiteBrowser(name) {
333
+ var r = await api('/api/sites/' + encodeURIComponent(name) + '/close-browser', { method: 'POST' });
334
+ if (r.error) { showToast(r.error, 'danger'); return; }
335
+ showToast('浏览器已关闭', 'success');
336
+ setTimeout(function() { renderSites(); }, 1000);
337
+ }
338
+
315
339
  // Register renderer
316
340
  if (typeof window._adminRenderers === 'undefined') window._adminRenderers = {};
317
341
  window._adminRenderers['sites'] = renderSites;