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 +1 -1
- package/web/api_server.py +125 -59
- package/web/ui/admin/admin-sites.js +24 -0
package/package.json
CHANGED
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
|
-
|
|
4961
|
-
|
|
4962
|
-
|
|
4963
|
-
|
|
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
|
-
|
|
4973
|
-
|
|
4974
|
-
|
|
4975
|
-
|
|
4976
|
-
|
|
4977
|
-
|
|
4978
|
-
|
|
4979
|
-
|
|
4980
|
-
|
|
4981
|
-
|
|
4982
|
-
|
|
4983
|
-
|
|
4984
|
-
|
|
4985
|
-
|
|
4986
|
-
|
|
4987
|
-
|
|
4988
|
-
|
|
4989
|
-
|
|
4990
|
-
|
|
4991
|
-
|
|
4992
|
-
|
|
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
|
-
|
|
4996
|
-
|
|
4997
|
-
|
|
4998
|
-
|
|
4999
|
-
|
|
5000
|
-
|
|
5001
|
-
|
|
5002
|
-
|
|
5003
|
-
|
|
5004
|
-
|
|
5005
|
-
|
|
5006
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5019
|
-
|
|
5020
|
-
|
|
5021
|
-
|
|
5022
|
-
|
|
5023
|
-
|
|
5024
|
-
|
|
5025
|
-
|
|
5026
|
-
|
|
5027
|
-
|
|
5028
|
-
|
|
5029
|
-
|
|
5030
|
-
|
|
5031
|
-
|
|
5032
|
-
|
|
5033
|
-
self.core.skill_registry
|
|
5034
|
-
|
|
5035
|
-
|
|
5036
|
-
|
|
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;
|