Fix configure startup stalls from outbound send-deps imports (#46301)

* fix: avoid configure startup plugin stalls

* fix: credit configure startup changelog entry
This commit is contained in:
scoootscooob
2026-03-14 09:58:03 -07:00
committed by GitHub
parent 62afc4b514
commit d9c285e930
18 changed files with 65 additions and 57 deletions

View File

@@ -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

View File

@@ -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<

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";

View File

@@ -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 = {

View File

@@ -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";

View File

@@ -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<

View File

@@ -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";

View File

@@ -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";

View File

@@ -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,

View File

@@ -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,

View File

@@ -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";

View File

@@ -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";

View File

@@ -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";
/**

View File

@@ -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<string, keyof LegacyOutboundSendDeps>;
export function resolveOutboundSendDep<T>(
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<OutboundChannel, "none">;
messageId: string;

View File

@@ -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<string, keyof LegacyOutboundSendDeps>;
export function resolveOutboundSendDep<T>(
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;
}