* fix(monitoring): restore SQLite token statistics
Allow the monitored SQLite strftime bucket expression through the tenant SQL guard and preserve structured backend errors in the token dashboard. Add persistence and frontend regressions.\n\nVerified-by: independent-review
* test(runtime): accept reconcile timeout in capacity stub
Keep the PostgreSQL capacity probe aligned with the runtime handler contract and assert the bounded reconcile timeout.
---------
Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
The LINE adapter passed text through as a single Plain component,
ignoring the mention payload (mentions[].index/length/isSelf) that the
Line Messaging API includes in the webhook. As a result:
- At(target=bot_account_id) never appeared in the message chain, so the
'at-bot' group respond rule silently dropped every @bot mention.
- The bot only replied when the message happened to match the prefix
rule (e.g. starting with 'ai').
Now LINEMessageConverter reads message.message.mention and builds the
chain per mention position:
- Bot mention (isSelf) -> At(target=bot_account_id) so AtBotRule matches
the same way as other adapters (dingtalk/lark etc.).
- Other mentions -> At(target=<line user id>, display=<mention text>).
At.__str__ already prepends '@', so the display text carries no
double '@' and the rendered text (prefix/regexp rules, quotes,
session context) is byte-identical to before.
- Missing/out-of-bounds mentions are skipped defensively.
target2yiri becomes an instance method (like wechatpad/aiocqhttp) so
the converters can hold bot_account_id; LINEAdapter passes it in from
its own config.
execute_func_call returns list[ContentElement] for MCP tools, but the
runner assigned that list directly to the tool-message content. The
OpenAI chat-completions spec requires tool-message content to be a
string, so OpenAI-compatible endpoints return HTTP 500 when the raw
list is sent.
Serialize the list to a string before building the tool message, using
ContentElement.__str__ which returns the text payload for text elements
and a human-readable placeholder for images and files. Fixes#2457.
SessionManager clears image_base64 on past turns to save memory, and
exclude_none serialization drops the hollowed field entirely, so a
replayed history part can arrive as {'type': 'image_base64'} with no
payload. The converter accessed the missing key unconditionally and
raised KeyError on every turn after an image was sent.
Prefer the base64 payload when present, fall back to an image_url that
survived on the same element, and drop hollow parts otherwise (same
strategy as the existing file-part handling). Fixes#2469.