From d9c285e930d744a747ccc4ee600b4f6ffd6bd772 Mon Sep 17 00:00:00 2001 From: scoootscooob <167050519+scoootscooob@users.noreply.github.com> Date: Sat, 14 Mar 2026 09:58:03 -0700 Subject: [PATCH] Fix configure startup stalls from outbound send-deps imports (#46301) * fix: avoid configure startup plugin stalls * fix: credit configure startup changelog entry --- CHANGELOG.md | 1 + extensions/discord/src/channel.ts | 2 +- extensions/discord/src/outbound-adapter.ts | 2 +- extensions/imessage/src/channel.ts | 2 +- extensions/matrix/src/outbound.ts | 2 +- extensions/msteams/src/outbound.ts | 2 +- extensions/signal/src/channel.ts | 2 +- extensions/slack/src/channel.ts | 2 +- extensions/telegram/src/channel.ts | 2 +- extensions/telegram/src/outbound-adapter.ts | 2 +- extensions/whatsapp/src/outbound-adapter.ts | 2 +- src/channels/plugins/outbound/imessage.ts | 5 ++- src/channels/plugins/outbound/signal.ts | 5 ++- src/channels/plugins/outbound/slack.ts | 2 +- src/channels/plugins/whatsapp-shared.ts | 2 +- src/cli/deps.ts | 2 +- src/infra/outbound/deliver.ts | 44 +-------------------- src/infra/outbound/send-deps.ts | 41 +++++++++++++++++++ 18 files changed, 65 insertions(+), 57 deletions(-) create mode 100644 src/infra/outbound/send-deps.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 5885a548e0d..b11e1f03da9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ Docs: https://docs.openclaw.ai - Z.AI/onboarding: detect a working default model even for explicit `zai-coding-*` endpoint choices, so Coding Plan setup can keep the selected endpoint while defaulting to `glm-5` when available or `glm-4.7` as fallback. (#45969) - Control UI/chat sessions: show human-readable labels in the grouped session dropdown again, keep unique scoped fallbacks when metadata is missing, and disambiguate duplicate labels only when needed. (#45130) thanks @luzhidong. +- Configure/startup: move outbound send-deps resolution into a lightweight helper so `openclaw configure` no longer stalls after the banner while eagerly loading channel plugins. (#46301) thanks @scoootscooob. ## 2026.3.13 diff --git a/extensions/discord/src/channel.ts b/extensions/discord/src/channel.ts index c910e56342d..dff426ab2e4 100644 --- a/extensions/discord/src/channel.ts +++ b/extensions/discord/src/channel.ts @@ -37,7 +37,7 @@ import { type ChannelPlugin, type ResolvedDiscordAccount, } from "openclaw/plugin-sdk/discord"; -import { resolveOutboundSendDep } from "../../../src/infra/outbound/deliver.js"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { getDiscordRuntime } from "./runtime.js"; type DiscordSendFn = ReturnType< diff --git a/extensions/discord/src/outbound-adapter.ts b/extensions/discord/src/outbound-adapter.ts index cea9bdb3cee..4c17960791d 100644 --- a/extensions/discord/src/outbound-adapter.ts +++ b/extensions/discord/src/outbound-adapter.ts @@ -1,8 +1,8 @@ import { sendTextMediaPayload } from "../../../src/channels/plugins/outbound/direct-text-media.js"; import type { ChannelOutboundAdapter } from "../../../src/channels/plugins/types.js"; import type { OpenClawConfig } from "../../../src/config/config.js"; -import { resolveOutboundSendDep } from "../../../src/infra/outbound/deliver.js"; import type { OutboundIdentity } from "../../../src/infra/outbound/identity.js"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { getThreadBindingManager, type ThreadBindingRecord } from "./monitor/thread-bindings.js"; import { normalizeDiscordOutboundTarget } from "./normalize.js"; import { sendMessageDiscord, sendPollDiscord, sendWebhookMessageDiscord } from "./send.js"; diff --git a/extensions/imessage/src/channel.ts b/extensions/imessage/src/channel.ts index 2394f80ec62..ff3758bf0d6 100644 --- a/extensions/imessage/src/channel.ts +++ b/extensions/imessage/src/channel.ts @@ -29,7 +29,7 @@ import { type ChannelPlugin, type ResolvedIMessageAccount, } from "openclaw/plugin-sdk/imessage"; -import { resolveOutboundSendDep } from "../../../src/infra/outbound/deliver.js"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js"; import { getIMessageRuntime } from "./runtime.js"; diff --git a/extensions/matrix/src/outbound.ts b/extensions/matrix/src/outbound.ts index 1018fd0c2e5..072ab2fb8c1 100644 --- a/extensions/matrix/src/outbound.ts +++ b/extensions/matrix/src/outbound.ts @@ -1,5 +1,5 @@ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/matrix"; -import { resolveOutboundSendDep } from "../../../src/infra/outbound/deliver.js"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { sendMessageMatrix, sendPollMatrix } from "./matrix/send.js"; import { getMatrixRuntime } from "./runtime.js"; diff --git a/extensions/msteams/src/outbound.ts b/extensions/msteams/src/outbound.ts index 4241e166872..60d78a2dac5 100644 --- a/extensions/msteams/src/outbound.ts +++ b/extensions/msteams/src/outbound.ts @@ -1,5 +1,5 @@ import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/msteams"; -import { resolveOutboundSendDep } from "../../../src/infra/outbound/deliver.js"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { createMSTeamsPollStoreFs } from "./polls.js"; import { getMSTeamsRuntime } from "./runtime.js"; import { sendMessageMSTeams, sendPollMSTeams } from "./send.js"; diff --git a/extensions/signal/src/channel.ts b/extensions/signal/src/channel.ts index f763f0c6769..7b1f3e5493a 100644 --- a/extensions/signal/src/channel.ts +++ b/extensions/signal/src/channel.ts @@ -30,7 +30,7 @@ import { type ChannelPlugin, type ResolvedSignalAccount, } from "openclaw/plugin-sdk/signal"; -import { resolveOutboundSendDep } from "../../../src/infra/outbound/deliver.js"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { getSignalRuntime } from "./runtime.js"; const signalMessageActions: ChannelMessageActionAdapter = { diff --git a/extensions/slack/src/channel.ts b/extensions/slack/src/channel.ts index d288963efc6..04b46357db4 100644 --- a/extensions/slack/src/channel.ts +++ b/extensions/slack/src/channel.ts @@ -38,7 +38,7 @@ import { type ChannelPlugin, type ResolvedSlackAccount, } from "openclaw/plugin-sdk/slack"; -import { resolveOutboundSendDep } from "../../../src/infra/outbound/deliver.js"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { buildPassiveProbedChannelStatusSummary } from "../../shared/channel-status-summary.js"; import { getSlackRuntime } from "./runtime.js"; diff --git a/extensions/telegram/src/channel.ts b/extensions/telegram/src/channel.ts index b13e33859f9..50509e51fca 100644 --- a/extensions/telegram/src/channel.ts +++ b/extensions/telegram/src/channel.ts @@ -43,7 +43,7 @@ import { import { type OutboundSendDeps, resolveOutboundSendDep, -} from "../../../src/infra/outbound/deliver.js"; +} from "../../../src/infra/outbound/send-deps.js"; import { getTelegramRuntime } from "./runtime.js"; type TelegramSendFn = ReturnType< diff --git a/extensions/telegram/src/outbound-adapter.ts b/extensions/telegram/src/outbound-adapter.ts index 52700ba61dc..7fcbd564e67 100644 --- a/extensions/telegram/src/outbound-adapter.ts +++ b/extensions/telegram/src/outbound-adapter.ts @@ -7,7 +7,7 @@ import type { ChannelOutboundAdapter } from "../../../src/channels/plugins/types import { resolveOutboundSendDep, type OutboundSendDeps, -} from "../../../src/infra/outbound/deliver.js"; +} from "../../../src/infra/outbound/send-deps.js"; import type { TelegramInlineButtons } from "./button-types.js"; import { markdownToTelegramHtmlChunks } from "./format.js"; import { parseTelegramReplyToMessageId, parseTelegramThreadId } from "./outbound-params.js"; diff --git a/extensions/whatsapp/src/outbound-adapter.ts b/extensions/whatsapp/src/outbound-adapter.ts index 428b8a3f8c8..ba84e336d0e 100644 --- a/extensions/whatsapp/src/outbound-adapter.ts +++ b/extensions/whatsapp/src/outbound-adapter.ts @@ -2,7 +2,7 @@ import { chunkText } from "../../../src/auto-reply/chunk.js"; import { sendTextMediaPayload } from "../../../src/channels/plugins/outbound/direct-text-media.js"; import type { ChannelOutboundAdapter } from "../../../src/channels/plugins/types.js"; import { shouldLogVerbose } from "../../../src/globals.js"; -import { resolveOutboundSendDep } from "../../../src/infra/outbound/deliver.js"; +import { resolveOutboundSendDep } from "../../../src/infra/outbound/send-deps.js"; import { resolveWhatsAppOutboundTarget } from "../../../src/whatsapp/resolve-outbound-target.js"; import { sendMessageWhatsApp, sendPollWhatsApp } from "./send.js"; diff --git a/src/channels/plugins/outbound/imessage.ts b/src/channels/plugins/outbound/imessage.ts index f088f88cf4e..b916c1e37df 100644 --- a/src/channels/plugins/outbound/imessage.ts +++ b/src/channels/plugins/outbound/imessage.ts @@ -1,5 +1,8 @@ import { sendMessageIMessage } from "../../../../extensions/imessage/src/send.js"; -import { resolveOutboundSendDep, type OutboundSendDeps } from "../../../infra/outbound/deliver.js"; +import { + resolveOutboundSendDep, + type OutboundSendDeps, +} from "../../../infra/outbound/send-deps.js"; import { createScopedChannelMediaMaxBytesResolver, createDirectTextMediaOutbound, diff --git a/src/channels/plugins/outbound/signal.ts b/src/channels/plugins/outbound/signal.ts index 16016de2fac..028192a3f54 100644 --- a/src/channels/plugins/outbound/signal.ts +++ b/src/channels/plugins/outbound/signal.ts @@ -1,5 +1,8 @@ import { sendMessageSignal } from "../../../../extensions/signal/src/send.js"; -import { resolveOutboundSendDep, type OutboundSendDeps } from "../../../infra/outbound/deliver.js"; +import { + resolveOutboundSendDep, + type OutboundSendDeps, +} from "../../../infra/outbound/send-deps.js"; import { createScopedChannelMediaMaxBytesResolver, createDirectTextMediaOutbound, diff --git a/src/channels/plugins/outbound/slack.ts b/src/channels/plugins/outbound/slack.ts index b73f33ff286..923317c7d58 100644 --- a/src/channels/plugins/outbound/slack.ts +++ b/src/channels/plugins/outbound/slack.ts @@ -1,7 +1,7 @@ import { parseSlackBlocksInput } from "../../../../extensions/slack/src/blocks-input.js"; import { sendMessageSlack, type SlackSendIdentity } from "../../../../extensions/slack/src/send.js"; -import { resolveOutboundSendDep } from "../../../infra/outbound/deliver.js"; import type { OutboundIdentity } from "../../../infra/outbound/identity.js"; +import { resolveOutboundSendDep } from "../../../infra/outbound/send-deps.js"; import { getGlobalHookRunner } from "../../../plugins/hook-runner-global.js"; import type { ChannelOutboundAdapter } from "../types.js"; import { sendTextMediaPayload } from "./direct-text-media.js"; diff --git a/src/channels/plugins/whatsapp-shared.ts b/src/channels/plugins/whatsapp-shared.ts index 99c94aead1d..3a51e2263bd 100644 --- a/src/channels/plugins/whatsapp-shared.ts +++ b/src/channels/plugins/whatsapp-shared.ts @@ -1,4 +1,4 @@ -import { resolveOutboundSendDep } from "../../infra/outbound/deliver.js"; +import { resolveOutboundSendDep } from "../../infra/outbound/send-deps.js"; import type { PluginRuntimeChannel } from "../../plugins/runtime/types-channel.js"; import { escapeRegExp } from "../../utils.js"; import { resolveWhatsAppOutboundTarget } from "../../whatsapp/resolve-outbound-target.js"; diff --git a/src/cli/deps.ts b/src/cli/deps.ts index 81126168e3f..c9ab341dd18 100644 --- a/src/cli/deps.ts +++ b/src/cli/deps.ts @@ -1,4 +1,4 @@ -import type { OutboundSendDeps } from "../infra/outbound/deliver.js"; +import type { OutboundSendDeps } from "../infra/outbound/send-deps.js"; import { createOutboundSendDepsFromCliSource } from "./outbound-send-mapping.js"; /** diff --git a/src/infra/outbound/deliver.ts b/src/infra/outbound/deliver.ts index b67f1b7d2a0..7932cae2968 100644 --- a/src/infra/outbound/deliver.ts +++ b/src/infra/outbound/deliver.ts @@ -40,57 +40,17 @@ import type { DeliveryMirror } from "./mirror.js"; import type { NormalizedOutboundPayload } from "./payloads.js"; import { normalizeReplyPayloadsForDelivery } from "./payloads.js"; import { isPlainTextSurface, sanitizeForPlainText } from "./sanitize-text.js"; +import { resolveOutboundSendDep, type OutboundSendDeps } from "./send-deps.js"; import type { OutboundSessionContext } from "./session-context.js"; import type { OutboundChannel } from "./targets.js"; export type { NormalizedOutboundPayload } from "./payloads.js"; export { normalizeOutboundPayloads } from "./payloads.js"; +export { resolveOutboundSendDep, type OutboundSendDeps } from "./send-deps.js"; const log = createSubsystemLogger("outbound/deliver"); const TELEGRAM_TEXT_LIMIT = 4096; -type LegacyOutboundSendDeps = { - sendWhatsApp?: unknown; - sendTelegram?: unknown; - sendDiscord?: unknown; - sendSlack?: unknown; - sendSignal?: unknown; - sendIMessage?: unknown; - sendMatrix?: unknown; - sendMSTeams?: unknown; -}; - -/** - * Dynamic bag of per-channel send functions, keyed by channel ID. - * Each outbound adapter resolves its own function from this record and - * falls back to a direct import when the key is absent. - */ -export type OutboundSendDeps = LegacyOutboundSendDeps & { [channelId: string]: unknown }; - -const LEGACY_SEND_DEP_KEYS = { - whatsapp: "sendWhatsApp", - telegram: "sendTelegram", - discord: "sendDiscord", - slack: "sendSlack", - signal: "sendSignal", - imessage: "sendIMessage", - matrix: "sendMatrix", - msteams: "sendMSTeams", -} as const satisfies Record; - -export function resolveOutboundSendDep( - deps: OutboundSendDeps | null | undefined, - channelId: keyof typeof LEGACY_SEND_DEP_KEYS, -): T | undefined { - const dynamic = deps?.[channelId]; - if (dynamic !== undefined) { - return dynamic as T; - } - const legacyKey = LEGACY_SEND_DEP_KEYS[channelId]; - const legacy = deps?.[legacyKey]; - return legacy as T | undefined; -} - export type OutboundDeliveryResult = { channel: Exclude; messageId: string; diff --git a/src/infra/outbound/send-deps.ts b/src/infra/outbound/send-deps.ts new file mode 100644 index 00000000000..be2a5d43cb2 --- /dev/null +++ b/src/infra/outbound/send-deps.ts @@ -0,0 +1,41 @@ +type LegacyOutboundSendDeps = { + sendWhatsApp?: unknown; + sendTelegram?: unknown; + sendDiscord?: unknown; + sendSlack?: unknown; + sendSignal?: unknown; + sendIMessage?: unknown; + sendMatrix?: unknown; + sendMSTeams?: unknown; +}; + +/** + * Dynamic bag of per-channel send functions, keyed by channel ID. + * Each outbound adapter resolves its own function from this record and + * falls back to a direct import when the key is absent. + */ +export type OutboundSendDeps = LegacyOutboundSendDeps & { [channelId: string]: unknown }; + +const LEGACY_SEND_DEP_KEYS = { + whatsapp: "sendWhatsApp", + telegram: "sendTelegram", + discord: "sendDiscord", + slack: "sendSlack", + signal: "sendSignal", + imessage: "sendIMessage", + matrix: "sendMatrix", + msteams: "sendMSTeams", +} as const satisfies Record; + +export function resolveOutboundSendDep( + deps: OutboundSendDeps | null | undefined, + channelId: keyof typeof LEGACY_SEND_DEP_KEYS, +): T | undefined { + const dynamic = deps?.[channelId]; + if (dynamic !== undefined) { + return dynamic as T; + } + const legacyKey = LEGACY_SEND_DEP_KEYS[channelId]; + const legacy = deps?.[legacyKey]; + return legacy as T | undefined; +}