@@ -156,6 +156,89 @@ function channelChatType(kind: ChatType): "direct" | "group" | "channel" {
156156 return "channel" ;
157157}
158158
159+ export type MattermostRequireMentionResolverInput = {
160+ cfg : OpenClawConfig ;
161+ channel : "mattermost" ;
162+ accountId : string ;
163+ groupId : string ;
164+ requireMentionOverride ?: boolean ;
165+ } ;
166+
167+ export type MattermostMentionGateInput = {
168+ kind : ChatType ;
169+ cfg : OpenClawConfig ;
170+ accountId : string ;
171+ channelId : string ;
172+ threadRootId ?: string ;
173+ requireMentionOverride ?: boolean ;
174+ resolveRequireMention : ( params : MattermostRequireMentionResolverInput ) => boolean ;
175+ wasMentioned : boolean ;
176+ isControlCommand : boolean ;
177+ commandAuthorized : boolean ;
178+ oncharEnabled : boolean ;
179+ oncharTriggered : boolean ;
180+ canDetectMention : boolean ;
181+ } ;
182+
183+ type MattermostMentionGateDecision = {
184+ shouldRequireMention : boolean ;
185+ shouldBypassMention : boolean ;
186+ effectiveWasMentioned : boolean ;
187+ dropReason : "onchar-not-triggered" | "missing-mention" | null ;
188+ } ;
189+
190+ export function evaluateMattermostMentionGate (
191+ params : MattermostMentionGateInput ,
192+ ) : MattermostMentionGateDecision {
193+ const shouldRequireMention =
194+ params . kind !== "direct" &&
195+ params . resolveRequireMention ( {
196+ cfg : params . cfg ,
197+ channel : "mattermost" ,
198+ accountId : params . accountId ,
199+ groupId : params . channelId ,
200+ requireMentionOverride : params . requireMentionOverride ,
201+ } ) ;
202+ const shouldBypassMention =
203+ params . isControlCommand &&
204+ shouldRequireMention &&
205+ ! params . wasMentioned &&
206+ params . commandAuthorized ;
207+ const effectiveWasMentioned =
208+ params . wasMentioned || shouldBypassMention || params . oncharTriggered ;
209+ if (
210+ params . oncharEnabled &&
211+ ! params . oncharTriggered &&
212+ ! params . wasMentioned &&
213+ ! params . isControlCommand
214+ ) {
215+ return {
216+ shouldRequireMention,
217+ shouldBypassMention,
218+ effectiveWasMentioned,
219+ dropReason : "onchar-not-triggered" ,
220+ } ;
221+ }
222+ if (
223+ params . kind !== "direct" &&
224+ shouldRequireMention &&
225+ params . canDetectMention &&
226+ ! effectiveWasMentioned
227+ ) {
228+ return {
229+ shouldRequireMention,
230+ shouldBypassMention,
231+ effectiveWasMentioned,
232+ dropReason : "missing-mention" ,
233+ } ;
234+ }
235+ return {
236+ shouldRequireMention,
237+ shouldBypassMention,
238+ effectiveWasMentioned,
239+ dropReason : null ,
240+ } ;
241+ }
159242type MattermostMediaInfo = {
160243 path : string ;
161244 contentType ?: string ;
@@ -485,28 +568,36 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
485568 ) => {
486569 const channelId = post . channel_id ?? payload . data ?. channel_id ?? payload . broadcast ?. channel_id ;
487570 if ( ! channelId ) {
571+ logVerboseMessage ( "mattermost: drop post (missing channel id)" ) ;
488572 return ;
489573 }
490574
491575 const allMessageIds = messageIds ?. length ? messageIds : post . id ? [ post . id ] : [ ] ;
492576 if ( allMessageIds . length === 0 ) {
577+ logVerboseMessage ( "mattermost: drop post (missing message id)" ) ;
493578 return ;
494579 }
495580 const dedupeEntries = allMessageIds . map ( ( id ) =>
496581 recentInboundMessages . check ( `${ account . accountId } :${ id } ` ) ,
497582 ) ;
498583 if ( dedupeEntries . length > 0 && dedupeEntries . every ( Boolean ) ) {
584+ logVerboseMessage (
585+ `mattermost: drop post (dedupe account=${ account . accountId } ids=${ allMessageIds . length } )` ,
586+ ) ;
499587 return ;
500588 }
501589
502590 const senderId = post . user_id ?? payload . broadcast ?. user_id ;
503591 if ( ! senderId ) {
592+ logVerboseMessage ( "mattermost: drop post (missing sender id)" ) ;
504593 return ;
505594 }
506595 if ( senderId === botUserId ) {
596+ logVerboseMessage ( `mattermost: drop post (self sender=${ senderId } )` ) ;
507597 return ;
508598 }
509599 if ( isSystemPost ( post ) ) {
600+ logVerboseMessage ( `mattermost: drop post (system post type=${ post . type ?? "unknown" } )` ) ;
510601 return ;
511602 }
512603
@@ -707,37 +798,48 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
707798 ? stripOncharPrefix ( rawText , oncharPrefixes )
708799 : { triggered : false , stripped : rawText } ;
709800 const oncharTriggered = oncharResult . triggered ;
710-
711- const shouldRequireMention =
712- kind !== "direct" &&
713- core . channel . groups . resolveRequireMention ( {
714- cfg,
715- channel : "mattermost" ,
716- accountId : account . accountId ,
717- groupId : channelId ,
718- } ) ;
719- const shouldBypassMention =
720- isControlCommand && shouldRequireMention && ! wasMentioned && commandAuthorized ;
721- const effectiveWasMentioned = wasMentioned || shouldBypassMention || oncharTriggered ;
722801 const canDetectMention = Boolean ( botUsername ) || mentionRegexes . length > 0 ;
802+ const mentionDecision = evaluateMattermostMentionGate ( {
803+ kind,
804+ cfg,
805+ accountId : account . accountId ,
806+ channelId,
807+ threadRootId,
808+ requireMentionOverride : account . requireMention ,
809+ resolveRequireMention : core . channel . groups . resolveRequireMention ,
810+ wasMentioned,
811+ isControlCommand,
812+ commandAuthorized,
813+ oncharEnabled,
814+ oncharTriggered,
815+ canDetectMention,
816+ } ) ;
817+ const { shouldRequireMention, shouldBypassMention } = mentionDecision ;
723818
724- if ( oncharEnabled && ! oncharTriggered && ! wasMentioned && ! isControlCommand ) {
819+ if ( mentionDecision . dropReason === "onchar-not-triggered" ) {
820+ logVerboseMessage (
821+ `mattermost: drop group message (onchar not triggered channel=${ channelId } sender=${ senderId } )` ,
822+ ) ;
725823 recordPendingHistory ( ) ;
726824 return ;
727825 }
728826
729- if ( kind !== "direct" && shouldRequireMention && canDetectMention ) {
730- if ( ! effectiveWasMentioned ) {
731- recordPendingHistory ( ) ;
732- return ;
733- }
827+ if ( mentionDecision . dropReason === "missing-mention" ) {
828+ logVerboseMessage (
829+ `mattermost: drop group message (missing mention channel=${ channelId } sender=${ senderId } requireMention=${ shouldRequireMention } bypass=${ shouldBypassMention } canDetectMention=${ canDetectMention } )` ,
830+ ) ;
831+ recordPendingHistory ( ) ;
832+ return ;
734833 }
735834 const mediaList = await resolveMattermostMedia ( post . file_ids ) ;
736835 const mediaPlaceholder = buildMattermostAttachmentPlaceholder ( mediaList ) ;
737836 const bodySource = oncharTriggered ? oncharResult . stripped : rawText ;
738837 const baseText = [ bodySource , mediaPlaceholder ] . filter ( Boolean ) . join ( "\n" ) . trim ( ) ;
739838 const bodyText = normalizeMention ( baseText , botUsername ) ;
740839 if ( ! bodyText ) {
840+ logVerboseMessage (
841+ `mattermost: drop group message (empty body after normalization channel=${ channelId } sender=${ senderId } )` ,
842+ ) ;
741843 return ;
742844 }
743845
@@ -841,7 +943,7 @@ export async function monitorMattermostProvider(opts: MonitorMattermostOpts = {}
841943 ReplyToId : threadRootId ,
842944 MessageThreadId : threadRootId ,
843945 Timestamp : typeof post . create_at === "number" ? post . create_at : undefined ,
844- WasMentioned : kind !== "direct" ? effectiveWasMentioned : undefined ,
946+ WasMentioned : kind !== "direct" ? mentionDecision . effectiveWasMentioned : undefined ,
845947 CommandAuthorized : commandAuthorized ,
846948 OriginatingChannel : "mattermost" as const ,
847949 OriginatingTo : to ,
0 commit comments