From 59b08b46930b5ef70dc704f20bc2a9fbe5d9fd59 Mon Sep 17 00:00:00 2001 From: Dallin Romney Date: Thu, 2 Jul 2026 19:08:11 -0700 Subject: [PATCH] refactor(shared): consolidate remaining channel lazy loaders (#99302) --- extensions/device-pair/index.ts | 32 +++------- extensions/feishu/src/monitor.ts | 8 +-- extensions/feishu/src/setup-surface.ts | 12 +--- extensions/feishu/subagent-hooks-api.ts | 12 ++-- extensions/google-meet/index.ts | 14 +---- extensions/imessage/src/approval-reactions.ts | 10 ++- extensions/irc/src/channel.ts | 10 +-- extensions/irc/src/gateway.ts | 10 +-- extensions/line/index.ts | 13 ++-- extensions/matrix/index.ts | 12 ++-- extensions/matrix/src/channel.ts | 11 ++-- extensions/matrix/src/cli.ts | 20 +++--- .../matrix/src/matrix/client-bootstrap.ts | 29 +++------ extensions/matrix/src/matrix/client/config.ts | 61 +++++++------------ .../matrix/src/matrix/client/create-client.ts | 20 ++---- extensions/matrix/src/matrix/client/shared.ts | 16 ++--- .../src/matrix/credentials-write.runtime.ts | 10 +-- .../matrix/src/matrix/monitor/handler.ts | 43 +++---------- .../src/matrix/monitor/preflight-audio.ts | 12 ++-- .../src/matrix/monitor/reaction-events.ts | 22 +++---- .../matrix/src/matrix/monitor/startup.ts | 12 ++-- .../src/matrix/monitor/verification-events.ts | 26 +++----- extensions/matrix/src/matrix/probe.ts | 14 ++--- extensions/matrix/src/matrix/sdk.ts | 11 ++-- .../matrix/src/matrix/sdk/crypto-facade.ts | 14 +++-- extensions/matrix/src/matrix/send/client.ts | 13 +--- extensions/matrix/src/plugin-entry.runtime.ts | 12 ++-- extensions/matrix/subagent-hooks-api.ts | 12 ++-- extensions/msteams/src/sdk-proactive.ts | 10 ++- extensions/msteams/src/sdk.ts | 42 ++++++------- extensions/qqbot/src/bridge/bootstrap.ts | 11 ++-- extensions/qqbot/src/channel.ts | 22 ++----- extensions/signal/src/approval-reactions.ts | 10 ++- extensions/signal/src/channel.ts | 30 +++------ extensions/voice-call/src/runtime.ts | 47 +++----------- extensions/voice-call/src/webhook.ts | 21 +++---- extensions/whatsapp/login-qr-runtime.ts | 8 +-- extensions/whatsapp/src/approval-reactions.ts | 10 ++- extensions/whatsapp/src/auto-reply/monitor.ts | 11 ++-- extensions/whatsapp/src/outbound-adapter.ts | 10 +-- extensions/whatsapp/src/runtime-api.ts | 8 +-- extensions/zalo/src/monitor.ts | 10 +-- .../monitor-mocks-test-support.ts | 9 ++- extensions/zalouser/src/accounts.ts | 8 +-- extensions/zalouser/src/zca-client.ts | 13 ++-- 45 files changed, 244 insertions(+), 527 deletions(-) diff --git a/extensions/device-pair/index.ts b/extensions/device-pair/index.ts index 34c8870493f6..1a634f335eb7 100644 --- a/extensions/device-pair/index.ts +++ b/extensions/device-pair/index.ts @@ -2,42 +2,24 @@ import { rm } from "node:fs/promises"; import os from "node:os"; import path from "node:path"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; import { normalizeLowercaseStringOrEmpty, normalizeOptionalString, } from "openclaw/plugin-sdk/string-coerce-runtime"; import { buildDevicePairPairingQrChannelData } from "./pairing-qr-channel-data.js"; - -type DevicePairApiModule = typeof import("./api.js"); type NotifyModule = typeof import("./notify.js"); -type PairCommandApproveModule = typeof import("./pair-command-approve.js"); -type PairCommandAuthModule = typeof import("./pair-command-auth.js"); -let devicePairApiModulePromise: Promise | undefined; -let notifyModulePromise: Promise | undefined; -let pairCommandApproveModulePromise: Promise | undefined; -let pairCommandAuthModulePromise: Promise | undefined; +const loadDevicePairApiModule = createLazyRuntimeModule(() => import("./api.js")); -function loadDevicePairApiModule(): Promise { - devicePairApiModulePromise ??= import("./api.js"); - return devicePairApiModulePromise; -} +const loadNotifyModule = createLazyRuntimeModule(() => import("./notify.js")); -function loadNotifyModule(): Promise { - notifyModulePromise ??= import("./notify.js"); - return notifyModulePromise; -} +const loadPairCommandApproveModule = createLazyRuntimeModule( + () => import("./pair-command-approve.js"), +); -function loadPairCommandApproveModule(): Promise { - pairCommandApproveModulePromise ??= import("./pair-command-approve.js"); - return pairCommandApproveModulePromise; -} - -function loadPairCommandAuthModule(): Promise { - pairCommandAuthModulePromise ??= import("./pair-command-auth.js"); - return pairCommandAuthModulePromise; -} +const loadPairCommandAuthModule = createLazyRuntimeModule(() => import("./pair-command-auth.js")); function formatDurationMinutes(expiresAtMs: number): string { const msRemaining = Math.max(0, expiresAtMs - Date.now()); diff --git a/extensions/feishu/src/monitor.ts b/extensions/feishu/src/monitor.ts index 27b654d510d1..923eb172a1e3 100644 --- a/extensions/feishu/src/monitor.ts +++ b/extensions/feishu/src/monitor.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Feishu plugin module implements monitor behavior. import type { ClawdbotConfig, PluginRuntime, RuntimeEnv } from "../runtime-api.js"; import { listEnabledFeishuAccounts, resolveFeishuRuntimeAccount } from "./accounts.js"; @@ -40,12 +41,7 @@ export type FeishuStatusSink = (patch: { lastError?: string | null; }) => void; -let monitorAccountRuntimePromise: Promise | undefined; - -async function loadMonitorAccountRuntime() { - monitorAccountRuntimePromise ??= import("./monitor.account.js"); - return await monitorAccountRuntimePromise; -} +const loadMonitorAccountRuntime = createLazyRuntimeModule(() => import("./monitor.account.js")); export { clearFeishuWebhookRateLimitStateForTest, diff --git a/extensions/feishu/src/setup-surface.ts b/extensions/feishu/src/setup-surface.ts index 5a69d1f4cc6d..d8d80312ade0 100644 --- a/extensions/feishu/src/setup-surface.ts +++ b/extensions/feishu/src/setup-surface.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Feishu plugin module implements setup surface behavior. import { DEFAULT_ACCOUNT_ID, @@ -248,16 +249,7 @@ function applyNewAppSecurityPolicy( return next; } -// --------------------------------------------------------------------------- -// Scan-to-create flow -// --------------------------------------------------------------------------- - -let appRegistrationModulePromise: Promise | null = null; - -const loadAppRegistrationModule = async () => { - appRegistrationModulePromise ??= import("./app-registration.js"); - return await appRegistrationModulePromise; -}; +const loadAppRegistrationModule = createLazyRuntimeModule(() => import("./app-registration.js")); async function promptFeishuDomain(params: { prompter: WizardPrompter; diff --git a/extensions/feishu/subagent-hooks-api.ts b/extensions/feishu/subagent-hooks-api.ts index 9d8737050f20..7a647ecbd42d 100644 --- a/extensions/feishu/subagent-hooks-api.ts +++ b/extensions/feishu/subagent-hooks-api.ts @@ -1,14 +1,10 @@ // Feishu API module exposes the plugin public contract. import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; -type FeishuSubagentHooksModule = typeof import("./src/subagent-hooks.js"); - -let feishuSubagentHooksPromise: Promise | null = null; - -function loadFeishuSubagentHooksModule() { - feishuSubagentHooksPromise ??= import("./src/subagent-hooks.js"); - return feishuSubagentHooksPromise; -} +const loadFeishuSubagentHooksModule = createLazyRuntimeModule( + () => import("./src/subagent-hooks.js"), +); export function registerFeishuSubagentHooks(api: OpenClawPluginApi): void { api.on("subagent_delivery_target", async (event) => { diff --git a/extensions/google-meet/index.ts b/extensions/google-meet/index.ts index cef79e5608f2..3d32b5a3919c 100644 --- a/extensions/google-meet/index.ts +++ b/extensions/google-meet/index.ts @@ -10,6 +10,7 @@ import { errorShape, type GatewayRequestHandlerOptions, } from "openclaw/plugin-sdk/gateway-runtime"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { definePluginEntry, type OpenClawPluginApi } from "openclaw/plugin-sdk/plugin-entry"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; import { Type } from "typebox"; @@ -42,18 +43,9 @@ import { import { GoogleMeetRuntime } from "./src/runtime.js"; import { isGoogleMeetBrowserManualActionError } from "./src/transports/chrome-create.js"; -let googleMeetCreateModulePromise: Promise | null = null; -let googleMeetCliModulePromise: Promise | null = null; +const loadGoogleMeetCreateModule = createLazyRuntimeModule(() => import("./src/create.js")); -const loadGoogleMeetCreateModule = async () => { - googleMeetCreateModulePromise ??= import("./src/create.js"); - return await googleMeetCreateModulePromise; -}; - -const loadGoogleMeetCliModule = async () => { - googleMeetCliModulePromise ??= import("./src/cli.js"); - return await googleMeetCliModulePromise; -}; +const loadGoogleMeetCliModule = createLazyRuntimeModule(() => import("./src/cli.js")); const googleMeetConfigSchema = { parse(value: unknown) { diff --git a/extensions/imessage/src/approval-reactions.ts b/extensions/imessage/src/approval-reactions.ts index ad628b4d1aa7..53d1a5e6db3d 100644 --- a/extensions/imessage/src/approval-reactions.ts +++ b/extensions/imessage/src/approval-reactions.ts @@ -9,6 +9,7 @@ import { } from "openclaw/plugin-sdk/approval-reaction-runtime"; import type { ExecApprovalReplyDecision } from "openclaw/plugin-sdk/approval-reply-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { asDateTimestampMs, isFutureDateTimestampMs, @@ -58,13 +59,10 @@ export type PendingIMessageApprovalReactionPollTarget = { expiresAtMs: number; }; -let resolverRuntimePromise: Promise | undefined; +const resolverRuntimeLoader = createLazyRuntimeModule(() => import("./approval-resolver.js")); const pendingReactionPollTargets = new Map(); -function loadApprovalResolver(): Promise { - resolverRuntimePromise ??= import("./approval-resolver.js"); - return resolverRuntimePromise; -} +const loadApprovalResolver = resolverRuntimeLoader; function chatIdToKeyValue(chatId: number | string | undefined): string | null { if (chatId == null || chatId === "") { @@ -639,5 +637,5 @@ export async function maybeResolveIMessageApprovalReaction(params: { export function clearIMessageApprovalReactionTargetsForTest(): void { imessageApprovalReactionTargets.clearForTest(); pendingReactionPollTargets.clear(); - resolverRuntimePromise = undefined; + resolverRuntimeLoader.clear(); } diff --git a/extensions/irc/src/channel.ts b/extensions/irc/src/channel.ts index 8cc74cc82a5c..dc1dae67460b 100644 --- a/extensions/irc/src/channel.ts +++ b/extensions/irc/src/channel.ts @@ -15,6 +15,7 @@ import { createChannelDirectoryAdapter, createResolvedDirectoryEntriesLister, } from "openclaw/plugin-sdk/directory-runtime"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { createComputedAccountStatusAdapter, createDefaultChannelRuntimeState, @@ -62,14 +63,7 @@ const meta = { markdownCapable: true, }; -type IrcChannelRuntimeModule = typeof import("./channel-runtime.js"); - -let ircChannelRuntimePromise: Promise | undefined; - -async function loadIrcChannelRuntime(): Promise { - ircChannelRuntimePromise ??= import("./channel-runtime.js"); - return await ircChannelRuntimePromise; -} +const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime.js")); function normalizePairingTarget(raw: string): string { const normalized = normalizeIrcAllowEntry(raw); diff --git a/extensions/irc/src/gateway.ts b/extensions/irc/src/gateway.ts index 6004c3bd30f9..61ad5abae08e 100644 --- a/extensions/irc/src/gateway.ts +++ b/extensions/irc/src/gateway.ts @@ -1,19 +1,13 @@ // Irc plugin module implements gateway behavior. import { runStoppablePassiveMonitor } from "openclaw/plugin-sdk/extension-shared"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import type { ChannelAccountSnapshot } from "openclaw/plugin-sdk/status-helpers"; import type { ResolvedIrcAccount } from "./accounts.js"; import { createAccountStatusSink } from "./channel-api.js"; import type { RuntimeEnv } from "./runtime-api.js"; import type { CoreConfig } from "./types.js"; -type IrcChannelRuntimeModule = typeof import("./channel-runtime.js"); - -let ircChannelRuntimePromise: Promise | undefined; - -async function loadIrcChannelRuntime(): Promise { - ircChannelRuntimePromise ??= import("./channel-runtime.js"); - return await ircChannelRuntimePromise; -} +const loadIrcChannelRuntime = createLazyRuntimeModule(() => import("./channel-runtime.js")); export async function startIrcGatewayAccount(ctx: { cfg: CoreConfig; diff --git a/extensions/line/index.ts b/extensions/line/index.ts index 2ff34d938e7b..52df9f31a537 100644 --- a/extensions/line/index.ts +++ b/extensions/line/index.ts @@ -4,13 +4,12 @@ import { type OpenClawPluginCommandDefinition, type OpenClawPluginApi, } from "openclaw/plugin-sdk/channel-entry-contract"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; type RegisteredLineCardCommand = OpenClawPluginCommandDefinition; -let lineCardCommandPromise: Promise | null = null; - -async function loadLineCardCommand(api: OpenClawPluginApi): Promise { - lineCardCommandPromise ??= (async () => { +function createLineCardCommandLoader(api: OpenClawPluginApi) { + return createLazyRuntimeModule(async () => { let registered: RegisteredLineCardCommand | null = null; const { registerLineCardCommand } = await import("./src/card-command.js"); registerLineCardCommand({ @@ -23,8 +22,7 @@ async function loadLineCardCommand(api: OpenClawPluginApi): Promise | null = null; - -function loadMatrixHandlersRuntimeModule() { - matrixHandlersRuntimePromise ??= import("./plugin-entry.handlers.runtime.js"); - return matrixHandlersRuntimePromise; -} +const loadMatrixHandlersRuntimeModule = createLazyRuntimeModule( + () => import("./plugin-entry.handlers.runtime.js"), +); export function registerMatrixFullRuntime(api: OpenClawPluginApi): void { api.registerGatewayMethod("matrix.verify.recoveryKey", async (ctx) => { diff --git a/extensions/matrix/src/channel.ts b/extensions/matrix/src/channel.ts index b2e765b7a7b5..ae4b67021392 100644 --- a/extensions/matrix/src/channel.ts +++ b/extensions/matrix/src/channel.ts @@ -21,7 +21,10 @@ import { createResolvedDirectoryEntriesLister, createRuntimeDirectoryLiveAdapter, } from "openclaw/plugin-sdk/directory-runtime"; -import { createLazyRuntimeNamedExport } from "openclaw/plugin-sdk/lazy-runtime"; +import { + createLazyRuntimeNamedExport, + createLazyRuntimeModule, +} from "openclaw/plugin-sdk/lazy-runtime"; import { buildProbeChannelStatusSummary, collectStatusIssuesFromLastError, @@ -89,12 +92,8 @@ const loadMatrixChannelRuntime = createLazyRuntimeNamedExport( () => import("./channel.runtime.js"), "matrixChannelRuntime", ); -let matrixDoctorModulePromise: Promise | null = null; -const loadMatrixDoctorModule = async () => { - matrixDoctorModulePromise ??= import("./doctor.js"); - return await matrixDoctorModulePromise; -}; +const loadMatrixDoctorModule = createLazyRuntimeModule(() => import("./doctor.js")); const meta = { id: "matrix", diff --git a/extensions/matrix/src/cli.ts b/extensions/matrix/src/cli.ts index 45cd3c8a13dc..65e240dd89cd 100644 --- a/extensions/matrix/src/cli.ts +++ b/extensions/matrix/src/cli.ts @@ -1,6 +1,7 @@ // Matrix plugin module implements cli behavior. import type { Command } from "commander"; import { normalizeAccountId } from "openclaw/plugin-sdk/account-id"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { parseStrictInteger, timestampMsToIsoString } from "openclaw/plugin-sdk/number-runtime"; import type { ChannelSetupInput } from "openclaw/plugin-sdk/setup"; import { resolveMatrixAccount, resolveMatrixAccountConfig } from "./matrix/accounts.js"; @@ -37,21 +38,14 @@ import { matrixSetupAdapter } from "./setup-core.js"; import type { CoreConfig } from "./types.js"; let matrixCliExitScheduled = false; -type MatrixActionClientModule = typeof import("./matrix/actions/client.js"); -type MatrixDirectManagementModule = typeof import("./matrix/direct-management.js"); -let matrixActionClientModulePromise: Promise | undefined; -let matrixDirectManagementModulePromise: Promise | undefined; +const loadMatrixActionClientModule = createLazyRuntimeModule( + () => import("./matrix/actions/client.js"), +); -function loadMatrixActionClientModule(): Promise { - matrixActionClientModulePromise ??= import("./matrix/actions/client.js"); - return matrixActionClientModulePromise; -} - -function loadMatrixDirectManagementModule(): Promise { - matrixDirectManagementModulePromise ??= import("./matrix/direct-management.js"); - return matrixDirectManagementModulePromise; -} +const loadMatrixDirectManagementModule = createLazyRuntimeModule( + () => import("./matrix/direct-management.js"), +); export function resetMatrixCliStateForTests(): void { matrixCliExitScheduled = false; diff --git a/extensions/matrix/src/matrix/client-bootstrap.ts b/extensions/matrix/src/matrix/client-bootstrap.ts index 736d5caaac26..040ef47e95b4 100644 --- a/extensions/matrix/src/matrix/client-bootstrap.ts +++ b/extensions/matrix/src/matrix/client-bootstrap.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Matrix plugin module implements client bootstrap behavior. import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime"; import type { CoreConfig } from "../types.js"; @@ -19,25 +20,15 @@ type MatrixResolvedClientHook = ( context: { preparedByDefault: boolean }, ) => Promise | void; -type MatrixSharedClientRuntimeDeps = Pick< - typeof import("./client.js"), - "acquireSharedMatrixClient" | "resolveMatrixAuthContext" -> & - Pick; - -let matrixSharedClientRuntimeDepsPromise: Promise | undefined; - -async function loadMatrixSharedClientRuntimeDeps(): Promise { - matrixSharedClientRuntimeDepsPromise ??= Promise.all([ - import("./client.js"), - import("./client/shared.js"), - ]).then(([clientModule, sharedModule]) => ({ - acquireSharedMatrixClient: clientModule.acquireSharedMatrixClient, - resolveMatrixAuthContext: clientModule.resolveMatrixAuthContext, - releaseSharedClientInstance: sharedModule.releaseSharedClientInstance, - })); - return await matrixSharedClientRuntimeDepsPromise; -} +const loadMatrixSharedClientRuntimeDeps = createLazyRuntimeModule(() => + Promise.all([import("./client.js"), import("./client/shared.js")]).then( + ([clientModule, sharedModule]) => ({ + acquireSharedMatrixClient: clientModule.acquireSharedMatrixClient, + resolveMatrixAuthContext: clientModule.resolveMatrixAuthContext, + releaseSharedClientInstance: sharedModule.releaseSharedClientInstance, + }), + ), +); async function ensureResolvedClientReadiness(params: { client: MatrixClient; diff --git a/extensions/matrix/src/matrix/client/config.ts b/extensions/matrix/src/matrix/client/config.ts index 3a6a59c261b3..06886e230ef2 100644 --- a/extensions/matrix/src/matrix/client/config.ts +++ b/extensions/matrix/src/matrix/client/config.ts @@ -1,5 +1,6 @@ // Matrix helper module supports config behavior. import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { resolveOptionalIntegerOption } from "openclaw/plugin-sdk/number-runtime"; import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime"; import { retryAsync } from "openclaw/plugin-sdk/retry-runtime"; @@ -40,21 +41,12 @@ type MatrixAuthClientDeps = { retryMinDelayMs?: number; }; -type MatrixCredentialsReadDeps = { - loadMatrixCredentials: typeof import("../credentials-read.js").loadMatrixCredentials; - credentialsMatchConfig: typeof import("../credentials-read.js").credentialsMatchConfig; -}; - -type MatrixCredentialsWriteRuntime = typeof import("../credentials-write.runtime.js"); - -type MatrixSecretInputDeps = { - resolveConfiguredSecretInputString: typeof import("./config-secret-input.runtime.js").resolveConfiguredSecretInputString; -}; - -let matrixAuthClientDepsPromise: Promise | undefined; -let matrixCredentialsReadDepsPromise: Promise | undefined; -let matrixCredentialsWriteRuntimePromise: Promise | undefined; -let matrixSecretInputDepsPromise: Promise | undefined; +const loadDefaultMatrixAuthClientDeps = createLazyRuntimeModule(() => + Promise.all([import("../sdk.js"), import("./logging.js")]).then(([sdkModule, loggingModule]) => ({ + MatrixClient: sdkModule.MatrixClient, + ensureMatrixSdkLoggingConfigured: loggingModule.ensureMatrixSdkLoggingConfigured, + })), +); let matrixAuthClientDepsForTest: MatrixAuthClientDeps | undefined; const MATRIX_AUTH_REQUEST_RETRY_RE = @@ -72,36 +64,25 @@ async function loadMatrixAuthClientDeps(): Promise { if (matrixAuthClientDepsForTest) { return matrixAuthClientDepsForTest; } - matrixAuthClientDepsPromise ??= Promise.all([import("../sdk.js"), import("./logging.js")]).then( - ([sdkModule, loggingModule]) => ({ - MatrixClient: sdkModule.MatrixClient, - ensureMatrixSdkLoggingConfigured: loggingModule.ensureMatrixSdkLoggingConfigured, - }), - ); - return await matrixAuthClientDepsPromise; + return await loadDefaultMatrixAuthClientDeps(); } -async function loadMatrixCredentialsReadDeps(): Promise { - matrixCredentialsReadDepsPromise ??= import("../credentials-read.js").then( - (credentialsReadModule) => ({ - loadMatrixCredentials: credentialsReadModule.loadMatrixCredentials, - credentialsMatchConfig: credentialsReadModule.credentialsMatchConfig, - }), - ); - return await matrixCredentialsReadDepsPromise; -} +const loadMatrixCredentialsReadDeps = createLazyRuntimeModule(() => + import("../credentials-read.js").then((credentialsReadModule) => ({ + loadMatrixCredentials: credentialsReadModule.loadMatrixCredentials, + credentialsMatchConfig: credentialsReadModule.credentialsMatchConfig, + })), +); -async function loadMatrixCredentialsWriteRuntime(): Promise { - matrixCredentialsWriteRuntimePromise ??= import("../credentials-write.runtime.js"); - return await matrixCredentialsWriteRuntimePromise; -} +const loadMatrixCredentialsWriteRuntime = createLazyRuntimeModule( + () => import("../credentials-write.runtime.js"), +); -async function loadMatrixSecretInputDeps(): Promise { - matrixSecretInputDepsPromise ??= import("./config-secret-input.runtime.js").then((runtime) => ({ +const loadMatrixSecretInputDeps = createLazyRuntimeModule(() => + import("./config-secret-input.runtime.js").then((runtime) => ({ resolveConfiguredSecretInputString: runtime.resolveConfiguredSecretInputString, - })); - return await matrixSecretInputDepsPromise; -} + })), +); function shouldRetryMatrixAuthRequest(err: unknown): boolean { return MATRIX_AUTH_REQUEST_RETRY_RE.test(formatErrorMessage(err)); diff --git a/extensions/matrix/src/matrix/client/create-client.ts b/extensions/matrix/src/matrix/client/create-client.ts index 3f3d1bc85c2b..7d2b298a58ef 100644 --- a/extensions/matrix/src/matrix/client/create-client.ts +++ b/extensions/matrix/src/matrix/client/create-client.ts @@ -1,5 +1,6 @@ // Matrix plugin module implements create client behavior. import fs from "node:fs"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import type { PinnedDispatcherPolicy } from "openclaw/plugin-sdk/ssrf-dispatcher"; import { ssrfPolicyFromDangerouslyAllowPrivateNetwork, @@ -14,23 +15,12 @@ import { writeStorageMeta, } from "./storage.js"; -type MatrixCreateClientRuntimeDeps = { - MatrixClient: typeof import("../sdk.js").MatrixClient; - ensureMatrixSdkLoggingConfigured: typeof import("./logging.js").ensureMatrixSdkLoggingConfigured; -}; - -let matrixCreateClientRuntimeDepsPromise: Promise | undefined; - -async function loadMatrixCreateClientRuntimeDeps(): Promise { - matrixCreateClientRuntimeDepsPromise ??= Promise.all([ - import("../sdk.js"), - import("./logging.js"), - ]).then(([sdkModule, loggingModule]) => ({ +const loadMatrixCreateClientRuntimeDeps = createLazyRuntimeModule(() => + Promise.all([import("../sdk.js"), import("./logging.js")]).then(([sdkModule, loggingModule]) => ({ MatrixClient: sdkModule.MatrixClient, ensureMatrixSdkLoggingConfigured: loggingModule.ensureMatrixSdkLoggingConfigured, - })); - return await matrixCreateClientRuntimeDepsPromise; -} + })), +); export async function createMatrixClient(params: { homeserver: string; diff --git a/extensions/matrix/src/matrix/client/shared.ts b/extensions/matrix/src/matrix/client/shared.ts index 013164de2908..802e9f90f648 100644 --- a/extensions/matrix/src/matrix/client/shared.ts +++ b/extensions/matrix/src/matrix/client/shared.ts @@ -1,5 +1,6 @@ // Matrix plugin module implements shared behavior. import { normalizeOptionalAccountId } from "openclaw/plugin-sdk/account-id"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import type { CoreConfig } from "../../types.js"; import type { MatrixClient } from "../sdk.js"; import { LogService } from "../sdk/logger.js"; @@ -7,18 +8,11 @@ import { awaitMatrixStartupWithAbort } from "../startup-abort.js"; import { resolveMatrixAuth, resolveMatrixAuthContext } from "./config.js"; import type { MatrixAuth } from "./types.js"; -type MatrixCreateClientDeps = { - createMatrixClient: typeof import("./create-client.js").createMatrixClient; -}; - -let matrixCreateClientDepsPromise: Promise | undefined; - -async function loadMatrixCreateClientDeps(): Promise { - matrixCreateClientDepsPromise ??= import("./create-client.js").then((runtime) => ({ +const loadMatrixCreateClientDeps = createLazyRuntimeModule(() => + import("./create-client.js").then((runtime) => ({ createMatrixClient: runtime.createMatrixClient, - })); - return await matrixCreateClientDepsPromise; -} + })), +); type SharedMatrixClientState = { client: MatrixClient; diff --git a/extensions/matrix/src/matrix/credentials-write.runtime.ts b/extensions/matrix/src/matrix/credentials-write.runtime.ts index 4ed91dc8f694..02252346c4b9 100644 --- a/extensions/matrix/src/matrix/credentials-write.runtime.ts +++ b/extensions/matrix/src/matrix/credentials-write.runtime.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Matrix plugin module implements credentials write behavior. import type { saveBackfilledMatrixDeviceId as saveBackfilledMatrixDeviceIdType, @@ -5,14 +6,7 @@ import type { touchMatrixCredentials as touchMatrixCredentialsType, } from "./credentials.js"; -type MatrixCredentialsRuntime = typeof import("./credentials.js"); - -let matrixCredentialsRuntimePromise: Promise | undefined; - -function loadMatrixCredentialsRuntime(): Promise { - matrixCredentialsRuntimePromise ??= import("./credentials.js"); - return matrixCredentialsRuntimePromise; -} +const loadMatrixCredentialsRuntime = createLazyRuntimeModule(() => import("./credentials.js")); export async function saveMatrixCredentials( ...args: Parameters diff --git a/extensions/matrix/src/matrix/monitor/handler.ts b/extensions/matrix/src/matrix/monitor/handler.ts index bec17ec64663..ae7a8a454717 100644 --- a/extensions/matrix/src/matrix/monitor/handler.ts +++ b/extensions/matrix/src/matrix/monitor/handler.ts @@ -27,6 +27,7 @@ import { resolveChannelContextVisibilityMode, } from "openclaw/plugin-sdk/context-visibility-runtime"; import { isDangerousNameMatchingEnabled } from "openclaw/plugin-sdk/dangerous-name-runtime"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { isFutureDateTimestampMs, resolveExpiresAtMsFromDurationMs, @@ -124,44 +125,20 @@ import { isMatrixVerificationRoomMessage } from "./verification-utils.js"; const ALLOW_FROM_STORE_CACHE_TTL_MS = 30_000; const PAIRING_REPLY_COOLDOWN_MS = 5 * 60_000; const MATRIX_TOOL_PROGRESS_MAX_CHARS = 300; -let matrixSendModulePromise: Promise | undefined; -let acpBindingRuntimePromise: - | Promise - | undefined; -let sessionBindingRuntimePromise: - | Promise - | undefined; -let matrixReactionEventsPromise: Promise | undefined; -let matrixDraftStreamPromise: Promise | undefined; -function loadMatrixSendModule(): Promise { - matrixSendModulePromise ??= import("../send.js"); - return matrixSendModulePromise; -} +const loadMatrixSendModule = createLazyRuntimeModule(() => import("../send.js")); -function loadAcpBindingRuntime(): Promise< - typeof import("openclaw/plugin-sdk/acp-binding-runtime") -> { - acpBindingRuntimePromise ??= import("openclaw/plugin-sdk/acp-binding-runtime"); - return acpBindingRuntimePromise; -} +const loadAcpBindingRuntime = createLazyRuntimeModule( + () => import("openclaw/plugin-sdk/acp-binding-runtime"), +); -function loadSessionBindingRuntime(): Promise< - typeof import("openclaw/plugin-sdk/session-binding-runtime") -> { - sessionBindingRuntimePromise ??= import("openclaw/plugin-sdk/session-binding-runtime"); - return sessionBindingRuntimePromise; -} +const loadSessionBindingRuntime = createLazyRuntimeModule( + () => import("openclaw/plugin-sdk/session-binding-runtime"), +); -function loadMatrixReactionEvents(): Promise { - matrixReactionEventsPromise ??= import("./reaction-events.js"); - return matrixReactionEventsPromise; -} +const loadMatrixReactionEvents = createLazyRuntimeModule(() => import("./reaction-events.js")); -function loadMatrixDraftStream(): Promise { - matrixDraftStreamPromise ??= import("../draft-stream.js"); - return matrixDraftStreamPromise; -} +const loadMatrixDraftStream = createLazyRuntimeModule(() => import("../draft-stream.js")); async function matrixTextWouldActivateMentions( client: MatrixClient, diff --git a/extensions/matrix/src/matrix/monitor/preflight-audio.ts b/extensions/matrix/src/matrix/monitor/preflight-audio.ts index af077952dfd0..b93a1f6d75b5 100644 --- a/extensions/matrix/src/matrix/monitor/preflight-audio.ts +++ b/extensions/matrix/src/matrix/monitor/preflight-audio.ts @@ -1,15 +1,11 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { logVerbose } from "openclaw/plugin-sdk/runtime-env"; - -type MatrixPreflightAudioRuntime = typeof import("./preflight-audio.runtime.js"); const MATRIX_DEFAULT_ECHO_TRANSCRIPT_FORMAT = '📝 "{transcript}"'; -let matrixPreflightAudioRuntimePromise: Promise | undefined; - -function loadMatrixPreflightAudioRuntime(): Promise { - matrixPreflightAudioRuntimePromise ??= import("./preflight-audio.runtime.js"); - return matrixPreflightAudioRuntimePromise; -} +const loadMatrixPreflightAudioRuntime = createLazyRuntimeModule( + () => import("./preflight-audio.runtime.js"), +); export function formatMatrixAudioTranscript(transcript: string): string { return `[Audio transcript (machine-generated, untrusted)]: ${JSON.stringify(transcript)}`; diff --git a/extensions/matrix/src/matrix/monitor/reaction-events.ts b/extensions/matrix/src/matrix/monitor/reaction-events.ts index b5c23536e7e8..ef90ef03c39c 100644 --- a/extensions/matrix/src/matrix/monitor/reaction-events.ts +++ b/extensions/matrix/src/matrix/monitor/reaction-events.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Matrix plugin module implements reaction events behavior. import { getSessionBindingService } from "openclaw/plugin-sdk/session-binding-runtime"; import { @@ -13,22 +14,13 @@ import type { PluginRuntime } from "./runtime-api.js"; import { resolveMatrixThreadRootId, resolveMatrixThreadRouting } from "./threads.js"; import type { MatrixRawEvent, RoomMessageEventContent } from "./types.js"; -let approvalReactionAuthPromise: - | Promise - | undefined; -let execApprovalResolverPromise: - | Promise - | undefined; +const loadApprovalReactionAuth = createLazyRuntimeModule( + () => import("../../approval-reaction-auth.js"), +); -function loadApprovalReactionAuth(): Promise { - approvalReactionAuthPromise ??= import("../../approval-reaction-auth.js"); - return approvalReactionAuthPromise; -} - -function loadExecApprovalResolver(): Promise { - execApprovalResolverPromise ??= import("../../exec-approval-resolver.js"); - return execApprovalResolverPromise; -} +const loadExecApprovalResolver = createLazyRuntimeModule( + () => import("../../exec-approval-resolver.js"), +); export type MatrixReactionNotificationMode = "off" | "own"; diff --git a/extensions/matrix/src/matrix/monitor/startup.ts b/extensions/matrix/src/matrix/monitor/startup.ts index d61f438babae..f4e57f75657c 100644 --- a/extensions/matrix/src/matrix/monitor/startup.ts +++ b/extensions/matrix/src/matrix/monitor/startup.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Matrix plugin module implements startup behavior. import type { RuntimeLogger } from "../../runtime-api.js"; import type { CoreConfig, MatrixConfig } from "../../types.js"; @@ -25,10 +26,8 @@ export type MatrixStartupMaintenanceDeps = { ensureMatrixStartupVerification: typeof import("./startup-verification.js").ensureMatrixStartupVerification; }; -let matrixStartupMaintenanceDepsPromise: Promise | undefined; - -async function loadMatrixStartupMaintenanceDeps(): Promise { - matrixStartupMaintenanceDepsPromise ??= Promise.all([ +const loadMatrixStartupMaintenanceDeps = createLazyRuntimeModule(() => + Promise.all([ import("../config-update.js"), import("../device-health.js"), import("../profile.js"), @@ -48,9 +47,8 @@ async function loadMatrixStartupMaintenanceDeps(): Promise | undefined; - -async function loadMatrixDirectRoomDeps(): Promise { - matrixDirectRoomDepsPromise ??= Promise.all([ - import("../direct-management.js"), - import("../direct-room.js"), - ]).then(([directManagementModule, directRoomModule]) => ({ - inspectMatrixDirectRooms: directManagementModule.inspectMatrixDirectRooms, - isStrictDirectRoom: directRoomModule.isStrictDirectRoom, - })); - return await matrixDirectRoomDepsPromise; -} +const loadMatrixDirectRoomDeps = createLazyRuntimeModule(() => + Promise.all([import("../direct-management.js"), import("../direct-room.js")]).then( + ([directManagementModule, directRoomModule]) => ({ + inspectMatrixDirectRooms: directManagementModule.inspectMatrixDirectRooms, + isStrictDirectRoom: directRoomModule.isStrictDirectRoom, + }), + ), +); function trimMaybeString(input: unknown): string | null { if (typeof input !== "string") { diff --git a/extensions/matrix/src/matrix/probe.ts b/extensions/matrix/src/matrix/probe.ts index 4666dea3cc32..1a4c2c91b257 100644 --- a/extensions/matrix/src/matrix/probe.ts +++ b/extensions/matrix/src/matrix/probe.ts @@ -1,21 +1,17 @@ // Matrix plugin module implements probe behavior. import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import type { PinnedDispatcherPolicy } from "openclaw/plugin-sdk/ssrf-dispatcher"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; import type { SsrFPolicy } from "../runtime-api.js"; import type { BaseProbeResult } from "../runtime-api.js"; import { isBunRuntime } from "./client/runtime.js"; -type MatrixProbeRuntimeDeps = Pick; - -let matrixProbeRuntimeDepsPromise: Promise | undefined; - -async function loadMatrixProbeRuntimeDeps(): Promise { - matrixProbeRuntimeDepsPromise ??= import("./probe.runtime.js").then((runtimeModule) => ({ +const loadMatrixProbeRuntimeDeps = createLazyRuntimeModule(() => + import("./probe.runtime.js").then((runtimeModule) => ({ createMatrixClient: runtimeModule.createMatrixClient, - })); - return await matrixProbeRuntimeDepsPromise; -} + })), +); export type MatrixProbe = BaseProbeResult & { status?: number | null; diff --git a/extensions/matrix/src/matrix/sdk.ts b/extensions/matrix/src/matrix/sdk.ts index ada1b354e002..67a11666e82a 100644 --- a/extensions/matrix/src/matrix/sdk.ts +++ b/extensions/matrix/src/matrix/sdk.ts @@ -13,6 +13,7 @@ import { import type { Direction } from "matrix-js-sdk/lib/models/event-timeline.js"; import { VerificationMethod } from "matrix-js-sdk/lib/types.js"; import { KeyedAsyncQueue } from "openclaw/plugin-sdk/keyed-async-queue"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import type { PinnedDispatcherPolicy } from "openclaw/plugin-sdk/ssrf-dispatcher"; import { normalizeNullableString, @@ -294,15 +295,13 @@ export type MatrixOwnDeviceDeleteResult = { type MatrixCryptoRuntime = typeof import("./sdk/crypto-runtime.js"); let loadedMatrixCryptoRuntime: MatrixCryptoRuntime | null = null; -let matrixCryptoRuntimePromise: Promise | null = null; -async function loadMatrixCryptoRuntime(): Promise { - matrixCryptoRuntimePromise ??= import("./sdk/crypto-runtime.js").then((runtime) => { +const loadMatrixCryptoRuntime = createLazyRuntimeModule(() => + import("./sdk/crypto-runtime.js").then((runtime) => { loadedMatrixCryptoRuntime = runtime; return runtime; - }); - return await matrixCryptoRuntimePromise; -} + }), +); const normalizeOptionalString = normalizeNullableString; diff --git a/extensions/matrix/src/matrix/sdk/crypto-facade.ts b/extensions/matrix/src/matrix/sdk/crypto-facade.ts index e3f205668ea7..9ab745691be4 100644 --- a/extensions/matrix/src/matrix/sdk/crypto-facade.ts +++ b/extensions/matrix/src/matrix/sdk/crypto-facade.ts @@ -1,4 +1,5 @@ // Matrix plugin module implements crypto facade behavior. +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { ensureMatrixCryptoRuntime } from "../deps.js"; import type { MatrixRecoveryKeyStore } from "./recovery-key-store.js"; import type { EncryptedFile } from "./types.js"; @@ -67,15 +68,18 @@ export type MatrixCryptoFacade = { }; type MatrixCryptoNodeRuntime = typeof import("./crypto-node.runtime.js"); -let matrixCryptoNodeRuntimePromise: Promise | null = null; +const matrixCryptoNodeRuntimeLoader = createLazyRuntimeModule( + () => import("./crypto-node.runtime.js"), +); async function loadMatrixCryptoNodeRuntime(): Promise { // Keep the native crypto package out of the main CLI startup graph. - matrixCryptoNodeRuntimePromise ??= import("./crypto-node.runtime.js").catch((error: unknown) => { - matrixCryptoNodeRuntimePromise = null; + try { + return await matrixCryptoNodeRuntimeLoader(); + } catch (error) { + matrixCryptoNodeRuntimeLoader.clear(); throw error; - }); - return await matrixCryptoNodeRuntimePromise; + } } async function loadMatrixCryptoNodeBindings() { diff --git a/extensions/matrix/src/matrix/send/client.ts b/extensions/matrix/src/matrix/send/client.ts index c6e0df36cbae..2e2d603879c9 100644 --- a/extensions/matrix/src/matrix/send/client.ts +++ b/extensions/matrix/src/matrix/send/client.ts @@ -1,20 +1,11 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Matrix plugin module implements client behavior. import { requireRuntimeConfig } from "openclaw/plugin-sdk/plugin-config-runtime"; import type { CoreConfig } from "../../types.js"; import { resolveMatrixAccountConfig } from "../account-config.js"; import type { MatrixClient } from "../sdk.js"; -type MatrixSendClientRuntime = Pick< - typeof import("../client-bootstrap.js"), - "withResolvedRuntimeMatrixClient" ->; - -let matrixSendClientRuntimePromise: Promise | null = null; - -async function loadMatrixSendClientRuntime(): Promise { - matrixSendClientRuntimePromise ??= import("../client-bootstrap.js"); - return await matrixSendClientRuntimePromise; -} +const loadMatrixSendClientRuntime = createLazyRuntimeModule(() => import("../client-bootstrap.js")); export function resolveMediaMaxBytes( accountId?: string | null, diff --git a/extensions/matrix/src/plugin-entry.runtime.ts b/extensions/matrix/src/plugin-entry.runtime.ts index d6554971bdc3..1051fddf9978 100644 --- a/extensions/matrix/src/plugin-entry.runtime.ts +++ b/extensions/matrix/src/plugin-entry.runtime.ts @@ -1,16 +1,12 @@ // Matrix plugin module implements plugin entry behavior. import type { GatewayRequestHandlerOptions } from "openclaw/plugin-sdk/gateway-runtime"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; import { formatMatrixErrorMessage } from "./matrix/errors.js"; -type MatrixVerificationRuntime = typeof import("./matrix/actions/verification.js"); - -let matrixVerificationRuntimePromise: Promise | undefined; - -function loadMatrixVerificationRuntime(): Promise { - matrixVerificationRuntimePromise ??= import("./matrix/actions/verification.js"); - return matrixVerificationRuntimePromise; -} +const loadMatrixVerificationRuntime = createLazyRuntimeModule( + () => import("./matrix/actions/verification.js"), +); function sendError(respond: (ok: boolean, payload?: unknown) => void, err: unknown) { respond(false, { error: formatMatrixErrorMessage(err) }); diff --git a/extensions/matrix/subagent-hooks-api.ts b/extensions/matrix/subagent-hooks-api.ts index ac19539d403d..dffff0f49abc 100644 --- a/extensions/matrix/subagent-hooks-api.ts +++ b/extensions/matrix/subagent-hooks-api.ts @@ -1,14 +1,10 @@ // Matrix API module exposes the plugin public contract. import type { OpenClawPluginApi } from "openclaw/plugin-sdk/channel-entry-contract"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; -type MatrixSubagentHooksModule = typeof import("./src/matrix/subagent-hooks.js"); - -let matrixSubagentHooksPromise: Promise | null = null; - -function loadMatrixSubagentHooksModule() { - matrixSubagentHooksPromise ??= import("./src/matrix/subagent-hooks.js"); - return matrixSubagentHooksPromise; -} +const loadMatrixSubagentHooksModule = createLazyRuntimeModule( + () => import("./src/matrix/subagent-hooks.js"), +); export function registerMatrixSubagentHooks(api: OpenClawPluginApi): void { api.on("subagent_ended", async (event) => { diff --git a/extensions/msteams/src/sdk-proactive.ts b/extensions/msteams/src/sdk-proactive.ts index df70e1d3158c..7560c0622d8a 100644 --- a/extensions/msteams/src/sdk-proactive.ts +++ b/extensions/msteams/src/sdk-proactive.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Msteams plugin module implements sdk proactive behavior. import { normalizeBotFrameworkServiceUrl } from "./bot-framework-service-url.js"; import { @@ -69,12 +70,9 @@ type MSTeamsProactiveOptions = { serviceUrlBoundary?: MSTeamsSdkCloudOptions; }; -let apiModulePromise: Promise | null = null; - -async function loadMSTeamsApiModule(): Promise { - apiModulePromise ??= import("@microsoft/teams.api") as unknown as Promise; - return apiModulePromise; -} +const loadMSTeamsApiModule = createLazyRuntimeModule( + () => import("@microsoft/teams.api") as unknown as Promise, +); function resolveThreadedConversationId(conversationId: string, threadActivityId?: string): string { if (!threadActivityId) { diff --git a/extensions/msteams/src/sdk.ts b/extensions/msteams/src/sdk.ts index 7bd7bd2353ce..977d541bcc5a 100644 --- a/extensions/msteams/src/sdk.ts +++ b/extensions/msteams/src/sdk.ts @@ -1,5 +1,6 @@ // Msteams plugin module implements sdk behavior. import * as fs from "node:fs"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { normalizeBotFrameworkServiceUrl } from "./bot-framework-service-url.js"; import type { MSTeamsCloudName } from "./cloud.js"; import type { MSTeamsCredentials, MSTeamsFederatedCredentials } from "./token.js"; @@ -199,31 +200,24 @@ type AzureIdentityModule = { const AZURE_IDENTITY_MODULE = "@azure/identity"; -let azureIdentityModulePromise: Promise | null = null; +const loadAzureIdentity = createLazyRuntimeModule( + () => import(AZURE_IDENTITY_MODULE) as Promise, +); -async function loadAzureIdentity(): Promise { - azureIdentityModulePromise ??= import(AZURE_IDENTITY_MODULE) as Promise; - return azureIdentityModulePromise; -} - -let sdkAppPromise: Promise | null = null; - -async function loadSdkModules(): Promise { - sdkAppPromise ??= Promise.all([ - import("@microsoft/teams.apps"), - import("@microsoft/teams.api"), - ]).then(([apps, api]) => ({ - App: apps.App, - // ExpressAdapter is in the runtime barrel but its type is hidden behind - // the SDK's chained `export *` (see MSTeamsHttpServerAdapter comment). - // Cast to the structural constructor we model locally so the seam stays - // typed without depending on the SDK's namespace shape. - ExpressAdapter: (apps as unknown as { ExpressAdapter: MSTeamsExpressAdapterCtor }) - .ExpressAdapter, - cloudFromName: (api as unknown as { cloudFromName: (name: string) => unknown }).cloudFromName, - })); - return sdkAppPromise; -} +const loadSdkModules = createLazyRuntimeModule(() => + Promise.all([import("@microsoft/teams.apps"), import("@microsoft/teams.api")]).then( + ([apps, api]) => ({ + App: apps.App, + // ExpressAdapter is in the runtime barrel but its type is hidden behind + // the SDK's chained `export *` (see MSTeamsHttpServerAdapter comment). + // Cast to the structural constructor we model locally so the seam stays + // typed without depending on the SDK's namespace shape. + ExpressAdapter: (apps as unknown as { ExpressAdapter: MSTeamsExpressAdapterCtor }) + .ExpressAdapter, + cloudFromName: (api as unknown as { cloudFromName: (name: string) => unknown }).cloudFromName, + }), + ), +); /** * Lazily construct an ExpressAdapter that the Teams SDK App can register its diff --git a/extensions/qqbot/src/bridge/bootstrap.ts b/extensions/qqbot/src/bridge/bootstrap.ts index 7421ff978c1b..ee72e56ab53c 100644 --- a/extensions/qqbot/src/bridge/bootstrap.ts +++ b/extensions/qqbot/src/bridge/bootstrap.ts @@ -23,6 +23,7 @@ * vitest (which resolves bare specifiers via `resolve.alias`, not Node CJS). */ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { hasConfiguredSecretInput, normalizeResolvedSecretInputString, @@ -38,13 +39,9 @@ import { import type { FetchMediaOptions, FetchMediaResult } from "../engine/adapter/types.js"; import { getBridgeLogger } from "./logger.js"; -let mediaRuntimeModulePromise: Promise | null = - null; - -const loadMediaRuntimeModule = async () => { - mediaRuntimeModulePromise ??= import("openclaw/plugin-sdk/media-runtime"); - return await mediaRuntimeModulePromise; -}; +const loadMediaRuntimeModule = createLazyRuntimeModule( + () => import("openclaw/plugin-sdk/media-runtime"), +); function createBuiltinAdapter(): PlatformAdapter { return { diff --git a/extensions/qqbot/src/channel.ts b/extensions/qqbot/src/channel.ts index f35302228aa4..287a4cb7b810 100644 --- a/extensions/qqbot/src/channel.ts +++ b/extensions/qqbot/src/channel.ts @@ -8,9 +8,10 @@ import { } from "openclaw/plugin-sdk/channel-outbound"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import type { ChannelPlugin } from "openclaw/plugin-sdk/core"; -import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Register the PlatformAdapter before any core/ module is used. import "./bridge/bootstrap.js"; +import { sanitizeAssistantVisibleText } from "openclaw/plugin-sdk/text-chunking"; import { getQQBotApprovalCapability } from "./bridge/approval/capability.js"; import { qqbotConfigAdapter, qqbotMeta, qqbotSetupAdapterShared } from "./bridge/config-shared.js"; import { @@ -34,21 +35,10 @@ import { import { resolveQQBotGroupToolPolicy } from "./group-policy.js"; import type { ResolvedQQBotAccount } from "./types.js"; -// Shared promise so concurrent multi-account startups serialize the dynamic -// import of the gateway module, avoiding an ESM circular-dependency race. -let gatewayModulePromise: Promise | undefined; -function loadGatewayModule(): Promise { - gatewayModulePromise ??= import("./bridge/gateway.js"); - return gatewayModulePromise; -} - -let outboundMessagingModulePromise: - | Promise - | undefined; -function loadOutboundMessagingModule(): Promise { - outboundMessagingModulePromise ??= import("./engine/messaging/outbound.js"); - return outboundMessagingModulePromise; -} +const loadGatewayModule = createLazyRuntimeModule(() => import("./bridge/gateway.js")); +const loadOutboundMessagingModule = createLazyRuntimeModule( + () => import("./engine/messaging/outbound.js"), +); function createQQBotSendReceipt(params: { messageId?: string; diff --git a/extensions/signal/src/approval-reactions.ts b/extensions/signal/src/approval-reactions.ts index 92a6bc423ddc..d4e4262fa6ff 100644 --- a/extensions/signal/src/approval-reactions.ts +++ b/extensions/signal/src/approval-reactions.ts @@ -13,6 +13,7 @@ import { type ExecApprovalReplyDecision, } from "openclaw/plugin-sdk/approval-reply-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import type { ReplyPayload } from "openclaw/plugin-sdk/reply-runtime"; import { normalizeAccountId } from "openclaw/plugin-sdk/routing"; import { @@ -75,7 +76,7 @@ type SignalApprovalDeliveryResult = { meta?: Record; }; -let resolverRuntimePromise: Promise | undefined; +const resolverRuntimeLoader = createLazyRuntimeModule(() => import("./approval-resolver.js")); const signalApprovalReactionTargets = createApprovalReactionTargetStore({ @@ -87,10 +88,7 @@ const signalApprovalReactionTargets = readPersistedTarget, }); -function loadApprovalResolver(): Promise { - resolverRuntimePromise ??= import("./approval-resolver.js"); - return resolverRuntimePromise; -} +const loadApprovalResolver = resolverRuntimeLoader; function resolveApprovalKindFromId(approvalId: string): ApprovalKind { return approvalId.startsWith("plugin:") ? "plugin" : "exec"; @@ -756,5 +754,5 @@ export async function maybeResolveSignalApprovalReaction(params: { export function clearSignalApprovalReactionTargetsForTest(): void { signalApprovalReactionTargets.clearForTest(); - resolverRuntimePromise = undefined; + resolverRuntimeLoader.clear(); } diff --git a/extensions/signal/src/channel.ts b/extensions/signal/src/channel.ts index 77252ffc6ad4..b69baf9b43f0 100644 --- a/extensions/signal/src/channel.ts +++ b/extensions/signal/src/channel.ts @@ -11,6 +11,7 @@ import { attachChannelToResults, } from "openclaw/plugin-sdk/channel-send-result"; import { PAIRING_APPROVED_MESSAGE } from "openclaw/plugin-sdk/channel-status"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { resolveMarkdownTableMode } from "openclaw/plugin-sdk/markdown-table-runtime"; import { resolveChannelMediaMaxBytes } from "openclaw/plugin-sdk/media-runtime"; import { chunkText, resolveTextChunkLimit } from "openclaw/plugin-sdk/reply-chunking"; @@ -40,34 +41,19 @@ import { signalSecurityAdapter, signalSetupWizard, } from "./shared.js"; + type SignalSendFn = typeof import("./send.runtime.js").sendMessageSignal; type SignalProbe = import("./probe.js").SignalProbe; -type SignalApprovalReactionsModule = typeof import("./approval-reactions.js"); -let signalMonitorModulePromise: Promise | null = null; -let signalProbeModulePromise: Promise | null = null; -let signalSendRuntimePromise: Promise | null = null; -let signalApprovalReactionsModulePromise: Promise | null = null; +const loadSignalMonitorModule = createLazyRuntimeModule(() => import("./monitor.js")); -async function loadSignalMonitorModule() { - signalMonitorModulePromise ??= import("./monitor.js"); - return await signalMonitorModulePromise; -} +const loadSignalProbeModule = createLazyRuntimeModule(() => import("./probe.js")); -async function loadSignalProbeModule() { - signalProbeModulePromise ??= import("./probe.js"); - return await signalProbeModulePromise; -} +const loadSignalSendRuntime = createLazyRuntimeModule(() => import("./send.runtime.js")); -async function loadSignalSendRuntime() { - signalSendRuntimePromise ??= import("./send.runtime.js"); - return await signalSendRuntimePromise; -} - -async function loadSignalApprovalReactionsModule() { - signalApprovalReactionsModulePromise ??= import("./approval-reactions.js"); - return await signalApprovalReactionsModulePromise; -} +const loadSignalApprovalReactionsModule = createLazyRuntimeModule( + () => import("./approval-reactions.js"), +); async function resolveSignalSendContext(params: { cfg: Parameters[0]["cfg"]; diff --git a/extensions/voice-call/src/runtime.ts b/extensions/voice-call/src/runtime.ts index 7f4ddbbc7196..249f7594ad75 100644 --- a/extensions/voice-call/src/runtime.ts +++ b/extensions/voice-call/src/runtime.ts @@ -2,6 +2,7 @@ import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import { formatErrorMessage } from "openclaw/plugin-sdk/error-runtime"; import { isLoopbackHost } from "openclaw/plugin-sdk/gateway-runtime"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { consultRealtimeVoiceAgent, REALTIME_VOICE_AGENT_CONSULT_TOOL_NAME, @@ -57,13 +58,6 @@ type Logger = { type ResolvedRealtimeProvider = ResolvedRealtimeVoiceProvider; -type TelnyxProviderModule = typeof import("./providers/telnyx.js"); -type TwilioProviderModule = typeof import("./providers/twilio.js"); -type PlivoProviderModule = typeof import("./providers/plivo.js"); -type MockProviderModule = typeof import("./providers/mock.js"); -type RealtimeVoiceRuntimeModule = typeof import("./realtime-voice.runtime.js"); -type RealtimeHandlerModule = typeof import("./webhook/realtime-handler.js"); - const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [ "You are the configured OpenClaw agent receiving delegated requests from a live phone voice bridge.", "Act on behalf of the caller using the normal available tools when the caller asks you to do work.", @@ -73,42 +67,19 @@ const REALTIME_VOICE_CONSULT_SYSTEM_PROMPT = [ "Be accurate, brief, and speakable.", ].join(" "); -let telnyxProviderPromise: Promise | undefined; -let twilioProviderPromise: Promise | undefined; -let plivoProviderPromise: Promise | undefined; -let mockProviderPromise: Promise | undefined; -let realtimeVoiceRuntimePromise: Promise | undefined; -let realtimeHandlerPromise: Promise | undefined; +const loadTelnyxProvider = createLazyRuntimeModule(() => import("./providers/telnyx.js")); -function loadTelnyxProvider(): Promise { - telnyxProviderPromise ??= import("./providers/telnyx.js"); - return telnyxProviderPromise; -} +const loadTwilioProvider = createLazyRuntimeModule(() => import("./providers/twilio.js")); -function loadTwilioProvider(): Promise { - twilioProviderPromise ??= import("./providers/twilio.js"); - return twilioProviderPromise; -} +const loadPlivoProvider = createLazyRuntimeModule(() => import("./providers/plivo.js")); -function loadPlivoProvider(): Promise { - plivoProviderPromise ??= import("./providers/plivo.js"); - return plivoProviderPromise; -} +const loadMockProvider = createLazyRuntimeModule(() => import("./providers/mock.js")); -function loadMockProvider(): Promise { - mockProviderPromise ??= import("./providers/mock.js"); - return mockProviderPromise; -} +const loadRealtimeVoiceRuntime = createLazyRuntimeModule( + () => import("./realtime-voice.runtime.js"), +); -function loadRealtimeVoiceRuntime(): Promise { - realtimeVoiceRuntimePromise ??= import("./realtime-voice.runtime.js"); - return realtimeVoiceRuntimePromise; -} - -function loadRealtimeHandler(): Promise { - realtimeHandlerPromise ??= import("./webhook/realtime-handler.js"); - return realtimeHandlerPromise; -} +const loadRealtimeHandler = createLazyRuntimeModule(() => import("./webhook/realtime-handler.js")); function resolveVoiceCallConsultSessionKey(call: { config: VoiceCallConfig; diff --git a/extensions/voice-call/src/webhook.ts b/extensions/voice-call/src/webhook.ts index 461a5ec3dc59..2291c85589c6 100644 --- a/extensions/voice-call/src/webhook.ts +++ b/extensions/voice-call/src/webhook.ts @@ -2,6 +2,7 @@ import http from "node:http"; import { URL } from "node:url"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { asDateTimestampMs, resolveExpiresAtMsFromDurationMs, @@ -46,9 +47,6 @@ const WEBHOOK_BODY_TIMEOUT_MS = WEBHOOK_BODY_READ_DEFAULTS.preAuth.timeoutMs; const MISSING_REMOTE_ADDRESS_IN_FLIGHT_KEY = "__voice_call_no_remote__"; const STREAM_DISCONNECT_HANGUP_GRACE_MS = 2000; const TRANSCRIPT_LOG_MAX_CHARS = 200; - -type RealtimeTranscriptionRuntime = typeof import("./realtime-transcription.runtime.js"); -type ResponseGeneratorModule = typeof import("./response-generator.js"); type Logger = { info: (message: string) => void; warn: (message: string) => void; @@ -56,18 +54,13 @@ type Logger = { debug?: (message: string) => void; }; -let realtimeTranscriptionRuntimePromise: Promise | undefined; -let responseGeneratorModulePromise: Promise | undefined; +const loadRealtimeTranscriptionRuntime = createLazyRuntimeModule( + () => import("./realtime-transcription.runtime.js"), +); -function loadRealtimeTranscriptionRuntime(): Promise { - realtimeTranscriptionRuntimePromise ??= import("./realtime-transcription.runtime.js"); - return realtimeTranscriptionRuntimePromise; -} - -function loadResponseGeneratorModule(): Promise { - responseGeneratorModulePromise ??= import("./response-generator.js"); - return responseGeneratorModulePromise; -} +const loadResponseGeneratorModule = createLazyRuntimeModule( + () => import("./response-generator.js"), +); type WebhookHeaderGateResult = | { ok: true } diff --git a/extensions/whatsapp/login-qr-runtime.ts b/extensions/whatsapp/login-qr-runtime.ts index 666c86d42fbf..d512ffef16a8 100644 --- a/extensions/whatsapp/login-qr-runtime.ts +++ b/extensions/whatsapp/login-qr-runtime.ts @@ -1,13 +1,9 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Whatsapp plugin module implements login qr runtime behavior. type StartWebLoginWithQr = typeof import("./src/login-qr.js").startWebLoginWithQr; type WaitForWebLogin = typeof import("./src/login-qr.js").waitForWebLogin; -let loginQrModulePromise: Promise | null = null; - -function loadLoginQrModule() { - loginQrModulePromise ??= import("./src/login-qr.js"); - return loginQrModulePromise; -} +const loadLoginQrModule = createLazyRuntimeModule(() => import("./src/login-qr.js")); export async function startWebLoginWithQr( ...args: Parameters diff --git a/extensions/whatsapp/src/approval-reactions.ts b/extensions/whatsapp/src/approval-reactions.ts index 324de6e6269c..9545850448c6 100644 --- a/extensions/whatsapp/src/approval-reactions.ts +++ b/extensions/whatsapp/src/approval-reactions.ts @@ -9,6 +9,7 @@ import { } from "openclaw/plugin-sdk/approval-reaction-runtime"; import type { ExecApprovalReplyDecision } from "openclaw/plugin-sdk/approval-reply-runtime"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { getWhatsAppApprovalApprovers, whatsappApprovalAuth } from "./approval-auth.js"; import { getOptionalWhatsAppRuntime } from "./runtime.js"; @@ -36,7 +37,7 @@ type ResolvedWhatsAppApprovalReactionTarget = WhatsAppApprovalReactionResolution remoteJid: string; }; -let resolverRuntimePromise: Promise | undefined; +const resolverRuntimeLoader = createLazyRuntimeModule(() => import("./approval-resolver.js")); const whatsappApprovalReactionTargets = createApprovalReactionTargetStore({ @@ -48,10 +49,7 @@ const whatsappApprovalReactionTargets = readPersistedTarget, }); -function loadApprovalResolver(): Promise { - resolverRuntimePromise ??= import("./approval-resolver.js"); - return resolverRuntimePromise; -} +const loadApprovalResolver = resolverRuntimeLoader; function buildReactionTargetKey(params: { accountId: string; @@ -398,5 +396,5 @@ export async function maybeResolveWhatsAppApprovalReaction(params: { export function clearWhatsAppApprovalReactionTargetsForTest(): void { whatsappApprovalReactionTargets.clearForTest(); - resolverRuntimePromise = undefined; + resolverRuntimeLoader.clear(); } diff --git a/extensions/whatsapp/src/auto-reply/monitor.ts b/extensions/whatsapp/src/auto-reply/monitor.ts index 13f8faf7ef4f..2aaf79a4add7 100644 --- a/extensions/whatsapp/src/auto-reply/monitor.ts +++ b/extensions/whatsapp/src/auto-reply/monitor.ts @@ -7,6 +7,7 @@ import { registerChannelRuntimeContext } from "openclaw/plugin-sdk/channel-runti import { formatCliCommand } from "openclaw/plugin-sdk/cli-runtime"; import { isControlCommandMessage } from "openclaw/plugin-sdk/command-detection"; import { drainPendingDeliveries } from "openclaw/plugin-sdk/delivery-queue-runtime"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { DEFAULT_GROUP_HISTORY_LIMIT } from "openclaw/plugin-sdk/reply-history"; import { resolveAgentRoute } from "openclaw/plugin-sdk/routing"; import { logVerbose } from "openclaw/plugin-sdk/runtime-env"; @@ -65,13 +66,9 @@ function isNonRetryableWebCloseStatus(statusCode: unknown): boolean { type ReplyResolver = typeof import("./reply-resolver.runtime.js").getReplyFromConfig; type WhatsAppRuntimeConfig = ReturnType; -let replyResolverRuntimePromise: Promise | null = - null; - -function loadReplyResolverRuntime() { - replyResolverRuntimePromise ??= import("./reply-resolver.runtime.js"); - return replyResolverRuntimePromise; -} +const loadReplyResolverRuntime = createLazyRuntimeModule( + () => import("./reply-resolver.runtime.js"), +); function resolveWebMonitorConfigSnapshot(params: { cfg: WhatsAppRuntimeConfig; diff --git a/extensions/whatsapp/src/outbound-adapter.ts b/extensions/whatsapp/src/outbound-adapter.ts index 5a6779da8d2f..a0945c493f69 100644 --- a/extensions/whatsapp/src/outbound-adapter.ts +++ b/extensions/whatsapp/src/outbound-adapter.ts @@ -1,19 +1,13 @@ // Whatsapp plugin module implements outbound adapter behavior. import type { ChannelOutboundAdapter } from "openclaw/plugin-sdk/channel-send-result"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { chunkText } from "openclaw/plugin-sdk/reply-chunking"; import { shouldLogVerbose } from "openclaw/plugin-sdk/runtime-env"; import { createWhatsAppOutboundBase } from "./outbound-base.js"; import { normalizeWhatsAppPayloadText } from "./outbound-media-contract.js"; import { resolveWhatsAppOutboundTarget } from "./resolve-outbound-target.js"; -type WhatsAppSendModule = typeof import("./send.js"); - -let whatsAppSendModulePromise: Promise | undefined; - -function loadWhatsAppSendModule(): Promise { - whatsAppSendModulePromise ??= import("./send.js"); - return whatsAppSendModulePromise; -} +const loadWhatsAppSendModule = createLazyRuntimeModule(() => import("./send.js")); function normalizeOutboundText(text: string | undefined): string { return normalizeWhatsAppPayloadText(text); diff --git a/extensions/whatsapp/src/runtime-api.ts b/extensions/whatsapp/src/runtime-api.ts index f4fd1fc0e350..5b68cf86e313 100644 --- a/extensions/whatsapp/src/runtime-api.ts +++ b/extensions/whatsapp/src/runtime-api.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Whatsapp API module exposes the plugin public contract. export { getChatChannelMeta, type ChannelPlugin } from "openclaw/plugin-sdk/core"; export { buildChannelConfigSchema, WhatsAppConfigSchema } from "../config-api.js"; @@ -45,12 +46,7 @@ export type { WhatsAppAccountConfig } from "./account-types.js"; type MonitorWebChannel = typeof import("./channel.runtime.js").monitorWebChannel; -let channelRuntimePromise: Promise | null = null; - -function loadChannelRuntime() { - channelRuntimePromise ??= import("./channel.runtime.js"); - return channelRuntimePromise; -} +const loadChannelRuntime = createLazyRuntimeModule(() => import("./channel.runtime.js")); export async function monitorWebChannel( ...args: Parameters diff --git a/extensions/zalo/src/monitor.ts b/extensions/zalo/src/monitor.ts index 6d5c05012a2d..27995da8cd50 100644 --- a/extensions/zalo/src/monitor.ts +++ b/extensions/zalo/src/monitor.ts @@ -5,6 +5,7 @@ import { resolveStableChannelMessageIngress } from "openclaw/plugin-sdk/channel- import { createChannelPairingController } from "openclaw/plugin-sdk/channel-pairing"; import type { MarkdownTableMode, OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; import { resolveInboundRouteEnvelopeBuilderWithRuntime } from "openclaw/plugin-sdk/inbound-envelope"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { resolveSendableOutboundReplyParts } from "openclaw/plugin-sdk/reply-payload"; import { deliverTextOrMediaReply, @@ -34,6 +35,7 @@ import { import { normalizeZaloAllowEntry, resolveZaloRuntimeGroupPolicy } from "./group-access.js"; import { resolveZaloProxyFetch } from "./proxy.js"; import { getZaloRuntime } from "./runtime.js"; + export type { ZaloRuntimeEnv } from "./monitor.types.js"; import { prepareZaloDurableReplyPayload, @@ -68,7 +70,6 @@ const UNIX_MILLISECONDS_THRESHOLD = 1_000_000_000_000; type ZaloCoreRuntime = ReturnType; type ZaloStatusSink = (patch: { lastInboundAt?: number; lastOutboundAt?: number }) => void; -type ZaloWebhookModule = typeof import("./monitor.webhook.js"); type ZaloProcessingContext = { token: string; account: ResolvedZaloAccount; @@ -90,8 +91,6 @@ type ZaloPollingLoopParams = ZaloProcessingContext & { type ZaloUpdateProcessingParams = ZaloProcessingContext & { update: ZaloUpdate; }; - -let zaloWebhookModulePromise: Promise | undefined; const hostedMediaRouteRefs = new Map void> }>(); function resolveZaloTimestampMs(date: number | undefined): number | undefined { @@ -101,10 +100,7 @@ function resolveZaloTimestampMs(date: number | undefined): number | undefined { return date >= UNIX_MILLISECONDS_THRESHOLD ? date : date * 1000; } -function loadZaloWebhookModule(): Promise { - zaloWebhookModulePromise ??= import("./monitor.webhook.js"); - return zaloWebhookModulePromise; -} +const loadZaloWebhookModule = createLazyRuntimeModule(() => import("./monitor.webhook.js")); function releaseSharedHostedMediaRouteRef(routePath: string): void { const current = hostedMediaRouteRefs.get(routePath); diff --git a/extensions/zalo/src/test-support/monitor-mocks-test-support.ts b/extensions/zalo/src/test-support/monitor-mocks-test-support.ts index 5fb0aeaaca37..d0d3778f9b62 100644 --- a/extensions/zalo/src/test-support/monitor-mocks-test-support.ts +++ b/extensions/zalo/src/test-support/monitor-mocks-test-support.ts @@ -1,5 +1,6 @@ // Zalo plugin module implements monitor mocks test support behavior. import { createPluginRuntimeMock } from "openclaw/plugin-sdk/channel-test-helpers"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { createEmptyPluginRegistry, createRuntimeEnv, @@ -23,7 +24,6 @@ type UnknownMock = Mock<(...args: unknown[]) => unknown>; type AsyncUnknownMock = Mock<(...args: unknown[]) => Promise>; const loadedMonitorModules = new Set(); const cachedMonitorModules = new Map>(); -let cachedWebhookModule: Promise | undefined; type ZaloLifecycleMocks = { setWebhookMock: AsyncUnknownMock; @@ -102,10 +102,9 @@ async function importSecretInputModule(cacheBust: string): Promise { - cachedWebhookModule ??= import(webhookModuleUrl) as Promise; - return await cachedWebhookModule; -} +const importCachedWebhookModule = createLazyRuntimeModule( + () => import(webhookModuleUrl) as Promise, +); export async function resetLifecycleTestState() { vi.clearAllMocks(); diff --git a/extensions/zalouser/src/accounts.ts b/extensions/zalouser/src/accounts.ts index fe1338504d11..dc7057e4cf44 100644 --- a/extensions/zalouser/src/accounts.ts +++ b/extensions/zalouser/src/accounts.ts @@ -6,15 +6,11 @@ import { resolveMergedAccountConfig, } from "openclaw/plugin-sdk/account-resolution"; import type { OpenClawConfig } from "openclaw/plugin-sdk/config-contracts"; +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; import { normalizeOptionalString } from "openclaw/plugin-sdk/string-coerce-runtime"; import type { ResolvedZalouserAccount, ZalouserAccountConfig, ZalouserConfig } from "./types.js"; -let zalouserAccountsRuntimePromise: Promise | undefined; - -async function loadZalouserAccountsRuntime() { - zalouserAccountsRuntimePromise ??= import("./accounts.runtime.js"); - return await zalouserAccountsRuntimePromise; -} +const loadZalouserAccountsRuntime = createLazyRuntimeModule(() => import("./accounts.runtime.js")); const { listAccountIds: listZalouserAccountIds, diff --git a/extensions/zalouser/src/zca-client.ts b/extensions/zalouser/src/zca-client.ts index 989fd0f8f103..46860852407f 100644 --- a/extensions/zalouser/src/zca-client.ts +++ b/extensions/zalouser/src/zca-client.ts @@ -1,3 +1,4 @@ +import { createLazyRuntimeModule } from "openclaw/plugin-sdk/lazy-runtime"; // Zalouser plugin module implements zca client behavior. import { LoginQRCallbackEventType, @@ -10,14 +11,12 @@ import { type ZcaJsRuntime = { Zalo: unknown; }; -let zcaJsRuntimePromise: Promise | null = null; -async function loadZcaJsRuntime(): Promise { - // Keep zca-js behind a runtime boundary so bundled metadata/contracts can load - // without resolving its optional WebSocket dependency tree. - zcaJsRuntimePromise ??= import("zca-js").then((mod) => mod as unknown as ZcaJsRuntime); - return await zcaJsRuntimePromise; -} +// Keep zca-js behind a runtime boundary so bundled metadata/contracts can load +// without resolving its optional WebSocket dependency tree. +const loadZcaJsRuntime = createLazyRuntimeModule(() => + import("zca-js").then((mod) => mod as unknown as ZcaJsRuntime), +); export { LoginQRCallbackEventType, Reactions, TextStyle, ThreadType }; export type { Style };