Cherry-picked valuable changes from PR #206:
- hasReadImageProvider supports chain format {"providers":[...]} config
- create_image/video/audio verify file persistence after write with diagnostic logging
- HistoryEntry gains Media field + CollectMedia() for group media context on @mention
- Zalo extractContentAndMedia refactored: all media types via DetectMIMEType/BuildMediaTags, 20MB limit
- Discord/Zalo pass media paths to Record() and collect historical media on @mention
- Zalo send_helpers logs directory contents when checkFileSize stat fails
IsPaired() silently returned false on DB query failures (connection
timeout, pool exhaustion), causing the system to treat already-paired
users/groups as unpaired and create spurious pairing requests.
Changed IsPaired signature to return (bool, error). All 13 callers
across all channels (Telegram, Discord, Slack, Feishu, WhatsApp, Zalo)
and gateway (browser WS, pairing method) now fail-open on DB errors:
log a warning and assume paired, avoiding false rejections.
Pass contactCollector through channel manager to all channel handlers
(Telegram, Discord, Feishu, Slack, Zalo) so contacts are automatically
collected when users interact with the agent.
- Fix compact endpoint using random provider instead of agent's configured provider+model
- Wire auto-compaction for all 5 channel types (telegram, discord, slack, feishu, zalo_personal)
via PendingCompactable interface and InstanceLoader
- Add global PendingCompactionConfig (threshold, keep_recent) to ChannelsConfig
- Wire global config through InstanceLoader and PendingMessagesHandler
- Increase compaction timeout from 45s to 180s for slow providers
- Add pending compaction config card to Behavior tab in config page
- Add HowItWorksCard (expanded by default) and toast notifications to pending messages page
- Add i18n support for all new strings (en/vi/zh)
Extract shared media utilities (MediaInfo, BuildMediaTags, TranscribeAudio,
DetectMIMEType) into internal/channels/media/ and refactor Telegram to use
them. Add full inbound/outbound media support to Discord and Feishu channels
(STT transcription, document extraction, media tags, voice agent routing).
Add WebSocket media upload/serve endpoints and MIME-aware media tags in
chat.send. Split large channel files for maintainability.
Add block.reply event that delivers intermediate assistant text to non-streaming
channels during multi-tool iterations. Includes 2-tier config toggle:
gateway-level default (disabled) + per-channel override (inherit/on/off).
Backend:
- Emit block.reply events from agent loop between tool iterations
- Add BlockReply *bool to GatewayConfig and all 6 channel config structs
- Add BlockReplyChannel interface with ResolveBlockReply() resolution
- Guard delivery in HandleAgentEvent by RunContext.BlockReplyEnabled
- Resolve config at RegisterRun time, pass to consumer goroutine
- Conditional dedup: skip final message if identical to last block reply
UI:
- Gateway settings: Switch toggle for global default
- Per-channel: tri-state select (Inherit from gateway / Enabled / Disabled)
- Protocol: BLOCK_REPLY constant in AgentEventTypes
- Form: coerceBoolSelects for proper JSON boolean serialization
- Revoke paired device now force-closes the active WebSocket connection
via pubsub (EventPairingRevoked → Server.DisconnectByPairing)
- Add group-level pairing policy ("pairing") to Discord, WhatsApp, and
Zalo Personal channels (matching existing Telegram/Feishu pattern)
- Block inactive agents at resolver level (chat, cron, delegation all
reject with "agent is inactive")
- Cascade: setting agent status to "inactive" auto-disables linked
channel instances via EventAgentStatusChanged pubsub
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Discord: include sender's mention format in [From:] annotation so models
use the correct user ID, and strip the bot's own @mention from message
content to prevent the model from echoing it back.
Summoning modal: remove AGENTS.md from progress list since backend only
generates SOUL.md and IDENTITY.md.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Implement `probeBotInfo()` to fetch bot's `open_id` via `/open-apis/bot/v3/info`, fixing group mention detection that was always false due to empty `botOpenID`.
- Add group pending history support to Feishu channel (record/build context/clear) matching Discord pattern.
- Fix Discord placeholder edit to chunk long responses instead of truncating at 2000 chars.
- Implement DM pairing flow for Discord and WhatsApp (checkDMPolicy, sendPairingReply with debounce)
- Add "pairing" case to BaseChannel.CheckPolicy() to reject instead of falling through to "open"
- Add group pending history tracking to Discord (record when not mentioned, prepend context when mentioned)
- Resolve Discord display names with priority: server nickname > global name > username
- Fix Gemini collapse format to prevent model from imitating tool call patterns
- Fix formatTokens crash on null/undefined input
Wire AgentToolPolicy from agent spec through loop creation and resolver, passing it to PolicyEngine.FilterTools for per-agent tool restrictions (nil = no restrictions). Set RestrictToWorkspace=true for default agent in managed mode seeding. Clean up thinking/placeholder messages in Discord and Telegram when agent suppresses empty/NO_REPLY responses by publishing empty outbound and deleting placeholders without sending messages.
Implement pagination across sessions.list, usage.get, and traces HTTP endpoints with limit/offset/total response fields. Add ListPaged method to SessionStore with SessionListOpts struct. Optimize PostgreSQL session queries using jsonb_array_length to avoid loading full message arrays. Add CountTraces method to TracingStore with shared WHERE clause builder. Create reusable Pagination component with page
Multi-agent AI gateway with WebSocket RPC, HTTP API, and messaging channel integrations.
Go port of OpenClaw with multi-tenant PostgreSQL, per-user isolation, security hardening,
and production observability.
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>