
























11// Channel resolution exposes read-only outbound runtime facades and performs
22// optional bootstrap for deliverable channels that are not loaded yet.
3+import { normalizeOptionalLowercaseString } from "@openclaw/normalization-core/string-coerce";
34import type { ChannelMessageAdapterShape } from "../../channels/message/types.js";
45import { getChannelPlugin, getLoadedChannelPlugin } from "../../channels/plugins/index.js";
56import { channelPluginHasNativeApprovalPromptUi } from "../../channels/plugins/native-approval-prompt.js";
@@ -23,6 +24,7 @@ import type {
2324import type { OpenClawConfig } from "../../config/types.openclaw.js";
2425import { getActivePluginChannelRegistry, getActivePluginRegistry } from "../../plugins/runtime.js";
2526import {
27+INTERNAL_MESSAGE_CHANNEL,
2628isDeliverableMessageChannel,
2729normalizeMessageChannel,
2830type DeliverableMessageChannel,
@@ -111,16 +113,62 @@ function maybeBootstrapChannelPlugin(params: {
111113bootstrapOutboundChannelPlugin(params);
112114}
113115116+function normalizeOutboundChannelForResolution(params: {
117+channel: string;
118+cfg?: OpenClawConfig;
119+allowBootstrap?: boolean;
120+}): { channel?: DeliverableMessageChannel; didBootstrap: boolean } {
121+const normalized = normalizeMessageChannel(params.channel);
122+const deliverable = normalizeDeliverableOutboundChannel(normalized);
123+if (deliverable || !normalized || normalized === INTERNAL_MESSAGE_CHANNEL) {
124+return { channel: deliverable, didBootstrap: false };
125+}
126+127+const activeRuntimePlugin = resolveActivatedOutboundPluginFromRuntimeRegistries(normalized);
128+if (activeRuntimePlugin) {
129+return {
130+channel: activeRuntimePlugin.id as DeliverableMessageChannel,
131+didBootstrap: false,
132+};
133+}
134+if (params.allowBootstrap !== true) {
135+return { channel: undefined, didBootstrap: false };
136+}
137+138+// External channel ids remain normalized before their runtime is registered.
139+// Bootstrap first, then let the runtime candidate lookup confirm sendability.
140+maybeBootstrapChannelPlugin({
141+channel: normalized as DeliverableMessageChannel,
142+cfg: params.cfg,
143+});
144+const bootstrappedRuntimePlugin = resolveActivatedOutboundPluginFromRuntimeRegistries(normalized);
145+return {
146+// The pinned channel registry may intentionally lag the active runtime
147+// registry, so strict registry validation here would hide a usable plugin.
148+channel: (bootstrappedRuntimePlugin?.id ?? normalized) as DeliverableMessageChannel,
149+didBootstrap: true,
150+};
151+}
152+114153function resolveDirectFromRegistry(
115154registry: ReturnType<typeof getActivePluginRegistry>,
116155channel: string,
117156): ChannelPlugin | undefined {
118157if (!registry) {
119158return undefined;
120159}
160+const normalizedChannel = normalizeOptionalLowercaseString(channel);
161+if (!normalizedChannel) {
162+return undefined;
163+}
121164for (const entry of registry.channels) {
122165const plugin = entry?.plugin;
123-if (plugin?.id === channel) {
166+if (
167+normalizeOptionalLowercaseString(plugin?.id) === normalizedChannel ||
168+plugin?.meta?.aliases?.some(
169+(alias) => normalizeOptionalLowercaseString(alias) === normalizedChannel,
170+)
171+) {
124172return plugin;
125173}
126174}
@@ -144,24 +192,40 @@ function channelPluginHasRuntimeOutboundSurface(plugin: ChannelPlugin | undefine
144192return Boolean(plugin?.outbound ?? resolveSendCapableMessageAdapter(plugin));
145193}
146194195+function channelPluginHasActivatedOutboundSurface(plugin: ChannelPlugin | undefined): boolean {
196+return Boolean(
197+plugin?.outbound?.sendText ||
198+plugin?.outbound?.deliveryMode === "gateway" ||
199+resolveSendCapableMessageAdapter(plugin),
200+);
201+}
202+147203function resolveRuntimeOutboundPlugin(plugin: ChannelPlugin): ChannelPlugin | undefined {
148204return channelPluginHasRuntimeOutboundSurface(plugin) ? plugin : undefined;
149205}
150206207+function resolveActivatedOutboundPlugin(plugin: ChannelPlugin): ChannelPlugin | undefined {
208+return channelPluginHasActivatedOutboundSurface(plugin) ? plugin : undefined;
209+}
210+151211function resolveRuntimeOutboundPluginCandidate(params: {
152212loaded?: ChannelPlugin;
153213runtime?: ChannelPlugin;
154214setupFallback?: ChannelPlugin;
155215bundled?: ChannelPlugin;
156216allowSetupShell?: boolean;
217+requireActivatedRuntime?: boolean;
157218}): ChannelPlugin | undefined {
158-if (channelPluginHasRuntimeOutboundSurface(params.loaded)) {
219+const hasRuntimeSurface = params.requireActivatedRuntime
220+ ? channelPluginHasActivatedOutboundSurface
221+ : channelPluginHasRuntimeOutboundSurface;
222+if (hasRuntimeSurface(params.loaded)) {
159223return params.loaded;
160224}
161-if (params.runtime) {
225+if (hasRuntimeSurface(params.runtime)) {
162226return params.runtime;
163227}
164-if (channelPluginHasRuntimeOutboundSurface(params.bundled)) {
228+if (hasRuntimeSurface(params.bundled)) {
165229return params.bundled;
166230}
167231if (params.allowSetupShell) {
@@ -202,6 +266,12 @@ function resolveRuntimeOutboundPluginFromRuntimeRegistries(
202266return resolveValueFromRuntimeRegistries(channel, resolveRuntimeOutboundPlugin);
203267}
204268269+function resolveActivatedOutboundPluginFromRuntimeRegistries(
270+channel: string,
271+): ChannelPlugin | undefined {
272+return resolveValueFromRuntimeRegistries(channel, resolveActivatedOutboundPlugin);
273+}
274+205275function toOutboundChannelRuntime(plugin: ChannelPlugin): OutboundChannelRuntime {
206276return {
207277id: plugin.id,
@@ -260,15 +330,18 @@ export function resolveOutboundChannelPlugin(params: {
260330cfg?: OpenClawConfig;
261331allowBootstrap?: boolean;
262332}): ChannelPlugin | undefined {
263-const normalized = normalizeDeliverableOutboundChannel(params.channel);
333+const { channel: normalized, didBootstrap } = normalizeOutboundChannelForResolution(params);
264334if (!normalized) {
265335return undefined;
266336}
267337268338const resolveLoaded = () => getLoadedChannelPlugin(normalized);
269339const resolve = () => getChannelPlugin(normalized);
270340const current = resolveLoaded();
271-const runtimeCurrent = resolveRuntimeOutboundPluginFromRuntimeRegistries(normalized);
341+const requireActivatedRuntime = params.allowBootstrap === true;
342+const runtimeCurrent = requireActivatedRuntime
343+ ? resolveActivatedOutboundPluginFromRuntimeRegistries(normalized)
344+ : resolveRuntimeOutboundPluginFromRuntimeRegistries(normalized);
272345const setupFallback = resolveDirectFromRuntimeRegistries(normalized);
273346const bundledCurrent = resolve();
274347const candidate = resolveRuntimeOutboundPluginCandidate({
@@ -277,21 +350,23 @@ export function resolveOutboundChannelPlugin(params: {
277350 setupFallback,
278351bundled: bundledCurrent,
279352allowSetupShell: params.allowBootstrap !== true,
353+ requireActivatedRuntime,
280354});
281355if (candidate) {
282356return candidate;
283357}
284358285-if (params.allowBootstrap !== true) {
359+if (params.allowBootstrap !== true || didBootstrap) {
286360return undefined;
287361}
288362289363maybeBootstrapChannelPlugin({ channel: normalized, cfg: params.cfg });
290364return resolveRuntimeOutboundPluginCandidate({
291365loaded: resolveLoaded(),
292-runtime: resolveRuntimeOutboundPluginFromRuntimeRegistries(normalized),
366+runtime: resolveActivatedOutboundPluginFromRuntimeRegistries(normalized),
293367setupFallback: resolveDirectFromRuntimeRegistries(normalized),
294368bundled: resolve(),
369+requireActivatedRuntime: true,
295370});
296371}
297372@@ -301,15 +376,15 @@ export function resolveOutboundChannelMessageAdapter(params: {
301376cfg?: OpenClawConfig;
302377allowBootstrap?: boolean;
303378}): ChannelMessageAdapterShape | undefined {
304-const normalized = normalizeDeliverableOutboundChannel(params.channel);
379+const { channel: normalized, didBootstrap } = normalizeOutboundChannelForResolution(params);
305380if (!normalized) {
306381return undefined;
307382}
308383const current =
309384resolveSendCapableMessageAdapter(getLoadedChannelPlugin(normalized)) ??
310385resolveValueFromRuntimeRegistries(normalized, resolveSendCapableMessageAdapter) ??
311386resolveSendCapableMessageAdapter(getChannelPlugin(normalized));
312-if (current || params.allowBootstrap !== true) {
387+if (current || params.allowBootstrap !== true || didBootstrap) {
313388return current;
314389}
315390maybeBootstrapChannelPlugin({ channel: normalized, cfg: params.cfg });
此内容由惯性聚合(RSS阅读器)自动聚合整理,仅供阅读参考。 原文来自 — 版权归原作者所有。