myagent-ai 1.20.6 → 1.20.8
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/chatbot/manager.py +72 -4
- package/core/web_control.py +12 -10
- package/package.json +1 -1
- package/web/api_server.py +9 -6
package/chatbot/manager.py
CHANGED
|
@@ -35,7 +35,9 @@ class ChatBotManager:
|
|
|
35
35
|
|
|
36
36
|
def __init__(self):
|
|
37
37
|
self._bots: Dict[str, BaseChatBot] = {}
|
|
38
|
+
self._bot_tasks: Dict[str, asyncio.Task] = {} # key -> asyncio.Task
|
|
38
39
|
self._session_map: Dict[str, str] = {} # session_id -> last_message
|
|
40
|
+
self._message_handler: Optional[Callable] = None
|
|
39
41
|
|
|
40
42
|
def get_bot(self, key: str):
|
|
41
43
|
"""根据 key (id 或 platform 名) 获取 bot 实例"""
|
|
@@ -49,20 +51,62 @@ class ChatBotManager:
|
|
|
49
51
|
"""
|
|
50
52
|
初始化所有聊天平台。
|
|
51
53
|
|
|
54
|
+
[v1.20.7] 修复: 先停止并移除已不存在的/被禁用的 bot,
|
|
55
|
+
再创建新启用的 bot。之前只做添加不做移除,导致禁用平台后
|
|
56
|
+
bot 仍在后台运行。
|
|
57
|
+
|
|
52
58
|
Args:
|
|
53
59
|
platform_configs: 平台配置列表
|
|
54
60
|
message_handler: 统一消息处理回调
|
|
55
61
|
"""
|
|
62
|
+
self._message_handler = message_handler
|
|
63
|
+
|
|
64
|
+
# 计算当前应该启用的平台 key 集合
|
|
65
|
+
new_keys = set()
|
|
66
|
+
for cfg in platform_configs:
|
|
67
|
+
if cfg.enabled:
|
|
68
|
+
key = cfg.id or cfg.platform
|
|
69
|
+
new_keys.add(key)
|
|
70
|
+
|
|
71
|
+
# 找出需要移除的(旧的 key 不在新的 key 集合中)
|
|
72
|
+
removed_keys = [k for k in self._bots if k not in new_keys]
|
|
73
|
+
for key in removed_keys:
|
|
74
|
+
bot = self._bots.pop(key, None)
|
|
75
|
+
task = self._bot_tasks.pop(key, None)
|
|
76
|
+
if bot:
|
|
77
|
+
logger.info(f"聊天平台已移除(禁用/删除): {key}")
|
|
78
|
+
# 尝试停止 bot(同步包装异步)
|
|
79
|
+
try:
|
|
80
|
+
loop = asyncio.get_event_loop()
|
|
81
|
+
if loop.is_running():
|
|
82
|
+
asyncio.ensure_future(self._safe_stop(key, bot))
|
|
83
|
+
else:
|
|
84
|
+
loop.run_until_complete(bot.stop())
|
|
85
|
+
except RuntimeError:
|
|
86
|
+
pass
|
|
87
|
+
if task and not task.done():
|
|
88
|
+
task.cancel()
|
|
89
|
+
logger.info(f"聊天平台 {key} 后台任务已取消")
|
|
90
|
+
|
|
91
|
+
# 创建或更新启用的平台
|
|
56
92
|
for cfg in platform_configs:
|
|
57
93
|
if not cfg.enabled:
|
|
58
94
|
continue
|
|
95
|
+
key = cfg.id or cfg.platform
|
|
96
|
+
# 如果已经存在且配置没变,跳过重建
|
|
97
|
+
if key in self._bots:
|
|
98
|
+
continue
|
|
59
99
|
try:
|
|
60
100
|
bot = self._create_bot(cfg, message_handler)
|
|
61
101
|
if bot:
|
|
62
|
-
# 使用唯一 id 作为 key,支持多实例
|
|
63
|
-
key = cfg.id or cfg.platform
|
|
64
102
|
self._bots[key] = bot
|
|
65
103
|
logger.info(f"聊天平台已配置: {cfg.display_name or key}")
|
|
104
|
+
# 如果已经在运行中(start_all 已调用),自动启动新 bot
|
|
105
|
+
loop = asyncio.get_event_loop()
|
|
106
|
+
if loop.is_running() and not any(
|
|
107
|
+
t for t in asyncio.all_tasks(loop) if t.get_name() == f"bot_{key}"
|
|
108
|
+
):
|
|
109
|
+
asyncio.ensure_future(self._run_bot(key, bot))
|
|
66
110
|
except Exception as e:
|
|
67
111
|
logger.error(f"平台 {cfg.display_name or cfg.platform} 初始化失败: {e}")
|
|
68
112
|
|
|
@@ -143,7 +187,8 @@ class ChatBotManager:
|
|
|
143
187
|
tasks = []
|
|
144
188
|
for name, bot in self._bots.items():
|
|
145
189
|
logger.info(f"启动聊天平台: {name}")
|
|
146
|
-
task = asyncio.create_task(self._run_bot(name, bot))
|
|
190
|
+
task = asyncio.create_task(self._run_bot(name, bot), name=f"bot_{name}")
|
|
191
|
+
self._bot_tasks[name] = task
|
|
147
192
|
tasks.append(task)
|
|
148
193
|
await asyncio.gather(*tasks, return_exceptions=True)
|
|
149
194
|
|
|
@@ -151,16 +196,39 @@ class ChatBotManager:
|
|
|
151
196
|
"""安全运行单个聊天平台"""
|
|
152
197
|
try:
|
|
153
198
|
await bot.start()
|
|
199
|
+
except asyncio.CancelledError:
|
|
200
|
+
logger.info(f"聊天平台 {name} 已取消")
|
|
154
201
|
except Exception as e:
|
|
155
202
|
logger.error(f"聊天平台 {name} 运行异常: {e}")
|
|
156
203
|
|
|
204
|
+
async def _safe_stop(self, name: str, bot: BaseChatBot):
|
|
205
|
+
"""安全停止单个 bot(不抛异常)"""
|
|
206
|
+
try:
|
|
207
|
+
await bot.stop()
|
|
208
|
+
logger.info(f"聊天平台 {name} 已停止")
|
|
209
|
+
except Exception as e:
|
|
210
|
+
logger.warning(f"停止聊天平台 {name} 异常: {e}")
|
|
211
|
+
|
|
212
|
+
async def stop_platform(self, key: str) -> bool:
|
|
213
|
+
"""[v1.20.7] 停止并移除单个聊天平台"""
|
|
214
|
+
bot = self._bots.pop(key, None)
|
|
215
|
+
task = self._bot_tasks.pop(key, None)
|
|
216
|
+
if not bot:
|
|
217
|
+
return False
|
|
218
|
+
await self._safe_stop(key, bot)
|
|
219
|
+
if task and not task.done():
|
|
220
|
+
task.cancel()
|
|
221
|
+
return True
|
|
222
|
+
|
|
157
223
|
async def stop_all(self):
|
|
158
224
|
"""停止所有聊天平台"""
|
|
159
|
-
for name, bot in self._bots.items():
|
|
225
|
+
for name, bot in list(self._bots.items()):
|
|
160
226
|
try:
|
|
161
227
|
await bot.stop()
|
|
162
228
|
except Exception as e:
|
|
163
229
|
logger.error(f"停止 {name} 失败: {e}")
|
|
230
|
+
self._bots.clear()
|
|
231
|
+
self._bot_tasks.clear()
|
|
164
232
|
logger.info("所有聊天平台已停止")
|
|
165
233
|
|
|
166
234
|
async def send_to_all(self, text: str):
|
package/core/web_control.py
CHANGED
|
@@ -601,13 +601,16 @@ class WebControlManager:
|
|
|
601
601
|
# 仅改写 HTML 属性中的 URL (不影响内联脚本)
|
|
602
602
|
def rewrite_attr(match):
|
|
603
603
|
attr_name = match.group(1).lower()
|
|
604
|
-
|
|
605
|
-
|
|
604
|
+
eq = match.group(2) # "=" (可能带空格)
|
|
605
|
+
q_open = match.group(3) # 开引号 " 或 '
|
|
606
|
+
url_val = match.group(4) # URL 值
|
|
607
|
+
q_close = match.group(5) # 闭引号 (同 q_open)
|
|
606
608
|
|
|
607
|
-
|
|
609
|
+
# 跳过特殊协议
|
|
610
|
+
if not url_val or url_val.startswith('data:') or url_val.startswith('blob:') or url_val.startswith('#') or url_val.startswith('javascript:') or url_val.startswith('mailto:') or url_val.startswith('tel:'):
|
|
608
611
|
return match.group(0)
|
|
609
612
|
|
|
610
|
-
# 已经是代理 URL
|
|
613
|
+
# 已经是代理 URL — 跳过
|
|
611
614
|
if '/api/web_control/proxy' in url_val:
|
|
612
615
|
return match.group(0)
|
|
613
616
|
|
|
@@ -619,23 +622,22 @@ class WebControlManager:
|
|
|
619
622
|
elif not url_val.startswith('http://') and not url_val.startswith('https://'):
|
|
620
623
|
url_val = urljoin(original_url, url_val)
|
|
621
624
|
|
|
622
|
-
# 非同源资源:
|
|
625
|
+
# 非同源资源: CDN 图片/脚本/样式直接访问, 不走代理
|
|
623
626
|
url_parsed = urlparse(url_val)
|
|
624
627
|
if url_parsed.netloc and url_parsed.netloc != parsed.netloc:
|
|
625
|
-
# 对于 src 属性的资源 (图片/脚本/样式/字体), 直接访问即可
|
|
626
628
|
if attr_name in ('src', 'data-src', 'data-original'):
|
|
627
629
|
return match.group(0)
|
|
628
|
-
|
|
629
|
-
if attr_name == 'href' and any(url_val.endswith(ext) for ext in ('.css', '.woff', '.woff2', '.ttf', '.eot', '.svg')):
|
|
630
|
+
if attr_name == 'href' and any(url_val.lower().endswith(ext) for ext in ('.css', '.woff', '.woff2', '.ttf', '.eot', '.svg', '.png', '.jpg', '.jpeg', '.gif', '.webp', '.ico')):
|
|
630
631
|
return match.group(0)
|
|
631
632
|
# 其他外链 (导航链接) 走代理
|
|
632
633
|
if attr_name == 'href':
|
|
633
|
-
return f'{attr_name}
|
|
634
|
+
return f'{attr_name}{eq}{q_open}{proxy_base}{url_quote(url_val)}{q_close}'
|
|
634
635
|
|
|
635
636
|
# 同源资源走代理
|
|
636
|
-
return f'{attr_name}
|
|
637
|
+
return f'{attr_name}{eq}{q_open}{proxy_base}{url_quote(url_val)}{q_close}'
|
|
637
638
|
|
|
638
639
|
# 匹配常见 URL 属性
|
|
640
|
+
# group1=属性名, group2="=", group3=开引号, group4=URL值, group5=闭引号
|
|
639
641
|
url_pattern = re.compile(
|
|
640
642
|
r'((?:src|href|action|data-src|data-original|poster|formaction|content|cite|background))'
|
|
641
643
|
r'(\s*=\s*)'
|
package/package.json
CHANGED
package/web/api_server.py
CHANGED
|
@@ -280,16 +280,19 @@ class ApiServer:
|
|
|
280
280
|
logger.info("通信管理器已热更新")
|
|
281
281
|
|
|
282
282
|
def _hot_reload_chat_platforms(self):
|
|
283
|
-
"""热更新聊天平台:重新加载所有平台配置到 ChatBotManager
|
|
283
|
+
"""热更新聊天平台:重新加载所有平台配置到 ChatBotManager
|
|
284
|
+
|
|
285
|
+
[v1.20.7] 修复: 调用 setup_platforms 时会自动停止已禁用的平台并启动新启用的平台。
|
|
286
|
+
"""
|
|
284
287
|
mgr = self.core.chat_manager
|
|
285
288
|
if not mgr:
|
|
286
289
|
return
|
|
287
|
-
# 重新设置平台(会重建bot实例)
|
|
288
290
|
platform_configs = self.core.config_mgr.config.chat_platforms
|
|
289
|
-
#
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
291
|
+
# setup_platforms 会自动对比新旧配置,停止被移除/禁用的 bot,启动新启用的 bot
|
|
292
|
+
mgr.setup_platforms(
|
|
293
|
+
platform_configs,
|
|
294
|
+
mgr._message_handler if hasattr(mgr, '_message_handler') else None
|
|
295
|
+
)
|
|
293
296
|
logger.info("聊天平台配置已热更新")
|
|
294
297
|
|
|
295
298
|
def _setup_routes(self):
|