Conversation binding callbacks
Plugins that bind a conversation can react when an approval is resolved. Useapi.onConversationBindingResolved(...) to receive a callback after a bind
request is approved or denied:
status:"approved"or"denied"decision:"allow-once","allow-always", or"deny"binding: the resolved binding for approved requestsrequest: the original request summary, detach hint, sender id, and conversation metadata
Message tool schemas
Plugins should own channel-specificdescribeMessageTool(...) schema
contributions for non-message primitives such as reactions, reads, and polls.
Shared send presentation should use the generic MessagePresentation contract
instead of provider-native button, component, block, or card fields.
See Message Presentation for the contract,
fallback rules, provider mapping, and plugin author checklist.
Provider-native schema extensions require explicit maintainer approval,
channel-owned parsing, documented cross-channel behavior, and capabilities that
MessagePresentation cannot express. Discord components is the approved
built-in exception for its advanced Components V2 layouts.
Send-capable plugins declare what they can render through message capabilities:
presentationfor semantic presentation blocks (text,context,divider,chart,table,buttons,select)delivery-pinfor pinned-delivery requests
Channel target resolution
Channel plugins should own channel-specific target semantics. Keep the shared outbound host generic and use the messaging adapter surface for provider rules:messaging.inferTargetChatType({ to })decides whether a normalized target should be treated asdirect,group, orchannelbefore directory lookup. Implicit owner heartbeat delivery requires this direct classification; without it, Gateway status reportswaiting for route.messaging.targetResolver.looksLikeId(raw, normalized)tells core whether an input should skip straight to id-like resolution instead of directory search.messaging.targetResolver.reservedLiteralslists bare words that are channel/session references for that provider. Resolution preserves configured directory entries before rejecting reserved literals, then fails closed on a directory miss.messaging.targetResolver.resolveTarget(...)is the plugin fallback when core needs a final provider-owned resolution after normalization or after a directory miss.messaging.resolveOutboundSessionRoute(...)owns provider-specific session route construction once a target is resolved.
- Use
inferTargetChatTypefor category decisions that should happen before searching peers/groups. - Use
looksLikeIdfor “treat this as an explicit/native target id” checks. - Use
resolveTargetfor provider-specific normalization fallback, not for broad directory search. - Keep provider-native ids like chat ids, thread ids, JIDs, handles, and room
ids inside
targetvalues or provider-specific params, not in generic SDK fields.
Config-backed directories
Plugins that derive directory entries from config should keep that logic in the plugin and reuse the shared helpers fromopenclaw/plugin-sdk/directory-runtime.
Use this when a channel needs config-backed peers/groups such as:
- allowlist-driven DM peers
- configured channel/group maps
- account-scoped static directory fallbacks
directory-runtime only handle generic operations:
- query filtering
- limit application
- deduping/normalization helpers
- building
ChannelDirectoryEntry[]
Read-only channel inspection
If your plugin registers a channel, prefer implementingplugin.config.inspectAccount(cfg, accountId) alongside resolveAccount(...).
Why:
resolveAccount(...)is the runtime path. It is allowed to assume credentials are fully materialized and can fail fast when required secrets are missing.- Read-only command paths such as
openclaw status,openclaw status --all,openclaw channels status,openclaw channels resolve, and doctor/config repair flows should not need to materialize runtime credentials just to describe configuration.
inspectAccount(...) behavior:
- Return descriptive account state only.
- Preserve
enabledandconfigured. - Include credential source/status fields when relevant, such as:
tokenSource,tokenStatusbotTokenSource,botTokenStatusappTokenSource,appTokenStatussigningSecretSource,signingSecretStatus
- You do not need to return raw token values just to report read-only
availability. Returning
tokenStatus: "available"(and the matching source field) is enough for status-style commands. - Use
configured_unavailablewhen a credential is configured via SecretRef but unavailable in the current command path.