platform_message_history.created_at stores UTC wall-clock values
(written via TimestampMixin's datetime.now(timezone.utc)), but
delete_platform_message_offset computed the cutoff from naive local
datetime.now(). On hosts outside UTC the comparison was shifted by the
local offset, so the wrong slice of history was deleted.
Generated with [Devin](https://devin.ai)
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
Telegram long-polling floods the log in two ways:
1. python-telegram-bot performs getUpdates through httpx, which logs every request at INFO level. httpx/httpcore were missing from LogManager._NOISY_LOGGER_LEVELS, so those per-request lines (one every ~10s) leaked through.
2. _on_polling_error logged a full traceback for every polling error, including non-network errors such as Conflict (two bot instances polling the same token). Log a concise one-line message for non-network errors and keep the traceback only for NetworkError, which still needs it for recovery.
* feat: group model selection by provider source
* feat: optimize model selector with source filters and sticky groups
* fix: align model filter borders and hide single-source filter
* fix(qqofficial): always send the state=10 closing frame in C2C streaming
QQ's C2C streaming protocol ends a stream with a state=10 frame whose
content must end with \n. AstrBot only sent it while unsent text
remained in the buffer: once the throttled middle frames had flushed
the full reply and the generator ended with an empty tail, no closing
frame went out at all. QQ then timed the stream out and rolled the
whole message back to the first packet, which is why a full 850-char
reply ended up displaying only its first few characters (#10066), and
why some streams hang in "generating" forever.
Close every open segment: when the stream is open (a frame id exists)
but the tail buffer is empty, send a minimal "\n" closing frame; when
nothing was ever sent (no id), keep sending nothing. The tool_call
break path closes the same way through a shared helper so an empty
segment cannot orphan its stream either.
Regression tests pin both paths against a fake _post_send: empty tail
after a middle flush, and break arriving on an empty but open segment.
* fix(qqofficial): treat an empty-Plain tail as empty for the closing frame
Review on #10069: a buffer holding only Plain("") components has a
truthy chain, so the "\n" fallback was skipped, _post_send_one rejected
the empty parsed content, and no state=10 frame went out for an opened
stream. Check for sendable content (non-empty text or a non-Plain
component) instead of chain non-emptiness. Regression test pins the
empty-Plain tail after a middle flush.
* refactor: prepare model images in the local process stage
Prepare current input images before agent construction and after the request hook. Keep original attachments intact and preserve existing tool image behavior.
Reuse the compression toggle for PNG stills and animation montages, with event-owned working files and portable history serialization.
Validation: 2571 Linux tests, 495 Windows regression tests, dashboard build, and live text/JPEG/GIF calls with agnes-3.0-flash.
* test: read source files as UTF-8 in image boundary check
* fix: avoid blanket PNG conversion of model input images
* fix: support updating installed plugins from uploaded archives
* fix: update existing plugins through URL installation
* fix: skip temporary directories during plugin discovery
* fix: stage plugin installs and backups in system temp
* test: align dashboard plugin installer mock with staging API
* feat(dashboard): add chat history pagination and reasoning blocks (#9652)
* refactor(dashboard): remove reasoning char count and loading text
* feat(dashboard): load earlier messages on scroll up
* refactor(dashboard): remove reasoning retry label
* refactor(dashboard): remove thread panel pagination and lazy reasoning
Restore the thread panel to pre-#9652 behavior: full history without
pagination and inline reasoning content. Reverts the out-of-scope thread
additions while keeping main-session pagination and reasoning lazy-loading.
* chore: retrigger smoke test
* fix(dashboard): pause automatic history loading after failure
* feat(dashboard): show progress while loading earlier messages
* fix(dashboard): show reasoning loading errors and retry action
* style(dashboard): align chat loading errors with chat UI
* style(dashboard): make chat loading errors more compact
* refactor(chat): retain complete message content in history pages
* refactor(dashboard): expand reasoning directly from history pages
* refactor(api): remove lazy chat message retrieval endpoint
* fix(kb): space out concurrent waiters in the URL cleaning RateLimiter
URL content cleaning processes all chunks with asyncio.gather and a
shared RateLimiter. Every coroutine read the same last_call_time
before sleeping, so they all woke up at the same moment and fired
their requests together; the configured RPM did not bound that burst
and larger documents could trip the upstream limit.
Reserve the next release slot under a lock (sleeping outside it), so
waiters are released one interval apart, in order. Idle time is still
credited and max_rpm=0 still disables limiting.
Fixes#9995
* fix(kb): reserve RateLimiter slots under a lock so waiters do not burst
The first commit only carried the test; this adds the RateLimiter change
it exercises. Each waiter now reserves the next release slot under an
asyncio.Lock and sleeps until that slot, so concurrent callers are
released one interval apart instead of waking up together.
* fix(kb): preserve rate limit spacing after event loop stalls
---------
Co-authored-by: Soulter <905617992@qq.com>
* fix(lark): fall back to private chat ID when open ID send fails
Private file sends can fail with error 230101 when addressed by open ID,
even though the same file can be delivered to the private chat ID.
Persist private open ID to chat ID mappings from incoming messages,
scoped by platform and app ID, while preserving existing session IDs.
Keep open ID as the primary recipient and retry each rejected message
once with its known private chat ID, reusing the content and UUID.
Do not resend successful components or retry transport exceptions.
Propagate proactive send and attachment upload failures to callers so
failed deliveries are not reported as successful. Preserve the existing
boolean failure handling for replies.
Refs: #9989
* test(lark): cover private chat fallback and delivery failures
Reproduce rejected open ID file sends and verify that the same uploaded
file is retried once with the private chat ID and original UUID.
Cover successful primary sends, mixed message chains, failed deliveries,
upload failures, group routing, transport timeouts, and reply behavior.
Verify stored routes survive adapter recreation, remain scoped to the
bot, and cannot be overwritten by group messages.
Isolate sender-name tests from persistent route storage and supply the
platform and app identity required by route persistence.
Validation: 26 related tests passed; ruff format and ruff check passed.
Live Feishu API validation remains pending application credentials.
Refs: #9989
* feat(chatui): refine settings, composer and streaming experience
* style(chatui): compact composer and narrow message layouts
* style(chatui): align composer gutters and remove excess height
* style(chatui): give composer editing area more height
* fix(chatui): preserve reading width when side panels open
* fix: correct At/AtAll branch order in message outline
Original logic checked At first and then AtAll. However, because AtAll is a subclass of At, the AtAll branch would never be executed. This change moves AtAll before At to ensure the code behaves correctly.
* test: assert AtAll outline renders as [At:全体成员]
WebChat generates its title on the platform session, while the conversation history reads the conversation title column, so WebChat rows were listed as untitled. Fall back to the session display name when a conversation has no title, and let conversation search match session titles as well.
Entering the Data & Logs -> Logs page froze the WebUI for seconds because ConsoleDisplayer rendered each historical log line with a separate appendChild plus a forced synchronous reflow (scrollTop = scrollHeight) per line, and used innerText which triggers a style recalc on every assignment.
Batch the rendered lines into a DocumentFragment and append it once, scroll to the bottom a single time, and use textContent instead of innerText. This collapses O(n) reflows into one and eliminates the main-thread freeze.
_run_job marked a cron job "completed" unless an exception escaped, but
_woke_main_agent never checked the agent runner's terminal state: a run
that dies with a malformed function call at max steps ends in
AgentState.ERROR with no exception, so cron_jobs showed
status=completed, last_error=NULL, and the user only ever saw the
intermediate messages (#9980).
_woke_main_agent now raises when the runner ends in AgentState.ERROR
(carrying the final error text when there is one) and when the main
agent fails to build at all; _run_job's existing handler records both as
failed with last_error. The runner base class gains a read-only `state`
property so callers no longer reach into `_state`.
Tests: two regression cases on _run_job (ERROR terminal state, build
failure) asserting status=failed with the detail in last_error; both red
on current master and green here. Existing fake runners in the file got
the state attribute the runner contract now documents.
Signed-off-by: Yufeng He <40085740+he-yufeng@users.noreply.github.com>
* fix(qqofficial): render markdown for proactive send_by_session messages
* fix(qqofficial): preserve use_markdown_ when splitting media chains
* fix(qqofficial): fall back to content when markdown payload is rejected
* feat(qqofficial): add use_markdown config to gate default markdown sending
* feat(dashboard): add i18n entries for qqofficial use_markdown config
* fix(qqofficial): expose use_markdown on webhook template and clarify label
Add use_markdown to the QQ Official (Webhook) config template so new
webhook platforms expose and save the setting in the WebUI, matching the
WebSocket template. Rename the field label from the ambiguous '主动消息发送模式'
to the clearer '主动消息使用 Markdown' (en/ru translations updated).
Add a regression test asserting both QQ Official templates expose use_markdown.
---------
Co-authored-by: OMSociety <OMSociety@users.noreply.github.com>
* ci: run unit tests on ubuntu, windows, and macos
* ci: promote macos lane to blocking now that it passes
* fix: make the unit test suite pass on Windows
* fix: address Windows CI encoding failures and review findings
- Force UTF-8 stdio for python children on Windows (exec, managed
sessions, python tool) so non-ASCII output survives ANSI code pages
- Try cp936/gbk before mbcs in the decode fallback so Chinese output is
not silently mojibake'd on non-Chinese Windows
- Use PowerShell call-operator quoting for space-containing executables
- Narrow the require_symlink skip to WinError 1314 and raise other OSErrors
- Raise managed-shell test yield budget to absorb CI cold starts
* fix: restore POSIX shell quoting in test_computer exec tests
The PowerShell call-operator form is a syntax error in POSIX shells;
generate the command per platform instead.
* fix: support mixed platform path mappings
* fix: preserve Windows shell output encoding
---------
Co-authored-by: zenfun <zenfun510@gmail.com>
The v-data-table-server in DocumentsTab inherits Vuetify's default
items-per-page-options [10, 25, 50, 100, -1], where -1 renders as 'All'.
Selecting it sends page_size=-1, which the backend clamps to 1, so the
document list shows only one item.
Explicitly set :items-per-page-options to [10, 25, 50, 100] so the 'All'
option is no longer offered, consistent with other pages such as
ConversationPage.
Closes#9907
Two market entries can share the same metadata `name` (different
authors), but the detail flow used `name` as the unique key:
- `openMarketPluginDetail` navigated with `plugin.name` as the route
param, so both entries resolved to the same detail route;
- `selectedMarketPlugin` matched `item.name === pluginId`, always
returning the first same-named entry (wrong version/author/repo/README);
- duplicate `v-for` keys (`plugin.name`) caused Vue to reuse wrong
component instances in the market list;
- the random-plugins section keyed its sampling Map by `name`, collapsing
same-named entries.
Introduce `getMarketPluginKey(plugin)` (`market_plugin_id || repo ||
name`) as the unique market identity, use it for navigation, list keys,
detail lookup (name kept as fallback for legacy deep links), and the
random-plugins sampling.
Fixes#9924