Merge remote-tracking branch 'origin/v3' into v3

# Conflicts:
#	app/api/endpoints/agent.py
#	app/api/endpoints/anthropic.py
#	app/api/endpoints/openai.py
#	app/chain/__init__.py
#	app/chain/message.py
#	app/chain/site.py
#	app/chain/subscribe.py
#	app/chain/transfer.py
#	app/modules/discord/__init__.py
#	app/modules/qqbot/__init__.py
#	app/modules/slack/__init__.py
#	app/modules/telegram/__init__.py
#	app/modules/wechat/__init__.py
#	app/runtime/extensions/module_manager.py
#	app/runtime/extensions/service_registry.py
#	tests/test_agent_interaction.py
#	tests/test_slash_command_interactions.py
#	tests/test_web_agent_stream.py
This commit is contained in:
jxxghp
2026-08-16 19:44:43 +08:00
232 changed files with 21375 additions and 6683 deletions
+7 -101
View File
@@ -1,28 +1,24 @@
import copy
import json
import re
from typing import Dict, Optional, Union, List, Tuple, Any
from typing import Optional, Union, List, Tuple, Any
from app.domain.context import MediaInfo, Context
from app.runtime.events import eventmanager
from app.application.messaging.agent import (
matches_channel_admin,
register_channel_admin_resolver,
resolve_config_principal_ids,
)
from app.runtime.log import logger
from app.modules import _ModuleBase, _MessageBase
from app.modules._base import _MessageChannelModuleBase
from app.modules.telegram.telegram import Telegram
from app.schemas import (
NotificationChannel,
IncomingMessage,
Message,
CommandRegisterEventData,
NotificationConf,
MessageResponse,
)
from app.schemas.types import ModuleType, ChainEventType
from app.foundation.collections import DictUtils
from app.schemas.types import ModuleType
register_channel_admin_resolver(
@@ -33,11 +29,14 @@ register_channel_admin_resolver(
)
class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
class TelegramModule(_MessageChannelModuleBase[Telegram]):
"""
Telegram 通知模块,负责模块生命周期、消息解析和通知发送。
"""
# 管理员配置键,与渠道 resolver 保持一致
_admin_config_key = "TELEGRAM_ADMINS"
def init_module(self) -> None:
"""
初始化模块
@@ -83,53 +82,12 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
except Exception as err:
logger.error(f"停止Telegram模块实例失败:{err}")
def test(self) -> Optional[Tuple[bool, str]]:
"""
测试模块连接性
"""
if not self.get_instances():
return None
for name, client in self.get_instances().items():
state = client.get_state()
if not state:
return False, f"Telegram {name} 未就绪"
return True, ""
def init_setting(self) -> Tuple[str, Union[str, bool]]:
"""
获取模块初始化配置项。
"""
pass
@staticmethod
def _get_admins(config: Optional[dict]) -> List[str]:
"""
解析 Telegram 管理员配置,兼容逗号分隔和首尾空白。
"""
return [
admin.strip()
for admin in str((config or {}).get("TELEGRAM_ADMINS") or "").split(",")
if admin.strip()
]
@classmethod
def _should_reject_admin_command(
cls,
config: Optional[dict],
*user_ids: Optional[Union[str, int]],
) -> bool:
"""
判断 Telegram 命令或命令型按钮回调是否应因非管理员身份被拒绝。
"""
admins = cls._get_admins(config)
if not admins:
return False
return not matches_channel_admin(
NotificationChannel.Telegram,
config,
*user_ids,
)
def message_parser(
self, source: str, body: Any, form: Any, args: Any
) -> Optional[IncomingMessage]:
@@ -795,58 +753,6 @@ class TelegramModule(_ModuleBase, _MessageBase[Telegram]):
)
return None
def register_commands(self, commands: Dict[str, dict]):
"""
注册命令,实现这个函数接收系统可用的命令菜单
:param commands: 命令字典
"""
for client_config in self.get_configs().values():
client = self.get_instance(client_config.name)
if not client:
continue
# 触发事件,允许调整命令数据,这里需要进行深复制,避免实例共享
scoped_commands = copy.deepcopy(commands)
event = eventmanager.send_event(
ChainEventType.CommandRegister,
CommandRegisterEventData(
commands=scoped_commands,
origin="Telegram",
service=client_config.name,
),
)
# 如果事件返回有效的 event_data,使用事件中调整后的命令
if event and event.event_data:
event_data: CommandRegisterEventData = event.event_data
# 如果事件被取消,跳过命令注册,并清理菜单
if event_data.cancel:
client.delete_commands()
logger.debug(
f"Command registration for {client_config.name} canceled by event: {event_data.source}"
)
continue
scoped_commands = event_data.commands or {}
if not scoped_commands:
logger.debug("Filtered commands are empty, skipping registration.")
client.delete_commands()
# scoped_commands 必须是 commands 的子集
filtered_scoped_commands = DictUtils.filter_keys_to_subset(
scoped_commands, commands
)
# 如果 filtered_scoped_commands 为空,则跳过注册
if not filtered_scoped_commands:
logger.debug("Filtered commands are empty, skipping registration.")
client.delete_commands()
continue
# 对比调整后的命令与当前命令
if filtered_scoped_commands != commands:
logger.debug(
f"Command set has changed, Updating new commands: {filtered_scoped_commands}"
)
client.register_commands(filtered_scoped_commands)
def download_telegram_file_to_base64(self, file_id: str, source: str) -> Optional[str]:
"""
下载Telegram文件并转为base64
+22
View File
@@ -0,0 +1,22 @@
schema_version = 1
id = "TelegramModule"
kind = "host_module"
entrypoint = "app.modules.telegram:TelegramModule"
depends_on = []
[metadata]
name = "Telegram"
type = "notification"
subtype = "Telegram"
priority = 0
[activation]
policy = "when_configured"
watch = ["Notifications"]
[activation.selector]
kind = "system_config_item"
key = "Notifications"
match_field = "type"
match_value = "telegram"
enabled_field = "enabled"