Compare commits

..

7 Commits

Author SHA1 Message Date
advent259141 d2b9997620 chore: bump version to 4.14.2 2026-02-04 17:42:41 +08:00
Gao Jinzhe 36432c4361 fix: 修复插件热重载时平台适配器未清理导致注册冲突的问题 (#4859) 2026-02-04 15:06:03 +08:00
圣达生物多 36f0d1f0f9 feat: add debug hint to console page and localization files (#4852) 2026-02-04 15:02:15 +08:00
Anima-IGCenter f65b268bb2 chore: create robots.txt (#4847) 2026-02-04 15:00:08 +08:00
Raven95676 fe06dfcca3 fix: update ruff version to 0.15.0 and add ASYNC240 to ignore list 2026-02-04 11:45:59 +08:00
Soulter bc9043bc3f fix: update ruff exclude list to include tests directory 2026-02-04 10:08:48 +08:00
Soulter 430694aae9 chore: update readme 2026-02-04 10:05:35 +08:00
17 changed files with 106 additions and 95 deletions
+2 -4
View File
@@ -67,8 +67,6 @@ AstrBot 是一个开源的一站式 Agentic 个人和群聊助手,可在 QQ、
</tr>
</table>
陪伴与能力**从来不应该是**对立面。我们希望创造的是一个既能理解情绪、给予陪伴,也能可靠完成工作的机器人——致敬[ATRI](https://zh.wikipedia.org/zh-cn/ATRI_-My_Dear_Moments-)。
## 快速开始
#### Docker 部署(推荐 🥳)
@@ -268,6 +266,6 @@ pre-commit install
_私は、高性能ですから!_
<img src="https://files.astrbot.app/watashiwa-koseino-desukara.gif" width="100"/>
</div
陪伴与能力从来不应该是对立面。我们希望创造的是一个既能理解情绪、给予陪伴,也能可靠完成工作的机器人。
+1 -1
View File
@@ -1 +1 @@
__version__ = "4.14.1"
__version__ = "4.14.2"
-35
View File
@@ -830,38 +830,6 @@ def _get_compress_provider(
return provider
def _apply_global_context_info(event: AstrMessageEvent, req: ProviderRequest) -> None:
"""Add platform and session information to user prompt when in global unified context mode."""
from astrbot.core.config.default import (
GLOBAL_UNIFIED_CONTEXT_UMO,
ORIGINAL_UMO_KEY,
)
if event.unified_msg_origin != GLOBAL_UNIFIED_CONTEXT_UMO:
return
# Get original UMO from extras
original_umo = event.get_extra(ORIGINAL_UMO_KEY)
if not original_umo:
return
# Parse the original UMO to extract platform, message type, and session info
try:
parts = original_umo.split(":", 2)
if len(parts) != 3:
logger.warning(
f"Original UMO format is invalid (expected 3 parts): {original_umo}"
)
return
platform_id, message_type, session_id = parts
context_info = f"[Context: Platform={platform_id}, Type={message_type}, Session={session_id}]"
# Prepend context info to the user prompt
req.prompt = f"{context_info} {req.prompt or ''}"
except Exception as e:
logger.warning(f"Failed to parse original UMO for global context: {e}")
async def build_main_agent(
*,
event: AstrMessageEvent,
@@ -920,9 +888,6 @@ async def build_main_agent(
if isinstance(req.contexts, str):
req.contexts = json.loads(req.contexts)
# Apply global context information if enabled
_apply_global_context_info(event, req)
if config.file_extract_enabled:
try:
await _apply_file_extract(event, req, config)
+1 -10
View File
@@ -212,10 +212,6 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
"required": ["type"],
},
},
"session": {
"type": "string",
"description": "Target session ID in format 'platform:type:session_id'. If not specified, sends to the current session.",
},
},
"required": ["messages"],
}
@@ -257,12 +253,7 @@ class SendMessageToUserTool(FunctionTool[AstrAgentContext]):
async def call(
self, context: ContextWrapper[AstrAgentContext], **kwargs
) -> ToolExecResult:
# In global context mode, default to original UMO if session not specified
from astrbot.core.config.default import ORIGINAL_UMO_KEY
original_umo = context.context.event.get_extra(ORIGINAL_UMO_KEY)
default_session = original_umo or context.context.event.unified_msg_origin
session = kwargs.get("session") or default_session
session = kwargs.get("session") or context.context.event.unified_msg_origin
messages = kwargs.get("messages")
if not isinstance(messages, list) or not messages:
+1 -7
View File
@@ -5,7 +5,7 @@ from typing import Any, TypedDict
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
VERSION = "4.14.1"
VERSION = "4.14.2"
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
WEBHOOK_SUPPORTED_PLATFORMS = [
@@ -17,17 +17,11 @@ WEBHOOK_SUPPORTED_PLATFORMS = [
"lark",
]
# Constant UMO for global unified context mode
GLOBAL_UNIFIED_CONTEXT_UMO = "global::global"
# Key for storing original UMO in event extras when global context mode is enabled
ORIGINAL_UMO_KEY = "original_umo"
# 默认配置
DEFAULT_CONFIG = {
"config_version": 2,
"platform_settings": {
"unique_session": False,
"global_unified_context_mode": False,
"rate_limit": {
"time": 60,
"count": 30,
-10
View File
@@ -169,16 +169,6 @@ class RespondStage(Stage):
f"Prepare to send - {event.get_sender_name()}/{event.get_sender_id()}: {event._outline_chain(result.chain)}",
)
# Restore original UMO before sending if in global context mode
from astrbot.core.config.default import ORIGINAL_UMO_KEY
original_umo = event.get_extra(ORIGINAL_UMO_KEY)
if original_umo:
logger.debug(
f"Restoring original UMO before sending: {event.unified_msg_origin} -> {original_umo}"
)
event.unified_msg_origin = original_umo
if result.result_content_type == ResultContentType.STREAMING_RESULT:
if result.async_stream is None:
logger.warning("async_stream 为空,跳过发送。")
@@ -72,29 +72,11 @@ class WakingCheckStage(Stage):
)
platform_settings = self.ctx.astrbot_config.get("platform_settings", {})
self.unique_session = platform_settings.get("unique_session", False)
self.global_unified_context_mode = platform_settings.get(
"global_unified_context_mode", False
)
async def process(
self,
event: AstrMessageEvent,
) -> None | AsyncGenerator[None, None]:
# apply global unified context mode
if self.global_unified_context_mode:
from astrbot.core.config.default import (
GLOBAL_UNIFIED_CONTEXT_UMO,
ORIGINAL_UMO_KEY,
)
original_umo = event.unified_msg_origin
event.unified_msg_origin = GLOBAL_UNIFIED_CONTEXT_UMO
# Store original UMO for reference in later stages
event.set_extra(ORIGINAL_UMO_KEY, original_umo)
logger.debug(
f"Global unified context mode enabled. Changed UMO from {original_umo} to {GLOBAL_UNIFIED_CONTEXT_UMO}"
)
# apply unique session
if self.unique_session and event.message_obj.type == MessageType.GROUP_MESSAGE:
sid = build_unique_session_id(event)
@@ -21,3 +21,6 @@ class PlatformMetadata:
"""平台是否支持真实流式传输"""
support_proactive_message: bool = True
"""平台是否支持主动消息推送(非用户触发)"""
module_path: str | None = None
"""注册该适配器的模块路径,用于插件热重载时清理"""
+32
View File
@@ -37,6 +37,9 @@ def register_platform_adapter(
if "id" not in default_config_tmpl:
default_config_tmpl["id"] = adapter_name
# Get the module path of the class being decorated
module_path = cls.__module__
pm = PlatformMetadata(
name=adapter_name,
description=desc,
@@ -45,6 +48,7 @@ def register_platform_adapter(
adapter_display_name=adapter_display_name,
logo_path=logo_path,
support_streaming_message=support_streaming_message,
module_path=module_path,
)
platform_registry.append(pm)
platform_cls_map[adapter_name] = cls
@@ -52,3 +56,31 @@ def register_platform_adapter(
return cls
return decorator
def unregister_platform_adapters_by_module(module_path_prefix: str) -> list[str]:
"""根据模块路径前缀注销平台适配器。
在插件热重载时调用,用于清理该插件注册的所有平台适配器。
Args:
module_path_prefix: 模块路径前缀,如 "data.plugins.my_plugin"
Returns:
被注销的平台适配器名称列表
"""
unregistered = []
to_remove = []
for pm in platform_registry:
if pm.module_path and pm.module_path.startswith(module_path_prefix):
to_remove.append(pm)
unregistered.append(pm.name)
for pm in to_remove:
platform_registry.remove(pm)
if pm.name in platform_cls_map:
del platform_cls_map[pm.name]
logger.debug(f"平台适配器 {pm.name} 已注销 (来自模块 {pm.module_path})")
return unregistered
+13
View File
@@ -15,6 +15,7 @@ import yaml
from astrbot.core import logger, pip_installer, sp
from astrbot.core.agent.handoff import FunctionTool, HandoffTool
from astrbot.core.config.astrbot_config import AstrBotConfig
from astrbot.core.platform.register import unregister_platform_adapters_by_module
from astrbot.core.provider.register import llm_tools
from astrbot.core.utils.astrbot_path import (
get_astrbot_config_path,
@@ -842,6 +843,18 @@ class PluginManager:
for func_tool in to_remove:
llm_tools.func_list.remove(func_tool)
# Unregister platform adapters registered by this plugin
# module_path is like "data.plugins.my_plugin.main", extract prefix like "data.plugins.my_plugin"
module_prefix = ".".join(plugin_module_path.split(".")[:-1])
if module_prefix:
unregistered_adapters = unregister_platform_adapters_by_module(
module_prefix
)
for adapter_name in unregistered_adapters:
logger.info(
f"移除了插件 {plugin_name} 的平台适配器 {adapter_name}",
)
if plugin is None:
return
+23
View File
@@ -0,0 +1,23 @@
## What's Changed
### 新增
- 控制台页面新增调试提示和本地化文件 ([#4852](https://github.com/AstrBotDevs/AstrBot/pull/4852))
### 修复
- 修复插件热重载时平台适配器未清理导致注册冲突的问题 ([#4859](https://github.com/AstrBotDevs/AstrBot/pull/4859))
### 其他
- 更新 ruff 版本至 0.15.0
- 新增 robots.txt ([#4847](https://github.com/AstrBotDevs/AstrBot/pull/4847))
## What's Changed (EN)
### New Features
- Add debug hint to console page and localization files ([#4852](https://github.com/AstrBotDevs/AstrBot/pull/4852))
### Bug Fixes
- Fix platform adapter not being cleaned up during plugin hot reload, causing registration conflicts ([#4859](https://github.com/AstrBotDevs/AstrBot/pull/4859))
### Others
- Update ruff version to 0.15.0
- Add robots.txt ([#4847](https://github.com/AstrBotDevs/AstrBot/pull/4847))
+2
View File
@@ -0,0 +1,2 @@
User-agent: *
Disallow: /
@@ -11,5 +11,8 @@
"mirrorLabel": "Force PyPI repository URL (optional)",
"mirrorHint": "Force PyPI repository URL > Config item `PyPI Repository Address`",
"installButton": "Install"
},
"debugHint": {
"text": "Debug logs can be enabled in \"Configuration File → System → Console Log Level\""
}
}
}
@@ -11,5 +11,8 @@
"mirrorLabel": "强制 PyPI 软件仓库链接(可选)",
"mirrorHint": "强制 PyPI 软件仓库链接 > 配置项 `PyPI 软件仓库地址`",
"installButton": "安装"
},
"debugHint": {
"text": "Debug 日志需要在「配置文件 → 系统 → 控制台日志级别」中开启"
}
}
}
+3 -3
View File
@@ -4,12 +4,12 @@ const PurpleThemeDark: ThemeTypes = {
name: 'PurpleThemeDark',
dark: true,
variables: {
'border-color': '#3c96ca',
'border-color': '#1677ff',
'carousel-control-size': 10
},
colors: {
primary: '#3c96ca',
secondary: '#2288b7',
primary: '#1677ff',
secondary: '#722ed1',
info: '#03c9d7',
success: '#52c41a',
accent: '#FFAB91',
+13 -2
View File
@@ -10,7 +10,18 @@ const { tm } = useModuleI18n('features/console');
<div style="height: 100%;">
<div
style="background-color: var(--v-theme-surface); padding: 8px; padding-left: 16px; border-radius: 8px; margin-bottom: 16px; display: flex; flex-direction: row; align-items: center; justify-content: space-between;">
<h4>{{ tm('title') }}</h4>
<div>
<h4>{{ tm('title') }}</h4>
<v-alert
type="info"
variant="tonal"
density="compact"
class="mt-2"
style="max-width: 600px;"
>
{{ tm('debugHint.text') }}
</v-alert>
</div>
<div class="d-flex align-center">
<v-switch
v-model="autoScrollEnabled"
@@ -111,4 +122,4 @@ export default {
.fade-in {
animation: fadeIn 0.2s ease-in-out;
}
</style>
</style>
+4 -3
View File
@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
version = "4.14.1"
version = "4.14.2"
description = "Easy-to-use multi-platform LLM chatbot and development framework"
readme = "README.md"
requires-python = ">=3.10"
@@ -69,14 +69,14 @@ dev = [
"pytest>=8.4.1",
"pytest-asyncio>=1.1.0",
"pytest-cov>=6.2.1",
"ruff>=0.12.8",
"ruff>=0.15.0",
]
[project.scripts]
astrbot = "astrbot.cli.__main__:cli"
[tool.ruff]
exclude = ["astrbot/core/utils/t2i/local_strategy.py", "astrbot/api/all.py"]
exclude = ["astrbot/core/utils/t2i/local_strategy.py", "astrbot/api/all.py", "tests"]
line-length = 88
target-version = "py310"
@@ -97,6 +97,7 @@ ignore = [
"F405",
"E501",
"ASYNC230", # TODO: handle ASYNC230 in AstrBot
"ASYNC240", # TODO: handle ASYNC240 in AstrBot
]
[tool.pyright]