mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-02 14:31:34 +00:00
* 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>