mirror of
https://github.com/moltbot/moltbot.git
synced 2026-05-13 15:47:28 +00:00
refactor: centralize channel ingress access
This commit is contained in:
137
docs/plugins/sdk-channel-ingress.md
Normal file
137
docs/plugins/sdk-channel-ingress.md
Normal file
@@ -0,0 +1,137 @@
|
||||
---
|
||||
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 API
|
||||
|
||||
Channel ingress is the experimental access-control boundary for inbound channel
|
||||
events. Use `openclaw/plugin-sdk/channel-ingress-runtime` for receive paths.
|
||||
The older `openclaw/plugin-sdk/channel-ingress` subpath stays exported as a
|
||||
deprecated compatibility facade for third-party plugins.
|
||||
|
||||
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.
|
||||
|
||||
## 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:
|
||||
|
||||
- `ingress`: ordered gate decision and admission
|
||||
- `senderAccess`: sender/conversation authorization only
|
||||
- `routeAccess`: route and route-sender projection
|
||||
- `commandAccess`: command authorization; false when no command gate ran
|
||||
- `activationAccess`: mention/activation result
|
||||
|
||||
Event authorization remains 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, can pass `activation.allowedImplicitMentionKinds`; the projected
|
||||
`activationAccess.shouldBypassMention` then 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
|
||||
```
|
||||
@@ -74,6 +74,16 @@ remain available for compatibility dispatchers. Do not use those names for new
|
||||
channel code; new plugins should start with the `message` adapter, receipts, and
|
||||
receive/send lifecycle helpers on `openclaw/plugin-sdk/channel-message`.
|
||||
|
||||
Channels migrating inbound authorization can use the experimental
|
||||
`openclaw/plugin-sdk/channel-ingress-runtime` subpath from runtime receive
|
||||
paths. The subpath keeps platform lookup and side effects in the plugin, while
|
||||
sharing allowlist state resolution, route/sender/command/event/activation
|
||||
decisions, redacted diagnostics, and turn-admission mapping. Keep plugin
|
||||
identity normalization in the descriptor you pass to the resolver; do not
|
||||
serialize raw match values from the resolved state or decision. See
|
||||
[Channel ingress API](/plugins/sdk-channel-ingress) for the API design,
|
||||
ownership boundary, and test expectations.
|
||||
|
||||
If your channel supports typing indicators outside inbound replies, expose
|
||||
`heartbeat.sendTyping(...)` on the channel plugin. Core calls it with the
|
||||
resolved heartbeat delivery target before the heartbeat model run starts and
|
||||
|
||||
@@ -64,6 +64,7 @@ The runtime exposes three preferred entry points so adapters can opt in at the l
|
||||
|
||||
```typescript
|
||||
runtime.channel.turn.run(...) // adapter-driven full pipeline
|
||||
runtime.channel.turn.runAssembled(...) // already-built context + delivery adapter
|
||||
runtime.channel.turn.runPrepared(...) // channel owns dispatch; kernel runs record + finalize
|
||||
runtime.channel.turn.buildContext(...) // pure facts to FinalizedMsgContext mapping
|
||||
```
|
||||
@@ -72,7 +73,7 @@ Two older runtime helpers remain available for Plugin SDK compatibility:
|
||||
|
||||
```typescript
|
||||
runtime.channel.turn.runResolved(...) // deprecated compatibility alias; prefer run
|
||||
runtime.channel.turn.dispatchAssembled(...) // deprecated compatibility alias; prefer run or runPrepared
|
||||
runtime.channel.turn.dispatchAssembled(...) // deprecated compatibility alias; prefer runAssembled
|
||||
```
|
||||
|
||||
### run
|
||||
@@ -114,6 +115,41 @@ await runtime.channel.turn.run({
|
||||
|
||||
`run` is the right shape when the channel has small adapter logic and benefits from owning the lifecycle through hooks.
|
||||
|
||||
### runAssembled
|
||||
|
||||
Use when the channel has already resolved routing, built a `FinalizedMsgContext`,
|
||||
and only needs the shared record, reply-pipeline, dispatch, and finalize
|
||||
ordering. This is the preferred shape for simple bundled inbound paths that
|
||||
would otherwise repeat `createChannelMessageReplyPipeline(...)` and
|
||||
`runPrepared(...)` boilerplate.
|
||||
|
||||
```typescript
|
||||
await runtime.channel.turn.runAssembled({
|
||||
cfg,
|
||||
channel: "irc",
|
||||
accountId,
|
||||
agentId: route.agentId,
|
||||
routeSessionKey: route.sessionKey,
|
||||
storePath,
|
||||
ctxPayload,
|
||||
recordInboundSession: runtime.channel.session.recordInboundSession,
|
||||
dispatchReplyWithBufferedBlockDispatcher:
|
||||
runtime.channel.reply.dispatchReplyWithBufferedBlockDispatcher,
|
||||
delivery: {
|
||||
deliver: async (payload) => {
|
||||
await sendPlatformReply(payload);
|
||||
},
|
||||
onError: (err, info) => {
|
||||
runtime.error?.(`reply ${info.kind} failed: ${String(err)}`);
|
||||
},
|
||||
},
|
||||
});
|
||||
```
|
||||
|
||||
Choose `runAssembled` over `runPrepared` when the only channel-owned dispatch
|
||||
behavior is final payload delivery plus optional typing, reply options, durable
|
||||
delivery, or error logging.
|
||||
|
||||
### runPrepared
|
||||
|
||||
Use when the channel has a complex local dispatcher with previews, retries, edits, or thread bootstrap that must stay channel-owned. The kernel still records the inbound session before dispatch and surfaces a uniform `DispatchedChannelTurnResult`.
|
||||
|
||||
@@ -56,6 +56,7 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/account-id` | `DEFAULT_ACCOUNT_ID`, account-id normalization helpers |
|
||||
| `plugin-sdk/account-resolution` | Account lookup + default-fallback helpers |
|
||||
| `plugin-sdk/account-helpers` | Narrow account-list/account-action helpers |
|
||||
| `plugin-sdk/access-groups` | Access-group allowlist parsing and redacted group diagnostics helpers |
|
||||
| `plugin-sdk/channel-pairing` | `createChannelPairingController` |
|
||||
| `plugin-sdk/channel-reply-pipeline` | Legacy reply pipeline helpers. New channel reply pipeline code should use `createChannelMessageReplyPipeline` and `resolveChannelMessageSourceReplyDeliveryMode` from `plugin-sdk/channel-message`. |
|
||||
| `plugin-sdk/channel-config-helpers` | `createHybridChannelConfigAdapter`, `resolveChannelDmAccess`, `resolveChannelDmAllowFrom`, `resolveChannelDmPolicy`, `normalizeChannelDmPolicy`, `normalizeLegacyDmAliases` |
|
||||
@@ -65,6 +66,8 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
|
||||
| `plugin-sdk/telegram-command-config` | Telegram custom-command normalization/validation helpers with bundled-contract fallback |
|
||||
| `plugin-sdk/command-gating` | Narrow command authorization gate helpers |
|
||||
| `plugin-sdk/channel-policy` | `resolveChannelGroupRequireMention` |
|
||||
| `plugin-sdk/channel-ingress` | Deprecated low-level channel ingress compatibility facade. New receive paths should use `plugin-sdk/channel-ingress-runtime`. |
|
||||
| `plugin-sdk/channel-ingress-runtime` | Experimental high-level channel ingress runtime resolver and route fact builders for migrated channel receive paths. Prefer this over assembling effective allowlists, command allowlists, and legacy projections in each plugin. See [Channel ingress API](/plugins/sdk-channel-ingress). |
|
||||
| `plugin-sdk/channel-lifecycle` | `createAccountStatusSink`, `createChannelRunQueue`, and legacy draft stream lifecycle helpers. New preview finalization code should use `plugin-sdk/channel-message`. |
|
||||
| `plugin-sdk/channel-message` | Cheap message lifecycle contract helpers such as `defineChannelMessageAdapter`, `createChannelMessageAdapterFromOutbound`, `createChannelMessageReplyPipeline`, `createReplyPrefixContext`, `resolveChannelMessageSourceReplyDeliveryMode`, durable-final capability derivation, capability proof helpers for send/receipt/side-effect capabilities, `MessageReceiveContext`, receive ack policy proofs, `defineFinalizableLivePreviewAdapter`, `deliverWithFinalizableLivePreviewAdapter`, live-preview and live-finalizer capability proofs, durable recovery state, `RenderedMessageBatch`, message receipt types, and receipt id helpers. See [Channel message API](/plugins/sdk-channel-message). Legacy reply-dispatch facades are deprecated compatibility only. |
|
||||
| `plugin-sdk/channel-message-runtime` | Runtime delivery helpers that may load outbound delivery, including `deliverInboundReplyWithMessageSendContext`, `sendDurableMessageBatch`, and `withDurableMessageSendContext`. Deprecated reply-dispatch bridges remain importable for compatibility dispatchers only. Use from monitor/send runtime modules, not hot plugin bootstrap files. |
|
||||
|
||||
Reference in New Issue
Block a user