Files
openclaw/src/config/implicit-mentions.ts
T
iloveleon19 382d570cbf feat(channels): add unified implicit mention policy (#108829)
* feat(mattermost): add thread.requireExplicitMention to opt out of thread auto-follow

Mattermost treats any reply in a thread the bot has participated in as an
implicit mention, so requireMention only gates the first message and the bot
then answers follow-ups addressed to other people for the participation TTL.
Slack exposes channels.slack.thread.requireExplicitMention for exactly this;
Mattermost had no equivalent and its strict schema rejected the key.

Add channels.mattermost.thread.requireExplicitMention (channel + per-account),
mirroring Slack. When set, thread participation no longer counts as a mention.
Default (unset/false) keeps today's auto-follow behavior unchanged.

Related: #108269

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* feat(channels): add implicit mention policy foundation

* feat(channels): unify implicit mention policy

Co-authored-by: leon <dodoma0919@gmail.com>

* refactor(channels): keep implicit policy inside evaluator

* fix(channels): use exported implicit mention type

* chore(channels): satisfy extension lint

* fix(config): break implicit mention type cycle

* fix(plugin-sdk): account for implicit mention config export

* refactor(config): isolate implicit mention schema

* chore(plugin-sdk): align implicit mention surface budget

* fix(config): remove unused schema re-export

---------

Co-authored-by: leon <dodoma0919@gmail.com>
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Peter Steinberger <steipete@gmail.com>
2026-07-16 17:16:01 -07:00

52 lines
2.0 KiB
TypeScript

// Resolves channel implicit-mention policy across account, channel, and shared defaults.
import { resolveAccountEntry } from "../routing/account-lookup.js";
import { normalizeAccountId } from "../routing/session-key.js";
import type { OpenClawConfig } from "./config.js";
import type { ChannelImplicitMentionsConfig } from "./types.channels.js";
export type ResolvedChannelImplicitMentions = Required<ChannelImplicitMentionsConfig>;
type ChannelImplicitMentionsSource = {
implicitMentions?: ChannelImplicitMentionsConfig;
accounts?: Record<string, { implicitMentions?: ChannelImplicitMentionsConfig }>;
};
const SHIPPED_IMPLICIT_MENTION_DEFAULTS: ResolvedChannelImplicitMentions = {
replyToBot: true,
quotedBot: true,
threadParticipation: true,
};
/** Resolves each implicit-mention kind using account, channel, defaults, then shipped behavior. */
export function resolveChannelImplicitMentions(params: {
cfg: OpenClawConfig;
channel: string;
accountId?: string | null;
}): ResolvedChannelImplicitMentions {
const channelConfig = params.cfg.channels?.[params.channel] as
| ChannelImplicitMentionsSource
| undefined;
const accountConfig = resolveAccountEntry(
channelConfig?.accounts,
normalizeAccountId(params.accountId),
);
const defaults = params.cfg.channels?.defaults?.implicitMentions;
return {
replyToBot:
accountConfig?.implicitMentions?.replyToBot ??
channelConfig?.implicitMentions?.replyToBot ??
defaults?.replyToBot ??
SHIPPED_IMPLICIT_MENTION_DEFAULTS.replyToBot,
quotedBot:
accountConfig?.implicitMentions?.quotedBot ??
channelConfig?.implicitMentions?.quotedBot ??
defaults?.quotedBot ??
SHIPPED_IMPLICIT_MENTION_DEFAULTS.quotedBot,
threadParticipation:
accountConfig?.implicitMentions?.threadParticipation ??
channelConfig?.implicitMentions?.threadParticipation ??
defaults?.threadParticipation ??
SHIPPED_IMPLICIT_MENTION_DEFAULTS.threadParticipation,
};
}