Skip to content
Closed
Show file tree
Hide file tree
Changes from 27 commits
Commits
Show all changes
50 commits
Select commit Hold shift + click to select a range
593a3d8
fix(desktop): make frozen pip installs independent of system python
zouyonghe Feb 17, 2026
86d904c
refactor: migrate desktop backend to cpython runtime
zouyonghe Feb 17, 2026
42a151f
fix(desktop): stabilize CI runtime and windows backend cleanup
zouyonghe Feb 18, 2026
cb15255
fix: write knowledge base under astrbot data dir
zouyonghe Feb 18, 2026
6f6d45b
fix(ci): support branch-based manual release runs
zouyonghe Feb 18, 2026
3c7fd49
fix(desktop): harden runtime packaging and windows cleanup
zouyonghe Feb 18, 2026
07bb9d2
fix(desktop): validate runtime python and harden windows pid checks
zouyonghe Feb 18, 2026
a858939
fix(desktop): harden cleanup fallback and runtime version detection
zouyonghe Feb 18, 2026
fe94a0a
fix(desktop): improve runtime validation and backend cleanup
zouyonghe Feb 18, 2026
c6c238e
fix(desktop): refactor packaged backend runtime resolution
zouyonghe Feb 18, 2026
f63f960
fix(desktop): harden packaged launch fallback and cleanup matching
zouyonghe Feb 18, 2026
d70a75f
refactor(desktop): split backend runtime helpers and cleanup logic
zouyonghe Feb 18, 2026
fb2a41b
refactor(core): remove pyinstaller-specific reboot reset
zouyonghe Feb 18, 2026
4260a95
refactor(core): remove frozen-runtime legacy branches
zouyonghe Feb 18, 2026
9f99b95
docs: use venv runtime env var in desktop readme
zouyonghe Feb 18, 2026
5512afb
fix: block desktop packaged online update paths
zouyonghe Feb 18, 2026
8a1f58a
fix: use writable cwd for packaged backend runtime
zouyonghe Feb 18, 2026
0300d56
refactor: simplify desktop runtime build and cleanup flow
zouyonghe Feb 18, 2026
d5ab04b
fix: normalize nested css selectors and simplify runtime probe parsing
zouyonghe Feb 18, 2026
8761e11
fix: scope css selector normalization and simplify backend launch flow
zouyonghe Feb 18, 2026
8834425
refactor: remove legacy frozen runtime compatibility path
zouyonghe Feb 18, 2026
72791a4
refactor: inline python runtime probe parsing flow
zouyonghe Feb 18, 2026
cdb8b26
refactor: simplify desktop backend build and launch strategy flow
zouyonghe Feb 18, 2026
4ca9618
fix: avoid auto-cleanup on plugin load failure and improve reload checks
zouyonghe Feb 18, 2026
b9c0945
fix: avoid packaging virtualenv as desktop runtime
zouyonghe Feb 18, 2026
184dd4b
refactor: simplify backend launch flow and runtime probe errors
zouyonghe Feb 18, 2026
e1b0a0f
docs: add troubleshooting note for requires-python probe failures
zouyonghe Feb 18, 2026
7365cc2
refactor: streamline backend config and unmanaged cleanup flow
zouyonghe Feb 18, 2026
e9920d1
fix(ci): package relocatable cpython runtime for desktop
zouyonghe Feb 18, 2026
3108863
fix(desktop): install runtime deps into packaged python
zouyonghe Feb 18, 2026
bdc963c
fix(desktop): retry pip install for uv-managed runtime
zouyonghe Feb 18, 2026
5257548
fix(ci): use setup-python runtime source for desktop packaging
zouyonghe Feb 18, 2026
126954f
refactor(ci): remove obsolete uv fallback paths
zouyonghe Feb 18, 2026
3a85efb
refactor(desktop): remove unused electron runtime APIs
zouyonghe Feb 18, 2026
650039a
fix(ci): use python-build-standalone runtime for desktop packaging
zouyonghe Feb 18, 2026
4987911
chore(ci): remove runtime import smoke check
zouyonghe Feb 18, 2026
3d86324
fix(desktop): add windows dll search paths for bundled runtime
zouyonghe Feb 18, 2026
8e15e80
fix(desktop): harden windows dll resolution in launcher
zouyonghe Feb 18, 2026
4e30186
refactor(ci): rebuild windows desktop release jobs
zouyonghe Feb 18, 2026
b46bd76
fix(ci): avoid cryptography source build on windows arm64
zouyonghe Feb 18, 2026
908c367
fix(desktop): bundle msvc runtime for windows backend
zouyonghe Feb 18, 2026
ac27872
fix(desktop): force utf-8 backend log output on windows
zouyonghe Feb 19, 2026
669d6d8
fix: make tray backend restart always run in main process
zouyonghe Feb 19, 2026
38f8d55
fix: enforce wheel-only plugin dependency installs in packaged runtime
zouyonghe Feb 19, 2026
ae9b164
refactor: simplify backend cleanup flow and extract vite postcss plugin
zouyonghe Feb 19, 2026
bca8ab1
refactor(ci): deduplicate packaged cpython runtime resolution
zouyonghe Feb 19, 2026
6f8417b
refactor: simplify windows cleanup state and harden runtime CI checks
zouyonghe Feb 19, 2026
cd16451
fix(ci): pass github token to runtime resolver
zouyonghe Feb 19, 2026
ed53778
fix(desktop): disable blockmap outputs and add jsonschema dependency
zouyonghe Feb 19, 2026
4e749bb
refactor: simplify backend cleanup and centralize pbs mapping
zouyonghe Feb 19, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
42 changes: 32 additions & 10 deletions .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,11 @@ on:
workflow_dispatch:
inputs:
ref:
description: "Git ref to build (branch/tag/SHA)"
description: "Git ref to release from (branch/tag/SHA); leave empty to use selected branch"
required: false
default: "master"
default: ""
tag:
description: "Release tag to publish assets to (for example: v4.14.6)"
description: "Release tag to publish assets to (required for workflow_dispatch, for example: v4.14.6)"
required: false

permissions:
Expand All @@ -30,7 +30,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}
ref: ${{ inputs.ref != '' && inputs.ref || github.ref_name }}

- name: Resolve tag
id: tag
Expand All @@ -41,7 +41,8 @@ jobs:
elif [ -n "${{ inputs.tag }}" ]; then
tag="${{ inputs.tag }}"
else
tag="$(git describe --tags --abbrev=0)"
echo "workflow_dispatch requires 'tag' input (for example: v4.17.5)." >&2
exit 1
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
fi
if [ -z "$tag" ]; then
echo "Failed to resolve tag." >&2
Expand Down Expand Up @@ -140,7 +141,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}
ref: ${{ inputs.ref != '' && inputs.ref || github.ref_name }}

- name: Resolve tag
id: tag
Expand All @@ -151,7 +152,8 @@ jobs:
elif [ -n "${{ inputs.tag }}" ]; then
tag="${{ inputs.tag }}"
else
tag="$(git describe --tags --abbrev=0)"
echo "workflow_dispatch requires 'tag' input (for example: v4.17.5)." >&2
exit 1
fi
if [ -z "$tag" ]; then
echo "Failed to resolve tag." >&2
Expand All @@ -163,10 +165,29 @@ jobs:
uses: astral-sh/setup-uv@v7

- name: Setup Python
id: setup-python
uses: actions/setup-python@v6
with:
python-version: "3.12"

- name: Resolve packaged CPython runtime source
shell: bash
env:
SETUP_PYTHON_PATH: ${{ steps.setup-python.outputs.python-path }}
run: |
if [ -z "$SETUP_PYTHON_PATH" ]; then
echo "actions/setup-python did not return python-path output." >&2
exit 1
fi
"$SETUP_PYTHON_PATH" - <<'PY' >> "$GITHUB_ENV"
import pathlib
import sys

executable = pathlib.Path(sys.executable).resolve()
runtime_root = executable.parent if sys.platform == "win32" else executable.parent.parent
print(f"ASTRBOT_DESKTOP_CPYTHON_HOME={runtime_root}")
PY

- name: Setup pnpm
uses: pnpm/action-setup@v4
with:
Expand Down Expand Up @@ -271,7 +292,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}
ref: ${{ inputs.ref != '' && inputs.ref || github.ref_name }}

- name: Resolve tag
id: tag
Expand All @@ -282,7 +303,8 @@ jobs:
elif [ -n "${{ inputs.tag }}" ]; then
tag="${{ inputs.tag }}"
else
tag="$(git describe --tags --abbrev=0)"
echo "workflow_dispatch requires 'tag' input (for example: v4.17.5)." >&2
exit 1
fi
if [ -z "$tag" ]; then
echo "Failed to resolve tag." >&2
Expand Down Expand Up @@ -355,7 +377,7 @@ jobs:
uses: actions/checkout@v6
with:
fetch-depth: 0
ref: ${{ inputs.ref || github.ref }}
ref: ${{ inputs.ref != '' && inputs.ref || github.ref_name }}

- name: Set up Python
uses: actions/setup-python@v6
Expand Down
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,10 @@ desktop/dist/
desktop/out/
desktop/resources/backend/astrbot-backend*
desktop/resources/backend/*.exe
desktop/resources/backend/app/
desktop/resources/backend/python/
desktop/resources/backend/launch_backend.py
desktop/resources/backend/runtime-manifest.json
desktop/resources/webui/*
desktop/resources/.pyinstaller/
package-lock.json
Expand Down
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -149,6 +149,7 @@ paru -S astrbot-git
#### 桌面端 Electron 打包

桌面端(Electron 打包,`pnpm` 工作流)构建流程请参阅:[`desktop/README.md`](desktop/README.md)。
打包前需要准备 CPython 运行时目录,并设置 `ASTRBOT_DESKTOP_CPYTHON_HOME`(详见该文档)。

## 支持的消息平台

Expand Down
7 changes: 5 additions & 2 deletions astrbot/core/knowledge_base/kb_db_sqlite.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,16 +13,19 @@
KBMedia,
KnowledgeBase,
)
from astrbot.core.utils.astrbot_path import get_astrbot_knowledge_base_path


class KBSQLiteDatabase:
def __init__(self, db_path: str = "data/knowledge_base/kb.db") -> None:
def __init__(self, db_path: str | None = None) -> None:
"""初始化知识库数据库

Args:
db_path: 数据库文件路径, 默认为 data/knowledge_base/kb.db
db_path: 数据库文件路径, 默认位于 AstrBot 数据目录下的 knowledge_base/kb.db

"""
if db_path is None:
db_path = str(Path(get_astrbot_knowledge_base_path()) / "kb.db")
self.db_path = db_path
self.DATABASE_URL = f"sqlite+aiosqlite:///{db_path}"
self.inited = False
Expand Down
5 changes: 3 additions & 2 deletions astrbot/core/knowledge_base/kb_mgr.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

from astrbot.core import logger
from astrbot.core.provider.manager import ProviderManager
from astrbot.core.utils.astrbot_path import get_astrbot_knowledge_base_path

# from .chunking.fixed_size import FixedSizeChunker
from .chunking.recursive import RecursiveCharacterChunker
Expand All @@ -13,7 +14,7 @@
from .retrieval.rank_fusion import RankFusion
from .retrieval.sparse_retriever import SparseRetriever

FILES_PATH = "data/knowledge_base"
FILES_PATH = get_astrbot_knowledge_base_path()
DB_PATH = Path(FILES_PATH) / "kb.db"
"""Knowledge Base storage root directory"""
CHUNKER = RecursiveCharacterChunker()
Expand All @@ -27,7 +28,7 @@ def __init__(
self,
provider_manager: ProviderManager,
) -> None:
Path(DB_PATH).parent.mkdir(parents=True, exist_ok=True)
DB_PATH.parent.mkdir(parents=True, exist_ok=True)
self.provider_manager = provider_manager
self._session_deleted_callback_registered = False

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,14 @@
import os
import re
from datetime import datetime
from pathlib import Path
from typing import cast

from funasr_onnx import SenseVoiceSmall
from funasr_onnx.utils.postprocess_utils import rich_transcription_postprocess

from astrbot.core import logger
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path
from astrbot.core.utils.io import download_file
from astrbot.core.utils.tencent_record_helper import tencent_silk_to_wav

Expand Down Expand Up @@ -50,7 +52,9 @@ async def initialize(self) -> None:

async def get_timestamped_path(self) -> str:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
return os.path.join("data", "temp", f"{timestamp}")
temp_dir = Path(get_astrbot_temp_path())
temp_dir.mkdir(parents=True, exist_ok=True)
return str(temp_dir / timestamp)

async def _is_silk_file(self, file_path) -> bool:
silk_header = b"SILK"
Expand Down
74 changes: 55 additions & 19 deletions astrbot/core/star/star_manager.py
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,13 @@
logger.warning("未安装 watchfiles,无法实现插件的热重载。")


class PluginDependencyInstallError(Exception):
def __init__(self, plugin_name: str, requirements_path: str, message: str) -> None:
super().__init__(message)
self.plugin_name = plugin_name
self.requirements_path = requirements_path


class PluginManager:
def __init__(self, context: Context, config: AstrBotConfig) -> None:
self.updator = PluginUpdator()
Expand Down Expand Up @@ -194,6 +201,13 @@ async def _check_plugin_dept_update(
await pip_installer.install(requirements_path=pth)
except Exception as e:
logger.error(f"更新插件 {p} 的依赖失败。Code: {e!s}")
if target_plugin:
raise PluginDependencyInstallError(
plugin_name=p,
requirements_path=pth,
message="插件依赖安装失败,请检查插件 requirements.txt "
"中的依赖版本或构建环境。",
) from e
return True

async def _import_plugin_with_dependency_recovery(
Expand Down Expand Up @@ -343,13 +357,21 @@ async def reload_failed_plugin(self, dir_name):
async with self._pm_lock:
if dir_name in self.failed_plugin_dict:
success, error = await self.load(specified_dir_name=dir_name)
if success:
self.failed_plugin_dict.pop(dir_name, None)
if not self.failed_plugin_dict:
self.failed_plugin_info = ""
return success, None
else:
if not success:
return False, error
plugin_reloaded = any(
star.root_dir_name == dir_name
for star in self.context.get_all_stars()
)
if not plugin_reloaded:
return (
False,
f"插件 {dir_name} 重载失败:未在插件目录中找到可加载插件。",
)
self.failed_plugin_dict.pop(dir_name, None)
if not self.failed_plugin_dict:
self.failed_plugin_info = ""
return True, None
return False, "插件不存在于失败列表中"

async def reload(self, specified_plugin_name=None):
Expand Down Expand Up @@ -431,6 +453,8 @@ async def load(self, specified_module_path=None, specified_dir_name=None):
return False, "未找到任何插件模块"

fail_rec = ""
has_target_filter = bool(specified_module_path or specified_dir_name)
matched_target = False

# 导入插件模块,并尝试实例化插件类
for plugin_module in plugin_modules:
Expand All @@ -457,6 +481,7 @@ async def load(self, specified_module_path=None, specified_dir_name=None):
continue
if specified_dir_name and root_dir_name != specified_dir_name:
continue
matched_target = True

logger.info(f"正在载入插件 {root_dir_name} ...")

Expand All @@ -471,6 +496,15 @@ async def load(self, specified_module_path=None, specified_dir_name=None):
except Exception as e:
logger.error(traceback.format_exc())
logger.error(f"插件 {root_dir_name} 导入失败。原因:{e!s}")
fail_rec += f"加载 {root_dir_name} 插件时出现问题,原因 {e!s}。\n"
self.failed_plugin_dict[root_dir_name] = {
"error": str(e),
"traceback": traceback.format_exc(),
}
if not reserved:
logger.warning(
f"{root_dir_name}插件安装失败,插件目录:{plugin_dir_path}"
)
continue

# 检查 _conf_schema.json
Expand Down Expand Up @@ -706,6 +740,10 @@ async def load(self, specified_module_path=None, specified_dir_name=None):
logger.error(f"同步指令配置失败: {e!s}")
logger.error(traceback.format_exc())

if has_target_filter and not matched_target:
target_label = specified_dir_name or specified_module_path
return False, f"未找到指定插件:{target_label}"

if not fail_rec:
return True, None
self.failed_plugin_info = fail_rec
Expand Down Expand Up @@ -781,10 +819,8 @@ async def install_plugin(self, repo_url: str, proxy=""):
async with self._pm_lock:
plugin_path = ""
dir_name = ""
cleanup_required = False
try:
plugin_path = await self.updator.install(repo_url, proxy)
cleanup_required = True

# reload the plugin
dir_name = os.path.basename(plugin_path)
Expand Down Expand Up @@ -829,10 +865,12 @@ async def install_plugin(self, repo_url: str, proxy=""):

return plugin_info
except Exception:
if cleanup_required and dir_name and plugin_path:
await self._cleanup_failed_plugin_install(
dir_name=dir_name,
plugin_path=plugin_path,
if plugin_path:
plugin_dir_name = dir_name or os.path.basename(plugin_path)
logger.warning(
"%s插件安装失败,插件目录:%s",
plugin_dir_name,
plugin_path,
)
raise

Expand Down Expand Up @@ -1096,7 +1134,6 @@ async def install_plugin_from_file(self, zip_file_path: str):
dir_name = os.path.basename(zip_file_path).replace(".zip", "")
dir_name = dir_name.removesuffix("-master").removesuffix("-main").lower()
desti_dir = os.path.join(self.plugin_store_path, dir_name)
cleanup_required = False

# 第一步:检查是否已安装同目录名的插件,先终止旧插件
existing_plugin = None
Expand All @@ -1118,7 +1155,6 @@ async def install_plugin_from_file(self, zip_file_path: str):

try:
self.updator.unzip_file(zip_file_path, desti_dir)
cleanup_required = True

# 第二步:解压后,读取新插件的 metadata.yaml,检查是否存在同名但不同目录的插件
try:
Expand Down Expand Up @@ -1195,9 +1231,9 @@ async def install_plugin_from_file(self, zip_file_path: str):

return plugin_info
except Exception:
if cleanup_required:
await self._cleanup_failed_plugin_install(
dir_name=dir_name,
plugin_path=desti_dir,
)
logger.warning(
"%s插件安装失败,插件目录:%s",
dir_name,
desti_dir,
)
raise
Loading