fix(outbound): keep Discord runtime adapters resolvable (#91119)

Summary:
- The branch changes outbound channel bootstrap and resolution so delivery paths prefer send-capable runtime a ...  avoid setup-only shells for runtime delivery, retry non-send-capable bootstraps, and add regression tests.
- PR surface: Source +121, Tests +294. Total +415 across 4 files.
- Reproducibility: yes. The linked stable-release report supplies the user-visible Discord failure, and curren ... p-shell/direct-registry path that can satisfy runtime resolution before a send-capable adapter is verified.

Automerge notes:
- PR branch already contained follow-up commit before automerge: fix(outbound): keep Discord runtime adapters resolvable
- PR branch already contained follow-up commit before automerge: fix(clawsweeper): reconcile automerge-openclaw-openclaw-90198 with ma…

Validation:
- ClawSweeper review passed for head 8711ada0c4.
- Required merge gates passed before the squash merge.

Prepared head SHA: 8711ada0c4
Review: https://github.com/openclaw/openclaw/pull/91119#issuecomment-4641934231

Co-authored-by: Andy Ye <35905412+TurboTheTurtle@users.noreply.github.com>
Co-authored-by: clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com>
Co-authored-by: clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com>
Approved-by: thewilloftheshadow
Co-authored-by: thewilloftheshadow <35580099+thewilloftheshadow@users.noreply.github.com>
This commit is contained in:
clawsweeper[bot]
2026-06-07 09:30:41 +00:00
committed by GitHub
co-authored by Andy Ye clawsweeper <274271284+clawsweeper[bot]@users.noreply.github.com> clawsweeper[bot] <274271284+clawsweeper[bot]@users.noreply.github.com> thewilloftheshadow
parent dcba17d019
commit 58b68e92f2
4 changed files with 446 additions and 31 deletions
@@ -76,4 +76,94 @@ describe("bootstrapOutboundChannelPlugin", () => {
expect(loaderMocks.resolveRuntimePluginRegistry).not.toHaveBeenCalled();
});
it("skips bootstrap when the active replacement registry can send for a pinned setup shell", () => {
const setup = createEmptyPluginRegistry();
setup.channels = [
{
pluginId: "discord",
plugin: { id: "discord", meta: {} },
source: "setup",
},
] as never;
const runtime = createEmptyPluginRegistry();
runtime.channels = [
{
pluginId: "discord",
plugin: {
id: "discord",
meta: {},
outbound: { sendText: async () => ({ messageId: "1" }) },
},
source: "runtime",
},
] as never;
setActivePluginRegistry(setup);
pinActivePluginChannelRegistry(setup);
setActivePluginRegistry(runtime);
bootstrapOutboundChannelPlugin({
channel: "discord",
cfg: discordConfig,
});
expect(loaderMocks.resolveRuntimePluginRegistry).not.toHaveBeenCalled();
});
it("skips bootstrap when the active replacement registry has a message send surface", () => {
const setup = createEmptyPluginRegistry();
setup.channels = [
{
pluginId: "discord",
plugin: { id: "discord", meta: {} },
source: "setup",
},
] as never;
const runtime = createEmptyPluginRegistry();
runtime.channels = [
{
pluginId: "discord",
plugin: {
id: "discord",
meta: {},
message: { send: { text: async () => ({ messageId: "1" }) } },
},
source: "runtime",
},
] as never;
setActivePluginRegistry(setup);
pinActivePluginChannelRegistry(setup);
setActivePluginRegistry(runtime);
bootstrapOutboundChannelPlugin({
channel: "discord",
cfg: discordConfig,
});
expect(loaderMocks.resolveRuntimePluginRegistry).not.toHaveBeenCalled();
});
it("retries when bootstrap returns without making the channel send-capable", () => {
const registry = createEmptyPluginRegistry();
registry.channels = [
{
pluginId: "discord",
plugin: { id: "discord", meta: {} },
source: "setup",
},
] as never;
setActivePluginRegistry(registry);
pinActivePluginChannelRegistry(registry);
bootstrapOutboundChannelPlugin({
channel: "discord",
cfg: discordConfig,
});
bootstrapOutboundChannelPlugin({
channel: "discord",
cfg: discordConfig,
});
expect(loaderMocks.resolveRuntimePluginRegistry).toHaveBeenCalledTimes(2);
});
});
@@ -8,6 +8,8 @@ import type { PluginChannelRegistration } from "../../plugins/registry-types.js"
import {
getActivePluginChannelRegistry,
getActivePluginChannelRegistryVersion,
getActivePluginRegistry,
getActivePluginRegistryVersion,
} from "../../plugins/runtime.js";
import type { DeliverableMessageChannel } from "../../utils/message-channel.js";
@@ -22,6 +24,27 @@ function channelEntryCanSend(entry: PluginChannelRegistration | undefined): bool
return Boolean(entry?.plugin?.outbound?.sendText ?? entry?.plugin?.message?.send?.text);
}
function findChannelEntry(
registry: ReturnType<typeof getActivePluginRegistry>,
channel: DeliverableMessageChannel,
): PluginChannelRegistration | undefined {
return registry?.channels?.find((entry) => entry?.plugin?.id === channel);
}
function canResolveSendCapableChannel(channel: DeliverableMessageChannel): boolean {
const activeChannelRegistry = getActivePluginChannelRegistry();
const channelEntry = findChannelEntry(activeChannelRegistry, channel);
if (channelEntryCanSend(channelEntry)) {
return true;
}
const activeRegistry = getActivePluginRegistry();
if (activeRegistry && activeRegistry !== activeChannelRegistry) {
return channelEntryCanSend(findChannelEntry(activeRegistry, channel));
}
return false;
}
/** Loads runtime plugins on demand when a selected outbound channel has only a setup shell. */
export function bootstrapOutboundChannelPlugin(params: {
channel: DeliverableMessageChannel;
@@ -32,15 +55,11 @@ export function bootstrapOutboundChannelPlugin(params: {
return;
}
const activeChannelRegistry = getActivePluginChannelRegistry();
const activeChannelEntry = activeChannelRegistry?.channels?.find(
(entry) => entry?.plugin?.id === params.channel,
);
if (channelEntryCanSend(activeChannelEntry)) {
if (canResolveSendCapableChannel(params.channel)) {
return;
}
const attemptKey = `${getActivePluginChannelRegistryVersion()}:${params.channel}`;
const attemptKey = `${getActivePluginChannelRegistryVersion()}:${getActivePluginRegistryVersion()}:${params.channel}`;
if (bootstrapAttempts.has(attemptKey)) {
return;
}
@@ -61,6 +80,9 @@ export function bootstrapOutboundChannelPlugin(params: {
allowGatewaySubagentBinding: true,
},
});
if (!canResolveSendCapableChannel(params.channel)) {
bootstrapAttempts.delete(attemptKey);
}
} catch {
bootstrapAttempts.delete(attemptKey);
}
+209 -5
View File
@@ -9,6 +9,7 @@ const getChannelPluginMock = vi.hoisted(() => vi.fn());
const applyPluginAutoEnableMock = vi.hoisted(() => vi.fn());
const resolveRuntimePluginRegistryMock = vi.hoisted(() => vi.fn());
const getActivePluginRegistryMock = vi.hoisted(() => vi.fn());
const getActivePluginRegistryVersionMock = vi.hoisted(() => vi.fn());
const getActivePluginChannelRegistryMock = vi.hoisted(() => vi.fn());
const getActivePluginChannelRegistryVersionMock = vi.hoisted(() => vi.fn());
const normalizeMessageChannelMock = vi.hoisted(() => vi.fn());
@@ -34,6 +35,8 @@ vi.mock("../../plugins/loader.js", () => ({
vi.mock("../../plugins/runtime.js", () => ({
getActivePluginRegistry: (...args: unknown[]) => getActivePluginRegistryMock(...args),
getActivePluginRegistryVersion: (...args: unknown[]) =>
getActivePluginRegistryVersionMock(...args),
getActivePluginChannelRegistry: (...args: unknown[]) =>
getActivePluginChannelRegistryMock(...args),
getActivePluginChannelRegistryVersion: (...args: unknown[]) =>
@@ -75,6 +78,7 @@ describe("outbound channel resolution", () => {
applyPluginAutoEnableMock.mockReset();
resolveRuntimePluginRegistryMock.mockReset();
getActivePluginRegistryMock.mockReset();
getActivePluginRegistryVersionMock.mockReset();
getActivePluginChannelRegistryMock.mockReset();
getActivePluginChannelRegistryVersionMock.mockReset();
normalizeMessageChannelMock.mockReset();
@@ -87,6 +91,7 @@ describe("outbound channel resolution", () => {
["alpha", "beta", "gamma"].includes(String(value)),
);
getActivePluginRegistryMock.mockReturnValue({ channels: [] });
getActivePluginRegistryVersionMock.mockReturnValue(1);
getActivePluginChannelRegistryMock.mockReturnValue({ channels: [] });
getActivePluginChannelRegistryVersionMock.mockReturnValue(1);
applyPluginAutoEnableMock.mockReturnValue({
@@ -110,7 +115,7 @@ describe("outbound channel resolution", () => {
});
it("returns the already-registered plugin without bootstrapping", async () => {
const plugin = { id: "alpha" };
const plugin = { id: "alpha", outbound: { sendText: vi.fn() } };
getLoadedChannelPluginMock.mockReturnValueOnce(plugin);
const channelResolution = await importChannelResolution("existing-plugin");
@@ -124,7 +129,7 @@ describe("outbound channel resolution", () => {
});
it("returns a bundled plugin without bootstrapping", async () => {
const plugin = { id: "alpha" };
const plugin = { id: "alpha", outbound: { sendText: vi.fn() } };
getLoadedChannelPluginMock.mockReturnValue(undefined);
getChannelPluginMock.mockReturnValue(plugin);
const channelResolution = await importChannelResolution("bundled-plugin");
@@ -158,8 +163,52 @@ describe("outbound channel resolution", () => {
).toBe(plugin);
});
it("resolves message adapters from the pinned channel registry after active registry replacement", async () => {
const message = { send: { text: vi.fn() } };
const plugin = { id: "alpha", message };
getLoadedChannelPluginMock.mockReturnValue(undefined);
getChannelPluginMock.mockReturnValue(undefined);
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin }],
});
getActivePluginRegistryMock.mockReturnValue({ channels: [] });
const channelResolution = await importChannelResolution("pinned-message-registry");
expect(
channelResolution.resolveOutboundChannelMessageAdapter({
channel: "alpha",
cfg: {} as never,
}),
).toBe(message);
});
it("skips metadata-only loaded message shells for active send-capable message adapters", async () => {
const setupMessage = { receive: { defaultAckPolicy: "manual" } };
const runtimeMessage = { send: { text: vi.fn() } };
const setupPlugin = { id: "alpha", message: setupMessage };
const runtimePlugin = { id: "alpha", message: runtimeMessage };
getLoadedChannelPluginMock.mockReturnValue(setupPlugin);
getChannelPluginMock.mockReturnValue(undefined);
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin: setupPlugin }],
});
getActivePluginRegistryMock.mockReturnValue({
channels: [{ plugin: runtimePlugin }],
});
const channelResolution = await importChannelResolution("message-metadata-shell");
expect(
channelResolution.resolveOutboundChannelMessageAdapter({
channel: "alpha",
cfg: { channels: {} } as never,
allowBootstrap: true,
}),
).toBe(runtimeMessage);
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
});
it("bootstraps configured channel plugins when the active registry is missing the target", async () => {
const plugin = { id: "alpha" };
const plugin = { id: "alpha", outbound: { sendText: vi.fn() } };
getLoadedChannelPluginMock.mockReturnValueOnce(undefined).mockReturnValueOnce(plugin);
const channelResolution = await importChannelResolution("bootstrap-missing-target");
@@ -182,6 +231,161 @@ describe("outbound channel resolution", () => {
});
});
it("bootstraps instead of returning a pinned setup shell as the outbound plugin", async () => {
const setupPlugin = { id: "alpha" };
const runtimePlugin = { id: "alpha", outbound: { sendText: vi.fn() } };
getLoadedChannelPluginMock.mockReturnValueOnce(setupPlugin).mockReturnValueOnce(runtimePlugin);
getChannelPluginMock.mockReturnValue(undefined);
getActivePluginRegistryMock.mockReturnValue({ channels: [] });
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin: setupPlugin }],
});
const channelResolution = await importChannelResolution("bootstrap-setup-shell");
expect(
channelResolution.resolveOutboundChannelPlugin({
channel: "alpha",
cfg: { channels: {} } as never,
allowBootstrap: true,
}),
).toBe(runtimePlugin);
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(1);
});
it("does not return a setup shell when bootstrap does not produce a runtime plugin", async () => {
const setupPlugin = { id: "alpha" };
getLoadedChannelPluginMock.mockReturnValue(setupPlugin);
getChannelPluginMock.mockReturnValue(setupPlugin);
getActivePluginRegistryMock.mockReturnValue({
channels: [{ plugin: setupPlugin }],
});
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin: setupPlugin }],
});
const channelResolution = await importChannelResolution("bootstrap-still-setup-shell");
expect(
channelResolution.resolveOutboundChannelPlugin({
channel: "alpha",
cfg: { channels: {} } as never,
allowBootstrap: true,
}),
).toBeUndefined();
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(1);
});
it("does not treat an actions-only plugin as send-capable after bootstrap", async () => {
const actionsOnlyPlugin = { id: "alpha", actions: { handleAction: vi.fn() } };
getLoadedChannelPluginMock.mockReturnValue(actionsOnlyPlugin);
getChannelPluginMock.mockReturnValue(actionsOnlyPlugin);
getActivePluginRegistryMock.mockReturnValue({
channels: [{ plugin: actionsOnlyPlugin }],
});
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin: actionsOnlyPlugin }],
});
const channelResolution = await importChannelResolution("actions-only-plugin");
expect(
channelResolution.resolveOutboundChannelPlugin({
channel: "alpha",
cfg: { channels: {} } as never,
allowBootstrap: true,
}),
).toBeUndefined();
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(1);
});
it("prefers an active runtime plugin over a loaded setup shell", async () => {
const setupPlugin = { id: "alpha" };
const runtimePlugin = { id: "alpha", outbound: { sendText: vi.fn() } };
getLoadedChannelPluginMock.mockReturnValue(setupPlugin);
getChannelPluginMock.mockReturnValue(undefined);
getActivePluginRegistryMock.mockReturnValue({
channels: [{ plugin: runtimePlugin }],
});
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin: setupPlugin }],
});
const channelResolution = await importChannelResolution("active-runtime-over-setup");
expect(
channelResolution.resolveOutboundChannelPlugin({
channel: "alpha",
cfg: { channels: {} } as never,
allowBootstrap: true,
}),
).toBe(runtimePlugin);
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
});
it("resolves outbound plugins from the selected runtime channel registry", async () => {
const setupPlugin = { id: "alpha" };
const runtimePlugin = { id: "alpha", outbound: { sendText: vi.fn() } };
getLoadedChannelPluginMock.mockReturnValue(undefined);
getChannelPluginMock.mockReturnValue(undefined);
getActivePluginRegistryMock.mockReturnValue({
channels: [{ plugin: setupPlugin }],
});
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin: runtimePlugin }],
});
const channelResolution = await importChannelResolution("selected-runtime-registry");
expect(
channelResolution.resolveOutboundChannelPlugin({
channel: "alpha",
cfg: { channels: {} } as never,
allowBootstrap: true,
}),
).toBe(runtimePlugin);
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
});
it("resolves runtime outbound adapters that do not send text directly", async () => {
const setupPlugin = { id: "alpha" };
const runtimePlugin = { id: "alpha", outbound: { deliveryMode: "gateway" } };
getLoadedChannelPluginMock.mockReturnValue(setupPlugin);
getChannelPluginMock.mockReturnValue(undefined);
getActivePluginRegistryMock.mockReturnValue({
channels: [{ plugin: runtimePlugin }],
});
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin: setupPlugin }],
});
const channelResolution = await importChannelResolution("runtime-outbound-adapter");
expect(
channelResolution.resolveOutboundChannelPlugin({
channel: "alpha",
cfg: { channels: {} } as never,
allowBootstrap: true,
}),
).toBe(runtimePlugin);
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
});
it("keeps setup shells visible for read-only channel lookup", async () => {
const setupPlugin = { id: "alpha" };
const runtimePlugin = { id: "alpha", outbound: { sendText: vi.fn() } };
getLoadedChannelPluginMock.mockReturnValue(setupPlugin);
getChannelPluginMock.mockReturnValue(undefined);
getActivePluginRegistryMock.mockReturnValue({
channels: [{ plugin: runtimePlugin }],
});
getActivePluginChannelRegistryMock.mockReturnValue({
channels: [{ plugin: setupPlugin }],
});
const channelResolution = await importChannelResolution("read-setup-shell");
expect(
channelResolution.resolveOutboundChannelPluginForRead({
channel: "alpha",
}),
).toBe(setupPlugin);
expect(resolveRuntimePluginRegistryMock).not.toHaveBeenCalled();
});
it("attempts activation when the active registry has other channels but not the requested one", async () => {
getLoadedChannelPluginMock.mockReturnValue(undefined);
getChannelPluginMock.mockReturnValue(undefined);
@@ -203,7 +407,7 @@ describe("outbound channel resolution", () => {
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(1);
});
it("does not retry registry loads after a missing outbound plugin", async () => {
it("retries registry loads after bootstrap does not make the channel send-capable", async () => {
getChannelPluginMock.mockReturnValue(undefined);
const channelResolution = await importChannelResolution("bootstrap-retry");
@@ -220,7 +424,7 @@ describe("outbound channel resolution", () => {
cfg: { channels: {} } as never,
allowBootstrap: true,
});
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(1);
expect(resolveRuntimePluginRegistryMock).toHaveBeenCalledTimes(2);
});
it("allows another activation attempt when the pinned channel registry version changes", async () => {
+119 -20
View File
@@ -21,7 +21,7 @@ import type {
ChannelThreadingAdapter,
} from "../../channels/plugins/types.public.js";
import type { OpenClawConfig } from "../../config/types.openclaw.js";
import { getActivePluginRegistry } from "../../plugins/runtime.js";
import { getActivePluginChannelRegistry, getActivePluginRegistry } from "../../plugins/runtime.js";
import {
isDeliverableMessageChannel,
normalizeMessageChannel,
@@ -111,12 +111,14 @@ function maybeBootstrapChannelPlugin(params: {
bootstrapOutboundChannelPlugin(params);
}
function resolveDirectFromActiveRegistry(channel: string): ChannelPlugin | undefined {
const activeRegistry = getActivePluginRegistry();
if (!activeRegistry) {
function resolveDirectFromRegistry(
registry: ReturnType<typeof getActivePluginRegistry>,
channel: string,
): ChannelPlugin | undefined {
if (!registry) {
return undefined;
}
for (const entry of activeRegistry.channels) {
for (const entry of registry.channels) {
const plugin = entry?.plugin;
if (plugin?.id === channel) {
return plugin;
@@ -125,6 +127,81 @@ function resolveDirectFromActiveRegistry(channel: string): ChannelPlugin | undef
return undefined;
}
function messageAdapterCanSendText(
message: ChannelMessageAdapterShape | undefined,
): message is ChannelMessageAdapterShape {
return typeof message?.send?.text === "function";
}
function resolveSendCapableMessageAdapter(
plugin: ChannelPlugin | undefined,
): ChannelMessageAdapterShape | undefined {
const message = plugin?.message;
return messageAdapterCanSendText(message) ? message : undefined;
}
function channelPluginHasRuntimeOutboundSurface(plugin: ChannelPlugin | undefined): boolean {
return Boolean(plugin?.outbound ?? resolveSendCapableMessageAdapter(plugin));
}
function resolveRuntimeOutboundPlugin(plugin: ChannelPlugin): ChannelPlugin | undefined {
return channelPluginHasRuntimeOutboundSurface(plugin) ? plugin : undefined;
}
function resolveRuntimeOutboundPluginCandidate(params: {
loaded?: ChannelPlugin;
runtime?: ChannelPlugin;
setupFallback?: ChannelPlugin;
bundled?: ChannelPlugin;
allowSetupShell?: boolean;
}): ChannelPlugin | undefined {
if (channelPluginHasRuntimeOutboundSurface(params.loaded)) {
return params.loaded;
}
if (params.runtime) {
return params.runtime;
}
if (channelPluginHasRuntimeOutboundSurface(params.bundled)) {
return params.bundled;
}
if (params.allowSetupShell) {
return params.loaded ?? params.setupFallback ?? params.bundled;
}
return undefined;
}
function resolveValueFromRuntimeRegistries<TValue>(
channel: string,
resolveValue: (plugin: ChannelPlugin) => TValue | undefined,
): TValue | undefined {
const channelRegistry = getActivePluginChannelRegistry();
const channelPlugin = resolveDirectFromRegistry(channelRegistry, channel);
if (channelPlugin) {
const value = resolveValue(channelPlugin);
if (value !== undefined) {
return value;
}
}
const activeRegistry = getActivePluginRegistry();
if (activeRegistry && activeRegistry !== channelRegistry) {
const activePlugin = resolveDirectFromRegistry(activeRegistry, channel);
if (activePlugin) {
return resolveValue(activePlugin);
}
}
return undefined;
}
function resolveDirectFromRuntimeRegistries(channel: string): ChannelPlugin | undefined {
return resolveValueFromRuntimeRegistries(channel, (plugin) => plugin);
}
function resolveRuntimeOutboundPluginFromRuntimeRegistries(
channel: string,
): ChannelPlugin | undefined {
return resolveValueFromRuntimeRegistries(channel, resolveRuntimeOutboundPlugin);
}
function toOutboundChannelRuntime(plugin: ChannelPlugin): OutboundChannelRuntime {
return {
id: plugin.id,
@@ -191,17 +268,18 @@ export function resolveOutboundChannelPlugin(params: {
const resolveLoaded = () => getLoadedChannelPlugin(normalized);
const resolve = () => getChannelPlugin(normalized);
const current = resolveLoaded();
if (current) {
return current;
}
const directCurrent = resolveDirectFromActiveRegistry(normalized);
if (directCurrent) {
return directCurrent;
}
const runtimeCurrent = resolveRuntimeOutboundPluginFromRuntimeRegistries(normalized);
const setupFallback = resolveDirectFromRuntimeRegistries(normalized);
const bundledCurrent = resolve();
if (bundledCurrent) {
return bundledCurrent;
const candidate = resolveRuntimeOutboundPluginCandidate({
loaded: current,
runtime: runtimeCurrent,
setupFallback,
bundled: bundledCurrent,
allowSetupShell: params.allowBootstrap !== true,
});
if (candidate) {
return candidate;
}
if (params.allowBootstrap !== true) {
@@ -209,7 +287,12 @@ export function resolveOutboundChannelPlugin(params: {
}
maybeBootstrapChannelPlugin({ channel: normalized, cfg: params.cfg });
return resolveLoaded() ?? resolveDirectFromActiveRegistry(normalized) ?? resolve();
return resolveRuntimeOutboundPluginCandidate({
loaded: resolveLoaded(),
runtime: resolveRuntimeOutboundPluginFromRuntimeRegistries(normalized),
setupFallback: resolveDirectFromRuntimeRegistries(normalized),
bundled: resolve(),
});
}
/** Resolves the message adapter for a deliverable outbound channel. */
@@ -218,7 +301,23 @@ export function resolveOutboundChannelMessageAdapter(params: {
cfg?: OpenClawConfig;
allowBootstrap?: boolean;
}): ChannelMessageAdapterShape | undefined {
return resolveOutboundChannelPlugin(params)?.message;
const normalized = normalizeDeliverableOutboundChannel(params.channel);
if (!normalized) {
return undefined;
}
const current =
resolveSendCapableMessageAdapter(getLoadedChannelPlugin(normalized)) ??
resolveValueFromRuntimeRegistries(normalized, resolveSendCapableMessageAdapter) ??
resolveSendCapableMessageAdapter(getChannelPlugin(normalized));
if (current || params.allowBootstrap !== true) {
return current;
}
maybeBootstrapChannelPlugin({ channel: normalized, cfg: params.cfg });
return (
resolveSendCapableMessageAdapter(getLoadedChannelPlugin(normalized)) ??
resolveValueFromRuntimeRegistries(normalized, resolveSendCapableMessageAdapter) ??
resolveSendCapableMessageAdapter(getChannelPlugin(normalized))
);
}
/** Resolves a channel plugin for read-only metadata paths. */
@@ -235,7 +334,7 @@ export function resolveOutboundChannelPluginForRead(params: {
if (current) {
return current;
}
const directCurrent = resolveDirectFromActiveRegistry(normalized);
const directCurrent = resolveDirectFromRuntimeRegistries(normalized);
if (directCurrent) {
return directCurrent;
}
@@ -244,7 +343,7 @@ export function resolveOutboundChannelPluginForRead(params: {
maybeBootstrapChannelPlugin({ channel: deliverable, cfg: params.cfg });
return (
getLoadedChannelPlugin(deliverable) ??
resolveDirectFromActiveRegistry(deliverable) ??
resolveDirectFromRuntimeRegistries(deliverable) ??
getChannelPlugin(deliverable)
);
}
@@ -270,6 +369,6 @@ export function resolveLoadedOutboundChannelPluginForRead(params: {
}
return (
getLoadedChannelPlugin(normalized as Parameters<typeof getLoadedChannelPlugin>[0]) ??
resolveDirectFromActiveRegistry(normalized)
resolveDirectFromRuntimeRegistries(normalized)
);
}