* fix(wecombot): align media upload protocol
* fix(wecombot): deliver outbox media in reply and fix tool call recording
- Integrate _send_media into reply_message and reply_message_chunk so
sandbox outbox images/voices/files are uploaded and sent instead of
being silently dropped.
- Add missing import base64 that caused _send_media to fail with a
NameError swallowed by its except clause.
- Change yiri2target to return component dicts (text/image/voice/file)
so callers can distinguish text from media.
- Fix _get_message_for_tool_context using result.first()/row[0] which
returned a raw string instead of the ORM object, causing
"'str' object has no attribute 'pipeline_id'" in tool call recording.
Use result.scalars().first() per SQLAlchemy 2.0 convention.
* fix(pipeline): collect outbox attachments on final chunk with empty content
When the last streaming chunk has is_final=True but empty content
(e.g. the LLM sends all text in earlier chunks), the 'if result.content'
branch is skipped entirely, so _append_outbound_attachments never runs
and sandbox outbox images are silently dropped.
Add an elif branch for _is_final_assistant_message that creates an
empty MessageChain and still collects outbox attachments, so images
are delivered even when the final chunk carries no text.
* fix(box): bypass stdout truncation when reading outbox via exec
_read_outbox_via_exec used execute_tool which returns _serialize_result
where stdout is truncated to output_limit_chars (4000). A 7KB JPEG
encodes to ~9400 base64 chars, so the JSON payload was truncated and
json.loads failed silently, returning an empty list.
Call client.execute directly to get the raw BoxExecutionResult with
untruncated stdout, so base64 file data is preserved.
* fix(tests): adapt box and wrapper tests for client.execute and strict is_final check
- wrapper.py: restrict outbox collection on empty-content chunks to
actual MessageChunk instances with is_final=True, not generic Mock
objects that happen to have role='assistant'
- test_box_service.py: update _read_outbox_via_exec tests to mock
client.execute (returning BoxExecutionResult) instead of
execute_tool, matching the implementation change
* chore(wecombot): remove temporary upload log
* test(box): preserve direct outbox read and cleanup coverage
---------
Co-authored-by: fdc310 <2213070223@qq.com>
Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
The local-agent runner's system prompt is a static string with no
template-variable support, so the model had no anchor for "today" and
resolved relative time references (e.g. "this quarter", "latest")
against whichever period was best represented in training data instead
of the real date, sometimes confidently answering with stale
information for time-sensitive questions.
PreProcessor now appends a short, deterministically-computed
"Current date: ..." note to the system prompt on every request for
local-agent pipelines, alongside guidance to verify time-sensitive
facts with a search tool rather than answering from memory. The
existing skill-awareness prompt injection is refactored to share the
same append-to-system-prompt helper.
* feat: report independent instance and workspace identities
* test: include workspace in OAuth callback fixture
* ci: pin production cloud adapter to Space release
* fix: preserve authenticated Workspace telemetry attribution
* ci: pin production cloud adapter to final Space release
---------
Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
* fix(cloud): show owner model balance and enforce single owner
* fix(migrations): create owner index idempotently
---------
Co-authored-by: dadachann <185672915+dadachann@users.noreply.github.com>
Telegram file.file_path is a full URL of the form
https://api.telegram.org/file/bot<TOKEN>/<path> that embeds the bot
token. Since #2362 this URL was copied into Image.url, so the token was
serialized into the message chain and thereby persisted to the
monitoring database, shown in the dashboard, and forwarded to every
installed plugin via event dispatch. Anyone with dashboard or plugin
access could recover the token and take full control of the bot.
Unlike the public CDN URLs used by the other adapters changed in #2362,
Telegram file URLs are only usable with the embedded token, so there is
no safe URL to expose. Store base64 only (as before #2362); the vision
path already relies solely on base64, so nothing downstream changes.
Add a regression test asserting the token never appears in the
converted Image or the serialized message chain.
Co-authored-by: Constantine1916 <Constantine1916@users.noreply.github.com>
When the LLM (e.g. MiniMax-M3) returns multiple rounds after tool calls, the _StreamAccumulator was initialized with initial_content=first_content, causing every subsequent round to repeat the entire first message. Remove the re-seeding so each round starts with a clean accumulator.
Add regression test verifying multi-round tool call content is not duplicated.
Move the Lark SDK synchronous connection URL lookup off the main asyncio loop, serialize reconnects, and cover the incident with a non-blocking regression test.
* fix(provider): strip think tags for MiniMax-M3 and other OpenAI-compatible models
MiniMax-M3 (and other OpenAI-compatible providers) emit chain-of-thought
reasoning directly in the content field wrapped in tags, instead
of using a separate reasoning_content field or the legacy CRETIRE_REASONING
markers. The existing remove_think logic only handled CRETIRE_* tags, so
think blocks leaked into user-visible output even when remove_think was enabled.
- Add _ThinkStripState: a stateful filter that correctly handles tags
split across streaming chunk boundaries.
- Add _strip_think classmethod with regex patterns for both and
CRETIRE_* tags.
- Wire think_state into invoke_llm_stream so deltas are filtered before
reaching the accumulator.
- Add remove_think safety net in _StreamAccumulator so the final message
from tool-call rounds also gets stripped.
- Fix remove_think resolution to use defensive nested .get() so
pipelines missing output.misc don't raise AttributeError.
* fix(litellmchat): add missing _CLOSE_TAG class attribute on _ThinkStripState
* fix(provider): handle think stripping across LiteLLM paths
---------
Co-authored-by: WangCham <651122857@qq.com>