跳到正文
FunCoding

搜索

搜索文档、Skill 和 MCP

Plugin runtime channel helpers

Channel-specific runtime helper groups for chunking, routing, pairing, media, and mentions

Channel-specific runtime helpers, available when a channel plugin is loaded. Part of the Plugin runtime helpers reference; Channel plugins is the step-by-step guide.

Channel namespaces

api.runtime.channel

Channel-specific runtime helpers (available when a channel plugin is loaded). Grouped by concern:

GroupPurpose
textChunking (chunkText, chunkMarkdownText, resolveChunkMode), control-command detection, Markdown table conversion.
replyBuffered-block reply dispatch, envelope formatting, effective messages/human-delay config resolution.
routingbuildAgentSessionKey, resolveAgentRoute.
pairingbuildPairingReply, allowlist reads/removals, pairing-request upserts, and request-derived approval entries.
mediaRemote media download/save (see below).
activityRecord/read last channel activity.
sessionSession metadata from inbound events, last-route updates.
mentionsMention-policy helpers (see below).
reactionsAck-reaction handles for in-flight processing indicators.
groupsGroup policy and require-mention resolution.
debounceInbound message debouncing.
commandsCommand authorization and text-command gating.
outboundLoad a channel's outbound adapter.
inboundResolve ingress with the host-bound ingress helpers, build inbound event context, and run the shared inbound-event/reply kernel.
threadBindingsAdjust idle-timeout/max-age for bound session threads.
runtimeContextsRegister, read, and watch process-local per-channel/account/capability context.

api.runtime.channel.media is the preferred surface for channel media downloads and storage:

const saved = await api.runtime.channel.media.saveRemoteMedia({
  url,
  subdir: "inbound",
  maxBytes,
  filePathHint: fileName,
});

Use saveRemoteMedia(...) when a remote URL should become OpenClaw media. Use saveResponseMedia(...) when the plugin already fetched a Response with plugin-owned auth, redirect, or allowlist handling. Use readRemoteMediaBuffer(...) only when the plugin needs raw bytes for inspection, transforms, decryption, or reupload. fetchRemoteMedia(...) remains a deprecated compatibility alias for readRemoteMediaBuffer(...), tracked as plugin-runtime-api-compat-aliases in the compatibility registry with a removeAfter date of 2026-10-01.

For unsuccessful HTTP responses, media errors report the status and include a bounded body excerpt when available. A discarded error body is not reported as an empty upstream response. A successful response with no body is still rejected as empty media.

Remote media options and fetchWithSsrFGuard(...) from openclaw/plugin-sdk/ssrf-runtime accept a synchronous beforeRequest callback for final-dispatch authorization checks. It runs after proxy, DNS, and dispatcher preparation and immediately before every physical request. Redirects invoke it once per hop; media retries invoke it again for every attempt and hop. If it throws, that request is not sent and the same error propagates. Promise or thenable results are rejected before transport dispatch.

For saveRemoteMedia(...), pass a synchronous assertCurrent callback when a read can lose permission while its body is downloading. The media owner combines it with any enclosing read scope and rechecks it through requests, streaming, and local-file publication, cleaning up unaccepted files on failure. Forward cancellation with requestInit.signal as well. Omitting assertCurrent preserves existing behavior; beforeRequest remains the per-request hook rather than a file-publication guard.

Guarded fetch also accepts a synchronous resolveDispatcherPolicy(url) override, reevaluated for each redirect. An undefined result uses dispatcherPolicy, or direct routing when no default policy is supplied. Providers preserving operator-configured proxy routing can use resolveEnvHttpProxyAgentOptions and matchesNoProxy from openclaw/plugin-sdk/fetch-runtime to select each hop. The trusted_explicit_proxy mode permits HTTP, HTTPS, socks: and socks5: proxy URLs and delegates target DNS to the explicitly trusted proxy; proxy-host validation and target-host policy still apply. Direct hops keep DNS pinning. Strict mode rejects SOCKS proxies, and the separate trusted-env-proxy gate remains HTTP(S)-only.

When returning a guarded response for streaming, use responseWithRelease(response, release) from openclaw/plugin-sdk/fetch-runtime to retain request ownership until its body completes, fails, or is cancelled. Consume or cancel bodies explicitly for prompt release; garbage collection of abandoned wrappers provides only best-effort cleanup.

api.runtime.channel.mentions is the shared inbound mention-policy surface for bundled channel plugins that use runtime injection:

const mentionMatch = api.runtime.channel.mentions.matchesMentionWithExplicit(text, {
  mentionRegexes,
  mentionPatterns,
});

const decision = api.runtime.channel.mentions.resolveInboundMentionDecision({
  facts: {
    canDetectMention: true,
    wasMentioned: mentionMatch.matched,
    implicitMentionKinds: api.runtime.channel.mentions.implicitMentionKindWhen(
      "reply_to_bot",
      isReplyToBot,
    ),
  },
  policy: {
    isGroup,
    requireMention,
    allowTextCommands,
    hasControlCommand,
    commandAuthorized,
  },
});

Available mention helpers:

  • buildMentionRegexes
  • matchesMentionPatterns
  • matchesMentionWithExplicit
  • implicitMentionKindWhen
  • resolveInboundMentionDecision

Use the normalized { facts, policy } path for mention decisions.

Several fields under reply, session, and inbound carry per-field @deprecated notes pointing at the current channel-turn kernel or channel-outbound adapters; check the inline JSDoc on the specific helper before building new code on it. They share the same plugin-runtime-api-compat-aliases registry record and removeAfter date of 2026-10-01.

Reply options accept onVisibleWorkSessions(sessions) to receive accepted visible work sessions before final reply delivery, including when the settled run failed. Each descriptor carries sessionKey, the canonical url, and an optional label; descriptors are deduplicated by session key in acceptance order.

Awaited conversation binding mutations

Import routing and service helpers from openclaw/plugin-sdk/conversation-binding-runtime.

Await service.bind(input) and service.unbind(input) before publishing a binding change. Current-conversation persistence uses the existing worker owner and rechecks the selected adapter before committing. A failed or uncertain write does not authorize replay. Released synchronous selectors retain their current contract; bundled session listings await the internal owner.

For synchronous directory ownership checks, a channel may implement messaging.prepareConversationRouteOwners(inputs, inspectBindings). It prepares binding reads once and returns exactly one synchronous resolver per input, in the same order. Core invokes these resolvers immediately and preserves the scalar resolver's error and fallback handling. Existing plugins keep resolveConversationRouteOwner. Prepared facts must not survive a wait or be reused for a later authority check.

Call the supplied inspectBindings(refs) once for the batch. It performs one current native selection across generic and account-owned bindings while preserving external adapter ownership. It is valid only during synchronous preparation and rejects later use. Each later check receives a fresh inspector.

Slack retains its declared OpenClaw 2026.9.8 host support through its unchanged scalar resolver. Older hosts do not invoke the optional preparation hook.

Await getSessionBindingService().touchAsync(bindingId, at, scope) when recording binding activity. Adapters implement touchAsync to return a Promise that settles their accepted mutation. Async dispatch prefers that method and propagates its failure; it does not invoke the legacy touch alongside it. Before invoking each selected adapter, dispatch checks that its registration is still current. It skips retired registrations without adopting their replacements.

Use resolveRuntimeConversationBindingRouteAsync for routing that records activity. It waits for the selected mutation and then rechecks the current binding before returning its route. Prepare ownership facts with await service.inspectByConversationAsync(conversation), then pass those facts to inspectRuntimeConversationBindingRoute({ route, inspection }). This synchronous projection performs no storage access. Inspection preserves the distinction between a missing binding and an unavailable adapter without creating a missing store or pruning expired rows.

inspectRuntimeConversationBindingRoute and the synchronous resolveRuntimeConversationBindingRoute also accept a deferred resolveRoute callback instead of a completed route. Pass exactly one of route or resolveRoute; the input type rejects supplying both or neither. Existing callers can keep passing a completed route. The callback receives { inspection, bindingOwnerAvailable, bindingRecord, boundAgentId } after the owner has classified the binding, before ordinary agent selection:

const result = inspectRuntimeConversationBindingRoute({
  inspection,
  resolveRoute: ({ bindingOwnerAvailable, boundAgentId }) => {
    if (!bindingOwnerAvailable) {
      throw new Error("Conversation binding owner is unavailable; retry the message.");
    }
    return resolveAgentRoute({
      channel: "acme-chat",
      accountId,
      peer,
      cfg: boundAgentId ? { session: cfg.session } : cfg,
      defaultAgentId: boundAgentId,
    });
  },
});

Import resolveAgentRoute from openclaw/plugin-sdk/routing. A bound agent can therefore supply the route even when the ordinary roster requires an explicit selection. Agent-scoped session keys take precedence over metadata; unscoped targets can use metadata.agentId. Missing, ignored cron-run, and plugin-owned bindings do not supply a bound agent. An unscoped target without a nonblank metadata agent ID also leaves boundAgentId undefined; it does not invent a default agent. Plugin bindings retain their record so a channel can distinguish a plugin fallback from an unbound parent lookup. inspection retains the prepared conversation identity for composing a thread observation before its selected parent without reading the binding store again. The callback owns route construction; core still projects the selected session and ownership facts. If an agent-owned binding selects a different agent, core rebuilds mainSessionKey for that agent while preserving the base route's main-key name, then derives lastRoutePolicy against the bound agent's main session. This also applies to completed-route inputs and leaves the ordinary route unchanged for channel-specific stale-binding comparison. Preserve those facts through context construction so reply admission can reject a revoked, reassigned, or unavailable owner. When activity must retain a captured selection, await the scoped touchAsync after projection and keep that route for admission rather than silently selecting a replacement.

Adapters provide inspectByConversationAsync for read-only inspection and resolveByConversationAsync for ordinary lookup. The host service exposes both methods. Generic bindings and bundled account-scoped adapters run inspection in the shared-state read worker; lookup repairs and activity writes use the existing writer broker. Their transaction predicates, expiry rules, and account ownership remain unchanged. Host eligibility is prepared before IPC, and current adapter and registry ownership are rechecked after reads and at write admission.

Lifecycle setters have explicit Promise-returning counterparts: channel.threadBindings.setIdleTimeoutBySessionKeyAsync and setMaxAgeBySessionKeyAsync. Channel adapters expose the same suffixed methods. Callers await their results before reporting the affected bindings.

The existing synchronous lookup, touch, route resolver, and lifecycle setter contracts remain deprecated through the next Plugin SDK major. The resolver's staged migration is recorded here and in the compatibility registry; its broad barrel is already deprecated, while its per-function IDE annotation is deferred until the caller migration is complete. Synchronous entry points call only synchronous implementations; they never start an async mutation whose result would be lost. An adapter exposing both variants keeps them under the same state owner.

During the staged migration, async dispatch falls back to an adapter's existing synchronous method when its async counterpart is absent. This preserves external plugin compatibility; that fallback does not make a legacy adapter nonblocking. Generic and account-scoped bind/unbind operations and bundled session listings use the shared-state worker. Their released synchronous selectors remain available through the compatibility boundary. Separate lifecycle setters and other bundled stores retain their existing behavior until their respective cutovers; this execution change does not strengthen their durability contracts.