feat: implement follow-up message handling in ToolLoopAgentRunner#5484
feat: implement follow-up message handling in ToolLoopAgentRunner#5484
Conversation
Summary of ChangesHello @Soulter, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request introduces a robust system for handling follow-up messages, significantly enhancing the interactivity of agents. It allows users to provide additional instructions to an agent even when it's actively executing tools, ensuring these instructions are delivered in a structured manner and influence the agent's subsequent actions. This feature improves the agent's responsiveness to user guidance during complex, multi-step operations. Highlights
Changelog
Activity
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
There was a problem hiding this comment.
Hey - 我发现了 4 个问题,并给出了一些整体反馈:
- follow_up 排序状态目前使用了未类型化的
dict[str, object]并依赖运行时断言;建议把它重构成一个小的 @DataClass(例如FollowUpOrderState),包含类型化字段(condition、statuses、next_order、next_turn),以便让逻辑更易理解并减少出错风险。 ToolLoopAgentRunner会无限累积_pending_follow_ups;建议对队列长度设置上限或丢弃较旧的 ticket,以避免在一次长时间的工具执行期间,用户发送大量 follow-up 时出现无限制的内存增长。
供 AI Agents 使用的提示词
Please address the comments from this code review:
## Overall Comments
- follow_up 排序状态目前使用了未类型化的 `dict[str, object]` 并依赖运行时断言;建议把它重构成一个小的 @dataclass(例如 `FollowUpOrderState`),包含类型化字段(`condition`、`statuses`、`next_order`、`next_turn`),以便让逻辑更易理解并减少出错风险。
- `ToolLoopAgentRunner` 会无限累积 `_pending_follow_ups`;建议对队列长度设置上限或丢弃较旧的 ticket,以避免在一次长时间的工具执行期间,用户发送大量 follow-up 时出现无限制的内存增长。
## Individual Comments
### Comment 1
<location path="astrbot/core/pipeline/process_stage/follow_up.py" line_range="30-34" />
<code_context>
+ monitor_task: asyncio.Task[None]
+
+
+def _event_follow_up_text(event: AstrMessageEvent) -> str:
+ text = (event.get_message_str() or "").strip()
+ if text:
+ return text
+ return event.get_message_outline().strip()
+
+
</code_context>
<issue_to_address>
**issue:** 在调用 `.strip()` 之前,需要防止 `get_message_outline()` 返回 `None`。
如果 `get_message_outline()` 可能返回 `None`,最后一行 `return event.get_message_outline().strip()` 会抛出 `AttributeError`。建议参照 `get_message_str()` 的处理方式,例如:`outline = (event.get_message_outline() or '').strip()` 然后返回 `outline`。
</issue_to_address>
### Comment 2
<location path="tests/test_tool_loop_agent_runner.py" line_range="152-161" />
<code_context>
self.agent_done_called = True
+class MockEvent:
+ def __init__(self, umo: str, sender_id: str):
+ self.unified_msg_origin = umo
+ self._sender_id = sender_id
+
+ def get_sender_id(self):
+ return self._sender_id
+
+
+class MockAgentContext:
+ def __init__(self, event):
+ self.event = event
</code_context>
<issue_to_address>
**suggestion (testing):** 建议为 `ToolLoopAgentRunner.follow_up` 的边界情况(runner 已完成,以及空/仅空白消息)添加测试用例。
现有的 follow-up 注入测试覆盖了正常路径,但没有覆盖这些边界行为。请添加一些小的单元测试,直接断言:
- 当 runner 已经完成/被中止时,`follow_up()` 返回 `None`。
- 对于 `message_text=""` 或仅包含空白字符的输入,`follow_up()` 返回 `None`。
这些测试可以使用最小化的 runner 初始化,不需要经过完整的工具执行流程。
建议实现:
```python
class MockAgentContext:
def __init__(self, event):
self.event = event
@pytest.mark.asyncio
async def test_follow_up_returns_none_when_runner_done(runner):
# Mark the runner as done/aborted before calling follow_up
runner.agent_done()
ctx = MockAgentContext(
MockEvent(umo="test-origin", sender_id="test-sender"),
)
result = await runner.follow_up(ctx, "some follow-up message")
assert result is None
@pytest.mark.asyncio
@pytest.mark.parametrize("message_text", ["", " "])
async def test_follow_up_returns_none_for_empty_or_whitespace_message(runner, message_text):
ctx = MockAgentContext(
MockEvent(umo="test-origin", sender_id="test-sender"),
)
result = await runner.follow_up(ctx, message_text)
assert result is None
@pytest.fixture
```
这些测试基于以下假设:
1. 存在一个 `runner` fixture,能够返回一个已完全初始化的 `ToolLoopAgentRunner` 实例。
2. `ToolLoopAgentRunner` 暴露了 `agent_done()` 方法,用于将 runner 切换到“已完成/中止”的状态。
3. `ToolLoopAgentRunner.follow_up` 是一个 `async` 方法,签名类似于 `follow_up(agent_context, message_text)`,并在上述边界情况下返回 `None`。
如果你代码库中的实现与这些假设不一致,你需要:
- 在测试中更新 fixture 的名称/类型(例如把 `runner` 替换为你实际使用的 fixture)。
- 用正确的方式替代 `runner.agent_done()` 去标记 runner 已完成/中止(例如 `runner.abort()`,设置某个标志位等)。
- 如果 `follow_up` 是同步的,移除 `await` 和 `@pytest.mark.asyncio`;或者根据实际实现调整调用方式/函数签名。
</issue_to_address>
### Comment 3
<location path="astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py" line_range="141" />
<code_context>
async def process(
self, event: AstrMessageEvent, provider_wake_prefix: str
) -> AsyncGenerator[None, None]:
+ follow_up_capture: FollowUpCapture | None = None
+ follow_up_consumed_marked = False
+ follow_up_activated = False
</code_context>
<issue_to_address>
**issue (complexity):** 建议将 follow-up 处理逻辑和 runner 注册逻辑抽取到专门的上下文管理器中,从而让主流程保持线性、更易阅读。
你可以保留现在的行为,但通过把新增关注点(follow-up + runner 注册)封装到小的辅助函数/上下文管理器中,来降低复杂度。这可以移除跨作用域的标志变量和嵌套的 try/finally,同时保留原有语义。
### 1. 使用异步上下文管理器封装 follow-up 处理
与其在整个方法中手动跟踪 `follow_up_capture`、`follow_up_consumed_marked` 和 `follow_up_activated`,不如把这些逻辑集中起来:
```python
# follow_up.py (or similar)
from contextlib import asynccontextmanager
@asynccontextmanager
async def follow_up_flow(event: AstrMessageEvent):
follow_up_capture = try_capture_follow_up(event)
consumed_marked = False
activated = False
if follow_up_capture:
consumed_marked, activated = await prepare_follow_up_capture(follow_up_capture)
if consumed_marked:
logger.info(
"Follow-up ticket already consumed, stopping processing. umo=%s, seq=%s",
event.unified_msg_origin,
follow_up_capture.ticket.seq,
)
# Do not yield; caller should detect this via state
await finalize_follow_up_capture(
follow_up_capture, activated=activated, consumed_marked=consumed_marked
)
yield None # indicate "short-circuit"
return
try:
yield follow_up_capture
finally:
if follow_up_capture:
await finalize_follow_up_capture(
follow_up_capture, activated=activated, consumed_marked=consumed_marked
)
```
在 `process` 中使用:
```python
async def process(self, event: AstrMessageEvent, provider_wake_prefix: str) -> AsyncGenerator[None, None]:
try:
async with follow_up_flow(event) as follow_up_capture:
# short-circuited because ticket consumed
if follow_up_capture is None and try_capture_follow_up(event) is not None:
return
streaming_response = self.streaming_response
if (enable_streaming := event.get_extra("enable_streaming")) is not None:
streaming_response = bool(enable_streaming)
...
await event.send_typing()
await call_event_hook(event, EventType.OnWaitingLLMRequestEvent)
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
...
except Exception as e:
...
```
这样可以移除顶层的 `follow_up_*` 标志变量以及 `process` 末尾的 `finally`,让与 follow-up 相关的逻辑自成一体。
### 2. 使用上下文管理器封装 runner 注册
同样地,可以用一个小的上下文管理器替换 `runner_registered` 标志和内部的 `finally`:
```python
# follow_up.py (or a more generic module)
from contextlib import contextmanager
@contextmanager
def active_runner(origin: str, agent_runner: AgentRunner):
register_active_runner(origin, agent_runner)
try:
yield
finally:
unregister_active_runner(origin, agent_runner)
```
在会话锁内部使用:
```python
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
...
agent_runner = build_result.agent_runner
req = build_result.provider_request
provider = build_result.provider
reset_coro = build_result.reset_coro
...
if await call_event_hook(event, EventType.OnLLMRequestEvent, req):
if reset_coro:
reset_coro.close()
return
if reset_coro:
await reset_coro
with active_runner(event.unified_msg_origin, agent_runner):
action_type = event.get_extra("action_type")
event.trace.record(...)
if action_type == "live":
...
elif streaming_response and not stream_to_general:
...
else:
async for _ in run_agent(...):
yield
final_resp = agent_runner.get_final_llm_resp()
event.trace.record(...)
if not event.is_stopped() or agent_runner.was_aborted():
await self._save_to_history(...)
asyncio.create_task(Metric.upload(...))
```
这样可以移除 `runner_registered` 和 `process` 中显式的 `finally`,让核心流程(构建 → 运行 → 记录/打点)更易于理解,同时保留现有行为。
</issue_to_address>
### Comment 4
<location path="astrbot/core/pipeline/process_stage/follow_up.py" line_range="12" />
<code_context>
+from astrbot.core.platform.astr_message_event import AstrMessageEvent
+
+_ACTIVE_AGENT_RUNNERS: dict[str, AgentRunner] = {}
+_FOLLOW_UP_ORDER_STATE: dict[str, dict[str, object]] = {}
+"""UMO-level follow-up order state.
+
</code_context>
<issue_to_address>
**issue (complexity):** 建议使用一个专门的控制器类来封装每个 UMO 的 follow-up 状态,而不是用通用的基于 dict 的结构,这样可以让排序逻辑更易理解和维护。
你可以保持所有现有行为,但通过把每个 UMO 的状态封装到一个小类中,而不是使用带字符串 key 和重复断言的 `dict[str, object]`,显著降低心智负担。
### 1. 用类型化控制器替代 `dict[str, object]`
目前:
```python
_FOLLOW_UP_ORDER_STATE: dict[str, dict[str, object]] = {}
```
你需要手动维护:
- `"condition"`
- `"statuses"`
- `"next_order"`
- `"next_turn"`
这些字段分散在 `_get_follow_up_order_state`、`_advance_follow_up_turn_locked`、`_allocate_follow_up_order`、`_mark_follow_up_consumed`、`_activate_and_wait_follow_up_turn`、`_finish_follow_up_turn` 等函数中。
可以把这些逻辑集中到一个小的控制器类里:
```python
@dataclass(slots=True)
class _FollowUpOrderController:
condition: asyncio.Condition
statuses: dict[int, str]
next_order: int
next_turn: int
def __init__(self) -> None:
self.condition = asyncio.Condition()
self.statuses = {}
self.next_order = 0
self.next_turn = 0
def allocate_order(self) -> int:
seq = self.next_order
self.next_order += 1
self.statuses[seq] = "pending"
return seq
def _advance_turn_locked(self) -> None:
while True:
status = self.statuses.get(self.next_turn)
if status in ("consumed", "finished"):
self.statuses.pop(self.next_turn, None)
self.next_turn += 1
continue
break
```
然后全局变量可以改为:
```python
_FOLLOW_UP_ORDER_STATE: dict[str, _FollowUpOrderController] = {}
```
配合一个更简单的 getter:
```python
def _get_follow_up_order_state(umo: str) -> _FollowUpOrderController:
state = _FOLLOW_UP_ORDER_STATE.get(umo)
if state is None:
state = _FollowUpOrderController()
_FOLLOW_UP_ORDER_STATE[umo] = state
return state
```
### 2. 把异步操作移入控制器
三个异步辅助函数可以在控制器内部简化并去重,并把清理逻辑集中到一个地方:
```python
class _FollowUpOrderController:
# ... from above ...
async def mark_consumed(self, umo: str, seq: int) -> None:
async with self.condition:
if seq in self.statuses and self.statuses[seq] != "finished":
self.statuses[seq] = "consumed"
self._advance_turn_locked()
self.condition.notify_all()
self._cleanup_if_idle(umo)
async def activate_and_wait_turn(self, seq: int) -> None:
async with self.condition:
if seq in self.statuses:
self.statuses[seq] = "active"
while self.next_turn != seq:
await self.condition.wait()
async def finish_turn(self, umo: str, seq: int) -> None:
async with self.condition:
if seq in self.statuses:
self.statuses[seq] = "finished"
self._advance_turn_locked()
self.condition.notify_all()
self._cleanup_if_idle(umo)
def _cleanup_if_idle(self, umo: str) -> None:
if not self.statuses and _ACTIVE_AGENT_RUNNERS.get(umo) is None:
_FOLLOW_UP_ORDER_STATE.pop(umo, None)
```
顶层辅助函数就可以变成很薄的包装:
```python
def _get_state_if_any(umo: str) -> _FollowUpOrderController | None:
return _FOLLOW_UP_ORDER_STATE.get(umo)
async def _mark_follow_up_consumed(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.mark_consumed(umo, seq)
async def _activate_and_wait_follow_up_turn(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.activate_and_wait_turn(seq)
async def _finish_follow_up_turn(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.finish_turn(umo, seq)
```
### 3. 在不改变行为的前提下获得的好处
- 移除了基于字符串 key(`"condition"`、`"statuses"` 等)以及 `assert isinstance(...)` 的“字符串驱动”结构。
- 把排序不变式和清理规则局部化到 `_FollowUpOrderController` 内部,不再需要跨多个函数在脑中跟踪这些规则。
- 保留现有的对外 API(`_mark_follow_up_consumed`、`_activate_and_wait_follow_up_turn`、`_finish_follow_up_turn`、`_allocate_follow_up_order`),因此模块的外部行为和调用方可以保持不变。
</issue_to_address>帮我变得更有用!请对每条评论点选 👍 或 👎,我会根据你的反馈改进评审质量。
Original comment in English
Hey - I've found 4 issues, and left some high level feedback:
- The follow_up ordering state uses untyped
dict[str, object]with runtime asserts; consider refactoring this into a small @DataClass (e.g., FollowUpOrderState) with typed fields (condition,statuses,next_order,next_turn) to make the logic easier to follow and less error-prone. - ToolLoopAgentRunner accumulates
_pending_follow_upswithout any bound; consider enforcing a maximum queue length or dropping older tickets to avoid unbounded memory growth if a user sends many follow-ups during a long-running tool execution.
Prompt for AI Agents
Please address the comments from this code review:
## Overall Comments
- The follow_up ordering state uses untyped `dict[str, object]` with runtime asserts; consider refactoring this into a small @dataclass (e.g., FollowUpOrderState) with typed fields (`condition`, `statuses`, `next_order`, `next_turn`) to make the logic easier to follow and less error-prone.
- ToolLoopAgentRunner accumulates `_pending_follow_ups` without any bound; consider enforcing a maximum queue length or dropping older tickets to avoid unbounded memory growth if a user sends many follow-ups during a long-running tool execution.
## Individual Comments
### Comment 1
<location path="astrbot/core/pipeline/process_stage/follow_up.py" line_range="30-34" />
<code_context>
+ monitor_task: asyncio.Task[None]
+
+
+def _event_follow_up_text(event: AstrMessageEvent) -> str:
+ text = (event.get_message_str() or "").strip()
+ if text:
+ return text
+ return event.get_message_outline().strip()
+
+
</code_context>
<issue_to_address>
**issue:** Guard against `get_message_outline()` returning `None` before calling `.strip()`.
If `get_message_outline()` can return `None`, the final `return event.get_message_outline().strip()` will raise an `AttributeError`. Consider mirroring the `get_message_str()` pattern, e.g. `outline = (event.get_message_outline() or '').strip()` and returning `outline`.
</issue_to_address>
### Comment 2
<location path="tests/test_tool_loop_agent_runner.py" line_range="152-161" />
<code_context>
self.agent_done_called = True
+class MockEvent:
+ def __init__(self, umo: str, sender_id: str):
+ self.unified_msg_origin = umo
+ self._sender_id = sender_id
+
+ def get_sender_id(self):
+ return self._sender_id
+
+
+class MockAgentContext:
+ def __init__(self, event):
+ self.event = event
</code_context>
<issue_to_address>
**suggestion (testing):** Consider adding tests for `ToolLoopAgentRunner.follow_up` edge cases (done runner and empty/whitespace messages).
The follow-up injection tests cover the happy path, but these edge behaviors aren’t exercised. Please add small unit tests that directly assert:
- `follow_up()` returns `None` when the runner is already done/aborted.
- `follow_up()` returns `None` for `message_text=""` or whitespace-only input.
These can use a minimal runner setup without going through the full tool-execution flow.
Suggested implementation:
```python
class MockAgentContext:
def __init__(self, event):
self.event = event
@pytest.mark.asyncio
async def test_follow_up_returns_none_when_runner_done(runner):
# Mark the runner as done/aborted before calling follow_up
runner.agent_done()
ctx = MockAgentContext(
MockEvent(umo="test-origin", sender_id="test-sender"),
)
result = await runner.follow_up(ctx, "some follow-up message")
assert result is None
@pytest.mark.asyncio
@pytest.mark.parametrize("message_text", ["", " "])
async def test_follow_up_returns_none_for_empty_or_whitespace_message(runner, message_text):
ctx = MockAgentContext(
MockEvent(umo="test-origin", sender_id="test-sender"),
)
result = await runner.follow_up(ctx, message_text)
assert result is None
@pytest.fixture
```
These tests assume:
1. There is a `runner` fixture that returns a fully initialized `ToolLoopAgentRunner` instance.
2. `ToolLoopAgentRunner` exposes an `agent_done()` method that transitions the runner into a "done/aborted" state.
3. `ToolLoopAgentRunner.follow_up` is an `async` method with signature similar to `follow_up(agent_context, message_text)` and returns `None` in the edge cases described.
If any of these assumptions differ in your codebase, you should:
- Update the fixture name/type in the tests (`runner` → your actual fixture).
- Replace `runner.agent_done()` with the correct way to mark the runner as done/aborted (e.g. `runner.abort()`, setting a flag, etc.).
- Remove `await` and `@pytest.mark.asyncio` if `follow_up` is synchronous, or adjust the call/signature to match the actual implementation.
</issue_to_address>
### Comment 3
<location path="astrbot/core/pipeline/process_stage/method/agent_sub_stages/internal.py" line_range="141" />
<code_context>
async def process(
self, event: AstrMessageEvent, provider_wake_prefix: str
) -> AsyncGenerator[None, None]:
+ follow_up_capture: FollowUpCapture | None = None
+ follow_up_consumed_marked = False
+ follow_up_activated = False
</code_context>
<issue_to_address>
**issue (complexity):** Consider extracting the follow-up handling and runner registration logic into dedicated context managers to keep the main process flow linear and easier to read.
You can keep the new behavior but reduce complexity by encapsulating the new concerns (follow-up + runner registration) into small helpers/context managers. This removes the cross-scope flags and nested try/finally while preserving semantics.
### 1. Wrap follow-up handling in an async context manager
Instead of manually tracking `follow_up_capture`, `follow_up_consumed_marked`, and `follow_up_activated` across the whole method, you can centralize this logic:
```python
# follow_up.py (or similar)
from contextlib import asynccontextmanager
@asynccontextmanager
async def follow_up_flow(event: AstrMessageEvent):
follow_up_capture = try_capture_follow_up(event)
consumed_marked = False
activated = False
if follow_up_capture:
consumed_marked, activated = await prepare_follow_up_capture(follow_up_capture)
if consumed_marked:
logger.info(
"Follow-up ticket already consumed, stopping processing. umo=%s, seq=%s",
event.unified_msg_origin,
follow_up_capture.ticket.seq,
)
# Do not yield; caller should detect this via state
await finalize_follow_up_capture(
follow_up_capture, activated=activated, consumed_marked=consumed_marked
)
yield None # indicate "short-circuit"
return
try:
yield follow_up_capture
finally:
if follow_up_capture:
await finalize_follow_up_capture(
follow_up_capture, activated=activated, consumed_marked=consumed_marked
)
```
Usage in `process`:
```python
async def process(self, event: AstrMessageEvent, provider_wake_prefix: str) -> AsyncGenerator[None, None]:
try:
async with follow_up_flow(event) as follow_up_capture:
# short-circuited because ticket consumed
if follow_up_capture is None and try_capture_follow_up(event) is not None:
return
streaming_response = self.streaming_response
if (enable_streaming := event.get_extra("enable_streaming")) is not None:
streaming_response = bool(enable_streaming)
...
await event.send_typing()
await call_event_hook(event, EventType.OnWaitingLLMRequestEvent)
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
...
except Exception as e:
...
```
This removes the top-level `follow_up_*` flags and the final `finally` block in `process`, keeping follow-up concerns self-contained.
### 2. Wrap runner registration in a context manager
Similarly, the `runner_registered` flag and inner `finally` can be replaced by a small context manager:
```python
# follow_up.py (or a more generic module)
from contextlib import contextmanager
@contextmanager
def active_runner(origin: str, agent_runner: AgentRunner):
register_active_runner(origin, agent_runner)
try:
yield
finally:
unregister_active_runner(origin, agent_runner)
```
Usage inside the session lock:
```python
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
...
agent_runner = build_result.agent_runner
req = build_result.provider_request
provider = build_result.provider
reset_coro = build_result.reset_coro
...
if await call_event_hook(event, EventType.OnLLMRequestEvent, req):
if reset_coro:
reset_coro.close()
return
if reset_coro:
await reset_coro
with active_runner(event.unified_msg_origin, agent_runner):
action_type = event.get_extra("action_type")
event.trace.record(...)
if action_type == "live":
...
elif streaming_response and not stream_to_general:
...
else:
async for _ in run_agent(...):
yield
final_resp = agent_runner.get_final_llm_resp()
event.trace.record(...)
if not event.is_stopped() or agent_runner.was_aborted():
await self._save_to_history(...)
asyncio.create_task(Metric.upload(...))
```
This removes `runner_registered` and the explicit `finally` in `process`, making the core flow (build → run → record/metric) easier to follow while preserving behavior.
</issue_to_address>
### Comment 4
<location path="astrbot/core/pipeline/process_stage/follow_up.py" line_range="12" />
<code_context>
+from astrbot.core.platform.astr_message_event import AstrMessageEvent
+
+_ACTIVE_AGENT_RUNNERS: dict[str, AgentRunner] = {}
+_FOLLOW_UP_ORDER_STATE: dict[str, dict[str, object]] = {}
+"""UMO-level follow-up order state.
+
</code_context>
<issue_to_address>
**issue (complexity):** Consider encapsulating the per-UMO follow-up state in a dedicated controller class instead of a generic dict-based structure to make the ordering logic easier to understand and maintain.
You can keep all behavior but significantly reduce mental overhead by encapsulating the per‑UMO state into a small class instead of using `dict[str, object]` with string keys and repeated assertions.
### 1. Replace `dict[str, object]` with a typed controller
Right now:
```python
_FOLLOW_UP_ORDER_STATE: dict[str, dict[str, object]] = {}
```
and you manually manage:
- `"condition"`
- `"statuses"`
- `"next_order"`
- `"next_turn"`
across `_get_follow_up_order_state`, `_advance_follow_up_turn_locked`, `_allocate_follow_up_order`, `_mark_follow_up_consumed`, `_activate_and_wait_follow_up_turn`, `_finish_follow_up_turn`.
You can move that logic into a small controller class:
```python
@dataclass(slots=True)
class _FollowUpOrderController:
condition: asyncio.Condition
statuses: dict[int, str]
next_order: int
next_turn: int
def __init__(self) -> None:
self.condition = asyncio.Condition()
self.statuses = {}
self.next_order = 0
self.next_turn = 0
def allocate_order(self) -> int:
seq = self.next_order
self.next_order += 1
self.statuses[seq] = "pending"
return seq
def _advance_turn_locked(self) -> None:
while True:
status = self.statuses.get(self.next_turn)
if status in ("consumed", "finished"):
self.statuses.pop(self.next_turn, None)
self.next_turn += 1
continue
break
```
Then your global can become:
```python
_FOLLOW_UP_ORDER_STATE: dict[str, _FollowUpOrderController] = {}
```
with a simpler getter:
```python
def _get_follow_up_order_state(umo: str) -> _FollowUpOrderController:
state = _FOLLOW_UP_ORDER_STATE.get(umo)
if state is None:
state = _FollowUpOrderController()
_FOLLOW_UP_ORDER_STATE[umo] = state
return state
```
### 2. Move the async operations into the controller
The three async helpers can be simplified and de‑duplicated inside the controller and keep cleanup logic in one place:
```python
class _FollowUpOrderController:
# ... from above ...
async def mark_consumed(self, umo: str, seq: int) -> None:
async with self.condition:
if seq in self.statuses and self.statuses[seq] != "finished":
self.statuses[seq] = "consumed"
self._advance_turn_locked()
self.condition.notify_all()
self._cleanup_if_idle(umo)
async def activate_and_wait_turn(self, seq: int) -> None:
async with self.condition:
if seq in self.statuses:
self.statuses[seq] = "active"
while self.next_turn != seq:
await self.condition.wait()
async def finish_turn(self, umo: str, seq: int) -> None:
async with self.condition:
if seq in self.statuses:
self.statuses[seq] = "finished"
self._advance_turn_locked()
self.condition.notify_all()
self._cleanup_if_idle(umo)
def _cleanup_if_idle(self, umo: str) -> None:
if not self.statuses and _ACTIVE_AGENT_RUNNERS.get(umo) is None:
_FOLLOW_UP_ORDER_STATE.pop(umo, None)
```
Your top‑level helpers become thin wrappers:
```python
def _get_state_if_any(umo: str) -> _FollowUpOrderController | None:
return _FOLLOW_UP_ORDER_STATE.get(umo)
async def _mark_follow_up_consumed(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.mark_consumed(umo, seq)
async def _activate_and_wait_follow_up_turn(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.activate_and_wait_turn(seq)
async def _finish_follow_up_turn(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.finish_turn(umo, seq)
```
### 3. Benefits without changing behavior
- Removes stringly‑typed keys (`"condition"`, `"statuses"`, …) and `assert isinstance(...)` calls.
- Localizes the ordering invariants and cleanup rules inside `_FollowUpOrderController`, so you don’t have to mentally track them across multiple functions.
- Keeps your existing public API (`_mark_follow_up_consumed`, `_activate_and_wait_follow_up_turn`, `_finish_follow_up_turn`, `_allocate_follow_up_order`) intact, so the module’s external behavior and call sites remain unchanged.
</issue_to_address>Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.
| def _event_follow_up_text(event: AstrMessageEvent) -> str: | ||
| text = (event.get_message_str() or "").strip() | ||
| if text: | ||
| return text | ||
| return event.get_message_outline().strip() |
There was a problem hiding this comment.
issue: 在调用 .strip() 之前,需要防止 get_message_outline() 返回 None。
如果 get_message_outline() 可能返回 None,最后一行 return event.get_message_outline().strip() 会抛出 AttributeError。建议参照 get_message_str() 的处理方式,例如:outline = (event.get_message_outline() or '').strip() 然后返回 outline。
Original comment in English
issue: Guard against get_message_outline() returning None before calling .strip().
If get_message_outline() can return None, the final return event.get_message_outline().strip() will raise an AttributeError. Consider mirroring the get_message_str() pattern, e.g. outline = (event.get_message_outline() or '').strip() and returning outline.
| class MockEvent: | ||
| def __init__(self, umo: str, sender_id: str): | ||
| self.unified_msg_origin = umo | ||
| self._sender_id = sender_id | ||
|
|
||
| def get_sender_id(self): | ||
| return self._sender_id | ||
|
|
||
|
|
||
| class MockAgentContext: |
There was a problem hiding this comment.
suggestion (testing): 建议为 ToolLoopAgentRunner.follow_up 的边界情况(runner 已完成,以及空/仅空白消息)添加测试用例。
现有的 follow-up 注入测试覆盖了正常路径,但没有覆盖这些边界行为。请添加一些小的单元测试,直接断言:
- 当 runner 已经完成/被中止时,
follow_up()返回None。 - 对于
message_text=""或仅包含空白字符的输入,follow_up()返回None。
这些测试可以使用最小化的 runner 初始化,不需要经过完整的工具执行流程。
建议实现:
class MockAgentContext:
def __init__(self, event):
self.event = event
@pytest.mark.asyncio
async def test_follow_up_returns_none_when_runner_done(runner):
# Mark the runner as done/aborted before calling follow_up
runner.agent_done()
ctx = MockAgentContext(
MockEvent(umo="test-origin", sender_id="test-sender"),
)
result = await runner.follow_up(ctx, "some follow-up message")
assert result is None
@pytest.mark.asyncio
@pytest.mark.parametrize("message_text", ["", " "])
async def test_follow_up_returns_none_for_empty_or_whitespace_message(runner, message_text):
ctx = MockAgentContext(
MockEvent(umo="test-origin", sender_id="test-sender"),
)
result = await runner.follow_up(ctx, message_text)
assert result is None
@pytest.fixture这些测试基于以下假设:
- 存在一个
runnerfixture,能够返回一个已完全初始化的ToolLoopAgentRunner实例。 ToolLoopAgentRunner暴露了agent_done()方法,用于将 runner 切换到“已完成/中止”的状态。ToolLoopAgentRunner.follow_up是一个async方法,签名类似于follow_up(agent_context, message_text),并在上述边界情况下返回None。
如果你代码库中的实现与这些假设不一致,你需要:
- 在测试中更新 fixture 的名称/类型(例如把
runner替换为你实际使用的 fixture)。 - 用正确的方式替代
runner.agent_done()去标记 runner 已完成/中止(例如runner.abort(),设置某个标志位等)。 - 如果
follow_up是同步的,移除await和@pytest.mark.asyncio;或者根据实际实现调整调用方式/函数签名。
Original comment in English
suggestion (testing): Consider adding tests for ToolLoopAgentRunner.follow_up edge cases (done runner and empty/whitespace messages).
The follow-up injection tests cover the happy path, but these edge behaviors aren’t exercised. Please add small unit tests that directly assert:
follow_up()returnsNonewhen the runner is already done/aborted.follow_up()returnsNoneformessage_text=""or whitespace-only input.
These can use a minimal runner setup without going through the full tool-execution flow.
Suggested implementation:
class MockAgentContext:
def __init__(self, event):
self.event = event
@pytest.mark.asyncio
async def test_follow_up_returns_none_when_runner_done(runner):
# Mark the runner as done/aborted before calling follow_up
runner.agent_done()
ctx = MockAgentContext(
MockEvent(umo="test-origin", sender_id="test-sender"),
)
result = await runner.follow_up(ctx, "some follow-up message")
assert result is None
@pytest.mark.asyncio
@pytest.mark.parametrize("message_text", ["", " "])
async def test_follow_up_returns_none_for_empty_or_whitespace_message(runner, message_text):
ctx = MockAgentContext(
MockEvent(umo="test-origin", sender_id="test-sender"),
)
result = await runner.follow_up(ctx, message_text)
assert result is None
@pytest.fixtureThese tests assume:
- There is a
runnerfixture that returns a fully initializedToolLoopAgentRunnerinstance. ToolLoopAgentRunnerexposes anagent_done()method that transitions the runner into a "done/aborted" state.ToolLoopAgentRunner.follow_upis anasyncmethod with signature similar tofollow_up(agent_context, message_text)and returnsNonein the edge cases described.
If any of these assumptions differ in your codebase, you should:
- Update the fixture name/type in the tests (
runner→ your actual fixture). - Replace
runner.agent_done()with the correct way to mark the runner as done/aborted (e.g.runner.abort(), setting a flag, etc.). - Remove
awaitand@pytest.mark.asyncioiffollow_upis synchronous, or adjust the call/signature to match the actual implementation.
| async def process( | ||
| self, event: AstrMessageEvent, provider_wake_prefix: str | ||
| ) -> AsyncGenerator[None, None]: | ||
| follow_up_capture: FollowUpCapture | None = None |
There was a problem hiding this comment.
issue (complexity): 建议将 follow-up 处理逻辑和 runner 注册逻辑抽取到专门的上下文管理器中,从而让主流程保持线性、更易阅读。
你可以保留现在的行为,但通过把新增关注点(follow-up + runner 注册)封装到小的辅助函数/上下文管理器中,来降低复杂度。这可以移除跨作用域的标志变量和嵌套的 try/finally,同时保留原有语义。
1. 使用异步上下文管理器封装 follow-up 处理
与其在整个方法中手动跟踪 follow_up_capture、follow_up_consumed_marked 和 follow_up_activated,不如把这些逻辑集中起来:
# follow_up.py (or similar)
from contextlib import asynccontextmanager
@asynccontextmanager
async def follow_up_flow(event: AstrMessageEvent):
follow_up_capture = try_capture_follow_up(event)
consumed_marked = False
activated = False
if follow_up_capture:
consumed_marked, activated = await prepare_follow_up_capture(follow_up_capture)
if consumed_marked:
logger.info(
"Follow-up ticket already consumed, stopping processing. umo=%s, seq=%s",
event.unified_msg_origin,
follow_up_capture.ticket.seq,
)
# Do not yield; caller should detect this via state
await finalize_follow_up_capture(
follow_up_capture, activated=activated, consumed_marked=consumed_marked
)
yield None # indicate "short-circuit"
return
try:
yield follow_up_capture
finally:
if follow_up_capture:
await finalize_follow_up_capture(
follow_up_capture, activated=activated, consumed_marked=consumed_marked
)在 process 中使用:
async def process(self, event: AstrMessageEvent, provider_wake_prefix: str) -> AsyncGenerator[None, None]:
try:
async with follow_up_flow(event) as follow_up_capture:
# short-circuited because ticket consumed
if follow_up_capture is None and try_capture_follow_up(event) is not None:
return
streaming_response = self.streaming_response
if (enable_streaming := event.get_extra("enable_streaming")) is not None:
streaming_response = bool(enable_streaming)
...
await event.send_typing()
await call_event_hook(event, EventType.OnWaitingLLMRequestEvent)
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
...
except Exception as e:
...这样可以移除顶层的 follow_up_* 标志变量以及 process 末尾的 finally,让与 follow-up 相关的逻辑自成一体。
2. 使用上下文管理器封装 runner 注册
同样地,可以用一个小的上下文管理器替换 runner_registered 标志和内部的 finally:
# follow_up.py (or a more generic module)
from contextlib import contextmanager
@contextmanager
def active_runner(origin: str, agent_runner: AgentRunner):
register_active_runner(origin, agent_runner)
try:
yield
finally:
unregister_active_runner(origin, agent_runner)在会话锁内部使用:
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
...
agent_runner = build_result.agent_runner
req = build_result.provider_request
provider = build_result.provider
reset_coro = build_result.reset_coro
...
if await call_event_hook(event, EventType.OnLLMRequestEvent, req):
if reset_coro:
reset_coro.close()
return
if reset_coro:
await reset_coro
with active_runner(event.unified_msg_origin, agent_runner):
action_type = event.get_extra("action_type")
event.trace.record(...)
if action_type == "live":
...
elif streaming_response and not stream_to_general:
...
else:
async for _ in run_agent(...):
yield
final_resp = agent_runner.get_final_llm_resp()
event.trace.record(...)
if not event.is_stopped() or agent_runner.was_aborted():
await self._save_to_history(...)
asyncio.create_task(Metric.upload(...))这样可以移除 runner_registered 和 process 中显式的 finally,让核心流程(构建 → 运行 → 记录/打点)更易于理解,同时保留现有行为。
Original comment in English
issue (complexity): Consider extracting the follow-up handling and runner registration logic into dedicated context managers to keep the main process flow linear and easier to read.
You can keep the new behavior but reduce complexity by encapsulating the new concerns (follow-up + runner registration) into small helpers/context managers. This removes the cross-scope flags and nested try/finally while preserving semantics.
1. Wrap follow-up handling in an async context manager
Instead of manually tracking follow_up_capture, follow_up_consumed_marked, and follow_up_activated across the whole method, you can centralize this logic:
# follow_up.py (or similar)
from contextlib import asynccontextmanager
@asynccontextmanager
async def follow_up_flow(event: AstrMessageEvent):
follow_up_capture = try_capture_follow_up(event)
consumed_marked = False
activated = False
if follow_up_capture:
consumed_marked, activated = await prepare_follow_up_capture(follow_up_capture)
if consumed_marked:
logger.info(
"Follow-up ticket already consumed, stopping processing. umo=%s, seq=%s",
event.unified_msg_origin,
follow_up_capture.ticket.seq,
)
# Do not yield; caller should detect this via state
await finalize_follow_up_capture(
follow_up_capture, activated=activated, consumed_marked=consumed_marked
)
yield None # indicate "short-circuit"
return
try:
yield follow_up_capture
finally:
if follow_up_capture:
await finalize_follow_up_capture(
follow_up_capture, activated=activated, consumed_marked=consumed_marked
)Usage in process:
async def process(self, event: AstrMessageEvent, provider_wake_prefix: str) -> AsyncGenerator[None, None]:
try:
async with follow_up_flow(event) as follow_up_capture:
# short-circuited because ticket consumed
if follow_up_capture is None and try_capture_follow_up(event) is not None:
return
streaming_response = self.streaming_response
if (enable_streaming := event.get_extra("enable_streaming")) is not None:
streaming_response = bool(enable_streaming)
...
await event.send_typing()
await call_event_hook(event, EventType.OnWaitingLLMRequestEvent)
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
...
except Exception as e:
...This removes the top-level follow_up_* flags and the final finally block in process, keeping follow-up concerns self-contained.
2. Wrap runner registration in a context manager
Similarly, the runner_registered flag and inner finally can be replaced by a small context manager:
# follow_up.py (or a more generic module)
from contextlib import contextmanager
@contextmanager
def active_runner(origin: str, agent_runner: AgentRunner):
register_active_runner(origin, agent_runner)
try:
yield
finally:
unregister_active_runner(origin, agent_runner)Usage inside the session lock:
async with session_lock_manager.acquire_lock(event.unified_msg_origin):
...
agent_runner = build_result.agent_runner
req = build_result.provider_request
provider = build_result.provider
reset_coro = build_result.reset_coro
...
if await call_event_hook(event, EventType.OnLLMRequestEvent, req):
if reset_coro:
reset_coro.close()
return
if reset_coro:
await reset_coro
with active_runner(event.unified_msg_origin, agent_runner):
action_type = event.get_extra("action_type")
event.trace.record(...)
if action_type == "live":
...
elif streaming_response and not stream_to_general:
...
else:
async for _ in run_agent(...):
yield
final_resp = agent_runner.get_final_llm_resp()
event.trace.record(...)
if not event.is_stopped() or agent_runner.was_aborted():
await self._save_to_history(...)
asyncio.create_task(Metric.upload(...))This removes runner_registered and the explicit finally in process, making the core flow (build → run → record/metric) easier to follow while preserving behavior.
| from astrbot.core.platform.astr_message_event import AstrMessageEvent | ||
|
|
||
| _ACTIVE_AGENT_RUNNERS: dict[str, AgentRunner] = {} | ||
| _FOLLOW_UP_ORDER_STATE: dict[str, dict[str, object]] = {} |
There was a problem hiding this comment.
issue (complexity): 建议使用一个专门的控制器类来封装每个 UMO 的 follow-up 状态,而不是用通用的基于 dict 的结构,这样可以让排序逻辑更易理解和维护。
你可以保持所有现有行为,但通过把每个 UMO 的状态封装到一个小类中,而不是使用带字符串 key 和重复断言的 dict[str, object],显著降低心智负担。
1. 用类型化控制器替代 dict[str, object]
目前:
_FOLLOW_UP_ORDER_STATE: dict[str, dict[str, object]] = {}你需要手动维护:
"condition""statuses""next_order""next_turn"
这些字段分散在 _get_follow_up_order_state、_advance_follow_up_turn_locked、_allocate_follow_up_order、_mark_follow_up_consumed、_activate_and_wait_follow_up_turn、_finish_follow_up_turn 等函数中。
可以把这些逻辑集中到一个小的控制器类里:
@dataclass(slots=True)
class _FollowUpOrderController:
condition: asyncio.Condition
statuses: dict[int, str]
next_order: int
next_turn: int
def __init__(self) -> None:
self.condition = asyncio.Condition()
self.statuses = {}
self.next_order = 0
self.next_turn = 0
def allocate_order(self) -> int:
seq = self.next_order
self.next_order += 1
self.statuses[seq] = "pending"
return seq
def _advance_turn_locked(self) -> None:
while True:
status = self.statuses.get(self.next_turn)
if status in ("consumed", "finished"):
self.statuses.pop(self.next_turn, None)
self.next_turn += 1
continue
break然后全局变量可以改为:
_FOLLOW_UP_ORDER_STATE: dict[str, _FollowUpOrderController] = {}配合一个更简单的 getter:
def _get_follow_up_order_state(umo: str) -> _FollowUpOrderController:
state = _FOLLOW_UP_ORDER_STATE.get(umo)
if state is None:
state = _FollowUpOrderController()
_FOLLOW_UP_ORDER_STATE[umo] = state
return state2. 把异步操作移入控制器
三个异步辅助函数可以在控制器内部简化并去重,并把清理逻辑集中到一个地方:
class _FollowUpOrderController:
# ... from above ...
async def mark_consumed(self, umo: str, seq: int) -> None:
async with self.condition:
if seq in self.statuses and self.statuses[seq] != "finished":
self.statuses[seq] = "consumed"
self._advance_turn_locked()
self.condition.notify_all()
self._cleanup_if_idle(umo)
async def activate_and_wait_turn(self, seq: int) -> None:
async with self.condition:
if seq in self.statuses:
self.statuses[seq] = "active"
while self.next_turn != seq:
await self.condition.wait()
async def finish_turn(self, umo: str, seq: int) -> None:
async with self.condition:
if seq in self.statuses:
self.statuses[seq] = "finished"
self._advance_turn_locked()
self.condition.notify_all()
self._cleanup_if_idle(umo)
def _cleanup_if_idle(self, umo: str) -> None:
if not self.statuses and _ACTIVE_AGENT_RUNNERS.get(umo) is None:
_FOLLOW_UP_ORDER_STATE.pop(umo, None)顶层辅助函数就可以变成很薄的包装:
def _get_state_if_any(umo: str) -> _FollowUpOrderController | None:
return _FOLLOW_UP_ORDER_STATE.get(umo)
async def _mark_follow_up_consumed(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.mark_consumed(umo, seq)
async def _activate_and_wait_follow_up_turn(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.activate_and_wait_turn(seq)
async def _finish_follow_up_turn(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.finish_turn(umo, seq)3. 在不改变行为的前提下获得的好处
- 移除了基于字符串 key(
"condition"、"statuses"等)以及assert isinstance(...)的“字符串驱动”结构。 - 把排序不变式和清理规则局部化到
_FollowUpOrderController内部,不再需要跨多个函数在脑中跟踪这些规则。 - 保留现有的对外 API(
_mark_follow_up_consumed、_activate_and_wait_follow_up_turn、_finish_follow_up_turn、_allocate_follow_up_order),因此模块的外部行为和调用方可以保持不变。
Original comment in English
issue (complexity): Consider encapsulating the per-UMO follow-up state in a dedicated controller class instead of a generic dict-based structure to make the ordering logic easier to understand and maintain.
You can keep all behavior but significantly reduce mental overhead by encapsulating the per‑UMO state into a small class instead of using dict[str, object] with string keys and repeated assertions.
1. Replace dict[str, object] with a typed controller
Right now:
_FOLLOW_UP_ORDER_STATE: dict[str, dict[str, object]] = {}and you manually manage:
"condition""statuses""next_order""next_turn"
across _get_follow_up_order_state, _advance_follow_up_turn_locked, _allocate_follow_up_order, _mark_follow_up_consumed, _activate_and_wait_follow_up_turn, _finish_follow_up_turn.
You can move that logic into a small controller class:
@dataclass(slots=True)
class _FollowUpOrderController:
condition: asyncio.Condition
statuses: dict[int, str]
next_order: int
next_turn: int
def __init__(self) -> None:
self.condition = asyncio.Condition()
self.statuses = {}
self.next_order = 0
self.next_turn = 0
def allocate_order(self) -> int:
seq = self.next_order
self.next_order += 1
self.statuses[seq] = "pending"
return seq
def _advance_turn_locked(self) -> None:
while True:
status = self.statuses.get(self.next_turn)
if status in ("consumed", "finished"):
self.statuses.pop(self.next_turn, None)
self.next_turn += 1
continue
breakThen your global can become:
_FOLLOW_UP_ORDER_STATE: dict[str, _FollowUpOrderController] = {}with a simpler getter:
def _get_follow_up_order_state(umo: str) -> _FollowUpOrderController:
state = _FOLLOW_UP_ORDER_STATE.get(umo)
if state is None:
state = _FollowUpOrderController()
_FOLLOW_UP_ORDER_STATE[umo] = state
return state2. Move the async operations into the controller
The three async helpers can be simplified and de‑duplicated inside the controller and keep cleanup logic in one place:
class _FollowUpOrderController:
# ... from above ...
async def mark_consumed(self, umo: str, seq: int) -> None:
async with self.condition:
if seq in self.statuses and self.statuses[seq] != "finished":
self.statuses[seq] = "consumed"
self._advance_turn_locked()
self.condition.notify_all()
self._cleanup_if_idle(umo)
async def activate_and_wait_turn(self, seq: int) -> None:
async with self.condition:
if seq in self.statuses:
self.statuses[seq] = "active"
while self.next_turn != seq:
await self.condition.wait()
async def finish_turn(self, umo: str, seq: int) -> None:
async with self.condition:
if seq in self.statuses:
self.statuses[seq] = "finished"
self._advance_turn_locked()
self.condition.notify_all()
self._cleanup_if_idle(umo)
def _cleanup_if_idle(self, umo: str) -> None:
if not self.statuses and _ACTIVE_AGENT_RUNNERS.get(umo) is None:
_FOLLOW_UP_ORDER_STATE.pop(umo, None)Your top‑level helpers become thin wrappers:
def _get_state_if_any(umo: str) -> _FollowUpOrderController | None:
return _FOLLOW_UP_ORDER_STATE.get(umo)
async def _mark_follow_up_consumed(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.mark_consumed(umo, seq)
async def _activate_and_wait_follow_up_turn(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.activate_and_wait_turn(seq)
async def _finish_follow_up_turn(umo: str, seq: int) -> None:
state = _get_state_if_any(umo)
if state:
await state.finish_turn(umo, seq)3. Benefits without changing behavior
- Removes stringly‑typed keys (
"condition","statuses", …) andassert isinstance(...)calls. - Localizes the ordering invariants and cleanup rules inside
_FollowUpOrderController, so you don’t have to mentally track them across multiple functions. - Keeps your existing public API (
_mark_follow_up_consumed,_activate_and_wait_follow_up_turn,_finish_follow_up_turn,_allocate_follow_up_order) intact, so the module’s external behavior and call sites remain unchanged.
There was a problem hiding this comment.
Code Review
This PR introduces a follow-up message handling mechanism for the ToolLoopAgentRunner. While the implementation is generally clear and well-tested, a critical prompt injection vulnerability has been identified: unvalidated user input is wrapped in a [SYSTEM NOTICE] block, potentially allowing users to bypass system-level instructions. Additionally, the review highlighted two potential race condition issues in the concurrency and state management logic, which could lead to data inconsistency or message loss, and suggested a refactoring for state management to enhance readability and maintainability.
| self._pending_follow_ups: list[FollowUpTicket] = [] | ||
| self._follow_up_seq = 0 |
There was a problem hiding this comment.
这里存在一个竞态条件。_pending_follow_ups 列表会在没有同步机制的情况下被多个协程访问。follow_up 方法(从一个协程调用)会向列表追加内容,而 _consume_follow_up_notice 和 _resolve_unconsumed_follow_ups 等方法(从代理的 step 协程调用)会读取并清空它。这可能导致后续消息丢失。
为了修复这个问题,建议使用 asyncio.Lock 来保护对 self._pending_follow_ups 的所有访问。
- 在这里初始化一个锁:
self._follow_up_lock = asyncio.Lock()。 - 将
follow_up,_resolve_unconsumed_follow_ups, 和_consume_follow_up_notice方法改为async。 - 在这些方法中,将修改
_pending_follow_ups的临界区代码包裹在async with self._follow_up_lock:中。 - 相应地更新这些方法的调用方,使用
await。例如,_merge_follow_up_notice也需要变成async。
| self._pending_follow_ups: list[FollowUpTicket] = [] | |
| self._follow_up_seq = 0 | |
| self._pending_follow_ups: list[FollowUpTicket] = [] | |
| self._follow_up_seq = 0 | |
| self._follow_up_lock = asyncio.Lock() |
| def _get_follow_up_order_state(umo: str) -> dict[str, object]: | ||
| state = _FOLLOW_UP_ORDER_STATE.get(umo) | ||
| if state is None: | ||
| state = { | ||
| "condition": asyncio.Condition(), | ||
| # Sequence status map for strict in-order resume after unresolved follow-ups. | ||
| "statuses": {}, | ||
| # Stable allocator for arrival order; never decreases for the same UMO state. | ||
| "next_order": 0, | ||
| # The sequence currently allowed to continue main internal flow. | ||
| "next_turn": 0, | ||
| } | ||
| _FOLLOW_UP_ORDER_STATE[umo] = state | ||
| return state |
There was a problem hiding this comment.
目前后续消息的排序状态是通过一个 dict[str, object] 来管理的。这使得代码更难阅读和维护,因为你需要使用字符串键和 isinstance 断言来访问状态字段。
建议将此重构为使用 dataclass 来管理状态。这将提供更好的类型安全性、自动补全,并使代码更具自文档性。
例如:
from dataclasses import dataclass, field
@dataclass
class FollowUpOrderState:
condition: asyncio.Condition = field(default_factory=asyncio.Condition)
statuses: dict[int, str] = field(default_factory=dict)
next_order: int = 0
next_turn: int = 0
_FOLLOW_UP_ORDER_STATE: dict[str, FollowUpOrderState] = {}
def _get_follow_up_order_state(umo: str) -> FollowUpOrderState:
state = _FOLLOW_UP_ORDER_STATE.get(umo)
if state is None:
state = FollowUpOrderState()
_FOLLOW_UP_ORDER_STATE[umo] = state
return state之后,其他函数就可以更新为使用这个类型化的状态对象,从而不再需要 isinstance 检查。
|
Generated docs update PR (pending manual review): AI change summary:
Experimental bot notice:
|
* 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…
…trBotDevs#5484) * feat: implement follow-up message handling in ToolLoopAgentRunner * fix: correct import path for follow-up module in InternalAgentSubStage
fixes: #5385
Modifications / 改动点
Screenshots or Test Results / 运行截图或测试结果
Checklist / 检查清单
requirements.txt和pyproject.toml文件相应位置。/ I have ensured that no new dependencies are introduced, OR if new dependencies are introduced, they have been added to the appropriate locations inrequirements.txtandpyproject.toml.Summary by Sourcery
为正在执行中的 ToolLoopAgentRunner 添加后续消息(follow-up message)处理,并将其集成到内部智能体(agent)流水线中。
New Features:
Enhancements:
Tests:
Original summary in English
Summary by Sourcery
Add follow-up message handling for in-progress ToolLoopAgentRunner executions and integrate it into the internal agent pipeline.
New Features:
Enhancements:
Tests: