* fix: roll back document rows when FAISS insert fails
Validate embedding shape/dimension before local writes. If DocumentStorage
commits but FAISS write fails, best-effort delete the partial FAISS ids and
document rows so the two stores do not diverge.
* fix: clean up partial KB upload state before metadata commit
Track metadata_committed right after commit succeeds. On failure before
metadata commit, roll back chunks/vectors first, then residual KB rows and
media files. After metadata is committed, keep the document even if stats
or refresh fails.
* test: cover knowledge base upload atomicity and rollback
Add unit tests for insert_batch FAISS failure rollback, dimension validation
before document writes, upload cleanup order, no-rollback after metadata
commit, and real DocumentStorage+FAISS no-orphan cases.
* fix: log media directory cleanup failures after upload rollback
Best-effort rmdir of the per-document media directory should still emit a
warning when removal fails so incomplete cleanup is diagnosable.
* fix: exit MCP anyio contexts in the lifecycle task on disable
#9070 moved MCP shutdown cleanup into the per-server lifecycle task so
that the anyio cancel scopes entered in connect_to_server() are exited
from the task that entered them. Running the cleanup through
asyncio.shield() defeats that: shield wraps the coroutine in a new
task, so disabling a server still fails with "Attempted to exit cancel
scope in a different task than it was entered in" and leaves the scope
state corrupted -- the failure mode behind the CPU spin reported in
#9068.
Await _terminate_mcp_client() directly instead. The graceful disable
path has no cancellation in flight, and on forced shutdown the
lifecycle task still runs its finally block in-task, so the shield only
served to break task affinity.
Closes#9068
* fix: absorb late cancellations during MCP shutdown cleanup
A cancellation delivered while the lifecycle task is already running its
finally-block cleanup would abort _terminate_mcp_client() halfway,
stranding the runtime entry and the MCP transport. Retry the cleanup in
the same task and uncancel() the absorbed request instead, so a forced
shutdown can no longer skip it.
* fix: guard Task.uncancel() for Python 3.10 runtimes
Task.uncancel() only exists on Python 3.11+. On 3.10 absorbing the
CancelledError is sufficient, so skip the call when unavailable.
* fix(webui): fix plugin activation status sync issue in turn_on_plugin #9149
* fix(webui): sync plugin activation status after enabling
* Potential fix for pull request finding
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
---------
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
* fix: adapt MiMo STT to V2.5 models and reject non-WAV audio
The MiMo-V2 series went offline on 2026-06-30, so the default STT model
mimo-v2-omni fails for every default configuration. Switch the default
to mimo-v2.5-asr, the dedicated speech recognition model whose official
docs use exactly the bare input_audio payload this provider sends.
For non-ASR multimodal models such as mimo-v2.5, the audio understanding
docs require a text instruction alongside the audio, so restore the
system/user transcription prompts for that model family only.
Also validate that the resolved audio payload really is RIFF/WAVE before
calling the API: when a platform voice file (e.g. Tencent SILK from QQ)
slips through the WAV conversion chain unchanged, fail locally with an
actionable error instead of the opaque HTTP 400 from the API.
Fixes#9113
* fix: accept unpadded MiMo wav headers
---------
Co-authored-by: tangtaizong666 <212687958+tangtaizong666@users.noreply.github.com>
* fix(kb): clean up KBMedia records on document delete, fix update_kb_stats
- delete_document_by_id now deletes associated KBMedia records before
removing the KBDocument row, preventing orphaned media data.
- update_kb_stats now passes kb_id filter to vec_db.count_documents,
fixing chunk count that previously counted across all KBs.
- add tests for document deletion cleanup and update_kb_stats behavior.
* fix(kb): merge media and document deletion into single transaction
* fix: skip _unbind_plugin for inactivated plugins in reload()
* fix: only skip _unbind_plugin for deactivated plugins on specified-plugin reload path
Revert the full reload unconditional smd.activated guard added in 555fcfa; it is unnecessary because load() re-adds everything after the clear anyway. Keep the guard only on the specified-plugin reload path.
* test: set tool active=False precondition in turn_on_plugin test; clean up mock_load in unbind test
When LLM passes a directory path to astrbot_file_read_tool, the tool previously
returned Error: [Errno 13] Permission denied, misleading the LLM into thinking
it was a permissions issue. The real cause: _probe_local_file() calls open('rb')
on the path, which fails on directories with Errno 13 on Windows. This is caught
by except PermissionError and displayed as-is.
Fix: Add os.path.isdir() check in FileReadTool.call() before any file I/O, at
the earliest safe point after path normalization and permission validation.
Returns a clear message: '<path> is a directory, not a file. Use a file path
instead, or use astrbot_execute_shell to list directory contents.'
Changes:
- astrbot/core/tools/computer_tools/fs.py: add isdir guard
- tests/test_computer_fs_tools.py: add test_file_read_tool_rejects_directory_with_clear_message
* fix: preserve assistant messages with reasoning_content in sanitize pass
When _sanitize_assistant_messages encounters an assistant message with empty content and no tool_calls but with reasoning_content, keep it with content set to empty string instead of dropping it. Reasoning models (DeepSeek V4, MiMo, etc.) require this history for subsequent turn validation.
* Update astrbot/core/provider/sources/openai_source.py
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
* fix: default to empty TokenUsage when completion.usage is None
When completion.usage is None (e.g. certain proxy/streaming edge cases), llm_response.usage stayed unset (None). Plugins accessing .input_tokens on it would crash with AttributeError.
Always assign llm_response.usage — extract from completion.usage if present, otherwise fall back to a zeroed TokenUsage().
Closes#8605
---------
Co-authored-by: gemini-code-assist[bot] <176961590+gemini-code-assist[bot]@users.noreply.github.com>
Co-authored-by: renchonghan <renchonghan@users.noreply.github.com>