myagent-ai 1.30.0 → 1.31.0

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.
@@ -33,6 +33,7 @@ import asyncio
33
33
  import json
34
34
  import os
35
35
  import shutil
36
+ import threading
36
37
  import time
37
38
  from pathlib import Path
38
39
  from typing import Any, Dict, List, Optional
@@ -89,7 +90,9 @@ class StealthBrowser:
89
90
  mgr = get_browser_profile_manager()
90
91
  profile = mgr.get_profile(self.profile_name)
91
92
  profile.ensure_dirs()
92
- self._user_data_dir = str(profile.user_data_dir)
93
+ # DrissionPage 的 set_user_data_path 会自动在路径下创建 Default 目录
94
+ # 我们直接用 profile_dir 作为 user-data-dir,避免嵌套
95
+ self._user_data_dir = str(profile.profile_dir)
93
96
  return self._user_data_dir
94
97
 
95
98
  async def start(self) -> SkillResult:
@@ -107,8 +110,9 @@ class StealthBrowser:
107
110
  co = ChromiumOptions()
108
111
 
109
112
  # 设置用户数据目录(Profile 持久化)
113
+ # 注意:只用 set_argument,不用 set_user_data_path,因为后者会
114
+ # 在路径下再套一层 Default/ 子目录,导致路径混乱
110
115
  if user_data:
111
- co.set_user_data_path(user_data)
112
116
  co.set_argument(f"--user-data-dir={user_data}")
113
117
 
114
118
  # 反检测核心参数
@@ -120,20 +124,16 @@ class StealthBrowser:
120
124
  co.set_argument("--disable-infobars")
121
125
  co.set_argument("--disable-extensions")
122
126
 
123
- # VNC/容器环境适配
124
- if os.environ.get("DISPLAY"):
125
- co.set_argument(f"--display={os.environ['DISPLAY']}")
127
+ # 容器环境(无论有无 DISPLAY 都加上,避免切换环境后出错)
128
+ co.set_argument("--no-sandbox")
129
+ co.set_argument("--disable-setuid-sandbox")
130
+ co.set_argument("--disable-dev-shm-usage")
131
+ co.set_argument("--disable-gpu")
126
132
 
127
133
  # 无头模式
128
134
  if self._headless:
129
135
  co.headless()
130
136
 
131
- # 容器环境
132
- if not os.environ.get("DISPLAY"):
133
- co.set_argument("--no-sandbox")
134
- co.set_argument("--disable-setuid-sandbox")
135
- co.set_argument("--disable-gpu")
136
-
137
137
  # 自动检测浏览器路径
138
138
  browser_path = self._detect_browser()
139
139
  if browser_path:
@@ -146,17 +146,27 @@ class StealthBrowser:
146
146
  self._browser = Chromium(co)
147
147
  self._page = self._browser.latest_tab
148
148
 
149
+ # 如果浏览器已崩溃或无法获取 tab
150
+ if not self._page:
151
+ return SkillResult(
152
+ success=False,
153
+ error="启动浏览器后无法获取页面标签(Chrome 可能未正确启动)",
154
+ )
155
+
149
156
  # 隐藏 webdriver 标志(额外注入)
150
157
  try:
151
- self._page.run_js("""
152
- Object.defineProperty(navigator, 'webdriver', {get: () => undefined});
153
- Object.defineProperty(navigator, 'plugins', {get: () => [1, 2, 3, 4, 5]});
154
- Object.defineProperty(navigator, 'languages', {get: () => ['zh-CN', 'zh', 'en']});
155
- window.chrome = {runtime: {}};
156
- """)
158
+ self._page.run_js(
159
+ "Object.defineProperty(navigator,'webdriver',{get:()=>undefined});"
160
+ "Object.defineProperty(navigator,'plugins',{get:()=>[1,2,3,4,5]});"
161
+ "Object.defineProperty(navigator,'languages',{get:()=>['zh-CN','zh','en']});"
162
+ "window.chrome={runtime:{}};"
163
+ )
157
164
  except Exception as e:
158
165
  logger.debug(f"反检测 JS 注入提示: {e}")
159
166
 
167
+ # 等待浏览器完全就绪
168
+ time.sleep(1)
169
+
160
170
  self._started = True
161
171
  logger.info(
162
172
  f"反检测浏览器已启动 (profile={self.profile_name}, "
@@ -177,17 +187,18 @@ class StealthBrowser:
177
187
 
178
188
  async def close(self) -> SkillResult:
179
189
  """关闭浏览器"""
190
+ self._started = False
180
191
  try:
181
192
  if self._browser:
182
193
  self._browser.quit()
183
194
  self._browser = None
184
195
  self._page = None
185
- self._started = False
186
196
  logger.info("反检测浏览器已关闭")
187
197
  return SkillResult(success=True, message="浏览器已关闭")
188
198
  except Exception as e:
189
199
  logger.error(f"关闭浏览器异常: {e}")
190
- self._started = False
200
+ self._browser = None
201
+ self._page = None
191
202
  return SkillResult(success=True, message="浏览器已关闭")
192
203
 
193
204
  async def navigate(self, url: str, wait: float = 2.0) -> SkillResult:
@@ -198,7 +209,7 @@ class StealthBrowser:
198
209
  try:
199
210
  self._page.get(url)
200
211
  if wait > 0:
201
- time.sleep(wait)
212
+ await asyncio.sleep(wait)
202
213
 
203
214
  title = self._page.title or ""
204
215
  current_url = self._page.url or ""
@@ -225,7 +236,7 @@ class StealthBrowser:
225
236
  )
226
237
  ele.click()
227
238
  if wait > 0:
228
- time.sleep(wait)
239
+ await asyncio.sleep(wait)
229
240
  return SkillResult(success=True, message=f"已点击: {selector}")
230
241
  except Exception as e:
231
242
  return SkillResult(success=False, error=f"点击失败: {e}")
@@ -248,7 +259,7 @@ class StealthBrowser:
248
259
  ele.clear()
249
260
  ele.input(value)
250
261
  if wait > 0:
251
- time.sleep(wait)
262
+ await asyncio.sleep(wait)
252
263
  return SkillResult(
253
264
  success=True,
254
265
  message=f"已填写 {selector}: {value[:50]}{'...' if len(value) > 50 else ''}",
@@ -303,7 +314,17 @@ class StealthBrowser:
303
314
  return SkillResult(success=False, error="浏览器未启动")
304
315
 
305
316
  try:
306
- text = self._page.text or ""
317
+ # Bug Fix: DrissionPage 没有 page.text 属性
318
+ # 需要通过 page.ele('tag:html').text 获取页面文本
319
+ try:
320
+ text = self._page.ele('tag:html').text or ""
321
+ except Exception:
322
+ # 降级:用 BS4 从 html 提取文本
323
+ try:
324
+ from bs4 import BeautifulSoup
325
+ text = BeautifulSoup(self._page.html or "", "html.parser").get_text(strip=True, separator="\n")
326
+ except Exception:
327
+ text = ""
307
328
  title = self._page.title or ""
308
329
  url = self._page.url or ""
309
330
 
@@ -375,16 +396,21 @@ class StealthBrowser:
375
396
  return SkillResult(success=False, error="浏览器未启动")
376
397
 
377
398
  try:
399
+ # DrissionPage cookies() 返回 CookiesList(list 子类),每项是 dict
378
400
  cookies = self._page.cookies()
379
401
  cookie_list = []
380
402
  if cookies:
381
403
  for c in cookies:
382
- cookie_list.append({
383
- "name": c.get("name", ""),
384
- "value": c.get("value", ""),
385
- "domain": c.get("domain", ""),
386
- "path": c.get("path", "/"),
387
- })
404
+ # CookiesList 中的每项是 dict,但 key 可能不全
405
+ if isinstance(c, dict):
406
+ cookie_list.append({
407
+ "name": c.get("name", ""),
408
+ "value": c.get("value", ""),
409
+ "domain": c.get("domain", ""),
410
+ "path": c.get("path", "/"),
411
+ })
412
+ else:
413
+ cookie_list.append(str(c))
388
414
 
389
415
  return SkillResult(
390
416
  success=True,
@@ -404,10 +430,15 @@ class StealthBrowser:
404
430
  cookie_list = []
405
431
  if cookies:
406
432
  for c in cookies:
407
- cookie_list.append(dict(c))
433
+ # CookiesList 中每项是 dict,确保可序列化
434
+ if isinstance(c, dict):
435
+ cookie_list.append(dict(c))
436
+ else:
437
+ cookie_list.append(str(c))
408
438
 
409
439
  from core.browser_profile import get_browser_profile_manager
410
440
  mgr = get_browser_profile_manager()
441
+ # 保存时用 profile_name 对应的原始 BrowserProfile(不加 user_data 子路径)
411
442
  profile = mgr.get_profile(self.profile_name)
412
443
  profile.save_cookies(cookie_list)
413
444
 
@@ -430,6 +461,8 @@ class StealthBrowser:
430
461
  cookies = profile.load_cookies()
431
462
 
432
463
  if cookies:
464
+ # Bug Fix: page.set.cookies 是 CookiesSetter 对象(可调用)
465
+ # 直接调用 page.set.cookies(cookies_list) 即可
433
466
  self._page.set.cookies(cookies)
434
467
  return SkillResult(
435
468
  success=True,
@@ -449,7 +482,13 @@ class StealthBrowser:
449
482
  return SkillResult(success=False, error="浏览器未启动")
450
483
 
451
484
  try:
452
- self._page.clear_cookies()
485
+ # Bug Fix: DrissionPage 没有 page.clear_cookies() 方法
486
+ # 正确方式是 page.set.cookies.clear() 或 page.clear_cache()
487
+ try:
488
+ self._page.set.cookies.clear()
489
+ except Exception:
490
+ self._page.clear_cache(cookies=True)
491
+
453
492
  from core.browser_profile import get_browser_profile_manager
454
493
  mgr = get_browser_profile_manager()
455
494
  profile = mgr.get_profile(self.profile_name)
@@ -479,13 +518,17 @@ class StealthBrowser:
479
518
  )
480
519
  logger.info(f"[{self.profile_name}] 等待用户手动操作: {reason}")
481
520
 
482
- # 简单的等待机制:轮询页面变化
521
+ # Bug Fix: 使用 asyncio.sleep 避免阻塞事件循环
483
522
  start = time.time()
484
523
  last_url = self._page.url if self._page else ""
524
+ poll_interval = 3
485
525
  while time.time() - start < timeout:
486
- time.sleep(3)
526
+ await asyncio.sleep(poll_interval)
487
527
  if self._page:
488
- current_url = self._page.url or ""
528
+ try:
529
+ current_url = self._page.url or ""
530
+ except Exception:
531
+ break # 浏览器已关闭
489
532
  # 如果 URL 发生变化,说明用户完成了操作
490
533
  if current_url != last_url and current_url:
491
534
  elapsed = int(time.time() - start)
@@ -506,11 +549,13 @@ class StealthBrowser:
506
549
  if not self._started or not self._page:
507
550
  return False
508
551
  try:
509
- # 检查页面是否仍然有效
552
+ # 检查页面是否仍然有效(try 访问 url,如果浏览器已崩溃会抛异常)
510
553
  _ = self._page.url
511
554
  return True
512
555
  except Exception:
513
- self._started = False
556
+ # 不要在这里重置 _started,由 close() 方法负责
557
+ # 避免在异步上下文中误判
558
+ logger.debug(f"页面检查失败 (profile={self.profile_name}),浏览器可能已关闭")
514
559
  return False
515
560
 
516
561
  @staticmethod
@@ -560,10 +605,10 @@ class StealthBrowser:
560
605
  # ── 全局浏览器实例管理 ──────────────────────────────────────
561
606
 
562
607
  _browsers: Dict[str, StealthBrowser] = {}
563
- _browser_lock = asyncio.Lock()
608
+ _browser_lock = threading.Lock()
564
609
 
565
610
 
566
- async def get_stealth_browser(
611
+ def get_stealth_browser(
567
612
  profile_name: str = "default",
568
613
  headless: bool = False,
569
614
  ) -> StealthBrowser:
@@ -571,8 +616,9 @@ async def get_stealth_browser(
571
616
  获取反检测浏览器实例(按 profile_name 复用)。
572
617
 
573
618
  同一 profile 名称返回相同实例,避免重复启动。
619
+ Bug Fix: 改用同步锁 + 同步返回,避免在非 async 上下文中死锁。
574
620
  """
575
- async with _browser_lock:
621
+ with _browser_lock:
576
622
  if profile_name in _browsers:
577
623
  browser = _browsers[profile_name]
578
624
  if browser._started and browser._ensure_page():
@@ -583,21 +629,37 @@ async def get_stealth_browser(
583
629
  return browser
584
630
 
585
631
 
586
- async def close_stealth_browser(profile_name: str = "") -> None:
587
- """关闭浏览器实例"""
588
- async with _browser_lock:
632
+ def close_stealth_browser(profile_name: str = "") -> None:
633
+ """关闭浏览器实例(同步版本)"""
634
+ with _browser_lock:
589
635
  if profile_name and profile_name in _browsers:
590
- await _browsers[profile_name].close()
636
+ browser = _browsers[profile_name]
637
+ # 同步调用内部关闭逻辑
638
+ browser._started = False
639
+ try:
640
+ if browser._browser:
641
+ browser._browser.quit()
642
+ except Exception:
643
+ pass
644
+ browser._browser = None
645
+ browser._page = None
591
646
  del _browsers[profile_name]
592
647
  elif not profile_name:
593
648
  for name, browser in _browsers.items():
594
649
  try:
595
- await browser.close()
650
+ browser._started = False
651
+ if browser._browser:
652
+ browser._browser.quit()
596
653
  except Exception:
597
654
  pass
598
655
  _browsers.clear()
599
656
 
600
657
 
658
+ async def close_stealth_browser_async(profile_name: str = "") -> None:
659
+ """关闭浏览器实例(异步版本,供 async 上下文调用)"""
660
+ close_stealth_browser(profile_name)
661
+
662
+
601
663
  # ── 技能类(供 SkillRegistry 自动发现)─────────────────────
602
664
 
603
665
 
@@ -607,15 +669,15 @@ class StealthBrowserStartSkill(Skill):
607
669
  name = "stealth_browser_start"
608
670
  description = (
609
671
  "启动反检测浏览器 — 使用 DrissionPage 启动带有反检测能力的 Chromium 浏览器,"
610
- "适合需要登录的网站操作(Google/X.com/微博/抖音等)。"
611
- "每个网站使用独立 Profile,登录状态自动保持。"
672
+ "适合需要登录的网站操作。每个网站使用独立 Profile,登录状态自动保持。"
673
+ "使用 site-manage list 查看所有可用网站。"
612
674
  )
613
675
  category = "browser_stealth"
614
676
  parameters = [
615
677
  SkillParameter(
616
678
  name="profile",
617
679
  type="string",
618
- description="Profile 名称(预置: google/gmail/x_com/weibo/wechat_mp/douyin/mail139/bilibili/github)",
680
+ description="Profile 名称(使用 site-manage list 查看可用网站)",
619
681
  required=False,
620
682
  default="default",
621
683
  ),
@@ -630,7 +692,8 @@ class StealthBrowserStartSkill(Skill):
630
692
  dangerous = False
631
693
 
632
694
  async def execute(self, profile: str = "default", headless: bool = False, **kw) -> SkillResult:
633
- browser = await get_stealth_browser(profile_name=profile, headless=headless)
695
+ # Bug Fix: get_stealth_browser 是同步函数,不能用 await
696
+ browser = get_stealth_browser(profile_name=profile, headless=headless)
634
697
  return await browser.start()
635
698
 
636
699
 
@@ -646,7 +709,7 @@ class StealthBrowserNavigateSkill(Skill):
646
709
  ]
647
710
 
648
711
  async def execute(self, url: str, profile: str = "default", **kw) -> SkillResult:
649
- browser = await get_stealth_browser(profile_name=profile)
712
+ browser = get_stealth_browser(profile_name=profile)
650
713
  if not browser._started:
651
714
  r = await browser.start()
652
715
  if not r.success:
@@ -666,7 +729,7 @@ class StealthBrowserClickSkill(Skill):
666
729
  ]
667
730
 
668
731
  async def execute(self, selector: str, profile: str = "default", **kw) -> SkillResult:
669
- browser = await get_stealth_browser(profile_name=profile)
732
+ browser = get_stealth_browser(profile_name=profile)
670
733
  return await browser.click(selector)
671
734
 
672
735
 
@@ -686,7 +749,7 @@ class StealthBrowserFillSkill(Skill):
686
749
  async def execute(
687
750
  self, selector: str, value: str, clear: bool = True, profile: str = "default", **kw
688
751
  ) -> SkillResult:
689
- browser = await get_stealth_browser(profile_name=profile)
752
+ browser = get_stealth_browser(profile_name=profile)
690
753
  return await browser.fill(selector, value, clear=clear)
691
754
 
692
755
 
@@ -702,7 +765,7 @@ class StealthBrowserScreenshotSkill(Skill):
702
765
  ]
703
766
 
704
767
  async def execute(self, save_path: str = "", profile: str = "default", **kw) -> SkillResult:
705
- browser = await get_stealth_browser(profile_name=profile)
768
+ browser = get_stealth_browser(profile_name=profile)
706
769
  return await browser.screenshot(save_path=save_path)
707
770
 
708
771
 
@@ -718,7 +781,7 @@ class StealthBrowserEvalSkill(Skill):
718
781
  ]
719
782
 
720
783
  async def execute(self, script: str, profile: str = "default", **kw) -> SkillResult:
721
- browser = await get_stealth_browser(profile_name=profile)
784
+ browser = get_stealth_browser(profile_name=profile)
722
785
  return await browser.evaluate(script)
723
786
 
724
787
 
@@ -733,7 +796,7 @@ class StealthBrowserGetContentSkill(Skill):
733
796
  ]
734
797
 
735
798
  async def execute(self, profile: str = "default", **kw) -> SkillResult:
736
- browser = await get_stealth_browser(profile_name=profile)
799
+ browser = get_stealth_browser(profile_name=profile)
737
800
  return await browser.get_content()
738
801
 
739
802
 
@@ -759,7 +822,7 @@ class StealthBrowserCookieSkill(Skill):
759
822
  ]
760
823
 
761
824
  async def execute(self, action: str, profile: str = "default", **kw) -> SkillResult:
762
- browser = await get_stealth_browser(profile_name=profile)
825
+ browser = get_stealth_browser(profile_name=profile)
763
826
  if action == "save":
764
827
  return await browser.save_cookies()
765
828
  elif action == "load":
@@ -783,7 +846,8 @@ class StealthBrowserCloseSkill(Skill):
783
846
  ]
784
847
 
785
848
  async def execute(self, profile: str = "", **kw) -> SkillResult:
786
- await close_stealth_browser(profile_name=profile)
849
+ # close_stealth_browser 现在是同步函数,直接调用
850
+ close_stealth_browser(profile_name=profile)
787
851
  return SkillResult(success=True, message="浏览器已关闭")
788
852
 
789
853
 
@@ -818,7 +882,7 @@ class StealthBrowserWaitManualSkill(Skill):
818
882
  async def execute(
819
883
  self, reason: str = "验证码", timeout: int = 300, profile: str = "default", **kw
820
884
  ) -> SkillResult:
821
- browser = await get_stealth_browser(profile_name=profile)
885
+ browser = get_stealth_browser(profile_name=profile)
822
886
  return await browser.wait_for_manual(reason=reason, timeout=float(timeout))
823
887
 
824
888
 
@@ -837,10 +901,149 @@ class StealthBrowserWaitSkill(Skill):
837
901
  async def execute(
838
902
  self, selector: str, timeout: int = 10, profile: str = "default", **kw
839
903
  ) -> SkillResult:
840
- browser = await get_stealth_browser(profile_name=profile)
904
+ browser = get_stealth_browser(profile_name=profile)
841
905
  return await browser.wait_for(selector, timeout=float(timeout))
842
906
 
843
907
 
908
+ # ── Site Registry 管理技能 (v1.31.0) ────────────────────
909
+
910
+
911
+ class SiteManageListSkill(Skill):
912
+ """列出所有已注册网站"""
913
+
914
+ name = "site_manage_list"
915
+ description = "列出所有已注册网站 — 显示网站名称、分类、登录 URL 和 Profile 状态"
916
+ category = "browser_stealth"
917
+ parameters = [
918
+ SkillParameter(
919
+ name="category",
920
+ type="string",
921
+ description="按分类筛选(空=全部)",
922
+ required=False,
923
+ default="",
924
+ ),
925
+ ]
926
+
927
+ async def execute(self, category: str = "", **kw) -> SkillResult:
928
+ from core.site_registry import get_site_registry
929
+ reg = get_site_registry()
930
+ sites = reg.list_sites(category=category)
931
+ summary = reg.to_summary()
932
+
933
+ return SkillResult(
934
+ success=True,
935
+ message=f"共 {len(sites)} 个网站" + (f" (分类: {category})" if category else ""),
936
+ data={"sites": sites, "summary": {k: v for k, v in summary.items() if k != "sites"}},
937
+ output=json.dumps(sites, ensure_ascii=False, indent=2),
938
+ )
939
+
940
+
941
+ class SiteManageShowSkill(Skill):
942
+ """显示网站详细配置"""
943
+
944
+ name = "site_manage_show"
945
+ description = "显示网站详细配置 — 查看网站的登录 URL、可用页面和操作提示"
946
+ category = "browser_stealth"
947
+ parameters = [
948
+ SkillParameter(name="name", type="string", description="网站名称", required=True),
949
+ ]
950
+
951
+ async def execute(self, name: str, **kw) -> SkillResult:
952
+ from core.site_registry import get_site_registry
953
+ reg = get_site_registry()
954
+ site = reg.get_site(name)
955
+ if not site:
956
+ return SkillResult(success=False, error=f"网站不存在: {name}")
957
+
958
+ return SkillResult(
959
+ success=True,
960
+ message=f"网站: {site.get('display_name', name)}",
961
+ data=site,
962
+ output=json.dumps(site, ensure_ascii=False, indent=2),
963
+ )
964
+
965
+
966
+ class SiteManageAddSkill(Skill):
967
+ """添加自定义网站"""
968
+
969
+ name = "site_manage_add"
970
+ description = (
971
+ "添加自定义网站 — 为新网站创建配置,后续可用 stealth-open 登录。"
972
+ "需提供 name(英文标识)、login_url(登录页)和 display_name(显示名称)。"
973
+ )
974
+ category = "browser_stealth"
975
+ parameters = [
976
+ SkillParameter(name="name", type="string", description="网站名称(英文,如 my_site)", required=True),
977
+ SkillParameter(name="display_name", type="string", description="显示名称", required=False, default=""),
978
+ SkillParameter(name="login_url", type="string", description="登录页 URL", required=False, default=""),
979
+ SkillParameter(name="home_url", type="string", description="首页 URL", required=False, default=""),
980
+ SkillParameter(name="category", type="string", description="分类", required=False, default="custom"),
981
+ ]
982
+
983
+ async def execute(
984
+ self, name: str, display_name: str = "", login_url: str = "",
985
+ home_url: str = "", category: str = "custom", **kw
986
+ ) -> SkillResult:
987
+ from core.site_registry import get_site_registry
988
+ reg = get_site_registry()
989
+ try:
990
+ site = reg.add_site({
991
+ "name": name,
992
+ "display_name": display_name or name,
993
+ "login_url": login_url,
994
+ "home_url": home_url,
995
+ "category": category,
996
+ })
997
+ return SkillResult(
998
+ success=True,
999
+ message=f"已添加网站: {name}",
1000
+ data=site,
1001
+ )
1002
+ except ValueError as e:
1003
+ return SkillResult(success=False, error=str(e))
1004
+
1005
+
1006
+ class SiteManageRemoveSkill(Skill):
1007
+ """删除自定义网站"""
1008
+
1009
+ name = "site_manage_remove"
1010
+ description = "删除自定义网站(内置网站不可删除)"
1011
+ category = "browser_stealth"
1012
+ parameters = [
1013
+ SkillParameter(name="name", type="string", description="网站名称", required=True),
1014
+ ]
1015
+
1016
+ async def execute(self, name: str, **kw) -> SkillResult:
1017
+ from core.site_registry import get_site_registry
1018
+ reg = get_site_registry()
1019
+ ok = reg.remove_site(name)
1020
+ if ok:
1021
+ return SkillResult(success=True, message=f"已删除网站: {name}")
1022
+ return SkillResult(
1023
+ success=False,
1024
+ error=f"删除失败: {name}(内置网站不可删除,或网站不存在)",
1025
+ )
1026
+
1027
+
1028
+ class SiteManageInitSkill(Skill):
1029
+ """批量初始化网站 Profile"""
1030
+
1031
+ name = "site_manage_init"
1032
+ description = "批量初始化所有网站的浏览器 Profile(仅创建目录,不自动登录)"
1033
+ category = "browser_stealth"
1034
+ parameters = []
1035
+
1036
+ async def execute(self, **kw) -> SkillResult:
1037
+ from core.site_registry import get_site_registry
1038
+ reg = get_site_registry()
1039
+ initialized = reg.init_profiles()
1040
+ return SkillResult(
1041
+ success=True,
1042
+ message=f"已初始化 {len(initialized)} 个网站的 Profile",
1043
+ data={"initialized": initialized, "count": len(initialized)},
1044
+ )
1045
+
1046
+
844
1047
  # ── Profile 管理技能 ──────────────────────────────────────
845
1048
 
846
1049
 
@@ -206,6 +206,9 @@ CLI_COMMANDS: List[Dict[str, Any]] = [
206
206
  "description": "创建浏览器 Profile"},
207
207
  {"name": "profile-delete", "category": "stealth", "cli": "myagent-ai profile-delete <name>",
208
208
  "description": "删除浏览器 Profile"},
209
+ # ── Site 管理 (v1.31.0) ──
210
+ {"name": "site-manage", "category": "stealth", "cli": "myagent-ai site-manage <list|show|add|remove|init> [args]",
211
+ "description": "网站注册管理 — 列出/查看/添加/删除/初始化网站配置(支持 20+ 内置网站和动态扩展)"},
209
212
  # ── GUI 桌面 ──
210
213
  {"name": "screenshot", "category": "gui", "cli": "myagent-ai screenshot [region] [-m monitor]",
211
214
  "description": "屏幕截图(仅 Windows/macOS)"},