refactor: consolidate message delivery API

This commit is contained in:
Peter Steinberger
2026-05-09 07:02:33 +01:00
parent 195db88d71
commit a4b17d65a8
74 changed files with 1561 additions and 340 deletions

View File

@@ -369,6 +369,12 @@ Decision rules:
- `message_sending` with `cancel: false` is treated as no decision.
- Rewritten `content` continues to lower-priority hooks unless a later hook
cancels delivery.
- `message_sending` can return `cancelReason` and bounded `metadata` with a
cancellation. New message lifecycle APIs expose this as a suppressed delivery
outcome with reason `cancelled_by_message_sending_hook`; legacy direct
delivery keeps returning an empty result array for compatibility.
- `message_sent` is observation-only. Handler failures are logged and do not
change the delivery result.
## Install hooks

View File

@@ -31,6 +31,36 @@ Runtime delivery helpers are available from
`openclaw/plugin-sdk/channel-message-runtime` for monitor/send code paths that
are already doing asynchronous message I/O.
New channel and plugin send code should use the message lifecycle helpers from
`openclaw/plugin-sdk/channel-message-runtime`: `sendDurableMessageBatch`,
`withDurableMessageSendContext`, or `deliverInboundReplyWithMessageSendContext`.
The older
`deliverOutboundPayloads(...)` helper in `openclaw/plugin-sdk/outbound-runtime`
is deprecated compatibility/runtime substrate for outbound internals, recovery,
and legacy adapters. Do not use it for new channel or plugin send paths.
`sendDurableMessageBatch(...)` returns an explicit lifecycle outcome:
- `sent` - at least one visible platform message was delivered.
- `suppressed` - no platform message should be treated as missing. Stable
reasons include `cancelled_by_message_sending_hook`,
`empty_after_message_sending_hook`, `no_visible_payload`,
`adapter_returned_no_identity`, and legacy `no_visible_result`.
- `partial_failed` - at least one platform message was delivered before a later
payload or side effect failed. The result includes the delivered receipt prefix
plus the failure.
- `failed` - no platform receipt was produced.
Use `payloadOutcomes` when a batch mixes sent, suppressed, and failed payloads.
Do not infer hook cancellation by checking whether the old direct-delivery array
is empty.
Compatibility dispatchers that still need the buffered reply dispatcher should
build reply-prefix options with `createChannelMessageReplyPipeline(...)` from
`openclaw/plugin-sdk/channel-message`, then call the runtime's
`channel.turn.runPrepared(...)`. That keeps session recording and dispatch
ordering on the shared turn lifecycle without adding another public turn wrapper.
## Minimal adapter
Most new channel plugins can start with a small adapter:
@@ -124,8 +154,10 @@ tool send, implement `actions.prepareSendPayload(...)` instead of sending from
`prepareSendPayload(...)` receives the normalized core `ReplyPayload` plus the
full action context. Return a payload with channel-specific data in
`payload.channelData.<channel>` and let core call `sendMessage(...)`,
`deliverOutboundPayloads(...)`, the write-ahead queue, message-sending hooks,
retry, recovery, and ack cleanup.
the message lifecycle runtime, the write-ahead queue, message-sending hooks,
retry, recovery, and ack cleanup. The lifecycle runtime may call
`deliverOutboundPayloads(...)` internally as compatibility substrate, but channel
plugins should not call it directly for new send behavior.
Return `null` only when the send cannot be represented as a durable payload, for
example because it contains a non-serializable component factory. Core will keep
@@ -390,17 +422,21 @@ surface.
These APIs remain importable for third-party compatibility. Do not use them for
new channel code.
| Deprecated API | Replacement |
| -------------------------------------------- | ------------------------------------------------------------------------------------------------------------------- |
| `openclaw/plugin-sdk/channel-reply-pipeline` | `openclaw/plugin-sdk/channel-message` |
| `createChannelTurnReplyPipeline(...)` | `createChannelMessageReplyPipeline(...)` for compatibility dispatchers, or a `message` adapter for new channel code |
| `deliverDurableInboundReplyPayload(...)` | `deliverInboundReplyWithMessageSendContext(...)` from `openclaw/plugin-sdk/channel-message-runtime` |
| `dispatchInboundReplyWithBase(...)` | `dispatchChannelMessageReplyWithBase(...)` only for compatibility dispatchers |
| `recordInboundSessionAndDispatchReply(...)` | `recordChannelMessageReplyDispatch(...)` only for compatibility dispatchers |
| `resolveChannelSourceReplyDeliveryMode(...)` | `resolveChannelMessageSourceReplyDeliveryMode(...)` |
| `deliverFinalizableDraftPreview(...)` | `defineFinalizableLivePreviewAdapter(...)` plus `deliverWithFinalizableLivePreviewAdapter(...)` |
| `DraftPreviewFinalizerDraft` | `LivePreviewFinalizerDraft` |
| `DraftPreviewFinalizerResult` | `LivePreviewFinalizerResult` |
| Deprecated API | Replacement |
| -------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------- |
| `openclaw/plugin-sdk/channel-reply-pipeline` | `openclaw/plugin-sdk/channel-message` |
| `createChannelTurnReplyPipeline(...)` | `createChannelMessageReplyPipeline(...)` for compatibility dispatchers, or a `message` adapter for new channel code |
| `buildChannelMessageReplyDispatchBase(...)` | `createChannelMessageReplyPipeline(...)` plus `channel.turn.runPrepared(...)`, or a `message` adapter for new channel code |
| `dispatchChannelMessageReplyWithBase(...)` | `createChannelMessageReplyPipeline(...)` plus `channel.turn.runPrepared(...)`, or a `message` adapter for new channel code |
| `recordChannelMessageReplyDispatch(...)` | `createChannelMessageReplyPipeline(...)` plus `channel.turn.runPrepared(...)`, or a `message` adapter for new channel code |
| `deliverOutboundPayloads(...)` | `sendDurableMessageBatch(...)` or `deliverInboundReplyWithMessageSendContext(...)` from `channel-message-runtime` |
| `deliverDurableInboundReplyPayload(...)` | `deliverInboundReplyWithMessageSendContext(...)` from `openclaw/plugin-sdk/channel-message-runtime` |
| `dispatchInboundReplyWithBase(...)` | `createChannelMessageReplyPipeline(...)` plus `channel.turn.runPrepared(...)`, or a `message` adapter for new channel code |
| `recordInboundSessionAndDispatchReply(...)` | `createChannelMessageReplyPipeline(...)` plus `channel.turn.runPrepared(...)`, or a `message` adapter for new channel code |
| `resolveChannelSourceReplyDeliveryMode(...)` | `resolveChannelMessageSourceReplyDeliveryMode(...)` |
| `deliverFinalizableDraftPreview(...)` | `defineFinalizableLivePreviewAdapter(...)` plus `deliverWithFinalizableLivePreviewAdapter(...)` |
| `DraftPreviewFinalizerDraft` | `LivePreviewFinalizerDraft` |
| `DraftPreviewFinalizerResult` | `LivePreviewFinalizerResult` |
Compatibility dispatchers can still use `createReplyPrefixContext(...)`,
`createReplyPrefixOptions(...)`, and `createTypingCallbacks(...)` through the

View File

@@ -250,7 +250,7 @@ releases.
helpers for external plugins during the migration window and warn once with
the `runtime-config-load-write` compatibility code. Bundled plugins and repo
runtime code are protected by scanner guardrails in
`pnpm check:deprecated-internal-config-api` and
`pnpm check:deprecated-api-usage` and
`pnpm check:no-runtime-action-load-config`: new production plugin usage
fails outright, direct config writes fail, gateway server methods must use
the request runtime snapshot, runtime channel send/action/client helpers

View File

@@ -66,14 +66,14 @@ For the plugin authoring guide, see [Plugin SDK overview](/plugins/sdk-overview)
| `plugin-sdk/command-gating` | Narrow command authorization gate helpers |
| `plugin-sdk/channel-policy` | `resolveChannelGroupRequireMention` |
| `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`, `createReplyPrefixContext`, `resolveChannelMessageSourceReplyDeliveryMode`, compatibility facades, 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 `createChannelTurnReplyPipeline` remains only for compatibility dispatchers. |
| `plugin-sdk/channel-message-runtime` | Runtime delivery helpers that may load outbound delivery, including `deliverInboundReplyWithMessageSendContext`, `sendDurableMessageBatch`, `withDurableMessageSendContext`, `dispatchChannelMessageReplyWithBase`, and `recordChannelMessageReplyDispatch`. Use from monitor/send runtime modules, not hot plugin bootstrap files. |
| `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. |
| `plugin-sdk/inbound-envelope` | Shared inbound route + envelope builder helpers |
| `plugin-sdk/inbound-reply-dispatch` | Legacy shared inbound record-and-dispatch helpers, visible/final dispatch predicates, and deprecated `deliverDurableInboundReplyPayload` compatibility for prepared channel dispatchers. New channel receive/dispatch code should import runtime lifecycle helpers from `plugin-sdk/channel-message-runtime`. |
| `plugin-sdk/messaging-targets` | Target parsing/matching helpers |
| `plugin-sdk/outbound-media` | Shared outbound media loading helpers |
| `plugin-sdk/outbound-send-deps` | Lightweight outbound send dependency lookup for channel adapters |
| `plugin-sdk/outbound-runtime` | Outbound delivery, identity, send delegate, session, formatting, and payload planning helpers |
| `plugin-sdk/outbound-runtime` | Outbound identity, send delegate, session, formatting, and payload planning helpers. Direct delivery helpers such as `deliverOutboundPayloads` are deprecated compatibility substrate; use `plugin-sdk/channel-message-runtime` for new send paths. |
| `plugin-sdk/poll-runtime` | Narrow poll normalization helpers |
| `plugin-sdk/thread-bindings-runtime` | Thread-binding lifecycle and adapter helpers |
| `plugin-sdk/agent-media-payload` | Legacy agent media payload builder |