perf: optimize webchat and wecom ai queue lifecycle#4941
Conversation
There was a problem hiding this comment.
你好,我发现了 5 个问题,并补充了一些整体性的反馈:
- 在
WecomAIQueueMgr中,is_stream_finished和cleanup_expired_responses都在管理completed_streams的 TTL,但一个用的是参数(默认值),另一个则是硬编码的 60 秒字面量;建议将这个保留时长集中到一个地方进行配置,以避免随着时间推移在行为上产生微妙差异。 - 两个队列管理器的
_listen_to_queue方法在监听回调抛出异常时,只记录了简短的日志信息并吞掉异常;建议在日志中加入exc_info=True或完整的 traceback,方便在生产环境中排查事件驱动回调中的失败。
给 AI 代理的提示词
Please address the comments from this code review:
## Overall Comments
- In `WecomAIQueueMgr`, `is_stream_finished` and `cleanup_expired_responses` both manage `completed_streams` TTLs but use different hard-coded/default values (function arg vs. literal 60s); consider centralizing this retention period in one place to avoid subtle discrepancies in behavior over time.
- Both queue managers’ `_listen_to_queue` methods swallow listener callback exceptions with only a short log message; it may be helpful to include `exc_info=True` or the full traceback in these logs so that failures in the event-driven callbacks are easier to diagnose in production.
## Individual Comments
### Comment 1
<location> `astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py:183-187` </location>
<code_context>
+ return False
+ return True
+
def cleanup_expired_responses(self, max_age_seconds: int = 300):
"""清理过期的待处理响应
</code_context>
<issue_to_address>
**建议(suggestion):** 对齐 `pending_responses` 与 `completed_streams` 的 TTL 处理方式,以避免它们的存活时间出现出人意料的差异。
`cleanup_expired_responses` 使用 `max_age_seconds` 来处理 `pending_responses`,但 `completed_streams` 始终使用硬编码的 60 秒进行清理:
```python
expired_finished = [
session_id
for session_id, finished_at in self.completed_streams.items()
if current_time - finished_at > 60
]
```
这意味着调用方修改 `max_age_seconds` 时只会影响 `pending_responses`,从 API 上并不直观。建议将 `completed_streams` 的 TTL 绑定到 `max_age_seconds`(或由其推导出的值),或者把 `60` 提取为具名常量/参数,使 TTL 行为更加显式且可配置。
```suggestion
expired_finished = [
session_id
for session_id, finished_at in self.completed_streams.items()
if current_time - finished_at > max_age_seconds
]
```
</issue_to_address>
### Comment 2
<location> `astrbot/core/platform/sources/webchat/webchat_queue_mgr.py:39-41` </location>
<code_context>
+ """Remove back queue for the given request ID"""
+ self.back_queues.pop(request_id, None)
def remove_queues(self, conversation_id: str):
"""Remove queues for the given conversation ID"""
- if conversation_id in self.queues:
- del self.queues[conversation_id]
- if conversation_id in self.back_queues:
- del self.back_queues[conversation_id]
+ self.remove_queue(conversation_id)
+
+ def remove_queue(self, conversation_id: str):
</code_context>
<issue_to_address>
**问题(bug_risk,待确认):** 请再次确认与某个会话关联的 back 队列是否也需要被清理,以避免内存泄漏。
之前 `remove_queues(conversation_id)` 会删除以该 `conversation_id` 为 key 的输入队列和 back 队列。重构之后,back 队列改为使用 `request_id` 作为 key,而 `remove_queues` 现在只调用 `remove_queue`,它只会移除输入队列/监听器。
如果仍有现有调用方依赖 `remove_queues(conversation_id)` 来完整清理会话相关资源,那么对应的 back 队列现在可能会被无限期保留。请检查所有调用点(或者添加一个能够根据会话名义/显式映射来移除该会话所有队列的辅助方法),以确保在需要的情况下 back 队列同样会被清理。
</issue_to_address>
### Comment 3
<location> `astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py:227-236` </location>
<code_context>
- """Listen to a specific conversation queue"""
- queue = self.webchat_queue_mgr.get_or_create_queue(conversation_id)
- while True:
- try:
- data = await queue.get()
- await self.callback(data)
</code_context>
<issue_to_address>
**建议(suggestion):** 对 WeCom 监听器回调中的异常也记录 traceback 详情。
当前 `_listen_to_queue` 里,在回调报错时只记录了异常消息:
```python
logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")
```
由于这是在后台任务中运行,捕获完整的堆栈信息会让问题更易于调试。建议改为使用 `logger.exception`,或者 `logger.error(..., exc_info=True)`,以便将完整 traceback 打到日志中。
</issue_to_address>
### Comment 4
<location> `astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py:16` </location>
<code_context>
class WebChatQueueMgr:
- def __init__(self) -> None:
- self.queues = {}
+ def __init__(self, queue_maxsize: int = 128, back_queue_maxsize: int = 512) -> None:
+ self.queues: dict[str, asyncio.Queue] = {}
"""Conversation ID to asyncio.Queue mapping"""
</code_context>
<issue_to_address>
**问题(complexity):** 建议重构新增的监听器与生命周期管理逻辑,以拆分职责、集中清理逻辑,并封装异步等待/取消模式,从而提升可读性与可维护性。
当前在该类中添加的监听器/生命周期逻辑比较密集。可以在保持功能不变的前提下,通过以下方式降低复杂度:
### 1. 分离生命周期职责
目前 `remove_queues`、`remove_queue`、`cleanup_expired_responses` 和 `mark_finished` 的逻辑交织在一起。建议将“标记完成”和“清理”分离,并把清理逻辑统一到一个地方。
```python
def mark_stream_finished(self, session_id: str) -> None:
"""单独负责标记流结束"""
self.completed_streams[session_id] = asyncio.get_event_loop().time()
logger.debug(f"[WecomAI] 标记流已结束: {session_id}")
def _cleanup_session(self, session_id: str) -> None:
"""集中清理所有与 session 相关的状态"""
# 输入队列 + 监听
if session_id in self.queues:
del self.queues[session_id]
logger.debug(f"[WecomAI] 移除输入队列: {session_id}")
close_event = self._queue_close_events.pop(session_id, None)
if close_event is not None:
close_event.set()
task = self._listener_tasks.pop(session_id, None)
if task is not None:
task.cancel()
# 输出队列
if session_id in self.back_queues:
del self.back_queues[session_id]
logger.debug(f"[WecomAI] 移除输出队列: {session_id}")
# 待处理响应
if session_id in self.pending_responses:
del self.pending_responses[session_id]
logger.debug(f"[WecomAI] 移除待处理响应: {session_id}")
```
然后让对外的 API 更小、更清晰:
```python
def remove_queues(self, session_id: str, mark_finished: bool = False) -> None:
self._cleanup_session(session_id)
if mark_finished:
self.mark_stream_finished(session_id)
def remove_queue(self, session_id: str) -> None:
# 保留向后兼容,但委托给统一清理逻辑
self._cleanup_session(session_id)
```
`cleanup_expired_responses` 可以调用 `_cleanup_session`,避免部分清理逻辑重复。
---
### 2. 封装 wait/cancel 模式
`_listen_to_queue` 目前在多个地方内联了一个带手动取消的 `asyncio.wait` 模式。建议提取到一个辅助方法中,这样取消行为更容易审计和复用:
```python
async def _wait_queue_or_close(
self,
queue: asyncio.Queue,
close_event: asyncio.Event,
) -> dict | None:
"""返回队列数据, 或在关闭时返回 None"""
get_task = asyncio.create_task(queue.get())
close_task = asyncio.create_task(close_event.wait())
try:
done, pending = await asyncio.wait(
{get_task, close_task},
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
if close_task in done:
return None
return get_task.result()
finally:
if not get_task.done():
get_task.cancel()
if not close_task.done():
close_task.cancel()
```
这样 `_listen_to_queue` 会清晰很多:
```python
async def _listen_to_queue(
self,
session_id: str,
queue: asyncio.Queue,
close_event: asyncio.Event,
):
while True:
try:
data = await self._wait_queue_or_close(queue, close_event)
if data is None: # close_event set
break
if self._listener_callback is None:
continue
try:
await self._listener_callback(data)
except Exception as e:
logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")
except asyncio.CancelledError:
break
```
这样既保留了原有的行为(队列或关闭二选一、取消未完成任务),又把复杂的 `asyncio.wait` 逻辑集中到一个地方。
---
### 3. 让监听器职责更加明确
为减少“推模式 vs 拉模式”的双重 API 混淆,可以让管理器的对外接口更小、更明确:
- 在文档中说明 `set_listener` 是可选的;当设置后,管理器会自动为每个 session 启动监听任务。
- 可以考虑为每个 session 显式开启监听,而不是默认在队列创建时就绑定监听任务,这样可以减弱“存储 + 编排”混在一起的感觉。例如:
```python
def enable_listener_for_session(self, session_id: str) -> None:
if self._listener_callback is None:
raise RuntimeError("Listener callback must be set before enabling listener.")
self._start_listener_if_needed(session_id)
```
这样 `get_or_create_queue` 只负责创建队列,而监听编排是显式的、按需开启的,不再是隐式行为。
这些调整在保留现有功能的前提下,让生命周期边界更加清晰,也让异步编排逻辑更容易理解。
</issue_to_address>
### Comment 5
<location> `astrbot/core/platform/sources/webchat/webchat_queue_mgr.py:13` </location>
<code_context>
- """Conversation ID to asyncio.Queue mapping for responses"""
+ self.back_queues: dict[str, asyncio.Queue] = {}
+ """Request ID to asyncio.Queue mapping for responses"""
+ self._queue_close_events: dict[str, asyncio.Event] = {}
+ self._listener_tasks: dict[str, asyncio.Task] = {}
+ self._listener_callback: Callable[[tuple], Awaitable[None]] | None = None
</code_context>
<issue_to_address>
**问题(complexity):** 建议通过移除显式的关闭事件、仅依赖任务取消和简单的队列读取循环来简化监听器生命周期管理。
你可以只依赖任务取消来简化监听编排逻辑,而不再维护 `_queue_close_events` 与 `_listener_tasks` 两套状态。这样可以去掉一整个状态维度,也不用在 `_listen_to_queue` 内部进行复杂的 `asyncio.wait` 控制,同时仍能保持原有行为。
具体建议:
- 完全移除 `_queue_close_events`。
- 在 `get_or_create_queue` 中,只负责创建队列并在有回调时启动监听器。
- 在 `remove_queue` 中,取消监听任务;此时 `queue.get()` 会被取消,循环可以在捕获 `CancelledError` 后退出。
- 将 `_listen_to_queue` 简化为一个直接 `while True: data = await queue.get()` 的循环。
示例修改(截取相关部分):
```python
class WebChatQueueMgr:
def __init__(self, queue_maxsize: int = 128, back_queue_maxsize: int = 512) -> None:
self.queues: dict[str, asyncio.Queue] = {}
self.back_queues: dict[str, asyncio.Queue] = {}
self._listener_tasks: dict[str, asyncio.Task] = {}
self._listener_callback: Callable[[tuple], Awaitable[None]] | None = None
self.queue_maxsize = queue_maxsize
self.back_queue_maxsize = back_queue_maxsize
def get_or_create_queue(self, conversation_id: str) -> asyncio.Queue:
if conversation_id not in self.queues:
self.queues[conversation_id] = asyncio.Queue(maxsize=self.queue_maxsize)
self._start_listener_if_needed(conversation_id)
return self.queues[conversation_id]
def remove_queue(self, conversation_id: str):
self.queues.pop(conversation_id, None)
task = self._listener_tasks.pop(conversation_id, None)
if task is not None:
task.cancel()
```
监听器启动基本保持不变,只是不再需要 close event:
```python
def _start_listener_if_needed(self, conversation_id: str):
if self._listener_callback is None:
return
task = self._listener_tasks.get(conversation_id)
if task is not None and not task.done():
return
queue = self.queues.get(conversation_id)
if queue is None:
return
task = asyncio.create_task(
self._listen_to_queue(conversation_id, queue),
name=f"webchat_listener_{conversation_id}",
)
self._listener_tasks[conversation_id] = task
task.add_done_callback(lambda _: self._listener_tasks.pop(conversation_id, None))
logger.debug(f"Started listener for conversation: {conversation_id}")
```
监听循环也会更直观:
```python
async def _listen_to_queue(
self,
conversation_id: str,
queue: asyncio.Queue,
):
while True:
try:
data = await queue.get()
except asyncio.CancelledError:
break
if self._listener_callback is None:
continue
try:
await self._listener_callback(data)
except Exception as e:
logger.error(
f"Error processing message from conversation {conversation_id}: {e}"
)
```
这样可以:
- 去掉 `_queue_close_events` 以及每轮循环中创建/取消 `get_task`/`close_task` 的逻辑;
- 让生命周期更清晰:`remove_queue` → 取消任务 → 监听器退出;
- 同时保留“每个会话一个监听器”和 back 队列的现有行为与 API。
</issue_to_address>
帮我变得更有用!请在每条评论上点击 👍 或 👎,我会根据你的反馈改进后续的审查建议。
Original comment in English
Hey - I've found 5 issues, and left some high level feedback:
- In
WecomAIQueueMgr,is_stream_finishedandcleanup_expired_responsesboth managecompleted_streamsTTLs but use different hard-coded/default values (function arg vs. literal 60s); consider centralizing this retention period in one place to avoid subtle discrepancies in behavior over time. - Both queue managers’
_listen_to_queuemethods swallow listener callback exceptions with only a short log message; it may be helpful to includeexc_info=Trueor the full traceback in these logs so that failures in the event-driven callbacks are easier to diagnose in production.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- In `WecomAIQueueMgr`, `is_stream_finished` and `cleanup_expired_responses` both manage `completed_streams` TTLs but use different hard-coded/default values (function arg vs. literal 60s); consider centralizing this retention period in one place to avoid subtle discrepancies in behavior over time.
- Both queue managers’ `_listen_to_queue` methods swallow listener callback exceptions with only a short log message; it may be helpful to include `exc_info=True` or the full traceback in these logs so that failures in the event-driven callbacks are easier to diagnose in production.
## Individual Comments
### Comment 1
<location> `astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py:183-187` </location>
<code_context>
+ return False
+ return True
+
def cleanup_expired_responses(self, max_age_seconds: int = 300):
"""清理过期的待处理响应
</code_context>
<issue_to_address>
**suggestion:** Align the TTL handling for `pending_responses` and `completed_streams` to avoid surprising lifetime differences.
`cleanup_expired_responses` uses `max_age_seconds` for `pending_responses`, but `completed_streams` is always pruned with a hard‑coded 60 seconds:
```python
expired_finished = [
session_id
for session_id, finished_at in self.completed_streams.items()
if current_time - finished_at > 60
]
```
This means callers changing `max_age_seconds` affect only `pending_responses`, which is not obvious from the API. Consider tying `completed_streams` to `max_age_seconds` (or a derived value) or extracting the `60` into a named constant/parameter so TTL behavior is explicit and configurable.
```suggestion
expired_finished = [
session_id
for session_id, finished_at in self.completed_streams.items()
if current_time - finished_at > max_age_seconds
]
```
</issue_to_address>
### Comment 2
<location> `astrbot/core/platform/sources/webchat/webchat_queue_mgr.py:39-41` </location>
<code_context>
+ """Remove back queue for the given request ID"""
+ self.back_queues.pop(request_id, None)
def remove_queues(self, conversation_id: str):
"""Remove queues for the given conversation ID"""
- if conversation_id in self.queues:
- del self.queues[conversation_id]
- if conversation_id in self.back_queues:
- del self.back_queues[conversation_id]
+ self.remove_queue(conversation_id)
+
+ def remove_queue(self, conversation_id: str):
</code_context>
<issue_to_address>
**question (bug_risk):** Double‑check whether back queues associated with a conversation need to be cleaned up to avoid leaks.
Previously `remove_queues(conversation_id)` removed both input and back queues keyed by that `conversation_id`. After the refactor, back queues are keyed by `request_id`, and `remove_queues` now only calls `remove_queue`, which only removes the input queue/listener.
If any existing callers still rely on `remove_queues(conversation_id)` to fully clean up conversation resources, back queues may now be left behind indefinitely. Please review call sites (or add a helper that removes all queues for a conversation, e.g., via naming or an explicit mapping) to ensure back queues are also cleaned up where needed.
</issue_to_address>
### Comment 3
<location> `astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py:227-236` </location>
<code_context>
- """Listen to a specific conversation queue"""
- queue = self.webchat_queue_mgr.get_or_create_queue(conversation_id)
- while True:
- try:
- data = await queue.get()
- await self.callback(data)
</code_context>
<issue_to_address>
**suggestion:** Also log traceback details for WeCom listener callback failures.
Here, `_listen_to_queue` logs callback errors with only the exception message:
```python
logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")
```
Since this runs in a background task, capturing the stack trace will make failures debuggable. Please switch to `logger.exception` or `logger.error(..., exc_info=True)` so the full traceback is logged.
</issue_to_address>
### Comment 4
<location> `astrbot/core/platform/sources/wecom_ai_bot/wecomai_queue_mgr.py:16` </location>
<code_context>
class WebChatQueueMgr:
- def __init__(self) -> None:
- self.queues = {}
+ def __init__(self, queue_maxsize: int = 128, back_queue_maxsize: int = 512) -> None:
+ self.queues: dict[str, asyncio.Queue] = {}
"""Conversation ID to asyncio.Queue mapping"""
</code_context>
<issue_to_address>
**issue (complexity):** Consider refactoring the new listener and lifecycle management logic to separate concerns, centralize cleanup, and encapsulate the async wait/cancel pattern for better clarity and maintainability.
The added listener/lifecycle logic is quite dense in this class. You can keep the feature but reduce complexity by:
### 1. Separate lifecycle responsibilities
Right now `remove_queues`, `remove_queue`, `cleanup_expired_responses`, and `mark_finished` logic are mixed. Split “mark finished” from “cleanup” and unify cleanup in one place.
```python
def mark_stream_finished(self, session_id: str) -> None:
"""单独负责标记流结束"""
self.completed_streams[session_id] = asyncio.get_event_loop().time()
logger.debug(f"[WecomAI] 标记流已结束: {session_id}")
def _cleanup_session(self, session_id: str) -> None:
"""集中清理所有与 session 相关的状态"""
# 输入队列 + 监听
if session_id in self.queues:
del self.queues[session_id]
logger.debug(f"[WecomAI] 移除输入队列: {session_id}")
close_event = self._queue_close_events.pop(session_id, None)
if close_event is not None:
close_event.set()
task = self._listener_tasks.pop(session_id, None)
if task is not None:
task.cancel()
# 输出队列
if session_id in self.back_queues:
del self.back_queues[session_id]
logger.debug(f"[WecomAI] 移除输出队列: {session_id}")
# 待处理响应
if session_id in self.pending_responses:
del self.pending_responses[session_id]
logger.debug(f"[WecomAI] 移除待处理响应: {session_id}")
```
Then keep public APIs small and clear:
```python
def remove_queues(self, session_id: str, mark_finished: bool = False) -> None:
self._cleanup_session(session_id)
if mark_finished:
self.mark_stream_finished(session_id)
def remove_queue(self, session_id: str) -> None:
# 保留向后兼容,但委托给统一清理逻辑
self._cleanup_session(session_id)
```
`cleanup_expired_responses` can call `_cleanup_session` instead of partially duplicating cleanup logic.
---
### 2. Encapsulate the wait/cancel pattern
`_listen_to_queue` currently inlines an `asyncio.wait` pattern with manual cancellation in multiple places. Extract that into a helper so the cancellation behavior is easy to audit and reuse:
```python
async def _wait_queue_or_close(
self,
queue: asyncio.Queue,
close_event: asyncio.Event,
) -> dict | None:
"""返回队列数据, 或在关闭时返回 None"""
get_task = asyncio.create_task(queue.get())
close_task = asyncio.create_task(close_event.wait())
try:
done, pending = await asyncio.wait(
{get_task, close_task},
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
if close_task in done:
return None
return get_task.result()
finally:
if not get_task.done():
get_task.cancel()
if not close_task.done():
close_task.cancel()
```
Then `_listen_to_queue` becomes much easier to follow:
```python
async def _listen_to_queue(
self,
session_id: str,
queue: asyncio.Queue,
close_event: asyncio.Event,
):
while True:
try:
data = await self._wait_queue_or_close(queue, close_event)
if data is None: # close_event set
break
if self._listener_callback is None:
continue
try:
await self._listener_callback(data)
except Exception as e:
logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")
except asyncio.CancelledError:
break
```
This keeps the same behavior (queue-or-close, cancel pending tasks) but localizes the tricky `asyncio.wait` logic into a single helper.
---
### 3. Make listener responsibility more explicit
To reduce the “dual API” confusion (push vs pull), keep the manager surface small and explicit:
- Document that `set_listener` is optional and that, when set, the manager will start per-session listener tasks automatically.
- Consider requiring explicit opt-in per session, so the queue manager doesn’t always mix “storage + orchestration”. For example:
```python
def enable_listener_for_session(self, session_id: str) -> None:
if self._listener_callback is None:
raise RuntimeError("Listener callback must be set before enabling listener.")
self._start_listener_if_needed(session_id)
```
Then `get_or_create_queue` only creates queues, and listener orchestration is clearly separated and opt-in instead of implicit.
These changes keep all current functionality but make lifecycle boundaries clearer and the async orchestration logic easier to reason about.
</issue_to_address>
### Comment 5
<location> `astrbot/core/platform/sources/webchat/webchat_queue_mgr.py:13` </location>
<code_context>
- """Conversation ID to asyncio.Queue mapping for responses"""
+ self.back_queues: dict[str, asyncio.Queue] = {}
+ """Request ID to asyncio.Queue mapping for responses"""
+ self._queue_close_events: dict[str, asyncio.Event] = {}
+ self._listener_tasks: dict[str, asyncio.Task] = {}
+ self._listener_callback: Callable[[tuple], Awaitable[None]] | None = None
</code_context>
<issue_to_address>
**issue (complexity):** Consider simplifying the listener lifecycle by removing the explicit close events and relying solely on task cancellation with a straightforward queue-read loop.
You can simplify the listener orchestration by relying solely on task cancellation instead of maintaining both `_queue_close_events` and `_listener_tasks`. That removes an entire axis of state and the `asyncio.wait` juggling inside `_listen_to_queue`, while preserving behavior.
Concretely:
- Drop `_queue_close_events` entirely.
- In `get_or_create_queue`, just create the queue and start the listener if a callback is set.
- In `remove_queue`, cancel the listener task; `queue.get()` will be cancelled and the loop can exit on `CancelledError`.
- Simplify `_listen_to_queue` to a straightforward `while True: data = await queue.get()` loop.
Example changes (trimmed to the relevant parts):
```python
class WebChatQueueMgr:
def __init__(self, queue_maxsize: int = 128, back_queue_maxsize: int = 512) -> None:
self.queues: dict[str, asyncio.Queue] = {}
self.back_queues: dict[str, asyncio.Queue] = {}
self._listener_tasks: dict[str, asyncio.Task] = {}
self._listener_callback: Callable[[tuple], Awaitable[None]] | None = None
self.queue_maxsize = queue_maxsize
self.back_queue_maxsize = back_queue_maxsize
def get_or_create_queue(self, conversation_id: str) -> asyncio.Queue:
if conversation_id not in self.queues:
self.queues[conversation_id] = asyncio.Queue(maxsize=self.queue_maxsize)
self._start_listener_if_needed(conversation_id)
return self.queues[conversation_id]
def remove_queue(self, conversation_id: str):
self.queues.pop(conversation_id, None)
task = self._listener_tasks.pop(conversation_id, None)
if task is not None:
task.cancel()
```
Listener startup stays almost the same, but no close event:
```python
def _start_listener_if_needed(self, conversation_id: str):
if self._listener_callback is None:
return
task = self._listener_tasks.get(conversation_id)
if task is not None and not task.done():
return
queue = self.queues.get(conversation_id)
if queue is None:
return
task = asyncio.create_task(
self._listen_to_queue(conversation_id, queue),
name=f"webchat_listener_{conversation_id}",
)
self._listener_tasks[conversation_id] = task
task.add_done_callback(lambda _: self._listener_tasks.pop(conversation_id, None))
logger.debug(f"Started listener for conversation: {conversation_id}")
```
The listener loop becomes much easier to reason about:
```python
async def _listen_to_queue(
self,
conversation_id: str,
queue: asyncio.Queue,
):
while True:
try:
data = await queue.get()
except asyncio.CancelledError:
break
if self._listener_callback is None:
continue
try:
await self._listener_callback(data)
except Exception as e:
logger.error(
f"Error processing message from conversation {conversation_id}: {e}"
)
```
This:
- Removes `_queue_close_events` and the per-iteration creation/cancellation of `get_task`/`close_task`.
- Makes the lifecycle clearer: `remove_queue` → cancel task → listener exits.
- Keeps the listener-per-conversation behavior and back-queue API intact.
</issue_to_address>
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| expired_finished = [ | ||
| session_id | ||
| for session_id, finished_at in self.completed_streams.items() | ||
| if current_time - finished_at > 60 | ||
| ] |
There was a problem hiding this comment.
建议(suggestion): 对齐 pending_responses 与 completed_streams 的 TTL 处理方式,以避免它们的存活时间出现出人意料的差异。
cleanup_expired_responses 使用 max_age_seconds 来处理 pending_responses,但 completed_streams 始终使用硬编码的 60 秒进行清理:
expired_finished = [
session_id
for session_id, finished_at in self.completed_streams.items()
if current_time - finished_at > 60
]这意味着调用方修改 max_age_seconds 时只会影响 pending_responses,从 API 上并不直观。建议将 completed_streams 的 TTL 绑定到 max_age_seconds(或由其推导出的值),或者把 60 提取为具名常量/参数,使 TTL 行为更加显式且可配置。
| expired_finished = [ | |
| session_id | |
| for session_id, finished_at in self.completed_streams.items() | |
| if current_time - finished_at > 60 | |
| ] | |
| expired_finished = [ | |
| session_id | |
| for session_id, finished_at in self.completed_streams.items() | |
| if current_time - finished_at > max_age_seconds | |
| ] |
Original comment in English
suggestion: Align the TTL handling for pending_responses and completed_streams to avoid surprising lifetime differences.
cleanup_expired_responses uses max_age_seconds for pending_responses, but completed_streams is always pruned with a hard‑coded 60 seconds:
expired_finished = [
session_id
for session_id, finished_at in self.completed_streams.items()
if current_time - finished_at > 60
]This means callers changing max_age_seconds affect only pending_responses, which is not obvious from the API. Consider tying completed_streams to max_age_seconds (or a derived value) or extracting the 60 into a named constant/parameter so TTL behavior is explicit and configurable.
| expired_finished = [ | |
| session_id | |
| for session_id, finished_at in self.completed_streams.items() | |
| if current_time - finished_at > 60 | |
| ] | |
| expired_finished = [ | |
| session_id | |
| for session_id, finished_at in self.completed_streams.items() | |
| if current_time - finished_at > max_age_seconds | |
| ] |
| try: | ||
| done, pending = await asyncio.wait( | ||
| {get_task, close_task}, | ||
| return_when=asyncio.FIRST_COMPLETED, | ||
| ) | ||
| for task in pending: | ||
| task.cancel() | ||
| if close_task in done: | ||
| break | ||
| data = get_task.result() |
There was a problem hiding this comment.
建议(suggestion): 对 WeCom 监听器回调中的异常也记录 traceback 详情。
当前 _listen_to_queue 里,在回调报错时只记录了异常消息:
logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")由于这是在后台任务中运行,捕获完整的堆栈信息会让问题更易于调试。建议改为使用 logger.exception,或者 logger.error(..., exc_info=True),以便将完整 traceback 打到日志中。
Original comment in English
suggestion: Also log traceback details for WeCom listener callback failures.
Here, _listen_to_queue logs callback errors with only the exception message:
logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")Since this runs in a background task, capturing the stack trace will make failures debuggable. Please switch to logger.exception or logger.error(..., exc_info=True) so the full traceback is logged.
| """企业微信智能机器人队列管理器""" | ||
|
|
||
| def __init__(self) -> None: | ||
| def __init__(self, queue_maxsize: int = 128, back_queue_maxsize: int = 512) -> None: |
There was a problem hiding this comment.
问题(complexity): 建议重构新增的监听器与生命周期管理逻辑,以拆分职责、集中清理逻辑,并封装异步等待/取消模式,从而提升可读性与可维护性。
当前在该类中添加的监听器/生命周期逻辑比较密集。可以在保持功能不变的前提下,通过以下方式降低复杂度:
1. 分离生命周期职责
目前 remove_queues、remove_queue、cleanup_expired_responses 和 mark_finished 的逻辑交织在一起。建议将“标记完成”和“清理”分离,并把清理逻辑统一到一个地方。
def mark_stream_finished(self, session_id: str) -> None:
"""单独负责标记流结束"""
self.completed_streams[session_id] = asyncio.get_event_loop().time()
logger.debug(f"[WecomAI] 标记流已结束: {session_id}")
def _cleanup_session(self, session_id: str) -> None:
"""集中清理所有与 session 相关的状态"""
# 输入队列 + 监听
if session_id in self.queues:
del self.queues[session_id]
logger.debug(f"[WecomAI] 移除输入队列: {session_id}")
close_event = self._queue_close_events.pop(session_id, None)
if close_event is not None:
close_event.set()
task = self._listener_tasks.pop(session_id, None)
if task is not None:
task.cancel()
# 输出队列
if session_id in self.back_queues:
del self.back_queues[session_id]
logger.debug(f"[WecomAI] 移除输出队列: {session_id}")
# 待处理响应
if session_id in self.pending_responses:
del self.pending_responses[session_id]
logger.debug(f"[WecomAI] 移除待处理响应: {session_id}")然后让对外的 API 更小、更清晰:
def remove_queues(self, session_id: str, mark_finished: bool = False) -> None:
self._cleanup_session(session_id)
if mark_finished:
self.mark_stream_finished(session_id)
def remove_queue(self, session_id: str) -> None:
# 保留向后兼容,但委托给统一清理逻辑
self._cleanup_session(session_id)cleanup_expired_responses 可以调用 _cleanup_session,避免部分清理逻辑重复。
2. 封装 wait/cancel 模式
_listen_to_queue 目前在多个地方内联了一个带手动取消的 asyncio.wait 模式。建议提取到一个辅助方法中,这样取消行为更容易审计和复用:
async def _wait_queue_or_close(
self,
queue: asyncio.Queue,
close_event: asyncio.Event,
) -> dict | None:
"""返回队列数据, 或在关闭时返回 None"""
get_task = asyncio.create_task(queue.get())
close_task = asyncio.create_task(close_event.wait())
try:
done, pending = await asyncio.wait(
{get_task, close_task},
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
if close_task in done:
return None
return get_task.result()
finally:
if not get_task.done():
get_task.cancel()
if not close_task.done():
close_task.cancel()这样 _listen_to_queue 会清晰很多:
async def _listen_to_queue(
self,
session_id: str,
queue: asyncio.Queue,
close_event: asyncio.Event,
):
while True:
try:
data = await self._wait_queue_or_close(queue, close_event)
if data is None: # close_event set
break
if self._listener_callback is None:
continue
try:
await self._listener_callback(data)
except Exception as e:
logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")
except asyncio.CancelledError:
break这样既保留了原有的行为(队列或关闭二选一、取消未完成任务),又把复杂的 asyncio.wait 逻辑集中到一个地方。
3. 让监听器职责更加明确
为减少“推模式 vs 拉模式”的双重 API 混淆,可以让管理器的对外接口更小、更明确:
- 在文档中说明
set_listener是可选的;当设置后,管理器会自动为每个 session 启动监听任务。 - 可以考虑为每个 session 显式开启监听,而不是默认在队列创建时就绑定监听任务,这样可以减弱“存储 + 编排”混在一起的感觉。例如:
def enable_listener_for_session(self, session_id: str) -> None:
if self._listener_callback is None:
raise RuntimeError("Listener callback must be set before enabling listener.")
self._start_listener_if_needed(session_id)这样 get_or_create_queue 只负责创建队列,而监听编排是显式的、按需开启的,不再是隐式行为。
这些调整在保留现有功能的前提下,让生命周期边界更加清晰,也让异步编排逻辑更容易理解。
Original comment in English
issue (complexity): Consider refactoring the new listener and lifecycle management logic to separate concerns, centralize cleanup, and encapsulate the async wait/cancel pattern for better clarity and maintainability.
The added listener/lifecycle logic is quite dense in this class. You can keep the feature but reduce complexity by:
1. Separate lifecycle responsibilities
Right now remove_queues, remove_queue, cleanup_expired_responses, and mark_finished logic are mixed. Split “mark finished” from “cleanup” and unify cleanup in one place.
def mark_stream_finished(self, session_id: str) -> None:
"""单独负责标记流结束"""
self.completed_streams[session_id] = asyncio.get_event_loop().time()
logger.debug(f"[WecomAI] 标记流已结束: {session_id}")
def _cleanup_session(self, session_id: str) -> None:
"""集中清理所有与 session 相关的状态"""
# 输入队列 + 监听
if session_id in self.queues:
del self.queues[session_id]
logger.debug(f"[WecomAI] 移除输入队列: {session_id}")
close_event = self._queue_close_events.pop(session_id, None)
if close_event is not None:
close_event.set()
task = self._listener_tasks.pop(session_id, None)
if task is not None:
task.cancel()
# 输出队列
if session_id in self.back_queues:
del self.back_queues[session_id]
logger.debug(f"[WecomAI] 移除输出队列: {session_id}")
# 待处理响应
if session_id in self.pending_responses:
del self.pending_responses[session_id]
logger.debug(f"[WecomAI] 移除待处理响应: {session_id}")Then keep public APIs small and clear:
def remove_queues(self, session_id: str, mark_finished: bool = False) -> None:
self._cleanup_session(session_id)
if mark_finished:
self.mark_stream_finished(session_id)
def remove_queue(self, session_id: str) -> None:
# 保留向后兼容,但委托给统一清理逻辑
self._cleanup_session(session_id)cleanup_expired_responses can call _cleanup_session instead of partially duplicating cleanup logic.
2. Encapsulate the wait/cancel pattern
_listen_to_queue currently inlines an asyncio.wait pattern with manual cancellation in multiple places. Extract that into a helper so the cancellation behavior is easy to audit and reuse:
async def _wait_queue_or_close(
self,
queue: asyncio.Queue,
close_event: asyncio.Event,
) -> dict | None:
"""返回队列数据, 或在关闭时返回 None"""
get_task = asyncio.create_task(queue.get())
close_task = asyncio.create_task(close_event.wait())
try:
done, pending = await asyncio.wait(
{get_task, close_task},
return_when=asyncio.FIRST_COMPLETED,
)
for task in pending:
task.cancel()
if close_task in done:
return None
return get_task.result()
finally:
if not get_task.done():
get_task.cancel()
if not close_task.done():
close_task.cancel()Then _listen_to_queue becomes much easier to follow:
async def _listen_to_queue(
self,
session_id: str,
queue: asyncio.Queue,
close_event: asyncio.Event,
):
while True:
try:
data = await self._wait_queue_or_close(queue, close_event)
if data is None: # close_event set
break
if self._listener_callback is None:
continue
try:
await self._listener_callback(data)
except Exception as e:
logger.error(f"处理会话 {session_id} 消息时发生错误: {e}")
except asyncio.CancelledError:
breakThis keeps the same behavior (queue-or-close, cancel pending tasks) but localizes the tricky asyncio.wait logic into a single helper.
3. Make listener responsibility more explicit
To reduce the “dual API” confusion (push vs pull), keep the manager surface small and explicit:
- Document that
set_listeneris optional and that, when set, the manager will start per-session listener tasks automatically. - Consider requiring explicit opt-in per session, so the queue manager doesn’t always mix “storage + orchestration”. For example:
def enable_listener_for_session(self, session_id: str) -> None:
if self._listener_callback is None:
raise RuntimeError("Listener callback must be set before enabling listener.")
self._start_listener_if_needed(session_id)Then get_or_create_queue only creates queues, and listener orchestration is clearly separated and opt-in instead of implicit.
These changes keep all current functionality but make lifecycle boundaries clearer and the async orchestration logic easier to reason about.
| """Conversation ID to asyncio.Queue mapping for responses""" | ||
| self.back_queues: dict[str, asyncio.Queue] = {} | ||
| """Request ID to asyncio.Queue mapping for responses""" | ||
| self._queue_close_events: dict[str, asyncio.Event] = {} |
There was a problem hiding this comment.
问题(complexity): 建议通过移除显式的关闭事件、仅依赖任务取消和简单的队列读取循环来简化监听器生命周期管理。
你可以只依赖任务取消来简化监听编排逻辑,而不再维护 _queue_close_events 与 _listener_tasks 两套状态。这样可以去掉一整个状态维度,也不用在 _listen_to_queue 内部进行复杂的 asyncio.wait 控制,同时仍能保持原有行为。
具体建议:
- 完全移除
_queue_close_events。 - 在
get_or_create_queue中,只负责创建队列并在有回调时启动监听器。 - 在
remove_queue中,取消监听任务;此时queue.get()会被取消,循环可以在捕获CancelledError后退出。 - 将
_listen_to_queue简化为一个直接while True: data = await queue.get()的循环。
示例修改(截取相关部分):
class WebChatQueueMgr:
def __init__(self, queue_maxsize: int = 128, back_queue_maxsize: int = 512) -> None:
self.queues: dict[str, asyncio.Queue] = {}
self.back_queues: dict[str, asyncio.Queue] = {}
self._listener_tasks: dict[str, asyncio.Task] = {}
self._listener_callback: Callable[[tuple], Awaitable[None]] | None = None
self.queue_maxsize = queue_maxsize
self.back_queue_maxsize = back_queue_maxsize
def get_or_create_queue(self, conversation_id: str) -> asyncio.Queue:
if conversation_id not in self.queues:
self.queues[conversation_id] = asyncio.Queue(maxsize=self.queue_maxsize)
self._start_listener_if_needed(conversation_id)
return self.queues[conversation_id]
def remove_queue(self, conversation_id: str):
self.queues.pop(conversation_id, None)
task = self._listener_tasks.pop(conversation_id, None)
if task is not None:
task.cancel()监听器启动基本保持不变,只是不再需要 close event:
def _start_listener_if_needed(self, conversation_id: str):
if self._listener_callback is None:
return
task = self._listener_tasks.get(conversation_id)
if task is not None and not task.done():
return
queue = self.queues.get(conversation_id)
if queue is None:
return
task = asyncio.create_task(
self._listen_to_queue(conversation_id, queue),
name=f"webchat_listener_{conversation_id}",
)
self._listener_tasks[conversation_id] = task
task.add_done_callback(lambda _: self._listener_tasks.pop(conversation_id, None))
logger.debug(f"Started listener for conversation: {conversation_id}")监听循环也会更直观:
async def _listen_to_queue(
self,
conversation_id: str,
queue: asyncio.Queue,
):
while True:
try:
data = await queue.get()
except asyncio.CancelledError:
break
if self._listener_callback is None:
continue
try:
await self._listener_callback(data)
except Exception as e:
logger.error(
f"Error processing message from conversation {conversation_id}: {e}"
)这样可以:
- 去掉
_queue_close_events以及每轮循环中创建/取消get_task/close_task的逻辑; - 让生命周期更清晰:
remove_queue→ 取消任务 → 监听器退出; - 同时保留“每个会话一个监听器”和 back 队列的现有行为与 API。
Original comment in English
issue (complexity): Consider simplifying the listener lifecycle by removing the explicit close events and relying solely on task cancellation with a straightforward queue-read loop.
You can simplify the listener orchestration by relying solely on task cancellation instead of maintaining both _queue_close_events and _listener_tasks. That removes an entire axis of state and the asyncio.wait juggling inside _listen_to_queue, while preserving behavior.
Concretely:
- Drop
_queue_close_eventsentirely. - In
get_or_create_queue, just create the queue and start the listener if a callback is set. - In
remove_queue, cancel the listener task;queue.get()will be cancelled and the loop can exit onCancelledError. - Simplify
_listen_to_queueto a straightforwardwhile True: data = await queue.get()loop.
Example changes (trimmed to the relevant parts):
class WebChatQueueMgr:
def __init__(self, queue_maxsize: int = 128, back_queue_maxsize: int = 512) -> None:
self.queues: dict[str, asyncio.Queue] = {}
self.back_queues: dict[str, asyncio.Queue] = {}
self._listener_tasks: dict[str, asyncio.Task] = {}
self._listener_callback: Callable[[tuple], Awaitable[None]] | None = None
self.queue_maxsize = queue_maxsize
self.back_queue_maxsize = back_queue_maxsize
def get_or_create_queue(self, conversation_id: str) -> asyncio.Queue:
if conversation_id not in self.queues:
self.queues[conversation_id] = asyncio.Queue(maxsize=self.queue_maxsize)
self._start_listener_if_needed(conversation_id)
return self.queues[conversation_id]
def remove_queue(self, conversation_id: str):
self.queues.pop(conversation_id, None)
task = self._listener_tasks.pop(conversation_id, None)
if task is not None:
task.cancel()Listener startup stays almost the same, but no close event:
def _start_listener_if_needed(self, conversation_id: str):
if self._listener_callback is None:
return
task = self._listener_tasks.get(conversation_id)
if task is not None and not task.done():
return
queue = self.queues.get(conversation_id)
if queue is None:
return
task = asyncio.create_task(
self._listen_to_queue(conversation_id, queue),
name=f"webchat_listener_{conversation_id}",
)
self._listener_tasks[conversation_id] = task
task.add_done_callback(lambda _: self._listener_tasks.pop(conversation_id, None))
logger.debug(f"Started listener for conversation: {conversation_id}")The listener loop becomes much easier to reason about:
async def _listen_to_queue(
self,
conversation_id: str,
queue: asyncio.Queue,
):
while True:
try:
data = await queue.get()
except asyncio.CancelledError:
break
if self._listener_callback is None:
continue
try:
await self._listener_callback(data)
except Exception as e:
logger.error(
f"Error processing message from conversation {conversation_id}: {e}"
)This:
- Removes
_queue_close_eventsand the per-iteration creation/cancellation ofget_task/close_task. - Makes the lifecycle clearer:
remove_queue→ cancel task → listener exits. - Keeps the listener-per-conversation behavior and back-queue API intact.
* perf: optimize webchat and wecom ai queue lifecycle * perf: enhance webchat back queue management with conversation ID support
* feat: add bocha web search tool (#4902)
* add bocha web search tool
* Revert "add bocha web search tool"
This reverts commit 1b36d75a17b4c4751828f31f6759357cd2d4000a.
* add bocha web search tool
* fix: correct temporary_cache spelling and update supported tools for web search
* ruff
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix: messages[x] assistant content must contain at least one part (#4928)
* fix: messages[x] assistant content must contain at least one part
fixes: #4876
* ruff format
* chore: bump version to 4.14.5 (#4930)
* feat: implement feishu / lark media file handling utilities for file, audio and video processing (#4938)
* feat: implement media file handling utilities for audio and video processing
* feat: refactor file upload handling for audio and video in LarkMessageEvent
* feat: add cleanup for failed audio and video conversion outputs in media_utils
* feat: add utility methods for sending messages and uploading files in LarkMessageEvent
* fix: correct spelling of 'temporary' in SharedPreferences class
* perf: optimize webchat and wecom ai queue lifecycle (#4941)
* perf: optimize webchat and wecom ai queue lifecycle
* perf: enhance webchat back queue management with conversation ID support
* fix: localize provider source config UI (#4933)
* fix: localize provider source ui
* feat: localize provider metadata keys
* chore: add provider metadata translations
* chore: format provider i18n changes
* fix: preserve metadata fields in i18n conversion
* fix: internationalize platform config and dialog
* fix: add Weixin official account platform icon
---------
Co-authored-by: Soulter <905617992@qq.com>
* chore: bump version to 4.14.6
* feat: add provider-souce-level proxy (#4949)
* feat: 添加 Provider 级别代理支持及请求失败日志
* refactor: simplify provider source configuration structure
* refactor: move env proxy fallback logic to log_connection_failure
* refactor: update client proxy handling and add terminate method for cleanup
* refactor: update no_proxy configuration to remove redundant subnet
---------
Co-authored-by: Soulter <905617992@qq.com>
* feat(ComponentPanel): implement permission management for dashboard (#4887)
* feat(backend): add permission update api
* feat(useCommandActions): add updatePermission action and translations
* feat(dashboard): implement permission editing ui
* style: fix import sorting in command.py
* refactor(backend): extract permission update logic to service
* feat(i18n): add success and failure messages for command updates
---------
Co-authored-by: Soulter <905617992@qq.com>
* feat: 允许 LLM 预览工具返回的图片并自主决定是否发送 (#4895)
* feat: 允许 LLM 预览工具返回的图片并自主决定是否发送
* 复用 send_message_to_user 替代独立的图片发送工具
* feat: implement _HandleFunctionToolsResult class for improved tool response handling
* docs: add path handling guidelines to AGENTS.md
---------
Co-authored-by: Soulter <905617992@qq.com>
* feat(telegram): 添加媒体组(相册)支持 / add media group (album) support (#4893)
* feat(telegram): 添加媒体组(相册)支持 / add media group (album) support
## 功能说明
支持 Telegram 的媒体组消息(相册),将多张图片/视频合并为一条消息处理,而不是分散成多条消息。
## 主要改动
### 1. 初始化媒体组缓存 (__init__)
- 添加 `media_group_cache` 字典存储待处理的媒体组消息
- 使用 2.5 秒超时收集媒体组消息(基于社区最佳实践)
- 最大等待时间 10 秒(防止永久等待)
### 2. 消息处理流程 (message_handler)
- 检测 `media_group_id` 判断是否为媒体组消息
- 媒体组消息走特殊处理流程,避免分散处理
### 3. 媒体组消息缓存 (handle_media_group_message)
- 缓存收到的媒体组消息
- 使用 APScheduler 实现防抖(debounce)机制
- 每收到新消息时重置超时计时器
- 超时后触发统一处理
### 4. 媒体组合并处理 (process_media_group)
- 从缓存中取出所有媒体项
- 使用第一条消息作为基础(保留文本、回复等信息)
- 依次添加所有图片、视频、文档到消息链
- 将合并后的消息发送到处理流程
## 技术方案论证
Telegram Bot API 在处理媒体组时的设计限制:
1. 将媒体组的每个消息作为独立的 update 发送
2. 每个 update 带有相同的 `media_group_id`
3. **不提供**组的总数、结束标志或一次性完整组的机制
因此,bot 必须自行收集消息,并通过硬编码超时(timeout/delay)等待可能延迟到达的消息。
这是目前唯一可靠的方案,被官方实现、主流框架和开发者社区广泛采用。
### 官方和社区证据:
- **Telegram Bot API 服务器实现(tdlib)**:明确指出缺少结束标志或总数信息
https://github.com/tdlib/telegram-bot-api/issues/643
- **Telegram Bot API 服务器 issue**:讨论媒体组处理的不便性,推荐使用超时机制
https://github.com/tdlib/telegram-bot-api/issues/339
- **Telegraf(Node.js 框架)**:专用媒体组中间件使用 timeout 控制等待时间
https://github.com/DieTime/telegraf-media-group
- **StackOverflow 讨论**:无法一次性获取媒体组所有文件,必须手动收集
https://stackoverflow.com/questions/50180048/telegram-api-get-all-uploaded-photos-by-media-group-id
- **python-telegram-bot 社区**:确认媒体组消息单独到达,需手动处理
https://github.com/python-telegram-bot/python-telegram-bot/discussions/3143
- **Telegram Bot API 官方文档**:仅定义 `media_group_id` 为可选字段,不提供获取完整组的接口
https://core.telegram.org/bots/api#message
## 实现细节
- 使用 2.5 秒超时收集媒体组消息(基于社区最佳实践)
- 最大等待时间 10 秒(防止永久等待)
- 采用防抖(debounce)机制:每收到新消息重置计时器
- 利用 APScheduler 实现延迟处理和任务调度
## 测试验证
- ✅ 发送 5 张图片相册,成功合并为一条消息
- ✅ 保留原始文本说明和回复信息
- ✅ 支持图片、视频、文档混合的媒体组
- ✅ 日志显示 Processing media group <media_group_id> with 5 items
## 代码变更
- 文件:astrbot/core/platform/sources/telegram/tg_adapter.py
- 新增代码:124 行
- 新增方法:handle_media_group_message(), process_media_group()
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* refactor(telegram): 优化媒体组处理性能和可靠性
根据代码审查反馈改进:
1. 实现 media_group_max_wait 防止无限延迟
- 跟踪媒体组创建时间,超过最大等待时间立即处理
- 最坏情况下 10 秒内必定处理,防止消息持续到达导致无限延迟
2. 移除手动 job 查找优化性能
- 删除 O(N) 的 get_jobs() 循环扫描
- 依赖 replace_existing=True 自动替换任务
3. 重用 convert_message 减少代码重复
- 统一所有媒体类型转换逻辑
- 未来添加新媒体类型只需修改一处
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(telegram): handle missing message in media group processing and improve logging messages
---------
Co-authored-by: Ubuntu <ubuntu@localhost.localdomain>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Soulter <905617992@qq.com>
* feat: add welcome feature with localized content and onboarding steps
* fix: correct height attribute to max-height for dialog component
* feat: supports electron app (#4952)
* feat: add desktop wrapper with frontend-only packaging
* docs: add desktop build docs and track dashboard lockfile
* fix: track desktop lockfile for npm ci
* fix: allow custom install directory for windows installer
* chore: migrate desktop workflow to pnpm
* fix(desktop): build AppImage only on Linux
* fix(desktop): harden packaged startup and backend bundling
* fix(desktop): adapt packaged restart and plugin dependency flow
* fix(desktop): prevent backend respawn race on quit
* fix(desktop): prefer pyproject version for desktop packaging
* fix(desktop): improve startup loading UX and reduce flicker
* ci: add desktop multi-platform release workflow
* ci: fix desktop release build and mac runner labels
* ci: disable electron-builder auto publish in desktop build
* ci: avoid electron-builder publish path in build matrix
* ci: normalize desktop release artifact names
* ci: exclude blockmap files from desktop release assets
* ci: prefix desktop release assets with AstrBot and purge blockmaps
* feat: add electron bridge types and expose backend control methods in preload script
* Update startup screen assets and styles
- Changed the icon from PNG to SVG format for better scalability.
- Updated the border color from #d0d0d0 to #eeeeee for a softer appearance.
- Adjusted the width of the startup screen from 460px to 360px for improved responsiveness.
* Update .gitignore to include package.json
* chore: remove desktop gitkeep ignore exceptions
* docs: update desktop troubleshooting for current runtime behavior
* refactor(desktop): modularize runtime and harden startup flow
---------
Co-authored-by: Soulter <905617992@qq.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: dedupe preset messages (#4961)
* feat: enhance package.json with resource filters and compression settings
* chore: update Python version requirements to 3.12 (#4963)
* chore: bump version to 4.14.7
* feat: refactor release workflow and add special update handling for electron app (#4969)
* chore: bump version to 4.14.8 and bump faiss-cpu version up to date
* chore: auto ann fix by ruff (#4903)
* chore: auto fix by ruff
* refactor: 统一修正返回类型注解为 None/bool 以匹配实现
* refactor: 将 _get_next_page 改为异步并移除多余的请求错误抛出
* refactor: 将 get_client 的返回类型改为 object
* style: 为 LarkMessageEvent 的相关方法添加返回类型注解 None
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: prepare OpenSSL via vcpkg for Windows ARM64
* ci: change ghcr namespace
* chore: update pydantic dependency version (#4980)
* feat: add delete button to persona management dialog (#4978)
* Initial plan
* feat: add delete button to persona management dialog
- Added delete button to PersonaForm dialog (only visible when editing)
- Implemented deletePersona method with confirmation dialog
- Connected delete event to PersonaManager for proper handling
- Button positioned on left side of dialog actions for clear separation
- Uses existing i18n translations for delete button and messages
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: use finally block to ensure saving state is reset
- Moved `this.saving = false` to finally block in deletePersona
- Ensures UI doesn't stay in saving state after errors
- Follows best practices for state management
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* feat: enhance Dingtalk adapter with active push message and image, video, audio message type (#4986)
* fix: handle pip install execution in frozen runtime (#4985)
* fix: handle pip install execution in frozen runtime
* fix: harden pip subprocess fallback handling
* fix: collect certifi data in desktop backend build (#4995)
* feat: 企业微信应用 支持主动消息推送,并优化企微应用、微信公众号、微信客服音频相关的处理 (#4998)
* feat: 企业微信智能机器人支持主动消息推送以及发送视频、文件等消息类型支持 (#4999)
* feat: enhance WecomAIBotAdapter and WecomAIBotMessageEvent for improved streaming message handling (#5000)
fixes: #3965
* feat: enhance persona tool management and update UI localization for subagent orchestration (#4990)
* feat: enhance persona tool management and update UI localization for subagent orchestration
* fix: remove debug logging for final ProviderRequest in build_main_agent function
* perf: 稳定源码与 Electron 打包环境下的 pip 安装行为,并修复非 Electron 环境下点击 WebUI 更新按钮时出现跳转对话框的问题 (#4996)
* fix: handle pip install execution in frozen runtime
* fix: harden pip subprocess fallback handling
* fix: scope global data root to packaged electron runtime
* refactor: inline frozen runtime check for electron guard
* fix: prefer current interpreter for source pip installs
* fix: avoid resolving venv python symlink for pip
* refactor: share runtime environment detection utilities
* fix: improve error message when pip module is unavailable
* fix: raise ImportError when pip module is unavailable
* fix: preserve ImportError semantics for missing pip
* fix: 修复非electron app环境更新时仍然显示electron更新对话框的问题
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix: 'HandoffTool' object has no attribute 'agent' (#5005)
* fix: 移动agent的位置到super().__init__之后
* add: 添加一行注释
* chore(deps): bump the github-actions group with 2 updates (#5006)
Bumps the github-actions group with 2 updates: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [actions/download-artifact](https://github.com/actions/download-artifact).
Updates `astral-sh/setup-uv` from 6 to 7
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v6...v7)
Updates `actions/download-artifact` from 6 to 7
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)
---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: actions/download-artifact
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>
* fix: stabilize packaged runtime pip/ssl behavior and mac font fallback (#5007)
* fix: patch pip distlib finder for frozen electron runtime
* fix: use certifi CA bundle for runtime SSL requests
* fix: configure certifi CA before core imports
* fix: improve mac font fallback for dashboard text
* fix: harden frozen pip patch and unify TLS connector
* refactor: centralize dashboard CJK font fallback stacks
* perf: reuse TLS context and avoid repeated frozen pip patch
* refactor: bootstrap TLS setup before core imports
* fix: use async confirm dialog for provider deletions
* fix: replace native confirm dialogs in dashboard
- Add shared confirm helper in dashboard/src/utils/confirmDialog.ts for async dialog usage with safe fallback.
- Migrate provider, chat, config, session, platform, persona, MCP, backup, and knowledge-base delete/close confirmations to use the shared helper.
- Remove scattered inline confirm handling to keep behavior consistent and avoid native blocking dialog focus/caret issues in Electron.
* fix: capture runtime bootstrap logs after logger init
- Add bootstrap record buffer in runtime_bootstrap for early TLS patch logs before logger is ready.
- Flush buffered bootstrap logs to astrbot logger at process startup in main.py.
- Include concrete exception details for TLS bootstrap failures to improve diagnosis.
* fix: harden runtime bootstrap and unify confirm handling
- Simplify bootstrap log buffering and add a public initialize hook for non-main startup paths.
- Guard aiohttp TLS patching with feature/type checks and keep graceful fallback when internals are unavailable.
- Standardize dashboard confirmation flow via shared confirm helpers across composition and options API components.
* refactor: simplify runtime tls bootstrap and tighten confirm typing
* refactor: align ssl helper namespace and confirm usage
* fix: 修复 Windows 打包版后端重启失败问题 (#5009)
* fix: patch pip distlib finder for frozen electron runtime
* fix: use certifi CA bundle for runtime SSL requests
* fix: configure certifi CA before core imports
* fix: improve mac font fallback for dashboard text
* fix: harden frozen pip patch and unify TLS connector
* refactor: centralize dashboard CJK font fallback stacks
* perf: reuse TLS context and avoid repeated frozen pip patch
* refactor: bootstrap TLS setup before core imports
* fix: use async confirm dialog for provider deletions
* fix: replace native confirm dialogs in dashboard
- Add shared confirm helper in dashboard/src/utils/confirmDialog.ts for async dialog usage with safe fallback.
- Migrate provider, chat, config, session, platform, persona, MCP, backup, and knowledge-base delete/close confirmations to use the shared helper.
- Remove scattered inline confirm handling to keep behavior consistent and avoid native blocking dialog focus/caret issues in Electron.
* fix: capture runtime bootstrap logs after logger init
- Add bootstrap record buffer in runtime_bootstrap for early TLS patch logs before logger is ready.
- Flush buffered bootstrap logs to astrbot logger at process startup in main.py.
- Include concrete exception details for TLS bootstrap failures to improve diagnosis.
* fix: harden runtime bootstrap and unify confirm handling
- Simplify bootstrap log buffering and add a public initialize hook for non-main startup paths.
- Guard aiohttp TLS patching with feature/type checks and keep graceful fallback when internals are unavailable.
- Standardize dashboard confirmation flow via shared confirm helpers across composition and options API components.
* refactor: simplify runtime tls bootstrap and tighten confirm typing
* refactor: align ssl helper namespace and confirm usage
* fix: avoid frozen restart crash from multiprocessing import
* fix: include missing frozen dependencies for windows backend
* fix: use execv for stable backend reboot args
* Revert "fix: use execv for stable backend reboot args"
This reverts commit 9cc27becffeba0e117fea26aa5c2e1fe7afc6e36.
* Revert "fix: include missing frozen dependencies for windows backend"
This reverts commit 52554bea1fa61045451600c64447b7bf38cf6c92.
* Revert "fix: avoid frozen restart crash from multiprocessing import"
This reverts commit 10548645b0ba1e19b64194878ece478a48067959.
* fix: reset pyinstaller onefile env before reboot
* fix: unify electron restart path and tray-exit backend cleanup
* fix: stabilize desktop restart detection and frozen reboot args
* fix: make dashboard restart wait detection robust
* fix: revert dashboard restart waiting interaction tweaks
* fix: pass auth token for desktop graceful restart
* fix: avoid false failure during graceful restart wait
* fix: start restart waiting before electron restart call
* fix: harden restart waiting and reboot arg parsing
* fix: parse start_time as numeric timestamp
* fix: 修复app内重启异常,修复app内点击重启不能立刻提示重启,以及在后端就绪时及时刷新界面的问题 (#5013)
* fix: patch pip distlib finder for frozen electron runtime
* fix: use certifi CA bundle for runtime SSL requests
* fix: configure certifi CA before core imports
* fix: improve mac font fallback for dashboard text
* fix: harden frozen pip patch and unify TLS connector
* refactor: centralize dashboard CJK font fallback stacks
* perf: reuse TLS context and avoid repeated frozen pip patch
* refactor: bootstrap TLS setup before core imports
* fix: use async confirm dialog for provider deletions
* fix: replace native confirm dialogs in dashboard
- Add shared confirm helper in dashboard/src/utils/confirmDialog.ts for async dialog usage with safe fallback.
- Migrate provider, chat, config, session, platform, persona, MCP, backup, and knowledge-base delete/close confirmations to use the shared helper.
- Remove scattered inline confirm handling to keep behavior consistent and avoid native blocking dialog focus/caret issues in Electron.
* fix: capture runtime bootstrap logs after logger init
- Add bootstrap record buffer in runtime_bootstrap for early TLS patch logs before logger is ready.
- Flush buffered bootstrap logs to astrbot logger at process startup in main.py.
- Include concrete exception details for TLS bootstrap failures to improve diagnosis.
* fix: harden runtime bootstrap and unify confirm handling
- Simplify bootstrap log buffering and add a public initialize hook for non-main startup paths.
- Guard aiohttp TLS patching with feature/type checks and keep graceful fallback when internals are unavailable.
- Standardize dashboard confirmation flow via shared confirm helpers across composition and options API components.
* refactor: simplify runtime tls bootstrap and tighten confirm typing
* refactor: align ssl helper namespace and confirm usage
* fix: avoid frozen restart crash from multiprocessing import
* fix: include missing frozen dependencies for windows backend
* fix: use execv for stable backend reboot args
* Revert "fix: use execv for stable backend reboot args"
This reverts commit 9cc27becffeba0e117fea26aa5c2e1fe7afc6e36.
* Revert "fix: include missing frozen dependencies for windows backend"
This reverts commit 52554bea1fa61045451600c64447b7bf38cf6c92.
* Revert "fix: avoid frozen restart crash from multiprocessing import"
This reverts commit 10548645b0ba1e19b64194878ece478a48067959.
* fix: reset pyinstaller onefile env before reboot
* fix: unify electron restart path and tray-exit backend cleanup
* fix: stabilize desktop restart detection and frozen reboot args
* fix: make dashboard restart wait detection robust
* fix: revert dashboard restart waiting interaction tweaks
* fix: pass auth token for desktop graceful restart
* fix: avoid false failure during graceful restart wait
* fix: start restart waiting before electron restart call
* fix: harden restart waiting and reboot arg parsing
* fix: parse start_time as numeric timestamp
* fix: preserve windows frozen reboot argv quoting
* fix: align restart waiting with electron restart timing
* fix: tighten graceful restart and unmanaged kill safety
* chore: bump version to 4.15.0 (#5003)
* fix: add reminder for v4.14.8 users regarding manual redeployment due to a bug
* fix: harden plugin dependency loading in frozen app runtime (#5015)
* fix: compare plugin versions semantically in market updates
* fix: prioritize plugin site-packages for in-process pip
* fix: reload starlette from plugin target site-packages
* fix: harden plugin dependency import precedence in frozen runtime
* fix: improve plugin dependency conflict handling
* refactor: simplify plugin conflict checks and version utils
* fix: expand transitive plugin dependencies for conflict checks
* fix: recover conflicting plugin dependencies during module prefer
* fix: reuse renderer restart flow for tray backend restart
* fix: add recoverable plugin dependency conflict handling
* revert: remove plugin version comparison changes
* fix: add missing tray restart backend labels
* feat: adding support for media and quoted message attachments for feishu (#5018)
* docs: add AUR installation method (#4879)
* docs: sync system package manager installation instructions to all languages
* Update README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update README.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* fix/typo
* refactor: update system package manager installation instructions for Arch Linux across multiple language README files
* feat: add installation command for AstrBot in multiple language README files
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
Co-authored-by: Soulter <905617992@qq.com>
* fix(desktop): 为 Electron 与后端日志增加按大小轮转 (#5029)
* fix(desktop): rotate electron and backend logs
* refactor(desktop): centralize log rotation defaults and debug fs errors
* fix(desktop): harden rotation fs ops and buffer backend log writes
* refactor(desktop): extract buffered logger and reduce sync stat calls
* refactor(desktop): simplify rotation flow and harden logger config
* fix(desktop): make app logging async and flush-safe
* fix: harden app log path switching and debug-gated rotation errors
* fix: cap buffered log chunk size during path switch
* feat: add first notice feature with multilingual support and UI integration
* fix: 提升打包版桌面端启动稳定性并优化插件依赖处理 (#5031)
* fix(desktop): rotate electron and backend logs
* refactor(desktop): centralize log rotation defaults and debug fs errors
* fix(desktop): harden rotation fs ops and buffer backend log writes
* refactor(desktop): extract buffered logger and reduce sync stat calls
* refactor(desktop): simplify rotation flow and harden logger config
* fix(desktop): make app logging async and flush-safe
* fix: harden app log path switching and debug-gated rotation errors
* fix: cap buffered log chunk size during path switch
* fix: avoid redundant plugin reinstall and upgrade electron
* fix: stop webchat tasks cleanly and bind packaged backend to localhost
* fix: unify platform shutdown and await webchat listener cleanup
* fix: improve startup logs for dashboard and onebot listeners
* fix: revert extra startup service logs
* fix: harden plugin import recovery and webchat listener cleanup
* fix: pin dashboard ci node version to 24.13.0
* fix: avoid duplicate webchat listener cleanup on terminate
* refactor: clarify platform task lifecycle management
* fix: continue platform shutdown when terminate fails
* feat: temporary file handling and introduce TempDirCleaner (#5026)
* feat: temporary file handling and introduce TempDirCleaner
- Updated various modules to use `get_astrbot_temp_path()` instead of `get_astrbot_data_path()` for temporary file storage.
- Renamed temporary files for better identification and organization.
- Introduced `TempDirCleaner` to manage the size of the temporary directory, ensuring it does not exceed a specified limit by deleting the oldest files.
- Added configuration option for maximum temporary directory size in the dashboard.
- Implemented tests for `TempDirCleaner` to verify cleanup functionality and size management.
* ruff
* fix: close unawaited reset coroutine on early return (#5033)
When an OnLLMRequestEvent hook stops event propagation, the
reset_coro created by build_main_agent was never awaited, causing
a RuntimeWarning. Close the coroutine explicitly before returning.
Fixes #5032
Co-authored-by: Limitless2023 <limitless@users.noreply.github.com>
* fix: update error logging message for connection failures
* docs: clean and sync README (#5014)
* fix: close missing div in README
* fix: sync README_zh-TW with README
* fix: sync README
* fix: correct typo
correct url in README_en README_fr README_ru
* docs: sync README_en with README
* Update README_en.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: provider extra param dialog key display error
* chore: ruff format
* feat: add send_chat_action for Telegram platform adapter (#5037)
* feat: add send_chat_action for Telegram platform adapter
Add typing/upload indicator when sending messages via Telegram.
- Added _send_chat_action helper method for sending chat actions
- Send appropriate action (typing, upload_photo, upload_document, upload_voice)
before sending different message types
- Support streaming mode with typing indicator
- Support supergroup with message_thread_id
* refactor(telegram): extract chat action helpers and add throttling
- Add ACTION_BY_TYPE mapping for message type to action priority
- Add _get_chat_action_for_chain() to determine action from message chain
- Add _send_media_with_action() for upload → send → restore typing pattern
- Add _ensure_typing() helper for typing status
- Add chat action throttling (0.5s) in streaming mode to avoid rate limits
- Update type annotation to ChatAction | str for better static checking
* feat(telegram): implement send_typing method for Telegram platform
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix: 修复更新日志、官方文档弹窗双滚动条问题 (#5060)
* docs: sync and fix readme typo (#5055)
* docs: fix index typo
* docs: fix typo in README_en.md
- 移除英文README中意外出现的俄语,并替换为英语
* docs: fix html typo
- remove unused '</p>'
* docs: sync table with README
* docs: sync README header format
- keep the README header format consistent
* doc: sync key features
* style: format files
- Fix formatting issues from previous PR
* fix: correct md anchor link
* docs: correct typo in README_fr.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* docs: correct typo in README_zh-TW.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* fix: 修复备份时缺失的人格文件夹映射 (#5042)
* feat: QQ 官方机器人平台支持主动推送消息、私聊场景下支持接收文件 (#5066)
* feat: QQ 官方机器人平台支持主动推送消息、私聊场景下支持接收文件
* feat: enhance QQOfficialWebhook to remember session scenes for group, channel, and friend messages
* perf: 优化分段回复间隔时间的初始化逻辑 (#5068)
fixes: #5059
* fix: chunk err when using openrouter deepseek (#5069)
* feat: add i18n supports for custom platform adapters (#5045)
* Feat: 为插件提供的适配器的元数据&i18n提供数据通路
* chore: update docstrings with pull request references
Added references to pull request 5045 in docstrings.
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: 完善转发引用解析与图片回退并支持配置化控制 (#5054)
* feat: support fallback image parsing for quoted messages
* fix: fallback parse quoted images when reply chain has placeholders
* style: format network utils with ruff
* test: expand quoted parser coverage and improve fallback diagnostics
* fix: fallback to text-only retry when image requests fail
* fix: tighten image fallback and resolve nested quoted forwards
* refactor: simplify quoted message extraction and dedupe images
* fix: harden quoted parsing and openai error candidates
* fix: harden quoted image ref normalization
* refactor: organize quoted parser settings and logging
* fix: cap quoted fallback images and avoid retry loops
* refactor: split quoted message parser into focused modules
* refactor: share onebot segment parsing logic
* refactor: unify quoted message parsing flow
* feat: move quoted parser tuning to provider settings
* fix: add missing i18n metadata for quoted parser settings
* chore: refine forwarded message setting labels
* fix: add config tabs and routing for normal and system configurations
* chore: bump version to 4.16.0 (#5074)
* feat: add LINE platform support with adapter and configuration (#5085)
* fix-correct-FIRST_NOTICE.md-locale-path-resolution (#5083) (#5082)
* fix:修改配置文件目录
* fix:添加备选的FIRST_NOTICE.zh-CN.md用于兼容
* fix: remove unnecessary frozen flag from requirements export in Dockerfile
fixes: #5089
* fix #5089: add uv lock step in Dockerfile before export (#5091)
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* feat: support hot reload after plugin load failure (#5043)
* add :Support hot reload after plugin load failure
* Apply suggestions from code review
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* fix:reformat code
* fix:reformat code
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* feat: add fallback chat model chain in tool loop runner (#5109)
* feat: implement fallback provider support for chat models and update configuration
* feat: enhance provider selection display with count and chips for selected providers
* feat: update fallback chat providers to use provider settings and add warning for non-list fallback models
* feat: add Afdian support card to resources section in WelcomePage
* feat: replace colorlog with loguru for enhanced logging support (#5115)
* feat: add SSL configuration options for WebUI and update related logging (#5117)
* chore: bump version to 4.17.0
* fix: handle list format content from OpenAI-compatible APIs (#5128)
* fix: handle list format content from OpenAI-compatible APIs
Some LLM providers (e.g., GLM-4.5V via SiliconFlow) return content as
list[dict] format like [{'type': 'text', 'text': '...'}] instead of
plain string. This causes the raw list representation to be displayed
to users.
Changes:
- Add _normalize_content() helper to extract text from various content formats
- Use json.loads instead of ast.literal_eval for safer parsing
- Add size limit check (8KB) before attempting JSON parsing
- Only convert lists that match OpenAI content-part schema (has 'type': 'text')
to avoid collapsing legitimate list-literal replies like ['foo', 'bar']
- Add strip parameter to preserve whitespace in streaming chunks
- Clean up orphan </think> tags that may leak from some models
Fixes #5124
* fix: improve content normalization safety
- Try json.loads first, fallback to ast.literal_eval for single-quoted
Python literals to avoid corrupting apostrophes (e.g., "don't")
- Coerce text values to str to handle null or non-string text fields
* fix: update retention logic in LogManager to handle backup count correctly
* chore: bump version to 4.17.1
* docs: Added instructions for deploying AstrBot using AstrBot Launcher. (#5136)
Added instructions for deploying AstrBot using AstrBot Launcher.
* fix: add MCP tools to function tool set in _plugin_tool_fix (#5144)
* fix: add support for collecting data from builtin stars in electron pyinstaller build (#5145)
* chore: bump version to 4.17.1
* chore: ruff format
* fix: prevent updates for AstrBot launched via launcher
* fix(desktop): include runtime deps for builtin plugins in backend build (#5146)
* fix: 'Plain' object has no attribute 'text' when using python 3.14 (#5154)
* fix: enhance plugin metadata handling by injecting attributes before instantiation (#5155)
* fix: enhance handle_result to support event context and webchat image sending
* chore: bump version to 4.17.3
* chore: ruff format
* feat: add NVIDIA provider template (#5157)
fixes: #5156
* feat: enhance provider sources panel with styled menu and mobile support
* fix: improve permission denied message for local execution in Python and shell tools
* feat: enhance PersonaForm component with responsive design and improved styling (#5162)
fix: #5159
* ui(CronJobPage): fix action column buttons overlapping in CronJobPage (#5163)
- 修改前:操作列容器仅使用 `d-flex`,在页面宽度变窄时,子元素(开关和删除按钮)会因为宽度挤压而发生视觉重叠,甚至堆叠在一起。
- 修改后:
1. 为容器添加了 `flex-nowrap`,强制禁止子元素换行。
2. 设置了 `min-width: 140px`,确保该列拥有固定的保护空间,防止被其他长文本列挤压。
3. 增加了 `gap: 12px` 间距,提升了操作辨识度并优化了点击体验。
* feat: add unsaved changes notice to configuration page and update messages
* feat: implement search functionality in configuration components and update UI (#5168)
* feat: add FAQ link to vertical sidebar and update navigation for localization
* feat: add announcement section to WelcomePage and localize announcement title
* chore: bump version to 4.17.4
* feat: supports send markdown message in qqofficial (#5173)
* feat: supports send markdown message in qqofficial
closes: #1093 #918 #4180 #4264
* ruff format
* fix: prevent duplicate error message when all LLM providers fail (#5183)
* fix: 修复选择配置文件进入配置文件管理弹窗直接关闭弹窗显示的配置文件不正确 (#5174)
* feat: add MarketPluginCard component and integrate random plugin feature in ExtensionPage (#5190)
* feat: add MarketPluginCard component and integrate random plugin feature in ExtensionPage
* feat: update random plugin selection logic to use pluginMarketData and refresh on relevant events
* feat: supports aihubmix
* docs: update readme
* chore: ruff format
* feat: add LINE support to multiple language README files
* feat(core): add plugin error hook for custom error routing (#5192)
* feat(core): add plugin error hook for custom error routing
* fix(core): align plugin error suppression with event stop state
* refactor: extract Voice_messages_forbidden fallback into shared helper with typed BadRequest exception (#5204)
- Add _send_voice_with_fallback helper to deduplicate voice forbidden handling
- Catch telegram.error.BadRequest instead of bare Exception with string matching
- Add text field to Record component to preserve TTS source text
- Store original text in Record during TTS conversion for use as document caption
- Skip _send_chat_action when chat_id is empty to avoid unnecessary warnings
* chore: bump version to 4.17.5
* feat: add admin permission checks for Python and Shell execution (#5214)
* fix: 改进微信公众号被动回复处理机制,引入缓冲与分片回复,并优化超时行为 (#5224)
* 修复wechat official 被动回复功能
* ruff format
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix: 修复仅发送 JSON 消息段时的空消息回复报错 (#5208)
* Fix Register_Stage
· 补全 JSON 消息判断,修复发送 JSON 消息时遇到 “消息为空,跳过发送阶段” 的问题。
· 顺带补全其它消息类型判断。
Co-authored-by: Pizero <zhaory200707@outlook.com>
* Fix formatting and comments in stage.py
* Format stage.py
---------
Co-authored-by: Pizero <zhaory200707@outlook.com>
* docs: update related repo links
* fix(core): terminate active events on reset/new/del to prevent stale responses (#5225)
* fix(core): terminate active events on reset/new/del to prevent stale responses
Closes #5222
* style: fix import sorting in scheduler.py
* chore: remove Electron desktop pipeline and switch to tauri repo (#5226)
* ci: remove Electron desktop build from release pipeline
* chore: remove electron desktop and switch to tauri release trigger
* ci: remove desktop workflow dispatch trigger
* refactor: migrate data paths to astrbot_path helpers
* fix: point desktop update prompt to AstrBot-desktop releases
* fix: update feature request template for clarity and consistency in English and Chinese
* Feat/config leave confirm (#5249)
* feat: 配置文件增加未保存提示弹窗
* fix: 移除unsavedChangesDialog插件使用组件方式实现弹窗
* feat: add support for plugin astrbot-version and platform requirement checks (#5235)
* feat: add support for plugin astrbot-version and platform requirement checks
* fix: remove unsupported platform and version constraints from metadata.yaml
* fix: remove restriction on 'v' in astrbot_version specification format
* ruff format
* feat: add password confirmation when changing password (#5247)
* feat: add password confirmation when changing password
Fixes #5177
Adds a password confirmation field to prevent accidental password typos.
Changes:
- Backend: validate confirm_password matches new_password
- Frontend: add confirmation input with validation
- i18n: add labels and error messages for password mismatch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(auth): improve error message for password confirmation mismatch
* fix(auth): update password hashing logic and improve confirmation validation
---------
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(provider): 修复 dict 格式 content 导致的 JSON 残留问题 (#5250)
* fix(provider): 修复 dict 格式 content 导致的 JSON 残留问题
修复 _normalize_content 函数未处理 dict 类型 content 的问题。
当 LLM 返回 {"type": "text", "text": "..."} 格式的 content 时,
现在会正确提取 text 字段而非直接转为字符串。
同时改进 fallback 行为,对 None 值返回空字符串。
Fixes #5244
* Update warning message for unexpected dict format
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* chore: remove outdated heihe.md documentation file
* fix: all mcp tools exposed to main agent (#5252)
* fix: enhance PersonaForm layout and improve tool selection display
* fix: update tool status display and add localization for inactive tools
* fix: remove additionalProperties from tool schema properties (#5253)
fixes: #5217
* fix: simplify error messages for account edit validation
* fix: streamline error response for empty new username and password in account edit
* chore: bump vertion to 4.17.6
* feat: add OpenRouter provider support and icon
* chore: ruff format
* refactor(dashboard): replace legacy isElectron bridge fields with isDesktop (#5269)
* refactor dashboard desktop bridge fields from isElectron to isDesktop
* refactor dashboard runtime detection into shared helper
* fix: update contributor avatar image URL to include max size and columns (#5268)
* feat: astrbot http api (#5280)
* feat: astrbot http api
* Potential fix for code scanning alert no. 34: Use of a broken or weak cryptographic hashing algorithm on sensitive data
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* fix: improve error handling for missing attachment path in file upload
* feat: implement paginated retrieval of platform sessions for creators
* feat: refactor attachment directory handling in ChatRoute
* feat: update API endpoint paths for file and message handling
* feat: add documentation link to API key management section in settings
* feat: update API key scopes and related configurations in API routes and tests
* feat: enhance API key expiration options and add warning for permanent keys
* feat: add UTC normalization and serialization for API key timestamps
* feat: implement chat session management and validation for usernames
* feat: ignore session_id type chunks in message processing
---------
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* feat(dashboard): improve plugin platform support display and mobile accessibility (#5271)
* feat(dashboard): improve plugin platform support display and mobile accessibility
- Replace hover-based tooltips with interactive click menus for platform support information.
- Fix mobile touch issues by introducing explicit state control for status capsules.
- Enhance UI aesthetics with platform-specific icons and a structured vertical list layout.
- Add dynamic chevron icons to provide clear visual cues for expandable content.
* refactor(dashboard): refactor market card with computed properties for performance
* refactor(dashboard): unify plugin platform support UI with new reusable chip component
- Create shared 'PluginPlatformChip' component to encapsulate platform meta display.
- Fix mobile interaction bugs by simplifying menu triggers and event handling.
- Add stacked platform icon previews and dynamic chevron indicators within capsules.
- Improve information hierarchy using structured vertical lists for platform details.
- Optimize rendering efficiency with computed properties across both card views.
* fix: qq official guild message send error (#5287)
* fix: qq official guild message send error
* Update astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* 更新readme文档,补充桌面app说明,并向前移动位置 (#5297)
* docs: update desktop deployment section in README
* docs: refine desktop and launcher deployment descriptions
* Update README.md
* feat: add Anthropic Claude Code OAuth provider and adaptive thinking support (#5209)
* feat: add Anthropic Claude Code OAuth provider and adaptive thinking support
* fix: add defensive guard for metadata overrides and align budget condition with docs
* refactor: adopt sourcery-ai suggestions for OAuth provider
- Use use_api_key=False in OAuth subclass to avoid redundant
API-key client construction before replacing with auth_token client
- Generalize metadata override helper to merge all dict keys
instead of only handling 'limit', improving extensibility
* Feat/telegram command alias register #5233 (#5234)
* feat: support registering command aliases for Telegram
Now when registering commands with aliases, all aliases will be
registered as Telegram bot commands in addition to the main command.
Example:
@register_command(command_name="draw", alias={"画", "gen"})
Now /draw, /画, and /gen will all appear in the Telegram command menu.
* feat(telegram): add duplicate command name warning when registering commands
Log a warning when duplicate command names are detected during Telegram
command registration to help identify configuration conflicts.
* refactor: remove Anthropic OAuth provider implementation and related metadata overrides
* fix: 修复新建对话时因缺少会话ID导致配置绑定失败的问题 (#5292)
* fix:尝试修改
* fix:添加详细日志
* fix:进行详细修改,并添加日志
* fix:删除所有日志
* fix: 增加安全访问函数
- 给 localStorage 访问加了 try/catch + 可用性判断:dashboard/src/utils/chatConfigBinding.ts:13
- 新增 getFromLocalStorage/setToLocalStorage(在受限存储/无痕模式下异常时回退/忽略)
- getStoredDashboardUsername() / getStoredSelectedChatConfigId() 改为走安全读取:dashboard/src/utils/chatConfigBinding.ts:36 - 新增 setStoredSelectedChatConfigId(),写入失败静默忽略:dashboard/src/utils/chatConfigBinding.ts:44
- 把 ConfigSelector.vue 里直接 localStorage.getItem/setItem 全部替换为上述安全方法:dashboard/src/components/chat/ConfigSelector.vue:81
- 已重新跑过 pnpm run typecheck,通过。
* rm:删除个人用的文档文件
* Revert "rm:删除个人用的文档文件"
This reverts commit 0fceee05434cfbcb11e45bb967a77d5fa93196bf.
* rm:删除个人用的文档文件
* rm:删除个人用的文档文件
* chore: bump version to 4.18.0
* fix(SubAgentPage): 当中间的介绍文本非常长时,Flex 布局会自动挤压右侧的控制按钮区域 (#5306)
* fix: 修复新版本插件市场出现插件显示为空白的 bug;纠正已安装插件卡片的排版,统一大小 (#5309)
* fix(ExtensionCard): 解决插件卡片大小不统一的问题
* fix(MarketPluginCard): 解决插件市场不加载插件的问题 (#5303)
* feat: supports spawn subagent as a background task that not block the main agent workflow (#5081)
* feat:为subagent添加后台任务参数
* ruff
* fix: update terminology from 'handoff mission' to 'background task' and refactor related logic
* fix: update terminology from 'background_mission' to 'background_task' in HandoffTool and related logic
* fix(HandoffTool): update background_task description for clarity on usage
---------
Co-authored-by: Soulter <905617992@qq.com>
* cho
* fix: 修复 aiohttp 版本过新导致 qq-botpy 报错的问题 (#5316)
* chore: ruff format
* fix: remove hard-coded 6s timeout from tavily request
* fix: remove changelogs directory from .dockerignore
* feat(dashboard): make release redirect base URL configurable (#5330)
* feat(dashboard): make desktop release base URL configurable
* refactor(dashboard): use generic release base URL env with upstream default
* fix(dashboard): guard release base URL normalization when env is unset
* refactor(dashboard): use generic release URL helpers and avoid latest suffix duplication
* feat: add stop functionality for active agent sessions and improve handling of stop requests (#5380)
* feat: add stop functionality for active agent sessions and improve handling of stop requests
* feat: update stop button icon and tooltip in ChatInput component
* fix: correct indentation in tool call handling within ChatRoute class
* fix: chatui cannot persist file segment (#5386)
* fix(plugin): update plugin directory handling for reserved plugins (#5369)
* fix(plugin): update plugin directory handling for reserved plugins
* fix(plugin): add warning logs for missing plugin name, object, directory, and changelog
* chore(README): updated with README.md (#5375)
* chore(README): updated with README.md
* Update README_fr.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* Update README_zh-TW.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* feat: add image urls / paths supports for subagent (#5348)
* fix: 修复5081号PR在子代理执行后台任务时,未正确使用系统配置的流式/非流请求的问题(#5081)
* feat:为子代理增加远程图片URL参数支持
* fix: update description for image_urls parameter in HandoffTool to clarify usage in multimodal tasks
* ruff format
---------
Co-authored-by: Soulter <905617992@qq.com>
* feat: add hot reload when failed to load plugins (#5334)
* feat:add hot reload when failed to load plugins
* apply bot suggestions
* fix(chatui): add copy rollback path and error message. (#5352)
* fix(chatui): add copy rollback path and error message.
* fix(chatui): fixed textarea leak in the copy button.
* fix(chatui): use color styles from the component library.
* fix: 处理配置文件中的 UTF-8 BOM 编码问题 (#5376)
* fix(config): handle UTF-8 BOM in configuration file loading
Problem:
On Windows, some text editors (like Notepad) automatically add UTF-8 BOM
to JSON files when saving. This causes json.decoder.JSONDecodeError:
"Unexpected UTF-8 BOM" and AstrBot fails to start when cmd_config.json
contains BOM.
Solution:
Add defensive check to strip UTF-8 BOM (\ufeff) if present before
parsing JSON configuration file.
Impact:
- Improves robustness and cross-platform compatibility
- No breaking changes to existing functionality
- Fixes startup failure when configuration file has UTF-8 BOM encoding
Relates-to: Windows editor compatibility issues
* style: fix code formatting with ruff
Fix single quote to double quote to comply with project code style.
* feat: add plugin load&unload hook (#5331)
* 添加了插件的加载完成和卸载完成的钩子事件
* 添加了插件的加载完成和卸载完成的钩子事件
* format code with ruff
* ruff format
---------
Co-authored-by: Soulter <905617992@qq.com>
* test: enhance test framework with comprehensive fixtures and mocks (#5354)
* test: enhance test framework with comprehensive fixtures and mocks
- Add shared mock builders for aiocqhttp, discord, telegram
- Add test helpers for platform configs and mock objects
- Expand conftest.py with test profile support
- Update coverage test workflow configuration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(tests): 移动并重构模拟 LLM 响应和消息组件函数
* fix(tests): 优化 pytest_runtest_setup 中的标记检查逻辑
---------
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: add comprehensive tests for message event handling (#5355)
* test: add comprehensive tests for message event handling
- Add AstrMessageEvent unit tests (688 lines)
- Add AstrBotMessage unit tests
- Enhance smoke tests with message event scenarios
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: improve message type handling and add defensive tests
---------
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add support for showing tool call results in agent execution (#5388)
closes: #5329
* fix: resolve pipeline and star import cycles (#5353)
* fix: resolve pipeline and star import cycles
- Add bootstrap.py and stage_order.py to break circular dependencies
- Export Context, PluginManager, StarTools from star module
- Update pipeline __init__ to defer imports
- Split pipeline initialization into separate bootstrap module
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add logging for get_config() failure in Star class
* fix: reorder logger initialization in base.py
---------
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: enable computer-use tools for subagent handoff (#5399)
* fix: enforce admin guard for sandbox file transfer tools (#5402)
* fix: enforce admin guard for sandbox file transfer tools
* refactor: deduplicate computer tools admin permission checks
* fix: add missing space in permission error message
* fix(core): 优化 File 组件处理逻辑并增强 OneBot 驱动层路径兼容性 (#5391)
* fix(core): 优化 File 组件处理逻辑并增强 OneBot 驱动层路径兼容性
原因 (Necessity):
1. 内核一致性:AstrBot 内核的 Record 和 Video 组件均具备识别 `file:///` 协议头的逻辑,但 File 组件此前缺失此功能,导致行为不统一。
2. OneBot 协议合规:OneBot 11 标准要求本地文件路径必须使用 `file:///` 协议头。此前驱动层未对裸路径进行自动转换,导致发送本地文件时常触发 retcode 1200 (识别URL失败) 错误。
3. 容器环境适配:在 Docker 等路径隔离环境下,裸路径更容易因驱动或协议端的解析歧义而失效。
更改 (Changes):
- [astrbot/core/message/components.py]:
- 在 File.get_file() 中增加对 `file:///` 前缀的识别与剥离逻辑,使其与 Record/Video 组件行为对齐。
- [astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py]:
- 在发送文件前增加自动修正逻辑:若路径为绝对路径且未包含协议头,驱动层将自动补全 `file:///` 前缀。
- 对 http、base64 及已有协议头,确保不干扰原有的正常传输逻辑。
影响 (Impact):
- 以完全兼容的方式增强了文件发送的鲁棒性。
- 解决了插件在发送日志等本地生成的压缩包时,因路径格式不规范导致的发送失败问题。
* refactor(core): 根据 cr 建议,规范化文件 URI 生成与解析逻辑,优化跨平台兼容性
原因 (Necessity):
1. 修复原生路径与 URI 转换在 Windows 下的不对称问题。
2. 规范化 file: 协议头处理,确保符合 RFC 标准并能在 Linux/Windows 间稳健切换。
3. 增强协议判定准确度,防止对普通绝对路径的误处理。
更改 (Changes):
- [astrbot/core/platform/sources/aiocqhttp]:
- 弃用手动拼接,改用 `pathlib.Path.as_uri()` 生成标准 URI。
- 将协议检测逻辑从前缀匹配优化为包含性检测 ("://")。
- [astrbot/core/message/components]:
- 重构 `File.get_file` 解析逻辑,支持对称处理 2/3 斜杠格式。
- 针对 Windows 环境增加了对 `file:///C:/` 格式的自动修正,避免 `os.path` 识别失效。
- [data/plugins/astrbot_plugin_logplus]:
- 在直接 API 调用中同步应用 URI 规范化处理。
影响 (Impact):
- 解决 Docker 环境中因路径不规范导致的 "识别URL失败" 报错。
- 提升了本体框架在 Windows 系统下的文件操作鲁棒性。
* i18n(SubAgentPage): complete internationalization for subagent orchestration page (#5400)
* i18n: complete internationalization for subagent orchestration page
- Replace hardcoded English strings in [SubAgentPage.vue] with i18n keys.
- Update `en-US` and `zh-CN` locales with missing hints, validation messages, and empty state translations.
- Fix translation typos and improve consistency across the SubAgent orchestration UI.
* fix(bug_risk): 避免在模板中的翻译调用上使用 || 'Close' 作为回退值。
* fix(aiocqhttp): enhance shutdown process for aiocqhttp adapter (#5412)
* fix: pass embedding dimensions to provider apis (#5411)
* fix(context): log warning when platform not found for session
* fix(context): improve logging for platform not found in session
* chore: bump version to 4.18.2
* chore: bump version to 4.18.2
* chore: bump version to 4.18.2
* fix: Telegram voice message format (OGG instead of WAV) causing issues with OpenAI STT API (#5389)
* chore: ruff format
* feat(dashboard): add generic desktop app updater bridge (#5424)
* feat(dashboard): add generic desktop app updater bridge
* fix(dashboard): address updater bridge review feedback
* fix(dashboard): unify updater bridge types and error logging
* fix(dashboard): consolidate updater bridge typings
* fix(conversation): retain existing persona_id when updating conversation
* fix(dashboard): 修复设置页新建 API Key 后复制失败问题 (#5439)
* Fix: GitHub proxy not displaying correctly in WebUI (#5438)
* fix(dashboard): preserve custom GitHub proxy setting on reload
* fix(dashboard): keep github proxy selection persisted in settings
* fix(persona): enhance persona resolution logic for conversations and sessions
* fix: ensure tool call/response pairing in context truncation (#5417)
* fix: ensure tool call/response pairing in context truncation
* refactor: simplify fix_messages to single-pass state machine
* perf(cron): enhance future task session isolation
fixes: #5392
* feat: add useExtensionPage composable for managing plugin extensions
- Implemented a new composable `useExtensionPage` to handle various functionalities related to plugin management, including fetching extensions, handling updates, and managing UI states.
- Added support for conflict checking, plugin installation, and custom source management.
- Integrated search and filtering capabilities for plugins in the market.
- Enhanced user experience with dialogs for confirmations and notifications.
- Included pagination and sorting features for better plugin visibility.
* fix: clear markdown field when sending media messages via QQ Official Platform (#5445)
* fix: clear markdown field when sending media messages via QQ Official API
* refactor: use pop() to remove markdown key instead of setting None
* fix: cannot automatically get embedding dim when create embedding provider (#5442)
* fix(dashboard): 强化 API Key 复制临时节点清理逻辑
* fix(embedding): 自动检测改为探测 OpenAI embedding 最大可用维度
* fix: normalize openai embedding base url and add hint key
* i18n: add embedding_api_base hint translations
* i18n: localize provider embedding/proxy metadata hints
* fix: show provider-specific embedding API Base URL hint as field subtitle
* fix(embedding): cap OpenAI detect_dim probes with early short-circuit
* fix(dashboard): return generic error on provider adapter import failure
* 回退检测逻辑
* fix: 修复Pyright静态类型检查报错 (#5437)
* refactor: 修正 Sqlite 查询、下载回调、接口重构与类型调整
* feat: 为 OneBotClient 增加 CallAction 协议与异步调用支持
* fix(telegram): avoid duplicate message_thread_id in streaming (#5430)
* perf: batch metadata query in KB retrieval to fix N+1 problem (#5463)
* perf: batch metadata query in KB retrieval to fix N+1 problem
Replace N sequential get_document_with_metadata() calls with a single
get_documents_with_metadata_batch() call using SQL IN clause.
Benchmark results (local SQLite):
- 10 docs: 10.67ms → 1.47ms (7.3x faster)
- 20 docs: 26.00ms → 2.68ms (9.7x faster)
- 50 docs: 63.87ms → 2.79ms (22.9x faster)
* refactor: use set[str] param type and chunk IN clause for SQLite safety
Address review feedback:
- Change doc_ids param from list[str] to set[str] to avoid unnecessary conversion
- Chunk IN clause into batches of 900 to stay under SQLite's 999 parameter limit
- Remove list() wrapping at call site, pass set directly
* fix:fix the issue where incomplete cleanup of residual plugins occurs… (#5462)
* fix:fix the issue where incomplete cleanup of residual plugins occurs in the failed loading of plugins
* fix:ruff format,apply bot suggestions
* Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* chore: 为类型检查添加 TYPE_CHECKING 的导入与阶段类型引用 (#5474)
* fix(line): line adapter does not appear in the add platform dialog
fixes: #5477
* [bug]查看介入教程line前往错误界面的问题 (#5479)
Fixes #5478
* chore: bump version to 4.18.3
* feat: implement follow-up message handling in ToolLoopAgentRunner (#5484)
* feat: implement follow-up message handling in ToolLoopAgentRunner
* fix: correct import path for follow-up module in InternalAgentSubStage
* feat: implement websockets transport mode selection for chat (#5410)
* feat: implement websockets transport mode selection for chat
- Added transport mode selection (SSE/WebSocket) in the chat component.
- Updated conversation sidebar to include transport mode options.
- Integrated transport mode handling in message sending logic.
- Refactored message sending functions to support both SSE and WebSocket.
- Enhanced WebSocket connection management and message handling.
- Updated localization files for transport mode labels.
- Configured Vite to support WebSocket proxying.
* feat(webchat): refactor message parsing logic and integrate new parsing function
* feat(chat): add websocket API key extraction and scope validation
* Revert "可选后端,实现前后端分离" (#5536)
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: can <51474963+weijintaocode@users.noreply.github.com>
Co-authored-by: Soulter <905617992@qq.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
Co-authored-by: letr <123731298+letr007@users.noreply.github.com>
Co-authored-by: 搁浅 <id6543156918@gmail.com>
Co-authored-by: Helian Nuits <sxp20061207@163.com>
Co-authored-by: Gao Jinzhe <2968474907@qq.com>
Co-authored-by: DD斩首 <155905740+DDZS987@users.noreply.github.com>
Co-authored-by: Ubuntu <ubuntu@localhost.localdomain>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: エイカク <62183434+zouyonghe@users.noreply.github.com>
Co-authored-by: 鸦羽 <Raven95676@gmail.com>
Co-authored-by: Dt8333 <25431943+Dt8333@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Li-shi-ling <114913764+Li-shi-ling@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: Limitless <127183162+Limitless2023@users.noreply.github.com>
Co-authored-by: Limitless2023 <limitless@users.noreply.github.com>
Co-authored-by: evpeople <54983536+evpeople@users.noreply.github.com>
Co-authored-by: SnowNightt <127504703+SnowNightt@users.noreply.github.com>
Co-authored-by: xzj0898 <62733743+xzj0898@users.noreply.github.com>
Co-authored-by: stevessr <89645372+stevessr@users.noreply.github.com>
Co-authored-by: Waterwzy <2916963017@qq.com>
Co-authored-by: NayukiMeko <MekoNayuki@outlook.com>
Co-authored-by: 時壹 <137363396+KBVsent@users.noreply.github.com>
Co-authored-by: sanyekana <Clhikari@qq.com>
Co-authored-by: Chiu Chun-Hsien <95356121+911218sky@users.noreply.github.com>
Co-authored-by: Dream Tokenizer <60459821+Trance-0@users.noreply.github.com>
Co-authored-by: NanoRocky <76585834+NanoRocky@users.noreply.github.com>
Co-authored-by: Pizero <zhaory200707@outlook.com>
Co-authored-by: 雪語 <167516635+YukiRa1n@users.noreply.github.com>
Co-authored-by: whatevertogo <1879483647@qq.com>
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-…
* feat: add bocha web search tool (#4902)
* add bocha web search tool
* Revert "add bocha web search tool"
This reverts commit 1b36d75a17b4c4751828f31f6759357cd2d4000a.
* add bocha web search tool
* fix: correct temporary_cache spelling and update supported tools for web search
* ruff
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix: messages[x] assistant content must contain at least one part (#4928)
* fix: messages[x] assistant content must contain at least one part
fixes: #4876
* ruff format
* chore: bump version to 4.14.5 (#4930)
* feat: implement feishu / lark media file handling utilities for file, audio and video processing (#4938)
* feat: implement media file handling utilities for audio and video processing
* feat: refactor file upload handling for audio and video in LarkMessageEvent
* feat: add cleanup for failed audio and video conversion outputs in media_utils
* feat: add utility methods for sending messages and uploading files in LarkMessageEvent
* fix: correct spelling of 'temporary' in SharedPreferences class
* perf: optimize webchat and wecom ai queue lifecycle (#4941)
* perf: optimize webchat and wecom ai queue lifecycle
* perf: enhance webchat back queue management with conversation ID support
* fix: localize provider source config UI (#4933)
* fix: localize provider source ui
* feat: localize provider metadata keys
* chore: add provider metadata translations
* chore: format provider i18n changes
* fix: preserve metadata fields in i18n conversion
* fix: internationalize platform config and dialog
* fix: add Weixin official account platform icon
---------
Co-authored-by: Soulter <905617992@qq.com>
* chore: bump version to 4.14.6
* feat: add provider-souce-level proxy (#4949)
* feat: 添加 Provider 级别代理支持及请求失败日志
* refactor: simplify provider source configuration structure
* refactor: move env proxy fallback logic to log_connection_failure
* refactor: update client proxy handling and add terminate method for cleanup
* refactor: update no_proxy configuration to remove redundant subnet
---------
Co-authored-by: Soulter <905617992@qq.com>
* feat(ComponentPanel): implement permission management for dashboard (#4887)
* feat(backend): add permission update api
* feat(useCommandActions): add updatePermission action and translations
* feat(dashboard): implement permission editing ui
* style: fix import sorting in command.py
* refactor(backend): extract permission update logic to service
* feat(i18n): add success and failure messages for command updates
---------
Co-authored-by: Soulter <905617992@qq.com>
* feat: 允许 LLM 预览工具返回的图片并自主决定是否发送 (#4895)
* feat: 允许 LLM 预览工具返回的图片并自主决定是否发送
* 复用 send_message_to_user 替代独立的图片发送工具
* feat: implement _HandleFunctionToolsResult class for improved tool response handling
* docs: add path handling guidelines to AGENTS.md
---------
Co-authored-by: Soulter <905617992@qq.com>
* feat(telegram): 添加媒体组(相册)支持 / add media group (album) support (#4893)
* feat(telegram): 添加媒体组(相册)支持 / add media group (album) support
## 功能说明
支持 Telegram 的媒体组消息(相册),将多张图片/视频合并为一条消息处理,而不是分散成多条消息。
## 主要改动
### 1. 初始化媒体组缓存 (__init__)
- 添加 `media_group_cache` 字典存储待处理的媒体组消息
- 使用 2.5 秒超时收集媒体组消息(基于社区最佳实践)
- 最大等待时间 10 秒(防止永久等待)
### 2. 消息处理流程 (message_handler)
- 检测 `media_group_id` 判断是否为媒体组消息
- 媒体组消息走特殊处理流程,避免分散处理
### 3. 媒体组消息缓存 (handle_media_group_message)
- 缓存收到的媒体组消息
- 使用 APScheduler 实现防抖(debounce)机制
- 每收到新消息时重置超时计时器
- 超时后触发统一处理
### 4. 媒体组合并处理 (process_media_group)
- 从缓存中取出所有媒体项
- 使用第一条消息作为基础(保留文本、回复等信息)
- 依次添加所有图片、视频、文档到消息链
- 将合并后的消息发送到处理流程
## 技术方案论证
Telegram Bot API 在处理媒体组时的设计限制:
1. 将媒体组的每个消息作为独立的 update 发送
2. 每个 update 带有相同的 `media_group_id`
3. **不提供**组的总数、结束标志或一次性完整组的机制
因此,bot 必须自行收集消息,并通过硬编码超时(timeout/delay)等待可能延迟到达的消息。
这是目前唯一可靠的方案,被官方实现、主流框架和开发者社区广泛采用。
### 官方和社区证据:
- **Telegram Bot API 服务器实现(tdlib)**:明确指出缺少结束标志或总数信息
https://github.com/tdlib/telegram-bot-api/issues/643
- **Telegram Bot API 服务器 issue**:讨论媒体组处理的不便性,推荐使用超时机制
https://github.com/tdlib/telegram-bot-api/issues/339
- **Telegraf(Node.js 框架)**:专用媒体组中间件使用 timeout 控制等待时间
https://github.com/DieTime/telegraf-media-group
- **StackOverflow 讨论**:无法一次性获取媒体组所有文件,必须手动收集
https://stackoverflow.com/questions/50180048/telegram-api-get-all-uploaded-photos-by-media-group-id
- **python-telegram-bot 社区**:确认媒体组消息单独到达,需手动处理
https://github.com/python-telegram-bot/python-telegram-bot/discussions/3143
- **Telegram Bot API 官方文档**:仅定义 `media_group_id` 为可选字段,不提供获取完整组的接口
https://core.telegram.org/bots/api#message
## 实现细节
- 使用 2.5 秒超时收集媒体组消息(基于社区最佳实践)
- 最大等待时间 10 秒(防止永久等待)
- 采用防抖(debounce)机制:每收到新消息重置计时器
- 利用 APScheduler 实现延迟处理和任务调度
## 测试验证
- ✅ 发送 5 张图片相册,成功合并为一条消息
- ✅ 保留原始文本说明和回复信息
- ✅ 支持图片、视频、文档混合的媒体组
- ✅ 日志显示 Processing media group <media_group_id> with 5 items
## 代码变更
- 文件:astrbot/core/platform/sources/telegram/tg_adapter.py
- 新增代码:124 行
- 新增方法:handle_media_group_message(), process_media_group()
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* refactor(telegram): 优化媒体组处理性能和可靠性
根据代码审查反馈改进:
1. 实现 media_group_max_wait 防止无限延迟
- 跟踪媒体组创建时间,超过最大等待时间立即处理
- 最坏情况下 10 秒内必定处理,防止消息持续到达导致无限延迟
2. 移除手动 job 查找优化性能
- 删除 O(N) 的 get_jobs() 循环扫描
- 依赖 replace_existing=True 自动替换任务
3. 重用 convert_message 减少代码重复
- 统一所有媒体类型转换逻辑
- 未来添加新媒体类型只需修改一处
Co-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>
* fix(telegram): handle missing message in media group processing and improve logging messages
---------
Co-authored-by: Ubuntu <ubuntu@localhost.localdomain>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Soulter <905617992@qq.com>
* feat: add welcome feature with localized content and onboarding steps
* fix: correct height attribute to max-height for dialog component
* feat: supports electron app (#4952)
* feat: add desktop wrapper with frontend-only packaging
* docs: add desktop build docs and track dashboard lockfile
* fix: track desktop lockfile for npm ci
* fix: allow custom install directory for windows installer
* chore: migrate desktop workflow to pnpm
* fix(desktop): build AppImage only on Linux
* fix(desktop): harden packaged startup and backend bundling
* fix(desktop): adapt packaged restart and plugin dependency flow
* fix(desktop): prevent backend respawn race on quit
* fix(desktop): prefer pyproject version for desktop packaging
* fix(desktop): improve startup loading UX and reduce flicker
* ci: add desktop multi-platform release workflow
* ci: fix desktop release build and mac runner labels
* ci: disable electron-builder auto publish in desktop build
* ci: avoid electron-builder publish path in build matrix
* ci: normalize desktop release artifact names
* ci: exclude blockmap files from desktop release assets
* ci: prefix desktop release assets with AstrBot and purge blockmaps
* feat: add electron bridge types and expose backend control methods in preload script
* Update startup screen assets and styles
- Changed the icon from PNG to SVG format for better scalability.
- Updated the border color from #d0d0d0 to #eeeeee for a softer appearance.
- Adjusted the width of the startup screen from 460px to 360px for improved responsiveness.
* Update .gitignore to include package.json
* chore: remove desktop gitkeep ignore exceptions
* docs: update desktop troubleshooting for current runtime behavior
* refactor(desktop): modularize runtime and harden startup flow
---------
Co-authored-by: Soulter <905617992@qq.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: dedupe preset messages (#4961)
* feat: enhance package.json with resource filters and compression settings
* chore: update Python version requirements to 3.12 (#4963)
* chore: bump version to 4.14.7
* feat: refactor release workflow and add special update handling for electron app (#4969)
* chore: bump version to 4.14.8 and bump faiss-cpu version up to date
* chore: auto ann fix by ruff (#4903)
* chore: auto fix by ruff
* refactor: 统一修正返回类型注解为 None/bool 以匹配实现
* refactor: 将 _get_next_page 改为异步并移除多余的请求错误抛出
* refactor: 将 get_client 的返回类型改为 object
* style: 为 LarkMessageEvent 的相关方法添加返回类型注解 None
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: prepare OpenSSL via vcpkg for Windows ARM64
* ci: change ghcr namespace
* chore: update pydantic dependency version (#4980)
* feat: add delete button to persona management dialog (#4978)
* Initial plan
* feat: add delete button to persona management dialog
- Added delete button to PersonaForm dialog (only visible when editing)
- Implemented deletePersona method with confirmation dialog
- Connected delete event to PersonaManager for proper handling
- Button positioned on left side of dialog actions for clear separation
- Uses existing i18n translations for delete button and messages
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: use finally block to ensure saving state is reset
- Moved `this.saving = false` to finally block in deletePersona
- Ensures UI doesn't stay in saving state after errors
- Follows best practices for state management
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
---------
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* feat: enhance Dingtalk adapter with active push message and image, video, audio message type (#4986)
* fix: handle pip install execution in frozen runtime (#4985)
* fix: handle pip install execution in frozen runtime
* fix: harden pip subprocess fallback handling
* fix: collect certifi data in desktop backend build (#4995)
* feat: 企业微信应用 支持主动消息推送,并优化企微应用、微信公众号、微信客服音频相关的处理 (#4998)
* feat: 企业微信智能机器人支持主动消息推送以及发送视频、文件等消息类型支持 (#4999)
* feat: enhance WecomAIBotAdapter and WecomAIBotMessageEvent for improved streaming message handling (#5000)
fixes: #3965
* feat: enhance persona tool management and update UI localization for subagent orchestration (#4990)
* feat: enhance persona tool management and update UI localization for subagent orchestration
* fix: remove debug logging for final ProviderRequest in build_main_agent function
* perf: 稳定源码与 Electron 打包环境下的 pip 安装行为,并修复非 Electron 环境下点击 WebUI 更新按钮时出现跳转对话框的问题 (#4996)
* fix: handle pip install execution in frozen runtime
* fix: harden pip subprocess fallback handling
* fix: scope global data root to packaged electron runtime
* refactor: inline frozen runtime check for electron guard
* fix: prefer current interpreter for source pip installs
* fix: avoid resolving venv python symlink for pip
* refactor: share runtime environment detection utilities
* fix: improve error message when pip module is unavailable
* fix: raise ImportError when pip module is unavailable
* fix: preserve ImportError semantics for missing pip
* fix: 修复非electron app环境更新时仍然显示electron更新对话框的问题
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix: 'HandoffTool' object has no attribute 'agent' (#5005)
* fix: 移动agent的位置到super().__init__之后
* add: 添加一行注释
* chore(deps): bump the github-actions group with 2 updates (#5006)
Bumps the github-actions group with 2 updates: [astral-sh/setup-uv](https://github.com/astral-sh/setup-uv) and [actions/download-artifact](https://github.com/actions/download-artifact).
Updates `astral-sh/setup-uv` from 6 to 7
- [Release notes](https://github.com/astral-sh/setup-uv/releases)
- [Commits](https://github.com/astral-sh/setup-uv/compare/v6...v7)
Updates `actions/download-artifact` from 6 to 7
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v6...v7)
---
updated-dependencies:
- dependency-name: astral-sh/setup-uv
dependency-version: '7'
dependency-type: direct:production
update-type: version-update:semver-major
dependency-group: github-actions
- dependency-name: actions/download-artifact
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>
* fix: stabilize packaged runtime pip/ssl behavior and mac font fallback (#5007)
* fix: patch pip distlib finder for frozen electron runtime
* fix: use certifi CA bundle for runtime SSL requests
* fix: configure certifi CA before core imports
* fix: improve mac font fallback for dashboard text
* fix: harden frozen pip patch and unify TLS connector
* refactor: centralize dashboard CJK font fallback stacks
* perf: reuse TLS context and avoid repeated frozen pip patch
* refactor: bootstrap TLS setup before core imports
* fix: use async confirm dialog for provider deletions
* fix: replace native confirm dialogs in dashboard
- Add shared confirm helper in dashboard/src/utils/confirmDialog.ts for async dialog usage with safe fallback.
- Migrate provider, chat, config, session, platform, persona, MCP, backup, and knowledge-base delete/close confirmations to use the shared helper.
- Remove scattered inline confirm handling to keep behavior consistent and avoid native blocking dialog focus/caret issues in Electron.
* fix: capture runtime bootstrap logs after logger init
- Add bootstrap record buffer in runtime_bootstrap for early TLS patch logs before logger is ready.
- Flush buffered bootstrap logs to astrbot logger at process startup in main.py.
- Include concrete exception details for TLS bootstrap failures to improve diagnosis.
* fix: harden runtime bootstrap and unify confirm handling
- Simplify bootstrap log buffering and add a public initialize hook for non-main startup paths.
- Guard aiohttp TLS patching with feature/type checks and keep graceful fallback when internals are unavailable.
- Standardize dashboard confirmation flow via shared confirm helpers across composition and options API components.
* refactor: simplify runtime tls bootstrap and tighten confirm typing
* refactor: align ssl helper namespace and confirm usage
* fix: 修复 Windows 打包版后端重启失败问题 (#5009)
* fix: patch pip distlib finder for frozen electron runtime
* fix: use certifi CA bundle for runtime SSL requests
* fix: configure certifi CA before core imports
* fix: improve mac font fallback for dashboard text
* fix: harden frozen pip patch and unify TLS connector
* refactor: centralize dashboard CJK font fallback stacks
* perf: reuse TLS context and avoid repeated frozen pip patch
* refactor: bootstrap TLS setup before core imports
* fix: use async confirm dialog for provider deletions
* fix: replace native confirm dialogs in dashboard
- Add shared confirm helper in dashboard/src/utils/confirmDialog.ts for async dialog usage with safe fallback.
- Migrate provider, chat, config, session, platform, persona, MCP, backup, and knowledge-base delete/close confirmations to use the shared helper.
- Remove scattered inline confirm handling to keep behavior consistent and avoid native blocking dialog focus/caret issues in Electron.
* fix: capture runtime bootstrap logs after logger init
- Add bootstrap record buffer in runtime_bootstrap for early TLS patch logs before logger is ready.
- Flush buffered bootstrap logs to astrbot logger at process startup in main.py.
- Include concrete exception details for TLS bootstrap failures to improve diagnosis.
* fix: harden runtime bootstrap and unify confirm handling
- Simplify bootstrap log buffering and add a public initialize hook for non-main startup paths.
- Guard aiohttp TLS patching with feature/type checks and keep graceful fallback when internals are unavailable.
- Standardize dashboard confirmation flow via shared confirm helpers across composition and options API components.
* refactor: simplify runtime tls bootstrap and tighten confirm typing
* refactor: align ssl helper namespace and confirm usage
* fix: avoid frozen restart crash from multiprocessing import
* fix: include missing frozen dependencies for windows backend
* fix: use execv for stable backend reboot args
* Revert "fix: use execv for stable backend reboot args"
This reverts commit 9cc27becffeba0e117fea26aa5c2e1fe7afc6e36.
* Revert "fix: include missing frozen dependencies for windows backend"
This reverts commit 52554bea1fa61045451600c64447b7bf38cf6c92.
* Revert "fix: avoid frozen restart crash from multiprocessing import"
This reverts commit 10548645b0ba1e19b64194878ece478a48067959.
* fix: reset pyinstaller onefile env before reboot
* fix: unify electron restart path and tray-exit backend cleanup
* fix: stabilize desktop restart detection and frozen reboot args
* fix: make dashboard restart wait detection robust
* fix: revert dashboard restart waiting interaction tweaks
* fix: pass auth token for desktop graceful restart
* fix: avoid false failure during graceful restart wait
* fix: start restart waiting before electron restart call
* fix: harden restart waiting and reboot arg parsing
* fix: parse start_time as numeric timestamp
* fix: 修复app内重启异常,修复app内点击重启不能立刻提示重启,以及在后端就绪时及时刷新界面的问题 (#5013)
* fix: patch pip distlib finder for frozen electron runtime
* fix: use certifi CA bundle for runtime SSL requests
* fix: configure certifi CA before core imports
* fix: improve mac font fallback for dashboard text
* fix: harden frozen pip patch and unify TLS connector
* refactor: centralize dashboard CJK font fallback stacks
* perf: reuse TLS context and avoid repeated frozen pip patch
* refactor: bootstrap TLS setup before core imports
* fix: use async confirm dialog for provider deletions
* fix: replace native confirm dialogs in dashboard
- Add shared confirm helper in dashboard/src/utils/confirmDialog.ts for async dialog usage with safe fallback.
- Migrate provider, chat, config, session, platform, persona, MCP, backup, and knowledge-base delete/close confirmations to use the shared helper.
- Remove scattered inline confirm handling to keep behavior consistent and avoid native blocking dialog focus/caret issues in Electron.
* fix: capture runtime bootstrap logs after logger init
- Add bootstrap record buffer in runtime_bootstrap for early TLS patch logs before logger is ready.
- Flush buffered bootstrap logs to astrbot logger at process startup in main.py.
- Include concrete exception details for TLS bootstrap failures to improve diagnosis.
* fix: harden runtime bootstrap and unify confirm handling
- Simplify bootstrap log buffering and add a public initialize hook for non-main startup paths.
- Guard aiohttp TLS patching with feature/type checks and keep graceful fallback when internals are unavailable.
- Standardize dashboard confirmation flow via shared confirm helpers across composition and options API components.
* refactor: simplify runtime tls bootstrap and tighten confirm typing
* refactor: align ssl helper namespace and confirm usage
* fix: avoid frozen restart crash from multiprocessing import
* fix: include missing frozen dependencies for windows backend
* fix: use execv for stable backend reboot args
* Revert "fix: use execv for stable backend reboot args"
This reverts commit 9cc27becffeba0e117fea26aa5c2e1fe7afc6e36.
* Revert "fix: include missing frozen dependencies for windows backend"
This reverts commit 52554bea1fa61045451600c64447b7bf38cf6c92.
* Revert "fix: avoid frozen restart crash from multiprocessing import"
This reverts commit 10548645b0ba1e19b64194878ece478a48067959.
* fix: reset pyinstaller onefile env before reboot
* fix: unify electron restart path and tray-exit backend cleanup
* fix: stabilize desktop restart detection and frozen reboot args
* fix: make dashboard restart wait detection robust
* fix: revert dashboard restart waiting interaction tweaks
* fix: pass auth token for desktop graceful restart
* fix: avoid false failure during graceful restart wait
* fix: start restart waiting before electron restart call
* fix: harden restart waiting and reboot arg parsing
* fix: parse start_time as numeric timestamp
* fix: preserve windows frozen reboot argv quoting
* fix: align restart waiting with electron restart timing
* fix: tighten graceful restart and unmanaged kill safety
* chore: bump version to 4.15.0 (#5003)
* fix: add reminder for v4.14.8 users regarding manual redeployment due to a bug
* fix: harden plugin dependency loading in frozen app runtime (#5015)
* fix: compare plugin versions semantically in market updates
* fix: prioritize plugin site-packages for in-process pip
* fix: reload starlette from plugin target site-packages
* fix: harden plugin dependency import precedence in frozen runtime
* fix: improve plugin dependency conflict handling
* refactor: simplify plugin conflict checks and version utils
* fix: expand transitive plugin dependencies for conflict checks
* fix: recover conflicting plugin dependencies during module prefer
* fix: reuse renderer restart flow for tray backend restart
* fix: add recoverable plugin dependency conflict handling
* revert: remove plugin version comparison changes
* fix: add missing tray restart backend labels
* feat: adding support for media and quoted message attachments for feishu (#5018)
* docs: add AUR installation method (#4879)
* docs: sync system package manager installation instructions to all languages
* Update README.md
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
* Update README.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* fix/typo
* refactor: update system package manager installation instructions for Arch Linux across multiple language README files
* feat: add installation command for AstrBot in multiple language README files
---------
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
Co-authored-by: Soulter <905617992@qq.com>
* fix(desktop): 为 Electron 与后端日志增加按大小轮转 (#5029)
* fix(desktop): rotate electron and backend logs
* refactor(desktop): centralize log rotation defaults and debug fs errors
* fix(desktop): harden rotation fs ops and buffer backend log writes
* refactor(desktop): extract buffered logger and reduce sync stat calls
* refactor(desktop): simplify rotation flow and harden logger config
* fix(desktop): make app logging async and flush-safe
* fix: harden app log path switching and debug-gated rotation errors
* fix: cap buffered log chunk size during path switch
* feat: add first notice feature with multilingual support and UI integration
* fix: 提升打包版桌面端启动稳定性并优化插件依赖处理 (#5031)
* fix(desktop): rotate electron and backend logs
* refactor(desktop): centralize log rotation defaults and debug fs errors
* fix(desktop): harden rotation fs ops and buffer backend log writes
* refactor(desktop): extract buffered logger and reduce sync stat calls
* refactor(desktop): simplify rotation flow and harden logger config
* fix(desktop): make app logging async and flush-safe
* fix: harden app log path switching and debug-gated rotation errors
* fix: cap buffered log chunk size during path switch
* fix: avoid redundant plugin reinstall and upgrade electron
* fix: stop webchat tasks cleanly and bind packaged backend to localhost
* fix: unify platform shutdown and await webchat listener cleanup
* fix: improve startup logs for dashboard and onebot listeners
* fix: revert extra startup service logs
* fix: harden plugin import recovery and webchat listener cleanup
* fix: pin dashboard ci node version to 24.13.0
* fix: avoid duplicate webchat listener cleanup on terminate
* refactor: clarify platform task lifecycle management
* fix: continue platform shutdown when terminate fails
* feat: temporary file handling and introduce TempDirCleaner (#5026)
* feat: temporary file handling and introduce TempDirCleaner
- Updated various modules to use `get_astrbot_temp_path()` instead of `get_astrbot_data_path()` for temporary file storage.
- Renamed temporary files for better identification and organization.
- Introduced `TempDirCleaner` to manage the size of the temporary directory, ensuring it does not exceed a specified limit by deleting the oldest files.
- Added configuration option for maximum temporary directory size in the dashboard.
- Implemented tests for `TempDirCleaner` to verify cleanup functionality and size management.
* ruff
* fix: close unawaited reset coroutine on early return (#5033)
When an OnLLMRequestEvent hook stops event propagation, the
reset_coro created by build_main_agent was never awaited, causing
a RuntimeWarning. Close the coroutine explicitly before returning.
Fixes #5032
Co-authored-by: Limitless2023 <limitless@users.noreply.github.com>
* fix: update error logging message for connection failures
* docs: clean and sync README (#5014)
* fix: close missing div in README
* fix: sync README_zh-TW with README
* fix: sync README
* fix: correct typo
correct url in README_en README_fr README_ru
* docs: sync README_en with README
* Update README_en.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: provider extra param dialog key display error
* chore: ruff format
* feat: add send_chat_action for Telegram platform adapter (#5037)
* feat: add send_chat_action for Telegram platform adapter
Add typing/upload indicator when sending messages via Telegram.
- Added _send_chat_action helper method for sending chat actions
- Send appropriate action (typing, upload_photo, upload_document, upload_voice)
before sending different message types
- Support streaming mode with typing indicator
- Support supergroup with message_thread_id
* refactor(telegram): extract chat action helpers and add throttling
- Add ACTION_BY_TYPE mapping for message type to action priority
- Add _get_chat_action_for_chain() to determine action from message chain
- Add _send_media_with_action() for upload → send → restore typing pattern
- Add _ensure_typing() helper for typing status
- Add chat action throttling (0.5s) in streaming mode to avoid rate limits
- Update type annotation to ChatAction | str for better static checking
* feat(telegram): implement send_typing method for Telegram platform
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix: 修复更新日志、官方文档弹窗双滚动条问题 (#5060)
* docs: sync and fix readme typo (#5055)
* docs: fix index typo
* docs: fix typo in README_en.md
- 移除英文README中意外出现的俄语,并替换为英语
* docs: fix html typo
- remove unused '</p>'
* docs: sync table with README
* docs: sync README header format
- keep the README header format consistent
* doc: sync key features
* style: format files
- Fix formatting issues from previous PR
* fix: correct md anchor link
* docs: correct typo in README_fr.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* docs: correct typo in README_zh-TW.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* fix: 修复备份时缺失的人格文件夹映射 (#5042)
* feat: QQ 官方机器人平台支持主动推送消息、私聊场景下支持接收文件 (#5066)
* feat: QQ 官方机器人平台支持主动推送消息、私聊场景下支持接收文件
* feat: enhance QQOfficialWebhook to remember session scenes for group, channel, and friend messages
* perf: 优化分段回复间隔时间的初始化逻辑 (#5068)
fixes: #5059
* fix: chunk err when using openrouter deepseek (#5069)
* feat: add i18n supports for custom platform adapters (#5045)
* Feat: 为插件提供的适配器的元数据&i18n提供数据通路
* chore: update docstrings with pull request references
Added references to pull request 5045 in docstrings.
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* fix: 完善转发引用解析与图片回退并支持配置化控制 (#5054)
* feat: support fallback image parsing for quoted messages
* fix: fallback parse quoted images when reply chain has placeholders
* style: format network utils with ruff
* test: expand quoted parser coverage and improve fallback diagnostics
* fix: fallback to text-only retry when image requests fail
* fix: tighten image fallback and resolve nested quoted forwards
* refactor: simplify quoted message extraction and dedupe images
* fix: harden quoted parsing and openai error candidates
* fix: harden quoted image ref normalization
* refactor: organize quoted parser settings and logging
* fix: cap quoted fallback images and avoid retry loops
* refactor: split quoted message parser into focused modules
* refactor: share onebot segment parsing logic
* refactor: unify quoted message parsing flow
* feat: move quoted parser tuning to provider settings
* fix: add missing i18n metadata for quoted parser settings
* chore: refine forwarded message setting labels
* fix: add config tabs and routing for normal and system configurations
* chore: bump version to 4.16.0 (#5074)
* feat: add LINE platform support with adapter and configuration (#5085)
* fix-correct-FIRST_NOTICE.md-locale-path-resolution (#5083) (#5082)
* fix:修改配置文件目录
* fix:添加备选的FIRST_NOTICE.zh-CN.md用于兼容
* fix: remove unnecessary frozen flag from requirements export in Dockerfile
fixes: #5089
* fix #5089: add uv lock step in Dockerfile before export (#5091)
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* feat: support hot reload after plugin load failure (#5043)
* add :Support hot reload after plugin load failure
* Apply suggestions from code review
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* fix:reformat code
* fix:reformat code
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* feat: add fallback chat model chain in tool loop runner (#5109)
* feat: implement fallback provider support for chat models and update configuration
* feat: enhance provider selection display with count and chips for selected providers
* feat: update fallback chat providers to use provider settings and add warning for non-list fallback models
* feat: add Afdian support card to resources section in WelcomePage
* feat: replace colorlog with loguru for enhanced logging support (#5115)
* feat: add SSL configuration options for WebUI and update related logging (#5117)
* chore: bump version to 4.17.0
* fix: handle list format content from OpenAI-compatible APIs (#5128)
* fix: handle list format content from OpenAI-compatible APIs
Some LLM providers (e.g., GLM-4.5V via SiliconFlow) return content as
list[dict] format like [{'type': 'text', 'text': '...'}] instead of
plain string. This causes the raw list representation to be displayed
to users.
Changes:
- Add _normalize_content() helper to extract text from various content formats
- Use json.loads instead of ast.literal_eval for safer parsing
- Add size limit check (8KB) before attempting JSON parsing
- Only convert lists that match OpenAI content-part schema (has 'type': 'text')
to avoid collapsing legitimate list-literal replies like ['foo', 'bar']
- Add strip parameter to preserve whitespace in streaming chunks
- Clean up orphan </think> tags that may leak from some models
Fixes #5124
* fix: improve content normalization safety
- Try json.loads first, fallback to ast.literal_eval for single-quoted
Python literals to avoid corrupting apostrophes (e.g., "don't")
- Coerce text values to str to handle null or non-string text fields
* fix: update retention logic in LogManager to handle backup count correctly
* chore: bump version to 4.17.1
* docs: Added instructions for deploying AstrBot using AstrBot Launcher. (#5136)
Added instructions for deploying AstrBot using AstrBot Launcher.
* fix: add MCP tools to function tool set in _plugin_tool_fix (#5144)
* fix: add support for collecting data from builtin stars in electron pyinstaller build (#5145)
* chore: bump version to 4.17.1
* chore: ruff format
* fix: prevent updates for AstrBot launched via launcher
* fix(desktop): include runtime deps for builtin plugins in backend build (#5146)
* fix: 'Plain' object has no attribute 'text' when using python 3.14 (#5154)
* fix: enhance plugin metadata handling by injecting attributes before instantiation (#5155)
* fix: enhance handle_result to support event context and webchat image sending
* chore: bump version to 4.17.3
* chore: ruff format
* feat: add NVIDIA provider template (#5157)
fixes: #5156
* feat: enhance provider sources panel with styled menu and mobile support
* fix: improve permission denied message for local execution in Python and shell tools
* feat: enhance PersonaForm component with responsive design and improved styling (#5162)
fix: #5159
* ui(CronJobPage): fix action column buttons overlapping in CronJobPage (#5163)
- 修改前:操作列容器仅使用 `d-flex`,在页面宽度变窄时,子元素(开关和删除按钮)会因为宽度挤压而发生视觉重叠,甚至堆叠在一起。
- 修改后:
1. 为容器添加了 `flex-nowrap`,强制禁止子元素换行。
2. 设置了 `min-width: 140px`,确保该列拥有固定的保护空间,防止被其他长文本列挤压。
3. 增加了 `gap: 12px` 间距,提升了操作辨识度并优化了点击体验。
* feat: add unsaved changes notice to configuration page and update messages
* feat: implement search functionality in configuration components and update UI (#5168)
* feat: add FAQ link to vertical sidebar and update navigation for localization
* feat: add announcement section to WelcomePage and localize announcement title
* chore: bump version to 4.17.4
* feat: supports send markdown message in qqofficial (#5173)
* feat: supports send markdown message in qqofficial
closes: #1093 #918 #4180 #4264
* ruff format
* fix: prevent duplicate error message when all LLM providers fail (#5183)
* fix: 修复选择配置文件进入配置文件管理弹窗直接关闭弹窗显示的配置文件不正确 (#5174)
* feat: add MarketPluginCard component and integrate random plugin feature in ExtensionPage (#5190)
* feat: add MarketPluginCard component and integrate random plugin feature in ExtensionPage
* feat: update random plugin selection logic to use pluginMarketData and refresh on relevant events
* feat: supports aihubmix
* docs: update readme
* chore: ruff format
* feat: add LINE support to multiple language README files
* feat(core): add plugin error hook for custom error routing (#5192)
* feat(core): add plugin error hook for custom error routing
* fix(core): align plugin error suppression with event stop state
* refactor: extract Voice_messages_forbidden fallback into shared helper with typed BadRequest exception (#5204)
- Add _send_voice_with_fallback helper to deduplicate voice forbidden handling
- Catch telegram.error.BadRequest instead of bare Exception with string matching
- Add text field to Record component to preserve TTS source text
- Store original text in Record during TTS conversion for use as document caption
- Skip _send_chat_action when chat_id is empty to avoid unnecessary warnings
* chore: bump version to 4.17.5
* feat: add admin permission checks for Python and Shell execution (#5214)
* fix: 改进微信公众号被动回复处理机制,引入缓冲与分片回复,并优化超时行为 (#5224)
* 修复wechat official 被动回复功能
* ruff format
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix: 修复仅发送 JSON 消息段时的空消息回复报错 (#5208)
* Fix Register_Stage
· 补全 JSON 消息判断,修复发送 JSON 消息时遇到 “消息为空,跳过发送阶段” 的问题。
· 顺带补全其它消息类型判断。
Co-authored-by: Pizero <zhaory200707@outlook.com>
* Fix formatting and comments in stage.py
* Format stage.py
---------
Co-authored-by: Pizero <zhaory200707@outlook.com>
* docs: update related repo links
* fix(core): terminate active events on reset/new/del to prevent stale responses (#5225)
* fix(core): terminate active events on reset/new/del to prevent stale responses
Closes #5222
* style: fix import sorting in scheduler.py
* chore: remove Electron desktop pipeline and switch to tauri repo (#5226)
* ci: remove Electron desktop build from release pipeline
* chore: remove electron desktop and switch to tauri release trigger
* ci: remove desktop workflow dispatch trigger
* refactor: migrate data paths to astrbot_path helpers
* fix: point desktop update prompt to AstrBot-desktop releases
* fix: update feature request template for clarity and consistency in English and Chinese
* Feat/config leave confirm (#5249)
* feat: 配置文件增加未保存提示弹窗
* fix: 移除unsavedChangesDialog插件使用组件方式实现弹窗
* feat: add support for plugin astrbot-version and platform requirement checks (#5235)
* feat: add support for plugin astrbot-version and platform requirement checks
* fix: remove unsupported platform and version constraints from metadata.yaml
* fix: remove restriction on 'v' in astrbot_version specification format
* ruff format
* feat: add password confirmation when changing password (#5247)
* feat: add password confirmation when changing password
Fixes #5177
Adds a password confirmation field to prevent accidental password typos.
Changes:
- Backend: validate confirm_password matches new_password
- Frontend: add confirmation input with validation
- i18n: add labels and error messages for password mismatch
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(auth): improve error message for password confirmation mismatch
* fix(auth): update password hashing logic and improve confirmation validation
---------
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix(provider): 修复 dict 格式 content 导致的 JSON 残留问题 (#5250)
* fix(provider): 修复 dict 格式 content 导致的 JSON 残留问题
修复 _normalize_content 函数未处理 dict 类型 content 的问题。
当 LLM 返回 {"type": "text", "text": "..."} 格式的 content 时,
现在会正确提取 text 字段而非直接转为字符串。
同时改进 fallback 行为,对 None 值返回空字符串。
Fixes #5244
* Update warning message for unexpected dict format
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
* chore: remove outdated heihe.md documentation file
* fix: all mcp tools exposed to main agent (#5252)
* fix: enhance PersonaForm layout and improve tool selection display
* fix: update tool status display and add localization for inactive tools
* fix: remove additionalProperties from tool schema properties (#5253)
fixes: #5217
* fix: simplify error messages for account edit validation
* fix: streamline error response for empty new username and password in account edit
* chore: bump vertion to 4.17.6
* feat: add OpenRouter provider support and icon
* chore: ruff format
* refactor(dashboard): replace legacy isElectron bridge fields with isDesktop (#5269)
* refactor dashboard desktop bridge fields from isElectron to isDesktop
* refactor dashboard runtime detection into shared helper
* fix: update contributor avatar image URL to include max size and columns (#5268)
* feat: astrbot http api (#5280)
* feat: astrbot http api
* Potential fix for code scanning alert no. 34: Use of a broken or weak cryptographic hashing algorithm on sensitive data
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* fix: improve error handling for missing attachment path in file upload
* feat: implement paginated retrieval of platform sessions for creators
* feat: refactor attachment directory handling in ChatRoute
* feat: update API endpoint paths for file and message handling
* feat: add documentation link to API key management section in settings
* feat: update API key scopes and related configurations in API routes and tests
* feat: enhance API key expiration options and add warning for permanent keys
* feat: add UTC normalization and serialization for API key timestamps
* feat: implement chat session management and validation for usernames
* feat: ignore session_id type chunks in message processing
---------
Co-authored-by: Copilot Autofix powered by AI <62310815+github-advanced-security[bot]@users.noreply.github.com>
* feat(dashboard): improve plugin platform support display and mobile accessibility (#5271)
* feat(dashboard): improve plugin platform support display and mobile accessibility
- Replace hover-based tooltips with interactive click menus for platform support information.
- Fix mobile touch issues by introducing explicit state control for status capsules.
- Enhance UI aesthetics with platform-specific icons and a structured vertical list layout.
- Add dynamic chevron icons to provide clear visual cues for expandable content.
* refactor(dashboard): refactor market card with computed properties for performance
* refactor(dashboard): unify plugin platform support UI with new reusable chip component
- Create shared 'PluginPlatformChip' component to encapsulate platform meta display.
- Fix mobile interaction bugs by simplifying menu triggers and event handling.
- Add stacked platform icon previews and dynamic chevron indicators within capsules.
- Improve information hierarchy using structured vertical lists for platform details.
- Optimize rendering efficiency with computed properties across both card views.
* fix: qq official guild message send error (#5287)
* fix: qq official guild message send error
* Update astrbot/core/platform/sources/qqofficial/qqofficial_message_event.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---------
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* 更新readme文档,补充桌面app说明,并向前移动位置 (#5297)
* docs: update desktop deployment section in README
* docs: refine desktop and launcher deployment descriptions
* Update README.md
* feat: add Anthropic Claude Code OAuth provider and adaptive thinking support (#5209)
* feat: add Anthropic Claude Code OAuth provider and adaptive thinking support
* fix: add defensive guard for metadata overrides and align budget condition with docs
* refactor: adopt sourcery-ai suggestions for OAuth provider
- Use use_api_key=False in OAuth subclass to avoid redundant
API-key client construction before replacing with auth_token client
- Generalize metadata override helper to merge all dict keys
instead of only handling 'limit', improving extensibility
* Feat/telegram command alias register #5233 (#5234)
* feat: support registering command aliases for Telegram
Now when registering commands with aliases, all aliases will be
registered as Telegram bot commands in addition to the main command.
Example:
@register_command(command_name="draw", alias={"画", "gen"})
Now /draw, /画, and /gen will all appear in the Telegram command menu.
* feat(telegram): add duplicate command name warning when registering commands
Log a warning when duplicate command names are detected during Telegram
command registration to help identify configuration conflicts.
* refactor: remove Anthropic OAuth provider implementation and related metadata overrides
* fix: 修复新建对话时因缺少会话ID导致配置绑定失败的问题 (#5292)
* fix:尝试修改
* fix:添加详细日志
* fix:进行详细修改,并添加日志
* fix:删除所有日志
* fix: 增加安全访问函数
- 给 localStorage 访问加了 try/catch + 可用性判断:dashboard/src/utils/chatConfigBinding.ts:13
- 新增 getFromLocalStorage/setToLocalStorage(在受限存储/无痕模式下异常时回退/忽略)
- getStoredDashboardUsername() / getStoredSelectedChatConfigId() 改为走安全读取:dashboard/src/utils/chatConfigBinding.ts:36 - 新增 setStoredSelectedChatConfigId(),写入失败静默忽略:dashboard/src/utils/chatConfigBinding.ts:44
- 把 ConfigSelector.vue 里直接 localStorage.getItem/setItem 全部替换为上述安全方法:dashboard/src/components/chat/ConfigSelector.vue:81
- 已重新跑过 pnpm run typecheck,通过。
* rm:删除个人用的文档文件
* Revert "rm:删除个人用的文档文件"
This reverts commit 0fceee05434cfbcb11e45bb967a77d5fa93196bf.
* rm:删除个人用的文档文件
* rm:删除个人用的文档文件
* chore: bump version to 4.18.0
* fix(SubAgentPage): 当中间的介绍文本非常长时,Flex 布局会自动挤压右侧的控制按钮区域 (#5306)
* fix: 修复新版本插件市场出现插件显示为空白的 bug;纠正已安装插件卡片的排版,统一大小 (#5309)
* fix(ExtensionCard): 解决插件卡片大小不统一的问题
* fix(MarketPluginCard): 解决插件市场不加载插件的问题 (#5303)
* feat: supports spawn subagent as a background task that not block the main agent workflow (#5081)
* feat:为subagent添加后台任务参数
* ruff
* fix: update terminology from 'handoff mission' to 'background task' and refactor related logic
* fix: update terminology from 'background_mission' to 'background_task' in HandoffTool and related logic
* fix(HandoffTool): update background_task description for clarity on usage
---------
Co-authored-by: Soulter <905617992@qq.com>
* cho
* fix: 修复 aiohttp 版本过新导致 qq-botpy 报错的问题 (#5316)
* chore: ruff format
* fix: remove hard-coded 6s timeout from tavily request
* fix: remove changelogs directory from .dockerignore
* feat(dashboard): make release redirect base URL configurable (#5330)
* feat(dashboard): make desktop release base URL configurable
* refactor(dashboard): use generic release base URL env with upstream default
* fix(dashboard): guard release base URL normalization when env is unset
* refactor(dashboard): use generic release URL helpers and avoid latest suffix duplication
* feat: add stop functionality for active agent sessions and improve handling of stop requests (#5380)
* feat: add stop functionality for active agent sessions and improve handling of stop requests
* feat: update stop button icon and tooltip in ChatInput component
* fix: correct indentation in tool call handling within ChatRoute class
* fix: chatui cannot persist file segment (#5386)
* fix(plugin): update plugin directory handling for reserved plugins (#5369)
* fix(plugin): update plugin directory handling for reserved plugins
* fix(plugin): add warning logs for missing plugin name, object, directory, and changelog
* chore(README): updated with README.md (#5375)
* chore(README): updated with README.md
* Update README_fr.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* Update README_zh-TW.md
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
---------
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
* feat: add image urls / paths supports for subagent (#5348)
* fix: 修复5081号PR在子代理执行后台任务时,未正确使用系统配置的流式/非流请求的问题(#5081)
* feat:为子代理增加远程图片URL参数支持
* fix: update description for image_urls parameter in HandoffTool to clarify usage in multimodal tasks
* ruff format
---------
Co-authored-by: Soulter <905617992@qq.com>
* feat: add hot reload when failed to load plugins (#5334)
* feat:add hot reload when failed to load plugins
* apply bot suggestions
* fix(chatui): add copy rollback path and error message. (#5352)
* fix(chatui): add copy rollback path and error message.
* fix(chatui): fixed textarea leak in the copy button.
* fix(chatui): use color styles from the component library.
* fix: 处理配置文件中的 UTF-8 BOM 编码问题 (#5376)
* fix(config): handle UTF-8 BOM in configuration file loading
Problem:
On Windows, some text editors (like Notepad) automatically add UTF-8 BOM
to JSON files when saving. This causes json.decoder.JSONDecodeError:
"Unexpected UTF-8 BOM" and AstrBot fails to start when cmd_config.json
contains BOM.
Solution:
Add defensive check to strip UTF-8 BOM (\ufeff) if present before
parsing JSON configuration file.
Impact:
- Improves robustness and cross-platform compatibility
- No breaking changes to existing functionality
- Fixes startup failure when configuration file has UTF-8 BOM encoding
Relates-to: Windows editor compatibility issues
* style: fix code formatting with ruff
Fix single quote to double quote to comply with project code style.
* feat: add plugin load&unload hook (#5331)
* 添加了插件的加载完成和卸载完成的钩子事件
* 添加了插件的加载完成和卸载完成的钩子事件
* format code with ruff
* ruff format
---------
Co-authored-by: Soulter <905617992@qq.com>
* test: enhance test framework with comprehensive fixtures and mocks (#5354)
* test: enhance test framework with comprehensive fixtures and mocks
- Add shared mock builders for aiocqhttp, discord, telegram
- Add test helpers for platform configs and mock objects
- Expand conftest.py with test profile support
- Update coverage test workflow configuration
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* refactor(tests): 移动并重构模拟 LLM 响应和消息组件函数
* fix(tests): 优化 pytest_runtest_setup 中的标记检查逻辑
---------
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* test: add comprehensive tests for message event handling (#5355)
* test: add comprehensive tests for message event handling
- Add AstrMessageEvent unit tests (688 lines)
- Add AstrBotMessage unit tests
- Enhance smoke tests with message event scenarios
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: improve message type handling and add defensive tests
---------
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: add support for showing tool call results in agent execution (#5388)
closes: #5329
* fix: resolve pipeline and star import cycles (#5353)
* fix: resolve pipeline and star import cycles
- Add bootstrap.py and stage_order.py to break circular dependencies
- Export Context, PluginManager, StarTools from star module
- Update pipeline __init__ to defer imports
- Split pipeline initialization into separate bootstrap module
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
* fix: add logging for get_config() failure in Star class
* fix: reorder logger initialization in base.py
---------
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
* feat: enable computer-use tools for subagent handoff (#5399)
* fix: enforce admin guard for sandbox file transfer tools (#5402)
* fix: enforce admin guard for sandbox file transfer tools
* refactor: deduplicate computer tools admin permission checks
* fix: add missing space in permission error message
* fix(core): 优化 File 组件处理逻辑并增强 OneBot 驱动层路径兼容性 (#5391)
* fix(core): 优化 File 组件处理逻辑并增强 OneBot 驱动层路径兼容性
原因 (Necessity):
1. 内核一致性:AstrBot 内核的 Record 和 Video 组件均具备识别 `file:///` 协议头的逻辑,但 File 组件此前缺失此功能,导致行为不统一。
2. OneBot 协议合规:OneBot 11 标准要求本地文件路径必须使用 `file:///` 协议头。此前驱动层未对裸路径进行自动转换,导致发送本地文件时常触发 retcode 1200 (识别URL失败) 错误。
3. 容器环境适配:在 Docker 等路径隔离环境下,裸路径更容易因驱动或协议端的解析歧义而失效。
更改 (Changes):
- [astrbot/core/message/components.py]:
- 在 File.get_file() 中增加对 `file:///` 前缀的识别与剥离逻辑,使其与 Record/Video 组件行为对齐。
- [astrbot/core/platform/sources/aiocqhttp/aiocqhttp_message_event.py]:
- 在发送文件前增加自动修正逻辑:若路径为绝对路径且未包含协议头,驱动层将自动补全 `file:///` 前缀。
- 对 http、base64 及已有协议头,确保不干扰原有的正常传输逻辑。
影响 (Impact):
- 以完全兼容的方式增强了文件发送的鲁棒性。
- 解决了插件在发送日志等本地生成的压缩包时,因路径格式不规范导致的发送失败问题。
* refactor(core): 根据 cr 建议,规范化文件 URI 生成与解析逻辑,优化跨平台兼容性
原因 (Necessity):
1. 修复原生路径与 URI 转换在 Windows 下的不对称问题。
2. 规范化 file: 协议头处理,确保符合 RFC 标准并能在 Linux/Windows 间稳健切换。
3. 增强协议判定准确度,防止对普通绝对路径的误处理。
更改 (Changes):
- [astrbot/core/platform/sources/aiocqhttp]:
- 弃用手动拼接,改用 `pathlib.Path.as_uri()` 生成标准 URI。
- 将协议检测逻辑从前缀匹配优化为包含性检测 ("://")。
- [astrbot/core/message/components]:
- 重构 `File.get_file` 解析逻辑,支持对称处理 2/3 斜杠格式。
- 针对 Windows 环境增加了对 `file:///C:/` 格式的自动修正,避免 `os.path` 识别失效。
- [data/plugins/astrbot_plugin_logplus]:
- 在直接 API 调用中同步应用 URI 规范化处理。
影响 (Impact):
- 解决 Docker 环境中因路径不规范导致的 "识别URL失败" 报错。
- 提升了本体框架在 Windows 系统下的文件操作鲁棒性。
* i18n(SubAgentPage): complete internationalization for subagent orchestration page (#5400)
* i18n: complete internationalization for subagent orchestration page
- Replace hardcoded English strings in [SubAgentPage.vue] with i18n keys.
- Update `en-US` and `zh-CN` locales with missing hints, validation messages, and empty state translations.
- Fix translation typos and improve consistency across the SubAgent orchestration UI.
* fix(bug_risk): 避免在模板中的翻译调用上使用 || 'Close' 作为回退值。
* fix(aiocqhttp): enhance shutdown process for aiocqhttp adapter (#5412)
* fix: pass embedding dimensions to provider apis (#5411)
* fix(context): log warning when platform not found for session
* fix(context): improve logging for platform not found in session
* chore: bump version to 4.18.2
* chore: bump version to 4.18.2
* chore: bump version to 4.18.2
* fix: Telegram voice message format (OGG instead of WAV) causing issues with OpenAI STT API (#5389)
* chore: ruff format
* feat(dashboard): add generic desktop app updater bridge (#5424)
* feat(dashboard): add generic desktop app updater bridge
* fix(dashboard): address updater bridge review feedback
* fix(dashboard): unify updater bridge types and error logging
* fix(dashboard): consolidate updater bridge typings
* fix(conversation): retain existing persona_id when updating conversation
* fix(dashboard): 修复设置页新建 API Key 后复制失败问题 (#5439)
* Fix: GitHub proxy not displaying correctly in WebUI (#5438)
* fix(dashboard): preserve custom GitHub proxy setting on reload
* fix(dashboard): keep github proxy selection persisted in settings
* fix(persona): enhance persona resolution logic for conversations and sessions
* fix: ensure tool call/response pairing in context truncation (#5417)
* fix: ensure tool call/response pairing in context truncation
* refactor: simplify fix_messages to single-pass state machine
* perf(cron): enhance future task session isolation
fixes: #5392
* feat: add useExtensionPage composable for managing plugin extensions
- Implemented a new composable `useExtensionPage` to handle various functionalities related to plugin management, including fetching extensions, handling updates, and managing UI states.
- Added support for conflict checking, plugin installation, and custom source management.
- Integrated search and filtering capabilities for plugins in the market.
- Enhanced user experience with dialogs for confirmations and notifications.
- Included pagination and sorting features for better plugin visibility.
* fix: clear markdown field when sending media messages via QQ Official Platform (#5445)
* fix: clear markdown field when sending media messages via QQ Official API
* refactor: use pop() to remove markdown key instead of setting None
* fix: cannot automatically get embedding dim when create embedding provider (#5442)
* fix(dashboard): 强化 API Key 复制临时节点清理逻辑
* fix(embedding): 自动检测改为探测 OpenAI embedding 最大可用维度
* fix: normalize openai embedding base url and add hint key
* i18n: add embedding_api_base hint translations
* i18n: localize provider embedding/proxy metadata hints
* fix: show provider-specific embedding API Base URL hint as field subtitle
* fix(embedding): cap OpenAI detect_dim probes with early short-circuit
* fix(dashboard): return generic error on provider adapter import failure
* 回退检测逻辑
* fix: 修复Pyright静态类型检查报错 (#5437)
* refactor: 修正 Sqlite 查询、下载回调、接口重构与类型调整
* feat: 为 OneBotClient 增加 CallAction 协议与异步调用支持
* fix(telegram): avoid duplicate message_thread_id in streaming (#5430)
* perf: batch metadata query in KB retrieval to fix N+1 problem (#5463)
* perf: batch metadata query in KB retrieval to fix N+1 problem
Replace N sequential get_document_with_metadata() calls with a single
get_documents_with_metadata_batch() call using SQL IN clause.
Benchmark results (local SQLite):
- 10 docs: 10.67ms → 1.47ms (7.3x faster)
- 20 docs: 26.00ms → 2.68ms (9.7x faster)
- 50 docs: 63.87ms → 2.79ms (22.9x faster)
* refactor: use set[str] param type and chunk IN clause for SQLite safety
Address review feedback:
- Change doc_ids param from list[str] to set[str] to avoid unnecessary conversion
- Chunk IN clause into batches of 900 to stay under SQLite's 999 parameter limit
- Remove list() wrapping at call site, pass set directly
* fix:fix the issue where incomplete cleanup of residual plugins occurs… (#5462)
* fix:fix the issue where incomplete cleanup of residual plugins occurs in the failed loading of plugins
* fix:ruff format,apply bot suggestions
* Apply suggestion from @gemini-code-assist[bot]
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* chore: 为类型检查添加 TYPE_CHECKING 的导入与阶段类型引用 (#5474)
* fix(line): line adapter does not appear in the add platform dialog
fixes: #5477
* [bug]查看介入教程line前往错误界面的问题 (#5479)
Fixes #5478
* chore: bump version to 4.18.3
* feat: implement follow-up message handling in ToolLoopAgentRunner (#5484)
* feat: implement follow-up message handling in ToolLoopAgentRunner
* fix: correct import path for follow-up module in InternalAgentSubStage
* feat: implement websockets transport mode selection for chat (#5410)
* feat: implement websockets transport mode selection for chat
- Added transport mode selection (SSE/WebSocket) in the chat component.
- Updated conversation sidebar to include transport mode options.
- Integrated transport mode handling in message sending logic.
- Refactored message sending functions to support both SSE and WebSocket.
- Enhanced WebSocket connection management and message handling.
- Updated localization files for transport mode labels.
- Configured Vite to support WebSocket proxying.
* feat(webchat): refactor message parsing logic and integrate new parsing function
* feat(chat): add websocket API key extraction and scope validation
* Revert "可选后端,实现前后端分离" (#5536)
---------
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: can <51474963+weijintaocode@users.noreply.github.com>
Co-authored-by: Soulter <905617992@qq.com>
Co-authored-by: Soulter <37870767+Soulter@users.noreply.github.com>
Co-authored-by: letr <123731298+letr007@users.noreply.github.com>
Co-authored-by: 搁浅 <id6543156918@gmail.com>
Co-authored-by: Helian Nuits <sxp20061207@163.com>
Co-authored-by: Gao Jinzhe <2968474907@qq.com>
Co-authored-by: DD斩首 <155905740+DDZS987@users.noreply.github.com>
Co-authored-by: Ubuntu <ubuntu@localhost.localdomain>
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: エイカク <62183434+zouyonghe@users.noreply.github.com>
Co-authored-by: 鸦羽 <Raven95676@gmail.com>
Co-authored-by: Dt8333 <25431943+Dt8333@users.noreply.github.com>
Co-authored-by: Copilot <198982749+Copilot@users.noreply.github.com>
Co-authored-by: Li-shi-ling <114913764+Li-shi-ling@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: sourcery-ai[bot] <58596630+sourcery-ai[bot]@users.noreply.github.com>
Co-authored-by: Limitless <127183162+Limitless2023@users.noreply.github.com>
Co-authored-by: Limitless2023 <limitless@users.noreply.github.com>
Co-authored-by: evpeople <54983536+evpeople@users.noreply.github.com>
Co-authored-by: SnowNightt <127504703+SnowNightt@users.noreply.github.com>
Co-authored-by: xzj0898 <62733743+xzj0898@users.noreply.github.com>
Co-authored-by: stevessr <89645372+stevessr@users.noreply.github.com>
Co-authored-by: Waterwzy <2916963017@qq.com>
Co-authored-by: NayukiMeko <MekoNayuki@outlook.com>
Co-authored-by: 時壹 <137363396+KBVsent@users.noreply.github.com>
Co-authored-by: sanyekana <Clhikari@qq.com>
Co-authored-by: Chiu Chun-Hsien <95356121+911218sky@users.noreply.github.com>
Co-authored-by: Dream Tokenizer <60459821+Trance-0@users.noreply.github.com>
Co-authored-by: NanoRocky <76585834+NanoRocky@users.noreply.github.com>
Co-authored-by: Pizero <zhaory200707@outlook.com>
Co-authored-by: 雪語 <167516635+YukiRa1n@users.noreply.github.com>
Co-authored-by: whatevertogo <1879483647@qq.com>
Co-authored-by: whatevertogo <whatevertogo@users.noreply.github.com>
Co-authore…
fixes: #4815
Summary
Validation