Allow before_agent_reply plugins to declare host-enforced trigger eligibility so scheduled-only hooks do not block interrupted user-turn recovery. Keep omitted and malformed scopes fail-closed, scope both memory-core maintenance hooks, and cover three runner reload cycles through the public registration contract.
Refs: #111442
Source: #114836
Co-authored-by: Vincent Koc <vincentkoc@ieee.org>
Preserve the authoritative inbound group origin for private Mattermost channels without changing public channels, direct messages, thread keys, or the native channel delivery target. Prove both Bot API channel types over real HTTP, gateway discovery, and SQLite-backed outbound persistence.
Credit the original group-origin observation in #95669.
Co-authored-by: hansraj <hansraj136@gmail.com>
Emit the established OpenClaw durable-delivery response marker only after an authenticated, non-empty LINE webhook event has been persisted. Verify the production gateway route with signed real HTTP requests covering verification, delayed admission, invalid signatures, and storage failures.
Co-authored-by: 許元豪 <146086744+edenfunf@users.noreply.github.com>
* fix(mattermost): record pending history for non-allowlisted group senders
When `groupPolicy` is `"allowlist"`, messages from senders not in
`groupAllowFrom` were silently dropped before reaching
`recordPendingHistoryEntryIfEnabled()`. This meant the bot had no
context of what non-allowlisted users said, breaking use cases like
conversation summarization.
This change introduces a `senderBlockedByGroupAllowlist` flag so that
non-allowlisted messages continue through the history-recording code
path before returning without generating a response.
Changes:
1. Add `senderBlockedByGroupAllowlist` flag before access check
2. `GROUP_POLICY_NOT_ALLOWLISTED` sets flag instead of returning
3. Skip `commandGate` check for blocked senders
4. Call `recordPendingHistory()` then return before response processing
Fixesopenclaw/openclaw#57607
* test(mattermost): verify denied history over real transport
Co-authored-by: Jason Wang <jasonwang@barkingdog.ai>
---------
Co-authored-by: Jason Wang <jasonwang@barkingdog.ai>
* fix(msteams): honor group conversation allowlists
Fixes#95737. Preserve opaque Teams conversation identities, the documented group fallback, personal-DM isolation, and display-name spoof protection.
Based on the original contributor fix in #95764.
Co-authored-by: 周鹤0668001310 <zhou.he3@xydigit.com>
* test(msteams): isolate conversation allowlist regression proof
Co-authored-by: 周鹤0668001310 <zhou.he3@xydigit.com>
---------
Co-authored-by: 周鹤0668001310 <zhou.he3@xydigit.com>
Sanitize both Nostr delivery paths with the shared assistant-visible text contract. Preserve ordinary replies and suppress internal-only messages before Markdown conversion.
Related: #90684
Co-authored-by: liyuanbin <li.yuanbin1@xydigit.com>
Qualify non-shared sandbox identities by resolved workspace while preserving shared runtime names. Existing non-shared runtimes reset once under the new identity.
Related: #51363
Co-authored-by: Tayoun <39609208+tayoun@users.noreply.github.com>
* fix(telegram): suppress message-tool-only reply fallbacks
Honor message-tool-only delivery when a non-silent Telegram reply is skipped in both ordinary group dispatch and native slash-command turns. Keep the existing visible fallback for genuinely empty native replies and cover both dispatch surfaces with regression tests.
Fixes#90091
* fix(telegram): preserve genuine delivery failure fallbacks
Suppress only non-silent skips owned by message-tool-only delivery. Preserve visible fallbacks when the same turn also encounters a real dispatch or final-delivery failure, with an explicit regression for the mixed failure path.
* fix(telegram): preserve native message-tool delivery errors
Track genuine native slash delivery failures so message-tool-only skip suppression never hides a failed response. Cover the exact mixed empty-skip and real-error path with a regression while preserving true-empty and intentionally cancelled fallbacks.
* test(telegram): preserve synchronous native delivery callbacks
Recover native image, video, voice, and document delivery when upstream media supplies no MIME or a generic binary document. Preserve authoritative MIME, explicitly selected documents, unknown binaries, and existing Opus conversion; prove the actual platform payload across a 14-case delivery matrix.
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Co-authored-by: MatthewSynthia <matthewsynthia@users.noreply.github.com>
Use the non-mutating dedupe-cache contract when checking unaccepted Telegram updates. Preserve active handler and completed-update deduplication while allowing a real update to be accepted after repeated handler skip probes.
Fixes#105192
Restore the explicitly shipped Matrix approval mode without changing omitted, boolean, invalid, or non-strict configuration behavior. Regenerate the canonical channel metadata and prove native Matrix approvals against a real homeserver.
Co-authored-by: mingdideng <deng.mingdi@xydigit.com>
* fix(mattermost): key private channels as group on outbound routing
A Mattermost private channel (server type `P`) is authoritatively chat_type
`group`, but it is addressed as the delivery target `channel:<id>` — the same
prefix as a public channel. Inbound classified it correctly as `group`, while
outbound/session reconstruction re-derived `channel` from the target string, so
one conversation was keyed under two session namespaces
(`...:mattermost:group:<id>:thread` inbound vs a phantom `...:channel:<id>:thread`
on delivery). Threaded/scheduled deliveries bound to one then failed to match the
other (fail-closed delivery, or a conversation split across two session keys).
The Mattermost outbound path could not represent `group` at all:
resolveMattermostOutboundSessionRoute only produced direct/channel, and
resolveMattermostOpaqueTarget only classified user/channel.
- session-route: key a conversation as `group` from an authoritative signal — the
resolved target kind, an explicit `group:` prefix, or the inbound
currentSessionKey peer kind — so outbound shares the inbound `group:<id>`
namespace instead of forking `channel:<id>`.
- target-resolution: classify a bare channel id by its real channel type
(P/G -> group, O -> channel), cached per id.
The wire target stays `channel:<id>` (Mattermost posts to the channel id either
way; parseMattermostTarget only accepts channel:/user:) — the group distinction
lives in the session key. Adds unit coverage for both paths.
Resolves#95646.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(cron): route bound-session cron deliveries under the bound namespace
An isolated cron run executes under an ephemeral agentSessionKey that does not
carry the source conversation's namespace. resolveDirectCronDeliverySessionKey
resolved the outbound delivery route from that isolated key, so for a job bound
to a real conversation thread (e.g. the gitlab-pipeline-watch recheck bound to
agent:...:mattermost:group:<id>🧵<root>) the lossy channel:<id> target was
re-derived as `channel`, forking a phantom channel:<id> session and splitting the
private-channel thread across two namespaces (#95646).
Prefer the job's bound conversation identity as the currentSessionKey used to
resolve the route (new selectCronRouteCurrentSessionKey helper), so the existing
currentSessionKey-based namespace resolution keeps group:<id>. No channel-type
cache is introduced — which is what made the cache-based attempts brittle on cold
restart (a sibling PR documented exactly that failure mode). Falls back to the
isolated key for unbound jobs and cron-namespace bindings. Adds unit coverage.
Refs #95646.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
* fix(mattermost): key public channels as channel in directory listing
listMattermostDirectoryGroups labeled every joined channel — public `O`
and private `P` — as kind `group`. A name-resolved public channel could
then be keyed as `mattermost:group:<id>` on outbound routing, forking a
phantom group session and splitting the transcript from the inbound
`channel:<id>` one. Derive the kind from the authoritative Mattermost
channel type (`O` -> channel, `P`/`G` -> group) and add a regression
test. This closes the public-channel regression path flagged in review
for #95646 while keeping private channels keyed as `group`.
* fix(mattermost): harden private channel routing
* test: expose cron route selection through production module
* fix. scope cron session reuse to Mattermost delivery
* fix(cron): validate bound delivery peer and channel authority
* fix(cron): capture validated delivery destination peer
---------
Co-authored-by: leon <leon@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
Classify pinned Grammy blocked, kicked, and deactivated recipient 403 responses as permanently unreachable. Preserve retry behavior for recoverable permissions and flood control, inspect wrapped causes, and prove real SQLite ingress drain dead-lettering without duplicate dispatch.
Fixes#112893
Retain the existing guarded-fetch timeout callback while consuming native Ollama NDJSON. Prove active slow HTTP streams remain alive and genuine stalls still expire.
Reconstructed from the independently reviewed contributor fix for #94251.
Co-authored-by: Henry <henrybrewer93@icloud.com>
* fix(active-memory): share one recall per run across retries
Register in-flight recalls per ctx.runId so overlapping or changed-prompt
attempts join a single execution, and gate replacement recalls on settled
timeout cleanup. Entries clear on agent_end. Fixes#106957.
* fix(active-memory): evict rejected recall entries
* test(active-memory): clarify run result retention
* test(active-memory): complete recall result fixture
signalRpcRequest returns the JSON-RPC result via a bare cast, so getAttachment data reaches Buffer.from unvalidated. Node drops out-of-alphabet characters instead of throwing, so a damaged payload was silently written to disk as a corrupted attachment. Canonicalize after the existing size guard and fail with the attachment id instead.