Compare commits

...

8 Commits

Author SHA1 Message Date
Weilong Liao
b6913833d4 chore: bump version to 4.26.0-beta.11
Release 4.26.0-beta.11
2026-06-20 22:32:56 +08:00
Soulter
f9d4082217 feat: add prerelease visibility toggle 2026-06-20 22:23:25 +08:00
Soulter
ddc4e142c7 fix: clarify WebUI recovery hint 2026-06-20 22:11:38 +08:00
Weilong Liao
d36987dd19 fix: restore static runtime version
Fixes #8924
2026-06-20 22:06:24 +08:00
NayukiChiba
0a0c677404 docs: 文档更新 - 指令、FAQ、网页搜索、插件发布等 (#8912)
* docs(community): 更新 QQ 群组信息
- 移除了旧的 QQ 群组信息并添加了新的群组。
- 确保所有群组信息的准确性和完整性。
- 提升文档的可读性和用户体验。

* docs(webui): 为忘记密码章节添加 FAQ 引用链接

- 在英文和中文文档的"忘记密码"章节末尾添加 TIP 提示框
- 提示框链接至对应 FAQ 条目,提供更详细的操作说明
- 增强文档之间的关联性,提升用户查阅体验

* docs(command): 新增 /stats 和 /provider 指令文档,更新 FAQ 管理员指令列表

- 在英文和中文的命令文档中添加 /stats 和 /provider 指令的详细使用说明
- 更新中英文 FAQ 页面的管理员指令列表,与最新的默认指令保持一致
- 确保中英文文档内容同步,提升文档准确性和用户查阅体验

* docs(websearch): 新增 Firecrawl 网页搜索提供商文档支持

- 在开发版配置文档中补充 websearch_provider 选项,加入 firecrawl 及对应密钥配置项
- 更新中英文网页搜索使用指南,将支持搜索源数量同步为五种并提供 Firecrawl 注册指引
- 确保中英文文档内容一致,提升配置说明的完整性和用户查阅体验

* docs(plugin-publish): 更新插件提交方式至新仓库

- 废弃通过 AstrBot 主仓库 Issue 提交插件的方式
- 新增警告框提示正确提交入口为 AstrBot_Plugins_Collection
- 同步更新中英文版插件发布文档
2026-06-20 16:43:58 +08:00
Weilong Liao
da7f53d5eb fix: keep system tools with persona tool lists (#8908) 2026-06-20 01:15:31 +08:00
Soulter
a7533aacda fix: fall back to stale WebUI when repair fails 2026-06-20 00:01:29 +08:00
Weilong Liao
46a846b88b chore: bump version to 4.26.0-beta.10 (#8905) 2026-06-19 23:59:39 +08:00
40 changed files with 432 additions and 230 deletions

View File

@@ -50,6 +50,7 @@ ruff check .
5. Use English for all new comments.
6. For path handling, use `pathlib.Path` instead of string paths, and use `astrbot.core.utils.path_utils` to get the AstrBot data and temp directory.
7. When backend API routes, request/response schemas, or OpenAPI definitions change, regenerate the frontend API client by running `cd dashboard && pnpm generate:api`.
8. When updating the project version, keep `[project].version` in `pyproject.toml` and `VERSION` in `astrbot/core/config/default.py` in sync.
### KISS and First Principles
@@ -108,7 +109,7 @@ Prepare a release from a clean worktree with:
uv run python scripts/prepare_release.py 4.25.0
```
The script updates `pyproject.toml`, creates `changelogs/v4.25.0.md`, runs the required Python checks, and prints the remaining steps. Use these flags when needed:
The script updates `pyproject.toml` and `astrbot/core/config/default.py`, creates `changelogs/v4.25.0.md`, runs the required Python checks, and prints the remaining steps. Use these flags when needed:
```bash
uv run python scripts/prepare_release.py 4.25.0 --generate-api-client

View File

@@ -234,10 +234,6 @@ pre-commit install
### QQ Groups
- Group 12: 916228568 (New)
- Group 9: 1076659624 (Full)
- Group 10: 1078079676 (Full)
- Group 11: 704659519 (Full)
- Group 1: 322154837 (Full)
- Group 3: 630166526 (Full)
- Group 4: 1077826412 (Full)
@@ -245,6 +241,12 @@ pre-commit install
- Group 6: 753075035 (Full)
- Group 7: 743746109 (Full)
- Group 8: 1030353265 (Full)
- Group 9: 1076659624 (Full)
- Group 10: 1078079676 (Full)
- Group 11: 704659519 (Full)
- Group 12: 916228568 (Full)
- Group 13: 1092185289
- Group 14: 1103419483
- Developer Group(Chit-chat): 975206796
- Developer Group(Formal): 1039761811

View File

@@ -226,10 +226,6 @@ pre-commit install
### QQ 群组
- 12 群916228568 (新)
- 9 群1076659624 (人满)
- 10 群1078079676 (人满)
- 11 群704659519 (人满)
- 1 群322154837 (人满)
- 3 群630166526 (人满)
- 4 群1077826412 (人满)
@@ -237,6 +233,14 @@ pre-commit install
- 6 群753075035 (人满)
- 7 群743746109 (人满)
- 8 群1030353265 (人满)
- 9 群1076659624 (人满)
- 10 群1078079676 (人满)
- 11 群704659519 (人满)
- 12 群916228568 (人满)
- 13 群1092185289
- 14 群1103419483
- 开发者群偏闲聊吹水975206796
- 开发者群正式1039761811

View File

@@ -457,10 +457,10 @@ async def _ensure_persona_and_skills(
cfg: dict,
plugin_context: Context,
event: AstrMessageEvent,
) -> set[str] | None:
) -> None:
"""Ensure persona and skills are applied to the request's system prompt or user prompt."""
if not req.conversation:
return None
return
(
persona_id,
@@ -527,13 +527,11 @@ async def _ensure_persona_and_skills(
# inject toolset in the persona
if (persona and persona.get("tools") is None) or not persona:
persona_allowed_tools = None
persona_toolset = tmgr.get_full_tool_set()
for tool in list(persona_toolset):
if not tool.active:
persona_toolset.remove_tool(tool.name)
else:
persona_allowed_tools = {str(tool_name) for tool_name in persona["tools"]}
persona_toolset = ToolSet()
if persona["tools"]:
for tool_name in persona["tools"]:
@@ -614,7 +612,6 @@ async def _ensure_persona_and_skills(
)
except Exception:
pass
return persona_allowed_tools
async def _request_img_caption(
@@ -947,13 +944,12 @@ async def _decorate_llm_request(
plugin_context: Context,
config: MainAgentBuildConfig,
provider: Provider | None = None,
) -> set[str] | None:
) -> None:
cfg = config.provider_settings or plugin_context.get_config(
umo=event.unified_msg_origin
).get("provider_settings", {})
_apply_prompt_prefix(req, cfg)
persona_allowed_tools = None
main_provider_supports_image = provider is not None and _provider_supports_modality(
provider, "image"
@@ -962,9 +958,7 @@ async def _decorate_llm_request(
quote_images_already_captioned = False
if req.conversation:
persona_allowed_tools = await _ensure_persona_and_skills(
req, cfg, plugin_context, event
)
await _ensure_persona_and_skills(req, cfg, plugin_context, event)
if img_cap_prov_id and req.image_urls and not main_provider_supports_image:
await _ensure_img_caption(
@@ -993,7 +987,6 @@ async def _decorate_llm_request(
tz = plugin_context.get_config().get("timezone")
_append_system_reminders(event, req, cfg, tz)
_apply_workspace_extra_prompt(event, req)
return persona_allowed_tools
def _plugin_tool_fix(event: AstrMessageEvent, req: ProviderRequest) -> None:
@@ -1522,9 +1515,7 @@ async def build_main_agent(
else:
return None
persona_allowed_tools = await _decorate_llm_request(
event, req, plugin_context, config, provider=provider
)
await _decorate_llm_request(event, req, plugin_context, config, provider=provider)
await _apply_kb(event, req, plugin_context, config)
@@ -1560,11 +1551,6 @@ async def build_main_agent(
)
)
if persona_allowed_tools is not None and req.func_tool:
req.func_tool.tools = [
tool for tool in req.func_tool.tools if tool.name in persona_allowed_tools
]
fallback_providers = _get_fallback_chat_providers(
provider, plugin_context, config.provider_settings
)

View File

@@ -1,39 +1,11 @@
"""如需修改配置,请在 `data/cmd_config.json` 中修改或者在管理面板中可视化修改。"""
import os
import re
from importlib.metadata import PackageNotFoundError
from importlib.metadata import version as package_version
from pathlib import Path
from astrbot.core.computer.booters.cua_defaults import CUA_DEFAULT_CONFIG
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
from astrbot.core.utils.toml_parser import read_pyproject_project_version
try:
import tomllib
except ModuleNotFoundError:
# <= Python 3.10 compatibility
tomllib = None
try:
pyproject_path = Path(__file__).resolve().parents[3] / "pyproject.toml"
if tomllib is None:
VERSION = read_pyproject_project_version(pyproject_path)
else:
with pyproject_path.open("rb") as f:
VERSION = tomllib.load(f)["project"]["version"]
except (FileNotFoundError, IndexError, KeyError, TypeError, ValueError):
try:
VERSION = package_version("astrbot") # PEP 440 version style, e.g. 1.2.3a4
match = re.match(r"^(\d+(?:\.\d+)*)(a|b|rc)(\d+)$", VERSION)
if match:
release, prerelease, number = match.groups()
prerelease = {"a": "alpha", "b": "beta", "rc": "rc"}[prerelease]
VERSION = f"{release}-{prerelease}.{number}"
except PackageNotFoundError:
VERSION = "0.0.0"
VERSION = "4.26.0-beta.11"
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
PERSONAL_WECHAT_CONFIG_METADATA = {
"weixin_oc_base_url": {

View File

@@ -72,46 +72,6 @@ def _read_dependency_array(raw_value: str) -> list[str]:
raise ValueError("Unterminated project.dependencies array")
def read_pyproject_project_version(pyproject_path: Path) -> str:
"""Read the project version from a pyproject.toml file.
Args:
pyproject_path: Path to the pyproject.toml file.
Returns:
The value of the project.version field.
Raises:
FileNotFoundError: The pyproject.toml file does not exist.
ValueError: The project.version field is missing or unsupported.
"""
in_project_section = False
for raw_line in pyproject_path.read_text(encoding="utf-8").splitlines():
line = raw_line.strip()
if not line or line.startswith("#"):
continue
if line.startswith("[") and line.endswith("]"):
in_project_section = line == "[project]"
continue
if not in_project_section:
continue
key, separator, raw_value = line.partition("=")
if key.strip() != "version":
continue
if not separator:
raise ValueError("Missing value separator for project.version")
version, tail = _read_quoted_value(raw_value, "project.version")
if tail and not tail.startswith("#"):
raise ValueError("Unsupported content after project.version")
return version
raise ValueError("Missing project.version")
def read_pyproject_project_dependencies(pyproject_path: Path) -> list[str]:
"""Read project dependencies from a pyproject.toml file.

View File

@@ -203,10 +203,19 @@ class AstrBotDashboard:
) or is_dashboard_dist_compatible(bundled_dist, VERSION):
self.data_path = str(bundled_dist)
logger.info("Using bundled dashboard dist: %s", self.data_path)
elif (
os.path.exists(user_dist) and (Path(user_dist) / "index.html").is_file()
):
logger.warning(
"Using existing data/dist as a fallback even though WebUI version mismatches core: %s, expected v%s. "
"Some dashboard features may not work until the matching WebUI is available.",
user_version,
VERSION,
)
self.data_path = os.path.abspath(user_dist)
elif os.path.exists(user_dist):
logger.warning(
"Ignoring data/dist because WebUI version mismatches core: %s, expected v%s.",
user_version,
"Ignoring data/dist because WebUI files are incomplete for core v%s.",
VERSION,
)
self.data_path = None

View File

@@ -0,0 +1,22 @@
- [更新日志(简体中文)](#chinese)
- [Changelog(English)](#english)
<a id="chinese"></a>
## What's Changed
### 修复
- 恢复 WebUI 在接口返回 401 时跳转登录页,避免会话失效后停留在异常状态。([#8903](https://github.com/AstrBotDevs/AstrBot/pull/8903))
- 保持 Core 版本与 WebUI 静态资源版本同步,修复打包或升级后可能加载旧 dist、资源版本错配的问题。([#8901](https://github.com/AstrBotDevs/AstrBot/pull/8901))
- 将知识库上下文作为临时 user 内容注入,修复模型请求中知识库上下文角色不准确的问题。([#8904](https://github.com/AstrBotDevs/AstrBot/pull/8904))
<a id="english"></a>
## What's Changed (EN)
### Bug Fixes
- Restored the WebUI login redirect when API requests return 401, preventing expired sessions from staying in a broken state. ([#8903](https://github.com/AstrBotDevs/AstrBot/pull/8903))
- Kept Core and WebUI static asset versions in sync, fixing stale dist loading and asset version mismatches after packaging or upgrades. ([#8901](https://github.com/AstrBotDevs/AstrBot/pull/8901))
- 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))

View File

@@ -0,0 +1,10 @@
## What's Changed
<!-- Review, group, and polish these entries before publishing. -->
- fix: keep system tools with persona tool lists (#8908) (da7f53d5e)
- docs: 文档更新 - 指令、FAQ、网页搜索、插件发布等 (#8912) (0a0c67740)
- fix: restore static runtime version (d36987dd1)
- fix: clarify WebUI recovery hint (ddc4e142c)
- feat: add prerelease visibility toggle (f9d408221)

View File

@@ -1,4 +1,4 @@
/* Auto-generated MDI subset 280 icons */
/* Auto-generated MDI subset 279 icons */
/* Do not edit manually. Run: pnpm run subset-icons */
@font-face {
@@ -312,10 +312,6 @@
content: "\F0193";
}
.mdi-content-save-check-outline::before {
content: "\F18EB";
}
.mdi-content-save-outline::before {
content: "\F0818";
}

View File

@@ -50,7 +50,7 @@
},
"upgradeRecovery": {
"title": "Upgrade Needs Restart",
"description": "The WebUI has been updated to {dashboardVersion}, but AstrBot Core is still {coreVersion}. This usually means the restart flow was interrupted by refreshing the page during upgrade.",
"description": "The WebUI has been updated to {dashboardVersion}, but AstrBot Core is still {coreVersion}. This usually means the restart flow was interrupted by refreshing the page during upgrade. If restarting still does not resolve it, try deleting the data/dist folder under the AstrBot runtime directory, then restart AstrBot.",
"hint": "Restart the backend to finish the upgrade. This page will reload automatically after AstrBot is back.",
"restartButton": "Restart Backend",
"laterButton": "Later",

View File

@@ -26,7 +26,9 @@
"release": "😊 Release"
},
"advancedSettings": "Advanced settings",
"releases": "Releases",
"updateToLatest": "Update to Latest Version",
"showPreReleases": "Show pre-release versions",
"preRelease": "Pre-release",
"preReleaseWarning": {
"title": "Pre-release Version Notice",

View File

@@ -50,7 +50,7 @@
},
"upgradeRecovery": {
"title": "Требуется завершить обновление",
"description": "WebUI обновлен до {dashboardVersion}, но AstrBot Core все еще {coreVersion}. Обычно это означает, что процесс перезапуска был прерван обновлением страницы во время обновления.",
"description": "WebUI обновлен до {dashboardVersion}, но AstrBot Core все еще {coreVersion}. Обычно это означает, что процесс перезапуска был прерван обновлением страницы во время обновления. Если перезапуск не решит проблему, попробуйте удалить папку data/dist в runtime-директории AstrBot, затем перезапустите AstrBot.",
"hint": "Перезапустите backend, чтобы завершить обновление. Страница автоматически обновится после восстановления AstrBot.",
"restartButton": "Перезапустить backend",
"laterButton": "Позже",

View File

@@ -26,7 +26,9 @@
"release": "😊 Релиз"
},
"advancedSettings": "Расширенные настройки",
"releases": "Релизы",
"updateToLatest": "Обновить до последней версии",
"showPreReleases": "Показывать предварительные версии",
"preRelease": "Предварительная версия",
"preReleaseWarning": {
"title": "Внимание: предварительная версия",

View File

@@ -50,7 +50,7 @@
},
"upgradeRecovery": {
"title": "检测到升级未完成",
"description": "当前 WebUI 已更新到 {dashboardVersion},但 AstrBot Core 仍是 {coreVersion}。这通常是升级过程中刷新页面导致重启流程被打断。",
"description": "当前 WebUI 已更新到 {dashboardVersion},但 AstrBot Core 仍是 {coreVersion}。这通常是升级过程中刷新页面导致重启流程被打断。如果重启后仍未解决,请尝试删除 AstrBot 运行时目录下的 data/dist 文件夹后重启 AstrBot。",
"hint": "请重启后端以完成升级,重启完成后页面会自动刷新。",
"restartButton": "立即重启后端",
"laterButton": "稍后处理",

View File

@@ -26,7 +26,9 @@
"release": "😊 正式版"
},
"advancedSettings": "高级设置",
"releases": "版本列表",
"updateToLatest": "更新到最新版本",
"showPreReleases": "显示预发布版本",
"preRelease": "预发布",
"preReleaseWarning": {
"title": "预发布版本提醒",

View File

@@ -30,6 +30,7 @@ const { t } = useI18n();
const route = useRoute();
const LAST_BOT_ROUTE_KEY = "astrbot:last_bot_route";
const LAST_CHAT_ROUTE_KEY = "astrbot:last_chat_route";
const SHOW_PRE_RELEASES_KEY = "astrbot:updateDialog:showPreReleases";
let dialog = ref(false);
let accountWarning = ref(false);
let accountWarningMd5 = ref(false);
@@ -50,6 +51,11 @@ let dashboardHasNewVersion = ref(false);
let dashboardCurrentVersion = ref("");
let releases = ref<any[]>([]);
let releasesLoading = ref(false);
const showPreReleases = ref(
typeof window === "undefined"
? false
: localStorage.getItem(SHOW_PRE_RELEASES_KEY) === "true",
);
let updatingDashboardLoading = ref(false);
let installLoading = ref(false);
let showAdvancedUpdateSettings = ref(false);
@@ -150,7 +156,12 @@ const releasesHeader = computed(() => [
{ title: t("core.header.updateDialog.table.content"), key: "body" },
{ title: t("core.header.updateDialog.table.actions"), key: "switch" },
]);
const firstReleasePageItems = computed(() => releases.value.slice(0, 6));
const visibleReleases = computed(() =>
showPreReleases.value
? releases.value
: releases.value.filter((item: any) => !isPreRelease(item.tag_name)),
);
const firstReleasePageItems = computed(() => visibleReleases.value.slice(0, 6));
const firstReleasePageHasPreRelease = computed(() =>
firstReleasePageItems.value.some((item: any) => isPreRelease(item.tag_name)),
);
@@ -883,6 +894,11 @@ onMounted(() => {
// 监听 viewMode 变化,切换到 bot 模式时跳转到首页
// 保存 bot 模式的最後路由
// 監聽 route 變化,保存最後一次 bot 路由
watch(showPreReleases, (value) => {
if (typeof window === "undefined") return;
localStorage.setItem(SHOW_PRE_RELEASES_KEY, value ? "true" : "false");
});
watch(
() => route.fullPath,
(newPath) => {
@@ -1456,6 +1472,21 @@ onMounted(async () => {
<!-- 发行版 -->
<div class="mt-5">
<div class="release-table-toolbar mb-3">
<div class="text-subtitle-1 font-weight-medium">
{{ t("core.header.updateDialog.releases") }}
</div>
<v-switch
v-model="showPreReleases"
class="release-prerelease-switch"
color="warning"
density="compact"
hide-details
inset
:label="t('core.header.updateDialog.showPreReleases')"
></v-switch>
</div>
<v-alert
v-if="!installLoading && firstReleasePageHasPreRelease"
type="warning"
@@ -1489,7 +1520,7 @@ onMounted(async () => {
<v-data-table
:headers="releasesHeader"
:items="releases"
:items="visibleReleases"
item-key="name"
:items-per-page="6"
density="comfortable"
@@ -1913,6 +1944,18 @@ onMounted(async () => {
margin-left: 0;
}
.release-table-toolbar {
display: flex;
align-items: center;
justify-content: space-between;
gap: 12px;
flex-wrap: wrap;
}
.release-prerelease-switch {
flex: 0 1 auto;
}
/* 响应式布局样式 */
.logo-container {
margin-left: 10px;

View File

@@ -16,10 +16,6 @@ Welcome to submit Issues or Pull Requests:
### Tencent QQ Groups
- Group 12: 916228568 (New)
- Group 9: 1076659624 (Full)
- Group 10: 1078079676 (Full)
- Group 11: 704659519 (Full)
- Group 1: 322154837 (Full)
- Group 3: 630166526 (Full)
- Group 4: 1077826412 (Full)
@@ -27,6 +23,12 @@ Welcome to submit Issues or Pull Requests:
- Group 6: 753075035 (Full)
- Group 7: 743746109 (Full)
- Group 8: 1030353265 (Full)
- Group 9: 1076659624 (Full)
- Group 10: 1078079676 (Full)
- Group 11: 704659519 (Full)
- Group 12: 916228568 (Full)
- Group 13: 1092185289
- Group 14: 1103419483
- **AstrBot Core Development Group: 975206796** (AstrBot development members are usually active here. Welcome to anyone interested in programming/AI technology~)
## Become an AstrBot Organization Member

View File

@@ -288,12 +288,13 @@ Whether to enable AstrBot's built-in web search capability. Default is `false`.
#### `provider_settings.websearch_provider`
Web search provider type. Default is `tavily`. Currently supports `tavily`, `bocha`, `baidu_ai_search`, and `brave`.
Web search provider type. Default is `tavily`. Currently supports `tavily`, `bocha`, `baidu_ai_search`, `brave`, and `firecrawl`.
- `tavily`: Uses the Tavily search engine.
- `bocha`: Uses the BoCha search engine.
- `baidu_ai_search`: Uses Baidu AI Search (MCP).
- `brave`: Uses Brave Search API.
- `firecrawl`: Uses the Firecrawl Search API.
#### `provider_settings.websearch_tavily_key`
@@ -307,6 +308,10 @@ API Key list for the BoCha search engine. Required when using `bocha` as the web
API Key list for the Brave search engine. Required when using `brave` as the web search provider.
#### `provider_settings.websearch_firecrawl_key`
API Key list for the Firecrawl search engine. Required when using `firecrawl` as the web search provider.
#### `provider_settings.web_search_link`
Whether to prompt the model to include links to search results in the reply. Default is `false`.

View File

@@ -4,7 +4,12 @@ After completing your plugin development, you can choose to publish it to the As
AstrBot uses GitHub to host plugins, so you'll need to push your plugin code to the GitHub plugin repository you created earlier.
You can submit your plugin by visiting the [AstrBot Plugin Marketplace](https://plugins.astrbot.app). Once on the website, click the `+` button in the bottom-right corner, fill in the basic information, author details, repository information, and other required fields. Then click the `Submit to GITHUB` button. You will be redirected to the AstrBot repository's Issue submission page. Please verify that all information is correct, then click the `Create` button to complete the plugin publication process.
You can submit your plugin by visiting the [AstrBot Plugin Marketplace](https://plugins.astrbot.app). Once on the website, click the `+` button in the bottom-right corner, fill in the basic information, author details, repository information, and other required fields. Then click the `Submit to GITHUB` button.
> [!WARNING]
> **Main repository Issue submission is deprecated**: The previous method of submitting plugins via Issues in the AstrBot main repository is no longer used. Please submit your plugin at the **[AstrBot_Plugins_Collection](https://github.com/AstrBotDevs/AstrBot_Plugins_Collection)** repository.
You will be redirected to the AstrBot_Plugins_Collection repository's Issue submission page. Please verify that all information is correct, then click the `Create` button to complete the plugin publication process.
![fill out the form](https://files.astrbot.app/docs/source/images/plugin-publish/image.png)

View File

@@ -100,7 +100,7 @@ After restart, AstrBot will reload or download WebUI files that match the curren
### No Permission to Execute Admin Commands
1. `/reset, /persona, /dashboard_update, /op, /deop, /wl, /dewl` are the default admin commands. You can use the `/sid` command to get a user's ID, then add it to the admin ID list in Settings -> Other Settings.
1. `/name, /provider, /dashboard_update, /op, /deop, /persona, /llm, /plugin, /model, /groupnew` are the default admin commands. You can use the `/sid` command to get a user's ID, then add it to the admin ID list in Settings -> Other Settings.
### Chinese Characters Garbled When Locally Rendering Markdown Images (t2i)

View File

@@ -18,6 +18,8 @@ The following commands are shipped with AstrBot and loaded by default:
- `/reset`: Reset the current conversation's LLM context.
- `/stop`: Stop Agent tasks currently running in the current session.
- `/new`: Create and switch to a new conversation.
- `/stats`: View token usage statistics for the current conversation.
- `/provider`: View or switch LLM Provider. This command requires admin permission.
- `/dashboard_update`: Update AstrBot WebUI. This command requires admin permission.
- `/set`: Set a session variable, commonly used for Agent Runner input variables such as Dify, Coze, or DashScope.
- `/unset`: Remove a session variable.
@@ -102,6 +104,45 @@ For third-party Agent Runners such as `dify`, `coze`, `dashscope`, and `deerflow
If there are no running tasks in the current session, AstrBot will report that no task is running.
### `/stats`
`/stats` shows token usage statistics for the current conversation.
It queries the database for all Provider call records in the current conversation and displays:
- Total tokens (input + output).
- Input tokens (cached) — input tokens that were cached by the provider and skipped for billing.
- Input tokens (other) — input tokens that were not cached and billed normally.
- Output tokens — tokens generated by the model.
If you are not in a conversation, AstrBot will prompt you to create one with `/new`.
### `/provider`
`/provider` views or switches the Provider (LLM / TTS / STT) used by the current UMO.
**Viewing the Provider list:**
With no arguments, `/provider` lists all configured Providers grouped by LLM, TTS, and STT. Each Provider shows:
- An index number for switching.
- Provider ID and the model currently in use (LLM type).
- Reachability status: `✅` means the connection is healthy, `❌` means a connection failure (with an error code).
- The currently active Provider is marked with `(currently in use)` at the end.
> [!NOTE]
> Reachability checks must be enabled in WebUI under `Config -> General Config -> AI Config`, expand the "More Settings" section at the bottom, and enable "Provider Reachability Check". When disabled, reachability markers are not shown and the list loads faster.
**Switching Providers:**
Use `/provider <index>` to switch the current session's LLM Provider to the Provider at the given index in the list.
- `/provider <index>`: Switch to the LLM Provider at the given index.
- `/provider tts <index>`: Switch to the TTS Provider at the given index.
- `/provider stt <index>`: Switch to the STT Provider at the given index.
This command requires admin permission.
## Built-in Commands Extension
Other commands that were previously shipped with the core have been moved to a separate plugin:

View File

@@ -14,11 +14,11 @@ When using a large language model that supports function calling with the web se
And other prompts with search intent to trigger the model to invoke the search tool.
AstrBot currently supports 4 web search providers: `Tavily`, `BoCha`, `Baidu AI Search`, and `Brave`.
AstrBot currently supports 5 web search providers: `Tavily`, `BoCha`, `Baidu AI Search`, `Brave`, and `Firecrawl`.
![image](https://files.astrbot.app/docs/source/images/websearch/image.png)
Go to `Configuration`, scroll down to find Web Search, where you can select `Tavily`, `BoCha`, `Baidu AI Search`, or `Brave`.
Go to `Configuration`, scroll down to find Web Search, where you can select `Tavily`, `BoCha`, `Baidu AI Search`, `Brave`, or `Firecrawl`.
### Tavily
@@ -36,6 +36,10 @@ Get an API Key from Baidu Qianfan APP Builder, then fill it in the corresponding
Get an API Key from Brave Search, then fill it in the corresponding configuration item.
### Firecrawl
Go to [Firecrawl](https://firecrawl.dev) to get an API Key, then fill it in the corresponding configuration item.
If you use Tavily as your web search source, you will get a better experience optimization on AstrBot ChatUI, including citation source display and more:
![](https://files.astrbot.app/docs/source/images/websearch/image1.png)

View File

@@ -119,3 +119,6 @@ Modify the `port` in the `dashboard` configuration in the data/cmd_config.json f
## Forgot Password
Modify the `password` in the `dashboard` configuration in the data/cmd_config.json file and delete the entire password key-value pair.
> [!TIP]
> For more details, see [FAQ - Forgot Dashboard Password](/en/faq.md#forgot-dashboard-password).

View File

@@ -6,17 +6,19 @@
### QQ 群
- 12: 916228568 (新)
- 9: 1076659624 (人满)
- 10 群: 1078079676 (人满)
- 11 群: 704659519 (人满)
- 1: 322154837 (人满)
- 3: 630166526 (人满)
- 4: 1077826412 (人满)
- 5: 822130018 (人满)
- 6 群: 753075035 (人满)
- 7 群: 743746109 (人满)
- 8 群: 1030353265 (人满)
- 1 群322154837 (人满)
- 3630166526 (人满)
- 4 群1077826412 (人满)
- 5 群822130018 (人满)
- 6753075035 (人满)
- 7743746109 (人满)
- 81030353265 (人满)
- 91076659624 (人满)
- 10 群1078079676 (人满)
- 11 群704659519 (人满)
- 12 群916228568 (人满)
- 13 群1092185289
- 14 群1103419483
- **AstrBot 核心开发交流群: 975206796**AstrBot 开发成员通常活跃于此,欢迎任何对编程/AI 技术感兴趣的同学加入~
### Discord

View File

@@ -288,12 +288,13 @@ ID 白名单。填写后,将只处理所填写的 ID 发来的消息事件。
#### `provider_settings.websearch_provider`
网页搜索提供商类型。默认为 `tavily`。目前支持 `tavily``bocha``baidu_ai_search``brave`
网页搜索提供商类型。默认为 `tavily`。目前支持 `tavily``bocha``baidu_ai_search``brave``firecrawl`
- `tavily`:使用 Tavily 搜索引擎。
- `bocha`:使用 BoCha 搜索引擎。
- `baidu_ai_search`:使用百度 AI SearchMCP
- `brave`:使用 Brave Search API。
- `firecrawl`:使用 Firecrawl Search API。
#### `provider_settings.websearch_tavily_key`
@@ -307,6 +308,10 @@ BoCha 搜索引擎的 API Key 列表。使用 `bocha` 作为网页搜索提供
Brave 搜索引擎的 API Key 列表。使用 `brave` 作为网页搜索提供商时需要填写。
#### `provider_settings.websearch_firecrawl_key`
Firecrawl 搜索引擎的 API Key 列表。使用 `firecrawl` 作为网页搜索提供商时需要填写。
#### `provider_settings.web_search_link`
是否在回复中提示模型附上搜索结果的链接。默认为 `false`

View File

@@ -4,7 +4,12 @@
AstrBot 使用 GitHub 托管插件,因此你需要先将插件代码推送到之前创建的 GitHub 插件仓库中。
你可以前往 [AstrBot 插件市场](https://plugins.astrbot.app) 提交你的插件。进入该网站后,点击右下角的 `+` 按钮,填写好基本信息、作者信息、仓库信息等内容后,点击 `提交到 GITHUB` 按钮,你将会被导航到 AstrBot 仓库的 Issue 提交页面,请确认信息无误后点击 `Create` 按钮提交,即可完成插件发布
你可以前往 [AstrBot 插件市场](https://plugins.astrbot.app) 提交你的插件。进入该网站后,点击右下角的 `+` 按钮,填写好基本信息、作者信息、仓库信息等内容后,点击 `提交到 GITHUB` 按钮。
> [!WARNING]
> **主仓库 Issue 提交方式已废弃**:此前通过 AstrBot 主仓库 Issue 提交插件的方式已不再使用。现在请前往 **[AstrBot_Plugins_Collection](https://github.com/AstrBotDevs/AstrBot_Plugins_Collection)** 仓库提交你的插件。
你将会被导航到 AstrBot_Plugins_Collection 仓库的 Issue 提交页面,请确认信息无误后点击 `Create` 按钮提交,即可完成插件发布。
![fill out the form](https://files.astrbot.app/docs/source/images/plugin-publish/image.png)

View File

@@ -118,7 +118,7 @@ Set dashboard.host in data/cmd_config.json to enable remote access.
### 没有权限操作管理员指令
1. `/reset, /persona, /dashboard_update, /op, /deop, /wl, /dewl` 是默认的管理员指令。可以通过 `/sid` 指令得到用户的 ID然后在 `配置` -> `其他配置` 中添加到管理员 ID 名单中。
1. `/name, /provider, /dashboard_update, /op, /deop, /persona, /llm, /plugin, /model, /groupnew` 是默认的管理员指令。可以通过 `/sid` 指令得到用户的 ID然后在 `配置` -> `其他配置` 中添加到管理员 ID 名单中。
### 本地渲染 Markdown 图片t2i时中文乱码

View File

@@ -18,6 +18,8 @@ AstrBot 的指令通过插件机制注册。为了保持主程序轻量,当前
- `/reset`:重置当前会话的 LLM 上下文。
- `/stop`:停止当前会话中正在运行的 Agent 任务。
- `/new`:创建并切换到一个新对话。
- `/stats`:查看当前会话的 Token 用量统计。
- `/provider`:查看或切换 LLM Provider。该指令需要管理员权限。
- `/dashboard_update`:更新 AstrBot WebUI。该指令需要管理员权限。
- `/set`:设置当前会话变量,常用于 Dify、Coze、DashScope 等 Agent 执行器的输入变量。
- `/unset`:移除当前会话变量。
@@ -96,6 +98,45 @@ AstrBot 的指令通过插件机制注册。为了保持主程序轻量,当前
如果当前会话没有正在运行的任务AstrBot 会提示当前会话没有运行中的任务。
### `/stats`
`/stats` 用于查看当前会话的 Token 用量统计。
它从数据库中查询当前对话的所有 Provider 调用记录,汇总并展示:
- 总 Token 用量(输入 Token + 输出 Token
- 输入 Token缓存命中即被提供商缓存并跳过计费的输入 Token。
- 输入 Token其他即未被缓存、正常计费的输入 Token。
- 输出 Token即模型生成的输出 Token。
如果当前不在任何对话中AstrBot 会提示先使用 `/new` 创建对话。
### `/provider`
`/provider` 用于查看或切换当前 UMO 使用的 ProviderLLM / TTS / STT
**查看 Provider 列表:**
不带参数时,`/provider` 会列出所有已配置的 Provider按 LLM、TTS、STT 分类展示。每个 Provider 旁会显示:
- 序号,用于后续切换。
- Provider ID 和当前使用的模型LLM 类型)。
- 可达性标记:`✅` 表示连接正常,`❌` 表示连接失败(附带错误码)。
- 当前正在使用的 Provider 末尾会标注 `(当前使用)`
> [!NOTE]
> 可达性检测需要在 WebUI 的 `配置 -> 普通配置 -> AI 配置` 中,展开底部的「更多配置」,开启「提供商可达性检测」后才会生效。关闭后不显示可达性标记,列表加载更快。
**切换 Provider**
使用 `/provider <序号>` 可以将当前会话的 LLM Provider 切换为列表中对应序号的 Provider。
- `/provider <序号>`:切换到指定序号的 LLM Provider。
- `/provider tts <序号>`:切换到指定序号的 TTS Provider。
- `/provider stt <序号>`:切换到指定序号的 STT Provider。
该指令需要管理员权限。
## 内置指令扩展
除上述基础指令外,其他原本随主程序提供的内置指令已经迁移到独立插件:

View File

@@ -13,11 +13,11 @@ AstrBot 内置的网页搜索功能依赖大模型提供 `函数调用` 能力
等等带有搜索意味的提示让大模型触发调用搜索工具。
AstrBot 当前支持 4 种网页搜索源接入方式:`Tavily``BoCha``百度 AI 搜索``Brave`
AstrBot 当前支持 5 种网页搜索源接入方式:`Tavily``BoCha``百度 AI 搜索``Brave``Firecrawl`
![image](https://files.astrbot.app/docs/source/images/websearch/image.png)
进入 `配置`,下拉找到网页搜索,您可选择 `Tavily``BoCha``百度 AI 搜索``Brave`
进入 `配置`,下拉找到网页搜索,您可选择 `Tavily``BoCha``百度 AI 搜索``Brave``Firecrawl`
### Tavily
@@ -35,6 +35,10 @@ AstrBot 当前支持 4 种网页搜索源接入方式:`Tavily`、`BoCha`、`
前往 Brave Search 获取 API Key然后填写在相应的配置项。
### Firecrawl
前往 [Firecrawl](https://firecrawl.dev) 获取 API Key然后填写在相应的配置项。
如果您使用 Tavily 作为网页搜索源,在 AstrBot ChatUI 上将会获得更好的体验优化,包括引用来源展示等:
![](https://files.astrbot.app/docs/source/images/websearch/image1.png)

View File

@@ -119,3 +119,6 @@ ChatUI 支持以下常用能力:
## 忘记密码
修改 data/cmd_config.json 文件内 `dashboard` 配置中的 `password`,将 password 整个键值对删除。
> [!TIP]
> 详细说明请参阅 [FAQ - 管理面板的密码忘记了](/faq.md#管理面板的密码忘记了)。

View File

@@ -160,6 +160,14 @@ async def check_dashboard_files(webui_dir: str | None = None):
)
except Exception as e:
logger.critical(f"下载管理面板文件失败: {e}")
if (data_dist_path / "index.html").is_file():
logger.warning(
"Falling back to existing data/dist WebUI %s even though core expects v%s. "
"Some dashboard features may not work until the matching WebUI is available.",
v or "unknown",
VERSION,
)
return str(data_dist_path)
return None
logger.info("管理面板下载完成。")
return str(data_dist_path)

View File

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

View File

@@ -193,6 +193,37 @@ def update_pyproject_version(version: str) -> Path:
raise ReleaseError("Missing [project].version in pyproject.toml")
def update_default_config_version(version: str) -> Path:
"""Update the hard-coded runtime version in default.py.
Args:
version: Release version to write.
Returns:
Path to the modified default.py file.
Raises:
ReleaseError: The runtime version constant cannot be found or parsed.
"""
default_config_path = REPO_ROOT / "astrbot" / "core" / "config" / "default.py"
lines = default_config_path.read_text(encoding="utf-8").splitlines(keepends=True)
for index, line in enumerate(lines):
match = re.match(
r"^(\s*VERSION\s*=\s*)([\"'])(.*?)(\2)(\s*(?:#.*)?)(\n?)$",
line,
)
if not match:
continue
prefix, quote, _current, _closing_quote, suffix, newline = match.groups()
lines[index] = f"{prefix}{quote}{version}{quote}{suffix}{newline}"
default_config_path.write_text("".join(lines), encoding="utf-8")
return default_config_path
raise ReleaseError("Missing VERSION in astrbot/core/config/default.py")
def write_changelog(version: str, commits: list[str]) -> Path:
"""Write a changelog draft for the release.
@@ -297,7 +328,14 @@ def commit_and_maybe_push(
Raises:
ReleaseError: Git add, commit, or push fails.
"""
git(["add", "pyproject.toml", str(changelog_path.relative_to(REPO_ROOT))])
git(
[
"add",
"pyproject.toml",
"astrbot/core/config/default.py",
str(changelog_path.relative_to(REPO_ROOT)),
]
)
if args.generate_api_client:
git(["add", "dashboard/src/api/generated"])
@@ -331,7 +369,9 @@ def print_next_steps(
else:
print("Next:")
print(f"1. Review and polish {changelog_rel}")
print(f"2. git add pyproject.toml {changelog_rel}")
print(
f"2. git add pyproject.toml astrbot/core/config/default.py {changelog_rel}"
)
print(f'3. git commit -m "chore: bump version to {version}"')
print(f"4. git push -u {args.remote} {branch}")
@@ -414,6 +454,7 @@ def main(argv: list[str] | None = None) -> int:
commits = release_commits(tag)
update_pyproject_version(version)
update_default_config_version(version)
changelog_path = write_changelog(version, commits)
run_validation(args)

View File

@@ -294,7 +294,34 @@ def test_dashboard_uses_bundled_dist_when_data_dist_is_stale(
assert server.data_path == str(bundled_dist)
def test_dashboard_ignores_mismatched_data_dist_without_bundled(
def test_dashboard_falls_back_to_mismatched_data_dist_without_bundled(
core_lifecycle_td: AstrBotCoreLifecycle,
monkeypatch,
tmp_path,
):
data_dir = tmp_path / "data"
user_dist = data_dir / "dist"
bundled_dist = tmp_path / "bundled-dist"
(user_dist / "assets").mkdir(parents=True)
(user_dist / "assets" / "version").write_text("v0.0.1", encoding="utf-8")
(user_dist / "index.html").write_text("stale", encoding="utf-8")
monkeypatch.setattr(
"astrbot.dashboard.server.get_astrbot_data_path",
lambda: str(data_dir),
)
monkeypatch.setattr(
"astrbot.dashboard.server.get_bundled_dashboard_dist_path",
lambda: bundled_dist,
)
shutdown_event = asyncio.Event()
server = AstrBotDashboard(core_lifecycle_td, core_lifecycle_td.db, shutdown_event)
assert server.data_path == str(user_dist)
def test_dashboard_ignores_incomplete_mismatched_data_dist_without_bundled(
core_lifecycle_td: AstrBotCoreLifecycle,
monkeypatch,
tmp_path,

View File

@@ -246,6 +246,44 @@ async def test_check_dashboard_files_exists_but_version_mismatch_downloads(tmp_p
assert "WebUI version mismatch" in call_args[0]
@pytest.mark.asyncio
async def test_check_dashboard_files_falls_back_to_stale_dist_when_download_fails(
tmp_path,
):
"""Tests stale dashboard fallback when the matching WebUI cannot be downloaded."""
from main import VERSION
data_dir = tmp_path / "data"
data_dist = data_dir / "dist"
bundled_dist = tmp_path / "bundled-dist"
(data_dist / "assets").mkdir(parents=True)
(data_dist / "assets" / "version").write_text("v0.0.1", encoding="utf-8")
(data_dist / "index.html").write_text("stale", encoding="utf-8")
with mock.patch("main.get_astrbot_data_path", return_value=str(data_dir)):
with mock.patch(
"main.get_bundled_dashboard_dist_path",
return_value=bundled_dist,
):
with mock.patch(
"main.download_dashboard",
side_effect=RuntimeError("missing dashboard asset"),
) as mock_download:
with mock.patch("main.logger.warning") as mock_logger_warning:
result = await check_dashboard_files()
assert result == str(data_dist)
mock_download.assert_called_once_with(
version=f"v{VERSION}",
latest=False,
allow_insecure_ssl_fallback=False,
)
assert any(
"Falling back to existing data/dist WebUI" in call.args[0]
for call in mock_logger_warning.call_args_list
)
@pytest.mark.asyncio
async def test_check_dashboard_files_downloads_when_matching_dist_is_incomplete(
tmp_path,

View File

@@ -2,87 +2,7 @@ from pathlib import Path
import pytest
from astrbot.core.utils.toml_parser import (
read_pyproject_project_dependencies,
read_pyproject_project_version,
)
def test_read_pyproject_project_version_reads_project_section(tmp_path: Path) -> None:
pyproject_path = tmp_path / "pyproject.toml"
pyproject_path.write_text(
"\n".join(
[
'version = "ignored"',
"[project]",
'name = "AstrBot"',
'version = "1.2.3-beta.4" # release version',
"[tool.example]",
'version = "ignored-again"',
]
),
encoding="utf-8",
)
assert read_pyproject_project_version(pyproject_path) == "1.2.3-beta.4"
@pytest.mark.parametrize(
("version_line", "expected"),
[
('version = "1.2.3"', "1.2.3"),
("version='1.2.3-beta.4'", "1.2.3-beta.4"),
(' version = "1.2.3-rc.1" ', "1.2.3-rc.1"),
],
)
def test_read_pyproject_project_version_accepts_simple_variants(
tmp_path: Path,
version_line: str,
expected: str,
) -> None:
pyproject_path = tmp_path / "pyproject.toml"
pyproject_path.write_text(
"\n".join(
[
"[project]",
'name = "AstrBot"',
version_line,
]
),
encoding="utf-8",
)
assert read_pyproject_project_version(pyproject_path) == expected
@pytest.mark.parametrize(
("version_line", "message"),
[
("version", "Missing value separator for project.version"),
('version = "1.2.3', "Unterminated project.version string"),
('version = "1.2.3" extra', "Unsupported content after project.version"),
('version = ""', "Empty project.version value"),
],
)
def test_read_pyproject_project_version_rejects_invalid_values(
tmp_path: Path,
version_line: str,
message: str,
) -> None:
pyproject_path = tmp_path / "pyproject.toml"
pyproject_path.write_text(
"\n".join(
[
"[project]",
'name = "AstrBot"',
version_line,
]
),
encoding="utf-8",
)
with pytest.raises(ValueError, match=message):
read_pyproject_project_version(pyproject_path)
from astrbot.core.utils.toml_parser import read_pyproject_project_dependencies
def test_read_pyproject_project_dependencies_reads_multiline_array(
@@ -174,11 +94,3 @@ def test_read_pyproject_project_dependencies_rejects_invalid_values(
with pytest.raises(ValueError, match=message):
read_pyproject_project_dependencies(pyproject_path)
def test_read_pyproject_project_version_raises_when_missing(tmp_path: Path) -> None:
pyproject_path = tmp_path / "pyproject.toml"
pyproject_path.write_text('[project]\nname = "AstrBot"\n', encoding="utf-8")
with pytest.raises(ValueError, match="Missing project.version"):
read_pyproject_project_version(pyproject_path)

View File

@@ -1009,7 +1009,7 @@ class TestEnsurePersonaAndSkills:
assert req.func_tool is not None
@pytest.mark.asyncio
async def test_persona_empty_tools_filters_late_builtin_tools(
async def test_persona_empty_tools_keeps_late_builtin_tools(
self, mock_event, mock_context, mock_provider
):
module = ama
@@ -1017,6 +1017,7 @@ class TestEnsurePersonaAndSkills:
mock_context.persona_manager.resolve_selected_persona = AsyncMock(
return_value=("locked", persona, None, False)
)
mock_event.platform_meta.support_proactive_message = False
mock_context.get_config.return_value = {
"provider_settings": {
"web_search": True,
@@ -1030,6 +1031,7 @@ class TestEnsurePersonaAndSkills:
"websearch_provider": "baidu_ai_search",
},
computer_use_runtime="none",
add_cron_tools=False,
)
req = ProviderRequest(prompt="hello")
req.conversation = MagicMock(persona_id="locked", history="[]")
@@ -1052,9 +1054,52 @@ class TestEnsurePersonaAndSkills:
)
assert result is not None
try:
assert result.provider_request.func_tool is None or (
result.provider_request.func_tool.empty()
assert result.provider_request.func_tool is not None
assert result.provider_request.func_tool.names() == ["web_search_baidu"]
finally:
if result.reset_coro:
result.reset_coro.close()
@pytest.mark.asyncio
async def test_persona_empty_tools_keeps_local_runtime_builtin_tools(
self, mock_event, mock_context, mock_provider
):
module = ama
persona = {"name": "locked", "prompt": "No tools.", "tools": []}
mock_context.persona_manager.resolve_selected_persona = AsyncMock(
return_value=("locked", persona, None, False)
)
mock_event.platform_meta.support_proactive_message = False
config = module.MainAgentBuildConfig(
tool_call_timeout=60,
computer_use_runtime="local",
add_cron_tools=False,
)
req = ProviderRequest(prompt="hello")
req.conversation = MagicMock(persona_id="locked", history="[]")
with (
patch("astrbot.core.astr_main_agent.AgentRunner") as mock_runner_cls,
patch("astrbot.core.astr_main_agent.AstrAgentContext"),
):
mock_runner = MagicMock()
mock_runner.reset = AsyncMock()
mock_runner_cls.return_value = mock_runner
result = await module.build_main_agent(
event=mock_event,
plugin_context=mock_context,
config=config,
provider=mock_provider,
req=req,
apply_reset=False,
)
assert result is not None
try:
assert result.provider_request.func_tool is not None
tool_names = result.provider_request.func_tool.names()
assert "astrbot_execute_shell" in tool_names
assert "astrbot_execute_python" in tool_names
finally:
if result.reset_coro:
result.reset_coro.close()