mirror of
https://github.com/openclaw/openclaw.git
synced 2026-08-24 19:35:28 -06:00
382d570cbf
* 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>
140 lines
5.3 KiB
Markdown
140 lines
5.3 KiB
Markdown
---
|
|
summary: "Experimental channel ingress API for inbound message authorization"
|
|
read_when:
|
|
- Building or migrating a messaging channel plugin
|
|
- Changing DM or group allowlists, route gates, command auth, event auth, or mention activation
|
|
- Reviewing channel ingress redaction or SDK compatibility boundaries
|
|
title: "Channel ingress API"
|
|
sidebarTitle: "Channel Ingress"
|
|
---
|
|
|
|
Channel ingress is the experimental access-control boundary for inbound
|
|
channel events. Plugins own platform facts and side effects; core owns
|
|
generic policy: DM/group allowlists, pairing-store DM entries, route gates,
|
|
command gates, event auth, mention activation, redacted diagnostics, and
|
|
admission.
|
|
|
|
Use `openclaw/plugin-sdk/channel-ingress-runtime` for receive paths.
|
|
|
|
## Runtime resolver
|
|
|
|
```ts
|
|
import {
|
|
defineStableChannelIngressIdentity,
|
|
resolveChannelMessageIngress,
|
|
} from "openclaw/plugin-sdk/channel-ingress-runtime";
|
|
|
|
const identity = defineStableChannelIngressIdentity({
|
|
key: "platform-user-id",
|
|
normalize: normalizePlatformUserId,
|
|
sensitivity: "pii",
|
|
});
|
|
|
|
const result = await resolveChannelMessageIngress({
|
|
channelId: "my-channel",
|
|
accountId,
|
|
identity,
|
|
subject: { stableId: platformUserId },
|
|
conversation: { kind: isGroup ? "group" : "direct", id: conversationId },
|
|
event: { kind: "message", authMode: "inbound", mayPair: !isGroup },
|
|
policy: {
|
|
dmPolicy: config.dmPolicy,
|
|
groupPolicy: config.groupPolicy,
|
|
groupAllowFromFallbackToAllowFrom: true,
|
|
},
|
|
allowFrom: config.allowFrom,
|
|
groupAllowFrom: config.groupAllowFrom,
|
|
accessGroups: cfg.accessGroups,
|
|
route,
|
|
readStoreAllowFrom,
|
|
command: hasControlCommand ? { allowTextCommands: true, hasControlCommand } : undefined,
|
|
});
|
|
```
|
|
|
|
Do not precompute effective allowlists, command owners, or command groups.
|
|
The resolver derives them from raw allowlists, store callbacks, route
|
|
descriptors, access groups, policy, and conversation kind.
|
|
|
|
## Result
|
|
|
|
Bundled plugins should consume modern projections directly:
|
|
|
|
| Field | Meaning |
|
|
| ------------------ | ------------------------------------------------------------------ |
|
|
| `ingress` | ordered gate decision and admission |
|
|
| `senderAccess` | sender/conversation authorization only |
|
|
| `routeAccess` | route and route-sender projection |
|
|
| `commandAccess` | command authorization; `requested: false` when no command gate ran |
|
|
| `activationAccess` | mention/activation result |
|
|
|
|
Event authorization stays available on the ordered `ingress.graph` and the
|
|
decisive `ingress.reasonCode`; no separate event projection is emitted.
|
|
|
|
Deprecated third-party SDK helpers may rebuild older shapes internally. New
|
|
bundled receive paths should not translate modern results back into local
|
|
DTOs.
|
|
|
|
## Access groups
|
|
|
|
`accessGroup:<name>` entries stay redacted. Core resolves static
|
|
`message.senders` groups itself and calls `resolveAccessGroupMembership` only
|
|
for dynamic groups that require a platform lookup. Missing, unsupported, and
|
|
failed groups fail closed.
|
|
|
|
## Event modes
|
|
|
|
| `authMode` | Meaning |
|
|
| ---------------- | ------------------------------------------------ |
|
|
| `inbound` | normal inbound sender gates |
|
|
| `command` | command gates for callbacks or scoped buttons |
|
|
| `origin-subject` | actor must match the original message subject |
|
|
| `route-only` | route gates only for route-scoped trusted events |
|
|
| `none` | plugin-owned internal events bypass shared auth |
|
|
|
|
Use `mayPair: false` for reactions, buttons, callbacks, and native commands.
|
|
|
|
## Routes and activation
|
|
|
|
Use route descriptors for room, topic, guild, thread, or nested route policy:
|
|
|
|
```ts
|
|
route: {
|
|
id: "room",
|
|
allowed: roomAllowed,
|
|
enabled: roomEnabled,
|
|
senderPolicy: "replace",
|
|
senderAllowFrom: roomAllowFrom,
|
|
blockReason: "room_sender_not_allowlisted",
|
|
}
|
|
```
|
|
|
|
Use `channelIngressRoutes(...)` when a plugin has several optional route
|
|
descriptors; it filters disabled branches while keeping route facts generic
|
|
and ordered by each descriptor's `precedence`.
|
|
|
|
Mention gating is an activation gate. A mention miss returns
|
|
`admission: "skip"` so the turn kernel does not process an observe-only turn.
|
|
Most channels should leave activation after sender and command gates. Public
|
|
chat surfaces that must quiet non-mentioned traffic before sender allowlist
|
|
noise can opt into `activation.order: "before-sender"` when text-command
|
|
bypass is disabled. Channels with implicit activation, such as replies in bot
|
|
threads, resolve `channels.defaults.implicitMentions` plus channel and account
|
|
overrides with `resolveChannelImplicitMentions(...)`, then pass the result as
|
|
`activation.implicitMentions`. The projected
|
|
`activationAccess.shouldBypassMention` reports when command or implicit
|
|
activation bypassed an explicit mention.
|
|
|
|
## Redaction
|
|
|
|
Raw sender values and raw allowlist entries are resolver input only. They
|
|
must not appear in resolved state, decisions, diagnostics, snapshots, or
|
|
compatibility facts. Use opaque subject ids, entry ids, route ids, and
|
|
diagnostic ids.
|
|
|
|
## Verification
|
|
|
|
```bash
|
|
pnpm test src/channels/message-access/message-access.test.ts src/plugin-sdk/channel-ingress-runtime.test.ts
|
|
pnpm plugin-sdk:api:check
|
|
```
|