Compare commits

..

1 Commits

Author SHA1 Message Date
Soulter
4d00309d70 fix: remove python-ripgrep dependency, use system rg/grep instead
python-ripgrep 0.0.9 does not support Python 3.13 (requires <3.13,>=3.10).
This change replaces the python-ripgrep library with direct subprocess calls
to system ripgrep (rg) with fallback to grep.

Changes:
- Remove python-ripgrep import from local.py
- Rewrite search_files() to use shutil.which() to detect rg/grep availability
- Support ripgrep first, fallback to grep if not available
- Handle proper exit codes (0=success, 1=no matches for grep)
- Remove python-ripgrep from requirements.txt and pyproject.toml

Fixes #7496
2026-04-13 16:15:59 +08:00
9 changed files with 95 additions and 219 deletions

View File

@@ -13,23 +13,10 @@ on:
jobs:
smoke-test:
name: Smoke test (${{ matrix.os }}, Python ${{ matrix.python-version }})
runs-on: ${{ matrix.os }}
name: Run smoke tests
runs-on: ubuntu-latest
timeout-minutes: 10
strategy:
fail-fast: false
matrix:
os:
- ubuntu-latest
- macos-latest
- windows-latest
python-version:
- '3.10'
- '3.11'
- '3.12'
- '3.13'
- '3.14'
steps:
- name: Checkout
uses: actions/checkout@v6
@@ -39,21 +26,33 @@ jobs:
- name: Set up Python
uses: actions/setup-python@v6
with:
python-version: ${{ matrix.python-version }}
cache: 'pip'
cache-dependency-path: requirements.txt
- name: Install uv
python-version: '3.12'
- name: Install UV package manager
run: |
python -m pip install --upgrade pip
python -m pip install uv
pip install uv
- name: Install dependencies
run: |
uv pip install --system -r requirements.txt
uv sync
timeout-minutes: 15
- name: Run smoke tests
run: |
python scripts/smoke_startup_check.py
uv run main.py &
APP_PID=$!
echo "Waiting for application to start..."
for i in {1..60}; do
if curl -f http://localhost:6185 > /dev/null 2>&1; then
echo "Application started successfully!"
kill $APP_PID
exit 0
fi
sleep 1
done
echo "Application failed to start within 30 seconds"
kill $APP_PID 2>/dev/null || true
exit 1
timeout-minutes: 2

View File

@@ -1 +1 @@
__version__ = "4.23.1"
__version__ = "4.23.0"

View File

@@ -9,8 +9,6 @@ import sys
from dataclasses import dataclass
from typing import Any
from python_ripgrep import search
from astrbot.api import logger
from astrbot.core.computer.file_read_utils import (
detect_text_encoding,
@@ -221,15 +219,57 @@ class LocalFileSystemComponent(FileSystemComponent):
before_context: int | None = None,
) -> dict[str, Any]:
def _run() -> dict[str, Any]:
results = search(
patterns=[pattern],
paths=[path] if path else None,
globs=[glob] if glob else None,
after_context=after_context,
before_context=before_context,
line_number=True,
)
return {"success": True, "content": _truncate_long_lines("".join(results))}
search_path = path if path else "."
# Try ripgrep first, fallback to grep
if shutil.which("rg"):
cmd = ["rg", "--line-number", "--color=never"]
if glob:
cmd.extend(["--glob", glob])
if after_context:
cmd.extend(["--after-context", str(after_context)])
if before_context:
cmd.extend(["--before-context", str(before_context)])
cmd.extend([pattern, search_path])
elif shutil.which("grep"):
cmd = ["grep", "-rn", "--color=never"]
if after_context:
cmd.extend(["-A", str(after_context)])
if before_context:
cmd.extend(["-B", str(before_context)])
# grep doesn't support glob directly, use include if available
if glob and shutil.which("grep"):
# Try to use --include if grep supports it (GNU grep)
cmd.extend(["--include", glob])
cmd.extend([pattern, search_path])
else:
return {
"success": False,
"error": "Neither ripgrep (rg) nor grep is available on the system",
}
try:
result = subprocess.run(
cmd,
capture_output=True,
text=True,
timeout=30,
)
# grep returns exit code 1 when no matches found, which is not an error
if result.returncode not in (0, 1):
return {
"success": False,
"error": f"Search command failed with exit code {result.returncode}: {result.stderr}",
}
output = result.stdout if result.stdout else ""
return {"success": True, "content": _truncate_long_lines(output)}
except subprocess.TimeoutExpired:
return {
"success": False,
"error": "Search command timed out after 30 seconds",
}
except Exception as e:
return {"success": False, "error": f"Search failed: {str(e)}"}
return await asyncio.to_thread(_run)

View File

@@ -5,7 +5,7 @@ from typing import Any, TypedDict
from astrbot.core.utils.astrbot_path import get_astrbot_data_path
VERSION = "4.23.1"
VERSION = "4.23.0"
DB_PATH = os.path.join(get_astrbot_data_path(), "data_v4.db")
PERSONAL_WECHAT_CONFIG_METADATA = {
"weixin_oc_base_url": {

View File

@@ -1,29 +0,0 @@
- [更新日志(简体中文)](#chinese)
- [Changelog(English)](#english)
<a id="chinese"></a>
## What's Changed
*hotfix of v4.23.0*
-`python-ripgrep` 依赖降级到 `0.0.8`,修复 Python 3.13, 3.14 版本无法正常启动的问题。([#7514](https://github.com/AstrBotDevs/AstrBot/pull/7514)
- 修复会话 ID 包含冒号 `:` 时,平台路由在 Dashboard 中无法显示的问题。([#7517](https://github.com/AstrBotDevs/AstrBot/pull/7517)
- 适配 DeerFlow 2.0,更新 DeerFlow Runner、API Client、会话命令、Provider 管理逻辑、配置文档与相关测试。([#7500](https://github.com/AstrBotDevs/AstrBot/pull/7500)
- 移除 Dashboard `v-main` 不必要的 margin额外的外边距会把背景色显露出来因此视觉上看起来像右侧多了一条边框。[#7481](https://github.com/AstrBotDevs/AstrBot/pull/7481)
- 修复插件安装状态检查时格式不一致导致的判断问题。([#7493](https://github.com/AstrBotDevs/AstrBot/pull/7493)
- Dashboard 代码块高亮切换到 Shiki并同步暗色/亮色主题渲染,优化 README、更新日志与聊天消息中的代码块显示效果。[#7497](https://github.com/AstrBotDevs/AstrBot/pull/7497)
<a id="english"></a>
## What's Changed (EN)
*hotfix of v4.23.0*
- Fixed inconsistent format handling when checking whether a plugin is installed. ([#7493](https://github.com/AstrBotDevs/AstrBot/pull/7493))
- Downgraded `python-ripgrep` to `0.0.8` to fix dependency compatibility issues. ([#7514](https://github.com/AstrBotDevs/AstrBot/pull/7514))
- Fixed platform routes not being displayed in Dashboard when the session ID contains a colon `:`. ([#7517](https://github.com/AstrBotDevs/AstrBot/pull/7517))
- Switched Dashboard code-block highlighting to Shiki, synchronized dark/light theme rendering, and improved code-block display in README, changelog, and chat messages. ([#7497](https://github.com/AstrBotDevs/AstrBot/pull/7497))
- Aligned the DeerFlow runner with DeerFlow 2.0, including updates to the runner, API client, conversation commands, provider management, documentation, and tests. ([#7500](https://github.com/AstrBotDevs/AstrBot/pull/7500))
- Removed unnecessary `v-main` margins in Dashboard for more consistent layout across viewports. ([#7481](https://github.com/AstrBotDevs/AstrBot/pull/7481))
- Updated the smoke test workflow to cover multiple operating systems and Python versions, and added a startup check script. ([#7514](https://github.com/AstrBotDevs/AstrBot/pull/7514))

View File

@@ -860,15 +860,17 @@ export default {
// 过滤出属于该平台的路由,并保持顺序
const routes = [];
for (const [umop, confId] of Object.entries(routingTable)) {
const parsedUmop = this.parseUmop(umop);
if (this.isParsedUmopMatchPlatform(parsedUmop, platformId)) {
routes.push({
umop: umop,
originalUmop: umop, // 保存原始 UMOP 用于更新时查找
messageType: parsedUmop.messageType || '*',
sessionId: parsedUmop.sessionId || '*',
configId: confId
});
if (this.isUmopMatchPlatform(umop, platformId)) {
const parts = umop.split(':');
if (parts.length === 3) {
routes.push({
umop: umop,
originalUmop: umop, // 保存原始 UMOP 用于更新时查找
messageType: parts[1] === '' || parts[1] === '*' ? '*' : parts[1],
sessionId: parts[2] === '' || parts[2] === '*' ? '*' : parts[2],
configId: confId
});
}
}
}
@@ -990,29 +992,11 @@ export default {
},
isUmopMatchPlatform(umop, platformId) {
const parsedUmop = this.parseUmop(umop);
return this.isParsedUmopMatchPlatform(parsedUmop, platformId);
},
isParsedUmopMatchPlatform(parsedUmop, platformId) {
if (!parsedUmop) return false;
return parsedUmop.platform === platformId || parsedUmop.platform === '' || parsedUmop.platform === '*';
},
parseUmop(umop) {
if (!umop) return null;
const firstSeparatorIndex = umop.indexOf(':');
if (firstSeparatorIndex === -1) return null;
const secondSeparatorIndex = umop.indexOf(':', firstSeparatorIndex + 1);
if (secondSeparatorIndex === -1) return null;
return {
platform: umop.slice(0, firstSeparatorIndex),
messageType: umop.slice(firstSeparatorIndex + 1, secondSeparatorIndex),
sessionId: umop.slice(secondSeparatorIndex + 1)
};
if (!umop) return false;
const parts = umop.split(':');
if (parts.length !== 3) return false;
const platform = parts[0];
return platform === platformId || platform === '' || platform === '*';
},
// 获取消息类型标签

View File

@@ -1,6 +1,6 @@
[project]
name = "AstrBot"
version = "4.23.1"
version = "4.23.0"
description = "Easy-to-use multi-platform LLM chatbot and development framework"
readme = "README.md"
license = { text = "AGPL-3.0-or-later" }
@@ -64,7 +64,6 @@ dependencies = [
"python-socks>=2.8.0",
"pysocks>=1.7.1",
"packaging>=24.2",
"python-ripgrep==0.0.8",
]
[dependency-groups]

View File

@@ -52,5 +52,4 @@ tenacity>=9.1.2
shipyard-python-sdk>=0.2.4
shipyard-neo-sdk>=0.2.0
packaging>=24.2
qrcode>=8.2
python-ripgrep==0.0.8
qrcode>=8.2

View File

@@ -1,116 +0,0 @@
"""Cross-platform startup smoke check for AstrBot."""
from __future__ import annotations
import os
import shutil
import subprocess
import sys
import tempfile
import time
import urllib.error
import urllib.request
from pathlib import Path
REPO_ROOT = Path(__file__).resolve().parents[1]
HEALTH_URL = "http://127.0.0.1:6185"
STARTUP_TIMEOUT_SECONDS = 60
REQUEST_TIMEOUT_SECONDS = 2
def _tail(path: Path, lines: int = 80) -> str:
try:
content = path.read_text(encoding="utf-8", errors="replace").splitlines()
except OSError as exc:
return f"Unable to read smoke log: {exc}"
return "\n".join(content[-lines:])
def _is_ready() -> bool:
try:
with urllib.request.urlopen( # noqa: S310
HEALTH_URL,
timeout=REQUEST_TIMEOUT_SECONDS,
) as response:
return response.status < 400
except (OSError, urllib.error.URLError):
return False
def _stop_process(proc: subprocess.Popen[bytes]) -> None:
if proc.poll() is not None:
return
proc.terminate()
try:
proc.wait(timeout=10)
except subprocess.TimeoutExpired:
proc.kill()
proc.wait(timeout=10)
def main() -> int:
env = os.environ.copy()
env.setdefault("PYTHONUTF8", "1")
env.setdefault("TESTING", "true")
smoke_root = Path(tempfile.mkdtemp(prefix="astrbot-smoke-root-"))
env["ASTRBOT_ROOT"] = str(smoke_root)
log_path = smoke_root / "smoke.log"
webui_dir = smoke_root / "webui"
webui_dir.mkdir()
(webui_dir / "index.html").write_text(
"<!doctype html><title>AstrBot</title>",
encoding="utf-8",
)
with log_path.open("wb") as log_file:
proc = subprocess.Popen(
[
sys.executable,
str(REPO_ROOT / "main.py"),
"--webui-dir",
str(webui_dir),
],
cwd=REPO_ROOT,
stdout=log_file,
stderr=subprocess.STDOUT,
env=env,
)
print(f"Starting smoke test on {HEALTH_URL}")
deadline = time.monotonic() + STARTUP_TIMEOUT_SECONDS
try:
while time.monotonic() < deadline:
if _is_ready():
print("Smoke test passed")
return 0
return_code = proc.poll()
if return_code is not None:
print(
f"AstrBot exited before becoming healthy. Exit code: {return_code}",
file=sys.stderr,
)
print(_tail(log_path), file=sys.stderr)
return 1
time.sleep(1)
print(
"Smoke test failed: health endpoint did not become ready in time.",
file=sys.stderr,
)
print(_tail(log_path), file=sys.stderr)
return 1
finally:
_stop_process(proc)
try:
log_path.unlink()
except OSError:
pass
shutil.rmtree(smoke_root, ignore_errors=True)
if __name__ == "__main__":
raise SystemExit(main())