Compare commits

...

10 Commits

Author SHA1 Message Date
Weilong Liao
952d1bfad7 chore: bump version to 4.26.0 (#8994)
* chore: bump version to 4.26.0

* feat: 更新 v4.26.0 更新日志,添加 WebUI 设置迁移提示及新功能说明

* fix: 修复多个 WebUI 和工具相关问题,提升稳定性和用户体验
2026-06-24 23:44:15 +08:00
Soulter
0cfe4163cd fix: update max_context_length and dequeue_context_length defaults 2026-06-24 23:21:50 +08:00
Weilong Liao
d90530af8e Revert "fix: reconnect MCP client on terminated session (#8694)" (#8991)
This reverts commit 2bda4e4d96.
2026-06-24 22:44:45 +08:00
EterUltimate
2bda4e4d96 fix: reconnect MCP client on terminated session (#8694)
* fix: reconnect MCP client on terminated session

* Update astrbot/core/agent/mcp_client.py

Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>

* Update mcp_client.py

---------

Co-authored-by: Weilong Liao <37870767+Soulter@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
2026-06-24 22:43:24 +08:00
ZouZhang
bc117038fb fix: preserve At components when sending messages on qq_official platform (#8983)
- Add At component handling in _parse_to_qqofficial method
- Convert At(qq=openid) to <@openid> plain_text format
- Maintain original message chain order by appending
- Skip At(qq='all') since QQ Official API may not support it

Closes #8982
2026-06-24 22:15:12 +08:00
FuShang114
b7cadfe704 fix: allow plugin page asset token fallback (#8970)
Co-authored-by: beidou-lab-macmini <beidou-lab-macmini@beidou-lab-macminideMac-mini.local>
2026-06-24 22:11:49 +08:00
dependabot[bot]
6a343b7656 chore(deps): bump actions/checkout in the github-actions group (#8963)
Bumps the github-actions group with 1 update: [actions/checkout](https://github.com/actions/checkout).


Updates `actions/checkout` from 6 to 7
- [Release notes](https://github.com/actions/checkout/releases)
- [Changelog](https://github.com/actions/checkout/blob/main/CHANGELOG.md)
- [Commits](https://github.com/actions/checkout/compare/v6...v7)

---
updated-dependencies:
- dependency-name: actions/checkout
  dependency-version: '7'
  dependency-type: direct:production
  update-type: version-update:semver-major
  dependency-group: github-actions
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-06-24 21:47:39 +08:00
Cooper
c89984bf42 Updated Documentation for Password Reset (#8987) 2026-06-24 21:47:20 +08:00
Weilong Liao
421d718041 feat: show public version details on login (#8986)
* feat: show public version details on login

* fix: address login version review feedback
2026-06-24 21:46:29 +08:00
Weilong Liao
967ed01cf7 fix: add data scope to api keys (#8985) 2026-06-24 16:37:46 +08:00
32 changed files with 686 additions and 51 deletions

View File

@@ -11,7 +11,7 @@ jobs:
runs-on: ubuntu-latest # 运行环境
steps:
- name: checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.9
with:

View File

@@ -12,7 +12,7 @@ jobs:
steps:
- name: Checkout code
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6

View File

@@ -56,7 +56,7 @@ jobs:
# your codebase is analyzed, see https://docs.github.com/en/code-security/code-scanning/creating-an-advanced-setup-for-code-scanning/codeql-code-scanning-for-compiled-languages
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL

View File

@@ -17,7 +17,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0

View File

@@ -12,7 +12,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Setup pnpm
uses: pnpm/action-setup@v6.0.9

View File

@@ -20,7 +20,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 1
fetch-tag: true
@@ -125,7 +125,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 1
fetch-tag: true

View File

@@ -28,7 +28,7 @@ jobs:
R2_SECRET_ACCESS_KEY: ${{ secrets.R2_SECRET_ACCESS_KEY }}
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}
@@ -128,7 +128,7 @@ jobs:
- build-dashboard
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}
@@ -208,7 +208,7 @@ jobs:
- publish-release
steps:
- name: Checkout repository
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}

View File

@@ -32,7 +32,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
with:
fetch-depth: 0

View File

@@ -31,7 +31,7 @@ jobs:
exit 1
- name: Check out docs repository
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6

View File

@@ -19,7 +19,7 @@ jobs:
steps:
- name: Checkout
uses: actions/checkout@v6
uses: actions/checkout@v7
- name: Set up Python
uses: actions/setup-python@v6

View File

@@ -1,4 +1,4 @@
import logging
__version__ = "4.26.0-beta.12"
__version__ = "4.26.0"
logger = logging.getLogger("astrbot")

View File

@@ -136,8 +136,8 @@ DEFAULT_CONFIG = {
),
"llm_compress_keep_recent_ratio": 0.15,
"llm_compress_provider_id": "",
"max_context_length": 50,
"dequeue_context_length": 10,
"max_context_length": -1, # 默认不限制
"dequeue_context_length": 1,
"streaming_response": False,
"show_tool_use_status": False,
"show_tool_call_result": False,

View File

@@ -25,7 +25,7 @@ from tenacity import (
from astrbot.api import logger
from astrbot.api.event import AstrMessageEvent, MessageChain
from astrbot.api.message_components import File, Image, Plain, Record, Video
from astrbot.api.message_components import At, File, Image, Plain, Record, Video
from astrbot.api.platform import AstrBotMessage, PlatformMetadata
from astrbot.core.utils.media_utils import MediaResolver, file_uri_to_path, is_file_uri
@@ -747,6 +747,10 @@ class QQOfficialMessageEvent(AstrMessageEvent):
file_source = file_path
elif i.url:
file_source = i.url
elif isinstance(i, At):
qq_id = getattr(i, "qq", "")
if qq_id and qq_id != "all":
plain_text += f"<@{qq_id}>"
else:
logger.debug(f"qq_official 忽略 {i.type}")
return (

View File

@@ -74,6 +74,18 @@ async def get_version(
return await _run(service.get_version())
@router.get("/stats/versions")
async def get_public_versions(
request: Request,
service: StatService = Depends(get_service),
):
return ok(
await service.get_public_versions(
getattr(request.app.state, "dashboard_static_folder", None)
)
)
@router.get("/stats/first-notice")
async def get_first_notice(
locale: str | None = None,
@@ -167,6 +179,18 @@ async def get_dashboard_version(
return await _run(service.get_version())
@legacy_router.get("/versions")
async def get_dashboard_public_versions(
request: Request,
service: StatService = Depends(get_service),
):
return ok(
await service.get_public_versions(
getattr(request.app.state, "dashboard_static_folder", None)
)
)
@legacy_router.get("/start-time")
async def get_dashboard_start_time(
service: StatService = Depends(get_service),

View File

@@ -265,6 +265,7 @@ class AstrBotDashboard:
"/api/auth/logout",
"/api/auth/setup-status",
"/api/auth/setup",
"/api/stat/versions",
}
allowed_endpoint_prefixes = [
"/api/file",
@@ -278,37 +279,74 @@ class AstrBotDashboard:
):
return None
is_plugin_page_path = PluginPageAuth.is_protected_path(path)
token = self._extract_dashboard_jwt(current_request)
if not token and is_plugin_page_path:
token = PluginPageAuth.extract_asset_token(current_request.query_params)
if not token:
dashboard_token = self._extract_dashboard_jwt(current_request)
asset_token = (
PluginPageAuth.extract_asset_token(current_request.query_params)
if is_plugin_page_path
else None
)
token_candidates = []
if dashboard_token:
token_candidates.append(dashboard_token)
if asset_token and asset_token != dashboard_token:
token_candidates.append(asset_token)
if not token_candidates:
r = JSONResponse(error("未授权"))
r.status_code = 401
return r
token_errors: list[str] = []
for token in token_candidates:
payload, token_error = self._validate_dashboard_token(token, path)
if payload is not None:
current_request.state.dashboard_g.username = cast(
str, payload["username"]
)
return None
token_errors.append(token_error)
error_message = (
"Token 过期"
if token_errors and all(item == "Token 过期" for item in token_errors)
else "Token 无效"
)
r = JSONResponse(error(error_message))
r.status_code = 401
return r
def _validate_dashboard_token(
self,
token: str,
path: str,
) -> tuple[dict[str, Any] | None, str]:
"""Validate a dashboard JWT or scoped plugin page asset token.
Args:
token: JWT value from the Authorization header, cookie, or query string.
path: Current request path used for plugin page asset token scope checks.
Returns:
A tuple of the decoded payload and an error message. The payload is
present only when the token is valid for the current request path.
"""
try:
payload = jwt.decode(token, self._jwt_secret, algorithms=["HS256"])
if PluginPageAuth.is_asset_token(
payload
) and not PluginPageAuth.is_scope_valid(
payload,
path,
):
r = JSONResponse(error("Token 无效"))
r.status_code = 401
return r
username = payload.get("username")
if not isinstance(username, str) or not username.strip():
raise jwt.InvalidTokenError("missing username in token payload")
current_request.state.dashboard_g.username = username
except jwt.ExpiredSignatureError:
r = JSONResponse(error("Token 过期"))
r.status_code = 401
return r
return None, "Token 过期"
except jwt.InvalidTokenError:
r = JSONResponse(error("Token 无效"))
r.status_code = 401
return r
return None, "Token 无效"
if PluginPageAuth.is_asset_token(payload) and not PluginPageAuth.is_scope_valid(
payload,
path,
):
return None, "Token 无效"
username = payload.get("username")
if not isinstance(username, str) or not username.strip():
return None, "Token 无效"
return payload, ""
async def _apply_auth_rate_limit(
self,

View File

@@ -54,6 +54,7 @@ ALL_OPEN_API_SCOPES = (
"im",
"config",
"chat",
"data",
"file",
"plugin",
"mcp",

View File

@@ -1,5 +1,6 @@
from __future__ import annotations
import ast
import asyncio
import re
import threading
@@ -25,7 +26,7 @@ from astrbot.core.utils.auth_password import (
is_default_dashboard_password,
is_md5_dashboard_password,
)
from astrbot.core.utils.io import get_dashboard_version
from astrbot.core.utils.io import get_dashboard_dist_version, get_dashboard_version
from astrbot.core.utils.storage_cleaner import StorageCleaner
from astrbot.core.utils.version_comparator import VersionComparator
from astrbot.dashboard.password_state import (
@@ -104,6 +105,68 @@ class StatService:
"password_upgrade_required": not storage_upgraded,
}
async def get_public_versions(
self,
dashboard_static_folder: str | None = None,
) -> dict:
"""Return version details that are safe to expose before login.
Args:
dashboard_static_folder: Static WebUI dist directory currently served by
the dashboard, when available.
Returns:
Public WebUI and AstrBot version information.
"""
def read_code_version() -> str | None:
"""Read the AstrBot code version from the package file.
Returns:
The version string from disk, or None when it is unavailable.
"""
version_file = Path(get_astrbot_path()) / "astrbot" / "__init__.py"
module = ast.parse(version_file.read_text(encoding="utf-8"))
for statement in module.body:
if not isinstance(statement, ast.Assign):
continue
if not any(
isinstance(target, ast.Name) and target.id == "__version__"
for target in statement.targets
):
continue
if isinstance(statement.value, ast.Constant) and isinstance(
statement.value.value,
str,
):
return statement.value.value.strip()
return None
return None
dashboard_version = None
try:
if dashboard_static_folder:
dashboard_version = get_dashboard_dist_version(
Path(dashboard_static_folder)
)
if dashboard_version is None:
dashboard_version = await get_dashboard_version()
except Exception as exc:
logger.warning("Failed to read public WebUI version: %s", exc)
code_version = None
try:
code_version = await asyncio.to_thread(read_code_version)
except Exception as exc:
logger.warning("Failed to read AstrBot code version from disk: %s", exc)
return {
"webui_version": dashboard_version,
"astrbot_version": VERSION,
"astrbot_code_version": code_version,
}
def get_start_time(self) -> dict:
return {"start_time": self.core_lifecycle.start_time}

154
changelogs/v4.26.0.md Normal file
View File

@@ -0,0 +1,154 @@
> Note:
> 1. WebUI “Config -> System Config” has moved to “Settings” at the bottom of the WebUI sidebar.
> 2. It's recommended to update to v4.25.6 before updating to this version.
>
> 提醒:
> 1. WebUI 的“配置 -> 系统配置”已迁移至 WebUI 侧边栏下方的“设置”。
> 2. 建议先升级到 v4.25.6 再升级到此版本。
>
- [更新日志(简体中文)](#chinese)
- [Changelog(English)](#english)
<a id="chinese"></a>
## What's Changed
### ✨ 新功能
- 后端架构从 Quart 迁移至 FastAPI。新增多个 AstrBot OpenAPI。([#8688](https://github.com/AstrBotDevs/AstrBot/pull/8688))
- 统一全平台消息媒体文件的处理逻辑,提升图片、音频、文件和引用消息媒体的解析一致性,对腾讯系 Silk 格式的语音文件不再使用 pilk 库。([#8764](https://github.com/AstrBotDevs/AstrBot/pull/8764))
- WebUI 新增函数工具的逐工具权限管理,支持在工具面板中查看和切换工具权限。([#8693](https://github.com/AstrBotDevs/AstrBot/pull/8693))
- WebUI 新增浅色、深色、跟随系统三种主题模式,并集中处理系统主题同步。([#8648](https://github.com/AstrBotDevs/AstrBot/pull/8648))
- 重组 WebUI 系统配置页面,将系统配置入口迁移到侧边栏下方的设置区域,并优化相关设置项、自动保存和重启提示体验。([#8777](https://github.com/AstrBotDevs/AstrBot/pull/8777))
- 新增 QQ 官方机器人 WebSocket 适配器扫码绑定流程,可通过 WebUI 一键扫码获取并回填 AppID 与 Secret同时将 WebSocket 模板标记为推荐。([#8821](https://github.com/AstrBotDevs/AstrBot/pull/8821))
- 增强 QQ 官方机器人群聊能力,支持群消息创建类型,并允许 Webhook 适配器在无缓存 `msg_id` 时主动发送群消息。([#8838](https://github.com/AstrBotDevs/AstrBot/pull/8838), [#8841](https://github.com/AstrBotDevs/AstrBot/pull/8841))
- 为 OpenAI、Gemini、Anthropic 等模型请求加入可配置的重试机制,并新增请求最大重试次数配置,提升临时网络错误与 5xx 服务端错误下的稳定性。([#8893](https://github.com/AstrBotDevs/AstrBot/pull/8893))
- 现在更新项目时,下载 AstrBot Core 会走 AstrBot 官方托管地址,提高网络稳定性。([#8888](https://github.com/AstrBotDevs/AstrBot/pull/8888))
- 支持在请求中加载 workspace skills并加固 workspace skill 发现流程。([#8884](https://github.com/AstrBotDevs/AstrBot/pull/8884))
- 新增 Exa Web Search 提供商。([#8973](https://github.com/AstrBotDevs/AstrBot/pull/8973)
- 新增 ElevenLabs TTS API Provider。([commit](https://github.com/AstrBotDevs/AstrBot/commit/0b2234936))
- 新增启动时重置 WebUI 密码的命令行开关,便于无法登录时恢复访问。([commit](https://github.com/AstrBotDevs/AstrBot/commit/4f5075e60))
- 新增预发布版本可见性开关。([commit](https://github.com/AstrBotDevs/AstrBot/commit/f9d408221))
- 登录页新增公开版本详情展示。([#8986](https://github.com/AstrBotDevs/AstrBot/pull/8986)
- 备份功能现在会包含 skills 目录。([#8700](https://github.com/AstrBotDevs/AstrBot/pull/8700))
### 优化
- 加强未来任务所有者校验,避免越权访问定时任务。([#8881](https://github.com/AstrBotDevs/AstrBot/pull/8881))
- 优化知识库上传文件名路径穿越风险。([#8971](https://github.com/AstrBotDevs/AstrBot/pull/8971)
- 优化插件上传文件名路径穿越风险。([#8968](https://github.com/AstrBotDevs/AstrBot/pull/8968)
- 在受限本地文件系统工具中拒绝 hardlink 文件,避免通过工作区 hardlink 别名读写允许目录外的文件。
- 加固沙箱文件传输与 CUA 健康检查流程,降低异常环境下的文件操作风险。([#8840](https://github.com/AstrBotDevs/AstrBot/pull/8840))
- 消息组件日志输出现在会截断过长的 base64 字段,避免日志中出现大体积内联媒体内容。([#8591](https://github.com/AstrBotDevs/AstrBot/pull/8591))
- 群聊上下文现在会展示被引用消息的内容。([commit](https://github.com/AstrBotDevs/AstrBot/commit/32cfcbf52))
- 对话上下文新增当前星期信息。([#8669](https://github.com/AstrBotDevs/AstrBot/pull/8669))
- 使用原子写入方式保存配置文件,降低写入中断导致配置损坏的概率。([#8793](https://github.com/AstrBotDevs/AstrBot/pull/8793))
- 新增 GitHub 代理 `gh.dpik.top`,并移除失效代理 `gh.llkk.cc`。([#8772](https://github.com/AstrBotDevs/AstrBot/pull/8772), [#8761](https://github.com/AstrBotDevs/AstrBot/pull/8761))
### 修复
- 修复 aiocqhttp 平台适配器与消息事件处理器在多处 API 调用中缺少 `self_id` 路由参数的问题。([#8779](https://github.com/AstrBotDevs/AstrBot/pull/8779))
- 修复生成平台 ID 时可能包含空白字符的问题。([#8768](https://github.com/AstrBotDevs/AstrBot/pull/8768))
- 修复 Gemini Provider 工具定义没有正确传回模型,导致重复工具调用的问题。([#8833](https://github.com/AstrBotDevs/AstrBot/pull/8833))
- 修复提供商源修改 ID 后保存被静默还原的问题。([#8915](https://github.com/AstrBotDevs/AstrBot/pull/8915)
- 修复插件 LLM Tools 开关的归属校验问题,避免误操作其他插件的工具配置。([commit](https://github.com/AstrBotDevs/AstrBot/commit/fadada3d6))
- 完善插件命名模式校验和边界场景处理。([commit](https://github.com/AstrBotDevs/AstrBot/commit/992aea986))
- 修复插件重装后仓库来源丢失的问题。([commit](https://github.com/AstrBotDevs/AstrBot/commit/a3c25ec2c))
- 修复子目录工具的 `handler_module_path` 不一致问题。([#8578](https://github.com/AstrBotDevs/AstrBot/pull/8578))
- 修复 run-based tools 的保护逻辑,避免受保护工具在注册流程中被错误丢失。([#8790](https://github.com/AstrBotDevs/AstrBot/pull/8790))
- 修复 persona 工具列表场景下系统工具被移除的问题。([#8908](https://github.com/AstrBotDevs/AstrBot/pull/8908)
- 修复人格设定中将工具和 Skills 从指定列表切回“默认使用全部”后不生效的问题。([#8835](https://github.com/AstrBotDevs/AstrBot/pull/8835))
- 修复新版 MCP 中 Streamable HTTP client 重命名导致的兼容问题,并保持 `mcp` 依赖小于 2。
- 修复本地 Python 工具没有在当前 session workspace 中运行的问题。([#8792](https://github.com/AstrBotDevs/AstrBot/pull/8792))
- 修复静态资源缺失时仍显示 WebUI ready banner 的问题。([#8804](https://github.com/AstrBotDevs/AstrBot/pull/8804))
- 修复 Dashboard 创建文件夹时按 Enter 无法提交的问题。([#8597](https://github.com/AstrBotDevs/AstrBot/pull/8597))
- 修复聊天输入框在非末尾位置使用输入法组合输入时可能丢失字符的问题。([#8811](https://github.com/AstrBotDevs/AstrBot/pull/8811))
- 修复 changelog 弹窗中的锚点链接处理。([#8750](https://github.com/AstrBotDevs/AstrBot/pull/8750))
- 修复 onboarding 平台配置与备份上传相关问题。([#8834](https://github.com/AstrBotDevs/AstrBot/pull/8834))
- 将知识库上下文作为临时 user 内容注入,修复模型请求中知识库上下文角色不准确的问题。([#8904](https://github.com/AstrBotDevs/AstrBot/pull/8904))
- 修复 cron 星期调度规范化问题。([#8984](https://github.com/AstrBotDevs/AstrBot/pull/8984)
- 修复 QQ 官方平台发送消息时 At 组件被丢失的问题。([#8983](https://github.com/AstrBotDevs/AstrBot/pull/8983)
- 修复引用消息中的 image caption 可能重复显示的问题。([#8718](https://github.com/AstrBotDevs/AstrBot/pull/8718))
- 修复 Embedding API version 后缀被错误截断的问题。([#8736](https://github.com/AstrBotDevs/AstrBot/pull/8736))
- 延迟导入 FAISS C 库,避免部分环境启动时进程卡住。([#8696](https://github.com/AstrBotDevs/AstrBot/pull/8696))
- 关闭时主动释放数据库 engine减少会话和测试环境中的资源残留。([#8650](https://github.com/AstrBotDevs/AstrBot/pull/8650))
- 修复 CLI 版本来源不正确的问题。([#8692](https://github.com/AstrBotDevs/AstrBot/pull/8692))
- 修复执行 `astrbot` 命令时不必要地创建 data 目录的问题。([#8932](https://github.com/AstrBotDevs/AstrBot/pull/8932)
- 修复 sdist 构建产物路径,确保 Dashboard artifact 可被包含。([#8933](https://github.com/AstrBotDevs/AstrBot/pull/8933)
- 修复插件页资源 token fallback。[#8970](https://github.com/AstrBotDevs/AstrBot/pull/8970)
- 更新 `max_context_length``dequeue_context_length` 默认值。
- 稳定 FastAPI Dashboard 路由注册测试,兼容 included router 节点。([commit](https://github.com/AstrBotDevs/AstrBot/commit/ad1b64d12))
- 稳定 Dashboard 路由相关测试,兼容最新 FastAPI 行为。([commit](https://github.com/AstrBotDevs/AstrBot/commit/a2b6aad84))
<a id="english"></a>
## What's Changed (EN)
### ✨ New Features
- Migrated the backend architecture from Quart to FastAPI and added multiple OpenAPI definitions. ([#8688](https://github.com/AstrBotDevs/AstrBot/pull/8688))
- Unified media file handling across platforms, improving consistency for images, audio, files, and quoted-message media. Tencent Silk voice files no longer use the pilk library. ([#8764](https://github.com/AstrBotDevs/AstrBot/pull/8764))
- Added per-tool permission management for function tools in WebUI, with support for viewing and toggling tool permissions from the tools panel. ([#8693](https://github.com/AstrBotDevs/AstrBot/pull/8693))
- Added light, dark, and system theme modes to WebUI, with centralized system theme synchronization. ([#8648](https://github.com/AstrBotDevs/AstrBot/pull/8648))
- Reorganized the WebUI system configuration page. The system configuration entry has moved to the Settings area at the bottom of the sidebar, with improved settings, autosave, and restart notices. ([#8777](https://github.com/AstrBotDevs/AstrBot/pull/8777))
- Added a QR binding flow for the QQ Official Bot WebSocket adapter. WebUI can now fetch and autofill AppID and Secret through one-click QR setup, and the WebSocket template is marked as recommended. ([#8821](https://github.com/AstrBotDevs/AstrBot/pull/8821))
- Enhanced QQ Official Bot group chat support by adding group message creation types and allowing the Webhook adapter to proactively send group messages without a cached `msg_id`. ([#8838](https://github.com/AstrBotDevs/AstrBot/pull/8838), [#8841](https://github.com/AstrBotDevs/AstrBot/pull/8841))
- Added configurable retry handling for OpenAI, Gemini, Anthropic, and related model requests, including a maximum request retry setting for better stability during temporary network errors and 5xx server errors. ([#8893](https://github.com/AstrBotDevs/AstrBot/pull/8893))
- AstrBot Core downloads now use AstrBot's officially hosted source during project updates, improving network stability. ([#8888](https://github.com/AstrBotDevs/AstrBot/pull/8888))
- Added support for loading workspace skills in requests and hardened workspace skill discovery. ([#8884](https://github.com/AstrBotDevs/AstrBot/pull/8884))
- Added Exa as a web search provider. ([#8973](https://github.com/AstrBotDevs/AstrBot/pull/8973))
- Added the ElevenLabs TTS API provider. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/0b2234936))
- Added a startup flag to reset the WebUI password, making it easier to recover access when login is unavailable. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/4f5075e60))
- Added a prerelease visibility toggle. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/f9d408221))
- Added public version details to the login page. ([#8986](https://github.com/AstrBotDevs/AstrBot/pull/8986))
- Backups now include the skills directory. ([#8700](https://github.com/AstrBotDevs/AstrBot/pull/8700))
### Improvements
- Strengthened Future Task owner checks to prevent unauthorized scheduled-task access. ([#8881](https://github.com/AstrBotDevs/AstrBot/pull/8881))
- Hardened knowledge base upload filename handling against path traversal risks. ([#8971](https://github.com/AstrBotDevs/AstrBot/pull/8971))
- Hardened plugin upload filename handling against path traversal risks. ([#8968](https://github.com/AstrBotDevs/AstrBot/pull/8968))
- Rejected hardlinked files in restricted local filesystem tools to prevent workspace hardlink aliases from reading or writing files outside allowed directories.
- Hardened sandbox file transfers and CUA health checks to reduce file-operation risks in abnormal environments. ([#8840](https://github.com/AstrBotDevs/AstrBot/pull/8840))
- Message component logs now truncate long base64 fields to avoid large inline media payloads in logs. ([#8591](https://github.com/AstrBotDevs/AstrBot/pull/8591))
- Group chat context now includes quoted message content. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/32cfcbf52))
- Added current weekday information to conversation context. ([#8669](https://github.com/AstrBotDevs/AstrBot/pull/8669))
- Configuration files are now saved atomically, reducing the chance of corruption during interrupted writes. ([#8793](https://github.com/AstrBotDevs/AstrBot/pull/8793))
- Added GitHub proxy `gh.dpik.top` and removed the invalid `gh.llkk.cc` proxy. ([#8772](https://github.com/AstrBotDevs/AstrBot/pull/8772), [#8761](https://github.com/AstrBotDevs/AstrBot/pull/8761))
### Bug Fixes
- Fixed missing `self_id` routing parameters across multiple aiocqhttp platform adapter and message event API calls. ([#8779](https://github.com/AstrBotDevs/AstrBot/pull/8779))
- Fixed generated platform IDs potentially containing whitespace characters. ([#8768](https://github.com/AstrBotDevs/AstrBot/pull/8768))
- Fixed Gemini Provider tool definitions not being passed back to the model correctly, which could cause repeated tool calls. ([#8833](https://github.com/AstrBotDevs/AstrBot/pull/8833))
- Fixed provider source ID edits being silently restored after saving. ([#8915](https://github.com/AstrBotDevs/AstrBot/pull/8915))
- Fixed ownership checks when toggling plugin LLM tools to prevent accidental changes to other plugins' tool settings. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/fadada3d6))
- Improved plugin naming pattern validation and edge-case handling. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/992aea986))
- Fixed repository source information being lost after reinstalling plugins. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/a3c25ec2c))
- Fixed inconsistent `handler_module_path` values for tools inside subdirectories. ([#8578](https://github.com/AstrBotDevs/AstrBot/pull/8578))
- Fixed run-based tool protection so protected tools are not incorrectly dropped during registration. ([#8790](https://github.com/AstrBotDevs/AstrBot/pull/8790))
- Fixed system tools being removed when persona tool lists are configured. ([#8908](https://github.com/AstrBotDevs/AstrBot/pull/8908))
- Fixed persona tool and Skill settings not taking effect after switching from selected items back to "use all by default". ([#8835](https://github.com/AstrBotDevs/AstrBot/pull/8835))
- Fixed compatibility with the renamed Streamable HTTP client in newer MCP versions while keeping the `mcp` dependency below 2.
- Fixed local Python tools not running inside the current session workspace. ([#8792](https://github.com/AstrBotDevs/AstrBot/pull/8792))
- Fixed the WebUI ready banner being shown even when static assets are missing. ([#8804](https://github.com/AstrBotDevs/AstrBot/pull/8804))
- Fixed Dashboard folder creation not being submitted when pressing Enter. ([#8597](https://github.com/AstrBotDevs/AstrBot/pull/8597))
- Fixed possible IME composition character loss when typing at a non-terminal cursor position in the chat input. ([#8811](https://github.com/AstrBotDevs/AstrBot/pull/8811))
- Fixed changelog anchor link handling in the Dashboard dialog. ([#8750](https://github.com/AstrBotDevs/AstrBot/pull/8750))
- Fixed onboarding platform configuration and backup upload issues. ([#8834](https://github.com/AstrBotDevs/AstrBot/pull/8834))
- Injected knowledge base context as temporary user content, fixing the role used for knowledge context in model requests. ([#8904](https://github.com/AstrBotDevs/AstrBot/pull/8904))
- Fixed cron weekday scheduling normalization. ([#8984](https://github.com/AstrBotDevs/AstrBot/pull/8984))
- Fixed At components being dropped when sending messages on the QQ Official platform. ([#8983](https://github.com/AstrBotDevs/AstrBot/pull/8983))
- Fixed duplicate captions for quoted images. ([#8718](https://github.com/AstrBotDevs/AstrBot/pull/8718))
- Fixed Embedding API version suffixes being truncated incorrectly. ([#8736](https://github.com/AstrBotDevs/AstrBot/pull/8736))
- Deferred FAISS C library imports to avoid startup hangs in some environments. ([#8696](https://github.com/AstrBotDevs/AstrBot/pull/8696))
- Disposed the database engine on shutdown to reduce resource leftovers in sessions and tests. ([#8650](https://github.com/AstrBotDevs/AstrBot/pull/8650))
- Fixed the CLI version source. ([#8692](https://github.com/AstrBotDevs/AstrBot/pull/8692))
- Fixed unnecessary data directory creation when executing the `astrbot` command. ([#8932](https://github.com/AstrBotDevs/AstrBot/pull/8932))
- Fixed the sdist build artifact path so the Dashboard artifact can be included. ([#8933](https://github.com/AstrBotDevs/AstrBot/pull/8933))
- Fixed plugin page asset token fallback. ([#8970](https://github.com/AstrBotDevs/AstrBot/pull/8970))
- Updated the `max_context_length` and `dequeue_context_length` defaults.
- Stabilized FastAPI Dashboard route registration tests for included router nodes. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/ad1b64d12))
- Stabilized Dashboard route tests for the latest FastAPI behavior. ([commit](https://github.com/AstrBotDevs/AstrBot/commit/a2b6aad84))

File diff suppressed because one or more lines are too long

View File

@@ -86,6 +86,9 @@ export type ChatProjectRequest = {
};
export type ChatRequest = {
/**
* Caller-declared WebChat sender/session owner. This value is used as the message sender identity and may participate in sender-ID-based command permission checks. Treat chat-scoped API keys as trusted backend credentials and map or validate usernames before accepting end-user input.
*/
username?: string;
session_id?: string;
/**
@@ -191,7 +194,7 @@ export type ConversationRef = {
export type CreateApiKeyRequest = {
name: string;
scopes?: Array<('bot' | 'provider' | 'persona' | 'im' | 'config' | 'chat' | 'file' | 'plugin' | 'mcp' | 'skill')>;
scopes?: Array<('bot' | 'provider' | 'persona' | 'im' | 'config' | 'chat' | 'data' | 'file' | 'plugin' | 'mcp' | 'skill')>;
expires_at?: string;
expires_in_days?: number;
};
@@ -3091,6 +3094,10 @@ export type GetVersionResponse = (SuccessEnvelope);
export type GetVersionError = unknown;
export type GetPublicVersionsResponse = (SuccessEnvelope);
export type GetPublicVersionsError = unknown;
export type GetFirstNoticeData = {
query?: {
locale?: string;

View File

@@ -105,6 +105,13 @@ export interface VersionData {
[key: string]: unknown;
}
export interface PublicVersionData {
webui_version?: string | null;
astrbot_version?: string | null;
astrbot_code_version?: string | null;
[key: string]: unknown;
}
type StartTimeData = {
start_time?: number | string | null;
};
@@ -1723,6 +1730,15 @@ export const statsApi = {
},
};
export const publicApi = {
versions() {
return withLegacyFallback<PublicVersionData>(
openApiV1.getPublicVersions(),
() => httpClient.get<ApiEnvelope<PublicVersionData>>('/api/stat/versions'),
);
},
};
export const changelogApi = {
listVersions() {
return typed<{ versions?: string[] }>(openApiV1.listChangelogVersions());

View File

@@ -56,6 +56,19 @@
"totpTitle": "Two-Factor Verification",
"subtitle": "Welcome"
},
"versions": {
"webui": "WebUI",
"astrbotRuntime": "AstrBot",
"astrbotCode": "AstrBot(Code)",
"mismatchTooltip": "Version mismatch",
"dialogTitle": "Version status needs attention",
"webuiMismatchTitle": "WebUI does not match the current AstrBot version",
"webuiMismatchMessage": "This WebUI may not have been built for the running AstrBot version, so some pages or APIs may behave unexpectedly. Restart AstrBot manually first. If the mismatch remains, delete data/dist in the runtime directory and restart again so AstrBot can download the matching WebUI.",
"runtimeMismatchTitle": "Running AstrBot does not match the code version",
"runtimeMismatchMessage": "The code has been updated, but the current process is still running an older version. Restart AstrBot manually to load the new code.",
"faq": "View FAQ",
"close": "Close"
},
"theme": {
"light": "Light Mode",
"dark": "Dark Mode",

View File

@@ -56,6 +56,19 @@
"totpTitle": "Двухфакторная аутентификация",
"subtitle": "Добро пожаловать"
},
"versions": {
"webui": "WebUI",
"astrbotRuntime": "AstrBot",
"astrbotCode": "AstrBot(Code)",
"mismatchTooltip": "Несовпадение версий",
"dialogTitle": "Проверьте состояние версий",
"webuiMismatchTitle": "WebUI не соответствует текущей версии AstrBot",
"webuiMismatchMessage": "Этот WebUI мог быть собран не для запущенной версии AstrBot, поэтому некоторые страницы или API могут работать нестабильно. Сначала перезапустите AstrBot вручную. Если несовпадение останется, удалите data/dist в рабочем каталоге и перезапустите снова, чтобы AstrBot скачал подходящий WebUI.",
"runtimeMismatchTitle": "Запущенный AstrBot не соответствует версии кода",
"runtimeMismatchMessage": "Код уже обновлен, но текущий процесс все еще работает на старой версии. Перезапустите AstrBot вручную, чтобы загрузить новый код.",
"faq": "Открыть FAQ",
"close": "Закрыть"
},
"theme": {
"light": "Светлая тема",
"dark": "Темная тема",

View File

@@ -56,6 +56,19 @@
"totpTitle": "两步验证",
"subtitle": "欢迎使用"
},
"versions": {
"webui": "WebUI",
"astrbotRuntime": "AstrBot",
"astrbotCode": "AstrBot(Code)",
"mismatchTooltip": "版本不一致",
"dialogTitle": "版本状态需要确认",
"webuiMismatchTitle": "WebUI 与当前 AstrBot 版本不匹配",
"webuiMismatchMessage": "当前 WebUI 可能不是为此 AstrBot 版本构建,部分页面或接口可能出现异常。请先手动重启 AstrBot若仍不一致删除运行目录下的 data/dist 后再次重启AstrBot 会重新下载匹配的 WebUI。",
"runtimeMismatchTitle": "运行中的 AstrBot 与代码版本不一致",
"runtimeMismatchMessage": "代码已经更新,但当前进程仍在运行旧版本。手动重启 AstrBot 后,将加载新的代码版本。",
"faq": "查看 FAQ",
"close": "关闭"
},
"theme": {
"light": "浅色模式",
"dark": "深色模式",

View File

@@ -513,6 +513,7 @@ const availableScopes = [
{ value: 'im', label: 'im' },
{ value: 'config', label: 'config' },
{ value: 'chat', label: 'chat' },
{ value: 'data', label: 'data' },
{ value: 'file', label: 'file' },
{ value: 'plugin', label: 'plugin' },
{ value: 'mcp', label: 'mcp' },

View File

@@ -7,7 +7,7 @@ import { useRouter } from 'vue-router';
import { useCustomizerStore } from "@/stores/customizer";
import { useModuleI18n } from '@/i18n/composables';
import { useTheme } from 'vuetify';
import { authApi } from '@/api/v1';
import { authApi, publicApi, type PublicVersionData } from '@/api/v1';
const cardVisible = ref(false);
const router = useRouter();
@@ -16,6 +16,10 @@ const customizer = useCustomizerStore();
const { tm: t } = useModuleI18n('features/auth');
const theme = useTheme();
const authLoginRef = ref<InstanceType<typeof AuthLogin> | null>(null);
const publicVersions = ref<PublicVersionData | null>(null);
const versionDialogVisible = ref(false);
type VersionItem = { key: string; label: string; value: string };
type VersionWarning = { key: string; title: string; message: string };
const logoTitle = computed(() => {
if (authLoginRef.value?.stage === 'totp' || authLoginRef.value?.stage === 'recovery') {
@@ -41,7 +45,90 @@ const currentThemeIcon = computed(() => {
return 'mdi-white-balance-sunny';
});
const versionValues = computed(() => {
const versions = publicVersions.value;
if (!versions) {
return { webui: '', runtime: '', code: '' };
}
return {
webui: String(versions.webui_version || '').trim(),
runtime: String(versions.astrbot_version || '').trim(),
code: String(versions.astrbot_code_version || '').trim(),
};
});
const normalizedVersionValues = computed(() => {
return {
webui: versionValues.value.webui.replace(/^v/i, ''),
runtime: versionValues.value.runtime.replace(/^v/i, ''),
code: versionValues.value.code.replace(/^v/i, ''),
};
});
const versionWarnings = computed(() => {
const normalized = normalizedVersionValues.value;
const warnings: VersionWarning[] = [];
if (normalized.webui && normalized.runtime && normalized.webui !== normalized.runtime) {
warnings.push({
key: 'webui-runtime',
title: t('versions.webuiMismatchTitle'),
message: t('versions.webuiMismatchMessage'),
});
}
if (normalized.runtime && normalized.code && normalized.runtime !== normalized.code) {
warnings.push({
key: 'runtime-code',
title: t('versions.runtimeMismatchTitle'),
message: t('versions.runtimeMismatchMessage'),
});
}
return warnings;
});
const versionItems = computed(() => {
const { webui, runtime, code } = versionValues.value;
const normalized = normalizedVersionValues.value;
const items: VersionItem[] = [];
if (webui) {
items.push({
key: 'webui',
label: t('versions.webui'),
value: webui,
});
}
if (runtime) {
items.push({
key: 'astrbot',
label: t('versions.astrbotRuntime'),
value: runtime,
});
}
if (runtime && code && normalized.runtime !== normalized.code) {
items.push({
key: 'astrbot-code',
label: t('versions.astrbotCode'),
value: code,
});
}
return items;
});
onMounted(async () => {
publicApi.versions()
.then((res) => {
publicVersions.value = res.data?.data || null;
})
.catch((error) => {
if (import.meta.env.DEV) {
console.warn('Failed to load public versions:', error);
}
});
// 检查用户是否已登录,如果已登录则重定向
if (authStore.has_token()) {
const onboardingCompleted = await authStore.checkOnboardingCompleted();
@@ -140,7 +227,60 @@ onMounted(async () => {
<v-card-text>
<AuthLogin ref="authLoginRef" />
</v-card-text>
<div v-if="versionItems.length" class="login-version-info">
<span v-for="item in versionItems" :key="item.key" class="login-version-item">
<span class="login-version-label">{{ item.label }}</span>
<span class="login-version-value">{{ item.value }}</span>
</span>
<v-btn
v-if="versionWarnings.length"
class="version-help-btn"
icon
variant="text"
size="x-small"
:aria-label="t('versions.mismatchTooltip')"
@click="versionDialogVisible = true"
>
<v-icon size="16">mdi-help-circle-outline</v-icon>
<v-tooltip activator="parent" location="top">
{{ t('versions.mismatchTooltip') }}
</v-tooltip>
</v-btn>
</div>
</v-card>
<v-dialog v-model="versionDialogVisible" max-width="460">
<v-card class="version-dialog-card">
<v-card-title class="version-dialog-title">
<v-icon size="20" color="warning">mdi-alert-circle-outline</v-icon>
<span>{{ t('versions.dialogTitle') }}</span>
</v-card-title>
<v-card-text class="version-dialog-content">
<div
v-for="warning in versionWarnings"
:key="warning.key"
class="version-warning-block"
>
<div class="version-warning-title">{{ warning.title }}</div>
<div class="version-warning-message">{{ warning.message }}</div>
</div>
</v-card-text>
<v-card-actions class="version-dialog-actions">
<v-btn
href="https://docs.astrbot.app/faq.html"
target="_blank"
rel="noopener noreferrer"
variant="text"
prepend-icon="mdi-help-circle-outline"
>
{{ t('versions.faq') }}
</v-btn>
<v-spacer />
<v-btn color="primary" variant="text" @click="versionDialogVisible = false">
{{ t('versions.close') }}
</v-btn>
</v-card-actions>
</v-card>
</v-dialog>
</div>
</template>
@@ -160,4 +300,71 @@ onMounted(async () => {
width: 400px;
padding: 8px;
}
.login-version-info {
align-items: center;
color: rgba(var(--v-theme-on-surface), 0.56);
display: flex;
flex-wrap: wrap;
gap: 4px 8px;
justify-content: center;
line-height: 1.45;
padding: 0 14px 10px;
text-align: center;
font-size: 12px;
}
.login-version-item {
max-width: 100%;
overflow: hidden;
text-overflow: ellipsis;
white-space: nowrap;
}
.login-version-label {
margin-right: 4px;
}
.version-help-btn {
color: rgba(var(--v-theme-warning), 0.95);
margin-left: -2px;
}
.version-dialog-card {
border-radius: 8px !important;
}
.version-dialog-title {
align-items: center;
display: flex;
gap: 8px;
font-size: 17px;
line-height: 1.35;
padding-bottom: 8px;
}
.version-dialog-content {
padding-top: 4px !important;
}
.version-warning-block + .version-warning-block {
margin-top: 14px;
}
.version-warning-title {
color: rgba(var(--v-theme-on-surface), 0.88);
font-size: 14px;
font-weight: 600;
margin-bottom: 6px;
}
.version-warning-message {
color: rgba(var(--v-theme-on-surface), 0.68);
font-size: 13px;
line-height: 1.65;
}
.version-dialog-actions {
padding-top: 0;
}
</style>

View File

@@ -27,7 +27,9 @@ Set dashboard.host in data/cmd_config.json to enable remote access.
### Forgot Dashboard Password
If you forgot your AstrBot dashboard password, find the `"dashboard"` field in `AstrBot/data/cmd_config.json`, for example:
If you forgot your AstrBot dashboard password, you can use the CLI tool `astrbot password` to change the password.
Another approach you can take is to find the `"dashboard"` field in `AstrBot/data/cmd_config.json`, for example:
```json
"dashboard": {

View File

@@ -28,7 +28,9 @@ Set dashboard.host in data/cmd_config.json to enable remote access.
### 管理面板的密码忘记了
如果你忘记了 AstrBot 管理面板的密码,你可以`AstrBot/data/cmd_config.json` 配置文件中找到 `"dashboard"` 字段,如下:
如果你忘记了 AstrBot 管理面板的密码,你可以直接使用CLI工具`astrbot password`来更改密码
另外,你也可以在 `AstrBot/data/cmd_config.json` 配置文件中找到 `"dashboard"` 字段,如下:
```json
"dashboard": {

View File

@@ -8,7 +8,7 @@ info:
JSON objects because their schemas are provided at runtime by template
endpoints.
Developer API keys currently support these scopes only: bot, provider,
persona, im, config, chat, file, plugin, mcp, skill. The config scope also
persona, im, config, chat, data, file, plugin, mcp, skill. The config scope also
grants bot and provider access.
servers:
- url: http://localhost:6185
@@ -4135,6 +4135,16 @@ paths:
"200":
$ref: "#/components/responses/Ok"
/api/v1/stats/versions:
get:
tags: [Stats]
summary: Get public WebUI and AstrBot versions
operationId: getPublicVersions
security: []
responses:
"200":
$ref: "#/components/responses/Ok"
/api/v1/stats/first-notice:
get:
tags: [Stats]
@@ -5127,8 +5137,8 @@ components:
type: array
items:
type: string
enum: [bot, provider, persona, im, config, chat, file, plugin, mcp, skill]
example: [bot, provider, persona, im, config, chat, file, plugin, mcp, skill]
enum: [bot, provider, persona, im, config, chat, data, file, plugin, mcp, skill]
example: [bot, provider, persona, im, config, chat, data, file, plugin, mcp, skill]
expires_at:
type: string
format: date-time

View File

@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
version = "4.26.0-beta.12"
version = "4.26.0"
description = "Easy-to-use multi-platform LLM chatbot and development framework"
readme = "README.md"
license = { text = "AGPL-3.0-or-later" }

View File

@@ -1248,6 +1248,23 @@ async def test_version_endpoints_use_md5_password_hint(
assert _removed_md5_hint_alias_key() not in data["data"]
@pytest.mark.asyncio
async def test_public_versions_endpoint_does_not_require_auth(app: FastAPIAppAdapter):
test_client = app.test_client()
response = await test_client.get("/api/stat/versions")
data = await response.get_json()
assert response.status_code == 200
assert data["status"] == "ok"
assert data["data"]["astrbot_version"]
assert "webui_version" in data["data"]
assert "astrbot_code_version" in data["data"]
assert "change_pwd_hint" not in data["data"]
assert "md5_pwd_hint" not in data["data"]
assert "password_upgrade_required" not in data["data"]
def test_password_hash_lookup_falls_back_to_md5_when_pbkdf2_missing(
core_lifecycle_td: AstrBotCoreLifecycle,
):
@@ -1756,6 +1773,12 @@ async def test_plugin_page_content_issues_scoped_asset_token(
css_response = await anonymous_client.get(css_url.group(1))
assert css_response.status_code == 200
stale_cookie_response = await anonymous_client.get(
app_js_url.group(1),
headers={"Cookie": f"{DASHBOARD_JWT_COOKIE_NAME}=stale.dashboard.token"},
)
assert stale_cookie_response.status_code == 200
out_of_scope_response = await anonymous_client.get(
f"/api/plugin/get?asset_token={asset_token}"
)

View File

@@ -981,6 +981,40 @@ def _jwt_headers() -> dict[str, str]:
return {"Authorization": f"Bearer {token}"}
@pytest.mark.asyncio
async def test_public_versions_route_uses_static_folder(
fake_core_lifecycle,
fake_db: FakeDb,
tmp_path: Path,
):
static_folder = tmp_path / "dist"
assets_folder = static_folder / "assets"
assets_folder.mkdir(parents=True)
(static_folder / "index.html").write_text("<!doctype html>", encoding="utf-8")
(assets_folder / "version").write_text("v9.8.7", encoding="utf-8")
app = create_dashboard_asgi_app(
core_lifecycle=fake_core_lifecycle,
db=fake_db,
jwt_secret=JWT_SECRET,
static_folder=str(static_folder),
)
transport = httpx.ASGITransport(app=app)
async with httpx.AsyncClient(
transport=transport,
base_url="http://testserver",
) as client:
response = await client.get("/api/v1/stats/versions")
data = response.json()
assert response.status_code == 200
assert data["status"] == "ok"
assert data["data"]["webui_version"] == "v9.8.7"
assert data["data"]["astrbot_version"]
assert "astrbot_code_version" in data["data"]
def test_fastapi_app_adapter_registers_on_app_state():
app = FastAPI()
adapter = FastAPIAppAdapter(app)